From 0611085176ed5b43950da69663c69b0e07f58ba9 Mon Sep 17 00:00:00 2001 From: DennisWG <13482820-DennisWG@users.noreply.gitlab.com> Date: Thu, 2 Jan 2025 17:42:59 +0000 Subject: [PATCH 1/4] AreaTrigger Tool --- CMakeLists.txt | 4 +- .../AreatriggerDescriptions.csv | 1220 ++ resources/noggit_font.ttf | Bin 29504 -> 32644 bytes src/external/rapidfuzz-cpp/.clang-format | 28 + src/external/rapidfuzz-cpp/.gitattributes | 1 + .../rapidfuzz-cpp/.github/FUNDING.yml | 2 + .../rapidfuzz-cpp/.github/RapidFuzz.svg | 195 + .../rapidfuzz-cpp/.github/workflows/cmake.yml | 202 + .../.github/workflows/documentation.yml | 18 + src/external/rapidfuzz-cpp/.gitignore | 16 + src/external/rapidfuzz-cpp/CHANGELOG.md | 224 + src/external/rapidfuzz-cpp/CMakeLists.txt | 143 + src/external/rapidfuzz-cpp/Doxyfile | 105 + src/external/rapidfuzz-cpp/LICENSE | 21 + src/external/rapidfuzz-cpp/README.md | 242 + src/external/rapidfuzz-cpp/SECURITY.md | 19 + .../rapidfuzz-cpp/bench/CMakeLists.txt | 25 + .../rapidfuzz-cpp/bench/bench-fuzz.cpp | 225 + .../rapidfuzz-cpp/bench/bench-jarowinkler.cpp | 196 + .../rapidfuzz-cpp/bench/bench-lcs.cpp | 181 + .../rapidfuzz-cpp/bench/bench-levenshtein.cpp | 224 + .../cmake/rapidfuzzConfig.cmake.in | 9 + .../docs/literature/hyrro_2002.bib | 7 + .../docs/literature/hyrro_2004.bib | 8 + .../docs/literature/hyrro_lcs_2004.bib | 8 + .../docs/literature/myers_1999.bib | 22 + .../docs/literature/wagner_fischer_1974.bib | 20 + .../examples/cmake_export/CMakeLists.txt | 39 + .../examples/cmake_export/fooConfig.cmake.in | 10 + .../examples/cmake_export/foo_lib.cc | 7 + .../examples/cmake_export/foo_lib.hpp | 4 + .../cmake_export/indirect_app/CMakeLists.txt | 5 + .../cmake_export/indirect_app/foo_app.cc | 7 + .../examples/cmake_installed/CMakeLists.txt | 6 + .../examples/cmake_installed/main.cpp | 10 + .../extras/rapidfuzz_amalgamated.hpp | 11096 ++++++++++++++++ .../rapidfuzz-cpp/fuzzing/CMakeLists.txt | 24 + .../fuzz_damerau_levenshtein_distance.cpp | 53 + .../fuzzing/fuzz_indel_distance.cpp | 43 + .../fuzzing/fuzz_indel_editops.cpp | 22 + .../fuzzing/fuzz_jaro_similarity.cpp | 99 + .../fuzzing/fuzz_lcs_similarity.cpp | 69 + .../fuzzing/fuzz_levenshtein_distance.cpp | 99 + .../fuzzing/fuzz_levenshtein_editops.cpp | 44 + .../fuzzing/fuzz_osa_distance.cpp | 53 + .../fuzzing/fuzz_partial_ratio.cpp | 53 + .../rapidfuzz-cpp/fuzzing/fuzzing.hpp | 55 + .../rapidfuzz/details/CharSet.hpp | 74 + .../rapidfuzz/details/GrowingHashmap.hpp | 203 + .../rapidfuzz/details/Matrix.hpp | 199 + .../rapidfuzz/details/PatternMatchVector.hpp | 222 + .../rapidfuzz-cpp/rapidfuzz/details/Range.hpp | 230 + .../details/SplittedSentenceView.hpp | 86 + .../rapidfuzz/details/common.hpp | 101 + .../rapidfuzz/details/common_impl.hpp | 172 + .../rapidfuzz/details/distance.hpp | 548 + .../rapidfuzz/details/intrinsics.hpp | 212 + .../rapidfuzz-cpp/rapidfuzz/details/simd.hpp | 21 + .../rapidfuzz/details/simd_avx2.hpp | 647 + .../rapidfuzz/details/simd_sse2.hpp | 602 + .../rapidfuzz/details/type_traits.hpp | 52 + .../rapidfuzz-cpp/rapidfuzz/details/types.hpp | 596 + .../rapidfuzz-cpp/rapidfuzz/distance.hpp | 161 + .../rapidfuzz/distance/DamerauLevenshtein.hpp | 152 + .../distance/DamerauLevenshtein_impl.hpp | 140 + .../rapidfuzz/distance/Hamming.hpp | 172 + .../rapidfuzz/distance/Hamming_impl.hpp | 60 + .../rapidfuzz/distance/Indel.hpp | 191 + .../rapidfuzz/distance/Indel_impl.hpp | 68 + .../rapidfuzz-cpp/rapidfuzz/distance/Jaro.hpp | 231 + .../rapidfuzz/distance/JaroWinkler.hpp | 210 + .../rapidfuzz/distance/JaroWinkler_impl.hpp | 90 + .../rapidfuzz/distance/Jaro_impl.hpp | 845 ++ .../rapidfuzz/distance/LCSseq.hpp | 235 + .../rapidfuzz/distance/LCSseq_impl.hpp | 529 + .../rapidfuzz/distance/Levenshtein.hpp | 492 + .../rapidfuzz/distance/Levenshtein_impl.hpp | 1220 ++ .../rapidfuzz-cpp/rapidfuzz/distance/OSA.hpp | 281 + .../rapidfuzz/distance/OSA_impl.hpp | 273 + .../rapidfuzz/distance/Postfix.hpp | 105 + .../rapidfuzz/distance/Postfix_impl.hpp | 30 + .../rapidfuzz/distance/Prefix.hpp | 104 + .../rapidfuzz/distance/Prefix_impl.hpp | 30 + src/external/rapidfuzz-cpp/rapidfuzz/fuzz.hpp | 789 ++ .../rapidfuzz-cpp/rapidfuzz/fuzz_impl.hpp | 937 ++ .../rapidfuzz-cpp/rapidfuzz/rapidfuzz_all.hpp | 6 + .../DamerauLevenshtein.hpp | 76 + .../rapidfuzz_reference/Hamming.hpp | 32 + .../rapidfuzz_reference/Indel.hpp | 38 + .../rapidfuzz_reference/Jaro.hpp | 74 + .../rapidfuzz_reference/JaroWinkler.hpp | 35 + .../rapidfuzz_reference/LCSseq.hpp | 25 + .../rapidfuzz_reference/Levenshtein.hpp | 104 + .../rapidfuzz-cpp/rapidfuzz_reference/OSA.hpp | 61 + .../rapidfuzz_reference/README.md | 4 + .../rapidfuzz_reference/common.hpp | 38 + .../rapidfuzz_reference/fuzz.hpp | 63 + .../rapidfuzz-cpp/test/CMakeLists.txt | 65 + src/external/rapidfuzz-cpp/test/common.hpp | 69 + .../test/distance/CMakeLists.txt | 22 + .../test/distance/examples/ocr.cpp | 10185 ++++++++++++++ .../test/distance/examples/ocr.hpp | 6 + .../examples/pythonLevenshteinIssue9.cpp | 426 + .../examples/pythonLevenshteinIssue9.hpp | 8 + .../distance/tests-DamerauLevenshtein.cpp | 129 + .../test/distance/tests-Hamming.cpp | 124 + .../test/distance/tests-Indel.cpp | 283 + .../test/distance/tests-Jaro.cpp | 251 + .../test/distance/tests-JaroWinkler.cpp | 211 + .../test/distance/tests-LCSseq.cpp | 240 + .../test/distance/tests-Levenshtein.cpp | 540 + .../rapidfuzz-cpp/test/distance/tests-OSA.cpp | 92 + .../rapidfuzz-cpp/test/tests-common.cpp | 35 + .../rapidfuzz-cpp/test/tests-fuzz.cpp | 245 + .../rapidfuzz-cpp/test/tests-main.cpp | 3 + .../rapidfuzz-cpp/tools/amalgamation.py | 107 + src/math/coordinates.hpp | 10 + src/math/frustum.cpp | 8 +- src/math/frustum.hpp | 6 +- src/math/ray.cpp | 110 +- src/math/ray.hpp | 14 + src/math/sphere.cpp | 7 + src/math/sphere.hpp | 14 + src/noggit/Action.cpp | 51 +- src/noggit/Action.hpp | 38 +- src/noggit/DBC.cpp | 2 + src/noggit/DBC.h | 23 + src/noggit/DBCFile.cpp | 35 +- src/noggit/DBCFile.h | 20 +- src/noggit/MapView.cpp | 72 +- src/noggit/MapView.h | 23 +- src/noggit/Tool.cpp | 6 +- src/noggit/Tool.hpp | 8 +- src/noggit/area_trigger.cpp | 155 + src/noggit/area_trigger.hpp | 61 + src/noggit/tool_enums.hpp | 3 +- src/noggit/tools/AreaTriggerTool.cpp | 470 + src/noggit/tools/AreaTriggerTool.hpp | 73 + src/noggit/ui/FontNoggit.hpp | 2 + .../AreaTriggerEditor/AreaTriggerEditor.cpp | 687 + .../AreaTriggerEditor/AreaTriggerEditor.hpp | 106 + .../ui/tools/LightEditor/LightEditor.cpp | 1 + .../ui/windows/noggitWindow/NoggitWindow.cpp | 2 +- .../ui/windows/noggitWindow/NoggitWindow.hpp | 5 + src/util/FlatMap.cpp | 0 src/util/FlatMap.hpp | 57 + 146 files changed, 42061 insertions(+), 78 deletions(-) create mode 100644 dist/noggit-definitions/AreatriggerDescriptions.csv create mode 100644 src/external/rapidfuzz-cpp/.clang-format create mode 100644 src/external/rapidfuzz-cpp/.gitattributes create mode 100644 src/external/rapidfuzz-cpp/.github/FUNDING.yml create mode 100644 src/external/rapidfuzz-cpp/.github/RapidFuzz.svg create mode 100644 src/external/rapidfuzz-cpp/.github/workflows/cmake.yml create mode 100644 src/external/rapidfuzz-cpp/.github/workflows/documentation.yml create mode 100644 src/external/rapidfuzz-cpp/.gitignore create mode 100644 src/external/rapidfuzz-cpp/CHANGELOG.md create mode 100644 src/external/rapidfuzz-cpp/CMakeLists.txt create mode 100644 src/external/rapidfuzz-cpp/Doxyfile create mode 100644 src/external/rapidfuzz-cpp/LICENSE create mode 100644 src/external/rapidfuzz-cpp/README.md create mode 100644 src/external/rapidfuzz-cpp/SECURITY.md create mode 100644 src/external/rapidfuzz-cpp/bench/CMakeLists.txt create mode 100644 src/external/rapidfuzz-cpp/bench/bench-fuzz.cpp create mode 100644 src/external/rapidfuzz-cpp/bench/bench-jarowinkler.cpp create mode 100644 src/external/rapidfuzz-cpp/bench/bench-lcs.cpp create mode 100644 src/external/rapidfuzz-cpp/bench/bench-levenshtein.cpp create mode 100644 src/external/rapidfuzz-cpp/cmake/rapidfuzzConfig.cmake.in create mode 100644 src/external/rapidfuzz-cpp/docs/literature/hyrro_2002.bib create mode 100644 src/external/rapidfuzz-cpp/docs/literature/hyrro_2004.bib create mode 100644 src/external/rapidfuzz-cpp/docs/literature/hyrro_lcs_2004.bib create mode 100644 src/external/rapidfuzz-cpp/docs/literature/myers_1999.bib create mode 100644 src/external/rapidfuzz-cpp/docs/literature/wagner_fischer_1974.bib create mode 100644 src/external/rapidfuzz-cpp/examples/cmake_export/CMakeLists.txt create mode 100644 src/external/rapidfuzz-cpp/examples/cmake_export/fooConfig.cmake.in create mode 100644 src/external/rapidfuzz-cpp/examples/cmake_export/foo_lib.cc create mode 100644 src/external/rapidfuzz-cpp/examples/cmake_export/foo_lib.hpp create mode 100644 src/external/rapidfuzz-cpp/examples/cmake_export/indirect_app/CMakeLists.txt create mode 100644 src/external/rapidfuzz-cpp/examples/cmake_export/indirect_app/foo_app.cc create mode 100644 src/external/rapidfuzz-cpp/examples/cmake_installed/CMakeLists.txt create mode 100644 src/external/rapidfuzz-cpp/examples/cmake_installed/main.cpp create mode 100644 src/external/rapidfuzz-cpp/extras/rapidfuzz_amalgamated.hpp create mode 100644 src/external/rapidfuzz-cpp/fuzzing/CMakeLists.txt create mode 100644 src/external/rapidfuzz-cpp/fuzzing/fuzz_damerau_levenshtein_distance.cpp create mode 100644 src/external/rapidfuzz-cpp/fuzzing/fuzz_indel_distance.cpp create mode 100644 src/external/rapidfuzz-cpp/fuzzing/fuzz_indel_editops.cpp create mode 100644 src/external/rapidfuzz-cpp/fuzzing/fuzz_jaro_similarity.cpp create mode 100644 src/external/rapidfuzz-cpp/fuzzing/fuzz_lcs_similarity.cpp create mode 100644 src/external/rapidfuzz-cpp/fuzzing/fuzz_levenshtein_distance.cpp create mode 100644 src/external/rapidfuzz-cpp/fuzzing/fuzz_levenshtein_editops.cpp create mode 100644 src/external/rapidfuzz-cpp/fuzzing/fuzz_osa_distance.cpp create mode 100644 src/external/rapidfuzz-cpp/fuzzing/fuzz_partial_ratio.cpp create mode 100644 src/external/rapidfuzz-cpp/fuzzing/fuzzing.hpp create mode 100644 src/external/rapidfuzz-cpp/rapidfuzz/details/CharSet.hpp create mode 100644 src/external/rapidfuzz-cpp/rapidfuzz/details/GrowingHashmap.hpp create mode 100644 src/external/rapidfuzz-cpp/rapidfuzz/details/Matrix.hpp create mode 100644 src/external/rapidfuzz-cpp/rapidfuzz/details/PatternMatchVector.hpp create mode 100644 src/external/rapidfuzz-cpp/rapidfuzz/details/Range.hpp create mode 100644 src/external/rapidfuzz-cpp/rapidfuzz/details/SplittedSentenceView.hpp create mode 100644 src/external/rapidfuzz-cpp/rapidfuzz/details/common.hpp create mode 100644 src/external/rapidfuzz-cpp/rapidfuzz/details/common_impl.hpp create mode 100644 src/external/rapidfuzz-cpp/rapidfuzz/details/distance.hpp create mode 100644 src/external/rapidfuzz-cpp/rapidfuzz/details/intrinsics.hpp create mode 100644 src/external/rapidfuzz-cpp/rapidfuzz/details/simd.hpp create mode 100644 src/external/rapidfuzz-cpp/rapidfuzz/details/simd_avx2.hpp create mode 100644 src/external/rapidfuzz-cpp/rapidfuzz/details/simd_sse2.hpp create mode 100644 src/external/rapidfuzz-cpp/rapidfuzz/details/type_traits.hpp create mode 100644 src/external/rapidfuzz-cpp/rapidfuzz/details/types.hpp create mode 100644 src/external/rapidfuzz-cpp/rapidfuzz/distance.hpp create mode 100644 src/external/rapidfuzz-cpp/rapidfuzz/distance/DamerauLevenshtein.hpp create mode 100644 src/external/rapidfuzz-cpp/rapidfuzz/distance/DamerauLevenshtein_impl.hpp create mode 100644 src/external/rapidfuzz-cpp/rapidfuzz/distance/Hamming.hpp create mode 100644 src/external/rapidfuzz-cpp/rapidfuzz/distance/Hamming_impl.hpp create mode 100644 src/external/rapidfuzz-cpp/rapidfuzz/distance/Indel.hpp create mode 100644 src/external/rapidfuzz-cpp/rapidfuzz/distance/Indel_impl.hpp create mode 100644 src/external/rapidfuzz-cpp/rapidfuzz/distance/Jaro.hpp create mode 100644 src/external/rapidfuzz-cpp/rapidfuzz/distance/JaroWinkler.hpp create mode 100644 src/external/rapidfuzz-cpp/rapidfuzz/distance/JaroWinkler_impl.hpp create mode 100644 src/external/rapidfuzz-cpp/rapidfuzz/distance/Jaro_impl.hpp create mode 100644 src/external/rapidfuzz-cpp/rapidfuzz/distance/LCSseq.hpp create mode 100644 src/external/rapidfuzz-cpp/rapidfuzz/distance/LCSseq_impl.hpp create mode 100644 src/external/rapidfuzz-cpp/rapidfuzz/distance/Levenshtein.hpp create mode 100644 src/external/rapidfuzz-cpp/rapidfuzz/distance/Levenshtein_impl.hpp create mode 100644 src/external/rapidfuzz-cpp/rapidfuzz/distance/OSA.hpp create mode 100644 src/external/rapidfuzz-cpp/rapidfuzz/distance/OSA_impl.hpp create mode 100644 src/external/rapidfuzz-cpp/rapidfuzz/distance/Postfix.hpp create mode 100644 src/external/rapidfuzz-cpp/rapidfuzz/distance/Postfix_impl.hpp create mode 100644 src/external/rapidfuzz-cpp/rapidfuzz/distance/Prefix.hpp create mode 100644 src/external/rapidfuzz-cpp/rapidfuzz/distance/Prefix_impl.hpp create mode 100644 src/external/rapidfuzz-cpp/rapidfuzz/fuzz.hpp create mode 100644 src/external/rapidfuzz-cpp/rapidfuzz/fuzz_impl.hpp create mode 100644 src/external/rapidfuzz-cpp/rapidfuzz/rapidfuzz_all.hpp create mode 100644 src/external/rapidfuzz-cpp/rapidfuzz_reference/DamerauLevenshtein.hpp create mode 100644 src/external/rapidfuzz-cpp/rapidfuzz_reference/Hamming.hpp create mode 100644 src/external/rapidfuzz-cpp/rapidfuzz_reference/Indel.hpp create mode 100644 src/external/rapidfuzz-cpp/rapidfuzz_reference/Jaro.hpp create mode 100644 src/external/rapidfuzz-cpp/rapidfuzz_reference/JaroWinkler.hpp create mode 100644 src/external/rapidfuzz-cpp/rapidfuzz_reference/LCSseq.hpp create mode 100644 src/external/rapidfuzz-cpp/rapidfuzz_reference/Levenshtein.hpp create mode 100644 src/external/rapidfuzz-cpp/rapidfuzz_reference/OSA.hpp create mode 100644 src/external/rapidfuzz-cpp/rapidfuzz_reference/README.md create mode 100644 src/external/rapidfuzz-cpp/rapidfuzz_reference/common.hpp create mode 100644 src/external/rapidfuzz-cpp/rapidfuzz_reference/fuzz.hpp create mode 100644 src/external/rapidfuzz-cpp/test/CMakeLists.txt create mode 100644 src/external/rapidfuzz-cpp/test/common.hpp create mode 100644 src/external/rapidfuzz-cpp/test/distance/CMakeLists.txt create mode 100644 src/external/rapidfuzz-cpp/test/distance/examples/ocr.cpp create mode 100644 src/external/rapidfuzz-cpp/test/distance/examples/ocr.hpp create mode 100644 src/external/rapidfuzz-cpp/test/distance/examples/pythonLevenshteinIssue9.cpp create mode 100644 src/external/rapidfuzz-cpp/test/distance/examples/pythonLevenshteinIssue9.hpp create mode 100644 src/external/rapidfuzz-cpp/test/distance/tests-DamerauLevenshtein.cpp create mode 100644 src/external/rapidfuzz-cpp/test/distance/tests-Hamming.cpp create mode 100644 src/external/rapidfuzz-cpp/test/distance/tests-Indel.cpp create mode 100644 src/external/rapidfuzz-cpp/test/distance/tests-Jaro.cpp create mode 100644 src/external/rapidfuzz-cpp/test/distance/tests-JaroWinkler.cpp create mode 100644 src/external/rapidfuzz-cpp/test/distance/tests-LCSseq.cpp create mode 100644 src/external/rapidfuzz-cpp/test/distance/tests-Levenshtein.cpp create mode 100644 src/external/rapidfuzz-cpp/test/distance/tests-OSA.cpp create mode 100644 src/external/rapidfuzz-cpp/test/tests-common.cpp create mode 100644 src/external/rapidfuzz-cpp/test/tests-fuzz.cpp create mode 100644 src/external/rapidfuzz-cpp/test/tests-main.cpp create mode 100644 src/external/rapidfuzz-cpp/tools/amalgamation.py create mode 100644 src/math/sphere.cpp create mode 100644 src/math/sphere.hpp create mode 100644 src/noggit/area_trigger.cpp create mode 100644 src/noggit/area_trigger.hpp create mode 100644 src/noggit/tools/AreaTriggerTool.cpp create mode 100644 src/noggit/tools/AreaTriggerTool.hpp create mode 100644 src/noggit/ui/tools/AreaTriggerEditor/AreaTriggerEditor.cpp create mode 100644 src/noggit/ui/tools/AreaTriggerEditor/AreaTriggerEditor.hpp create mode 100644 src/util/FlatMap.cpp create mode 100644 src/util/FlatMap.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 83e0d451..e6b93b56 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -142,6 +142,7 @@ ADD_SUBDIRECTORY("${EXTERNAL_SOURCE_DIR}/NodeEditor") option(BUILD_LIBNOISE_EXAMPLES "Build libnoise examples" OFF) ADD_SUBDIRECTORY("${EXTERNAL_SOURCE_DIR}/libnoise") ADD_SUBDIRECTORY("${EXTERNAL_SOURCE_DIR}/glm") +ADD_SUBDIRECTORY("${EXTERNAL_SOURCE_DIR}/rapidfuzz-cpp") # Add the found include directories to our include list. INCLUDE_DIRECTORIES(SYSTEM "${CMAKE_SOURCE_DIR}/include/") @@ -360,6 +361,7 @@ TARGET_LINK_LIBRARIES (noggit sol2::sane blizzard-archive-library blizzard-database-library + rapidfuzz::rapidfuzz ) #add distribution themes @@ -519,4 +521,4 @@ if(${FAST_BUILD_NOGGIT_JUMBO}) set_source_files_properties("${CMAKE_CURRENT_SOURCE_DIR}/src/external/imguizmo/ImGuizmo.cpp" PROPERTIES SKIP_UNITY_BUILD_INCLUSION TRUE) set_source_files_properties("${CMAKE_CURRENT_SOURCE_DIR}/src/external/imguizmo/ImSequencer.cpp" PROPERTIES SKIP_UNITY_BUILD_INCLUSION TRUE) set_source_files_properties("${CMAKE_CURRENT_SOURCE_DIR}/src/noggit/rendering/WorldRender.cpp" PROPERTIES SKIP_UNITY_BUILD_INCLUSION TRUE) -endif() \ No newline at end of file +endif() diff --git a/dist/noggit-definitions/AreatriggerDescriptions.csv b/dist/noggit-definitions/AreatriggerDescriptions.csv new file mode 100644 index 00000000..b0c4e213 --- /dev/null +++ b/dist/noggit-definitions/AreatriggerDescriptions.csv @@ -0,0 +1,1220 @@ +ID,Zone Name,Sub Category,Trigger Name,IsBuiltIn, +45,Tirisfal Glades,Portals,To Scarlet Monastery Graveyard,1, +71,Westfall,Inns,Sentinel Hill Inn,1, +78,Westfall,Portals,To Deadmines,1, +84,Eastern Kingdoms,UNKNOWN,UNNAMED,1, +87,Elwynn Forest,Quests,Jasperlode Mine 1,1, +88,Elwynn Forest,Quests,Fargodeep Mine 1,1, +97,Dun Morogh,Quests,Frostmane Hold 1,1, +98,Stranglethorn Vale,Quests,Nesingwary's Expedition,1, +100,Tirisfal Glades,Quests,Night's Web Hollow,1, +101,Elwynn Forest,Portals,To Stockades,1, +107,Elwynn Forest,Portals,To Prison,1, +145,Silverpine Forest,Portals,To Shadowfang Keep,1, +168,Dun Morogh,Quests,Frostmane Hold 2,1, +169,Dun Morogh,Quests,Frostmane Hold 3,1, +171,Loch Modan,Quests,The Loch,1, +173,Silverpine Forest,Quests,The Dead Field,1, +175,Wetlands,Quests,Dun Algaz,1, +178,Alterac Mountains,Quests,Strahnbrad,1, +196,Stranglethorn Vale,Quests,The Savage Coast,1, +197,Elwynn Forest,Quests,Fargodeep Mine 2,1, +284,Stormwind,Quests,Old Town 1,1, +286,Badlands,Portals,To Uldaman,1, +324,Dun Morogh,Portals,To Gnomeregan,1, +342,Elwynn Forest,Quests,Jasperlode Mine 2,1, +362,Swamp of Sorrows,Quests,Pool of Tears,1, +382,Stormwind,Quests,Old Town 2,1, +446,Swamp of Sorrows,Portals,To the Temple of Atal'Hakkar,1, +482,Redridge Mountains,Quests,Rethban Caves,1, +523,Dun Morogh,Portals,To Gnomeregan Backdoor,1, +562,Elwynn Forest,Inns,Lion's Pride Inn,1, +610,Tirisfal Glades,Portals,To Scarlet Monastery - Cathedral,1, +612,Tirisfal Glades,Portals,To Scarlet Monastery - Armory,1, +614,Tirisfal Glades,Portals,To Scarlet Monastery - Library,1, +682,Redridge Mountains,Inns,Lakeshire Inn,1, +702,Stormwind,Portals,Wizard's Sanctum 1,1, +704,Stormwind,Portals,To Wizard's Sanctum 1,1, +707,Duskwood,Inns,Scarlet Raven Tavern,1, +708,Hillbrad Foothills,Inns,Southshore Inn,1, +710,Dun Morogh,Inns,Thunderbrew Distillery,1, +712,Loch Modan,Inns,Stoutlager Inn,1, +713,Wetlands,Inns,Deepwater Tavern,1, +719,Tirisfal Glades,Inns,Brill Inn,1, +720,Silverpine Forest,Inns,The Sepulcher,1, +721,Hillbrad Foothills,Inns,Tarren Mill Inn,1, +802,Wetlands,Quests,Menethil Harbor 1,1, +803,Wetlands,Quests,Menethil Harbor 2,1, +844,Swamp of Sorrows,Inns,Stonard Inn,1, +862,Stranglethorn Vale,Inns,The Salty Sailor Tavern,1, +902,Badlands,Portals,To Uldaman Backdoor,1, +1042,The Hinterlands,Inns,Wildhammer Keep,1, +1103,Stranglethorn Vale,Portals,To Gnomeregan,1, +1104,Dun Morogh,Portals,To Stranglethorn Vale,1, +1125,Stormwind,Quests,Cathedral of Light,1, +1205,The Hinterlands,Quests,The Altar of Zul,1, +1426,Blasted Lands,Rise of the Defiler,West,1, +1427,Blasted Lands,Rise of the Defiler,North,1, +1428,Blasted Lands,Rise of the Defiler,East,1, +1429,Blasted Lands,Rise of the Defiler,South,1, +1446,Blasted Lands,Rise of the Defiler,Hilltop,1, +1466,Blackrock Mountain,Portals,To Blackrock Depths,1, +1468,Blackrock Mountain,Portals,To Blackrock Spire,1, +1506,Burning Steppes,Quests,Dreadmaul Rock,1, +1606,Badlands,Inns,Kargath Inn,1, +1646,Arathi Highlands,Inns,Hammerfall Inn,1, +2146,Eastern Kingdoms,UNKNOWN,UNNAMED,1, +2173,Stormwind,Portals,To Deeprum Tram,1, +2175,Ironforge,Portals,To Deeprum Tram,1, +2214,Eastern Plaguelands,Portals,Stratholme Eastwall Gate,1, +2216,Eastern Plaguelands,Portals,Stratholme Main North East,1, +2217,Eastern Plaguelands,Portals,Stratholme Main South West,1, +2246,Western Plaguelands,Quests,Felstone Field 1,1, +2248,Western Plaguelands,Quests,Dalson's Tears 1,1, +2250,Western Plaguelands,Quests,The Writhing Haunt 1,1, +2252,Western Plaguelands,Quests,Gahrron's Withering 1,1, +2387,Swamp of Sorrows,Quests,Fallow Sanctuary,1, +2412,Alterac Mountains,Portals,To Alterac Valley Alliance,1, +2413,Alterac Mountains,Portals,To Alterac Valley Horde,1, +2567,Western Plaguelands,Portals,To Scholomance,1, +2626,Western Plaguelands,Quests,Felstone Field 2,1, +2627,Western Plaguelands,Quests,Felstone Field 3,1, +2628,Western Plaguelands,Quests,Felstone Field 4,1, +2629,Western Plaguelands,Quests,Dalson's Tears 2,1, +2630,Western Plaguelands,Quests,Dalson's Tears 3,1, +2631,Western Plaguelands,Quests,Dalson's Tears 4,1, +2632,Western Plaguelands,Quests,The Writhing Haunt 2,1, +2633,Western Plaguelands,Quests,The Writhing Haunt 3,1, +2634,Western Plaguelands,Quests,The Writhing Haunt 4,1, +2635,Western Plaguelands,Quests,Gahrron's Withering 2,1, +2636,Western Plaguelands,Quests,Gahrron's Withering 3,1, +2637,Western Plaguelands,Quests,Gahrron's Withering 4,1, +2647,Western Plaguelands,Quests,Hearthglen Tower 1,1, +2706,Eastern Plaguelands,Quests,Terrorweb Tunnel South,1, +2707,Eastern Plaguelands,Quests,Terrorweb Tunnel North,1, +2726,Eastern Plaguelands,Quests,The Marris Stead,1, +2746,Stormwind,UNKNOWN,Trade District,1, +2786,Stormwind,UNKNOWN,Valley of Heroes 1,1, +3066,Alterac Mountains,Quests,Ravenholdt Manor Cave,1, +3106,Alterac Mountains,Quests,Ravenholdt Manor Field,1, +3366,Western Plaguelands,Quests,Hearthglen Tower 2,1, +3367,Western Plaguelands,Quests,Hearthglen Tower 3,1, +3528,Blackrock Mountain,Portals,To Molten Core Window,1, +3529,Blackrock Mountain,Portals,To Molten Core Drop,1, +3530,Stormwind,Quests,Stormwind Keep,1, +3547,Undercity,Quests,Throne Room,1, +3548,Loch Modan,Quests,Stonewrought Dam,1, +3552,Westfall,Quests,Westfall Lighthouse,1, +3606,Blackrock Mountain,Portals,To Molten Core Lava,1, +3690,The Hinterlands,Inns,Raventusk Village Inn,1, +3847,Blackrock Mountain,Portals,To Blackwing Lair,1, +3886,Stranglethorn Vale,Inns,Grom'gol Base Camp Inn,1, +3928,Stranglethorn Vale,Portals,To Zul'Gurub,1, +3953,Arathi Highlands,Portals,To Arathi Basin Alliance,1, +3954,Arathi Highlands,Portals,To Arathi Basin Horde,1, +3991,Tirisfal Glades,Quests,To Silverpine Forest,1, +4017,Duskwood,Quests,Twilight Grove,1, +4026,Dun Morogh,UNKNOWN,Ironforge Gates,1, +4028,Tirisfal Glades,Events,Zepplin Tower,1, +4029,Stranglethorn Vale,Events,Booty Bay,1, +4030,Wetlands,Events,Menethil Harbor 3,1, +4032,Stormwind,Events,Trade District 2,1, +4058,Eastern Plaguelands,Inns,Light's Hope Chapel,1, +4092,Elwynn Forest,UNKNOWN,Elwynn Forest 1,1, +4094,Elwynn Forest,UNKNOWN,Elwynn Forest 2,1, +4095,Elwynn Forest,UNKNOWN,Elwynn Forest 3,1, +4096,Elwynn Forest,UNKNOWN,Elwynn Forest 4,1, +4098,Dun Morogh,UNKNOWN,Dun Morogh 1,1, +4099,Dun Morogh,UNKNOWN,Dun Morogh 2,1, +4100,Tirisfal Glades,UNKNOWN,UNNAMED,1, +4131,Deadwind Pass,Portals,To Karazhan,1, +4135,Deadwind Pass,Portals,To Karazhan Upper,1, +4189,Undercity,Quests,Mage Quarters,1, +4294,Eastern Plaguelands,Quests,Eastwall Tower,1, +4354,Blasted Lands,Portals,To Outland,1, +4409,Eastern Plaguelands,Portals,To Ghostlands,1, +4712,Dun Morogh,UNKNOWN,Dun Morogh 3,1, +4715,Dun Morogh,UNKNOWN,Dun Morogh 4,1, +4716,Dun Morogh,UNKNOWN,Dun Morogh 5,1, +4718,Dun Morogh,UNKNOWN,Dun Morogh 6,1, +4733,Stormwind,Portals,To Wizard's Sanctum 2,1, +4734,Stormwind,Portals,Wizard's Sanctum 2,1, +4769,Ironforge,Quests,The Commons,1, +4770,Ironforge,Quests,Hall of Explorers,1, +4772,Ironforge,Quests,The Military Ward,1, +4774,Ironforge,Quests,The Mystic Ward,1, +4786,Dun Morogh,UNKNOWN,Kharanos 1,1, +4787,Dun Morogh,UNKNOWN,Dun Morogh 7,1, +4788,Dun Morogh,UNKNOWN,Kharanos 2,1, +4789,Dun Morogh,UNKNOWN,Dun Morogh 8,1, +4820,Dun Morogh,UNKNOWN,Dun Morogh 9,1, +4990,Silverpine Forest,Quests,Silverpine Forest,1, +5127,Eastern Plaguelands,UNKNOWN,Death's Breach Bottom West,1, +5128,Eastern Plaguelands,UNKNOWN,Death's Breach Top Upper West,1, +5129,Eastern Plaguelands,UNKNOWN,Death's Breach Bottom South,1, +5130,Eastern Plaguelands,UNKNOWN,Death's Breach Top Upper South,1, +5131,Eastern Plaguelands,UNKNOWN,Death's Breach Bottom North,1, +5132,Eastern Plaguelands,UNKNOWN,Death's Breach Top Upper North,1, +5133,Eastern Plaguelands,UNKNOWN,Death's Breach Bottom East,1, +5134,Eastern Plaguelands,UNKNOWN,Death's Breach Top Upper East,1, +5135,Eastern Plaguelands,UNKNOWN,Death's Breach Top Lower South West,1, +5136,Eastern Plaguelands,UNKNOWN,Death's Breach Top Lower North West,1, +5137,Eastern Plaguelands,UNKNOWN,Death's Breach Top Lower South East,1, +5138,Eastern Plaguelands,UNKNOWN,Death's Breach Top Lower North East,1, +5537,Undercity,Quests,Royal Quarters 1,1, +5538,Undercity,Quests,Royal Quarters 2,1, +5703,Elwynn Forest,UNKNOWN,Elwynn Forest 5,1, +5704,Stormwind,World,Trade District Mailbox,1, +5710,Stormwind,World,Trade District Bank,1, +5711,Stormwind,World,Trade District Auction House,1, +5712,Stormwind,World,Trade District Barber Shop,1, +5807,Dun Morogh,UNKNOWN,Gnomeregan Elevator,1, +5827,Elwynn Forest,UNKNOWN,Mirror Lake Orchard,1, +5828,Stormwind,Quests,Valley of Heroes 2,1, +5829,Elwynn Forest,Quests,Westbrook Garrison,1, +5830,Elwynn Forest,Quests,Goldshire,1, +5831,Stormwind,Quests,Stormwind Harbor,1, +205,Mulgore,Quests,Venture Co. Mine Entrance,1, +216,Barrens,Quests,Forgotten Pools,1, +217,Teldrassil,Quests,Shadowglen Moonwell,1, +218,Teldrassil,Quests,Starbreeze Village Moonwell,1, +219,Teldrassil,Quests,Pools of Arlithrien Moonwell,1, +220,Teldrassil,Quests,Oracle Glade Moonwell,1, +223,Darkshore,Quests,The Master's Glaive 1,1, +224,Darkshore,Quests,The Master's Glaive 2,1, +225,Darkshore,Quests,The Master's Glaive 3,1, +228,Barrens,Portals,To Wailing Caverns,1, +230,Darkshore,Quests,Bashal'Aran,1, +231,Darkshore,Quests,Furbolg Camp,1, +232,Darkshore,Quests,Furbolg Camp,1, +233,Darkshore,Quests,Furbolg Camp 2,1, +234,Darkshore,Quests,Blackwood Den 1,1, +235,Darkshore,Quests,Blackwood Den 2,1, +236,Darkshore,Quests,Blackwood Den 3,1, +237,Darkshore,Quests,Blackwood Den 4,1, +238,Darkshore,Quests,Furbolg Camp North,1, +239,Ashenvale,Quests,Zoram Strand,1, +244,Barrens,Portals,To Razorfen Kraul,1, +246,Thousand Needles,Quests,Test of Faith Top,1, +257,Darkshore,Portals,To Blackfathom,1, +302,Dustwallow,Quests,Sentry Point,1, +303,Dustwallow,UNKNOWN,UNNAMED,1, +422,Desolace,Quests,Destroyed Caravan,1, +442,Barrens,Portals,To Razorfen Downs,1, +522,Barrens,Quests,Fray Island,1, +527,Darnassus,Portals,To Rutheran Village,1, +542,Rutheran Village,Portals,To Darnassus,1, +709,Dustwallow Marsh,Inns,Theramore Inn,1, +715,Teldrassil,Inns,Dolanaar Inn,1, +716,Darkshore,Inns,Auberdine Inn,1, +717,Ashenvale,Inns,Astranaar Inn,1, +722,Mulgore,Inns,Bloodhoof Village Inn,1, +742,Barrens,Inns,Crossroads Inn,1, +743,Barrens,Inns,Ratchet Inn,1, +843,Durotar,Inns,Razor Hill Inn,1, +924,Tanaris,Portals,To Zul'Farrak,1, +942,Thousand Needles,Quests,Test of Faith Bottom 1,1, +943,Thousand Needles,Quests,Test of Faith Bottom 2,1, +944,Thousand Needles,Quests,Test of Faith Bottom 3,1, +982,Barrens,Inns,Camp Taurajo Inn,1, +1022,Stonetalon Mountains,Inns,Sun Rock Retreat Inn,1, +1023,Tanaris,Inns,Gadgetzan Inn,1, +1024,Feralas,Inns,Feathermoon Inn,1, +1025,Feralas,Inns,Camp Mojache Inn,1, +1387,Azshara,Quests,Thalassian Base Camp 1,1, +1388,Azshara,Quests,Thalassian Base Camp 2,1, +1667,Dustwallow,Quests,Sentry Point 2,1, +1726,Un'Goro Crater,Quests,Venomhide Eggs 1,1, +1727,Un'Goro Crater,Quests,Venomhide Eggs 2,1, +1728,Un'Goro Crater,Quests,Venomhide Eggs 3,1, +1729,Un'Goro Crater,Quests,Venomhide Eggs 4,1, +1730,Un'Goro Crater,Quests,Venomhide Eggs 5,1, +1731,Un'Goro Crater,Quests,Venomhide Eggs 6,1, +1732,Un'Goro Crater,Quests,Venomhide Eggs 7,1, +1733,Un'Goro Crater,Quests,Venomhide Eggs 8,1, +1734,Un'Goro Crater,Quests,Venomhide Eggs 9,1, +1735,Un'Goro Crater,Quests,Venomhide Eggs 10,1, +1736,Un'Goro Crater,Quests,Venomhide Eggs 11,1, +1737,Un'Goro Crater,Quests,Venomhide Eggs 12,1, +1738,Un'Goro Crater,Quests,Venomhide Eggs 13,1, +1739,Un'Goro Crater,Quests,Venomhide Eggs 14,1, +1740,Un'Goro Crater,Quests,Venomhide Eggs 15,1, +1766,Un'Goro Crater,Quests,Venomhide Eggs 16,1, +1906,Darkshore,Quests,Furbolg Camp North 2,1, +1966,Darkshore,Quests,Murloc Camp,1, +2206,Felwood,Quests,Shatter Scar Vale 1,1, +2207,Felwood,Quests,Shatter Scar Vale 2,1, +2208,Felwood,Quests,Shatter Scar Vale 3,1, +2211,Winterspring,Portals,To Cavern,1, +2213,Winterspring,Portals,To Mountain,1, +2230,Orgrimmar,Portals,To Ragefire Chasm,1, +2266,Desolace,Inns,Nijel's Point Inn,1, +2267,Desolace,Inns,Shadowprey Village Inn,1, +2286,Thousand Needles,Inns,Freewind Post Inn,1, +2287,Winterspring,Inns,Everlook Inn,1, +2327,Winterspring,Quests,Darkwhisper Gorge,1, +2486,Darkshore,Quests,Red Crystal,1, +2610,Ashenvale,Inns,Splintertree Post Inn,1, +2848,Dustwallow,Portals,To Onyxia's Lair,1, +2926,Ashenvale,Quests,Mystral Lake,1, +2946,Stonetalon Mountains,Quests,Boulderslide Cavern,1, +3133,Desolace,Portals,To Maraudon Orange,1, +3134,Desolace,Portals,To Maraudon Purple,1, +3146,Silithus,Quests,Southwind Village Tower,1, +3183,Feralas,Portals,To Dire Maul East,1, +3184,Feralas,Portals,To Dire Maul East 2,1, +3185,Feralas,Portals,To Dire Maul Library,1, +3186,Feralas,Portals,To Dire Maul West,1, +3187,Feralas,Portals,To Dire Maul West 2,1, +3189,Feralas,Portals,To Dire Maul North,1, +3546,Darnassus,Quests,Bank,1, +3549,Barrens,Quests,To Ashenvale,1, +3550,Barrens,Quests,Ratchet,1, +3551,Barrens,Quests,Ratchet Pier,1, +3586,Felwood,Quests,Irontree Woods,1, +3587,Kalimdor,Quests,Irontree Woods 2,1, +3650,Ashenvale,Portals,To Warsong Guilch Alliance,1, +3654,Barrens,Portals,To Warsong Guilch Horde,1, +3985,Silithus,Inns,Cenarion Hold Inn,1, +3986,Tanaris,Quests,Caverns of Time Entrance,1, +4008,Silithus,Portals,To Ruins of Ahn'Qiraj,1, +4010,Silithus,Portals,To Ahn'Qiraj Temple,1, +4015,Winterspring,Quests,AQ Opening,1, +4027,Durotar,Events,Winter Veil Thingy,1, +4031,Tanaris,Quests,Outside Gadgetzan,1, +4085,Winterspring,Quests,Frostsaber Rock,1, +4090,Stonetalon Mountains,Inns,Stonetalon Peak Inn,1, +4101,Durotar,UNKNOWN,UNNAMED,1, +4102,Durotar,UNKNOWN,UNNAMED,1, +4103,Mulgore,UNKNOWN,UNNAMED,1, +4104,Teldrassil,UNKNOWN,UNNAMED,1, +4105,Teldrassil,UNKNOWN,UNNAMED,1, +4162,Silithus,PVP,Alliance Silithyst Dropoff,1, +4168,Silithus,PVP,Horde Silithyst Dropoff,1, +4299,Silithus,Quests,UNNAMED,1, +4319,Cavenrs of Time,Portals,To Hyjal,1, +4320,Cavenrs of Time,Portals,To Black Morass,1, +4321,Cavenrs of Time,Portals,To Hillsbrad Foothills,1, +4666,Azshara,Quests,Southfury River 1,1, +4667,Azshara,Quests,Southfury River 2,1, +4670,Azshara,Quests,Southfury River 3,1, +4671,Azshara,Quests,Southfury River 4,1, +4672,Azshara,Quests,Southfury River 5,1, +4673,Azshara,Quests,Southfury River 6,1, +4680,Barrens,UNKNWON,UNNAMED,1, +4681,Barrens,UNKNWON,UNNAMED,1, +4682,Barrens,UNKNWON,UNNAMED,1, +4714,Dustwallow,Inns,Mudsprocket Inn,1, +4719,Mulgore,Quests,To Thunder Bluff,1, +4720,Dustwallow,UNKNOWN,UNNAMED,1, +4752,Dustwallow,Quests,Tidefury Cove Ship,1, +4775,Dustwallow,Inns,Brackenwall Village Inn,1, +4797,Durotar,UNKNOWN,UNNAMED,1, +4798,Durotar,UNKNOWN,UNNAMED,1, +4799,Durotar,UNKNOWN,UNNAMED,1, +4800,Durotar,UNKNOWN,UNNAMED,1, +4801,Orgrimmar,Quests,Valley of Strength,1, +4802,Orgrimmar,Quests,Valley of Honor,1, +4803,Orgrimmar,Quests,Valley of Wisdom,1, +4804,Orgrimmar,Quests,Valley of Spirits,1, +4807,Durotar,Quests,To Orgrimmar,1, +4808,Durotar,UNKNOWN,UNNAMED,1, +4809,Durotar,UNKNOWN,UNNAMED,1, +4810,Durotar,Quests,To Orgrimmar 2,1, +4829,Durotar,UNKNOWN,UNNAMED,1, +5047,Un'Goro Crater,Portals,To Sholazar Basin,1, +5049,Un'Goro Crater,UNKNOWN,UNNAMED,1, +5150,Cavenrs of Time,Portals,To Stratholme,1, +5263,Durotar,UNKNOWN,UNNAMED,1, +5264,Durotar,UNKNOWN,UNNAMED,1, +5265,Durotar,UNKNOWN,UNNAMED,1, +5266,Durotar,UNKNOWN,UNNAMED,1, +5267,Durotar,UNKNOWN,UNNAMED,1, +5268,Orgrimmar,Quests,Entrance,1, +5269,Orgrimmar,Quests,Entrance 2,1, +5270,Orgrimmar,Quests,UNNAMED,1, +5271,Orgrimmar,Quests,UNNAMED,1, +5272,Orgrimmar,Quests,UNNAMED,1, +5279,Orgrimmar,Quests,Thrall,1, +5311,Orgrimmar,Quests,Grommash Hold,1, +5462,Un'Goro Crater,Portals,To Sholazar Basin,1, +5463,Un'Goro Crater,Portals,UNNAMED,1, +5464,Un'Goro Crater,Portals,To Sholazar Basin,1, +5485,Thunder Bluff,Quests,Elder Rise,1, +5518,Moonglade,Inns,Nighthaven Inn,1, +5557,Darnassus,Quests,Temple of the Moon Entrace,1, +5705,Durotar,UNKNOWN,UNNAMED,1, +5706,Orgrimmar,Quests,UNNAMED,1, +5714,Orgrimmar,Quests,Auction House Entrace,1, +5715,Orgrimmar,Quests,Bamk Entrance,1, +5716,Orgrimmar,Quests,Barbershop Entrance,1, +5802,Durotar,UNKNOWN,UNNAMED,1, +5810,Durotar,UNKNOWN,UNNAMED,1, +5813,Durotar,World,Zepplin Tower,1, +5814,Durotar,World,Zepplin Tower Northrend,1, +5815,Durotar,Quests,Razor Hill,1, +5843,Durotar,Quests,Echo Isles,1, +5847,Durotar,Quests,Echo Isles 2,1, +5848,Durotar,Quests,Echo Isles 3,1, +60,UNKNOWN,UNKNOWN,UNNAMED,1, +81,UNKNOWN,UNKNOWN,UNNAMED,1, +83,UNKNOWN,UNKNOWN,UNNAMED,1, +95,Alterac Valley,Portals,To Alterac Horde (OLD),1, +2606,Alterac Valley,Portals,To Alterac Horde,1, +2608,Alterac Valley,Portals,To Alterac Alliance,1, +3326,Alterac Valley,PVP,Tower Point,1, +3327,Alterac Valley,PVP,Frostwolf Village,1, +3328,Alterac Valley,PVP,Frostwolf Keep Tower,1, +3329,Alterac Valley,PVP,Icewing Bunker,1, +3330,Alterac Valley,UNKNOWN,Horde Guy Location Thingy. Please rename me,1, +3331,Alterac Valley,PVP,Dun Baldar North Bunker,1, +194,Shadowfang,Portals,To Silverpine Forest,1, +254,Shadowfang,World,Walls,1, +255,Shadowfang,World,Wall 2,1, +256,Shadowfang,World,Wall 3,1, +2406,Shadowfang,Portals,Outside 1,1, +2407,Shadowfang,Portals,Outside 2,1, +2408,Shadowfang,Portals,Outside 3,1, +2409,Shadowfang,Portals,Outside 4,1, +2410,Shadowfang,Portals,Outside 5,1, +2411,Shadowfang,Portals,Outside 6,1, +5571,Shadowfang,Portals,To Back,1, +5572,Shadowfang,World,To Courtyard,1, +5722,Shadowfang,World,To Kitchen,1, +503,Stormwind Jail,Portals,To Stormwind,1, +109,Stormwind Prison,Portals,To Stormwind,1, +119,Deadmines,Portals,To Westfall,1, +121,Deadmines,Portals,To Westfall Exit,1, +3746,Deadmines,World,Entrance,1, +226,Wailing Caverns,Portals,To Barrens,1, +3766,Wailing Caverns,World,Entrance,1, +242,Razorfen Kraul,Portals,To Barrens,1, +262,Razorfen Kraul,World,Agathelos,1, +462,Razorfen Kraul,World,Bridges,1, +463,Razorfen Kraul,World,Cavern Exit,1, +259,Blackfathom,Portals,To Ashenvale,1, +283,Blackfathom,Quest,Thaelrid,1, +762,Blackfathom,World,Endboss,1, +288,Uldaman,Portals,To Badlands,1, +822,Uldaman,World,Ironaya,1, +882,Uldaman,Portals,To Badlands Backdoor,1, +322,Gnomeregan,Portals,To Dun Morogh,1, +525,Gnomeregan,Portals,To Dun Morogh Backdoor,1, +1105,Gnomeregan,World,Entrance,1, +448,Sunken Temple,Portals,To Swamp of Sorrows,1, +1306,Sunken Temple,World,To Prophet Boss,1, +1326,Sunken Temple,World,Pit of Sacrifice to Pit of Refuse,1, +4016,Sunken Temple,World,Endboss,1, +444,Razorfen Downs,Portals,To Kalimdor,1, +602,Scarlet Monastery,Portals,Graveyard to Tirisfal,1, +604,Scarlet Monastery,Portals,Cathedral to Tirisfal,1, +606,Scarlet Monastery,Portals,Armory to Tirisfal,1, +608,Scarlet Monastery,Portals,Library to Tirisfal,1, +4089,Scarlet Monastery,World,Ashbringer Event Cathedral,1, +4261,Scarlet Monastery,World,Ashbringer Event Graveyard,1, +4262,Scarlet Monastery,World,Ashbringer Event Armory,1, +4263,Scarlet Monastery,World,Ashbringer Event Library,1, +922,Zul'Farrak,Portals,To Tanaris,1, +962,Zul'Farrak,World,Witch Doctor Boss,1, +1447,Zul'Farrak,World,Cave Boss,1, +1470,Blackrock Spire,Portals,To Blackrock Mountain,1, +1628,Blackrock Spire,World,To Urok,1, +1946,Blackrock Spire,World,Vaelan 1,1, +1986,Blackrock Spire,World,Vaelan 2,1, +1987,Blackrock Spire,World,Vaelan 3,1, +2026,Blackrock Spire,World,Arena,1, +2046,Blackrock Spire,World,UBRS Entrance,1, +2066,Blackrock Spire,World,The Beast,1, +2067,Blackrock Spire,World,The Beast Entrance,1, +2068,Blackrock Spire,Portals,LBRS to Blackrock Mountain,1, +3726,Blackrock Spire,Portals,UBRS to Blackwing Lair,1, +1472,Blackrock Depths,Portals,To Blackrock Mountain,1, +1526,Blackrock Depths,World,Arena,1, +1590,Blackrock Depths,World,Jail 1,1, +1686,Blackrock Depths,World,Jail 2,1, +1746,Blackrock Depths,World,Jail 3,1, +1786,Blackrock Depths,World,Bridge Ambush,1, +1826,Blackrock Depths,World,Main Entrance,1, +1827,Blackrock Depths,World,To Endboss,1, +1828,Blackrock Depths,World,Gate Entrance,1, +1926,Blackrock Depths,World,Vault Entrance,1, +2886,Blackrock Depths,Portals,To Molten Core,1, +4751,Blackrock Depths,World,Grim Guzzler,1, +1626,Caverns's of Time,Portals,Hillsbrad to Tanaris (OLD),1, +4288,Caverns's of Time,World,Dark Portal,1, +4322,Caverns's of Time,Portals,Black Morass to Tanaris,1, +4485,Caverns's of Time,World,Entrance,1, +2547,Scholomance,Portals,Window 1,1, +2548,Scholomance,Portals,Window 2,1, +2549,Scholomance,Portals,Window 3,1, +2568,Scholomance,Portals,To Western Plaguelands,1, +3926,Zul'Gurub,World,Temple of Bethekk,1, +3930,Zul'Gurub,Portals,To Stranglethorn Vale,1, +3956,Zul'Gurub,World,First Bridge,1, +3957,Zul'Gurub,World,Entrance Steps,1, +3958,Zul'Gurub,World,Entrance,1, +3959,Zul'Gurub,World,Second Bridge,1, +3960,Zul'Gurub,World,Altar of Blood God Second Level,1, +3961,Zul'Gurub,World,To Bloodfire Pit,1, +3962,Zul'Gurub,World,To Edge of Madness,1, +3963,Zul'Gurub,World,To Hakkari Grounds,1, +3964,Zul'Gurub,World,Altar of Blood God First Level,1, +3965,Zul'Gurub,World,To Temple of Bethekk,1, +3966,Zul'Gurub,World,To Altar of Hir'eek,1, +2187,Stratholme,World,Alonsus Chapel,1, +2209,Stratholme,World,Crusader's Square 1,1, +2210,Stratholme,World,Crusader's Square 2,1, +2221,Stratholme,Portals,To Eastern Plaguelands,1, +3126,Maraudon,Portals,To Desolace Purple,1, +3131,Maraudon,Portals,To Desolace Orange,1, +2166,Deeprun Tram,Portals,To Ironforge,1, +2171,Deeprun Tram,Portals,To Stormwind,1, +2226,Ragefire Chasm,Portals,To Kalimdor,1, +2890,Molten Core,Portals,To Eastern Kingdoms,1, +3190,Dire Maul,Portals,West to Feralas 1,1, +3191,Dire Maul,Portals,West to Feralas 2,1, +3193,Dire Maul,Portals,North to Feralas,1, +3194,Dire Maul,Portals,East to Feralas 1,1, +3195,Dire Maul,Portals,East to Feralas 2,1, +3196,Dire Maul,Portals,Library to Feralas,1, +3197,Dire Maul,Portals,East Tunnel to Feralas,1, +3506,Dire Maul,World,To Endboss 1,1, +3507,Dire Maul,World,To Endboss 2,1, +3508,Dire Maul,World,To Endboss 3,1, +3509,Dire Maul,World,To Endboss 4,1, +2534,Barracks,Portals,To Stormwind,1, +2530,Barracks,Portals,To Orgrimmar,1, +2416,Development Land,UNKNOWN,UNNAMED,1, +4750,Development Land,UNKNOWN,UNNAMED,1, +4836,Development Land,UNKNOWN,UNNAMED,1, +4840,Development Land,UNKNOWN,UNNAMED,1, +4841,Development Land,UNKNOWN,UNNAMED,1, +4854,Development Land,UNKNOWN,UNNAMED,1, +4901,Development Land,UNKNOWN,UNNAMED,1, +4905,Development Land,UNKNOWN,UNNAMED,1, +4909,Development Land,UNKNOWN,UNNAMED,1, +4911,Development Land,UNKNOWN,UNNAMED,1, +4953,Development Land,UNKNOWN,UNNAMED,1, +4958,Development Land,UNKNOWN,UNNAMED,1, +4962,Development Land,UNKNOWN,UNNAMED,1, +5453,Development Land,UNKNOWN,UNNAMED,1, +5477,Development Land,UNKNOWN,UNNAMED,1, +2426,Blackwing Lair,Portals,Balcony to Blackrock Mountain 1,1, +2427,Blackwing Lair,Portals,Balcony to Blackrock Mountain 2,1, +2428,Blackwing Lair,Portals,Balcony to Blackrock Mountain 3,1, +2429,Blackwing Lair,Portals,Balcony to Blackrock Mountain 4,1, +3626,Blackwing Lair,World,Vael Entrance,1, +3728,Blackwing Lair,Portals,To Blackrock Mountain,1, +3646,Warsong Gulch,PVP,Alliance Flag,1, +3647,Warsong Gulch,PVP,Horde Flag,1, +3649,Warsong Gulch,UNKNOWN,UNNAMED,1, +3686,Warsong Gulch,PVP,Alliance Speed Pickup,1, +3687,Warsong Gulch,PVP,Horde Speed Pickup,1, +3688,Warsong Gulch,UNKNOWN,Infront Horde Berserk Pickup,1, +3706,Warsong Gulch,PVP,Alliance Healing Pickup,1, +3707,Warsong Gulch,PVP,Alliance Berserk Pickup,1, +3708,Warsong Gulch,PVP,Horde Healing Pickup,1, +3709,Warsong Gulch,PVP,Horde Berserk Pickup,1, +4628,Warsong Gulch,UNKNOWN,Alliance Base,1, +4629,Warsong Gulch,UNKNOWN,Horde Base,1, +4006,Ahn'Qiraj Ruins,Portals,To Kalimdor,1, +3866,Arathi Basin,PVP,Stables Pickup,1, +3867,Arathi Basin,PVP,Farm Pickup,1, +3868,Arathi Basin,PVP,Lumber Mill Pickup,1, +3869,Arathi Basin,PVP,Mine Pickup,1, +3870,Arathi Basin,PVP,Blacksmith Pickup,1, +3948,Arathi Basin,Portals,Alliance To Ashenvale,1, +3949,Arathi Basin,Portals,Horde To Barrens,1, +4020,Arathi Basin,UNKNOWN,Alliance Start,1, +4021,Arathi Basin,UNKNOWN,Horde Start,1, +4674,Arathi Basin,UNKNOWN,Blacksmith,1, +4064,Ghostlands,Quests,An'daroth,1, +4071,Ghostlands,Quests,Amani Catacombs,1, +4108,Ghostlands,Inns,Tranquillien Inn,1, +4109,Ghostlands,Inns,Tranquillien Inn Upper,1, +4150,Hellfire Peninsula,Portals,To Ramparts,1, +4151,Hellfire Peninsula,Portals,To Shattered Halls,1, +4152,Hellfire Peninsula,Portals,To Blood Furnace,1, +4153,Hellfire Peninsula,Portals,To Magtheridon's Lair,1, +4170,Hellfire Peninsula,Quests,Fel Orc Corpse,1, +4186,Azuremyst Isle,Quests,UNNAMED,1, +4233,Eversong Woods,Portals,To Duskwither Spire,1, +4240,Azuremyst Isle,Inns,Azuzre Watch Inn,1, +4241,Bloodmyst Isle,Inns,Bloodmyst Watch Inn,1, +4252,Hellfire Peninsula,UNKNOWN,UNNAMED,1, +4253,Hellfire Peninsula,UNKNOWN,UNNAMED,1, +4265,Eversong Woods,Inns,Fairbreeze Village Inn,1, +4267,Eversong Woods,Portals,To Duskwither Grounds,1, +4280,Bloodmyst Isle,Quests,Warp Piston,1, +4291,Zangarmarsh,Quests,Spawning Glen,1, +4293,Zangarmarsh,Quests,Umbrafen Steam Pump,1, +4298,Zangarmarsh,Quests,To Coilfang Reservoir,1, +4300,Zangarmarsh,World,Cenarion Refuge Inn Entrance,1, +4301,Zangarmarsh,Quests,Boha'mu Ruins,1, +4326,Bloodmyst Isle,Quests,Bridge to Bloodmyst Isle,1, +4336,Hellfire Peninsula,Inns,Thrallmar Inn,1, +4337,Hellfire Peninsula,Inns,Honor Hold Inn,1, +4339,Silvermoon City,World,M'uru,1, +4342,Zangarmarsh,PVP,Twin Spire Ruins,1, +4345,Terokkar Forest,Quests,Veil Shalas,1, +4352,Hellfire Peninsula,Portals,Dark Portal to Blasted Lands,1, +4356,Hellfire Peninsula,Quest,Dark Portal,1, +4363,Zangarmarsh,Portals,To Underbog,1, +4364,Zangarmarsh,Portals,To Steamvault,1, +4365,Zangarmarsh,Portals,To Slave Pens,1, +4368,Terokkar Forest,Quests,Bleeding Hollow Ruins,1, +4369,Nagrand,Quests,Sunspring Post,1, +4370,Nagrand,Quests,Ring of Blood,1, +4371,Nagrand,Quests,Laughing Skull Ruins,1, +4372,Nagrand,Quests,Garadar,1, +4373,Zangarmarsh,Inns,Zabra'jin Inn,1, +4374,Zangarmarsh,Inns,Telredor Inn,1, +4375,Nagrand,Inns,Garadar Inn,1, +4376,Nagrand,Inns,Telaar Inn,1, +4377,Terokkar Forest,Inns,Allerian Stronghold Inn,1, +4378,Terokkar Forest,Inns,Stonebreaker Hold Inn,1, +4380,Hellfire Peninsula,Inns,Falcon Watch Inn,1, +4381,Hellfire Peninsula,Inns,Temple of Telhamat Inn,1, +4382,Zangarmarsh,Inns,Cenarion Refuge Inn,1, +4383,Zangarmarsh,Inns,Orebor Harborage Inn,1, +4386,Ghostlands,Portals,To Eastern Plaguelands,1, +4387,Terokkar Forest,PVP,Spirit Tower,1, +4389,Shattrath City,World,Griftah,1, +4404,Terokkar Forest,Portals,To Auchenai Crypts,1, +4405,Terokkar Forest,Portals,To Mana-Tombs,1, +4406,Terokkar Forest,Portals,To Sethekk Halls,1, +4407,Terokkar Forest,Portals,To Shadow Labyrinth,1, +4410,Shattrath City,World,To Lower City,1, +4415,Netherstorm,Quests,Stormspire,1, +4416,Zangarmarsh,Portals,To Serpentshrine Cavern,1, +4422,Netherstorm,World,Area 52 Entrance 1,1, +4425,Shattrath City,Quests,Refuge,1, +4426,Shattrath City,UNKNOWN,To Center,1, +4427,Shattrath City,UNKNOWN,UNNAMED,1, +4428,Shattrath City,UNKNOWN,UNNAMED,1, +4429,Shattrath City,UNKNOWN,UNNAMED,1, +4430,Shattrath City,UNKNOWN,UNNAMED,1, +4431,Shattrath City,UNKNOWN,UNNAMED,1, +4432,Shattrath City,UNKNOWN,To Scryer's Tier,1, +4433,Shattrath City,UNKNOWN,To Scryer's Tier 2,1, +4434,Shattrath City,UNKNOWN,To Aldor Rise,1, +4435,Shattrath City,UNKNOWN,To Aldor Rise 2,1, +4439,Hellfire Peninsula,Portals,To Falcon Watch Top,1, +4440,Hellfire Peninsula,Portals,To Falcon Watch Bottom,1, +4443,Terokkar Forest,Portals,To Firewing Point Upper,1, +4445,Terokkar Forest,Portals,To Tower Upper,1, +4466,Netherstorm,World,Area 52 Entrance 2,1, +4467,Netherstorm,Portals,To Botanica,1, +4468,Netherstorm,Portals,To Arcatraz,1, +4469,Netherstorm,Portals,To Mechanar,1, +4470,Netherstorm,Portals,To Tempest Keep,1, +4471,Netherstorm,World,Area 52 Entrance 3,1, +4472,Netherstorm,World,Area 52 Entrance 4,1, +4473,Netherstorm,Quests,Triangulation Point One,1, +4475,Netherstorm,Quests,Triangulation Point Two,1, +4478,Netherstorm,Quests,Ruins of Farahlon,1, +4479,Netherstorm,Portals,To Shattrath,1, +4482,Netherstorm,Quests,Celestial Ridge,1, +4486,Eversong Woods,Inns,Falconwing Square Inn,1, +4493,Zangarmarsh,Inns,Telredor Inn 2,1, +4494,Blade's Edge Mountains,Inns,Thunderlord Stronghold Inn,1, +4495,Netherstorm,World,Karaaz,1, +4496,Shadowmoon Valley,Quests,Illidari Point,1, +4497,Netherstorm,Quests,Manaforge Coruu,1, +4499,Blade's Edge Mountains,Inns,Sylvanaar Inn,1, +4506,Netherstorm,World,Ya-six,1, +4507,Netherstorm,World,Ya-six 2,1, +4521,Netherstorm,Inns,Area 52 Inn,1, +4523,Netherstorm,Portals,To Invasion Point: Overlord,1, +4526,Shadowmoon Valley,Inns,Shadowmoon Village Inn,1, +4527,Shadowmoon Valley,Inns,Shadowmoon Village Inn 2,1, +4528,Shadowmoon Valley,Inns,Wildhammer Stronghold Inn,1, +4529,Shadowmoon Valley,Inns,Wildhammer Stronghold Inn,1, +4535,Blade's Edge Mountains,Portals,To Gruul's Lair,1, +4542,Blade's Edge Mountains,World,Bladespire Grounds,1, +4546,Blade's Edge Mountains,World,Daranelle,1, +4548,Shadowmoon Valley,Quests,Legion Communication Device,1, +4555,Netherstorm,Inns,Stormspire Inn,1, +4556,Netherstorm,Inns,Stormspire Inn 2,1, +4557,Netherstorm,Inns,Stormspire Inn 3,1, +4558,Blade's Edge Mountains,Inns,Toshley's Station Inn,1, +4560,Shadowmoon Valley,Portal,To Invasion Point: Cataclysm,1, +4561,Shadowmoon Valley,Portals,To Legion Hold Alliance,1, +4562,Shadowmoon Valley,Portals,To Legion Hold Horde,1, +4563,Shadowmoon Valley,Portal,To Invasion Point: Cataclysm,1, +4566,Shadowmoon Valley,Portal,To Invasion Point: Cataclysm,1, +4576,Blade's Edge Mountains,UNKNOWN,Grishnath 1,1, +4577,Shadowmoon Valley,Inns,Altar of Sha'tar Inn,1, +4578,Shadowmoon Valley,Portal,To Invasion Point: Cataclysm,1, +4579,Shadowmoon Valley,Portal,To Invasion Point: Cataclysm,1, +4580,Blade's Edge Mountains,Quests,Grishnath 2,1, +4581,Shadowmoon Valley,Quests,Path of Conquest 1,1, +4582,Shadowmoon Valley,Quests,Path of Conquest 2,1, +4583,Shadowmoon Valley,Quests,Path of Conquest 3,1, +4588,Shadowmoon Valley,Quests,Path of Conquest 4,1, +4589,Shadowmoon Valley,Quests,Path of Conquest 5,1, +4590,Shadowmoon Valley,Quests,Path of Conquest 6,1, +4591,Zangarmarsh,World,To Serpentshrine Cavern,1, +4595,Blade's Edge Mountains,Inns,Mok'Nathal Village Inn,1, +4598,Shadowmoon Valley,Portals,To Black Temple,1, +4607,Shadowmoon Valley,Inns,Sanctum of the Stars Inn 1,1, +4608,Shadowmoon Valley,Inns,Sanctum of the Stars Inn 2,1, +4609,Shadowmoon Valley,Inns,Sanctum of the Stars Inn 3,1, +4613,Blade's Edge Mountains,Quests,Grishnath 3,1, +4615,Blade's Edge Mountains,Quests,Grishnath 4,1, +4616,Blade's Edge Mountains,Quests,Grishnath 5,1, +4617,Blade's Edge Mountains,Quests,Grishnath 6,1, +4640,Blade's Edge Mountains,Inns,Evergrove Inn,1, +4713,Terokkar Forest,UNKNOWN,UNNAMED,1, +4722,Eversong Woods,Inns,Fairbreeze Village Inn Upper,1, +4738,Ghostlands,Portals,To Zul'Aman,1, +4847,Isle of Quel'Danas,Inns,Sun's Reach Harbor Inn,1, +4851,Eversong Woods,Events,Winter Veil Thingy,1, +4852,Azuremyst Isle,Events,Winter Veil Thingy,1, +4874,Terokkar Forest,Events,Winter Veil Thingy,1, +4887,Isle of Quel'Danas,Portals,To Magisters' Terrace,1, +4889,Isle of Quel'Danas,Portals,To Sunwell Plateau,1, +5151,Azuremyst Isle,UNKNOWN,UNNAMED,1, +5152,Azuremyst Isle,UNKNOWN,UNNAMED,1, +5153,Azuremyst Isle,UNKNOWN,UNNAMED,1, +5154,Azuremyst Isle,UNKNOWN,UNNAMED,1, +5158,Eversong Woods,UNKNOWN,UNNAMED,1, +5159,Eversong Woods,UNKNOWN,UNNAMED,1, +5160,Eversong Woods,UNKNOWN,UNNAMED,1, +5161,Eversong Woods,UNKNOWN,UNNAMED,1, +4012,Ahn'Qiraj Temple,Portals,To Silithus,1, +4033,Ahn'Qiraj Temple,World,Stomach Bottom,1, +4034,Ahn'Qiraj Temple,Portals,To C'thun,1, +4036,Ahn'Qiraj Temple,World,C'thun,1, +4047,Ahn'Qiraj Temple,World,Twin Emperor Chamber,1, +4052,Ahn'Qiraj Temple,World,Gauntlet,1, +4191,Karazhan,UNKNOWN,Servant's Access Door,1, +4216,Karazhan,World,Guardian's Library,1, +4242,Karazhan,World,Guardian's Library 2,1, +4436,Karazhan,Portals,Main Entrance to Deadwind Pass,1, +4520,Karazhan,Portals,Side Entrance to Deadwind Pass,1, +4522,Karazhan,UNKNOWN,Side Entrance,1, +5014,Karazhan,UNKNOWN,Blood Drenched Door,1, +5015,Karazhan,UNKNOWN,Prince Tenris Mirkblood,1, +4086,Naxxramas,World,Boss Gluth,1, +4087,Naxxramas,World,Boss Room Gluth 1,1, +4088,Naxxramas,World,Boss Room Gluth 2,1, +4097,Naxxramas,World,Boss Room Gluth 3,1, +4112,Naxxramas,World,Boss Room Kel'Thuzad,1, +4113,Naxxramas,World,Boss Room Thaddius1,1, +4114,Naxxramas,World,Boss Room Thaddius 2,1, +4115,Naxxramas,World,Boss Room Faerlina,1, +4116,Naxxramas,World,Boss Room Gothik,1, +4117,Naxxramas,World,Military Quarter Entrance,1, +4119,Naxxramas,World,Boss Room Anub'Rekhan,1, +4120,Naxxramas,Portals,To Naxxramas,1, +4156,Naxxramas,Portals,To Sapphiron's Lair,1, +4158,Naxxramas,World,Boss Kel'Thuzad,1, +4163,Naxxramas,World,Boss Room Gluth 4,1, +4166,Naxxramas,World,Outer Ring Construction Quarter,1, +4167,Naxxramas,World,Boss Room Sapphiron,1, +4177,Naxxramas,World,Boss Room Noth,1, +4180,Naxxramas,World,Before Boss Room Gluth,1, +4184,Naxxramas,World,Boss Heigan,1, +5196,Naxxramas,Portals,To Eastern Plaguelands,1, +5197,Naxxramas,Portals,To Eastern Plaguelands,1, +5198,Naxxramas,Portals,To Eastern Plaguelands,1, +5199,Naxxramas,Portals,To Eastern Plaguelands,1, +4311,Hyjal,Portals,To Alliance Camp,1, +4312,Hyjal,Portals,To Horde Camp,1, +4313,Hyjal,Portals,To Night Elf Camp,1, +4323,Hyjal,Portals,To Tanaris,1, +4341,Hyjal,World,Night Elf Camp,1, +4487,Hyjal,Portals,To Tanaris,1, +4145,Shattered Halls,Portals,To Hellfire Peninsula,1, +4182,Shattered Halls,World,Entrance 1,1, +4183,Shattered Halls,World,Entrance 2,1, +4246,Shattered Halls,World,Endboss,1, +4347,Shattered Halls,World,Sewer Exit,1, +4524,Shattered Halls,World,Throne of the Damned Exit,1, +4575,Shattered Halls,World,Gauntlet,1, +4147,Blood Furnace,Portals,To Hellfire Peninsula,1, +4200,Blood Furnace,World,Endboss Entrance 1,1, +4201,Blood Furnace,World,Endboss Entrance 2,1, +4297,Ramparts,Portals,To Hellfire Peninsula,1, +4304,Ramparts,Portals,To Hellfire Peninsula Falloff,1, +4149,Magtheridon's Lair,Portals,To Hellfire Peninsula,1, +4192,Magtheridon's Lair,World,Boss Room 1,1, +4193,Magtheridon's Lair,World,Boss Room 2,1, +4194,Magtheridon's Lair,World,Boss Room 3,1, +4195,Magtheridon's Lair,World,Boss Room 4,1, +4196,Magtheridon's Lair,World,Magtheridon,1, +4366,Steamvault,Portals,To Outland,1, +4281,Underbog,World,UNNAMED,1, +4292,Underbog,World,UNNAMED,1, +4302,Underbog,World,Boss Room 1,1, +4303,Underbog,World,Boss Room 2,1, +4367,Underbog,Portals,To Zangarmarsh,1, +4295,Slave Pens,World,Endboss Entrance,1, +4379,Slave Pens,Portals,To Zangarmarsh,1, +4411,Slave Pens,UNKNOWN,UNNAMED,1, +4418,Serpentshrine Cavern,Portals,To Zangarmarsh,1, +4652,Serpentshrine Cavern,World,The Lurker Below,1, +4457,Tempest Keep,Portals,To Outland,1, +4455,Arcatraz,Portals,To Outland,1, +4459,Botanica,Portals,To Netherstorm,1, +4612,Botanica,Portals,To Netherstorm Exit,1, +4461,Mechanar,Portals,To Netherstorm,1, +4614,Mechanar,Portals,To Netherstorm Exit,1, +4397,Shadow Labyrinth,Portals,To Outland,1, +4399,Sethekk Halls,Portals,To Outland,1, +4401,Mana-Tombs,Portals,To Outland,1, +4403,Auchenai Crypts,Portals,To Outland,1, +4536,Nagrand Arena,PVP,Pickup South,1, +4537,Nagrand Arena,PVP,Pickup North,1, +4917,Nagrand Arena,UNKNOWN,Underground 1,1, +5006,Nagrand Arena,UNKNOWN,Underground 2,1, +5008,Nagrand Arena,UNKNOWN,Underground 3,1, +4324,Hillsbrad Past,Portals,To Tanaris,1, +4498,Hillsbrad Past,World,Southshore Inn,1, +4501,Hillsbrad Past,World,Bartolo Ginsetti,1, +4502,Hillsbrad Past,World,Beggar,1, +4503,Hillsbrad Past,World,Behind Loo,1, +4504,Hillsbrad Past,World,Behind House,1, +4538,Blade's Edge Arena,PVP,Pickup Northwest,1, +4539,Blade's Edge Arena,PVP,Southeast,1, +4919,Blade's Edge Arena,UNKNOWN,Outside 1,1, +4921,Blade's Edge Arena,UNKNOWN,Outside 2,1, +4922,Blade's Edge Arena,UNKNOWN,Outside 3,1, +4923,Blade's Edge Arena,UNKNOWN,Outside 4,1, +4924,Blade's Edge Arena,UNKNOWN,Outside 5,1, +4925,Blade's Edge Arena,UNKNOWN,Outside 6,1, +4944,Blade's Edge Arena,UNKNOWN,Below 1,1, +5039,Blade's Edge Arena,UNKNOWN,Below 2,1, +5040,Blade's Edge Arena,UNKNOWN,Below 3,1, +4619,Black Temple,Portals,To Shadowmoon Valley,1, +4648,Black Temple,World,Endboss Room 1,1, +4649,Black Temple,World,Endboss Room 2,1, +4650,Black Temple,World,Endboss Room 3,1, +4653,Black Temple,World,Endboss Room 4,1, +4654,Black Temple,World,Endboss Room 5,1, +4655,Black Temple,World,Illidan,1, +4656,Black Temple,UNKNOWN,Old Entrance,1, +4657,Black Temple,UNKNOWN,UNNAMED,1, +4665,Black Temple,World,Boss Room Teron Gorefiend,1, +4760,Black Temple,World,Endboss Room 6,1, +4534,Gruul's Lair,Portals,To Outland,1, +4476,Eye of the Storm,PVP,Blood Elf Tower Flag Dropoff 1,1, +4512,Eye of the Storm,PVP,Blood Elf Tower Flag Dropoff 2,1, +4514,Eye of the Storm,PVP,Feld Reaver Ruins Flag Dropoff 1,1, +4515,Eye of the Storm,PVP,Feld Reaver Ruins Flag Dropoff 2,1, +4516,Eye of the Storm,PVP,Mage Tower Flag Dropoff 1,1, +4517,Eye of the Storm,PVP,Mage Tower Flag Dropoff 2,1, +4518,Eye of the Storm,PVP,Draenei Ruins Flag Dropoff 1,1, +4519,Eye of the Storm,PVP,Draenei Ruins Flag Dropoff 2,1, +4530,Eye of the Storm,UNKNOWN,Horde Start,1, +4531,Eye of the Storm,UNKNOWN,Alliance Start,1, +4568,Eye of the Storm,PVP,Blood Elf Tower Pickup,1, +4569,Eye of the Storm,PVP,Feld Reaver Ruins Pickup,1, +4570,Eye of the Storm,PVP,Mage Tower Pickup,1, +4571,Eye of the Storm,PVP,Draenei Ruins Pickup,1, +5866,Eye of the Storm,UNKNOWN,Fel Reaver Ruins,1, +4723,Zul'Aman,World,Bear God,1, +4724,Zul'Aman,World,Dragonhawk God,1, +4725,Zul'Aman,World,Eagle God,1, +4726,Zul'Aman,World,Lynx God,1, +4739,Zul'Aman,Portals,To Ghostlands,1, +4768,Zul'Aman,World,Instance Entrance,1, +4782,Zul'Aman,World,Dragonhawk God Eggs,1, +4745,Howling Fjord,Portals,To Utgarde Keep,1, +4747,Howling Fjord,Portals,To Utgarde Pinnacle,1, +4753,Howling Fjord,Inns,Westguard Keep Inn,1, +4755,Howling Fjord,Inns,Camp Winterhoof Inn,1, +4756,Howling Fjord,Inns,Fort Wildervar Inn,1, +4778,Howling Fjord,Quests,Wyrmskull Village House,1, +4779,Howling Fjord,Quests,King Ymiron,1, +4806,Howling Fjord,Quests,Wildervar Mine,1, +4850,Borean Tundra,Quests,Winterfin Caves,1, +4857,Borean Tundra,Quests,Spire of Decay,1, +4858,Borean Tundra,Quests,Spire of Blood,1, +4860,Borean Tundra,Quests,Spire of Pain,1, +4861,Borean Tundra,Inns,Bor'gorok Outpost Inn,1, +4867,Borean Tundra,Inns,Fizzcrank Airstrip Inn,1, +4868,Borean Tundra,Inns,Taunka'le Village Inn,1, +4871,Borean Tundra,Quests,Warsong Granary,1, +4872,Borean Tundra,Quests,Torp's Farm,1, +4873,Borean Tundra,Quests,Warsong Slaughterhouse,1, +4883,Borean Tundra,Quests,Evanor's Prison,1, +4894,Borean Tundra,World,Khu'nok the Behemoth,1, +4896,Borean Tundra,World,Kaw's Roost,1, +4899,Borean Tundra,Quests,Fizzcrank Pumping Station,1, +4910,Borean Tundra,Inns,Warsong Hold Inn,1, +4914,Borean Tundra,World,Transitus Shield,1, +4916,Borean Tundra,UNKNOWN,Coldara Air,1, +4946,Grizzly Hills,Quests,Thor Modan North Building,1, +4947,Grizzly Hills,Quests,Thor Modan East Building,1, +4948,Grizzly Hills,Quests,Thor Modan South Building,1, +4949,Dragonblight,UNKNOWN,Over Agmar's Hammer,1, +4950,Dragonblight,World,Before Azjol-Nerub,1, +4951,Dragonblight,Quests,The Briny Pinnacle,1, +4952,Borean Tundra,World,To Fizzcrank Airstrip,1, +4956,Dragonblight,Quests,The End of the Line,1, +4960,Dragonblight,World,Agmar's Hammer,1, +4961,Borean Tundra,Inns,Valiance Keep Inn,1, +4963,Borean Tundra,Quest,Plains of Nasam,1, +4964,Dragonblight,Inns,Stars' Rest Inn,1, +4965,Grizzly Hills,Inns,Amberpine Lodge Inn,1, +4966,Grizzly Hills,Inns,Westfall Brigade Encampment inn,1, +4967,Grizzly Hills,Inns,Camp Oneqwah Inn,1, +4968,Dragonblight,UNKNOWN,UNNAMED,1, +4969,Dragonblight,UNKNOWN,UNNAMED,1, +4970,Grizzly Hills,Inns,Conquest Hold Inn,1, +4971,Dragonblight,World,UNNAMED,1, +4972,Dragonblight,World,UNNAMED,1, +4973,Dragonblight,World,UNNAMED,1, +4974,Dragonblight,World,UNNAMED,1, +4975,Dragonblight,Inns,Mao'ki Harbor Inn,1, +4976,Howling Fjord,Inns,Kamagua Inn,1, +4977,Borean Tundra,Inns,unu'pe Inn,1, +4979,Dragonblight,Inns,Venomspite Inn,1, +4983,Borean Tundra,Portals,To the Nexus,1, +4984,Dragonblight,World,New Hearthglen Chapple,1, +4985,Dragonblight,World,Maw of Neltharion Entrance,1, +4986,Dragonblight,World,Maw of Neltharion Main Chamber,1, +4987,Dragonblight,World,New Hearthglen Abbey Entrance,1, +4988,Dragonblight,World,Maw of Neltharion Main Chamber Entrance,1, +4989,Dragonblight,World,Maw of Neltharion First Chamber,1, +4993,Dragonblight,Inns,Wintergarde Keep Inn,1, +4998,Zul'Drak,Portals,To Drak'Tharon Keep,1, +5003,Zul'Drak,Quests,Altar of Sseratus,1, +5010,Storm Peaks,Portals,To Halls of Stone,1, +5030,Sholazar Basin,Quests,Mistwhisper Treasure,1, +5045,Dragonblight,Inns,Agmar's Hammer Inn,1, +5046,Sholazar Basin,Portals,To Un'Goro Krater,1, +5048,Sholazar Basin,UNKNOWN,UNNAMED,1, +5051,Zul'Drak,Portals,Reliquary of Pain To Voltarus,1, +5052,Zul'Drak,Quests,Altar of Quetz'lun,1, +5056,Zul'Drak,World,Approaching Voltarus,1, +5057,Zul'Drak,Quests,Overlord Drakuru 1,1, +5058,Zul'Drak,Quests,Overlord Drakuru 2,1, +5059,Zul'Drak,Quests,Overlord Drakuru 3,1, +5060,Zul'Drak,Quests,Overlord Drakuru 4,1, +5061,Zul'Drak,Portals,Voltarus to Reliquary of Pain,1, +5062,Zul'Drak,Inns,Argent Stand Inn,1, +5079,Zul'Drak,Portals,Voltarus to Upper Voltarus,1, +5080,Zul'Drak,Portals,Upper Voltarus to Voltarus,1, +5093,Storm Peaks,Portals,To Halls of Lightning,1, +5095,Zul'Drak,Quests,Overlord Drakuru 5,1, +5096,Zul'Drak,Quests,Overlord Drakuru 6,1, +5097,Zul'Drak,Quests,Overlord Drakuru 7,1, +5098,Zul'Drak,Quests,Overlord Drakuru 8,1, +5108,Sholazar Basin,Quests,Shrine of the Tempest,1, +5117,Dragonblight,Portals,To Azjol-Nerub,1, +5163,Storm Peaks,World,Janks,1, +5164,Zul'Drak,Inns,Zim'Torga Inn,1, +5171,Storm Peaks,UNKNOWN,Frostgrip's Hollow Entrance,1, +5172,Storm Peaks,UNKNOWN,Frostgrip's Hollow 1,1, +5173,Storm Peaks,UNKNOWN,Frostgrip's Hollow 1,1, +5174,Zul'Drak,World,Thrym's End Gerk,1, +5175,Zul'Drak,World,Thrym's End Burr,1, +5176,Zul'Drak,Quests,Dargath's Demise,1, +5177,Zul'Drak,Quests,Crusader Forward Camp,1, +5182,Storm Peaks,Inns,Frosthold Inn,1, +5183,Storm Peaks,Inns,K3 Inn,1, +5187,Storm Peaks,Portals,Garm's Rise to K3,1, +5190,Storm Peaks,Portals,K3 to Garm's Rise,1, +5191,Dragonblight,To Naxxramas 1,UNNAMED,1, +5192,Dragonblight,To Naxxramas 2,UNNAMED,1, +5193,Dragonblight,Portals,To Naxxramas 3,1, +5194,Dragonblight,Portals,To Naxxramas 4,1, +5200,Storm Peaks,Inns,Brunhildar Village Inn,1, +5204,Storm Peaks,Inns,Bouldercrag's Refuge Inn,1, +5205,Zul'Drak,Portals,To Gundrak South,1, +5206,Zul'Drak,Portals,To Gundrak North,1, +5209,Dalaran,Portals,To Violet Hold,1, +5215,Dragonblight,Portals,To Ahn'kahet,1, +5217,Sholazar Basin,Inns,Nesingwary Base Camp Inn,1, +5227,Icecrown,Inns,Argent Vanguard Inn,1, +5243,Dragonblight,Portals,To Obsidian Sanctum,1, +5245,Icecrown,World,Rise of Suffering,1, +5246,Borean Tundra,Portals,To the Oculus,1, +5248,Icecrown,Quests,Crusaders' Pinnacle,1, +5254,Zul'Drak,Portals,Zeramas to Ground,1, +5257,Dragonblight,Quests,Angrathar the Wrathgate,1, +5258,Wintergrasp,Portals,To Vault of Archavon,1, +5273,Dalaran,Portals,Well to Underbelly,1, +5274,Storm Peaks,Quests,UNNAMED,1, +5275,Storm Peaks,Quests,UNNAMED,1, +5280,Icecrown,UNKNOWN,UNNAMED,1, +5281,Icecrown,World,Cloak Dome Alliance,1, +5282,Icecrown,World,Cloak Dome Horde,1, +5283,Dalaran,World,Dalaran Airspace,1, +5284,Icecrown,Quests,Aldur'thar South,1, +5285,Icecrown,Quests,Aldur'thar Center,1, +5286,Icecrown,Quests,Aldur'thar Northeast,1, +5287,Icecrown,Quests,Aldur'thar Northwest,1, +5290,Borean Tundra,Portals,To the Eye of Eternity,1, +5294,Wintergrasp,UNKNOWN,UNNAMED,1, +5296,Wintergrasp,UNKNOWN,UNNAMED,1, +5298,Wintergrasp,UNKNOWN,UNNAMED,1, +5300,Wintergrasp,UNKNOWN,UNNAMED,1, +5302,Wintergrasp,UNKNOWN,UNNAMED,1, +5304,Wintergrasp,UNKNOWN,UNNAMED,1, +5305,Icecrown,UNKNOWN,Ymirheim 1,1, +5306,Icecrown,UNKNOWN,Ymirheim 2,1, +5307,Icecrown,UNKNOWN,Ymirheim 3,1, +5308,Icecrown,UNKNOWN,Ymirheim 4,1, +5309,Icecrown,Inns,The Shadow Vault Inn,1, +5310,Dalaran,Events,Winter Veil Thingy,1, +5312,Icecrown,UNKNOWN,Ymirheim 5,1, +5313,Icecrown,UNKNOWN,Ymirheim 6,1, +5314,Dragonblight,Inns,Wyrmrest Temple Inn 1,1, +5315,Dragonblight,Inns,Wyrmrest Temple Inn 2,1, +5316,Dragonblight,Inns,Wyrmrest Temple Inn 3,1, +5317,Dragonblight,Inns,Wyrmrest Temple Inn 4,1, +5318,Dragonblight,World,Wyrmrest Temple Bottom Entrance,1, +5323,Storm Peaks,Inns,Camp Tunka'lo,1, +5327,Dalaran,Quests,Krasus' Landing,1, +5332,Borean Tundra,Portals,To Naxxanar,1, +5334,Borean Tundra,Portals,Naxxanar to Borean Tundra,1, +5338,Borean Tundra,Portals,Naxxanar Top to Naxxanar,1, +5339,Borean Tundra,Quests,Naxxanar Inside to Naxxanar,1, +5340,Borean Tundra,Portals,Naxxanar to Naxxanar Top,1, +5341,Borean Tundra,Portals,Naxxanar to Naxxanar Inside,1, +5360,Storm Peaks,Inns,Grom'arsh Crash-Site Inn,1, +5379,Storm Peaks,Portals,To Ulduar,1, +5392,Icecrown,World,Broxel Goldgrasp,1, +5403,Icecrown,World,Stables,1, +5404,Icecrown,World,Stables 2,1, +5405,Icecrown,World,Stables 3,1, +5440,Borean Tundra,Portals,Fizzcrank Paratrooper Teleporter,1, +5460,Sholazar Basin,Portals,To Un'Goro Krater,1, +5461,Sholazar Basin,Portals,To Un'Goro Krater,1, +5500,Icecrown,World,Black Knight Grave,1, +5503,Icecrown,Portals,To Trial of the Crusader,1, +5505,Icecrown,Portals,To Trial of the Champion,1, +5635,Icecrown,Portals,To Forge of Souls,1, +5636,Icecrown,Portals,To Halls of Reflection,1, +5637,Icecrown,Portals,To Pit of Saron,1, +5650,Dragonblight,Quests,Frostmourne Cavern,1, +5670,Icecrown,Portals,To Icecrown Citadel,1, +5691,Dalaran,World,To Sewers 1,1, +5692,Dalaran,World,To Sewers 2,1, +5693,Dalaran,World,To Sewers 3,1, +5694,Dalaran,World,To Sewers 4,1, +5774,Dalaran,World,The Violet Gate,1, +5869,Dragonblight,Portals,To Ruby Sanctum,1, +4696,Lordaeron Arena,PVP,Pickup 1,1, +4697,Lordaeron Arena,PVP,Pickup 2,1, +4927,Lordaeron Arena,UNKNOWN,Outside 1,1, +4928,Lordaeron Arena,UNKNOWN,Outside 2,1, +4929,Lordaeron Arena,UNKNOWN,Outside 3,1, +4930,Lordaeron Arena,UNKNOWN,Outside 4,1, +4931,Lordaeron Arena,UNKNOWN,Outside 5,1, +4932,Lordaeron Arena,UNKNOWN,Outside 6,1, +4933,Lordaeron Arena,UNKNOWN,Outside 7,1, +4934,Lordaeron Arena,UNKNOWN,Outside 8,1, +4935,Lordaeron Arena,UNKNOWN,Outside 9,1, +4936,Lordaeron Arena,UNKNOWN,Outside 10,1, +4941,Lordaeron Arena,UNKNOWN,Below 1,1, +5041,Lordaeron Arena,UNKNOWN,Below 2,1, +5042,Lordaeron Arena,UNKNOWN,Below 3,1, +4741,Utgarde Keep,Portals,To Howling Fjord,1, +4757,Utgarde Keep,UNKNOWN,Reavers' Hall,1, +4762,Utgarde Keep,UNKNOWN,Entrance 1,1, +4764,Utgarde Keep,UNKNOWN,Entrance 2,1, +4791,Utgarde Keep,UNKNOWN,Forgefire 1,1, +4792,Utgarde Keep,UNKNOWN,Forgefire 2,1, +4793,Utgarde Keep,UNKNOWN,Forgefire 3,1, +4838,Utgarde Keep,World,Tyr's Terrace,1, +4743,Utgarde Pinnacle,Portals,To Howling Fjord,1, +4991,Utgarde Pinnacle,World,Gauntlet,1, +5140,Utgarde Pinnacle,World,Observance Hall,1, +4954,The Nexus,World,Entrance 1,1, +4955,The Nexus,World,Entrance 2,1, +4981,The Nexus,Portals,To Borean Tundra,1, +5004,The Nexus,World,Below,1, +5001,The Oculus,Portals,To Northrend,1, +4853,The Sunwell,World,Brutallus,1, +4891,The Sunwell,Portals,To Sunwell Plateau,1, +4937,The Sunwell,World,Eredar Twins,1, +5626,The Sunwell,UNKNOWN,UNNAMED,1, +5652,The Sunwell,UNKNOWN,To Endboss 1,1, +5664,The Sunwell,UNKNOWN,To Endboss 2,1, +5681,The Sunwell,World,Entrance 1,1, +5682,The Sunwell,World,Entrance 2,1, +5684,The Sunwell,World,Endboss Entrance 1,1, +5685,The Sunwell,World,Endboss Entrance,1, +4849,Magister's Terrace,Quest,Solar Vigil,1, +4885,Magister's Terrace,Portals,To Sunwell Plateau,1, +5085,Culling of Stratholme,World,To Stratholme,1, +5148,Culling of Stratholme,Portals,To Tanaris,1, +5181,Culling of Stratholme,Portals,To Tanaris Exit,1, +5249,Culling of Stratholme,World,Fras Siabi's Premium Tabacco,1, +5250,Culling of Stratholme,World,Leeka's Shields & Maces,1, +5251,Culling of Stratholme,World,The Stone Crow Tavern,1, +5252,Culling of Stratholme,World,Aaren's Flowers,1, +5253,Culling of Stratholme,World,Angelista's Boutique,1, +5256,Culling of Stratholme,World,To burning city,1, +5291,Culling of Stratholme,World,Cellar RP,1, +5795,Culling of Stratholme,World,Stratholme Entrance,1, +5012,Halls of Stone,Portals,To Northrend,1, +5000,Drak'Tharon Keep,Portals,To Northrend,1, +5113,Azjol-Nerub,Portals,To Dragonblight Exit,1, +5115,Azjol-Nerub,Portals,To Dragonblight,1, +5292,Azjol-Nerub,World,To Endboss,1, +5082,Halls of Lightning,UNKNOWN,UNNAMED,1, +5083,Halls of Lightning,UNKNOWN,UNNAMED,1, +5084,Halls of Lightning,UNKNOWN,UNNAMED,1, +5091,Halls of Lightning,Portals,To Storm Peaks,1, +5357,Ulduar,World,Thorim,1, +5358,Ulduar,World,Entrance 1,1, +5359,Ulduar,World,Entrance,1, +5366,Ulduar,UNKNOWN,UNNAMED,1, +5369,Ulduar,World,Repair-o-matic Station 1,1, +5381,Ulduar,Portals,To Storm Peaks,1, +5388,Ulduar,World,Expedition Base Camp,1, +5390,Ulduar,World,The Shattered Walkway Killzone,1, +5398,Ulduar,World,UNNAMED,1, +5399,Ulduar,World,The Shattered Walkway,1, +5400,Ulduar,World,To Celestial Planetarium 1,1, +5401,Ulduar,World,To Celestial Planetarium 2,1, +5402,Ulduar,World,Tram Killzone,1, +5414,Ulduar,World,UNNAMED,1, +5415,Ulduar,World,To Tower of Flames,1, +5416,Ulduar,World,To Tower of Frost,1, +5417,Ulduar,World,To Tower of Frost,1, +5420,Ulduar,Portals,Ulduar Teleporter Main Entrance,1, +5423,Ulduar,World,Repair-o-matic Station 2,1, +5426,Ulduar,World,UNNAMED,1, +5427,Ulduar,World,Colossal Forge,1, +5428,Ulduar,World,UNNAMED,1, +5432,Ulduar,World,Tram Rail Killzone,1, +5433,Ulduar,World,Below Celestial Planetarium,1, +5442,Ulduar,World,UNNAMED,1, +5443,Ulduar,World,Gauntlet Begin,1, +5231,Gundrak,Portals,To Zul'Drak South,1, +5233,Gundrak,Portals,To Zul'Drak Exit North,1, +5276,Gundrak,Portals,To Zul'Drak Exit South,1, +5277,Gundrak,Portals,To Zul'Drak North,1, +5354,Strand of the Ancients,PVP,Workshop East,1, +5355,Strand of the Ancients,PVP,Workshop West,1, +5356,Strand of the Ancients,PVP,Seaforium Pile,1, +5211,Violet Hold,Portals,To Northrend,1, +5017,Ebon Hold,UNKNOWN,UNNAMED,1, +5029,Ebon Hold,UNKNOWN,UNNAMED,1, +5086,Ebon Hold,World,Scarlet Tavern 1,1, +5087,Ebon Hold,UNKNOWN,Crypt of Remembrance,1, +5088,Ebon Hold,UNKNOWN,Scarlet Tavern 2,1, +5089,Ebon Hold,UNKNOWN,Scarlet Tavern 3,1, +5094,Ebon Hold,UNKNOWN,UNNAMED,1, +5099,Ebon Hold,UNKNOWN,UNNAMED,1, +5100,Ebon Hold,UNKNOWN,UNNAMED,1, +5101,Ebon Hold,UNKNOWN,UNNAMED,1, +5102,Ebon Hold,UNKNOWN,UNNAMED,1, +5103,Ebon Hold,UNKNOWN,UNNAMED,1, +5104,Ebon Hold,UNKNOWN,UNNAMED,1, +5105,Ebon Hold,UNKNOWN,UNNAMED,1, +5106,Ebon Hold,UNKNOWN,UNNAMED,1, +5107,Ebon Hold,UNKNOWN,UNNAMED,1, +5228,Obsidian Sanctum,UNKNOWN,UNNAMED,1, +5229,Obsidian Sanctum,UNKNOWN,UNNAMED,1, +5241,Obsidian Sanctum,Portals,To Dragonblight,1, +5342,Eye of Eternity,Portals,To Northrend,1, +5326,Dalaran Arena,UNKNOWN,Below 1,1, +5328,Dalaran Arena,UNKNOWN,Out of Bounds 1,1, +5329,Dalaran Arena,UNKNOWN,Out of Bounds 2,1, +5330,Dalaran Arena,UNKNOWN,Out of Bounds 3,1, +5331,Dalaran Arena,UNKNOWN,Out of Bounds 4,1, +5343,Dalaran Arena,UNKNOWN,Below 2,1, +5344,Dalaran Arena,UNKNOWN,Below 3,1, +5347,Dalaran Arena,UNKNOWN,Pllayer 1,1, +5348,Dalaran Arena,UNKNOWN,Pllayer 2,1, +5224,Ring of Valor,UNKNOWN,Player 1,1, +5226,Ring of Valor,UNKNOWN,Player 2,1, +5447,Ring of Valor,UNKNOWN,Below,1, +5473,Ring of Valor,UNKNOWN,Grate 1,1, +5474,Ring of Valor,UNKNOWN,Grate 2,1, +5213,Ahn'kahet,Portals,To Dragonblight,1, +5235,Ahn'kahet,Portals,To Dragonblight Exit,1, +5322,Ahn'kahet,World,UNNAMED,1, +5262,Vault of Archavon,Portals,To Northrend,1, +5530,Isle of Conquest,UNKNOWN,Lighthouse,1, +5535,Isle of Conquest,UNKNOWN,Horde Keep,1, +5536,Isle of Conquest,UNKNOWN,Alliance Keep,1, +5555,Isle of Conquest,UNKNOWN,Alliance Keep 2,1, +5604,Icecrown Citadel,World,Frost Queen's Lair,1, +5608,Icecrown Citadel,World,Entrance 1,1, +5609,Icecrown Citadel,World,Entrance 2,1, +5611,Icecrown Citadel,World,Light's Hammer Exit 1,1, +5612,Icecrown Citadel,World,Light's Hammer Exit 2,1, +5616,Icecrown Citadel,World,Spire to Frostwing Halls 1,1, +5617,Icecrown Citadel,World,Spire to Frostwing Halls 2,1, +5618,Icecrown Citadel,World,Spire to Frostwing Halls 3,1, +5619,Icecrown Citadel,World,Frostwing Halls 1,1, +5623,Icecrown Citadel,World,Frostwing Halls Lower,1, +5628,Icecrown Citadel,World,Rampart of Skulls 1,1, +5629,Icecrown Citadel,World,Rampart of Skulls 2,1, +5630,Icecrown Citadel,World,Rampart of Skulls 3,1, +5631,Icecrown Citadel,World,Rampart of Skulls 4,1, +5647,Icecrown Citadel,World,Putricide's Laboratory Entrance,1, +5649,Icecrown Citadel,World,Deathbringer Rise to Spire 1,1, +5668,Icecrown Citadel,Portals,To Icecrown,1, +5698,Icecrown Citadel,Portals,To Spire 1,1, +5700,Icecrown Citadel,UNKNOWN,UNNAMED,1, +5708,Icecrown Citadel,World,Crimson Hall Boss,1, +5709,Icecrown Citadel,World,Lady Deathwisper,1, +5718,Icecrown Citadel,Portals,The Spire,1, +5729,Icecrown Citadel,World,Crimson Hall Entrance,1, +5730,Icecrown Citadel,World,Deathbringer Rise to Spire 2,1, +5732,Icecrown Citadel,World,To Lord Marrowgar,1, +5736,Icecrown Citadel,World,Entrance 3,1, +5744,Icecrown Citadel,World,To the Plagueworks,1, +5745,Icecrown Citadel,World,Spire to Crimson Hall,1, +5746,Icecrown Citadel,World,Spire to Deathbringer Rise 1,1, +5747,Icecrown Citadel,World,Spire to Deathbringer Rise 2,1, +5748,Icecrown Citadel,World,Spire to Deathbringer Rise 3,1, +5749,Icecrown Citadel,World,Frostwing Halls 2,1, +5772,Icecrown Citadel,Portals,To Spire 2,1, +5642,Forge of Souls,Portals,To Icecrown,1, +5672,Forge of Souls,World,Devourer of Souls,1, +5688,Forge of Souls,Portals,To Pit of Saron,1, +5475,Trial of the Crusader,World,Endboss,1, +5508,Trial of the Crusader,Portals,To Icecrown,1, +5562,Trial of the Crusader,UNKNOWN,UNNAMED,1, +5491,Trial of the Cchampion,World,Entrance 1,1, +5492,Trial of the Cchampion,World,Entrance 2,1, +5510,Trial of the Cchampion,Portals,To Icecrown,1, +5573,Pit of Saron,World,Instance Entry,1, +5578,Pit of Saron,World,To Cave 1,1, +5579,Pit of Saron,World,To Cave 2,1, +5580,Pit of Saron,World,Gauntlet Entrance,1, +5581,Pit of Saron,World,Gauntlet Exit,1, +5582,Pit of Saron,World,Gauntlet 1,1, +5583,Pit of Saron,World,Gauntlet 2,1, +5584,Pit of Saron,World,Gauntlet 3,1, +5585,Pit of Saron,World,Gauntlet 4,1, +5586,Pit of Saron,World,Gauntlet 5,1, +5587,Pit of Saron,World,Gauntlet 6,1, +5588,Pit of Saron,World,UNNAMED,1, +5589,Pit of Saron,World,UNNAMED,1, +5593,Pit of Saron,World,UNNAMED,1, +5594,Pit of Saron,World,UNNAMED,1, +5595,Pit of Saron,World,UNNAMED,1, +5596,Pit of Saron,World,UNNAMED,1, +5597,Pit of Saron,World,UNNAMED,1, +5598,Pit of Saron,World,UNNAMED,1, +5599,Pit of Saron,World,UNNAMED,1, +5600,Pit of Saron,World,UNNAMED,1, +5601,Pit of Saron,World,UNNAMED,1, +5602,Pit of Saron,World,UNNAMED,1, +5633,Pit of Saron,World,Endboss,1, +5643,Pit of Saron,Portals,To Icecrown,1, +5683,Pit of Saron,Portals,To Halls of Reflection,1, +5570,Halls of Reflection,World,UNNAMED,1, +5605,Halls of Reflection,World,UNNAMED,1, +5632,Halls of Reflection,World,Entrance 1,1, +5646,Halls of Reflection,Portals,To Icecrown,1, +5660,Halls of Reflection,World,Entrance 3,1, +5689,Halls of Reflection,World,Entrance 2,1, +5697,Halls of Reflection,World,Frostmourne,1, +5740,Halls of Reflection,World,UNNAMED,1, +5742,Halls of Reflection,World,UNNAMED,1, +5752,Halls of Reflection,World,UNNAMED,1, +5867,Ruby Sanctum,World,Baltharus,1, +5872,Ruby Sanctum,Portals,To Dragonblight,1, diff --git a/resources/noggit_font.ttf b/resources/noggit_font.ttf index 1ee856b5ff98e92399465cd9e8e22582678ff44b..22d9a0ef3f33d0eba33905213cb85739242c3cb1 100644 GIT binary patch delta 3661 zcmcImU2GKB6~5=r?9A?Z|Gdo3tk?FkJG0(dmu0*=yPk3FWdQ@WD@X|7w%`PT3pOT% zwSx^cwDFj~fFTJcj`L7x6fpE5QX*8NDk@UrR%#!rJhU%~)E}u7DM3{p(pI4~jd0Jb ze}XDf+K5=|x%d0dx#!$_zB5-d^8uWg1&aV7#7jmAL!M}B>wN6BUlmUhV)Y?g)4q0H z8>uI!5qBVtZrswKw}H%l3uEzNLQ0?Y^>pu!ow)uM+Wvt5Mqgi#)y+21UtmB1iA3N2q2YgZ#YMz#V?gei z{$1T%_lGA4;R48U`@4q+z{4hR-h}?KQuqFzAOHEUnD7v;XDbH!2ZtvA_};sO9ElO) zh?WnB2Yf$$eQJll@Cm7)xQvkB->N9u<1NqHzR>KQ*&B3}P9b)ZIXlM3-^}a`Zen!y zj{Pi$vezv99W67**f{B!XoYQXbNV9G&?XGvnEglD3Abg}g4E2dg$_h^wQC3qX&jvk z`Tj)i-kiS8gn$_(N2fny-IUG#+8mkY_^GWhGyTSnF^Vf^rj75614qd0&tT2;ySpv{ zyfabi8Fbu$VVIftpl1SIj`R*t+``01egCvL+{xeJ6PU!j0tsW@7-`0H%}{)|B9_fXRDziJyngHI`?ZW^Q!nxHyeMK{vzbU(G| zIeLjs(_hl}>4)?V<6xFBai)Q3X4W#BnVn3D8DU;w-e6{!_n42FyDVXyY=8~3u`jBg ztjIK{=j5O%E1U*)QGFIf;XYVK{G8ELd^Y5=oJLI}%SEZl;X0h>~g#@_NpQ0>{fr(L%-UgJopS z8Tnk%hDO$iQl85i8s!aKhjT@rL+FTz&zQE2XdaqSHLJ=(Nb-9Z;l_E+u^wmnHjZv$5iN|Sish+2i-JDax3b-(4?qI;blGMXzA+}p6DtG|iT?^n#h^!9>R!k%S!Yk{!Fb-U5WdGpGZbs0u3c z#6T-R>UeLU9!>TBJ;&DpBwrd)>JZi`Bj@U%3Tn^4ys8#zS5BO70N?YMc6hoW$!30$Y#!@((IZ&0dq4TN3 zcD~e+r+7@CfVLjoE+l%|XaQj6mh?k=>Q}9NoRt-Tz9z+chxR?>qRTgZOjM zq02**4qh3gerM@o$q76@d8i8Zp6dxhuy?#S2x?u|NEa3&(KXtYfVE0`{nP8y3d6+f z*YDdQl|BRI@{<5ri5s6b<3;E7r$)m_T2#KGwqo z`a*qdVQhI&B)_YFa==v<$?<;*WIa@&hY0ka-wq8heh9hNMX-EQ? zRU1w;Az_}}Q1vv>m8+EltAN6)L8Wyie4WDT9#7|L3b6W{o}N~dLgBflbOCW;dDBQ? zIZ*xdc1IQ$=#K4UdJNPv!_g!pqr+#DB2-(a&J?Plx^U*y*P!y)tD6H*x%t)O5pX^C z!(I3(wClC!{J;)hF4=GE<-^>et3v=oR}b;_*AvGCHcT3YZp=|W*^uDv}+_4an1rk$>AdwbU91Ph!_y{%2hc*_EThBa#%0s#Pj zAhTvoCg6v9v1(bdxU5P95X0$WF&!46E+VUjp~?}=7gtSFjYlBnQo}J#i-lDezTm19 z*R;5V^QfLk=*qY3XQE3EhvjgKQdOmdn=3t@N>4QCwY)(uJbrHg#{YNC9;IZ~m(c7{ zN@mS#hFaNIm95({)XG-O)@}KNG<%ei8DBy(lx+IeUX6ugQ^69j*h8b!!T#G!fInE; a+t#!$Ge2f0K0QziM<>FA3HFw!FZ6F|5t0l5 delta 495 zcmXv~K}#D^5dP-v#w1N^QJP+wiiI33D3%_?f(V9mh1Rx$9xN1eO*d|-t4kwdDs=ZC zh4xff=t&AS|G=jpIcReAkUwA`!AS9@rLH{e6V zq+v`y+uX<}fgBqWeJ6xEdvMkPi z5*l$}%zvQ{R<}=SS + + +image/svg+xml + + + + + + + + + + Page-1 + + + Rectangle + + + + + + + Rectangle.2 + + + + + + + Sheet.3 + Rapid + + + + Rapid + + + Sheet.4 + Fuzz + + + + Fuzz + + + diff --git a/src/external/rapidfuzz-cpp/.github/workflows/cmake.yml b/src/external/rapidfuzz-cpp/.github/workflows/cmake.yml new file mode 100644 index 00000000..8d2392cc --- /dev/null +++ b/src/external/rapidfuzz-cpp/.github/workflows/cmake.yml @@ -0,0 +1,202 @@ +name: CMake + +on: [push, pull_request] + +env: + BUILD_TYPE: Release + +jobs: + build_linux_clang: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + BUILD_TYPE: [Release, Debug] + + steps: + - uses: actions/checkout@v2 + + - name: Configure CMake + run: cmake -B build -DCMAKE_BUILD_TYPE=${{matrix.BUILD_TYPE}} -DRAPIDFUZZ_BUILD_TESTING=1 -DRAPIDFUZZ_ENABLE_LINTERS=1 -DRAPIDFUZZ_BUILD_FUZZERS=1 -DCMAKE_CXX_COMPILER=clang++ + + - name: Build + run: cmake --build build --config ${{matrix.BUILD_TYPE}} + + - name: Test + working-directory: build + run: ctest -C ${{matrix.BUILD_TYPE}} --rerun-failed --output-on-failure + + - name: Fuzz Test + working-directory: build + run: | + fuzzing/fuzz_lcs_similarity -max_total_time=30 + fuzzing/fuzz_levenshtein_distance -max_total_time=30 + fuzzing/fuzz_levenshtein_editops -max_total_time=30 + fuzzing/fuzz_indel_distance -max_total_time=30 + fuzzing/fuzz_indel_editops -max_total_time=30 + fuzzing/fuzz_osa_distance -max_total_time=30 + fuzzing/fuzz_damerau_levenshtein_distance -max_total_time=30 + + build_linux_clang_32: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + BUILD_TYPE: [Release, Debug] + env: + CXXFLAGS: -m32 + CFLAGS: -m32 + + steps: + - uses: actions/checkout@v2 + + - name: Install Dependencies + run: | + sudo apt update + sudo apt install -y libc6-dev-i386 g++-multilib + + - name: Configure CMake + run: cmake -B build -DCMAKE_BUILD_TYPE=${{matrix.BUILD_TYPE}} -DRAPIDFUZZ_BUILD_TESTING=1 -DRAPIDFUZZ_ENABLE_LINTERS=1 -DRAPIDFUZZ_BUILD_FUZZERS=1 -DCMAKE_CXX_COMPILER=clang++ + + - name: Build + run: cmake --build build --config ${{matrix.BUILD_TYPE}} + + - name: Test + working-directory: build + run: ctest -C ${{matrix.BUILD_TYPE}} --rerun-failed --output-on-failure + + - name: Fuzz Test + working-directory: build + run: | + fuzzing/fuzz_lcs_similarity -max_total_time=30 + fuzzing/fuzz_levenshtein_distance -max_total_time=30 + fuzzing/fuzz_levenshtein_editops -max_total_time=30 + fuzzing/fuzz_indel_distance -max_total_time=30 + fuzzing/fuzz_indel_editops -max_total_time=30 + fuzzing/fuzz_osa_distance -max_total_time=30 + fuzzing/fuzz_damerau_levenshtein_distance -max_total_time=30 + + build_linux_gcc: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + BUILD_TYPE: [Release, Debug] + + steps: + - uses: actions/checkout@v2 + + - name: Configure CMake + run: cmake -B build -DCMAKE_BUILD_TYPE=${{matrix.BUILD_TYPE}} -DRAPIDFUZZ_BUILD_TESTING=1 -DRAPIDFUZZ_ENABLE_LINTERS=1 -DCMAKE_CXX_COMPILER=g++ + + - name: Build + run: cmake --build build --config ${{matrix.BUILD_TYPE}} + + - name: Test + working-directory: build + run: ctest -C ${{matrix.BUILD_TYPE}} --rerun-failed --output-on-failure + + build_windows: + runs-on: windows-latest + strategy: + fail-fast: false + matrix: + BUILD_TYPE: [Release, Debug] + + steps: + - uses: actions/checkout@v2 + + - name: Configure CMake + run: cmake -B build -DCMAKE_BUILD_TYPE=${{matrix.BUILD_TYPE}} -DRAPIDFUZZ_BUILD_TESTING=1 -DRAPIDFUZZ_ENABLE_LINTERS=1 + + - name: Build + run: cmake --build build --config ${{matrix.BUILD_TYPE}} + + - name: Test + working-directory: build + run: ctest -C ${{matrix.BUILD_TYPE}} --rerun-failed --output-on-failure + + build_cmake_installed: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Configure CMake + run: cmake -B build -DCMAKE_BUILD_TYPE=Release + + - name: Install RapidFuzz + run: sudo cmake --build build --target install + + - name: Configure example project + working-directory: examples/cmake_installed + run: cmake -B build -DCMAKE_BUILD_TYPE=Release + + - name: Build example project + working-directory: examples/cmake_installed + run: cmake --build build --config ${{env.BUILD_TYPE}} + + - name: Run example project + working-directory: examples/cmake_installed/build + run: ./foo + + build_cmake_subdir: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + BUILD_TYPE: [Release, Debug] + + steps: + - uses: actions/checkout@v2 + + - name: Configure the library dependent on RapidFuzz + working-directory: examples/cmake_export + run: cmake -B build -DCMAKE_BUILD_TYPE=${{matrix.BUILD_TYPE}} + + - name: Build the library dependent on RapidFuzz + working-directory: examples/cmake_export + run: cmake --build build --config ${{matrix.BUILD_TYPE}} + + - name: Install the library dependent on RapidFuzz + working-directory: examples/cmake_export + run: sudo cmake --build build --target install + + - name: Configure the app indirectly dependent on RapidFuzz + working-directory: examples/cmake_export/indirect_app + run: cmake -B build -DCMAKE_BUILD_TYPE=${{matrix.BUILD_TYPE}} + + - name: Build the app indirectly dependent on RapidFuzz + working-directory: examples/cmake_export/indirect_app + run: cmake --build build --config ${{matrix.BUILD_TYPE}} + + - name: Run the app indirectly dependent on RapidFuzz + working-directory: examples/cmake_export/indirect_app/build + run: ./fooapp + + build_cpack_installed: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Configure CMake + run: cmake -B build -DCMAKE_BUILD_TYPE=Release + + - name: Install RapidFuzz + working-directory: build + run: | + cpack -G DEB + sudo dpkg -i *.deb + + - name: Configure example project + working-directory: examples/cmake_installed + run: cmake -B build -DCMAKE_BUILD_TYPE=Release + + - name: Build example project + working-directory: examples/cmake_installed + run: cmake --build build --config ${{env.BUILD_TYPE}} + + - name: Run example project + working-directory: examples/cmake_installed/build + run: ./foo diff --git a/src/external/rapidfuzz-cpp/.github/workflows/documentation.yml b/src/external/rapidfuzz-cpp/.github/workflows/documentation.yml new file mode 100644 index 00000000..aa6cdac7 --- /dev/null +++ b/src/external/rapidfuzz-cpp/.github/workflows/documentation.yml @@ -0,0 +1,18 @@ +name: documentation + +on: + push: + branches: + - main + +jobs: + build_docs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: sudo apt-get install -y doxygen + - run: doxygen ./Doxyfile + - uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./doxygen/html diff --git a/src/external/rapidfuzz-cpp/.gitignore b/src/external/rapidfuzz-cpp/.gitignore new file mode 100644 index 00000000..0d05ba22 --- /dev/null +++ b/src/external/rapidfuzz-cpp/.gitignore @@ -0,0 +1,16 @@ +.vscode/ +.cache/ +.idea/ +build/ +.cache/ +*.data +*.so +*.o +*.out +.vs/ + +CMakeCache.txt +CMakeFiles +CMakeScripts +Makefile +cmake_install.cmake diff --git a/src/external/rapidfuzz-cpp/CHANGELOG.md b/src/external/rapidfuzz-cpp/CHANGELOG.md new file mode 100644 index 00000000..b92b6632 --- /dev/null +++ b/src/external/rapidfuzz-cpp/CHANGELOG.md @@ -0,0 +1,224 @@ +## Changelog + +## [3.2.0] - 2024-12-17 +### Performance +- improve calculation of min score inside partial_ratio so it can skip more alignments + +## [3.1.1] - 2024-10-24 +### Fixed +- Fixed incorrect score calculation for SIMD implementations of Levenshtein and OSA on 32 bit systems + +## [3.1.0] - 024-10-24 +### Changed +- split `editops_apply`/`opcodes_apply` into `*_apply_str` and `*_apply_vec`. This avoids the instantiation of + std::basic_string for unsupported types. + +## [3.0.5] - 2024-07-02 +### Fixed +- the editops implementation didn't properly account for some cells in the Levenshtein matrix. + This could lead both to incorrect results and crashes. + +## [3.0.4] - 2023-04-07 +### Fixed +- fix tagged version + +## [3.0.3] - 2023-04-06 +### Fixed +- fix potentially incorrect results of JaroWinkler when using high prefix weights + +## [3.0.2] - 2023-03-04 +### Fixed +- fix assert leading to compilation failures + +## [3.0.1] - 2023-03-03 +### Fixed +- fix doxygen warnings + +## [3.0.0] - 2023-12-26 +### Performance +- add banded implementation of LCS / Indel. This improves the runtime from `O((|s1|/64) * |s2|)` to `O((score_cutoff/64) * |s2|)` + +### Changed +- changed many types in the interface from int64_t to size_t, since they can't be negative. + +### Fixed +- fix incorrect transposition calculation in simd implementation of Jaro similarity +- use posix_memalign on android + +## [2.2.3] - 2023-11-02 +### Fixed +- use _mm_malloc/_mm_free on macOS if aligned_alloc is unsupported + +## [2.2.2] - 2023-10-31 +### Fixed +- fix compilation failure on macOS + +## [2.2.1] - 2023-10-31 +### Fixed +- fix wraparound issue in simd implementation of Jaro and Jaro Winkler + +## [2.2.0] - 2023-10-30 +#### Performance +- improve performance of simd implementation for LCS and Indel by up to 50% +- improve performance of simd implementation for Jaro and Jaro Winkler +- improve performance of Jaro and Jaro Winkler for long sequences + +## [2.1.1] - 2023-10-08 +### Fixed +- fix edge case in new simd implementation of Jaro and Jaro Winkler + +## [2.1.0] - 2023-10-08 +### Changed +- add support for bidirectional iterators +- add experimental simd implementation for Jaro and Jaro Winkler + +### [2.0.0] - 2023-06-02 +#### Changed +- added argument ``pad`` to Hamming distance. This controls whether sequences of different + length should be padded or lead to a `std::invalid_argument` exception. +- improve behaviour when including the project as cmake sub project + +### [1.11.3] - 2023-04-18 +#### Fixed +- add missing include leading to build failures on gcc 13 + +### [1.11.2] - 2023-04-17 +#### Fixed +- fix handling of `score_cutoff > 1.0` in `Jaro` and `JaroWinkler` + +### [1.11.1] - 2023-04-16 +#### Fixed +- fix division by zero in simd implementation of normalized string metrics, when comparing empty strings + +### [1.11.0] - 2023-04-16 +#### Changed +- allow the usage of hamming for different string lengths. Length differences are handled as + insertions / deletions + +#### Fixed +- fix some floating point comparisions in the test suite + +### [1.10.4] - 2022-12-14 +#### Changed +- Linters are now disabled in test builds by default and can be enabled using `RAPIDFUZZ_ENABLE_LINTERS` + +### [1.10.3] - 2022-12-13 +#### Fixed +- fix warning about `project_options` when building the test suite with `cmake>=3.24` + +### [1.10.2] - 2022-12-01 +#### Fixed +- `fuzz::partial_ratio` was not always symmetric when `len(s1) == len(s2)` +- fix undefined behavior in experimental SIMD implementaton + +### [1.10.1] - 2022-11-02 +#### Fixed +- fix broken sse2 support + +### [1.10.0] - 2022-10-29 +#### Fixed +- fix bug in `Levenshtein.editops` leading to crashes when used with `score_hint` + +#### Changed +- add `score_hint` argument to cached implementations +- add `score_hint` argument to Levenshtein functions + +### [1.9.0] - 2022-10-22 +#### Added +- added `Prefix`/`Postfix` similarity + +### [1.8.0] - 2022-10-02 +#### Fixed +- fixed incorrect score_cutoff handling in `lcs_seq_distance` + +#### Added +- added experimental simd support for `ratio`/`Levenshtein`/`LCSseq`/`Indel` +- add Jaro and JaroWinkler + +### [1.7.0] - 2022-09-18 +#### Added +- add editops to hamming distance + +#### Performance +- strip common affix in osa distance + +### [1.6.0] - 2022-09-16 +#### Added +- add optimal string alignment (OSA) alignment + +### [1.5.0] - 2022-09-11 +#### Fix +- `fuzz::partial_ratio` did not find the optimal alignment in some edge cases + +#### Performance +- improve performance of `fuzz::partial_ratio` + +### [1.4.1] - 2022-09-11 +#### Fixed +- fix type mismatch error + +### [1.4.0] - 2022-09-10 +#### Performance +- improve performance of Levenshtein distance/editops calculation for long + sequences when providing a `score_cutoff`/`score_hint` + +### [1.3.0] - 2022-09-03 +#### Performance +- improve performance of Levenshtein distance + - improve performance when `score_cutoff = 1` + - improve performance for long sequences when `3 < score_cutoff < 32` +- improve performance of Levenshtein editops + +#### Fixed +- fix incorrect results of partial_ratio for long needles + +### [1.2.0] - 2022-08-20 +#### Added +- added damerau levenshtein implementation + - Not API stable yet, since it will be extended with weights in a future version + +### [1.1.1] - 2022-07-29 +#### Performance +- improve performance for banded Levenshtein implementation + +### [1.1.0] - 2022-07-29 +#### Fixed +- fix banded Levenshtein implementation + +#### Changed +- implement Hirschbergs algorithms to reduce memory usage of + levenshtein_editops + +### [1.0.5] - 2022-07-23 +#### Fixed +- fix opcode conversion for empty source sequence + +### [1.0.4] - 2022-06-29 +#### Fixed +- fix implementation of hamming_normalized_similarity +- fix implementation of CachedLCSseq::distance + +### [1.0.3] - 2022-06-24 +#### Fixed +- fix integer wraparound in partial_ratio/partial_ratio_alignment + +### [1.0.2] - 2022-06-11 +#### Fixed +- fix unlimited recursion in CachedLCSseq::similarity +- reduce compiler warnings + +### [1.0.1] - 2022-04-16 +#### Fixed +- fix undefined behavior in sorted_split incrementing iterator past the end +- fix use after free in editops calculation +- reduce compiler warnings + +### [1.0.1] - 2022-04-16 +#### Added +- added LCSseq (longest common subsequence) implementation + +#### Fixed +- reduced compiler warnings +- consider float imprecision in score_cutoff +- fix incorrect score_cutoff handling in token_set_ratio and token_ratio +- fix template deduction guides on MSVC diff --git a/src/external/rapidfuzz-cpp/CMakeLists.txt b/src/external/rapidfuzz-cpp/CMakeLists.txt new file mode 100644 index 00000000..5c1cead8 --- /dev/null +++ b/src/external/rapidfuzz-cpp/CMakeLists.txt @@ -0,0 +1,143 @@ +# Cmake config largely taken from catch2 +cmake_minimum_required(VERSION 3.5) + +if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.24) + cmake_policy(SET CMP0135 NEW) +endif() + +# detect if Catch is being bundled, +# disable testsuite in that case +if(NOT DEFINED PROJECT_NAME) + set(NOT_SUBPROJECT ON) + # If RapidFuzz is not being used as a subproject via `add_subdirectory`, + # usually installation is required + option(RAPIDFUZZ_INSTALL "Install rapidfuzz" ON) +else() + set(NOT_SUBPROJECT OFF) + # If RapidFuzz is being used as a subproject via `add_subdirectory`, + # chances are that the "main project" does not include RapidFuzz headers + # in any of its headers, in which case installation is not needed. + option(RAPIDFUZZ_INSTALL "Install rapidfuzz (Projects embedding rapidfuzz may want to turn this OFF.)" OFF) +endif() + +option(RAPIDFUZZ_BUILD_TESTING "Build tests" OFF) +option(RAPIDFUZZ_ENABLE_LINTERS "Enable Linters for the test builds" OFF) +option(RAPIDFUZZ_BUILD_BENCHMARKS "Build benchmarks" OFF) +option(RAPIDFUZZ_BUILD_FUZZERS "Build fuzzers" OFF) + +# RapidFuzz's build breaks if done in-tree. You probably should not build +# things in tree anyway, but we can allow projects that include RapidFuzz +# as a subproject to build in-tree as long as it is not in our tree. +if (CMAKE_BINARY_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) + message(FATAL_ERROR "Building in-source is not supported! Create a build dir and remove ${CMAKE_SOURCE_DIR}/CMakeCache.txt") +endif() + +project(rapidfuzz LANGUAGES CXX VERSION 3.2.0) + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") +include(GNUInstallDirs) +include(CMakePackageConfigHelpers) + +# Basic paths +set(BASE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) +set(SOURCES_DIR ${BASE_DIR}/rapidfuzz) +set(TEST_DIR ${BASE_DIR}/test) +set(BENCHMARK_DIR ${BASE_DIR}/tests/bench) +set(EXAMPLES_DIR ${BASE_DIR}/examples) + +add_library(rapidfuzz INTERFACE) + +# provide a namespaced alias for clients to 'link' against if RapidFuzz is included as a sub-project +add_library(rapidfuzz::rapidfuzz ALIAS rapidfuzz) + +target_compile_features(rapidfuzz INTERFACE cxx_std_17) + +target_include_directories(rapidfuzz + INTERFACE + $ + $ +) + +# Build tests only if requested +if(RAPIDFUZZ_BUILD_TESTING AND NOT_SUBPROJECT) + include(CTest) + enable_testing() + add_subdirectory(test) +endif() + +# Build examples only if requested +if(RAPIDFUZZ_BUILD_EXAMPLES) + #add_subdirectory(examples) +endif() + +# Build benchmarks only if requested +if(RAPIDFUZZ_BUILD_BENCHMARKS) + add_subdirectory(bench) +endif() + +# Build fuzz tests only if requested +if(RAPIDFUZZ_BUILD_FUZZERS) + add_subdirectory(fuzzing) +endif() + +if (RAPIDFUZZ_INSTALL) + set(RAPIDFUZZ_CMAKE_CONFIG_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/rapidfuzz") + + install( + TARGETS + rapidfuzz + EXPORT + rapidfuzzTargets + DESTINATION + ${CMAKE_INSTALL_LIBDIR} + ) + + install( + EXPORT + rapidfuzzTargets + NAMESPACE + rapidfuzz:: + DESTINATION + ${RAPIDFUZZ_CMAKE_CONFIG_DESTINATION} + ) + + install( + DIRECTORY + rapidfuzz + DESTINATION + ${CMAKE_INSTALL_INCLUDEDIR} + FILES_MATCHING + PATTERN "*.hpp" + PATTERN "*.impl" + ) + + configure_package_config_file( + ${CMAKE_CURRENT_LIST_DIR}/cmake/${PROJECT_NAME}Config.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake + INSTALL_DESTINATION ${RAPIDFUZZ_CMAKE_CONFIG_DESTINATION} + ) + + write_basic_package_version_file( + "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake" + COMPATIBILITY SameMajorVersion + ) + + install( + FILES + "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake" + DESTINATION + ${RAPIDFUZZ_CMAKE_CONFIG_DESTINATION} + ) + + # CPack/CMake started taking the package version from project version 3.12 + # So we need to set the version manually for older CMake versions + if(${CMAKE_VERSION} VERSION_LESS "3.12.0") + set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION}) + endif() + + set(CPACK_PACKAGE_VENDOR "Max Bachmann") + set(CPACK_PACKAGE_CONTACT "https://github.com/rapidfuzz/rapidfuzz-cpp") + include(CPack) + +endif(RAPIDFUZZ_INSTALL) diff --git a/src/external/rapidfuzz-cpp/Doxyfile b/src/external/rapidfuzz-cpp/Doxyfile new file mode 100644 index 00000000..3269c5a7 --- /dev/null +++ b/src/external/rapidfuzz-cpp/Doxyfile @@ -0,0 +1,105 @@ +# Doxyfile 1.8.20 + +PROJECT_NAME = RapidFuzz + +OUTPUT_DIRECTORY = doxygen + + + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = YES + + +EXTRACT_PRIVATE = YES + + + +EXTRACT_STATIC = YES + + +HIDE_UNDOC_MEMBERS = YES + +HIDE_UNDOC_CLASSES = YES + + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = YES + + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = NO + +SHOW_FILES = NO + + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = docs/literature/hyrro_lcs_2004 \ + docs/literature/hyrro_2002 \ + docs/literature/hyrro_2004 \ + docs/literature/myers_1999 \ + docs/literature/wagner_fischer_1974 + + +EXTRA_PACKAGES = amsmath xr amsfonts + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +INPUT = rapidfuzz + +FILE_PATTERNS = *.c \ + *.cxx \ + *.cpp \ + *.h \ + *.hpp \ + *.md + +#--------------------------------------------------------------------------- +# Configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +GENERATE_LATEX = NO + +HAVE_DOT = YES diff --git a/src/external/rapidfuzz-cpp/LICENSE b/src/external/rapidfuzz-cpp/LICENSE new file mode 100644 index 00000000..5955fc63 --- /dev/null +++ b/src/external/rapidfuzz-cpp/LICENSE @@ -0,0 +1,21 @@ +Copyright © 2020 Max Bachmann +Copyright © 2011 Adam Cohen + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/src/external/rapidfuzz-cpp/README.md b/src/external/rapidfuzz-cpp/README.md new file mode 100644 index 00000000..57b563f6 --- /dev/null +++ b/src/external/rapidfuzz-cpp/README.md @@ -0,0 +1,242 @@ +

+RapidFuzz +

+

Rapid fuzzy string matching in C++ using the Levenshtein Distance

+ +

+ + Continuous Integration + + + Documentation + + + GitHub license + +

+ +

+ Description • + Installation • + Usage • + License +

+ +--- +## Description +RapidFuzz is a fast string matching library for Python and C++, which is using the string similarity calculations from [FuzzyWuzzy](https://github.com/seatgeek/fuzzywuzzy). However, there are two aspects that set RapidFuzz apart from FuzzyWuzzy: +1) It is MIT licensed so it can be used whichever License you might want to choose for your project, while you're forced to adopt the GPL license when using FuzzyWuzzy +2) It is mostly written in C++ and on top of this comes with a lot of Algorithmic improvements to make string matching even faster, while still providing the same results. More details on these performance improvements in the form of benchmarks can be found [here](https://github.com/rapidfuzz/rapidfuzz/blob/master/Benchmarks.md) + +The Library is split across multiple repositories for the different supported programming languages: +- The C++ version is versioned in this repository +- The Python version can be found at [rapidfuzz/rapidfuzz](https://github.com/rapidfuzz/rapidfuzz) + + +## CMake Integration + +There are severals ways to integrate `rapidfuzz` in your CMake project. + +### By Installing it +```bash +git clone https://github.com/rapidfuzz/rapidfuzz-cpp.git rapidfuzz-cpp +cd rapidfuzz-cpp +mkdir build && cd build +cmake .. -DCMAKE_BUILD_TYPE=Release +cmake --build . +cmake --build . --target install +``` + +Then in your CMakeLists.txt: +```cmake +find_package(rapidfuzz REQUIRED) +add_executable(foo main.cpp) +target_link_libraries(foo rapidfuzz::rapidfuzz) +``` + +### Add this repository as a submodule +```bash +git submodule add https://github.com/rapidfuzz/rapidfuzz-cpp.git 3rdparty/RapidFuzz +``` +Then you can either: + +1. include it as a subdirectory + ```cmake + add_subdirectory(3rdparty/RapidFuzz) + add_executable(foo main.cpp) + target_link_libraries(foo rapidfuzz::rapidfuzz) + ``` +2. build it at configure time with `FetchContent` + ```cmake + FetchContent_Declare( + rapidfuzz + SOURCE_DIR ${CMAKE_SOURCE_DIR}/3rdparty/RapidFuzz + PREFIX ${CMAKE_CURRENT_BINARY_DIR}/rapidfuzz + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH= "${CMAKE_OPT_ARGS}" + ) + FetchContent_MakeAvailable(rapidfuzz) + add_executable(foo main.cpp) + target_link_libraries(foo PRIVATE rapidfuzz::rapidfuzz) + ``` +### Download it at configure time + +If you don't want to add `rapidfuzz-cpp` as a submodule, you can also download it with `FetchContent`: +```cmake +FetchContent_Declare(rapidfuzz + GIT_REPOSITORY https://github.com/rapidfuzz/rapidfuzz-cpp.git + GIT_TAG main) +FetchContent_MakeAvailable(rapidfuzz) +add_executable(foo main.cpp) +target_link_libraries(foo PRIVATE rapidfuzz::rapidfuzz) +``` +It will be downloaded each time you run CMake in a blank folder. + +## CMake option + +There are CMake options available: + +1. `RAPIDFUZZ_BUILD_TESTING` : to build test (default OFF and requires [Catch2](https://github.com/catchorg/Catch2)) +2. `RAPIDFUZZ_BUILD_BENCHMARKS` : to build benchmarks (default OFF and requires [Google Benchmark](https://github.com/google/benchmark)) +3. `RAPIDFUZZ_INSTALL` : to install the library to local computer + - When configured independently, installation is on. + - When used as a subproject, the installation is turned off by default. + - For library developers, you might want to toggle the behavior depending on your project. + - If your project is exported via `CMake`, turn installation on or export error will result. + - If your project publicly depends on `RapidFuzz` (includes `rapidfuzz.hpp` in header), + turn installation on or apps depending on your project would face include errors. + +## Usage +```cpp +#include +``` + +### Simple Ratio +```cpp +using rapidfuzz::fuzz::ratio; + +// score is 96.55171966552734 +double score = rapidfuzz::fuzz::ratio("this is a test", "this is a test!"); +``` + +### Partial Ratio +```cpp +// score is 100 +double score = rapidfuzz::fuzz::partial_ratio("this is a test", "this is a test!"); +``` + +### Token Sort Ratio +```cpp +// score is 90.90908813476562 +double score = rapidfuzz::fuzz::ratio("fuzzy wuzzy was a bear", "wuzzy fuzzy was a bear") + +// score is 100 +double score = rapidfuzz::fuzz::token_sort_ratio("fuzzy wuzzy was a bear", "wuzzy fuzzy was a bear") +``` + +### Token Set Ratio +```cpp +// score is 83.8709716796875 +double score = rapidfuzz::fuzz::token_sort_ratio("fuzzy was a bear", "fuzzy fuzzy was a bear") + +// score is 100 +double score = rapidfuzz::fuzz::token_set_ratio("fuzzy was a bear", "fuzzy fuzzy was a bear") +``` + +### Process +In the Python implementation, there is a module process, which is used to compare e.g. a string to a list of strings. +In Python, this both saves the time to implement those features yourself and can be a lot more efficient than repeated type +conversions between Python and C++. Implementing a similar function in C++ using templates is not easily possible and probably slower than implementing them on your own. That's why this section describes how users can implement those features with a couple of lines of code using the C++ library. + +### extract + +The following function compares a query string to all strings in a list of choices. It returns all +elements with a similarity over score_cutoff. Generally make use of the cached implementations when comparing +a string to multiple strings. + + +```cpp +template +std::vector> +extract(const Sentence1& query, const Iterable& choices, const double score_cutoff = 0.0) +{ + std::vector> results; + + rapidfuzz::fuzz::CachedRatio scorer(query); + + for (const auto& choice : choices) { + double score = scorer.similarity(choice, score_cutoff); + + if (score >= score_cutoff) { + results.emplace_back(choice, score); + } + } + + return results; +} +``` + +### extractOne + +The following function compares a query string to all strings in a list of choices. + +```cpp +template +std::optional> +extractOne(const Sentence1& query, const Iterable& choices, const double score_cutoff = 0.0) +{ + bool match_found = false; + double best_score = score_cutoff; + Sentence2 best_match; + + rapidfuzz::fuzz::CachedRatio scorer(query); + + for (const auto& choice : choices) { + double score = scorer.similarity(choice, best_score); + + if (score >= best_score) { + match_found = true; + best_score = score; + best_match = choice; + } + } + + if (!match_found) { + return nullopt; + } + + return std::make_pair(best_match, best_score); +} +``` + +### multithreading + +It is very simple to use those scorers e.g. with open OpenMP to achieve better performance. + +```cpp +template +std::vector> +extract(const Sentence1& query, const Iterable& choices, const double score_cutoff = 0.0) +{ + std::vector> results(choices.size()); + + rapidfuzz::fuzz::CachedRatio scorer(query); + + #pragma omp parallel for + for (size_t i = 0; i < choices.size(); ++i) { + double score = scorer.similarity(choices[i], score_cutoff); + results[i] = std::make_pair(choices[i], score); + } + + return results; +} +``` + +## License +RapidFuzz is licensed under the MIT license since I believe that everyone should be able to use it without being forced to adopt the GPL license. That's why the library is based on an older version of fuzzywuzzy that was MIT-licensed as well. +This old version of fuzzywuzzy can be found [here](https://github.com/seatgeek/fuzzywuzzy/tree/4bf28161f7005f3aa9d4d931455ac55126918df7). diff --git a/src/external/rapidfuzz-cpp/SECURITY.md b/src/external/rapidfuzz-cpp/SECURITY.md new file mode 100644 index 00000000..5ad4a63b --- /dev/null +++ b/src/external/rapidfuzz-cpp/SECURITY.md @@ -0,0 +1,19 @@ +## Reporting Security Issues + +If you believe you have found a security vulnerability in the project, please report it to us through coordinated disclosure. + +**Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.** + +Instead, please send an email to oss@maxbachmann.de. + +Please include as much of the information listed below as you can to help us better understand and resolve the issue: + + * The type of issue (e.g., buffer overflow, SQL injection, or cross-site scripting) + * Full paths of source file(s) related to the manifestation of the issue + * The location of the affected source code (tag/branch/commit or direct URL) + * Any special configuration required to reproduce the issue + * Step-by-step instructions to reproduce the issue + * Proof-of-concept or exploit code (if possible) + * Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. diff --git a/src/external/rapidfuzz-cpp/bench/CMakeLists.txt b/src/external/rapidfuzz-cpp/bench/CMakeLists.txt new file mode 100644 index 00000000..2fcee921 --- /dev/null +++ b/src/external/rapidfuzz-cpp/bench/CMakeLists.txt @@ -0,0 +1,25 @@ +include(FetchContent) +FetchContent_Declare(googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG v1.12.x) + +FetchContent_Declare(googlebenchmark + GIT_REPOSITORY https://github.com/google/benchmark.git + GIT_TAG main) # need master for benchmark::benchmark + +FetchContent_MakeAvailable( + googletest + googlebenchmark) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native") + +function(rapidfuzz_add_benchmark NAME SOURCE) + add_executable(bench_${NAME} ${SOURCE}) + target_link_libraries(bench_${NAME} PRIVATE ${PROJECT_NAME}) + target_link_libraries(bench_${NAME} PRIVATE benchmark::benchmark) +endfunction() + +rapidfuzz_add_benchmark(lcs bench-lcs.cpp) +rapidfuzz_add_benchmark(fuzz bench-fuzz.cpp) +rapidfuzz_add_benchmark(levenshtein bench-levenshtein.cpp) +rapidfuzz_add_benchmark(jarowinkler bench-jarowinkler.cpp) diff --git a/src/external/rapidfuzz-cpp/bench/bench-fuzz.cpp b/src/external/rapidfuzz-cpp/bench/bench-fuzz.cpp new file mode 100644 index 00000000..7d66f623 --- /dev/null +++ b/src/external/rapidfuzz-cpp/bench/bench-fuzz.cpp @@ -0,0 +1,225 @@ +#include +#include +#include +#include + +using rapidfuzz::fuzz::partial_ratio; +using rapidfuzz::fuzz::partial_token_ratio; +using rapidfuzz::fuzz::partial_token_set_ratio; +using rapidfuzz::fuzz::partial_token_sort_ratio; +using rapidfuzz::fuzz::ratio; +using rapidfuzz::fuzz::token_ratio; +using rapidfuzz::fuzz::token_set_ratio; +using rapidfuzz::fuzz::token_sort_ratio; +using rapidfuzz::fuzz::WRatio; + +static void BM_FuzzRatio1(benchmark::State& state) +{ + std::wstring a = L"aaaaa aaaaa"; + for (auto _ : state) { + benchmark::DoNotOptimize(ratio(a, a)); + } + state.SetLabel("Similar Strings"); +} + +static void BM_FuzzRatio2(benchmark::State& state) +{ + std::wstring a = L"aaaaa aaaaa"; + std::wstring b = L"bbbbb bbbbb"; + for (auto _ : state) { + benchmark::DoNotOptimize(ratio(a, b)); + } + state.SetLabel("Different Strings"); +} + +BENCHMARK(BM_FuzzRatio1); +BENCHMARK(BM_FuzzRatio2); + +static void BM_FuzzPartialRatio1(benchmark::State& state) +{ + std::wstring a = L"aaaaa aaaaa"; + for (auto _ : state) { + benchmark::DoNotOptimize(partial_ratio(a, a)); + } + state.SetLabel("Similar Strings"); +} + +static void BM_FuzzPartialRatio2(benchmark::State& state) +{ + std::wstring a = L"aaaaa aaaaa"; + std::wstring b = L"bbbbb bbbbb"; + for (auto _ : state) { + benchmark::DoNotOptimize(partial_ratio(a, b)); + } + state.SetLabel("Different Strings"); +} + +BENCHMARK(BM_FuzzPartialRatio1); +BENCHMARK(BM_FuzzPartialRatio2); + +static void BM_FuzzTokenSort1(benchmark::State& state) +{ + std::wstring a = L"aaaaa aaaaa"; + for (auto _ : state) { + benchmark::DoNotOptimize(token_sort_ratio(a, a)); + } + state.SetLabel("Similar Strings"); +} + +static void BM_FuzzTokenSort2(benchmark::State& state) +{ + std::wstring a = L"aaaaa aaaaa"; + std::wstring b = L"bbbbb bbbbb"; + for (auto _ : state) { + benchmark::DoNotOptimize(token_sort_ratio(a, b)); + } + state.SetLabel("Different Strings"); +} + +BENCHMARK(BM_FuzzTokenSort1); +BENCHMARK(BM_FuzzTokenSort2); + +static void BM_FuzzPartialTokenSort1(benchmark::State& state) +{ + std::wstring a = L"aaaaa aaaaa"; + for (auto _ : state) { + benchmark::DoNotOptimize(partial_token_sort_ratio(a, a)); + } + state.SetLabel("Similar Strings"); +} + +static void BM_FuzzPartialTokenSort2(benchmark::State& state) +{ + std::wstring a = L"aaaaa aaaaa"; + std::wstring b = L"bbbbb bbbbb"; + for (auto _ : state) { + benchmark::DoNotOptimize(partial_token_sort_ratio(a, b)); + } + state.SetLabel("Different Strings"); +} + +BENCHMARK(BM_FuzzPartialTokenSort1); +BENCHMARK(BM_FuzzPartialTokenSort2); + +static void BM_FuzzTokenSet1(benchmark::State& state) +{ + std::wstring a = L"aaaaa aaaaa"; + for (auto _ : state) { + benchmark::DoNotOptimize(token_set_ratio(a, a)); + } + state.SetLabel("Similar Strings"); +} + +static void BM_FuzzTokenSet2(benchmark::State& state) +{ + std::wstring a = L"aaaaa aaaaa"; + std::wstring b = L"bbbbb bbbbb"; + for (auto _ : state) { + benchmark::DoNotOptimize(token_set_ratio(a, b)); + } + state.SetLabel("Different Strings"); +} + +BENCHMARK(BM_FuzzTokenSet1); +BENCHMARK(BM_FuzzTokenSet2); + +static void BM_FuzzPartialTokenSet1(benchmark::State& state) +{ + std::wstring a = L"aaaaa aaaaa"; + for (auto _ : state) { + benchmark::DoNotOptimize(partial_token_set_ratio(a, a)); + } + state.SetLabel("Similar Strings"); +} + +static void BM_FuzzPartialTokenSet2(benchmark::State& state) +{ + std::wstring a = L"aaaaa aaaaa"; + std::wstring b = L"bbbbb bbbbb"; + for (auto _ : state) { + benchmark::DoNotOptimize(partial_token_set_ratio(a, b)); + } + state.SetLabel("Different Strings"); +} + +BENCHMARK(BM_FuzzPartialTokenSet1); +BENCHMARK(BM_FuzzPartialTokenSet2); + +static void BM_FuzzToken1(benchmark::State& state) +{ + std::wstring a = L"aaaaa aaaaa"; + for (auto _ : state) { + benchmark::DoNotOptimize(token_ratio(a, a)); + } + state.SetLabel("Similar Strings"); +} + +static void BM_FuzzToken2(benchmark::State& state) +{ + std::wstring a = L"aaaaa aaaaa"; + std::wstring b = L"bbbbb bbbbb"; + for (auto _ : state) { + benchmark::DoNotOptimize(token_ratio(a, b)); + } + state.SetLabel("Different Strings"); +} + +BENCHMARK(BM_FuzzToken1); +BENCHMARK(BM_FuzzToken2); + +static void BM_FuzzPartialToken1(benchmark::State& state) +{ + std::wstring a = L"aaaaa aaaaa"; + for (auto _ : state) { + benchmark::DoNotOptimize(partial_token_ratio(a, a)); + } + state.SetLabel("Similar Strings"); +} + +static void BM_FuzzPartialToken2(benchmark::State& state) +{ + std::wstring a = L"aaaaa aaaaa"; + std::wstring b = L"bbbbb bbbbb"; + for (auto _ : state) { + benchmark::DoNotOptimize(partial_token_ratio(a, b)); + } + state.SetLabel("Different Strings"); +} + +BENCHMARK(BM_FuzzPartialToken1); +BENCHMARK(BM_FuzzPartialToken2); + +static void BM_FuzzWRatio1(benchmark::State& state) +{ + std::wstring a = L"aaaaa aaaaa"; + for (auto _ : state) { + benchmark::DoNotOptimize(WRatio(a, a)); + } + state.SetLabel("Similar Strings"); +} + +static void BM_FuzzWRatio3(benchmark::State& state) +{ + std::wstring a = L"aaaaa aaaaa"; + std::wstring b = L"bbbbb bbbbb"; + for (auto _ : state) { + benchmark::DoNotOptimize(WRatio(a, b)); + } + state.SetLabel("Different Strings"); +} + +static void BM_FuzzWRatio2(benchmark::State& state) +{ + std::wstring a = L"aaaaa b"; + std::wstring b = L"bbbbb bbbbbbbbb"; + for (auto _ : state) { + benchmark::DoNotOptimize(WRatio(a, b)); + } + state.SetLabel("Different length Strings"); +} + +BENCHMARK(BM_FuzzWRatio1); +BENCHMARK(BM_FuzzWRatio2); +BENCHMARK(BM_FuzzWRatio3); + +BENCHMARK_MAIN(); diff --git a/src/external/rapidfuzz-cpp/bench/bench-jarowinkler.cpp b/src/external/rapidfuzz-cpp/bench/bench-jarowinkler.cpp new file mode 100644 index 00000000..b8d5c41e --- /dev/null +++ b/src/external/rapidfuzz-cpp/bench/bench-jarowinkler.cpp @@ -0,0 +1,196 @@ +#include +#include +#include +#include +#include + +std::string generate(int max_length) +{ + std::string possible_characters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + std::random_device rd; + std::mt19937 engine(rd()); + std::uniform_int_distribution<> dist(0, static_cast(possible_characters.size() - 1)); + std::string ret = ""; + for (int i = 0; i < max_length; i++) { + int random_index = dist(engine); + ret += possible_characters[static_cast(random_index)]; + } + return ret; +} + +template +std::basic_string str_multiply(std::basic_string a, unsigned int b) +{ + std::basic_string output; + while (b--) + output += a; + + return output; +} + +static void BM_JaroLongSimilarSequence(benchmark::State& state) +{ + size_t len = state.range(0); + size_t score_cutoff = state.range(1); + std::string s1 = std::string("a") + str_multiply(std::string("b"), (len - 2)) + std::string("a"); + std::string s2 = str_multiply(std::string("b"), len); + + size_t num = 0; + for (auto _ : state) { + benchmark::DoNotOptimize(rapidfuzz::jaro_similarity(s1, s2)); + ++num; + } + + state.counters["Rate"] = benchmark::Counter(static_cast(num * len), benchmark::Counter::kIsRate); + state.counters["InvRate"] = benchmark::Counter(static_cast(num * len), + benchmark::Counter::kIsRate | benchmark::Counter::kInvert); +} + +static void BM_JaroLongNonSimilarSequence(benchmark::State& state) +{ + size_t len = state.range(0); + size_t score_cutoff = state.range(1); + std::string s1 = str_multiply(std::string("a"), len); + std::string s2 = str_multiply(std::string("b"), len); + + size_t num = 0; + for (auto _ : state) { + benchmark::DoNotOptimize(rapidfuzz::jaro_similarity(s1, s2)); + ++num; + } + + state.counters["Rate"] = benchmark::Counter(static_cast(num * len), benchmark::Counter::kIsRate); + state.counters["InvRate"] = benchmark::Counter(static_cast(num * len), + benchmark::Counter::kIsRate | benchmark::Counter::kInvert); +} + +#ifdef RAPIDFUZZ_SIMD +template +static void BM_Jaro_SIMD(benchmark::State& state) +{ + std::vector seq1; + std::vector seq2; + std::vector results(64); + for (int i = 0; i < 64; i++) + seq1.push_back(generate(MaxLen1)); + for (int i = 0; i < 10000; i++) + seq2.push_back(generate(MaxLen2)); + + size_t num = 0; + for (auto _ : state) { + rapidfuzz::experimental::MultiJaro scorer(seq1.size()); + for (const auto& str1 : seq1) + scorer.insert(str1); + + for (const auto& str2 : seq2) + scorer.similarity(&results[0], results.size(), str2); + + num += seq1.size() * seq2.size(); + } + + state.counters["Rate"] = benchmark::Counter(static_cast(num), benchmark::Counter::kIsRate); + state.counters["InvRate"] = benchmark::Counter(static_cast(num), + benchmark::Counter::kIsRate | benchmark::Counter::kInvert); +} +#endif + +template +static void BM_Jaro(benchmark::State& state) +{ + std::vector seq1; + std::vector seq2; + for (int i = 0; i < 256; i++) + seq1.push_back(generate(MaxLen1)); + for (int i = 0; i < 10000; i++) + seq2.push_back(generate(MaxLen2)); + + size_t num = 0; + for (auto _ : state) { + for (size_t j = 0; j < seq2.size(); ++j) + for (size_t i = 0; i < seq1.size(); ++i) + benchmark::DoNotOptimize(rapidfuzz::jaro_similarity(seq1[i], seq2[j])); + + num += seq1.size() * seq2.size(); + } + + state.counters["Rate"] = benchmark::Counter(static_cast(num), benchmark::Counter::kIsRate); + state.counters["InvRate"] = benchmark::Counter(static_cast(num), + benchmark::Counter::kIsRate | benchmark::Counter::kInvert); +} + +template +static void BM_Jaro_Cached(benchmark::State& state) +{ + std::vector seq1; + std::vector seq2; + for (int i = 0; i < 256; i++) + seq1.push_back(generate(MaxLen1)); + for (int i = 0; i < 10000; i++) + seq2.push_back(generate(MaxLen2)); + + size_t num = 0; + for (auto _ : state) { + for (const auto& str1 : seq1) { + rapidfuzz::CachedJaro scorer(str1); + for (size_t j = 0; j < seq2.size(); ++j) + benchmark::DoNotOptimize(scorer.similarity(seq2[j])); + } + num += seq1.size() * seq2.size(); + } + + state.counters["Rate"] = benchmark::Counter(static_cast(num), benchmark::Counter::kIsRate); + state.counters["InvRate"] = benchmark::Counter(static_cast(num), + benchmark::Counter::kIsRate | benchmark::Counter::kInvert); +} + +BENCHMARK_TEMPLATE(BM_Jaro, 8, 8); +BENCHMARK_TEMPLATE(BM_Jaro, 16, 16); +BENCHMARK_TEMPLATE(BM_Jaro, 32, 32); +BENCHMARK_TEMPLATE(BM_Jaro, 64, 64); + +BENCHMARK_TEMPLATE(BM_Jaro_Cached, 8, 8); +BENCHMARK_TEMPLATE(BM_Jaro_Cached, 16, 16); +BENCHMARK_TEMPLATE(BM_Jaro_Cached, 32, 32); +BENCHMARK_TEMPLATE(BM_Jaro_Cached, 64, 64); + +#ifdef RAPIDFUZZ_SIMD +BENCHMARK_TEMPLATE(BM_Jaro_SIMD, 8, 8); +BENCHMARK_TEMPLATE(BM_Jaro_SIMD, 16, 16); +BENCHMARK_TEMPLATE(BM_Jaro_SIMD, 32, 32); +BENCHMARK_TEMPLATE(BM_Jaro_SIMD, 64, 64); +#endif + +BENCHMARK_TEMPLATE(BM_Jaro, 8, 1000); +BENCHMARK_TEMPLATE(BM_Jaro, 16, 1000); +BENCHMARK_TEMPLATE(BM_Jaro, 32, 1000); +BENCHMARK_TEMPLATE(BM_Jaro, 64, 1000); + +BENCHMARK_TEMPLATE(BM_Jaro_Cached, 8, 1000); +BENCHMARK_TEMPLATE(BM_Jaro_Cached, 16, 1000); +BENCHMARK_TEMPLATE(BM_Jaro_Cached, 32, 1000); +BENCHMARK_TEMPLATE(BM_Jaro_Cached, 64, 1000); + +#ifdef RAPIDFUZZ_SIMD +BENCHMARK_TEMPLATE(BM_Jaro_SIMD, 8, 1000); +BENCHMARK_TEMPLATE(BM_Jaro_SIMD, 16, 1000); +BENCHMARK_TEMPLATE(BM_Jaro_SIMD, 32, 1000); +BENCHMARK_TEMPLATE(BM_Jaro_SIMD, 64, 1000); +#endif + +BENCHMARK(BM_JaroLongSimilarSequence) + ->Args({100, 30}) + ->Args({500, 30}) + ->Args({5000, 30}) + ->Args({10000, 30}) + ->Args({20000, 30}) + ->Args({50000, 30}); + +BENCHMARK(BM_JaroLongNonSimilarSequence) + ->Args({100, 30}) + ->Args({500, 30}) + ->Args({5000, 30}) + ->Args({10000, 30}) + ->Args({20000, 30}) + ->Args({50000, 30}); + +BENCHMARK_MAIN(); \ No newline at end of file diff --git a/src/external/rapidfuzz-cpp/bench/bench-lcs.cpp b/src/external/rapidfuzz-cpp/bench/bench-lcs.cpp new file mode 100644 index 00000000..5ac63d02 --- /dev/null +++ b/src/external/rapidfuzz-cpp/bench/bench-lcs.cpp @@ -0,0 +1,181 @@ +#include +#include +#include +#include +#include +#include + +std::string generate(int max_length) +{ + std::string possible_characters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + std::random_device rd; + std::mt19937 engine(rd()); + std::uniform_int_distribution<> dist(0, static_cast(possible_characters.size() - 1)); + std::string ret = ""; + for (int i = 0; i < max_length; i++) { + int random_index = dist(engine); + ret += possible_characters[static_cast(random_index)]; + } + return ret; +} + +template +std::basic_string str_multiply(std::basic_string a, unsigned int b) +{ + std::basic_string output; + while (b--) + output += a; + + return output; +} + +static void BM_LcsLongSimilarSequence(benchmark::State& state) +{ + size_t len = state.range(0); + size_t score_cutoff = state.range(1); + std::string s1 = std::string("a") + str_multiply(std::string("b"), (len - 2)) + std::string("a"); + std::string s2 = str_multiply(std::string("b"), len); + + size_t num = 0; + for (auto _ : state) { + benchmark::DoNotOptimize(rapidfuzz::lcs_seq_distance(s1, s2, score_cutoff)); + ++num; + } + + state.counters["Rate"] = benchmark::Counter(static_cast(num * len), benchmark::Counter::kIsRate); + state.counters["InvRate"] = benchmark::Counter(static_cast(num * len), + benchmark::Counter::kIsRate | benchmark::Counter::kInvert); +} + +static void BM_LcsLongNonSimilarSequence(benchmark::State& state) +{ + size_t len = state.range(0); + size_t score_cutoff = state.range(1); + std::string s1 = str_multiply(std::string("a"), len); + std::string s2 = str_multiply(std::string("b"), len); + + size_t num = 0; + for (auto _ : state) { + benchmark::DoNotOptimize(rapidfuzz::lcs_seq_distance(s1, s2, score_cutoff)); + ++num; + } + + state.counters["Rate"] = benchmark::Counter(static_cast(num * len), benchmark::Counter::kIsRate); + state.counters["InvRate"] = benchmark::Counter(static_cast(num * len), + benchmark::Counter::kIsRate | benchmark::Counter::kInvert); +} + +template +static void BM_LCS(benchmark::State& state) +{ + std::vector seq1; + std::vector seq2; + for (int i = 0; i < 256; i++) + seq1.push_back(generate(MaxLen)); + for (int i = 0; i < 10000; i++) + seq2.push_back(generate(MaxLen)); + + size_t num = 0; + for (auto _ : state) { + for (size_t j = 0; j < seq2.size(); ++j) + for (size_t i = 0; i < seq1.size(); ++i) + benchmark::DoNotOptimize(rapidfuzz::lcs_seq_distance(seq1[i], seq2[j])); + + num += seq1.size() * seq2.size(); + } + + state.counters["Rate"] = benchmark::Counter(static_cast(num), benchmark::Counter::kIsRate); + state.counters["InvRate"] = benchmark::Counter(static_cast(num), + benchmark::Counter::kIsRate | benchmark::Counter::kInvert); +} + +template +static void BM_LCS_Cached(benchmark::State& state) +{ + std::vector seq1; + std::vector seq2; + for (int i = 0; i < 256; i++) + seq1.push_back(generate(MaxLen)); + for (int i = 0; i < 10000; i++) + seq2.push_back(generate(MaxLen)); + + size_t num = 0; + for (auto _ : state) { + for (const auto& str1 : seq1) { + rapidfuzz::CachedLCSseq scorer(str1); + for (size_t j = 0; j < seq2.size(); ++j) + benchmark::DoNotOptimize(scorer.similarity(seq2[j])); + } + num += seq1.size() * seq2.size(); + } + + state.counters["Rate"] = benchmark::Counter(static_cast(num), benchmark::Counter::kIsRate); + state.counters["InvRate"] = benchmark::Counter(static_cast(num), + benchmark::Counter::kIsRate | benchmark::Counter::kInvert); +} + +#ifdef RAPIDFUZZ_SIMD +template +static void BM_LCS_SIMD(benchmark::State& state) +{ + std::vector seq1; + std::vector seq2; + std::vector results(32 * 3 * 4); + for (int i = 0; i < 32 * 3 * 4; i++) + seq1.push_back(generate(MaxLen)); + for (int i = 0; i < 10000; i++) + seq2.push_back(generate(MaxLen)); + + size_t num = 0; + for (auto _ : state) { + rapidfuzz::experimental::MultiLCSseq scorer(seq1.size()); + for (const auto& str1 : seq1) + scorer.insert(str1); + + for (const auto& str2 : seq2) + scorer.similarity(&results[0], results.size(), str2); + + num += seq1.size() * seq2.size(); + } + + state.counters["Rate"] = benchmark::Counter(static_cast(num), benchmark::Counter::kIsRate); + state.counters["InvRate"] = benchmark::Counter(static_cast(num), + benchmark::Counter::kIsRate | benchmark::Counter::kInvert); +} +#endif + +BENCHMARK(BM_LcsLongSimilarSequence) + ->Args({100, 30}) + ->Args({500, 100}) + ->Args({500, 30}) + ->Args({5000, 30}) + ->Args({10000, 30}) + ->Args({20000, 30}) + ->Args({50000, 30}); + +BENCHMARK(BM_LcsLongNonSimilarSequence) + ->Args({100, 30}) + ->Args({500, 30}) + ->Args({5000, 30}) + ->Args({10000, 30}) + ->Args({20000, 30}) + ->Args({50000, 30}); + +BENCHMARK_TEMPLATE(BM_LCS, 8); +BENCHMARK_TEMPLATE(BM_LCS, 16); +BENCHMARK_TEMPLATE(BM_LCS, 32); +BENCHMARK_TEMPLATE(BM_LCS, 64); + +BENCHMARK_TEMPLATE(BM_LCS_Cached, 8); +BENCHMARK_TEMPLATE(BM_LCS_Cached, 16); +BENCHMARK_TEMPLATE(BM_LCS_Cached, 32); +BENCHMARK_TEMPLATE(BM_LCS_Cached, 64); + +#ifdef RAPIDFUZZ_SIMD +BENCHMARK_TEMPLATE(BM_LCS_SIMD, 8); +BENCHMARK_TEMPLATE(BM_LCS_SIMD, 16); +BENCHMARK_TEMPLATE(BM_LCS_SIMD, 32); +BENCHMARK_TEMPLATE(BM_LCS_SIMD, 64); +#endif + +BENCHMARK_MAIN(); \ No newline at end of file diff --git a/src/external/rapidfuzz-cpp/bench/bench-levenshtein.cpp b/src/external/rapidfuzz-cpp/bench/bench-levenshtein.cpp new file mode 100644 index 00000000..0bc29773 --- /dev/null +++ b/src/external/rapidfuzz-cpp/bench/bench-levenshtein.cpp @@ -0,0 +1,224 @@ +#include +#include +#include +#include +#include + +std::string generate(int max_length) +{ + std::string possible_characters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + std::random_device rd; + std::mt19937 engine(rd()); + std::uniform_int_distribution<> dist(0, static_cast(possible_characters.size() - 1)); + std::string ret = ""; + for (int i = 0; i < max_length; i++) { + int random_index = dist(engine); + ret += possible_characters[static_cast(random_index)]; + } + return ret; +} + +template +std::basic_string str_multiply(std::basic_string a, unsigned int b) +{ + std::basic_string output; + while (b--) + output += a; + + return output; +} + +// Define another benchmark +static void BM_LevWeightedDist1(benchmark::State& state) +{ + std::string a = "aaaaa aaaaa"; + for (auto _ : state) { + benchmark::DoNotOptimize(rapidfuzz::levenshtein_distance(a, a)); + } + state.SetLabel("Similar Strings"); +} + +static void BM_LevWeightedDist2(benchmark::State& state) +{ + std::string a = "aaaaa aaaaa"; + std::string b = "bbbbb bbbbb"; + for (auto _ : state) { + benchmark::DoNotOptimize(rapidfuzz::levenshtein_distance(a, b)); + } + state.SetLabel("Different Strings"); +} + +static void BM_LevNormWeightedDist1(benchmark::State& state) +{ + std::string a = "aaaaa aaaaa"; + for (auto _ : state) { + benchmark::DoNotOptimize(rapidfuzz::levenshtein_normalized_distance(a, a)); + } + state.SetLabel("Similar Strings"); +} + +static void BM_LevNormWeightedDist2(benchmark::State& state) +{ + std::string a = "aaaaa aaaaa"; + std::string b = "bbbbb bbbbb"; + for (auto _ : state) { + benchmark::DoNotOptimize(rapidfuzz::levenshtein_normalized_distance(a, b)); + } + state.SetLabel("Different Strings"); +} + +static void BM_LevLongSimilarSequence(benchmark::State& state) +{ + size_t len = state.range(0); + size_t score_cutoff = state.range(1); + std::string s1 = std::string("a") + str_multiply(std::string("b"), (len - 2)) + std::string("a"); + std::string s2 = str_multiply(std::string("b"), len); + + size_t num = 0; + for (auto _ : state) { + benchmark::DoNotOptimize(rapidfuzz::levenshtein_distance(s1, s2, {1, 1, 1}, score_cutoff)); + ++num; + } + + state.counters["Rate"] = benchmark::Counter(static_cast(num * len), benchmark::Counter::kIsRate); + state.counters["InvRate"] = benchmark::Counter(static_cast(num * len), + benchmark::Counter::kIsRate | benchmark::Counter::kInvert); +} + +static void BM_LevLongNonSimilarSequence(benchmark::State& state) +{ + size_t len = state.range(0); + size_t score_cutoff = state.range(1); + std::string s1 = str_multiply(std::string("a"), len); + std::string s2 = str_multiply(std::string("b"), len); + + size_t num = 0; + for (auto _ : state) { + benchmark::DoNotOptimize(rapidfuzz::levenshtein_distance(s1, s2, {1, 1, 1}, score_cutoff)); + ++num; + } + + state.counters["Rate"] = benchmark::Counter(static_cast(num * len), benchmark::Counter::kIsRate); + state.counters["InvRate"] = benchmark::Counter(static_cast(num * len), + benchmark::Counter::kIsRate | benchmark::Counter::kInvert); +} + +template +static void BM_Levenshtein(benchmark::State& state) +{ + std::vector seq1; + std::vector seq2; + for (int i = 0; i < 256; i++) + seq1.push_back(generate(MaxLen)); + for (int i = 0; i < 10000; i++) + seq2.push_back(generate(MaxLen)); + + size_t num = 0; + for (auto _ : state) { + for (size_t j = 0; j < seq2.size(); ++j) + for (size_t i = 0; i < seq1.size(); ++i) + benchmark::DoNotOptimize(rapidfuzz::levenshtein_distance(seq1[i], seq2[j])); + + num += seq1.size() * seq2.size(); + } + + state.counters["Rate"] = benchmark::Counter(static_cast(num), benchmark::Counter::kIsRate); + state.counters["InvRate"] = benchmark::Counter(static_cast(num), + benchmark::Counter::kIsRate | benchmark::Counter::kInvert); +} + +template +static void BM_Levenshtein_Cached(benchmark::State& state) +{ + std::vector seq1; + std::vector seq2; + for (int i = 0; i < 256; i++) + seq1.push_back(generate(MaxLen)); + for (int i = 0; i < 10000; i++) + seq2.push_back(generate(MaxLen)); + + size_t num = 0; + for (auto _ : state) { + for (const auto& str1 : seq1) { + rapidfuzz::CachedLevenshtein scorer(str1); + for (size_t j = 0; j < seq2.size(); ++j) + benchmark::DoNotOptimize(scorer.similarity(seq2[j])); + } + num += seq1.size() * seq2.size(); + } + + state.counters["Rate"] = benchmark::Counter(static_cast(num), benchmark::Counter::kIsRate); + state.counters["InvRate"] = benchmark::Counter(static_cast(num), + benchmark::Counter::kIsRate | benchmark::Counter::kInvert); +} + +#ifdef RAPIDFUZZ_SIMD +template +static void BM_Levenshtein_SIMD(benchmark::State& state) +{ + std::vector seq1; + std::vector seq2; + std::vector results(64); + for (int i = 0; i < 64; i++) + seq1.push_back(generate(MaxLen)); + for (int i = 0; i < 10000; i++) + seq2.push_back(generate(MaxLen)); + + size_t num = 0; + for (auto _ : state) { + rapidfuzz::experimental::MultiLevenshtein scorer(seq1.size()); + for (const auto& str1 : seq1) + scorer.insert(str1); + + for (const auto& str2 : seq2) + scorer.similarity(&results[0], results.size(), str2); + + num += seq1.size() * seq2.size(); + } + + state.counters["Rate"] = benchmark::Counter(static_cast(num), benchmark::Counter::kIsRate); + state.counters["InvRate"] = benchmark::Counter(static_cast(num), + benchmark::Counter::kIsRate | benchmark::Counter::kInvert); +} +#endif + +BENCHMARK(BM_LevLongSimilarSequence) + ->Args({100, 30}) + ->Args({500, 30}) + ->Args({5000, 30}) + ->Args({10000, 30}) + ->Args({20000, 30}) + ->Args({50000, 30}); + +BENCHMARK(BM_LevLongNonSimilarSequence) + ->Args({100, 30}) + ->Args({500, 30}) + ->Args({5000, 30}) + ->Args({10000, 30}) + ->Args({20000, 30}) + ->Args({50000, 30}); + +BENCHMARK(BM_LevWeightedDist1); +BENCHMARK(BM_LevWeightedDist2); + +BENCHMARK(BM_LevNormWeightedDist1); +BENCHMARK(BM_LevNormWeightedDist2); + +BENCHMARK_TEMPLATE(BM_Levenshtein, 8); +BENCHMARK_TEMPLATE(BM_Levenshtein, 16); +BENCHMARK_TEMPLATE(BM_Levenshtein, 32); +BENCHMARK_TEMPLATE(BM_Levenshtein, 64); + +BENCHMARK_TEMPLATE(BM_Levenshtein_Cached, 8); +BENCHMARK_TEMPLATE(BM_Levenshtein_Cached, 16); +BENCHMARK_TEMPLATE(BM_Levenshtein_Cached, 32); +BENCHMARK_TEMPLATE(BM_Levenshtein_Cached, 64); + +#ifdef RAPIDFUZZ_SIMD +BENCHMARK_TEMPLATE(BM_Levenshtein_SIMD, 8); +BENCHMARK_TEMPLATE(BM_Levenshtein_SIMD, 16); +BENCHMARK_TEMPLATE(BM_Levenshtein_SIMD, 32); +BENCHMARK_TEMPLATE(BM_Levenshtein_SIMD, 64); +#endif + +BENCHMARK_MAIN(); \ No newline at end of file diff --git a/src/external/rapidfuzz-cpp/cmake/rapidfuzzConfig.cmake.in b/src/external/rapidfuzz-cpp/cmake/rapidfuzzConfig.cmake.in new file mode 100644 index 00000000..71d71b89 --- /dev/null +++ b/src/external/rapidfuzz-cpp/cmake/rapidfuzzConfig.cmake.in @@ -0,0 +1,9 @@ +@PACKAGE_INIT@ + +# Avoid repeatedly including the targets +if(NOT TARGET rapidfuzz::rapidfuzz) + # Provide path for scripts + list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}") + + include(${CMAKE_CURRENT_LIST_DIR}/rapidfuzzTargets.cmake) +endif() \ No newline at end of file diff --git a/src/external/rapidfuzz-cpp/docs/literature/hyrro_2002.bib b/src/external/rapidfuzz-cpp/docs/literature/hyrro_2002.bib new file mode 100644 index 00000000..40e76cc2 --- /dev/null +++ b/src/external/rapidfuzz-cpp/docs/literature/hyrro_2002.bib @@ -0,0 +1,7 @@ +@article{hyrro_2002, + author = {Hyyro, Heikki}, + year = {2002}, + month = {10}, + pages = {}, + title = {Explaining and Extending the Bit-parallel Approximate String Matching Algorithm of Myers} +} diff --git a/src/external/rapidfuzz-cpp/docs/literature/hyrro_2004.bib b/src/external/rapidfuzz-cpp/docs/literature/hyrro_2004.bib new file mode 100644 index 00000000..6b69cf81 --- /dev/null +++ b/src/external/rapidfuzz-cpp/docs/literature/hyrro_2004.bib @@ -0,0 +1,8 @@ +@article{hyrro_2004, + author = {Hyyro, Heikki}, + year = {2004}, + month = {08}, + pages = {}, + title = {Bit-Parallel LCS-length Computation Revisited}, + journal = {Proc. 15th Australasian Workshop on Combinatorial Algorithms (AWOCA 2004)} +} diff --git a/src/external/rapidfuzz-cpp/docs/literature/hyrro_lcs_2004.bib b/src/external/rapidfuzz-cpp/docs/literature/hyrro_lcs_2004.bib new file mode 100644 index 00000000..d34383d8 --- /dev/null +++ b/src/external/rapidfuzz-cpp/docs/literature/hyrro_lcs_2004.bib @@ -0,0 +1,8 @@ +@article{hyrro_lcs_2004, + author = {Hyyro, Heikki}, + year = {2004}, + month = {08}, + pages = {}, + title = {Bit-Parallel LCS-length Computation Revisited}, + journal = {Proc. 15th Australasian Workshop on Combinatorial Algorithms (AWOCA 2004)} +} \ No newline at end of file diff --git a/src/external/rapidfuzz-cpp/docs/literature/myers_1999.bib b/src/external/rapidfuzz-cpp/docs/literature/myers_1999.bib new file mode 100644 index 00000000..3c2eba4b --- /dev/null +++ b/src/external/rapidfuzz-cpp/docs/literature/myers_1999.bib @@ -0,0 +1,22 @@ +@article{myers_1999, +author = {Myers, Gene}, +title = {A Fast Bit-Vector Algorithm for Approximate String Matching Based on Dynamic Programming}, +year = {1999}, +issue_date = {May 1999}, +publisher = {Association for Computing Machinery}, +address = {New York, NY, USA}, +volume = {46}, +number = {3}, +issn = {0004-5411}, +url = {https://doi.org/10.1145/316542.316550}, +doi = {10.1145/316542.316550}, +journal = {J. ACM}, +month = may, +pages = {395–415}, +numpages = {21}, +keywords = {approximate string search, sequence comparison, bit-parallelism} +} + + + + diff --git a/src/external/rapidfuzz-cpp/docs/literature/wagner_fischer_1974.bib b/src/external/rapidfuzz-cpp/docs/literature/wagner_fischer_1974.bib new file mode 100644 index 00000000..0dfa43a7 --- /dev/null +++ b/src/external/rapidfuzz-cpp/docs/literature/wagner_fischer_1974.bib @@ -0,0 +1,20 @@ +@article{wagner_fischer_1974, +author = {Wagner, Robert A. and Fischer, Michael J.}, +title = {The String-to-String Correction Problem}, +year = {1974}, +issue_date = {Jan. 1974}, +publisher = {Association for Computing Machinery}, +address = {New York, NY, USA}, +volume = {21}, +number = {1}, +issn = {0004-5411}, +url = {https://doi.org/10.1145/321796.321811}, +doi = {10.1145/321796.321811}, +abstract = {The string-to-string correction problem is to determine the distance between two strings as measured by the minimum cost sequence of “edit operations” needed to change the one string into the other. The edit operations investigated allow changing one symbol of a string into another single symbol, deleting one symbol from a string, or inserting a single symbol into a string. An algorithm is presented which solves this problem in time proportional to the product of the lengths of the two strings. Possible applications are to the problems of automatic spelling correction and determining the longest subsequence of characters common to two strings.}, +journal = {J. ACM}, +month = jan, +pages = {168–173}, +numpages = {6} +} + + diff --git a/src/external/rapidfuzz-cpp/examples/cmake_export/CMakeLists.txt b/src/external/rapidfuzz-cpp/examples/cmake_export/CMakeLists.txt new file mode 100644 index 00000000..54272d43 --- /dev/null +++ b/src/external/rapidfuzz-cpp/examples/cmake_export/CMakeLists.txt @@ -0,0 +1,39 @@ +cmake_minimum_required(VERSION 3.5) +project(foo LANGUAGES CXX VERSION 0.0.1) + +# The example library publicly dependent on RapidFuzz (includes +# rapidfuzz.hpp in foo_lib.hpp), necessitating RapidFuzz's installation +set(RAPIDFUZZ_INSTALL ON CACHE INTERNAL "") +add_subdirectory(${CMAKE_SOURCE_DIR}/../.. + ${CMAKE_SOURCE_DIR}/../../build) + +add_library(foo foo_lib.cc) +add_library(foo::foo ALIAS foo) +target_link_libraries(foo rapidfuzz) + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") +include(GNUInstallDirs) +include(CMakePackageConfigHelpers) + +set(FOO_CMAKE_CONFIG_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/foo") +install(TARGETS foo EXPORT fooTargs DESTINATION ${CMAKE_INSTALL_LIBDIR}) +install(EXPORT fooTargs NAMESPACE foo:: DESTINATION ${FOO_CMAKE_CONFIG_DESTINATION}) + +configure_package_config_file( + ${CMAKE_CURRENT_LIST_DIR}/${PROJECT_NAME}Config.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake + INSTALL_DESTINATION ${FOO_CMAKE_CONFIG_DESTINATION} +) +write_basic_package_version_file( + "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake" + COMPATIBILITY SameMajorVersion +) + +install( + FILES + "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake" + DESTINATION + ${FOO_CMAKE_CONFIG_DESTINATION} +) +install(FILES foo_lib.hpp DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) diff --git a/src/external/rapidfuzz-cpp/examples/cmake_export/fooConfig.cmake.in b/src/external/rapidfuzz-cpp/examples/cmake_export/fooConfig.cmake.in new file mode 100644 index 00000000..b92e98a2 --- /dev/null +++ b/src/external/rapidfuzz-cpp/examples/cmake_export/fooConfig.cmake.in @@ -0,0 +1,10 @@ +@PACKAGE_INIT@ + +# Avoid repeatedly including the targets +if(NOT TARGET foo::foo) + find_package(rapidfuzz REQUIRED) + # Provide path for scripts + list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}") + + include(${CMAKE_CURRENT_LIST_DIR}/fooTargs.cmake) +endif() \ No newline at end of file diff --git a/src/external/rapidfuzz-cpp/examples/cmake_export/foo_lib.cc b/src/external/rapidfuzz-cpp/examples/cmake_export/foo_lib.cc new file mode 100644 index 00000000..be86f72a --- /dev/null +++ b/src/external/rapidfuzz-cpp/examples/cmake_export/foo_lib.cc @@ -0,0 +1,7 @@ +#include "foo_lib.hpp" + +double fooFunc() { + std::string_view a("aaaa"), b("abaa"); + FooType cache(a.begin(), a.end()); + return cache.similarity(b); +} diff --git a/src/external/rapidfuzz-cpp/examples/cmake_export/foo_lib.hpp b/src/external/rapidfuzz-cpp/examples/cmake_export/foo_lib.hpp new file mode 100644 index 00000000..41fc6f1a --- /dev/null +++ b/src/external/rapidfuzz-cpp/examples/cmake_export/foo_lib.hpp @@ -0,0 +1,4 @@ +#include + +using FooType = rapidfuzz::fuzz::CachedRatio; +double fooFunc(); diff --git a/src/external/rapidfuzz-cpp/examples/cmake_export/indirect_app/CMakeLists.txt b/src/external/rapidfuzz-cpp/examples/cmake_export/indirect_app/CMakeLists.txt new file mode 100644 index 00000000..6df8469f --- /dev/null +++ b/src/external/rapidfuzz-cpp/examples/cmake_export/indirect_app/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 3.5) +project(fooapp LANGUAGES CXX VERSION 0.0.1) +find_package(foo REQUIRED) +add_executable(fooapp foo_app.cc) +target_link_libraries(fooapp foo::foo) diff --git a/src/external/rapidfuzz-cpp/examples/cmake_export/indirect_app/foo_app.cc b/src/external/rapidfuzz-cpp/examples/cmake_export/indirect_app/foo_app.cc new file mode 100644 index 00000000..235ea797 --- /dev/null +++ b/src/external/rapidfuzz-cpp/examples/cmake_export/indirect_app/foo_app.cc @@ -0,0 +1,7 @@ +#include +#include + +int main() { + std::cout << fooFunc() << '\n'; + return 0; +} \ No newline at end of file diff --git a/src/external/rapidfuzz-cpp/examples/cmake_installed/CMakeLists.txt b/src/external/rapidfuzz-cpp/examples/cmake_installed/CMakeLists.txt new file mode 100644 index 00000000..d8aca7ab --- /dev/null +++ b/src/external/rapidfuzz-cpp/examples/cmake_installed/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 3.8) +project(cmake_installed CXX) + +find_package(rapidfuzz REQUIRED) +add_executable(foo main.cpp) +target_link_libraries(foo rapidfuzz::rapidfuzz) \ No newline at end of file diff --git a/src/external/rapidfuzz-cpp/examples/cmake_installed/main.cpp b/src/external/rapidfuzz-cpp/examples/cmake_installed/main.cpp new file mode 100644 index 00000000..8f3a363f --- /dev/null +++ b/src/external/rapidfuzz-cpp/examples/cmake_installed/main.cpp @@ -0,0 +1,10 @@ +#include +#include +#include + +int main() +{ + std::string a = "aaaa"; + std::string b = "abab"; + std::cout << rapidfuzz::fuzz::ratio(a, b) << std::endl; +} \ No newline at end of file diff --git a/src/external/rapidfuzz-cpp/extras/rapidfuzz_amalgamated.hpp b/src/external/rapidfuzz-cpp/extras/rapidfuzz_amalgamated.hpp new file mode 100644 index 00000000..353088da --- /dev/null +++ b/src/external/rapidfuzz-cpp/extras/rapidfuzz_amalgamated.hpp @@ -0,0 +1,11096 @@ +// Licensed under the MIT License . +// SPDX-License-Identifier: MIT +// RapidFuzz v1.0.2 +// Generated: 2024-12-14 13:57:57.746331 +// ---------------------------------------------------------- +// This file is an amalgamation of multiple different files. +// You probably shouldn't edit it directly. +// ---------------------------------------------------------- +#ifndef RAPIDFUZZ_AMALGAMATED_HPP_INCLUDED +#define RAPIDFUZZ_AMALGAMATED_HPP_INCLUDED + +#include + +#include +#include +#include +#include + +#include +#include +#include + +namespace rapidfuzz::detail { + +/* hashmap for integers which can only grow, but can't remove elements */ +template +struct GrowingHashmap { + using key_type = T_Key; + using value_type = T_Entry; + using size_type = unsigned int; + +private: + static constexpr size_type min_size = 8; + struct MapElem { + key_type key; + value_type value = value_type(); + }; + + int used; + int fill; + int mask; + MapElem* m_map; + +public: + GrowingHashmap() : used(0), fill(0), mask(-1), m_map(nullptr) + {} + ~GrowingHashmap() + { + delete[] m_map; + } + + GrowingHashmap(const GrowingHashmap& other) : used(other.used), fill(other.fill), mask(other.mask) + { + int size = mask + 1; + m_map = new MapElem[size]; + std::copy(other.m_map, other.m_map + size, m_map); + } + + GrowingHashmap(GrowingHashmap&& other) noexcept : GrowingHashmap() + { + swap(*this, other); + } + + GrowingHashmap& operator=(GrowingHashmap other) + { + swap(*this, other); + return *this; + } + + friend void swap(GrowingHashmap& first, GrowingHashmap& second) noexcept + { + std::swap(first.used, second.used); + std::swap(first.fill, second.fill); + std::swap(first.mask, second.mask); + std::swap(first.m_map, second.m_map); + } + + size_type size() const + { + return used; + } + size_type capacity() const + { + return mask + 1; + } + bool empty() const + { + return used == 0; + } + + value_type get(key_type key) const noexcept + { + if (m_map == nullptr) return value_type(); + + return m_map[lookup(key)].value; + } + + value_type& operator[](key_type key) noexcept + { + if (m_map == nullptr) allocate(); + + size_t i = lookup(key); + + if (m_map[i].value == value_type()) { + /* resize when 2/3 full */ + if (++fill * 3 >= (mask + 1) * 2) { + grow((used + 1) * 2); + i = lookup(key); + } + + used++; + } + + m_map[i].key = key; + return m_map[i].value; + } + +private: + void allocate() + { + mask = min_size - 1; + m_map = new MapElem[min_size]; + } + + /** + * lookup key inside the hashmap using a similar collision resolution + * strategy to CPython and Ruby + */ + size_t lookup(key_type key) const + { + size_t hash = static_cast(key); + size_t i = hash & static_cast(mask); + + if (m_map[i].value == value_type() || m_map[i].key == key) return i; + + size_t perturb = hash; + while (true) { + i = (i * 5 + perturb + 1) & static_cast(mask); + if (m_map[i].value == value_type() || m_map[i].key == key) return i; + + perturb >>= 5; + } + } + + void grow(int minUsed) + { + int newSize = mask + 1; + while (newSize <= minUsed) + newSize <<= 1; + + MapElem* oldMap = m_map; + m_map = new MapElem[static_cast(newSize)]; + + fill = used; + mask = newSize - 1; + + for (int i = 0; used > 0; i++) + if (oldMap[i].value != value_type()) { + size_t j = lookup(oldMap[i].key); + + m_map[j].key = oldMap[i].key; + m_map[j].value = oldMap[i].value; + used--; + } + + used = fill; + delete[] oldMap; + } +}; + +template +struct HybridGrowingHashmap { + using key_type = T_Key; + using value_type = T_Entry; + + HybridGrowingHashmap() + { + m_extendedAscii.fill(value_type()); + } + + value_type get(char key) const noexcept + { + /** treat char as value between 0 and 127 for performance reasons */ + return m_extendedAscii[static_cast(key)]; + } + + template + value_type get(CharT key) const noexcept + { + if (key >= 0 && key <= 255) + return m_extendedAscii[static_cast(key)]; + else + return m_map.get(static_cast(key)); + } + + value_type& operator[](char key) noexcept + { + /** treat char as value between 0 and 127 for performance reasons */ + return m_extendedAscii[static_cast(key)]; + } + + template + value_type& operator[](CharT key) + { + if (key >= 0 && key <= 255) + return m_extendedAscii[static_cast(key)]; + else + return m_map[static_cast(key)]; + } + +private: + GrowingHashmap m_map; + std::array m_extendedAscii; +}; + +} // namespace rapidfuzz::detail + +#include +#include +#include +#include +#include + +namespace rapidfuzz::detail { + +template +struct BitMatrixView { + + using value_type = T; + using size_type = size_t; + using pointer = std::conditional_t; + using reference = std::conditional_t; + + BitMatrixView(pointer vector, size_type cols) noexcept : m_vector(vector), m_cols(cols) + {} + + reference operator[](size_type col) noexcept + { + assert(col < m_cols); + return m_vector[col]; + } + + size_type size() const noexcept + { + return m_cols; + } + +private: + pointer m_vector; + size_type m_cols; +}; + +template +struct BitMatrix { + + using value_type = T; + + BitMatrix() : m_rows(0), m_cols(0), m_matrix(nullptr) + {} + + BitMatrix(size_t rows, size_t cols, T val) : m_rows(rows), m_cols(cols), m_matrix(nullptr) + { + if (m_rows && m_cols) m_matrix = new T[m_rows * m_cols]; + std::fill_n(m_matrix, m_rows * m_cols, val); + } + + BitMatrix(const BitMatrix& other) : m_rows(other.m_rows), m_cols(other.m_cols), m_matrix(nullptr) + { + if (m_rows && m_cols) m_matrix = new T[m_rows * m_cols]; + std::copy(other.m_matrix, other.m_matrix + m_rows * m_cols, m_matrix); + } + + BitMatrix(BitMatrix&& other) noexcept : m_rows(0), m_cols(0), m_matrix(nullptr) + { + other.swap(*this); + } + + BitMatrix& operator=(BitMatrix&& other) noexcept + { + other.swap(*this); + return *this; + } + + BitMatrix& operator=(const BitMatrix& other) + { + BitMatrix temp = other; + temp.swap(*this); + return *this; + } + + void swap(BitMatrix& rhs) noexcept + { + using std::swap; + swap(m_rows, rhs.m_rows); + swap(m_cols, rhs.m_cols); + swap(m_matrix, rhs.m_matrix); + } + + ~BitMatrix() + { + delete[] m_matrix; + } + + BitMatrixView operator[](size_t row) noexcept + { + assert(row < m_rows); + return {&m_matrix[row * m_cols], m_cols}; + } + + BitMatrixView operator[](size_t row) const noexcept + { + assert(row < m_rows); + return {&m_matrix[row * m_cols], m_cols}; + } + + size_t rows() const noexcept + { + return m_rows; + } + + size_t cols() const noexcept + { + return m_cols; + } + +private: + size_t m_rows; + size_t m_cols; + T* m_matrix; +}; + +template +struct ShiftedBitMatrix { + using value_type = T; + + ShiftedBitMatrix() + {} + + ShiftedBitMatrix(size_t rows, size_t cols, T val) : m_matrix(rows, cols, val), m_offsets(rows) + {} + + ShiftedBitMatrix(const ShiftedBitMatrix& other) : m_matrix(other.m_matrix), m_offsets(other.m_offsets) + {} + + ShiftedBitMatrix(ShiftedBitMatrix&& other) noexcept + { + other.swap(*this); + } + + ShiftedBitMatrix& operator=(ShiftedBitMatrix&& other) noexcept + { + other.swap(*this); + return *this; + } + + ShiftedBitMatrix& operator=(const ShiftedBitMatrix& other) + { + ShiftedBitMatrix temp = other; + temp.swap(*this); + return *this; + } + + void swap(ShiftedBitMatrix& rhs) noexcept + { + using std::swap; + swap(m_matrix, rhs.m_matrix); + swap(m_offsets, rhs.m_offsets); + } + + bool test_bit(size_t row, size_t col, bool default_ = false) const noexcept + { + ptrdiff_t offset = m_offsets[row]; + + if (offset < 0) { + col += static_cast(-offset); + } + else if (col >= static_cast(offset)) { + col -= static_cast(offset); + } + /* bit on the left of the band */ + else { + return default_; + } + + size_t word_size = sizeof(value_type) * 8; + size_t col_word = col / word_size; + value_type col_mask = value_type(1) << (col % word_size); + + return bool(m_matrix[row][col_word] & col_mask); + } + + auto operator[](size_t row) noexcept + { + return m_matrix[row]; + } + + auto operator[](size_t row) const noexcept + { + return m_matrix[row]; + } + + void set_offset(size_t row, ptrdiff_t offset) + { + m_offsets[row] = offset; + } + +private: + BitMatrix m_matrix; + std::vector m_offsets; +}; + +} // namespace rapidfuzz::detail + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace rapidfuzz::detail { + +static inline void assume(bool b) +{ +#if defined(__clang__) + __builtin_assume(b); +#elif defined(__GNUC__) || defined(__GNUG__) + if (!b) __builtin_unreachable(); +#elif defined(_MSC_VER) + __assume(b); +#endif +} + +template +CharT* to_begin(CharT* s) +{ + return s; +} + +template +auto to_begin(T& x) +{ + using std::begin; + return begin(x); +} + +template +CharT* to_end(CharT* s) +{ + assume(s != nullptr); + while (*s != 0) + ++s; + + return s; +} + +template +auto to_end(T& x) +{ + using std::end; + return end(x); +} + +template +class Range { + Iter _first; + Iter _last; + // todo we might not want to cache the size for iterators + // that can can retrieve the size in O(1) time + size_t _size; + +public: + using value_type = typename std::iterator_traits::value_type; + using iterator = Iter; + using reverse_iterator = std::reverse_iterator; + + constexpr Range(Iter first, Iter last) : _first(first), _last(last) + { + assert(std::distance(_first, _last) >= 0); + _size = static_cast(std::distance(_first, _last)); + } + + constexpr Range(Iter first, Iter last, size_t size) : _first(first), _last(last), _size(size) + {} + + template + constexpr Range(T& x) : _first(to_begin(x)), _last(to_end(x)) + { + assert(std::distance(_first, _last) >= 0); + _size = static_cast(std::distance(_first, _last)); + } + + constexpr iterator begin() const noexcept + { + return _first; + } + constexpr iterator end() const noexcept + { + return _last; + } + + constexpr reverse_iterator rbegin() const noexcept + { + return reverse_iterator(end()); + } + constexpr reverse_iterator rend() const noexcept + { + return reverse_iterator(begin()); + } + + constexpr size_t size() const + { + return _size; + } + + constexpr bool empty() const + { + return size() == 0; + } + explicit constexpr operator bool() const + { + return !empty(); + } + + template < + typename... Dummy, typename IterCopy = Iter, + typename = std::enable_if_t::iterator_category>>> + constexpr decltype(auto) operator[](size_t n) const + { + return _first[static_cast(n)]; + } + + constexpr void remove_prefix(size_t n) + { + if constexpr (std::is_base_of_v::iterator_category>) + _first += static_cast(n); + else + for (size_t i = 0; i < n; ++i) + _first++; + + _size -= n; + } + constexpr void remove_suffix(size_t n) + { + if constexpr (std::is_base_of_v::iterator_category>) + _last -= static_cast(n); + else + for (size_t i = 0; i < n; ++i) + _last--; + + _size -= n; + } + + constexpr Range subseq(size_t pos = 0, size_t count = std::numeric_limits::max()) + { + if (pos > size()) throw std::out_of_range("Index out of range in Range::substr"); + + Range res = *this; + res.remove_prefix(pos); + if (count < res.size()) res.remove_suffix(res.size() - count); + + return res; + } + + constexpr decltype(auto) front() const + { + return *(_first); + } + + constexpr decltype(auto) back() const + { + return *(_last - 1); + } + + constexpr Range reversed() const + { + return {rbegin(), rend(), _size}; + } + + friend std::ostream& operator<<(std::ostream& os, const Range& seq) + { + os << "["; + for (auto x : seq) + os << static_cast(x) << ", "; + os << "]"; + return os; + } +}; + +template +Range(T& x) -> Range; + +template +inline bool operator==(const Range& a, const Range& b) +{ + return std::equal(a.begin(), a.end(), b.begin(), b.end()); +} + +template +inline bool operator!=(const Range& a, const Range& b) +{ + return !(a == b); +} + +template +inline bool operator<(const Range& a, const Range& b) +{ + return (std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end())); +} + +template +inline bool operator>(const Range& a, const Range& b) +{ + return b < a; +} + +template +inline bool operator<=(const Range& a, const Range& b) +{ + return !(b < a); +} + +template +inline bool operator>=(const Range& a, const Range& b) +{ + return !(a < b); +} + +template +using RangeVec = std::vector>; + +} // namespace rapidfuzz::detail + +#include + +#include + +#include +#include +#include +#include + +namespace rapidfuzz { + +struct StringAffix { + size_t prefix_len; + size_t suffix_len; +}; + +struct LevenshteinWeightTable { + size_t insert_cost; + size_t delete_cost; + size_t replace_cost; +}; + +/** + * @brief Edit operation types used by the Levenshtein distance + */ +enum class EditType { + None = 0, /**< No Operation required */ + Replace = 1, /**< Replace a character if a string by another character */ + Insert = 2, /**< Insert a character into a string */ + Delete = 3 /**< Delete a character from a string */ +}; + +/** + * @brief Edit operations used by the Levenshtein distance + * + * This represents an edit operation of type type which is applied to + * the source string + * + * Replace: replace character at src_pos with character at dest_pos + * Insert: insert character from dest_pos at src_pos + * Delete: delete character at src_pos + */ +struct EditOp { + EditType type; /**< type of the edit operation */ + size_t src_pos; /**< index into the source string */ + size_t dest_pos; /**< index into the destination string */ + + EditOp() : type(EditType::None), src_pos(0), dest_pos(0) + {} + + EditOp(EditType type_, size_t src_pos_, size_t dest_pos_) + : type(type_), src_pos(src_pos_), dest_pos(dest_pos_) + {} +}; + +inline bool operator==(EditOp a, EditOp b) +{ + return (a.type == b.type) && (a.src_pos == b.src_pos) && (a.dest_pos == b.dest_pos); +} + +inline bool operator!=(EditOp a, EditOp b) +{ + return !(a == b); +} + +/** + * @brief Edit operations used by the Levenshtein distance + * + * This represents an edit operation of type type which is applied to + * the source string + * + * None: s1[src_begin:src_end] == s1[dest_begin:dest_end] + * Replace: s1[i1:i2] should be replaced by s2[dest_begin:dest_end] + * Insert: s2[dest_begin:dest_end] should be inserted at s1[src_begin:src_begin]. + * Note that src_begin==src_end in this case. + * Delete: s1[src_begin:src_end] should be deleted. + * Note that dest_begin==dest_end in this case. + */ +struct Opcode { + EditType type; /**< type of the edit operation */ + size_t src_begin; /**< index into the source string */ + size_t src_end; /**< index into the source string */ + size_t dest_begin; /**< index into the destination string */ + size_t dest_end; /**< index into the destination string */ + + Opcode() : type(EditType::None), src_begin(0), src_end(0), dest_begin(0), dest_end(0) + {} + + Opcode(EditType type_, size_t src_begin_, size_t src_end_, size_t dest_begin_, size_t dest_end_) + : type(type_), src_begin(src_begin_), src_end(src_end_), dest_begin(dest_begin_), dest_end(dest_end_) + {} +}; + +inline bool operator==(Opcode a, Opcode b) +{ + return (a.type == b.type) && (a.src_begin == b.src_begin) && (a.src_end == b.src_end) && + (a.dest_begin == b.dest_begin) && (a.dest_end == b.dest_end); +} + +inline bool operator!=(Opcode a, Opcode b) +{ + return !(a == b); +} + +namespace detail { +template +auto vector_slice(const Vec& vec, int start, int stop, int step) -> Vec +{ + Vec new_vec; + + if (step == 0) throw std::invalid_argument("slice step cannot be zero"); + if (step < 0) throw std::invalid_argument("step sizes below 0 lead to an invalid order of editops"); + + if (start < 0) + start = std::max(start + static_cast(vec.size()), 0); + else if (start > static_cast(vec.size())) + start = static_cast(vec.size()); + + if (stop < 0) + stop = std::max(stop + static_cast(vec.size()), 0); + else if (stop > static_cast(vec.size())) + stop = static_cast(vec.size()); + + if (start >= stop) return new_vec; + + int count = (stop - 1 - start) / step + 1; + new_vec.reserve(static_cast(count)); + + for (int i = start; i < stop; i += step) + new_vec.push_back(vec[static_cast(i)]); + + return new_vec; +} + +template +void vector_remove_slice(Vec& vec, int start, int stop, int step) +{ + if (step == 0) throw std::invalid_argument("slice step cannot be zero"); + if (step < 0) throw std::invalid_argument("step sizes below 0 lead to an invalid order of editops"); + + if (start < 0) + start = std::max(start + static_cast(vec.size()), 0); + else if (start > static_cast(vec.size())) + start = static_cast(vec.size()); + + if (stop < 0) + stop = std::max(stop + static_cast(vec.size()), 0); + else if (stop > static_cast(vec.size())) + stop = static_cast(vec.size()); + + if (start >= stop) return; + + auto iter = vec.begin() + start; + for (int i = start; i < static_cast(vec.size()); i++) + if (i >= stop || ((i - start) % step != 0)) *(iter++) = vec[static_cast(i)]; + + vec.resize(static_cast(std::distance(vec.begin(), iter))); + vec.shrink_to_fit(); +} + +} // namespace detail + +class Opcodes; + +class Editops : private std::vector { +public: + using std::vector::size_type; + + Editops() noexcept : src_len(0), dest_len(0) + {} + + Editops(size_type count, const EditOp& value) : std::vector(count, value), src_len(0), dest_len(0) + {} + + explicit Editops(size_type count) : std::vector(count), src_len(0), dest_len(0) + {} + + Editops(const Editops& other) + : std::vector(other), src_len(other.src_len), dest_len(other.dest_len) + {} + + Editops(const Opcodes& other); + + Editops(Editops&& other) noexcept + { + swap(other); + } + + Editops& operator=(Editops other) noexcept + { + swap(other); + return *this; + } + + /* Element access */ + using std::vector::at; + using std::vector::operator[]; + using std::vector::front; + using std::vector::back; + using std::vector::data; + + /* Iterators */ + using std::vector::begin; + using std::vector::cbegin; + using std::vector::end; + using std::vector::cend; + using std::vector::rbegin; + using std::vector::crbegin; + using std::vector::rend; + using std::vector::crend; + + /* Capacity */ + using std::vector::empty; + using std::vector::size; + using std::vector::max_size; + using std::vector::reserve; + using std::vector::capacity; + using std::vector::shrink_to_fit; + + /* Modifiers */ + using std::vector::clear; + using std::vector::insert; + using std::vector::emplace; + using std::vector::erase; + using std::vector::push_back; + using std::vector::emplace_back; + using std::vector::pop_back; + using std::vector::resize; + + void swap(Editops& rhs) noexcept + { + std::swap(src_len, rhs.src_len); + std::swap(dest_len, rhs.dest_len); + std::vector::swap(rhs); + } + + Editops slice(int start, int stop, int step = 1) const + { + Editops ed_slice = detail::vector_slice(*this, start, stop, step); + ed_slice.src_len = src_len; + ed_slice.dest_len = dest_len; + return ed_slice; + } + + void remove_slice(int start, int stop, int step = 1) + { + detail::vector_remove_slice(*this, start, stop, step); + } + + Editops reverse() const + { + Editops reversed = *this; + std::reverse(reversed.begin(), reversed.end()); + return reversed; + } + + size_t get_src_len() const noexcept + { + return src_len; + } + void set_src_len(size_t len) noexcept + { + src_len = len; + } + size_t get_dest_len() const noexcept + { + return dest_len; + } + void set_dest_len(size_t len) noexcept + { + dest_len = len; + } + + Editops inverse() const + { + Editops inv_ops = *this; + std::swap(inv_ops.src_len, inv_ops.dest_len); + for (auto& op : inv_ops) { + std::swap(op.src_pos, op.dest_pos); + if (op.type == EditType::Delete) + op.type = EditType::Insert; + else if (op.type == EditType::Insert) + op.type = EditType::Delete; + } + return inv_ops; + } + + Editops remove_subsequence(const Editops& subsequence) const + { + Editops result; + result.set_src_len(src_len); + result.set_dest_len(dest_len); + + if (subsequence.size() > size()) throw std::invalid_argument("subsequence is not a subsequence"); + + result.resize(size() - subsequence.size()); + + /* offset to correct removed edit operations */ + int offset = 0; + auto op_iter = begin(); + auto op_end = end(); + size_t result_pos = 0; + for (const auto& sop : subsequence) { + for (; op_iter != op_end && sop != *op_iter; op_iter++) { + result[result_pos] = *op_iter; + result[result_pos].src_pos = + static_cast(static_cast(result[result_pos].src_pos) + offset); + result_pos++; + } + /* element of subsequence not part of the sequence */ + if (op_iter == op_end) throw std::invalid_argument("subsequence is not a subsequence"); + + if (sop.type == EditType::Insert) + offset++; + else if (sop.type == EditType::Delete) + offset--; + op_iter++; + } + + /* add remaining elements */ + for (; op_iter != op_end; op_iter++) { + result[result_pos] = *op_iter; + result[result_pos].src_pos = + static_cast(static_cast(result[result_pos].src_pos) + offset); + result_pos++; + } + + return result; + } + +private: + size_t src_len; + size_t dest_len; +}; + +inline bool operator==(const Editops& lhs, const Editops& rhs) +{ + if (lhs.get_src_len() != rhs.get_src_len() || lhs.get_dest_len() != rhs.get_dest_len()) { + return false; + } + + if (lhs.size() != rhs.size()) { + return false; + } + return std::equal(lhs.begin(), lhs.end(), rhs.begin()); +} + +inline bool operator!=(const Editops& lhs, const Editops& rhs) +{ + return !(lhs == rhs); +} + +inline void swap(Editops& lhs, Editops& rhs) noexcept(noexcept(lhs.swap(rhs))) +{ + lhs.swap(rhs); +} + +class Opcodes : private std::vector { +public: + using std::vector::size_type; + + Opcodes() noexcept : src_len(0), dest_len(0) + {} + + Opcodes(size_type count, const Opcode& value) : std::vector(count, value), src_len(0), dest_len(0) + {} + + explicit Opcodes(size_type count) : std::vector(count), src_len(0), dest_len(0) + {} + + Opcodes(const Opcodes& other) + : std::vector(other), src_len(other.src_len), dest_len(other.dest_len) + {} + + Opcodes(const Editops& other); + + Opcodes(Opcodes&& other) noexcept + { + swap(other); + } + + Opcodes& operator=(Opcodes other) noexcept + { + swap(other); + return *this; + } + + /* Element access */ + using std::vector::at; + using std::vector::operator[]; + using std::vector::front; + using std::vector::back; + using std::vector::data; + + /* Iterators */ + using std::vector::begin; + using std::vector::cbegin; + using std::vector::end; + using std::vector::cend; + using std::vector::rbegin; + using std::vector::crbegin; + using std::vector::rend; + using std::vector::crend; + + /* Capacity */ + using std::vector::empty; + using std::vector::size; + using std::vector::max_size; + using std::vector::reserve; + using std::vector::capacity; + using std::vector::shrink_to_fit; + + /* Modifiers */ + using std::vector::clear; + using std::vector::insert; + using std::vector::emplace; + using std::vector::erase; + using std::vector::push_back; + using std::vector::emplace_back; + using std::vector::pop_back; + using std::vector::resize; + + void swap(Opcodes& rhs) noexcept + { + std::swap(src_len, rhs.src_len); + std::swap(dest_len, rhs.dest_len); + std::vector::swap(rhs); + } + + Opcodes slice(int start, int stop, int step = 1) const + { + Opcodes ed_slice = detail::vector_slice(*this, start, stop, step); + ed_slice.src_len = src_len; + ed_slice.dest_len = dest_len; + return ed_slice; + } + + Opcodes reverse() const + { + Opcodes reversed = *this; + std::reverse(reversed.begin(), reversed.end()); + return reversed; + } + + size_t get_src_len() const noexcept + { + return src_len; + } + void set_src_len(size_t len) noexcept + { + src_len = len; + } + size_t get_dest_len() const noexcept + { + return dest_len; + } + void set_dest_len(size_t len) noexcept + { + dest_len = len; + } + + Opcodes inverse() const + { + Opcodes inv_ops = *this; + std::swap(inv_ops.src_len, inv_ops.dest_len); + for (auto& op : inv_ops) { + std::swap(op.src_begin, op.dest_begin); + std::swap(op.src_end, op.dest_end); + if (op.type == EditType::Delete) + op.type = EditType::Insert; + else if (op.type == EditType::Insert) + op.type = EditType::Delete; + } + return inv_ops; + } + +private: + size_t src_len; + size_t dest_len; +}; + +inline bool operator==(const Opcodes& lhs, const Opcodes& rhs) +{ + if (lhs.get_src_len() != rhs.get_src_len() || lhs.get_dest_len() != rhs.get_dest_len()) return false; + + if (lhs.size() != rhs.size()) return false; + + return std::equal(lhs.begin(), lhs.end(), rhs.begin()); +} + +inline bool operator!=(const Opcodes& lhs, const Opcodes& rhs) +{ + return !(lhs == rhs); +} + +inline void swap(Opcodes& lhs, Opcodes& rhs) noexcept(noexcept(lhs.swap(rhs))) +{ + lhs.swap(rhs); +} + +inline Editops::Editops(const Opcodes& other) +{ + src_len = other.get_src_len(); + dest_len = other.get_dest_len(); + for (const auto& op : other) { + switch (op.type) { + case EditType::None: break; + + case EditType::Replace: + for (size_t j = 0; j < op.src_end - op.src_begin; j++) + push_back({EditType::Replace, op.src_begin + j, op.dest_begin + j}); + break; + + case EditType::Insert: + for (size_t j = 0; j < op.dest_end - op.dest_begin; j++) + push_back({EditType::Insert, op.src_begin, op.dest_begin + j}); + break; + + case EditType::Delete: + for (size_t j = 0; j < op.src_end - op.src_begin; j++) + push_back({EditType::Delete, op.src_begin + j, op.dest_begin}); + break; + } + } +} + +inline Opcodes::Opcodes(const Editops& other) +{ + src_len = other.get_src_len(); + dest_len = other.get_dest_len(); + size_t src_pos = 0; + size_t dest_pos = 0; + for (size_t i = 0; i < other.size();) { + if (src_pos < other[i].src_pos || dest_pos < other[i].dest_pos) { + push_back({EditType::None, src_pos, other[i].src_pos, dest_pos, other[i].dest_pos}); + src_pos = other[i].src_pos; + dest_pos = other[i].dest_pos; + } + + size_t src_begin = src_pos; + size_t dest_begin = dest_pos; + EditType type = other[i].type; + do { + switch (type) { + case EditType::None: break; + + case EditType::Replace: + src_pos++; + dest_pos++; + break; + + case EditType::Insert: dest_pos++; break; + + case EditType::Delete: src_pos++; break; + } + i++; + } while (i < other.size() && other[i].type == type && src_pos == other[i].src_pos && + dest_pos == other[i].dest_pos); + + push_back({type, src_begin, src_pos, dest_begin, dest_pos}); + } + + if (src_pos < other.get_src_len() || dest_pos < other.get_dest_len()) { + push_back({EditType::None, src_pos, other.get_src_len(), dest_pos, other.get_dest_len()}); + } +} + +template +struct ScoreAlignment { + T score; /**< resulting score of the algorithm */ + size_t src_start; /**< index into the source string */ + size_t src_end; /**< index into the source string */ + size_t dest_start; /**< index into the destination string */ + size_t dest_end; /**< index into the destination string */ + + ScoreAlignment() : score(T()), src_start(0), src_end(0), dest_start(0), dest_end(0) + {} + + ScoreAlignment(T score_, size_t src_start_, size_t src_end_, size_t dest_start_, size_t dest_end_) + : score(score_), + src_start(src_start_), + src_end(src_end_), + dest_start(dest_start_), + dest_end(dest_end_) + {} +}; + +template +inline bool operator==(const ScoreAlignment& a, const ScoreAlignment& b) +{ + return (a.score == b.score) && (a.src_start == b.src_start) && (a.src_end == b.src_end) && + (a.dest_start == b.dest_start) && (a.dest_end == b.dest_end); +} + +} // namespace rapidfuzz + +#include +#include + +namespace rapidfuzz { + +namespace detail { +template +auto inner_type(T const*) -> T; + +template +auto inner_type(T const&) -> typename T::value_type; +} // namespace detail + +template +using char_type = decltype(detail::inner_type(std::declval())); + +/* backport of std::iter_value_t from C++20 + * This does not cover the complete functionality, but should be enough for + * the use cases in this library + */ +template +using iter_value_t = typename std::iterator_traits::value_type; + +// taken from +// https://stackoverflow.com/questions/16893992/check-if-type-can-be-explicitly-converted +template +struct is_explicitly_convertible { + template + static void f(T); + + template + static constexpr auto test(int /*unused*/) -> decltype(f(static_cast(std::declval())), true) + { + return true; + } + + template + static constexpr auto test(...) -> bool + { + return false; + } + + static bool const value = test(0); +}; + +} // namespace rapidfuzz + +namespace rapidfuzz::detail { + +template +class SplittedSentenceView { +public: + using CharT = iter_value_t; + + SplittedSentenceView(RangeVec sentence) noexcept( + std::is_nothrow_move_constructible_v>) + : m_sentence(std::move(sentence)) + {} + + size_t dedupe(); + size_t size() const; + + size_t length() const + { + return size(); + } + + bool empty() const + { + return m_sentence.empty(); + } + + size_t word_count() const + { + return m_sentence.size(); + } + + std::vector join() const; + + const RangeVec& words() const + { + return m_sentence; + } + +private: + RangeVec m_sentence; +}; + +template +size_t SplittedSentenceView::dedupe() +{ + size_t old_word_count = word_count(); + m_sentence.erase(std::unique(m_sentence.begin(), m_sentence.end()), m_sentence.end()); + return old_word_count - word_count(); +} + +template +size_t SplittedSentenceView::size() const +{ + if (m_sentence.empty()) return 0; + + // there is a whitespace between each word + size_t result = m_sentence.size() - 1; + for (const auto& word : m_sentence) { + result += static_cast(std::distance(word.begin(), word.end())); + } + + return result; +} + +template +auto SplittedSentenceView::join() const -> std::vector +{ + if (m_sentence.empty()) { + return std::vector(); + } + + auto sentence_iter = m_sentence.begin(); + std::vector joined(sentence_iter->begin(), sentence_iter->end()); + ++sentence_iter; + for (; sentence_iter != m_sentence.end(); ++sentence_iter) { + joined.push_back(0x20); + joined.insert(joined.end(), sentence_iter->begin(), sentence_iter->end()); + } + return joined; +} + +} // namespace rapidfuzz::detail + +#include +#include +#include +#include +#include +#include + +#if defined(_MSC_VER) && !defined(__clang__) +# include +#endif + +namespace rapidfuzz::detail { + +template +T bit_mask_lsb(size_t n) +{ + T mask = static_cast(-1); + if (n < sizeof(T) * 8) { + mask += static_cast(static_cast(1) << n); + } + return mask; +} + +template +bool bittest(T a, int bit) +{ + return (a >> bit) & 1; +} + +/* + * shift right without undefined behavior for shifts > bit width + */ +template +constexpr uint64_t shr64(uint64_t a, U shift) +{ + return (shift < 64) ? a >> shift : 0; +} + +/* + * shift left without undefined behavior for shifts > bit width + */ +template +constexpr uint64_t shl64(uint64_t a, U shift) +{ + return (shift < 64) ? a << shift : 0; +} + +constexpr uint64_t addc64(uint64_t a, uint64_t b, uint64_t carryin, uint64_t* carryout) +{ + /* todo should use _addcarry_u64 when available */ + a += carryin; + *carryout = a < carryin; + a += b; + *carryout |= a < b; + return a; +} + +template +constexpr T ceil_div(T a, U divisor) +{ + T _div = static_cast(divisor); + return a / _div + static_cast(a % _div != 0); +} + +static inline size_t popcount(uint64_t x) +{ + return std::bitset<64>(x).count(); +} + +static inline size_t popcount(uint32_t x) +{ + return std::bitset<32>(x).count(); +} + +static inline size_t popcount(uint16_t x) +{ + return std::bitset<16>(x).count(); +} + +static inline size_t popcount(uint8_t x) +{ + static constexpr uint8_t bit_count[256] = { + 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, + 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, + 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, + 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8}; + return bit_count[x]; +} + +template +constexpr T rotl(T x, unsigned int n) +{ + unsigned int num_bits = std::numeric_limits::digits; + assert(n < num_bits); + unsigned int count_mask = num_bits - 1; + +#if _MSC_VER && !defined(__clang__) +# pragma warning(push) +/* unary minus operator applied to unsigned type, result still unsigned */ +# pragma warning(disable : 4146) +#endif + return (x << n) | (x >> (-n & count_mask)); +#if _MSC_VER && !defined(__clang__) +# pragma warning(pop) +#endif +} + +/** + * Extract the lowest set bit from a. If no bits are set in a returns 0. + */ +template +constexpr T blsi(T a) +{ +#if _MSC_VER && !defined(__clang__) +# pragma warning(push) +/* unary minus operator applied to unsigned type, result still unsigned */ +# pragma warning(disable : 4146) +#endif + return a & -a; +#if _MSC_VER && !defined(__clang__) +# pragma warning(pop) +#endif +} + +/** + * Clear the lowest set bit in a. + */ +template +constexpr T blsr(T x) +{ + return x & (x - 1); +} + +/** + * Sets all the lower bits of the result to 1 up to and including lowest set bit (=1) in a. + * If a is zero, blsmsk sets all bits to 1. + */ +template +constexpr T blsmsk(T a) +{ + return a ^ (a - 1); +} + +#if defined(_MSC_VER) && !defined(__clang__) +static inline unsigned int countr_zero(uint32_t x) +{ + unsigned long trailing_zero = 0; + _BitScanForward(&trailing_zero, x); + return trailing_zero; +} + +# if defined(_M_ARM) || defined(_M_X64) +static inline unsigned int countr_zero(uint64_t x) +{ + unsigned long trailing_zero = 0; + _BitScanForward64(&trailing_zero, x); + return trailing_zero; +} +# else +static inline unsigned int countr_zero(uint64_t x) +{ + uint32_t msh = (uint32_t)(x >> 32); + uint32_t lsh = (uint32_t)(x & 0xFFFFFFFF); + if (lsh != 0) return countr_zero(lsh); + return 32 + countr_zero(msh); +} +# endif + +#else /* gcc / clang */ +static inline unsigned int countr_zero(uint32_t x) +{ + return static_cast(__builtin_ctz(x)); +} + +static inline unsigned int countr_zero(uint64_t x) +{ + return static_cast(__builtin_ctzll(x)); +} +#endif + +static inline unsigned int countr_zero(uint16_t x) +{ + return countr_zero(static_cast(x)); +} + +static inline unsigned int countr_zero(uint8_t x) +{ + return countr_zero(static_cast(x)); +} + +template +constexpr void unroll_impl(std::integer_sequence, F&& f) +{ + (f(std::integral_constant{}), ...); +} + +template +constexpr void unroll(F&& f) +{ + unroll_impl(std::make_integer_sequence{}, std::forward(f)); +} + +} // namespace rapidfuzz::detail + +#if defined(__APPLE__) && !defined(_LIBCPP_HAS_C11_FEATURES) +# include +#endif + +namespace rapidfuzz::detail { + +template +struct DecomposedSet { + SplittedSentenceView difference_ab; + SplittedSentenceView difference_ba; + SplittedSentenceView intersection; + DecomposedSet(SplittedSentenceView diff_ab, SplittedSentenceView diff_ba, + SplittedSentenceView intersect) + : difference_ab(std::move(diff_ab)), + difference_ba(std::move(diff_ba)), + intersection(std::move(intersect)) + {} +}; + +static inline size_t abs_diff(size_t a, size_t b) +{ + return a > b ? a - b : b - a; +} + +template +TO opt_static_cast(const FROM& value) +{ + if constexpr (std::is_same_v) + return value; + else + return static_cast(value); +} + +/** + * @defgroup Common Common + * Common utilities shared among multiple functions + * @{ + */ + +static inline double NormSim_to_NormDist(double score_cutoff, double imprecision = 0.00001) +{ + return std::min(1.0, 1.0 - score_cutoff + imprecision); +} + +template +DecomposedSet set_decomposition(SplittedSentenceView a, + SplittedSentenceView b); + +template +StringAffix remove_common_affix(Range& s1, Range& s2); + +template +size_t remove_common_prefix(Range& s1, Range& s2); + +template +size_t remove_common_suffix(Range& s1, Range& s2); + +template > +SplittedSentenceView sorted_split(InputIt first, InputIt last); + +static inline void* rf_aligned_alloc(size_t alignment, size_t size) +{ +#if defined(_WIN32) + return _aligned_malloc(size, alignment); +#elif defined(__APPLE__) && !defined(_LIBCPP_HAS_C11_FEATURES) + return _mm_malloc(size, alignment); +#elif defined(__ANDROID__) && __ANDROID_API__ > 16 + void* ptr = nullptr; + return posix_memalign(&ptr, alignment, size) ? nullptr : ptr; +#else + return aligned_alloc(alignment, size); +#endif +} + +static inline void rf_aligned_free(void* ptr) +{ +#if defined(_WIN32) + _aligned_free(ptr); +#elif defined(__APPLE__) && !defined(_LIBCPP_HAS_C11_FEATURES) + _mm_free(ptr); +#else + free(ptr); +#endif +} + +/**@}*/ + +} // namespace rapidfuzz::detail + +#include +#include +#include + +namespace rapidfuzz::detail { + +template +DecomposedSet set_decomposition(SplittedSentenceView a, + SplittedSentenceView b) +{ + a.dedupe(); + b.dedupe(); + + RangeVec intersection; + RangeVec difference_ab; + RangeVec difference_ba = b.words(); + + for (const auto& current_a : a.words()) { + auto element_b = std::find(difference_ba.begin(), difference_ba.end(), current_a); + + if (element_b != difference_ba.end()) { + difference_ba.erase(element_b); + intersection.push_back(current_a); + } + else { + difference_ab.push_back(current_a); + } + } + + return {difference_ab, difference_ba, intersection}; +} + +/** + * Removes common prefix of two string views + */ +template +size_t remove_common_prefix(Range& s1, Range& s2) +{ + auto first1 = std::begin(s1); + size_t prefix = static_cast( + std::distance(first1, std::mismatch(first1, std::end(s1), std::begin(s2), std::end(s2)).first)); + s1.remove_prefix(prefix); + s2.remove_prefix(prefix); + return prefix; +} + +/** + * Removes common suffix of two string views + */ +template +size_t remove_common_suffix(Range& s1, Range& s2) +{ + auto rfirst1 = std::rbegin(s1); + size_t suffix = static_cast( + std::distance(rfirst1, std::mismatch(rfirst1, std::rend(s1), std::rbegin(s2), std::rend(s2)).first)); + s1.remove_suffix(suffix); + s2.remove_suffix(suffix); + return suffix; +} + +/** + * Removes common affix of two string views + */ +template +StringAffix remove_common_affix(Range& s1, Range& s2) +{ + return StringAffix{remove_common_prefix(s1, s2), remove_common_suffix(s1, s2)}; +} + +template +struct is_space_dispatch_tag : std::integral_constant {}; + +template +struct is_space_dispatch_tag::type> + : std::integral_constant {}; + +/* + * Implementation of is_space for char types that are at least 2 Byte in size + */ +template +bool is_space_impl(const CharT ch, std::integral_constant) +{ + switch (ch) { + case 0x0009: + case 0x000A: + case 0x000B: + case 0x000C: + case 0x000D: + case 0x001C: + case 0x001D: + case 0x001E: + case 0x001F: + case 0x0020: + case 0x0085: + case 0x00A0: + case 0x1680: + case 0x2000: + case 0x2001: + case 0x2002: + case 0x2003: + case 0x2004: + case 0x2005: + case 0x2006: + case 0x2007: + case 0x2008: + case 0x2009: + case 0x200A: + case 0x2028: + case 0x2029: + case 0x202F: + case 0x205F: + case 0x3000: return true; + } + return false; +} + +/* + * Implementation of is_space for char types that are 1 Byte in size + */ +template +bool is_space_impl(const CharT ch, std::integral_constant) +{ + switch (ch) { + case 0x0009: + case 0x000A: + case 0x000B: + case 0x000C: + case 0x000D: + case 0x001C: + case 0x001D: + case 0x001E: + case 0x001F: + case 0x0020: return true; + } + return false; +} + +/* + * checks whether unicode characters have the bidirectional + * type 'WS', 'B' or 'S' or the category 'Zs' + */ +template +bool is_space(const CharT ch) +{ + return is_space_impl(ch, is_space_dispatch_tag{}); +} + +template +SplittedSentenceView sorted_split(InputIt first, InputIt last) +{ + RangeVec splitted; + auto second = first; + + for (; first != last; first = second + 1) { + second = std::find_if(first, last, is_space); + + if (first != second) { + splitted.emplace_back(first, second); + } + + if (second == last) break; + } + + std::sort(splitted.begin(), splitted.end()); + + return SplittedSentenceView(splitted); +} + +} // namespace rapidfuzz::detail + +#include + +/* RAPIDFUZZ_LTO_HACK is used to differentiate functions between different + * translation units to avoid warnings when using lto */ +#ifndef RAPIDFUZZ_EXCLUDE_SIMD +# if __AVX2__ +# define RAPIDFUZZ_SIMD +# define RAPIDFUZZ_AVX2 +# define RAPIDFUZZ_LTO_HACK 0 + +# include +# include +# include +# include + +namespace rapidfuzz { +namespace detail { +namespace simd_avx2 { + +template +class native_simd; + +template <> +class native_simd { +public: + using value_type = uint64_t; + + static constexpr int alignment = 32; + static const int size = 4; + __m256i xmm; + + native_simd() noexcept + {} + + native_simd(__m256i val) noexcept : xmm(val) + {} + + native_simd(uint64_t a) noexcept + { + xmm = _mm256_set1_epi64x(static_cast(a)); + } + + native_simd(const uint64_t* p) noexcept + { + load(p); + } + + operator __m256i() const noexcept + { + return xmm; + } + + native_simd load(const uint64_t* p) noexcept + { + xmm = _mm256_set_epi64x(static_cast(p[3]), static_cast(p[2]), + static_cast(p[1]), static_cast(p[0])); + return *this; + } + + void store(uint64_t* p) const noexcept + { + _mm256_store_si256(reinterpret_cast<__m256i*>(p), xmm); + } + + native_simd operator+(const native_simd b) const noexcept + { + return _mm256_add_epi64(xmm, b); + } + + native_simd& operator+=(const native_simd b) noexcept + { + xmm = _mm256_add_epi64(xmm, b); + return *this; + } + + native_simd operator-(const native_simd b) const noexcept + { + return _mm256_sub_epi64(xmm, b); + } + + native_simd operator-() const noexcept + { + return _mm256_sub_epi64(_mm256_setzero_si256(), xmm); + } + + native_simd& operator-=(const native_simd b) noexcept + { + xmm = _mm256_sub_epi64(xmm, b); + return *this; + } +}; + +template <> +class native_simd { +public: + using value_type = uint32_t; + + static constexpr int alignment = 32; + static const int size = 8; + __m256i xmm; + + native_simd() noexcept + {} + + native_simd(__m256i val) noexcept : xmm(val) + {} + + native_simd(uint32_t a) noexcept + { + xmm = _mm256_set1_epi32(static_cast(a)); + } + + native_simd(const uint64_t* p) noexcept + { + load(p); + } + + operator __m256i() const + { + return xmm; + } + + native_simd load(const uint64_t* p) noexcept + { + xmm = _mm256_set_epi64x(static_cast(p[3]), static_cast(p[2]), + static_cast(p[1]), static_cast(p[0])); + return *this; + } + + void store(uint32_t* p) const noexcept + { + _mm256_store_si256(reinterpret_cast<__m256i*>(p), xmm); + } + + native_simd operator+(const native_simd b) const noexcept + { + return _mm256_add_epi32(xmm, b); + } + + native_simd& operator+=(const native_simd b) noexcept + { + xmm = _mm256_add_epi32(xmm, b); + return *this; + } + + native_simd operator-() const noexcept + { + return _mm256_sub_epi32(_mm256_setzero_si256(), xmm); + } + + native_simd operator-(const native_simd b) const noexcept + { + return _mm256_sub_epi32(xmm, b); + } + + native_simd& operator-=(const native_simd b) noexcept + { + xmm = _mm256_sub_epi32(xmm, b); + return *this; + } +}; + +template <> +class native_simd { +public: + using value_type = uint16_t; + + static constexpr int alignment = 32; + static const int size = 16; + __m256i xmm; + + native_simd() noexcept + {} + + native_simd(__m256i val) : xmm(val) + {} + + native_simd(uint16_t a) noexcept + { + xmm = _mm256_set1_epi16(static_cast(a)); + } + + native_simd(const uint64_t* p) noexcept + { + load(p); + } + + operator __m256i() const noexcept + { + return xmm; + } + + native_simd load(const uint64_t* p) noexcept + { + xmm = _mm256_set_epi64x(static_cast(p[3]), static_cast(p[2]), + static_cast(p[1]), static_cast(p[0])); + return *this; + } + + void store(uint16_t* p) const noexcept + { + _mm256_store_si256(reinterpret_cast<__m256i*>(p), xmm); + } + + native_simd operator+(const native_simd b) const noexcept + { + return _mm256_add_epi16(xmm, b); + } + + native_simd& operator+=(const native_simd b) noexcept + { + xmm = _mm256_add_epi16(xmm, b); + return *this; + } + + native_simd operator-(const native_simd b) const noexcept + { + return _mm256_sub_epi16(xmm, b); + } + + native_simd operator-() const noexcept + { + return _mm256_sub_epi16(_mm256_setzero_si256(), xmm); + } + + native_simd& operator-=(const native_simd b) noexcept + { + xmm = _mm256_sub_epi16(xmm, b); + return *this; + } +}; + +template <> +class native_simd { +public: + using value_type = uint8_t; + + static constexpr int alignment = 32; + static const int size = 32; + __m256i xmm; + + native_simd() noexcept + {} + + native_simd(__m256i val) noexcept : xmm(val) + {} + + native_simd(uint8_t a) noexcept + { + xmm = _mm256_set1_epi8(static_cast(a)); + } + + native_simd(const uint64_t* p) noexcept + { + load(p); + } + + operator __m256i() const noexcept + { + return xmm; + } + + native_simd load(const uint64_t* p) noexcept + { + xmm = _mm256_set_epi64x(static_cast(p[3]), static_cast(p[2]), + static_cast(p[1]), static_cast(p[0])); + return *this; + } + + void store(uint8_t* p) const noexcept + { + _mm256_store_si256(reinterpret_cast<__m256i*>(p), xmm); + } + + native_simd operator+(const native_simd b) const noexcept + { + return _mm256_add_epi8(xmm, b); + } + + native_simd& operator+=(const native_simd b) noexcept + { + xmm = _mm256_add_epi8(xmm, b); + return *this; + } + + native_simd operator-(const native_simd b) const noexcept + { + return _mm256_sub_epi8(xmm, b); + } + + native_simd operator-() const noexcept + { + return _mm256_sub_epi8(_mm256_setzero_si256(), xmm); + } + + native_simd& operator-=(const native_simd b) noexcept + { + xmm = _mm256_sub_epi8(xmm, b); + return *this; + } +}; + +template +std::ostream& operator<<(std::ostream& os, const native_simd& a) +{ + alignas(native_simd::alignment) std::array::size> res; + a.store(&res[0]); + + for (size_t i = res.size() - 1; i != 0; i--) + os << std::bitset::digits>(res[i]) << "|"; + + os << std::bitset::digits>(res[0]); + return os; +} + +template +__m256i hadd_impl(__m256i x) noexcept; + +template <> +inline __m256i hadd_impl(__m256i x) noexcept +{ + return x; +} + +template <> +inline __m256i hadd_impl(__m256i x) noexcept +{ + const __m256i mask = _mm256_set1_epi16(0x001f); + __m256i y = _mm256_srli_si256(x, 1); + x = _mm256_add_epi16(x, y); + return _mm256_and_si256(x, mask); +} + +template <> +inline __m256i hadd_impl(__m256i x) noexcept +{ + const __m256i mask = _mm256_set1_epi32(0x0000003F); + x = hadd_impl(x); + __m256i y = _mm256_srli_si256(x, 2); + x = _mm256_add_epi32(x, y); + return _mm256_and_si256(x, mask); +} + +template <> +inline __m256i hadd_impl(__m256i x) noexcept +{ + return _mm256_sad_epu8(x, _mm256_setzero_si256()); +} + +/* based on the paper `Faster Population Counts Using AVX2 Instructions` */ +template +native_simd popcount_impl(const native_simd& v) noexcept +{ + __m256i lookup = _mm256_setr_epi8(0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 0, 1, 1, 2, 1, 2, 2, 3, + 1, 2, 2, 3, 2, 3, 3, 4); + const __m256i low_mask = _mm256_set1_epi8(0x0F); + __m256i lo = _mm256_and_si256(v, low_mask); + __m256i hi = _mm256_and_si256(_mm256_srli_epi32(v, 4), low_mask); + __m256i popcnt1 = _mm256_shuffle_epi8(lookup, lo); + __m256i popcnt2 = _mm256_shuffle_epi8(lookup, hi); + __m256i total = _mm256_add_epi8(popcnt1, popcnt2); + return hadd_impl(total); +} + +template +std::array::size> popcount(const native_simd& a) noexcept +{ + alignas(native_simd::alignment) std::array::size> res; + popcount_impl(a).store(&res[0]); + return res; +} + +// function andnot: a & ~ b +template +native_simd andnot(const native_simd& a, const native_simd& b) +{ + return _mm256_andnot_si256(b, a); +} + +static inline native_simd operator==(const native_simd& a, + const native_simd& b) noexcept +{ + return _mm256_cmpeq_epi8(a, b); +} + +static inline native_simd operator==(const native_simd& a, + const native_simd& b) noexcept +{ + return _mm256_cmpeq_epi16(a, b); +} + +static inline native_simd operator==(const native_simd& a, + const native_simd& b) noexcept +{ + return _mm256_cmpeq_epi32(a, b); +} + +static inline native_simd operator==(const native_simd& a, + const native_simd& b) noexcept +{ + return _mm256_cmpeq_epi64(a, b); +} + +template +static inline native_simd operator!=(const native_simd& a, const native_simd& b) noexcept +{ + return ~(a == b); +} + +static inline native_simd operator<<(const native_simd& a, int b) noexcept +{ + char mask = static_cast(0xFF >> b); + __m256i am = _mm256_and_si256(a, _mm256_set1_epi8(mask)); + return _mm256_slli_epi16(am, b); +} + +static inline native_simd operator<<(const native_simd& a, int b) noexcept +{ + return _mm256_slli_epi16(a, b); +} + +static inline native_simd operator<<(const native_simd& a, int b) noexcept +{ + return _mm256_slli_epi32(a, b); +} + +static inline native_simd operator<<(const native_simd& a, int b) noexcept +{ + return _mm256_slli_epi64(a, b); +} + +static inline native_simd operator>>(const native_simd& a, int b) noexcept +{ + char mask = static_cast(0xFF << b); + __m256i am = _mm256_and_si256(a, _mm256_set1_epi8(mask)); + return _mm256_srli_epi16(am, b); +} + +static inline native_simd operator>>(const native_simd& a, int b) noexcept +{ + return _mm256_srli_epi16(a, b); +} + +static inline native_simd operator>>(const native_simd& a, int b) noexcept +{ + return _mm256_srli_epi32(a, b); +} + +static inline native_simd operator>>(const native_simd& a, int b) noexcept +{ + return _mm256_srli_epi64(a, b); +} + +template +native_simd operator&(const native_simd& a, const native_simd& b) noexcept +{ + return _mm256_and_si256(a, b); +} + +template +native_simd operator&=(native_simd& a, const native_simd& b) noexcept +{ + a = a & b; + return a; +} + +template +native_simd operator|(const native_simd& a, const native_simd& b) noexcept +{ + return _mm256_or_si256(a, b); +} + +template +native_simd operator|=(native_simd& a, const native_simd& b) noexcept +{ + a = a | b; + return a; +} + +template +native_simd operator^(const native_simd& a, const native_simd& b) noexcept +{ + return _mm256_xor_si256(a, b); +} + +template +native_simd operator^=(native_simd& a, const native_simd& b) noexcept +{ + a = a ^ b; + return a; +} + +template +native_simd operator~(const native_simd& a) noexcept +{ + return _mm256_xor_si256(a, _mm256_set1_epi32(-1)); +} + +// potentially we want a special native_simd for this +static inline native_simd operator>=(const native_simd& a, + const native_simd& b) noexcept +{ + return _mm256_cmpeq_epi8(_mm256_max_epu8(a, b), a); // a == max(a,b) +} + +static inline native_simd operator>=(const native_simd& a, + const native_simd& b) noexcept +{ + return _mm256_cmpeq_epi16(_mm256_max_epu16(a, b), a); // a == max(a,b) +} + +static inline native_simd operator>=(const native_simd& a, + const native_simd& b) noexcept +{ + return _mm256_cmpeq_epi32(_mm256_max_epu32(a, b), a); // a == max(a,b) +} + +static inline native_simd operator>(const native_simd& a, + const native_simd& b) noexcept; + +static inline native_simd operator>=(const native_simd& a, + const native_simd& b) noexcept +{ + return ~(b > a); +} + +template +static inline native_simd operator<=(const native_simd& a, const native_simd& b) noexcept +{ + return b >= a; +} + +static inline native_simd operator>(const native_simd& a, + const native_simd& b) noexcept +{ + return ~(b >= a); +} + +static inline native_simd operator>(const native_simd& a, + const native_simd& b) noexcept +{ + return ~(b >= a); +} + +static inline native_simd operator>(const native_simd& a, + const native_simd& b) noexcept +{ + __m256i signbit = _mm256_set1_epi32(static_cast(0x80000000)); + __m256i a1 = _mm256_xor_si256(a, signbit); + __m256i b1 = _mm256_xor_si256(b, signbit); + return _mm256_cmpgt_epi32(a1, b1); // signed compare +} + +static inline native_simd operator>(const native_simd& a, + const native_simd& b) noexcept +{ + __m256i sign64 = native_simd(0x8000000000000000); + __m256i aflip = _mm256_xor_si256(a, sign64); + __m256i bflip = _mm256_xor_si256(b, sign64); + return _mm256_cmpgt_epi64(aflip, bflip); // signed compare +} + +template +static inline native_simd operator<(const native_simd& a, const native_simd& b) noexcept +{ + return b > a; +} + +template +static inline native_simd max8(const native_simd& a, const native_simd& b) noexcept +{ + return _mm256_max_epu8(a, b); +} + +template +static inline native_simd max16(const native_simd& a, const native_simd& b) noexcept +{ + return _mm256_max_epu16(a, b); +} + +template +static inline native_simd max32(const native_simd& a, const native_simd& b) noexcept +{ + return _mm256_max_epu32(a, b); +} + +template +static inline native_simd min8(const native_simd& a, const native_simd& b) noexcept +{ + return _mm256_min_epu8(a, b); +} + +template +static inline native_simd min16(const native_simd& a, const native_simd& b) noexcept +{ + return _mm256_min_epu16(a, b); +} + +template +static inline native_simd min32(const native_simd& a, const native_simd& b) noexcept +{ + return _mm256_min_epu32(a, b); +} + +/* taken from https://stackoverflow.com/a/51807800/11335032 */ +static inline native_simd sllv(const native_simd& a, + const native_simd& count_) noexcept +{ + __m256i mask_hi = _mm256_set1_epi32(static_cast(0xFF00FF00)); + __m256i multiplier_lut = _mm256_set_epi8(0, 0, 0, 0, 0, 0, 0, 0, char(128), 64, 32, 16, 8, 4, 2, 1, 0, 0, + 0, 0, 0, 0, 0, 0, char(128), 64, 32, 16, 8, 4, 2, 1); + + __m256i count_sat = + _mm256_min_epu8(count_, _mm256_set1_epi8(8)); /* AVX shift counts are not masked. So a_i << n_i = 0 + for n_i >= 8. count_sat is always less than 9.*/ + __m256i multiplier = _mm256_shuffle_epi8( + multiplier_lut, count_sat); /* Select the right multiplication factor in the lookup table. */ + __m256i x_lo = _mm256_mullo_epi16(a, multiplier); /* Unfortunately _mm256_mullo_epi8 doesn't exist. Split + the 16 bit elements in a high and low part. */ + + __m256i multiplier_hi = _mm256_srli_epi16(multiplier, 8); /* The multiplier of the high bits. */ + __m256i a_hi = _mm256_and_si256(a, mask_hi); /* Mask off the low bits. */ + __m256i x_hi = _mm256_mullo_epi16(a_hi, multiplier_hi); + __m256i x = _mm256_blendv_epi8(x_lo, x_hi, mask_hi); /* Merge the high and low part. */ + return x; +} + +/* taken from https://stackoverflow.com/a/51805592/11335032 */ +static inline native_simd sllv(const native_simd& a, + const native_simd& count) noexcept +{ + const __m256i mask = _mm256_set1_epi32(static_cast(0xFFFF0000)); + __m256i low_half = _mm256_sllv_epi32(a, _mm256_andnot_si256(mask, count)); + __m256i high_half = _mm256_sllv_epi32(_mm256_and_si256(mask, a), _mm256_srli_epi32(count, 16)); + return _mm256_blend_epi16(low_half, high_half, 0xAA); +} + +static inline native_simd sllv(const native_simd& a, + const native_simd& count) noexcept +{ + return _mm256_sllv_epi32(a, count); +} + +static inline native_simd sllv(const native_simd& a, + const native_simd& count) noexcept +{ + return _mm256_sllv_epi64(a, count); +} + +} // namespace simd_avx2 +} // namespace detail +} // namespace rapidfuzz + +# elif (defined(_M_AMD64) || defined(_M_X64)) || defined(__SSE2__) +# define RAPIDFUZZ_SIMD +# define RAPIDFUZZ_SSE2 +# define RAPIDFUZZ_LTO_HACK 1 + +# include +# include +# include +# include + +namespace rapidfuzz { +namespace detail { +namespace simd_sse2 { + +template +class native_simd; + +template <> +class native_simd { +public: + static constexpr int alignment = 16; + static const int size = 2; + __m128i xmm; + + native_simd() noexcept + {} + + native_simd(__m128i val) noexcept : xmm(val) + {} + + native_simd(uint64_t a) noexcept + { + xmm = _mm_set1_epi64x(static_cast(a)); + } + + native_simd(const uint64_t* p) noexcept + { + load(p); + } + + operator __m128i() const noexcept + { + return xmm; + } + + native_simd load(const uint64_t* p) noexcept + { + xmm = _mm_set_epi64x(static_cast(p[1]), static_cast(p[0])); + return *this; + } + + void store(uint64_t* p) const noexcept + { + _mm_store_si128(reinterpret_cast<__m128i*>(p), xmm); + } + + native_simd operator+(const native_simd b) const noexcept + { + return _mm_add_epi64(xmm, b); + } + + native_simd& operator+=(const native_simd b) noexcept + { + xmm = _mm_add_epi64(xmm, b); + return *this; + } + + native_simd operator-(const native_simd b) const noexcept + { + return _mm_sub_epi64(xmm, b); + } + + native_simd operator-() const noexcept + { + return _mm_sub_epi64(_mm_setzero_si128(), xmm); + } + + native_simd& operator-=(const native_simd b) noexcept + { + xmm = _mm_sub_epi64(xmm, b); + return *this; + } +}; + +template <> +class native_simd { +public: + static constexpr int alignment = 16; + static const int size = 4; + __m128i xmm; + + native_simd() noexcept + {} + + native_simd(__m128i val) noexcept : xmm(val) + {} + + native_simd(uint32_t a) noexcept + { + xmm = _mm_set1_epi32(static_cast(a)); + } + + native_simd(const uint64_t* p) noexcept + { + load(p); + } + + operator __m128i() const noexcept + { + return xmm; + } + + native_simd load(const uint64_t* p) noexcept + { + xmm = _mm_set_epi64x(static_cast(p[1]), static_cast(p[0])); + return *this; + } + + void store(uint32_t* p) const noexcept + { + _mm_store_si128(reinterpret_cast<__m128i*>(p), xmm); + } + + native_simd operator+(const native_simd b) const noexcept + { + return _mm_add_epi32(xmm, b); + } + + native_simd& operator+=(const native_simd b) noexcept + { + xmm = _mm_add_epi32(xmm, b); + return *this; + } + + native_simd operator-(const native_simd b) const noexcept + { + return _mm_sub_epi32(xmm, b); + } + + native_simd operator-() const noexcept + { + return _mm_sub_epi32(_mm_setzero_si128(), xmm); + } + + native_simd& operator-=(const native_simd b) noexcept + { + xmm = _mm_sub_epi32(xmm, b); + return *this; + } +}; + +template <> +class native_simd { +public: + static constexpr int alignment = 16; + static const int size = 8; + __m128i xmm; + + native_simd() noexcept + {} + + native_simd(__m128i val) noexcept : xmm(val) + {} + + native_simd(uint16_t a) noexcept + { + xmm = _mm_set1_epi16(static_cast(a)); + } + + native_simd(const uint64_t* p) noexcept + { + load(p); + } + + operator __m128i() const noexcept + { + return xmm; + } + + native_simd load(const uint64_t* p) noexcept + { + xmm = _mm_set_epi64x(static_cast(p[1]), static_cast(p[0])); + return *this; + } + + void store(uint16_t* p) const noexcept + { + _mm_store_si128(reinterpret_cast<__m128i*>(p), xmm); + } + + native_simd operator+(const native_simd b) const noexcept + { + return _mm_add_epi16(xmm, b); + } + + native_simd& operator+=(const native_simd b) noexcept + { + xmm = _mm_add_epi16(xmm, b); + return *this; + } + + native_simd operator-(const native_simd b) const noexcept + { + return _mm_sub_epi16(xmm, b); + } + + native_simd operator-() const noexcept + { + return _mm_sub_epi16(_mm_setzero_si128(), xmm); + } + + native_simd& operator-=(const native_simd b) noexcept + { + xmm = _mm_sub_epi16(xmm, b); + return *this; + } +}; + +template <> +class native_simd { +public: + static constexpr int alignment = 16; + static const int size = 16; + __m128i xmm; + + native_simd() noexcept + {} + + native_simd(__m128i val) noexcept : xmm(val) + {} + + native_simd(uint8_t a) noexcept + { + xmm = _mm_set1_epi8(static_cast(a)); + } + + native_simd(const uint64_t* p) noexcept + { + load(p); + } + + operator __m128i() const noexcept + { + return xmm; + } + + native_simd load(const uint64_t* p) noexcept + { + xmm = _mm_set_epi64x(static_cast(p[1]), static_cast(p[0])); + return *this; + } + + void store(uint8_t* p) const noexcept + { + _mm_store_si128(reinterpret_cast<__m128i*>(p), xmm); + } + + native_simd operator+(const native_simd b) const noexcept + { + return _mm_add_epi8(xmm, b); + } + + native_simd& operator+=(const native_simd b) noexcept + { + xmm = _mm_add_epi8(xmm, b); + return *this; + } + + native_simd operator-(const native_simd b) const noexcept + { + return _mm_sub_epi8(xmm, b); + } + + native_simd operator-() const noexcept + { + return _mm_sub_epi8(_mm_setzero_si128(), xmm); + } + + native_simd& operator-=(const native_simd b) noexcept + { + xmm = _mm_sub_epi8(xmm, b); + return *this; + } +}; + +template +std::ostream& operator<<(std::ostream& os, const native_simd& a) +{ + alignas(native_simd::alignment) std::array::size> res; + a.store(&res[0]); + + for (size_t i = res.size() - 1; i != 0; i--) + os << std::bitset::digits>(res[i]) << "|"; + + os << std::bitset::digits>(res[0]); + return os; +} + +template +__m128i hadd_impl(__m128i x) noexcept; + +template <> +inline __m128i hadd_impl(__m128i x) noexcept +{ + return x; +} + +template <> +inline __m128i hadd_impl(__m128i x) noexcept +{ + const __m128i mask = _mm_set1_epi16(0x001f); + __m128i y = _mm_srli_si128(x, 1); + x = _mm_add_epi16(x, y); + return _mm_and_si128(x, mask); +} + +template <> +inline __m128i hadd_impl(__m128i x) noexcept +{ + const __m128i mask = _mm_set1_epi32(0x0000003f); + x = hadd_impl(x); + __m128i y = _mm_srli_si128(x, 2); + x = _mm_add_epi32(x, y); + return _mm_and_si128(x, mask); +} + +template <> +inline __m128i hadd_impl(__m128i x) noexcept +{ + return _mm_sad_epu8(x, _mm_setzero_si128()); +} + +template +native_simd popcount_impl(const native_simd& v) noexcept +{ + const __m128i m1 = _mm_set1_epi8(0x55); + const __m128i m2 = _mm_set1_epi8(0x33); + const __m128i m3 = _mm_set1_epi8(0x0F); + + /* Note: if we returned x here it would be like _mm_popcnt_epi1(x) */ + __m128i y; + __m128i x = v; + /* add even and odd bits*/ + y = _mm_srli_epi64(x, 1); // put even bits in odd place + y = _mm_and_si128(y, m1); // mask out the even bits (0x55) + x = _mm_subs_epu8(x, y); // shortcut to mask even bits and add + /* if we just returned x here it would be like popcnt_epi2(x) */ + /* now add the half nibbles */ + y = _mm_srli_epi64(x, 2); // move half nibbles in place to add + y = _mm_and_si128(y, m2); // mask off the extra half nibbles (0x0f) + x = _mm_and_si128(x, m2); // ditto + x = _mm_adds_epu8(x, y); // totals are a maximum of 5 bits (0x1f) + /* if we just returned x here it would be like popcnt_epi4(x) */ + /* now add the nibbles */ + y = _mm_srli_epi64(x, 4); // move nibbles in place to add + x = _mm_adds_epu8(x, y); // totals are a maximum of 6 bits (0x3f) + x = _mm_and_si128(x, m3); // mask off the extra bits + + /* todo use when sse3 available + __m128i lookup = _mm_setr_epi8(0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4); + const __m128i low_mask = _mm_set1_epi8(0x0F); + __m128i lo = _mm_and_si128(v, low_mask); + __m128i hi = _mm_and_si256(_mm_srli_epi32(v, 4), low_mask); + __m128i popcnt1 = _mm_shuffle_epi8(lookup, lo); + __m128i popcnt2 = _mm_shuffle_epi8(lookup, hi); + __m128i total = _mm_add_epi8(popcnt1, popcnt2);*/ + + return hadd_impl(x); +} + +template +std::array::size> popcount(const native_simd& a) noexcept +{ + alignas(native_simd::alignment) std::array::size> res; + popcount_impl(a).store(&res[0]); + return res; +} + +// function andnot: a & ~ b +template +native_simd andnot(const native_simd& a, const native_simd& b) +{ + return _mm_andnot_si128(b, a); +} + +static inline native_simd operator==(const native_simd& a, + const native_simd& b) noexcept +{ + return _mm_cmpeq_epi8(a, b); +} + +static inline native_simd operator==(const native_simd& a, + const native_simd& b) noexcept +{ + return _mm_cmpeq_epi16(a, b); +} + +static inline native_simd operator==(const native_simd& a, + const native_simd& b) noexcept +{ + return _mm_cmpeq_epi32(a, b); +} + +static inline native_simd operator==(const native_simd& a, + const native_simd& b) noexcept +{ + // no 64 compare instruction. Do two 32 bit compares + __m128i com32 = _mm_cmpeq_epi32(a, b); // 32 bit compares + __m128i com32s = _mm_shuffle_epi32(com32, 0xB1); // swap low and high dwords + __m128i test = _mm_and_si128(com32, com32s); // low & high + __m128i teste = _mm_srai_epi32(test, 31); // extend sign bit to 32 bits + __m128i testee = _mm_shuffle_epi32(teste, 0xF5); // extend sign bit to 64 bits + return testee; +} + +template +static inline native_simd operator!=(const native_simd& a, const native_simd& b) noexcept +{ + return ~(a == b); +} + +static inline native_simd operator<<(const native_simd& a, int b) noexcept +{ + char mask = static_cast(0xFF >> b); + __m128i am = _mm_and_si128(a, _mm_set1_epi8(mask)); + return _mm_slli_epi16(am, b); +} + +static inline native_simd operator<<(const native_simd& a, int b) noexcept +{ + return _mm_slli_epi16(a, b); +} + +static inline native_simd operator<<(const native_simd& a, int b) noexcept +{ + return _mm_slli_epi32(a, b); +} + +static inline native_simd operator<<(const native_simd& a, int b) noexcept +{ + return _mm_slli_epi64(a, b); +} + +static inline native_simd operator>>(const native_simd& a, int b) noexcept +{ + char mask = static_cast(0xFF << b); + __m128i am = _mm_and_si128(a, _mm_set1_epi8(mask)); + return _mm_srli_epi16(am, b); +} + +static inline native_simd operator>>(const native_simd& a, int b) noexcept +{ + return _mm_srli_epi16(a, b); +} + +static inline native_simd operator>>(const native_simd& a, int b) noexcept +{ + return _mm_srli_epi32(a, b); +} + +static inline native_simd operator>>(const native_simd& a, int b) noexcept +{ + return _mm_srli_epi64(a, b); +} + +template +native_simd operator&(const native_simd& a, const native_simd& b) noexcept +{ + return _mm_and_si128(a, b); +} + +template +native_simd operator&=(native_simd& a, const native_simd& b) noexcept +{ + a = a & b; + return a; +} + +template +native_simd operator|(const native_simd& a, const native_simd& b) noexcept +{ + return _mm_or_si128(a, b); +} + +template +native_simd operator|=(native_simd& a, const native_simd& b) noexcept +{ + a = a | b; + return a; +} + +template +native_simd operator^(const native_simd& a, const native_simd& b) noexcept +{ + return _mm_xor_si128(a, b); +} + +template +native_simd operator^=(native_simd& a, const native_simd& b) noexcept +{ + a = a ^ b; + return a; +} + +template +native_simd operator~(const native_simd& a) noexcept +{ + return _mm_xor_si128(a, _mm_set1_epi32(-1)); +} + +// potentially we want a special native_simd for this +static inline native_simd operator>=(const native_simd& a, + const native_simd& b) noexcept +{ + return _mm_cmpeq_epi8(_mm_max_epu8(a, b), a); // a == max(a,b) +} + +static inline native_simd operator>=(const native_simd& a, + const native_simd& b) noexcept +{ + /* sse4.1 */ +# if 0 + return _mm_cmpeq_epi16(_mm_max_epu16(a, b), a); // a == max(a,b) +# endif + + __m128i s = _mm_subs_epu16(b, a); // b-a, saturated + return _mm_cmpeq_epi16(s, _mm_setzero_si128()); // s == 0 +} + +static inline native_simd operator>(const native_simd& a, + const native_simd& b) noexcept; +static inline native_simd operator>(const native_simd& a, + const native_simd& b) noexcept; + +static inline native_simd operator>=(const native_simd& a, + const native_simd& b) noexcept +{ + /* sse4.1 */ +# if 0 + return (Vec4ib)_mm_cmpeq_epi32(_mm_max_epu32(a, b), a); // a == max(a,b) +# endif + + return ~(b > a); +} + +static inline native_simd operator>=(const native_simd& a, + const native_simd& b) noexcept +{ + return ~(b > a); +} + +template +static inline native_simd operator<=(const native_simd& a, const native_simd& b) noexcept +{ + return b >= a; +} + +static inline native_simd operator>(const native_simd& a, + const native_simd& b) noexcept +{ + return ~(b >= a); +} + +static inline native_simd operator>(const native_simd& a, + const native_simd& b) noexcept +{ + return ~(b >= a); +} + +static inline native_simd operator>(const native_simd& a, + const native_simd& b) noexcept +{ + __m128i signbit = _mm_set1_epi32(static_cast(0x80000000)); + __m128i a1 = _mm_xor_si128(a, signbit); + __m128i b1 = _mm_xor_si128(b, signbit); + return _mm_cmpgt_epi32(a1, b1); // signed compare +} + +static inline native_simd operator>(const native_simd& a, + const native_simd& b) noexcept +{ + __m128i sign32 = _mm_set1_epi32(static_cast(0x80000000)); // sign bit of each dword + __m128i aflip = _mm_xor_si128(a, sign32); // a with sign bits flipped to use signed compare + __m128i bflip = _mm_xor_si128(b, sign32); // b with sign bits flipped to use signed compare + __m128i equal = _mm_cmpeq_epi32(a, b); // a == b, dwords + __m128i bigger = _mm_cmpgt_epi32(aflip, bflip); // a > b, dwords + __m128i biggerl = _mm_shuffle_epi32(bigger, 0xA0); // a > b, low dwords copied to high dwords + __m128i eqbig = _mm_and_si128(equal, biggerl); // high part equal and low part bigger + __m128i hibig = _mm_or_si128(bigger, eqbig); // high part bigger or high part equal and low part bigger + __m128i big = _mm_shuffle_epi32(hibig, 0xF5); // result copied to low part + return big; +} + +template +static inline native_simd operator<(const native_simd& a, const native_simd& b) noexcept +{ + return b > a; +} + +} // namespace simd_sse2 +} // namespace detail +} // namespace rapidfuzz + +# endif +#endif +#include + +namespace rapidfuzz::detail { + +template +struct NormalizedMetricBase { + template >> + static double normalized_distance(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + Args... args, double score_cutoff, double score_hint) + { + return _normalized_distance(Range(first1, last1), Range(first2, last2), std::forward(args)..., + score_cutoff, score_hint); + } + + template + static double normalized_distance(const Sentence1& s1, const Sentence2& s2, Args... args, + double score_cutoff, double score_hint) + { + return _normalized_distance(Range(s1), Range(s2), std::forward(args)..., score_cutoff, + score_hint); + } + + template >> + static double normalized_similarity(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + Args... args, double score_cutoff, double score_hint) + { + return _normalized_similarity(Range(first1, last1), Range(first2, last2), std::forward(args)..., + score_cutoff, score_hint); + } + + template + static double normalized_similarity(const Sentence1& s1, const Sentence2& s2, Args... args, + double score_cutoff, double score_hint) + { + return _normalized_similarity(Range(s1), Range(s2), std::forward(args)..., score_cutoff, + score_hint); + } + +protected: + template + static double _normalized_distance(const Range& s1, const Range& s2, Args... args, + double score_cutoff, double score_hint) + { + auto maximum = T::maximum(s1, s2, args...); + auto cutoff_distance = + static_cast(std::ceil(static_cast(maximum) * score_cutoff)); + auto hint_distance = + static_cast(std::ceil(static_cast(maximum) * score_hint)); + auto dist = T::_distance(s1, s2, std::forward(args)..., cutoff_distance, hint_distance); + double norm_dist = (maximum != 0) ? static_cast(dist) / static_cast(maximum) : 0.0; + return (norm_dist <= score_cutoff) ? norm_dist : 1.0; + } + + template + static double _normalized_similarity(const Range& s1, const Range& s2, Args... args, + double score_cutoff, double score_hint) + { + double cutoff_score = NormSim_to_NormDist(score_cutoff); + double hint_score = NormSim_to_NormDist(score_hint); + double norm_dist = + _normalized_distance(s1, s2, std::forward(args)..., cutoff_score, hint_score); + double norm_sim = 1.0 - norm_dist; + return (norm_sim >= score_cutoff) ? norm_sim : 0.0; + } + + NormalizedMetricBase() + {} + friend T; +}; + +template +struct DistanceBase : public NormalizedMetricBase { + template >> + static ResType distance(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, Args... args, + ResType score_cutoff, ResType score_hint) + { + return T::_distance(Range(first1, last1), Range(first2, last2), std::forward(args)..., + score_cutoff, score_hint); + } + + template + static ResType distance(const Sentence1& s1, const Sentence2& s2, Args... args, ResType score_cutoff, + ResType score_hint) + { + return T::_distance(Range(s1), Range(s2), std::forward(args)..., score_cutoff, score_hint); + } + + template >> + static ResType similarity(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, Args... args, + ResType score_cutoff, ResType score_hint) + { + return _similarity(Range(first1, last1), Range(first2, last2), std::forward(args)..., + score_cutoff, score_hint); + } + + template + static ResType similarity(const Sentence1& s1, const Sentence2& s2, Args... args, ResType score_cutoff, + ResType score_hint) + { + return _similarity(Range(s1), Range(s2), std::forward(args)..., score_cutoff, score_hint); + } + +protected: + template + static ResType _similarity(Range s1, Range s2, Args... args, ResType score_cutoff, + ResType score_hint) + { + auto maximum = T::maximum(s1, s2, args...); + if (score_cutoff > maximum) return 0; + + score_hint = std::min(score_cutoff, score_hint); + ResType cutoff_distance = maximum - score_cutoff; + ResType hint_distance = maximum - score_hint; + ResType dist = T::_distance(s1, s2, std::forward(args)..., cutoff_distance, hint_distance); + ResType sim = maximum - dist; + return (sim >= score_cutoff) ? sim : 0; + } + + DistanceBase() + {} + friend T; +}; + +template +struct SimilarityBase : public NormalizedMetricBase { + template >> + static ResType distance(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, Args... args, + ResType score_cutoff, ResType score_hint) + { + return _distance(Range(first1, last1), Range(first2, last2), std::forward(args)..., + score_cutoff, score_hint); + } + + template + static ResType distance(const Sentence1& s1, const Sentence2& s2, Args... args, ResType score_cutoff, + ResType score_hint) + { + return _distance(Range(s1), Range(s2), std::forward(args)..., score_cutoff, score_hint); + } + + template >> + static ResType similarity(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, Args... args, + ResType score_cutoff, ResType score_hint) + { + return T::_similarity(Range(first1, last1), Range(first2, last2), std::forward(args)..., + score_cutoff, score_hint); + } + + template + static ResType similarity(const Sentence1& s1, const Sentence2& s2, Args... args, ResType score_cutoff, + ResType score_hint) + { + return T::_similarity(Range(s1), Range(s2), std::forward(args)..., score_cutoff, score_hint); + } + +protected: + template + static ResType _distance(const Range& s1, const Range& s2, Args... args, + ResType score_cutoff, ResType score_hint) + { + auto maximum = T::maximum(s1, s2, args...); + ResType cutoff_similarity = + (maximum >= score_cutoff) ? maximum - score_cutoff : static_cast(WorstSimilarity); + ResType hint_similarity = + (maximum >= score_hint) ? maximum - score_hint : static_cast(WorstSimilarity); + ResType sim = T::_similarity(s1, s2, std::forward(args)..., cutoff_similarity, hint_similarity); + ResType dist = maximum - sim; + + if constexpr (std::is_floating_point_v) + return (dist <= score_cutoff) ? dist : 1.0; + else + return (dist <= score_cutoff) ? dist : score_cutoff + 1; + } + + SimilarityBase() + {} + friend T; +}; + +template +struct CachedNormalizedMetricBase { + template + double normalized_distance(InputIt2 first2, InputIt2 last2, double score_cutoff = 1.0, + double score_hint = 1.0) const + { + return _normalized_distance(Range(first2, last2), score_cutoff, score_hint); + } + + template + double normalized_distance(const Sentence2& s2, double score_cutoff = 1.0, double score_hint = 1.0) const + { + return _normalized_distance(Range(s2), score_cutoff, score_hint); + } + + template + double normalized_similarity(InputIt2 first2, InputIt2 last2, double score_cutoff = 0.0, + double score_hint = 0.0) const + { + return _normalized_similarity(Range(first2, last2), score_cutoff, score_hint); + } + + template + double normalized_similarity(const Sentence2& s2, double score_cutoff = 0.0, + double score_hint = 0.0) const + { + return _normalized_similarity(Range(s2), score_cutoff, score_hint); + } + +protected: + template + double _normalized_distance(const Range& s2, double score_cutoff, double score_hint) const + { + const T& derived = static_cast(*this); + auto maximum = derived.maximum(s2); + auto cutoff_distance = + static_cast(std::ceil(static_cast(maximum) * score_cutoff)); + auto hint_distance = + static_cast(std::ceil(static_cast(maximum) * score_hint)); + double dist = static_cast(derived._distance(s2, cutoff_distance, hint_distance)); + double norm_dist = (maximum != 0) ? dist / static_cast(maximum) : 0.0; + return (norm_dist <= score_cutoff) ? norm_dist : 1.0; + } + + template + double _normalized_similarity(const Range& s2, double score_cutoff, double score_hint) const + { + double cutoff_score = NormSim_to_NormDist(score_cutoff); + double hint_score = NormSim_to_NormDist(score_hint); + double norm_dist = _normalized_distance(s2, cutoff_score, hint_score); + double norm_sim = 1.0 - norm_dist; + return (norm_sim >= score_cutoff) ? norm_sim : 0.0; + } + + CachedNormalizedMetricBase() + {} + friend T; +}; + +template +struct CachedDistanceBase : public CachedNormalizedMetricBase { + template + ResType distance(InputIt2 first2, InputIt2 last2, + ResType score_cutoff = static_cast(WorstDistance), + ResType score_hint = static_cast(WorstDistance)) const + { + const T& derived = static_cast(*this); + return derived._distance(Range(first2, last2), score_cutoff, score_hint); + } + + template + ResType distance(const Sentence2& s2, ResType score_cutoff = static_cast(WorstDistance), + ResType score_hint = static_cast(WorstDistance)) const + { + const T& derived = static_cast(*this); + return derived._distance(Range(s2), score_cutoff, score_hint); + } + + template + ResType similarity(InputIt2 first2, InputIt2 last2, + ResType score_cutoff = static_cast(WorstSimilarity), + ResType score_hint = static_cast(WorstSimilarity)) const + { + return _similarity(Range(first2, last2), score_cutoff, score_hint); + } + + template + ResType similarity(const Sentence2& s2, ResType score_cutoff = static_cast(WorstSimilarity), + ResType score_hint = static_cast(WorstSimilarity)) const + { + return _similarity(Range(s2), score_cutoff, score_hint); + } + +protected: + template + ResType _similarity(const Range& s2, ResType score_cutoff, ResType score_hint) const + { + const T& derived = static_cast(*this); + ResType maximum = derived.maximum(s2); + if (score_cutoff > maximum) return 0; + + score_hint = std::min(score_cutoff, score_hint); + ResType cutoff_distance = maximum - score_cutoff; + ResType hint_distance = maximum - score_hint; + ResType dist = derived._distance(s2, cutoff_distance, hint_distance); + ResType sim = maximum - dist; + return (sim >= score_cutoff) ? sim : 0; + } + + CachedDistanceBase() + {} + friend T; +}; + +template +struct CachedSimilarityBase : public CachedNormalizedMetricBase { + template + ResType distance(InputIt2 first2, InputIt2 last2, + ResType score_cutoff = static_cast(WorstDistance), + ResType score_hint = static_cast(WorstDistance)) const + { + return _distance(Range(first2, last2), score_cutoff, score_hint); + } + + template + ResType distance(const Sentence2& s2, ResType score_cutoff = static_cast(WorstDistance), + ResType score_hint = static_cast(WorstDistance)) const + { + return _distance(Range(s2), score_cutoff, score_hint); + } + + template + ResType similarity(InputIt2 first2, InputIt2 last2, + ResType score_cutoff = static_cast(WorstSimilarity), + ResType score_hint = static_cast(WorstSimilarity)) const + { + const T& derived = static_cast(*this); + return derived._similarity(Range(first2, last2), score_cutoff, score_hint); + } + + template + ResType similarity(const Sentence2& s2, ResType score_cutoff = static_cast(WorstSimilarity), + ResType score_hint = static_cast(WorstSimilarity)) const + { + const T& derived = static_cast(*this); + return derived._similarity(Range(s2), score_cutoff, score_hint); + } + +protected: + template + ResType _distance(const Range& s2, ResType score_cutoff, ResType score_hint) const + { + const T& derived = static_cast(*this); + ResType maximum = derived.maximum(s2); + ResType cutoff_similarity = (maximum > score_cutoff) ? maximum - score_cutoff : 0; + ResType hint_similarity = (maximum > score_hint) ? maximum - score_hint : 0; + ResType sim = derived._similarity(s2, cutoff_similarity, hint_similarity); + ResType dist = maximum - sim; + + if constexpr (std::is_floating_point_v) + return (dist <= score_cutoff) ? dist : 1.0; + else + return (dist <= score_cutoff) ? dist : score_cutoff + 1; + } + + CachedSimilarityBase() + {} + friend T; +}; + +template +struct MultiNormalizedMetricBase { + template + void normalized_distance(double* scores, size_t score_count, InputIt2 first2, InputIt2 last2, + double score_cutoff = 1.0) const + { + _normalized_distance(scores, score_count, Range(first2, last2), score_cutoff); + } + + template + void normalized_distance(double* scores, size_t score_count, const Sentence2& s2, + double score_cutoff = 1.0) const + { + _normalized_distance(scores, score_count, Range(s2), score_cutoff); + } + + template + void normalized_similarity(double* scores, size_t score_count, InputIt2 first2, InputIt2 last2, + double score_cutoff = 0.0) const + { + _normalized_similarity(scores, score_count, Range(first2, last2), score_cutoff); + } + + template + void normalized_similarity(double* scores, size_t score_count, const Sentence2& s2, + double score_cutoff = 0.0) const + { + _normalized_similarity(scores, score_count, Range(s2), score_cutoff); + } + +protected: + template + void _normalized_distance(double* scores, size_t score_count, const Range& s2, + double score_cutoff = 1.0) const + { + const T& derived = static_cast(*this); + if (score_count < derived.result_count()) + throw std::invalid_argument("scores has to have >= result_count() elements"); + + // reinterpretation only works when the types have the same size + ResType* scores_orig = nullptr; + if constexpr (sizeof(double) == sizeof(ResType)) + scores_orig = reinterpret_cast(scores); + else + scores_orig = new ResType[derived.result_count()]; + + derived.distance(scores_orig, derived.result_count(), s2); + + for (size_t i = 0; i < derived.get_input_count(); ++i) { + auto maximum = derived.maximum(i, s2); + double norm_dist = + (maximum != 0) ? static_cast(scores_orig[i]) / static_cast(maximum) : 0.0; + scores[i] = (norm_dist <= score_cutoff) ? norm_dist : 1.0; + } + + if constexpr (sizeof(double) != sizeof(ResType)) delete[] scores_orig; + } + + template + void _normalized_similarity(double* scores, size_t score_count, const Range& s2, + double score_cutoff) const + { + const T& derived = static_cast(*this); + _normalized_distance(scores, score_count, s2); + + for (size_t i = 0; i < derived.get_input_count(); ++i) { + double norm_sim = 1.0 - scores[i]; + scores[i] = (norm_sim >= score_cutoff) ? norm_sim : 0.0; + } + } + + MultiNormalizedMetricBase() + {} + friend T; +}; + +template +struct MultiDistanceBase : public MultiNormalizedMetricBase { + template + void distance(ResType* scores, size_t score_count, InputIt2 first2, InputIt2 last2, + ResType score_cutoff = static_cast(WorstDistance)) const + { + const T& derived = static_cast(*this); + derived._distance(scores, score_count, Range(first2, last2), score_cutoff); + } + + template + void distance(ResType* scores, size_t score_count, const Sentence2& s2, + ResType score_cutoff = static_cast(WorstDistance)) const + { + const T& derived = static_cast(*this); + derived._distance(scores, score_count, Range(s2), score_cutoff); + } + + template + void similarity(ResType* scores, size_t score_count, InputIt2 first2, InputIt2 last2, + ResType score_cutoff = static_cast(WorstSimilarity)) const + { + _similarity(scores, score_count, Range(first2, last2), score_cutoff); + } + + template + void similarity(ResType* scores, size_t score_count, const Sentence2& s2, + ResType score_cutoff = static_cast(WorstSimilarity)) const + { + _similarity(scores, score_count, Range(s2), score_cutoff); + } + +protected: + template + void _similarity(ResType* scores, size_t score_count, const Range& s2, + ResType score_cutoff) const + { + const T& derived = static_cast(*this); + derived._distance(scores, score_count, s2); + + for (size_t i = 0; i < derived.get_input_count(); ++i) { + ResType maximum = derived.maximum(i, s2); + ResType sim = maximum - scores[i]; + scores[i] = (sim >= score_cutoff) ? sim : 0; + } + } + + MultiDistanceBase() + {} + friend T; +}; + +template +struct MultiSimilarityBase : public MultiNormalizedMetricBase { + template + void distance(ResType* scores, size_t score_count, InputIt2 first2, InputIt2 last2, + ResType score_cutoff = static_cast(WorstDistance)) const + { + _distance(scores, score_count, Range(first2, last2), score_cutoff); + } + + template + void distance(ResType* scores, size_t score_count, const Sentence2& s2, + ResType score_cutoff = static_cast(WorstDistance)) const + { + _distance(scores, score_count, Range(s2), score_cutoff); + } + + template + void similarity(ResType* scores, size_t score_count, InputIt2 first2, InputIt2 last2, + ResType score_cutoff = static_cast(WorstSimilarity)) const + { + const T& derived = static_cast(*this); + derived._similarity(scores, score_count, Range(first2, last2), score_cutoff); + } + + template + void similarity(ResType* scores, size_t score_count, const Sentence2& s2, + ResType score_cutoff = static_cast(WorstSimilarity)) const + { + const T& derived = static_cast(*this); + derived._similarity(scores, score_count, Range(s2), score_cutoff); + } + +protected: + template + void _distance(ResType* scores, size_t score_count, const Range& s2, ResType score_cutoff) const + { + const T& derived = static_cast(*this); + derived._similarity(scores, score_count, s2); + + for (size_t i = 0; i < derived.get_input_count(); ++i) { + ResType maximum = derived.maximum(i, s2); + ResType dist = maximum - scores[i]; + + if constexpr (std::is_floating_point_v) + scores[i] = (dist <= score_cutoff) ? dist : 1.0; + else + scores[i] = (dist <= score_cutoff) ? dist : score_cutoff + 1; + } + } + + MultiSimilarityBase() + {} + friend T; +}; + +} // namespace rapidfuzz::detail + +namespace rapidfuzz::detail { + +template +struct RowId { + IntType val = -1; + friend bool operator==(const RowId& lhs, const RowId& rhs) + { + return lhs.val == rhs.val; + } + + friend bool operator!=(const RowId& lhs, const RowId& rhs) + { + return !(lhs == rhs); + } +}; + +/* + * based on the paper + * "Linear space string correction algorithm using the Damerau-Levenshtein distance" + * from Chunchun Zhao and Sartaj Sahni + */ +template +size_t damerau_levenshtein_distance_zhao(const Range& s1, const Range& s2, size_t max) +{ + // todo check types + IntType len1 = static_cast(s1.size()); + IntType len2 = static_cast(s2.size()); + IntType maxVal = static_cast(std::max(len1, len2) + 1); + assert(std::numeric_limits::max() > maxVal); + + HybridGrowingHashmap::value_type, RowId> last_row_id; + size_t size = s2.size() + 2; + assume(size != 0); + std::vector FR_arr(size, maxVal); + std::vector R1_arr(size, maxVal); + std::vector R_arr(size); + R_arr[0] = maxVal; + std::iota(R_arr.begin() + 1, R_arr.end(), IntType(0)); + + IntType* R = &R_arr[1]; + IntType* R1 = &R1_arr[1]; + IntType* FR = &FR_arr[1]; + + auto iter_s1 = s1.begin(); + for (IntType i = 1; i <= len1; i++) { + std::swap(R, R1); + IntType last_col_id = -1; + IntType last_i2l1 = R[0]; + R[0] = i; + IntType T = maxVal; + + auto iter_s2 = s2.begin(); + for (IntType j = 1; j <= len2; j++) { + int64_t diag = R1[j - 1] + static_cast(*iter_s1 != *iter_s2); + int64_t left = R[j - 1] + 1; + int64_t up = R1[j] + 1; + int64_t temp = std::min({diag, left, up}); + + if (*iter_s1 == *iter_s2) { + last_col_id = j; // last occurence of s1_i + FR[j] = R1[j - 2]; // save H_k-1,j-2 + T = last_i2l1; // save H_i-2,l-1 + } + else { + int64_t k = last_row_id.get(static_cast(*iter_s2)).val; + int64_t l = last_col_id; + + if ((j - l) == 1) { + int64_t transpose = FR[j] + (i - k); + temp = std::min(temp, transpose); + } + else if ((i - k) == 1) { + int64_t transpose = T + (j - l); + temp = std::min(temp, transpose); + } + } + + last_i2l1 = R[j]; + R[j] = static_cast(temp); + iter_s2++; + } + last_row_id[*iter_s1].val = i; + iter_s1++; + } + + size_t dist = static_cast(R[s2.size()]); + return (dist <= max) ? dist : max + 1; +} + +template +size_t damerau_levenshtein_distance(Range s1, Range s2, size_t max) +{ + size_t min_edits = abs_diff(s1.size(), s2.size()); + if (min_edits > max) return max + 1; + + /* common affix does not effect Levenshtein distance */ + remove_common_affix(s1, s2); + + size_t maxVal = std::max(s1.size(), s2.size()) + 1; + if (std::numeric_limits::max() > maxVal) + return damerau_levenshtein_distance_zhao(s1, s2, max); + else if (std::numeric_limits::max() > maxVal) + return damerau_levenshtein_distance_zhao(s1, s2, max); + else + return damerau_levenshtein_distance_zhao(s1, s2, max); +} + +class DamerauLevenshtein + : public DistanceBase::max()> { + friend DistanceBase::max()>; + friend NormalizedMetricBase; + + template + static size_t maximum(const Range& s1, const Range& s2) + { + return std::max(s1.size(), s2.size()); + } + + template + static size_t _distance(const Range& s1, const Range& s2, size_t score_cutoff, + [[maybe_unused]] size_t score_hint) + { + return damerau_levenshtein_distance(s1, s2, score_cutoff); + } +}; + +} // namespace rapidfuzz::detail + +namespace rapidfuzz { +/* the API will require a change when adding custom weights */ +namespace experimental { +/** + * @brief Calculates the Damerau Levenshtein distance between two strings. + * + * + * @tparam Sentence1 This is a string that can be converted to + * basic_string_view + * @tparam Sentence2 This is a string that can be converted to + * basic_string_view + * + * @param s1 + * string to compare with s2 (for type info check Template parameters above) + * @param s2 + * string to compare with s1 (for type info check Template parameters above) + * @param max + * Maximum Damerau Levenshtein distance between s1 and s2, that is + * considered as a result. If the distance is bigger than max, + * max + 1 is returned instead. Default is std::numeric_limits::max(), + * which deactivates this behaviour. + * + * @return Damerau Levenshtein distance between s1 and s2 + */ +template +size_t damerau_levenshtein_distance(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + size_t score_cutoff = std::numeric_limits::max()) +{ + return detail::DamerauLevenshtein::distance(first1, last1, first2, last2, score_cutoff, score_cutoff); +} + +template +size_t damerau_levenshtein_distance(const Sentence1& s1, const Sentence2& s2, + size_t score_cutoff = std::numeric_limits::max()) +{ + return detail::DamerauLevenshtein::distance(s1, s2, score_cutoff, score_cutoff); +} + +template +size_t damerau_levenshtein_similarity(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + size_t score_cutoff = 0) +{ + return detail::DamerauLevenshtein::similarity(first1, last1, first2, last2, score_cutoff, score_cutoff); +} + +template +size_t damerau_levenshtein_similarity(const Sentence1& s1, const Sentence2& s2, size_t score_cutoff = 0) +{ + return detail::DamerauLevenshtein::similarity(s1, s2, score_cutoff, score_cutoff); +} + +template +double damerau_levenshtein_normalized_distance(InputIt1 first1, InputIt1 last1, InputIt2 first2, + InputIt2 last2, double score_cutoff = 1.0) +{ + return detail::DamerauLevenshtein::normalized_distance(first1, last1, first2, last2, score_cutoff, + score_cutoff); +} + +template +double damerau_levenshtein_normalized_distance(const Sentence1& s1, const Sentence2& s2, + double score_cutoff = 1.0) +{ + return detail::DamerauLevenshtein::normalized_distance(s1, s2, score_cutoff, score_cutoff); +} + +/** + * @brief Calculates a normalized Damerau Levenshtein similarity + * + * @details + * Both string require a similar length + * + * + * @tparam Sentence1 This is a string that can be converted to + * basic_string_view + * @tparam Sentence2 This is a string that can be converted to + * basic_string_view + * + * @param s1 + * string to compare with s2 (for type info check Template parameters above) + * @param s2 + * string to compare with s1 (for type info check Template parameters above) + * @param score_cutoff + * Optional argument for a score threshold as a float between 0 and 1.0. + * For ratio < score_cutoff 0 is returned instead. Default is 0, + * which deactivates this behaviour. + * + * @return Normalized Damerau Levenshtein distance between s1 and s2 + * as a float between 0 and 1.0 + */ +template +double damerau_levenshtein_normalized_similarity(InputIt1 first1, InputIt1 last1, InputIt2 first2, + InputIt2 last2, double score_cutoff = 0.0) +{ + return detail::DamerauLevenshtein::normalized_similarity(first1, last1, first2, last2, score_cutoff, + score_cutoff); +} + +template +double damerau_levenshtein_normalized_similarity(const Sentence1& s1, const Sentence2& s2, + double score_cutoff = 0.0) +{ + return detail::DamerauLevenshtein::normalized_similarity(s1, s2, score_cutoff, score_cutoff); +} + +template +struct CachedDamerauLevenshtein : public detail::CachedDistanceBase, size_t, + 0, std::numeric_limits::max()> { + template + explicit CachedDamerauLevenshtein(const Sentence1& s1_) + : CachedDamerauLevenshtein(detail::to_begin(s1_), detail::to_end(s1_)) + {} + + template + CachedDamerauLevenshtein(InputIt1 first1, InputIt1 last1) : s1(first1, last1) + {} + +private: + friend detail::CachedDistanceBase, size_t, 0, + std::numeric_limits::max()>; + friend detail::CachedNormalizedMetricBase>; + + template + size_t maximum(const detail::Range& s2) const + { + return std::max(s1.size(), s2.size()); + } + + template + size_t _distance(const detail::Range& s2, size_t score_cutoff, + [[maybe_unused]] size_t score_hint) const + { + return rapidfuzz::experimental::damerau_levenshtein_distance(s1, s2, score_cutoff); + } + + std::vector s1; +}; + +template +explicit CachedDamerauLevenshtein(const Sentence1& s1_) -> CachedDamerauLevenshtein>; + +template +CachedDamerauLevenshtein(InputIt1 first1, InputIt1 last1) -> CachedDamerauLevenshtein>; + +} // namespace experimental +} // namespace rapidfuzz + +#include + +#include + +namespace rapidfuzz::detail { + +class Hamming : public DistanceBase::max(), bool> { + friend DistanceBase::max(), bool>; + friend NormalizedMetricBase; + + template + static size_t maximum(const Range& s1, const Range& s2, bool) + { + return std::max(s1.size(), s2.size()); + } + + template + static size_t _distance(const Range& s1, const Range& s2, bool pad, + size_t score_cutoff, [[maybe_unused]] size_t score_hint) + { + if (!pad && s1.size() != s2.size()) throw std::invalid_argument("Sequences are not the same length."); + + size_t min_len = std::min(s1.size(), s2.size()); + size_t dist = std::max(s1.size(), s2.size()); + auto iter_s1 = s1.begin(); + auto iter_s2 = s2.begin(); + for (size_t i = 0; i < min_len; ++i) + dist -= bool(*(iter_s1++) == *(iter_s2++)); + + return (dist <= score_cutoff) ? dist : score_cutoff + 1; + } +}; + +template +Editops hamming_editops(const Range& s1, const Range& s2, bool pad, size_t) +{ + if (!pad && s1.size() != s2.size()) throw std::invalid_argument("Sequences are not the same length."); + + Editops ops; + size_t min_len = std::min(s1.size(), s2.size()); + size_t i = 0; + for (; i < min_len; ++i) + if (s1[i] != s2[i]) ops.emplace_back(EditType::Replace, i, i); + + for (; i < s1.size(); ++i) + ops.emplace_back(EditType::Delete, i, s2.size()); + + for (; i < s2.size(); ++i) + ops.emplace_back(EditType::Insert, s1.size(), i); + + ops.set_src_len(s1.size()); + ops.set_dest_len(s2.size()); + return ops; +} + +} // namespace rapidfuzz::detail + +namespace rapidfuzz { + +/** + * @brief Calculates the Hamming distance between two strings. + * + * @details + * Both strings require a similar length + * + * + * @tparam Sentence1 This is a string that can be converted to + * basic_string_view + * @tparam Sentence2 This is a string that can be converted to + * basic_string_view + * + * @param s1 + * string to compare with s2 (for type info check Template parameters above) + * @param s2 + * string to compare with s1 (for type info check Template parameters above) + * @param max + * Maximum Hamming distance between s1 and s2, that is + * considered as a result. If the distance is bigger than max, + * max + 1 is returned instead. Default is std::numeric_limits::max(), + * which deactivates this behaviour. + * + * @return Hamming distance between s1 and s2 + */ +template +size_t hamming_distance(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, bool pad_ = true, + size_t score_cutoff = std::numeric_limits::max()) +{ + return detail::Hamming::distance(first1, last1, first2, last2, pad_, score_cutoff, score_cutoff); +} + +template +size_t hamming_distance(const Sentence1& s1, const Sentence2& s2, bool pad_ = true, + size_t score_cutoff = std::numeric_limits::max()) +{ + return detail::Hamming::distance(s1, s2, pad_, score_cutoff, score_cutoff); +} + +template +size_t hamming_similarity(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, bool pad_ = true, + size_t score_cutoff = 0) +{ + return detail::Hamming::similarity(first1, last1, first2, last2, pad_, score_cutoff, score_cutoff); +} + +template +size_t hamming_similarity(const Sentence1& s1, const Sentence2& s2, bool pad_ = true, size_t score_cutoff = 0) +{ + return detail::Hamming::similarity(s1, s2, pad_, score_cutoff, score_cutoff); +} + +template +double hamming_normalized_distance(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + bool pad_ = true, double score_cutoff = 1.0) +{ + return detail::Hamming::normalized_distance(first1, last1, first2, last2, pad_, score_cutoff, + score_cutoff); +} + +template +double hamming_normalized_distance(const Sentence1& s1, const Sentence2& s2, bool pad_ = true, + double score_cutoff = 1.0) +{ + return detail::Hamming::normalized_distance(s1, s2, pad_, score_cutoff, score_cutoff); +} + +template +Editops hamming_editops(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, bool pad_ = true, + size_t score_hint = std::numeric_limits::max()) +{ + return detail::hamming_editops(detail::Range(first1, last1), detail::Range(first2, last2), pad_, + score_hint); +} + +template +Editops hamming_editops(const Sentence1& s1, const Sentence2& s2, bool pad_ = true, + size_t score_hint = std::numeric_limits::max()) +{ + return detail::hamming_editops(detail::Range(s1), detail::Range(s2), pad_, score_hint); +} + +/** + * @brief Calculates a normalized hamming similarity + * + * @details + * Both string require a similar length + * + * + * @tparam Sentence1 This is a string that can be converted to + * basic_string_view + * @tparam Sentence2 This is a string that can be converted to + * basic_string_view + * + * @param s1 + * string to compare with s2 (for type info check Template parameters above) + * @param s2 + * string to compare with s1 (for type info check Template parameters above) + * @param score_cutoff + * Optional argument for a score threshold as a float between 0 and 1.0. + * For ratio < score_cutoff 0 is returned instead. Default is 0, + * which deactivates this behaviour. + * + * @return Normalized hamming distance between s1 and s2 + * as a float between 0 and 1.0 + */ +template +double hamming_normalized_similarity(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + bool pad_ = true, double score_cutoff = 0.0) +{ + return detail::Hamming::normalized_similarity(first1, last1, first2, last2, pad_, score_cutoff, + score_cutoff); +} + +template +double hamming_normalized_similarity(const Sentence1& s1, const Sentence2& s2, bool pad_ = true, + double score_cutoff = 0.0) +{ + return detail::Hamming::normalized_similarity(s1, s2, pad_, score_cutoff, score_cutoff); +} + +template +struct CachedHamming : public detail::CachedDistanceBase, size_t, 0, + std::numeric_limits::max()> { + template + explicit CachedHamming(const Sentence1& s1_, bool pad_ = true) + : CachedHamming(detail::to_begin(s1_), detail::to_end(s1_), pad_) + {} + + template + CachedHamming(InputIt1 first1, InputIt1 last1, bool pad_ = true) : s1(first1, last1), pad(pad_) + {} + +private: + friend detail::CachedDistanceBase, size_t, 0, std::numeric_limits::max()>; + friend detail::CachedNormalizedMetricBase>; + + template + size_t maximum(const detail::Range& s2) const + { + return std::max(s1.size(), s2.size()); + } + + template + size_t _distance(const detail::Range& s2, size_t score_cutoff, + [[maybe_unused]] size_t score_hint) const + { + return detail::Hamming::distance(s1, s2, pad, score_cutoff, score_hint); + } + + std::vector s1; + bool pad; +}; + +template +explicit CachedHamming(const Sentence1& s1_, bool pad_ = true) -> CachedHamming>; + +template +CachedHamming(InputIt1 first1, InputIt1 last1, bool pad_ = true) -> CachedHamming>; + +/**@}*/ + +} // namespace rapidfuzz + +#include + +#include +#include +#include + +namespace rapidfuzz::detail { + +struct BitvectorHashmap { + BitvectorHashmap() : m_map() + {} + + template + uint64_t get(CharT key) const noexcept + { + return m_map[lookup(static_cast(key))].value; + } + + template + uint64_t& operator[](CharT key) noexcept + { + uint32_t i = lookup(static_cast(key)); + m_map[i].key = static_cast(key); + return m_map[i].value; + } + +private: + /** + * lookup key inside the hashmap using a similar collision resolution + * strategy to CPython and Ruby + */ + uint32_t lookup(uint64_t key) const noexcept + { + uint32_t i = key % 128; + + if (!m_map[i].value || m_map[i].key == key) return i; + + uint64_t perturb = key; + while (true) { + i = (static_cast(i) * 5 + perturb + 1) % 128; + if (!m_map[i].value || m_map[i].key == key) return i; + + perturb >>= 5; + } + } + + struct MapElem { + uint64_t key = 0; + uint64_t value = 0; + }; + std::array m_map; +}; + +struct PatternMatchVector { + PatternMatchVector() : m_extendedAscii() + {} + + template + PatternMatchVector(const Range& s) : m_extendedAscii() + { + insert(s); + } + + size_t size() const noexcept + { + return 1; + } + + template + void insert(const Range& s) noexcept + { + uint64_t mask = 1; + for (const auto& ch : s) { + insert_mask(ch, mask); + mask <<= 1; + } + } + + template + void insert(CharT key, int64_t pos) noexcept + { + insert_mask(key, UINT64_C(1) << pos); + } + + uint64_t get(char key) const noexcept + { + /** treat char as value between 0 and 127 for performance reasons */ + return m_extendedAscii[static_cast(key)]; + } + + template + uint64_t get(CharT key) const noexcept + { + if (key >= 0 && key <= 255) + return m_extendedAscii[static_cast(key)]; + else + return m_map.get(key); + } + + template + uint64_t get(size_t block, CharT key) const noexcept + { + assert(block == 0); + (void)block; + return get(key); + } + + void insert_mask(char key, uint64_t mask) noexcept + { + /** treat char as value between 0 and 127 for performance reasons */ + m_extendedAscii[static_cast(key)] |= mask; + } + + template + void insert_mask(CharT key, uint64_t mask) noexcept + { + if (key >= 0 && key <= 255) + m_extendedAscii[static_cast(key)] |= mask; + else + m_map[key] |= mask; + } + +private: + BitvectorHashmap m_map; + std::array m_extendedAscii; +}; + +struct BlockPatternMatchVector { + BlockPatternMatchVector() = delete; + + BlockPatternMatchVector(size_t str_len) + : m_block_count(ceil_div(str_len, 64)), m_map(nullptr), m_extendedAscii(256, m_block_count, 0) + {} + + template + BlockPatternMatchVector(const Range& s) : BlockPatternMatchVector(s.size()) + { + insert(s); + } + + ~BlockPatternMatchVector() + { + delete[] m_map; + } + + size_t size() const noexcept + { + return m_block_count; + } + + template + void insert(size_t block, CharT ch, int pos) noexcept + { + uint64_t mask = UINT64_C(1) << pos; + insert_mask(block, ch, mask); + } + + /** + * @warning undefined behavior if iterator \p first is greater than \p last + * @tparam InputIt + * @param first + * @param last + */ + template + void insert(const Range& s) noexcept + { + uint64_t mask = 1; + size_t i = 0; + for (auto iter = s.begin(); iter != s.end(); ++iter, ++i) { + size_t block = i / 64; + insert_mask(block, *iter, mask); + mask = rotl(mask, 1); + } + } + + template + void insert_mask(size_t block, CharT key, uint64_t mask) noexcept + { + assert(block < size()); + if (key >= 0 && key <= 255) + m_extendedAscii[static_cast(key)][block] |= mask; + else { + if (!m_map) m_map = new BitvectorHashmap[m_block_count]; + m_map[block][key] |= mask; + } + } + + void insert_mask(size_t block, char key, uint64_t mask) noexcept + { + insert_mask(block, static_cast(key), mask); + } + + template + uint64_t get(size_t block, CharT key) const noexcept + { + if (key >= 0 && key <= 255) + return m_extendedAscii[static_cast(key)][block]; + else if (m_map) + return m_map[block].get(key); + else + return 0; + } + + uint64_t get(size_t block, char ch) const noexcept + { + return get(block, static_cast(ch)); + } + +private: + size_t m_block_count; + BitvectorHashmap* m_map; + BitMatrix m_extendedAscii; +}; + +} // namespace rapidfuzz::detail + +#include + +#include +#include + +namespace rapidfuzz::detail { + +template +struct LCSseqResult; + +template <> +struct LCSseqResult { + ShiftedBitMatrix S; + + size_t sim; +}; + +template <> +struct LCSseqResult { + size_t sim; +}; + +/* + * An encoded mbleven model table. + * + * Each 8-bit integer represents an edit sequence, with using two + * bits for a single operation. + * + * Each Row of 8 integers represent all possible combinations + * of edit sequences for a gived maximum edit distance and length + * difference between the two strings, that is below the maximum + * edit distance + * + * 0x1 = 01 = DELETE, + * 0x2 = 10 = INSERT + * + * 0x5 -> DEL + DEL + * 0x6 -> DEL + INS + * 0x9 -> INS + DEL + * 0xA -> INS + INS + */ +static constexpr std::array, 14> lcs_seq_mbleven2018_matrix = {{ + /* max edit distance 1 */ + {0}, + /* case does not occur */ /* len_diff 0 */ + {0x01}, /* len_diff 1 */ + /* max edit distance 2 */ + {0x09, 0x06}, /* len_diff 0 */ + {0x01}, /* len_diff 1 */ + {0x05}, /* len_diff 2 */ + /* max edit distance 3 */ + {0x09, 0x06}, /* len_diff 0 */ + {0x25, 0x19, 0x16}, /* len_diff 1 */ + {0x05}, /* len_diff 2 */ + {0x15}, /* len_diff 3 */ + /* max edit distance 4 */ + {0x96, 0x66, 0x5A, 0x99, 0x69, 0xA5}, /* len_diff 0 */ + {0x25, 0x19, 0x16}, /* len_diff 1 */ + {0x65, 0x56, 0x95, 0x59}, /* len_diff 2 */ + {0x15}, /* len_diff 3 */ + {0x55}, /* len_diff 4 */ +}}; + +template +size_t lcs_seq_mbleven2018(const Range& s1, const Range& s2, size_t score_cutoff) +{ + auto len1 = s1.size(); + auto len2 = s2.size(); + assert(len1 != 0); + assert(len2 != 0); + + if (len1 < len2) return lcs_seq_mbleven2018(s2, s1, score_cutoff); + + auto len_diff = len1 - len2; + size_t max_misses = len1 + len2 - 2 * score_cutoff; + size_t ops_index = (max_misses + max_misses * max_misses) / 2 + len_diff - 1; + auto& possible_ops = lcs_seq_mbleven2018_matrix[ops_index]; + size_t max_len = 0; + + for (uint8_t ops : possible_ops) { + auto iter_s1 = s1.begin(); + auto iter_s2 = s2.begin(); + size_t cur_len = 0; + + if (!ops) break; + + while (iter_s1 != s1.end() && iter_s2 != s2.end()) { + if (*iter_s1 != *iter_s2) { + if (!ops) break; + if (ops & 1) + iter_s1++; + else if (ops & 2) + iter_s2++; +#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && __GNUC__ < 10 +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wconversion" +#endif + ops >>= 2; +#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && __GNUC__ < 10 +# pragma GCC diagnostic pop +#endif + } + else { + cur_len++; + iter_s1++; + iter_s2++; + } + } + + max_len = std::max(max_len, cur_len); + } + + return (max_len >= score_cutoff) ? max_len : 0; +} + +#ifdef RAPIDFUZZ_SIMD +template +void lcs_simd(Range scores, const BlockPatternMatchVector& block, const Range& s2, + size_t score_cutoff) noexcept +{ +# ifdef RAPIDFUZZ_AVX2 + using namespace simd_avx2; +# else + using namespace simd_sse2; +# endif + auto score_iter = scores.begin(); + static constexpr size_t alignment = native_simd::alignment; + static constexpr size_t vecs = native_simd::size; + assert(block.size() % vecs == 0); + + static constexpr size_t interleaveCount = 3; + + size_t cur_vec = 0; + for (; cur_vec + interleaveCount * vecs <= block.size(); cur_vec += interleaveCount * vecs) { + std::array, interleaveCount> S; + unroll([&](auto j) { S[j] = static_cast(-1); }); + + for (const auto& ch : s2) { + unroll([&](auto j) { + alignas(32) std::array stored; + unroll([&](auto i) { stored[i] = block.get(cur_vec + j * vecs + i, ch); }); + + native_simd Matches(stored.data()); + native_simd u = S[j] & Matches; + S[j] = (S[j] + u) | (S[j] - u); + }); + } + + unroll([&](auto j) { + auto counts = popcount(~S[j]); + unroll([&](auto i) { + *score_iter = (counts[i] >= score_cutoff) ? static_cast(counts[i]) : 0; + score_iter++; + }); + }); + } + + for (; cur_vec < block.size(); cur_vec += vecs) { + native_simd S = static_cast(-1); + + for (const auto& ch : s2) { + alignas(alignment) std::array stored; + unroll([&](auto i) { stored[i] = block.get(cur_vec + i, ch); }); + + native_simd Matches(stored.data()); + native_simd u = S & Matches; + S = (S + u) | (S - u); + } + + auto counts = popcount(~S); + unroll([&](auto i) { + *score_iter = (counts[i] >= score_cutoff) ? static_cast(counts[i]) : 0; + score_iter++; + }); + } +} + +#endif + +template +auto lcs_unroll(const PMV& block, const Range&, const Range& s2, + size_t score_cutoff = 0) -> LCSseqResult +{ + uint64_t S[N]; + unroll([&](size_t i) { S[i] = ~UINT64_C(0); }); + + LCSseqResult res; + if constexpr (RecordMatrix) res.S = ShiftedBitMatrix(s2.size(), N, ~UINT64_C(0)); + + auto iter_s2 = s2.begin(); + for (size_t i = 0; i < s2.size(); ++i) { + uint64_t carry = 0; + + static constexpr size_t unroll_factor = 3; + for (unsigned int j = 0; j < N / unroll_factor; ++j) { + unroll([&](size_t word_) { + size_t word = word_ + j * unroll_factor; + uint64_t Matches = block.get(word, *iter_s2); + uint64_t u = S[word] & Matches; + uint64_t x = addc64(S[word], u, carry, &carry); + S[word] = x | (S[word] - u); + + if constexpr (RecordMatrix) res.S[i][word] = S[word]; + }); + } + + unroll([&](size_t word_) { + size_t word = word_ + N / unroll_factor * unroll_factor; + uint64_t Matches = block.get(word, *iter_s2); + uint64_t u = S[word] & Matches; + uint64_t x = addc64(S[word], u, carry, &carry); + S[word] = x | (S[word] - u); + + if constexpr (RecordMatrix) res.S[i][word] = S[word]; + }); + + iter_s2++; + } + + res.sim = 0; + unroll([&](size_t i) { res.sim += popcount(~S[i]); }); + + if (res.sim < score_cutoff) res.sim = 0; + + return res; +} + +/** + * implementation is following the paper Bit-Parallel LCS-length Computation Revisited + * from Heikki Hyyrö + * + * The paper refers to s1 as m and s2 as n + */ +template +auto lcs_blockwise(const PMV& PM, const Range& s1, const Range& s2, + size_t score_cutoff = 0) -> LCSseqResult +{ + assert(score_cutoff <= s1.size()); + assert(score_cutoff <= s2.size()); + + size_t word_size = sizeof(uint64_t) * 8; + size_t words = PM.size(); + std::vector S(words, ~UINT64_C(0)); + + size_t band_width_left = s1.size() - score_cutoff; + size_t band_width_right = s2.size() - score_cutoff; + + LCSseqResult res; + if constexpr (RecordMatrix) { + size_t full_band = band_width_left + 1 + band_width_right; + size_t full_band_words = std::min(words, full_band / word_size + 2); + res.S = ShiftedBitMatrix(s2.size(), full_band_words, ~UINT64_C(0)); + } + + /* first_block is the index of the first block in Ukkonen band. */ + size_t first_block = 0; + size_t last_block = std::min(words, ceil_div(band_width_left + 1, word_size)); + + auto iter_s2 = s2.begin(); + for (size_t row = 0; row < s2.size(); ++row) { + uint64_t carry = 0; + + if constexpr (RecordMatrix) res.S.set_offset(row, static_cast(first_block * word_size)); + + for (size_t word = first_block; word < last_block; ++word) { + const uint64_t Matches = PM.get(word, *iter_s2); + uint64_t Stemp = S[word]; + + uint64_t u = Stemp & Matches; + + uint64_t x = addc64(Stemp, u, carry, &carry); + S[word] = x | (Stemp - u); + + if constexpr (RecordMatrix) res.S[row][word - first_block] = S[word]; + } + + if (row > band_width_right) first_block = (row - band_width_right) / word_size; + + if (row + 1 + band_width_left <= s1.size()) + last_block = ceil_div(row + 1 + band_width_left, word_size); + + iter_s2++; + } + + res.sim = 0; + for (uint64_t Stemp : S) + res.sim += popcount(~Stemp); + + if (res.sim < score_cutoff) res.sim = 0; + + return res; +} + +template +size_t longest_common_subsequence(const PMV& PM, const Range& s1, const Range& s2, + size_t score_cutoff) +{ + assert(score_cutoff <= s1.size()); + assert(score_cutoff <= s2.size()); + + size_t word_size = sizeof(uint64_t) * 8; + size_t words = PM.size(); + size_t band_width_left = s1.size() - score_cutoff; + size_t band_width_right = s2.size() - score_cutoff; + size_t full_band = band_width_left + 1 + band_width_right; + size_t full_band_words = std::min(words, full_band / word_size + 2); + + if (full_band_words < words) return lcs_blockwise(PM, s1, s2, score_cutoff).sim; + + auto nr = ceil_div(s1.size(), 64); + switch (nr) { + case 0: return 0; + case 1: return lcs_unroll<1, false>(PM, s1, s2, score_cutoff).sim; + case 2: return lcs_unroll<2, false>(PM, s1, s2, score_cutoff).sim; + case 3: return lcs_unroll<3, false>(PM, s1, s2, score_cutoff).sim; + case 4: return lcs_unroll<4, false>(PM, s1, s2, score_cutoff).sim; + case 5: return lcs_unroll<5, false>(PM, s1, s2, score_cutoff).sim; + case 6: return lcs_unroll<6, false>(PM, s1, s2, score_cutoff).sim; + case 7: return lcs_unroll<7, false>(PM, s1, s2, score_cutoff).sim; + case 8: return lcs_unroll<8, false>(PM, s1, s2, score_cutoff).sim; + default: return lcs_blockwise(PM, s1, s2, score_cutoff).sim; + } +} + +template +size_t longest_common_subsequence(const Range& s1, const Range& s2, size_t score_cutoff) +{ + if (s1.empty()) return 0; + if (s1.size() <= 64) return longest_common_subsequence(PatternMatchVector(s1), s1, s2, score_cutoff); + + return longest_common_subsequence(BlockPatternMatchVector(s1), s1, s2, score_cutoff); +} + +template +size_t lcs_seq_similarity(const BlockPatternMatchVector& block, Range s1, Range s2, + size_t score_cutoff) +{ + auto len1 = s1.size(); + auto len2 = s2.size(); + + if (score_cutoff > len1 || score_cutoff > len2) return 0; + + size_t max_misses = len1 + len2 - 2 * score_cutoff; + + /* no edits are allowed */ + if (max_misses == 0 || (max_misses == 1 && len1 == len2)) + return std::equal(s1.begin(), s1.end(), s2.begin(), s2.end()) ? len1 : 0; + + if (max_misses < abs_diff(len1, len2)) return 0; + + // do this first, since we can not remove any affix in encoded form + if (max_misses >= 5) return longest_common_subsequence(block, s1, s2, score_cutoff); + + /* common affix does not effect Levenshtein distance */ + StringAffix affix = remove_common_affix(s1, s2); + size_t lcs_sim = affix.prefix_len + affix.suffix_len; + if (!s1.empty() && !s2.empty()) { + size_t adjusted_cutoff = score_cutoff >= lcs_sim ? score_cutoff - lcs_sim : 0; + lcs_sim += lcs_seq_mbleven2018(s1, s2, adjusted_cutoff); + } + + return (lcs_sim >= score_cutoff) ? lcs_sim : 0; +} + +template +size_t lcs_seq_similarity(Range s1, Range s2, size_t score_cutoff) +{ + auto len1 = s1.size(); + auto len2 = s2.size(); + + // Swapping the strings so the second string is shorter + if (len1 < len2) return lcs_seq_similarity(s2, s1, score_cutoff); + + if (score_cutoff > len1 || score_cutoff > len2) return 0; + + size_t max_misses = len1 + len2 - 2 * score_cutoff; + + /* no edits are allowed */ + if (max_misses == 0 || (max_misses == 1 && len1 == len2)) + return std::equal(s1.begin(), s1.end(), s2.begin(), s2.end()) ? len1 : 0; + + if (max_misses < abs_diff(len1, len2)) return 0; + + /* common affix does not effect Levenshtein distance */ + StringAffix affix = remove_common_affix(s1, s2); + size_t lcs_sim = affix.prefix_len + affix.suffix_len; + if (s1.size() && s2.size()) { + size_t adjusted_cutoff = score_cutoff >= lcs_sim ? score_cutoff - lcs_sim : 0; + if (max_misses < 5) + lcs_sim += lcs_seq_mbleven2018(s1, s2, adjusted_cutoff); + else + lcs_sim += longest_common_subsequence(s1, s2, adjusted_cutoff); + } + + return (lcs_sim >= score_cutoff) ? lcs_sim : 0; +} + +/** + * @brief recover alignment from bitparallel Levenshtein matrix + */ +template +Editops recover_alignment(const Range& s1, const Range& s2, + const LCSseqResult& matrix, StringAffix affix) +{ + size_t len1 = s1.size(); + size_t len2 = s2.size(); + size_t dist = len1 + len2 - 2 * matrix.sim; + Editops editops(dist); + editops.set_src_len(len1 + affix.prefix_len + affix.suffix_len); + editops.set_dest_len(len2 + affix.prefix_len + affix.suffix_len); + + if (dist == 0) return editops; + + [[maybe_unused]] size_t band_width_right = s2.size() - matrix.sim; + + auto col = len1; + auto row = len2; + + while (row && col) { + /* Deletion */ + if (matrix.S.test_bit(row - 1, col - 1)) { + assert(dist > 0); + assert(static_cast(col) >= + static_cast(row) - static_cast(band_width_right)); + dist--; + col--; + editops[dist].type = EditType::Delete; + editops[dist].src_pos = col + affix.prefix_len; + editops[dist].dest_pos = row + affix.prefix_len; + } + else { + row--; + + /* Insertion */ + if (row && !(matrix.S.test_bit(row - 1, col - 1))) { + assert(dist > 0); + dist--; + editops[dist].type = EditType::Insert; + editops[dist].src_pos = col + affix.prefix_len; + editops[dist].dest_pos = row + affix.prefix_len; + } + /* Match */ + else { + col--; + assert(s1[col] == s2[row]); + } + } + } + + while (col) { + dist--; + col--; + editops[dist].type = EditType::Delete; + editops[dist].src_pos = col + affix.prefix_len; + editops[dist].dest_pos = row + affix.prefix_len; + } + + while (row) { + dist--; + row--; + editops[dist].type = EditType::Insert; + editops[dist].src_pos = col + affix.prefix_len; + editops[dist].dest_pos = row + affix.prefix_len; + } + + return editops; +} + +template +LCSseqResult lcs_matrix(const Range& s1, const Range& s2) +{ + size_t nr = ceil_div(s1.size(), 64); + switch (nr) { + case 0: + { + LCSseqResult res; + res.sim = 0; + return res; + } + case 1: return lcs_unroll<1, true>(PatternMatchVector(s1), s1, s2); + case 2: return lcs_unroll<2, true>(BlockPatternMatchVector(s1), s1, s2); + case 3: return lcs_unroll<3, true>(BlockPatternMatchVector(s1), s1, s2); + case 4: return lcs_unroll<4, true>(BlockPatternMatchVector(s1), s1, s2); + case 5: return lcs_unroll<5, true>(BlockPatternMatchVector(s1), s1, s2); + case 6: return lcs_unroll<6, true>(BlockPatternMatchVector(s1), s1, s2); + case 7: return lcs_unroll<7, true>(BlockPatternMatchVector(s1), s1, s2); + case 8: return lcs_unroll<8, true>(BlockPatternMatchVector(s1), s1, s2); + default: return lcs_blockwise(BlockPatternMatchVector(s1), s1, s2); + } +} + +template +Editops lcs_seq_editops(Range s1, Range s2) +{ + /* prefix and suffix are no-ops, which do not need to be added to the editops */ + StringAffix affix = remove_common_affix(s1, s2); + + return recover_alignment(s1, s2, lcs_matrix(s1, s2), affix); +} + +class LCSseq : public SimilarityBase::max()> { + friend SimilarityBase::max()>; + friend NormalizedMetricBase; + + template + static size_t maximum(const Range& s1, const Range& s2) + { + return std::max(s1.size(), s2.size()); + } + + template + static size_t _similarity(const Range& s1, const Range& s2, size_t score_cutoff, + [[maybe_unused]] size_t score_hint) + { + return lcs_seq_similarity(s1, s2, score_cutoff); + } +}; + +} // namespace rapidfuzz::detail + +#include +#include + +namespace rapidfuzz { + +template +size_t lcs_seq_distance(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + size_t score_cutoff = std::numeric_limits::max()) +{ + return detail::LCSseq::distance(first1, last1, first2, last2, score_cutoff, score_cutoff); +} + +template +size_t lcs_seq_distance(const Sentence1& s1, const Sentence2& s2, + size_t score_cutoff = std::numeric_limits::max()) +{ + return detail::LCSseq::distance(s1, s2, score_cutoff, score_cutoff); +} + +template +size_t lcs_seq_similarity(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + size_t score_cutoff = 0) +{ + return detail::LCSseq::similarity(first1, last1, first2, last2, score_cutoff, score_cutoff); +} + +template +size_t lcs_seq_similarity(const Sentence1& s1, const Sentence2& s2, size_t score_cutoff = 0) +{ + return detail::LCSseq::similarity(s1, s2, score_cutoff, score_cutoff); +} + +template +double lcs_seq_normalized_distance(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + double score_cutoff = 1.0) +{ + return detail::LCSseq::normalized_distance(first1, last1, first2, last2, score_cutoff, score_cutoff); +} + +template +double lcs_seq_normalized_distance(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 1.0) +{ + return detail::LCSseq::normalized_distance(s1, s2, score_cutoff, score_cutoff); +} + +template +double lcs_seq_normalized_similarity(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + double score_cutoff = 0.0) +{ + return detail::LCSseq::normalized_similarity(first1, last1, first2, last2, score_cutoff, score_cutoff); +} + +template +double lcs_seq_normalized_similarity(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 0.0) +{ + return detail::LCSseq::normalized_similarity(s1, s2, score_cutoff, score_cutoff); +} + +template +Editops lcs_seq_editops(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2) +{ + return detail::lcs_seq_editops(detail::Range(first1, last1), detail::Range(first2, last2)); +} + +template +Editops lcs_seq_editops(const Sentence1& s1, const Sentence2& s2) +{ + return detail::lcs_seq_editops(detail::Range(s1), detail::Range(s2)); +} + +#ifdef RAPIDFUZZ_SIMD +namespace experimental { +template +struct MultiLCSseq : public detail::MultiSimilarityBase, size_t, 0, + std::numeric_limits::max()> { +private: + friend detail::MultiSimilarityBase, size_t, 0, std::numeric_limits::max()>; + friend detail::MultiNormalizedMetricBase, size_t>; + + constexpr static size_t get_vec_size() + { +# ifdef RAPIDFUZZ_AVX2 + using namespace detail::simd_avx2; +# else + using namespace detail::simd_sse2; +# endif + if constexpr (MaxLen <= 8) + return native_simd::size; + else if constexpr (MaxLen <= 16) + return native_simd::size; + else if constexpr (MaxLen <= 32) + return native_simd::size; + else if constexpr (MaxLen <= 64) + return native_simd::size; + + static_assert(MaxLen <= 64); + } + + constexpr static size_t find_block_count(size_t count) + { + size_t vec_size = get_vec_size(); + size_t simd_vec_count = detail::ceil_div(count, vec_size); + return detail::ceil_div(simd_vec_count * vec_size * MaxLen, 64); + } + +public: + MultiLCSseq(size_t count) : input_count(count), pos(0), PM(find_block_count(count) * 64) + { + str_lens.resize(result_count()); + } + + /** + * @brief get minimum size required for result vectors passed into + * - distance + * - similarity + * - normalized_distance + * - normalized_similarity + * + * @return minimum vector size + */ + size_t result_count() const + { + size_t vec_size = get_vec_size(); + size_t simd_vec_count = detail::ceil_div(input_count, vec_size); + return simd_vec_count * vec_size; + } + + template + void insert(const Sentence1& s1_) + { + insert(detail::to_begin(s1_), detail::to_end(s1_)); + } + + template + void insert(InputIt1 first1, InputIt1 last1) + { + auto len = std::distance(first1, last1); + int block_pos = static_cast((pos * MaxLen) % 64); + auto block = (pos * MaxLen) / 64; + assert(len <= MaxLen); + + if (pos >= input_count) throw std::invalid_argument("out of bounds insert"); + + str_lens[pos] = static_cast(len); + + for (; first1 != last1; ++first1) { + PM.insert(block, *first1, block_pos); + block_pos++; + } + pos++; + } + +private: + template + void _similarity(size_t* scores, size_t score_count, const detail::Range& s2, + size_t score_cutoff = 0) const + { + if (score_count < result_count()) + throw std::invalid_argument("scores has to have >= result_count() elements"); + + detail::Range scores_(scores, scores + score_count); + if constexpr (MaxLen == 8) + detail::lcs_simd(scores_, PM, s2, score_cutoff); + else if constexpr (MaxLen == 16) + detail::lcs_simd(scores_, PM, s2, score_cutoff); + else if constexpr (MaxLen == 32) + detail::lcs_simd(scores_, PM, s2, score_cutoff); + else if constexpr (MaxLen == 64) + detail::lcs_simd(scores_, PM, s2, score_cutoff); + } + + template + size_t maximum(size_t s1_idx, const detail::Range& s2) const + { + return std::max(str_lens[s1_idx], s2.size()); + } + + size_t get_input_count() const noexcept + { + return input_count; + } + + size_t input_count; + size_t pos; + detail::BlockPatternMatchVector PM; + std::vector str_lens; +}; +} /* namespace experimental */ +#endif + +template +struct CachedLCSseq + : detail::CachedSimilarityBase, size_t, 0, std::numeric_limits::max()> { + template + explicit CachedLCSseq(const Sentence1& s1_) : CachedLCSseq(detail::to_begin(s1_), detail::to_end(s1_)) + {} + + template + CachedLCSseq(InputIt1 first1, InputIt1 last1) : s1(first1, last1), PM(detail::Range(first1, last1)) + {} + +private: + friend detail::CachedSimilarityBase, size_t, 0, std::numeric_limits::max()>; + friend detail::CachedNormalizedMetricBase>; + + template + size_t maximum(const detail::Range& s2) const + { + return std::max(s1.size(), s2.size()); + } + + template + size_t _similarity(const detail::Range& s2, size_t score_cutoff, + [[maybe_unused]] size_t score_hint) const + { + return detail::lcs_seq_similarity(PM, detail::Range(s1), s2, score_cutoff); + } + + std::vector s1; + detail::BlockPatternMatchVector PM; +}; + +template +explicit CachedLCSseq(const Sentence1& s1_) -> CachedLCSseq>; + +template +CachedLCSseq(InputIt1 first1, InputIt1 last1) -> CachedLCSseq>; + +} // namespace rapidfuzz + +namespace rapidfuzz::detail { + +template +size_t indel_distance(const BlockPatternMatchVector& block, const Range& s1, + const Range& s2, size_t score_cutoff) +{ + size_t maximum = s1.size() + s2.size(); + size_t lcs_cutoff = (maximum / 2 >= score_cutoff) ? maximum / 2 - score_cutoff : 0; + size_t lcs_sim = lcs_seq_similarity(block, s1, s2, lcs_cutoff); + size_t dist = maximum - 2 * lcs_sim; + return (dist <= score_cutoff) ? dist : score_cutoff + 1; +} + +template +double indel_normalized_distance(const BlockPatternMatchVector& block, const Range& s1, + const Range& s2, double score_cutoff) +{ + size_t maximum = s1.size() + s2.size(); + size_t cutoff_distance = static_cast(std::ceil(static_cast(maximum) * score_cutoff)); + size_t dist = indel_distance(block, s1, s2, cutoff_distance); + double norm_dist = (maximum) ? static_cast(dist) / static_cast(maximum) : 0.0; + return (norm_dist <= score_cutoff) ? norm_dist : 1.0; +} + +template +double indel_normalized_similarity(const BlockPatternMatchVector& block, const Range& s1, + const Range& s2, double score_cutoff) +{ + double cutoff_score = NormSim_to_NormDist(score_cutoff); + double norm_dist = indel_normalized_distance(block, s1, s2, cutoff_score); + double norm_sim = 1.0 - norm_dist; + return (norm_sim >= score_cutoff) ? norm_sim : 0.0; +} + +class Indel : public DistanceBase::max()> { + friend DistanceBase::max()>; + friend NormalizedMetricBase; + + template + static size_t maximum(const Range& s1, const Range& s2) + { + return s1.size() + s2.size(); + } + + template + static size_t _distance(const Range& s1, const Range& s2, size_t score_cutoff, + size_t score_hint) + { + size_t maximum = Indel::maximum(s1, s2); + size_t lcs_cutoff = (maximum / 2 >= score_cutoff) ? maximum / 2 - score_cutoff : 0; + size_t lcs_hint = (maximum / 2 >= score_hint) ? maximum / 2 - score_hint : 0; + size_t lcs_sim = LCSseq::similarity(s1, s2, lcs_cutoff, lcs_hint); + size_t dist = maximum - 2 * lcs_sim; + return (dist <= score_cutoff) ? dist : score_cutoff + 1; + } +}; + +} // namespace rapidfuzz::detail + +namespace rapidfuzz { + +template +size_t indel_distance(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + size_t score_cutoff = std::numeric_limits::max()) +{ + return detail::Indel::distance(first1, last1, first2, last2, score_cutoff, score_cutoff); +} + +template +size_t indel_distance(const Sentence1& s1, const Sentence2& s2, + size_t score_cutoff = std::numeric_limits::max()) +{ + return detail::Indel::distance(s1, s2, score_cutoff, score_cutoff); +} + +template +size_t indel_similarity(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + size_t score_cutoff = 0.0) +{ + return detail::Indel::similarity(first1, last1, first2, last2, score_cutoff, score_cutoff); +} + +template +size_t indel_similarity(const Sentence1& s1, const Sentence2& s2, size_t score_cutoff = 0.0) +{ + return detail::Indel::similarity(s1, s2, score_cutoff, score_cutoff); +} + +template +double indel_normalized_distance(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + double score_cutoff = 1.0) +{ + return detail::Indel::normalized_distance(first1, last1, first2, last2, score_cutoff, score_cutoff); +} + +template +double indel_normalized_distance(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 1.0) +{ + return detail::Indel::normalized_distance(s1, s2, score_cutoff, score_cutoff); +} + +template +double indel_normalized_similarity(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + double score_cutoff = 0.0) +{ + return detail::Indel::normalized_similarity(first1, last1, first2, last2, score_cutoff, score_cutoff); +} + +template +double indel_normalized_similarity(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 0.0) +{ + return detail::Indel::normalized_similarity(s1, s2, score_cutoff, score_cutoff); +} + +template +Editops indel_editops(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2) +{ + return lcs_seq_editops(first1, last1, first2, last2); +} + +template +Editops indel_editops(const Sentence1& s1, const Sentence2& s2) +{ + return lcs_seq_editops(s1, s2); +} + +#ifdef RAPIDFUZZ_SIMD +namespace experimental { +template +struct MultiIndel + : public detail::MultiDistanceBase, size_t, 0, std::numeric_limits::max()> { +private: + friend detail::MultiDistanceBase, size_t, 0, std::numeric_limits::max()>; + friend detail::MultiNormalizedMetricBase, size_t>; + +public: + MultiIndel(size_t count) : scorer(count) + {} + + /** + * @brief get minimum size required for result vectors passed into + * - distance + * - similarity + * - normalized_distance + * - normalized_similarity + * + * @return minimum vector size + */ + size_t result_count() const + { + return scorer.result_count(); + } + + template + void insert(const Sentence1& s1_) + { + insert(detail::to_begin(s1_), detail::to_end(s1_)); + } + + template + void insert(InputIt1 first1, InputIt1 last1) + { + scorer.insert(first1, last1); + str_lens.push_back(static_cast(std::distance(first1, last1))); + } + +private: + template + void _distance(size_t* scores, size_t score_count, const detail::Range& s2, + size_t score_cutoff = std::numeric_limits::max()) const + { + scorer.similarity(scores, score_count, s2); + + for (size_t i = 0; i < get_input_count(); ++i) { + size_t maximum_ = maximum(i, s2); + size_t dist = maximum_ - 2 * scores[i]; + scores[i] = (dist <= score_cutoff) ? dist : score_cutoff + 1; + } + } + + template + size_t maximum(size_t s1_idx, const detail::Range& s2) const + { + return str_lens[s1_idx] + s2.size(); + } + + size_t get_input_count() const noexcept + { + return str_lens.size(); + } + + std::vector str_lens; + MultiLCSseq scorer; +}; +} /* namespace experimental */ +#endif + +template +struct CachedIndel + : public detail::CachedDistanceBase, size_t, 0, std::numeric_limits::max()> { + template + explicit CachedIndel(const Sentence1& s1_) : CachedIndel(detail::to_begin(s1_), detail::to_end(s1_)) + {} + + template + CachedIndel(InputIt1 first1, InputIt1 last1) + : s1_len(static_cast(std::distance(first1, last1))), scorer(first1, last1) + {} + +private: + friend detail::CachedDistanceBase, size_t, 0, std::numeric_limits::max()>; + friend detail::CachedNormalizedMetricBase>; + + template + size_t maximum(const detail::Range& s2) const + { + return s1_len + s2.size(); + } + + template + size_t _distance(const detail::Range& s2, size_t score_cutoff, size_t score_hint) const + { + size_t maximum_ = maximum(s2); + size_t lcs_cutoff = (maximum_ / 2 >= score_cutoff) ? maximum_ / 2 - score_cutoff : 0; + size_t lcs_cutoff_hint = (maximum_ / 2 >= score_hint) ? maximum_ / 2 - score_hint : 0; + size_t lcs_sim = scorer.similarity(s2, lcs_cutoff, lcs_cutoff_hint); + size_t dist = maximum_ - 2 * lcs_sim; + return (dist <= score_cutoff) ? dist : score_cutoff + 1; + } + + size_t s1_len; + CachedLCSseq scorer; +}; + +template +explicit CachedIndel(const Sentence1& s1_) -> CachedIndel>; + +template +CachedIndel(InputIt1 first1, InputIt1 last1) -> CachedIndel>; + +} // namespace rapidfuzz + +#include +#include +#include + +namespace rapidfuzz::detail { + +struct FlaggedCharsWord { + uint64_t P_flag; + uint64_t T_flag; +}; + +struct FlaggedCharsMultiword { + std::vector P_flag; + std::vector T_flag; +}; + +struct SearchBoundMask { + size_t words = 0; + size_t empty_words = 0; + uint64_t last_mask = 0; + uint64_t first_mask = 0; +}; + +static inline double jaro_calculate_similarity(size_t P_len, size_t T_len, size_t CommonChars, + size_t Transpositions) +{ + Transpositions /= 2; + double Sim = 0; + Sim += static_cast(CommonChars) / static_cast(P_len); + Sim += static_cast(CommonChars) / static_cast(T_len); + Sim += (static_cast(CommonChars) - static_cast(Transpositions)) / + static_cast(CommonChars); + return Sim / 3.0; +} + +/** + * @brief filter matches below score_cutoff based on string lengths + */ +static inline bool jaro_length_filter(size_t P_len, size_t T_len, double score_cutoff) +{ + if (!T_len || !P_len) return false; + + double min_len = static_cast(std::min(P_len, T_len)); + double Sim = min_len / static_cast(P_len) + min_len / static_cast(T_len) + 1.0; + Sim /= 3.0; + return Sim >= score_cutoff; +} + +/** + * @brief filter matches below score_cutoff based on string lengths and common characters + */ +static inline bool jaro_common_char_filter(size_t P_len, size_t T_len, size_t CommonChars, + double score_cutoff) +{ + if (!CommonChars) return false; + + double Sim = 0; + Sim += static_cast(CommonChars) / static_cast(P_len); + Sim += static_cast(CommonChars) / static_cast(T_len); + Sim += 1.0; + Sim /= 3.0; + return Sim >= score_cutoff; +} + +static inline size_t count_common_chars(const FlaggedCharsWord& flagged) +{ + return popcount(flagged.P_flag); +} + +static inline size_t count_common_chars(const FlaggedCharsMultiword& flagged) +{ + size_t CommonChars = 0; + if (flagged.P_flag.size() < flagged.T_flag.size()) { + for (uint64_t flag : flagged.P_flag) { + CommonChars += popcount(flag); + } + } + else { + for (uint64_t flag : flagged.T_flag) { + CommonChars += popcount(flag); + } + } + return CommonChars; +} + +template +static inline FlaggedCharsWord flag_similar_characters_word(const PM_Vec& PM, + [[maybe_unused]] const Range& P, + const Range& T, size_t Bound) +{ + assert(P.size() <= 64); + assert(T.size() <= 64); + assert(Bound > P.size() || P.size() - Bound <= T.size()); + + FlaggedCharsWord flagged = {0, 0}; + + uint64_t BoundMask = bit_mask_lsb(Bound + 1); + + size_t j = 0; + auto T_iter = T.begin(); + for (; j < std::min(Bound, T.size()); ++j, ++T_iter) { + uint64_t PM_j = PM.get(0, *T_iter) & BoundMask & (~flagged.P_flag); + + flagged.P_flag |= blsi(PM_j); + flagged.T_flag |= static_cast(PM_j != 0) << j; + + BoundMask = (BoundMask << 1) | 1; + } + + for (; j < T.size(); ++j, ++T_iter) { + uint64_t PM_j = PM.get(0, *T_iter) & BoundMask & (~flagged.P_flag); + + flagged.P_flag |= blsi(PM_j); + flagged.T_flag |= static_cast(PM_j != 0) << j; + + BoundMask <<= 1; + } + + return flagged; +} + +template +static inline void flag_similar_characters_step(const BlockPatternMatchVector& PM, CharT T_j, + FlaggedCharsMultiword& flagged, size_t j, + SearchBoundMask BoundMask) +{ + size_t j_word = j / 64; + size_t j_pos = j % 64; + size_t word = BoundMask.empty_words; + size_t last_word = word + BoundMask.words; + + if (BoundMask.words == 1) { + uint64_t PM_j = + PM.get(word, T_j) & BoundMask.last_mask & BoundMask.first_mask & (~flagged.P_flag[word]); + + flagged.P_flag[word] |= blsi(PM_j); + flagged.T_flag[j_word] |= static_cast(PM_j != 0) << j_pos; + return; + } + + if (BoundMask.first_mask) { + uint64_t PM_j = PM.get(word, T_j) & BoundMask.first_mask & (~flagged.P_flag[word]); + + if (PM_j) { + flagged.P_flag[word] |= blsi(PM_j); + flagged.T_flag[j_word] |= 1ull << j_pos; + return; + } + word++; + } + + /* unroll for better performance on long sequences when access is fast */ + if (T_j >= 0 && T_j < 256) { + for (; word + 3 < last_word - 1; word += 4) { + uint64_t PM_j[4]; + unroll([&](auto i) { + PM_j[i] = PM.get(word + i, static_cast(T_j)) & (~flagged.P_flag[word + i]); + }); + + if (PM_j[0]) { + flagged.P_flag[word] |= blsi(PM_j[0]); + flagged.T_flag[j_word] |= 1ull << j_pos; + return; + } + if (PM_j[1]) { + flagged.P_flag[word + 1] |= blsi(PM_j[1]); + flagged.T_flag[j_word] |= 1ull << j_pos; + return; + } + if (PM_j[2]) { + flagged.P_flag[word + 2] |= blsi(PM_j[2]); + flagged.T_flag[j_word] |= 1ull << j_pos; + return; + } + if (PM_j[3]) { + flagged.P_flag[word + 3] |= blsi(PM_j[3]); + flagged.T_flag[j_word] |= 1ull << j_pos; + return; + } + } + } + + for (; word < last_word - 1; ++word) { + uint64_t PM_j = PM.get(word, T_j) & (~flagged.P_flag[word]); + + if (PM_j) { + flagged.P_flag[word] |= blsi(PM_j); + flagged.T_flag[j_word] |= 1ull << j_pos; + return; + } + } + + if (BoundMask.last_mask) { + uint64_t PM_j = PM.get(word, T_j) & BoundMask.last_mask & (~flagged.P_flag[word]); + + flagged.P_flag[word] |= blsi(PM_j); + flagged.T_flag[j_word] |= static_cast(PM_j != 0) << j_pos; + } +} + +template +static inline FlaggedCharsMultiword flag_similar_characters_block(const BlockPatternMatchVector& PM, + const Range& P, + const Range& T, size_t Bound) +{ + assert(P.size() > 64 || T.size() > 64); + assert(Bound > P.size() || P.size() - Bound <= T.size()); + assert(Bound >= 31); + + FlaggedCharsMultiword flagged; + flagged.T_flag.resize(ceil_div(T.size(), 64)); + flagged.P_flag.resize(ceil_div(P.size(), 64)); + + SearchBoundMask BoundMask; + size_t start_range = std::min(Bound + 1, P.size()); + BoundMask.words = 1 + start_range / 64; + BoundMask.empty_words = 0; + BoundMask.last_mask = (1ull << (start_range % 64)) - 1; + BoundMask.first_mask = ~UINT64_C(0); + + auto T_iter = T.begin(); + for (size_t j = 0; j < T.size(); ++j, ++T_iter) { + flag_similar_characters_step(PM, *T_iter, flagged, j, BoundMask); + + if (j + Bound + 1 < P.size()) { + BoundMask.last_mask = (BoundMask.last_mask << 1) | 1; + if (j + Bound + 2 < P.size() && BoundMask.last_mask == ~UINT64_C(0)) { + BoundMask.last_mask = 0; + BoundMask.words++; + } + } + + if (j >= Bound) { + BoundMask.first_mask <<= 1; + if (BoundMask.first_mask == 0) { + BoundMask.first_mask = ~UINT64_C(0); + BoundMask.words--; + BoundMask.empty_words++; + } + } + } + + return flagged; +} + +template +static inline size_t count_transpositions_word(const PM_Vec& PM, const Range& T, + const FlaggedCharsWord& flagged) +{ + uint64_t P_flag = flagged.P_flag; + uint64_t T_flag = flagged.T_flag; + + size_t Transpositions = 0; + while (T_flag) { + uint64_t PatternFlagMask = blsi(P_flag); + + Transpositions += !(PM.get(0, T[countr_zero(T_flag)]) & PatternFlagMask); + + T_flag = blsr(T_flag); + P_flag ^= PatternFlagMask; + } + + return Transpositions; +} + +template +static inline size_t count_transpositions_block(const BlockPatternMatchVector& PM, const Range& T, + const FlaggedCharsMultiword& flagged, size_t FlaggedChars) +{ + size_t TextWord = 0; + size_t PatternWord = 0; + uint64_t T_flag = flagged.T_flag[TextWord]; + uint64_t P_flag = flagged.P_flag[PatternWord]; + + auto T_first = T.begin(); + size_t Transpositions = 0; + while (FlaggedChars) { + while (!T_flag) { + TextWord++; + T_first += 64; + T_flag = flagged.T_flag[TextWord]; + } + + while (T_flag) { + while (!P_flag) { + PatternWord++; + P_flag = flagged.P_flag[PatternWord]; + } + + uint64_t PatternFlagMask = blsi(P_flag); + + Transpositions += !(PM.get(PatternWord, T_first[static_cast(countr_zero(T_flag))]) & + PatternFlagMask); + + T_flag = blsr(T_flag); + P_flag ^= PatternFlagMask; + + FlaggedChars--; + } + } + + return Transpositions; +} + +// todo cleanup the split between jaro_bounds +/** + * @brief find bounds + */ +static inline size_t jaro_bounds(size_t P_len, size_t T_len) +{ + /* since jaro uses a sliding window some parts of T/P might never be in + * range an can be removed ahead of time + */ + size_t Bound = (T_len > P_len) ? T_len : P_len; + Bound /= 2; + if (Bound > 0) Bound--; + + return Bound; +} + +/** + * @brief find bounds and skip out of bound parts of the sequences + */ +template +static inline size_t jaro_bounds(Range& P, Range& T) +{ + size_t P_len = P.size(); + size_t T_len = T.size(); + + // this is currently an early exit condition + // if this is changed handle this below, so Bound is never below 0 + assert(P_len != 0 || T_len != 0); + + /* since jaro uses a sliding window some parts of T/P might never be in + * range an can be removed ahead of time + */ + size_t Bound = 0; + if (T_len > P_len) { + Bound = T_len / 2 - 1; + if (T_len > P_len + Bound) T.remove_suffix(T_len - (P_len + Bound)); + } + else { + Bound = P_len / 2 - 1; + if (P_len > T_len + Bound) P.remove_suffix(P_len - (T_len + Bound)); + } + return Bound; +} + +template +static inline double jaro_similarity(Range P, Range T, double score_cutoff) +{ + size_t P_len = P.size(); + size_t T_len = T.size(); + + if (score_cutoff > 1.0) return 0.0; + + if (!P_len && !T_len) return 1.0; + + /* filter out based on the length difference between the two strings */ + if (!jaro_length_filter(P_len, T_len, score_cutoff)) return 0.0; + + if (P_len == 1 && T_len == 1) return static_cast(P.front() == T.front()); + + size_t Bound = jaro_bounds(P, T); + + /* common prefix never includes Transpositions */ + size_t CommonChars = remove_common_prefix(P, T); + size_t Transpositions = 0; + + if (P.empty() || T.empty()) { + /* already has correct number of common chars and transpositions */ + } + else if (P.size() <= 64 && T.size() <= 64) { + PatternMatchVector PM(P); + auto flagged = flag_similar_characters_word(PM, P, T, Bound); + CommonChars += count_common_chars(flagged); + + if (!jaro_common_char_filter(P_len, T_len, CommonChars, score_cutoff)) return 0.0; + + Transpositions = count_transpositions_word(PM, T, flagged); + } + else { + BlockPatternMatchVector PM(P); + auto flagged = flag_similar_characters_block(PM, P, T, Bound); + size_t FlaggedChars = count_common_chars(flagged); + CommonChars += FlaggedChars; + + if (!jaro_common_char_filter(P_len, T_len, CommonChars, score_cutoff)) return 0.0; + + Transpositions = count_transpositions_block(PM, T, flagged, FlaggedChars); + } + + double Sim = jaro_calculate_similarity(P_len, T_len, CommonChars, Transpositions); + return (Sim >= score_cutoff) ? Sim : 0; +} + +template +static inline double jaro_similarity(const BlockPatternMatchVector& PM, Range P, Range T, + double score_cutoff) +{ + size_t P_len = P.size(); + size_t T_len = T.size(); + + if (score_cutoff > 1.0) return 0.0; + + if (!P_len && !T_len) return 1.0; + + /* filter out based on the length difference between the two strings */ + if (!jaro_length_filter(P_len, T_len, score_cutoff)) return 0.0; + + if (P_len == 1 && T_len == 1) return static_cast(P[0] == T[0]); + + size_t Bound = jaro_bounds(P, T); + + /* common prefix never includes Transpositions */ + size_t CommonChars = 0; + size_t Transpositions = 0; + + if (P.empty() || T.empty()) { + /* already has correct number of common chars and transpositions */ + } + else if (P.size() <= 64 && T.size() <= 64) { + auto flagged = flag_similar_characters_word(PM, P, T, Bound); + CommonChars += count_common_chars(flagged); + + if (!jaro_common_char_filter(P_len, T_len, CommonChars, score_cutoff)) return 0.0; + + Transpositions = count_transpositions_word(PM, T, flagged); + } + else { + auto flagged = flag_similar_characters_block(PM, P, T, Bound); + size_t FlaggedChars = count_common_chars(flagged); + CommonChars += FlaggedChars; + + if (!jaro_common_char_filter(P_len, T_len, CommonChars, score_cutoff)) return 0.0; + + Transpositions = count_transpositions_block(PM, T, flagged, FlaggedChars); + } + + double Sim = jaro_calculate_similarity(P_len, T_len, CommonChars, Transpositions); + return (Sim >= score_cutoff) ? Sim : 0; +} + +#ifdef RAPIDFUZZ_SIMD + +template +struct JaroSimilaritySimdBounds { + size_t maxBound = 0; + VecType boundMaskSize; + VecType boundMask; +}; + +template +static inline auto jaro_similarity_prepare_bound_short_s2(const VecType* s1_lengths, Range& s2) +{ +# ifdef RAPIDFUZZ_AVX2 + using namespace simd_avx2; +# else + using namespace simd_sse2; +# endif + + [[maybe_unused]] static constexpr size_t alignment = native_simd::alignment; + static constexpr size_t vec_width = native_simd::size; + assert(s2.size() <= sizeof(VecType) * 8); + + JaroSimilaritySimdBounds> bounds; + + VecType maxLen = 0; + // todo permutate + max to find maxLen + // side-note: we know only the first 8 bit are actually used + for (size_t i = 0; i < vec_width; ++i) + if (s1_lengths[i] > maxLen) maxLen = s1_lengths[i]; + +# ifdef RAPIDFUZZ_AVX2 + native_simd zero(VecType(0)); + native_simd one(1); + + native_simd s1_lengths_simd(reinterpret_cast(s1_lengths)); + native_simd s2_length_simd(static_cast(s2.size())); + + // we always know that the number does not exceed 64, so we can operate on smaller vectors if this + // proves to be faster + native_simd boundSizes = max8(s1_lengths_simd, s2_length_simd) >> 1; // divide by two + // todo there could be faster options since comparisions can be relatively expensive for some vector sizes + boundSizes -= (boundSizes > zero) & one; + + // this can never overflow even when using larger vectors for shifting here, since in the worst case of + // 8bit vectors this shifts by (8/2-1)*2=6 bits todo << 1 performs unneeded masking here sllv is pretty + // expensive for 8 / 16 bit since it has to be emulated maybe there is a better solution + bounds.boundMaskSize = sllv(one, boundSizes << 1) - one; + bounds.boundMask = sllv(one, boundSizes + one) - one; + + bounds.maxBound = (s2.size() > maxLen) ? s2.size() : maxLen; + bounds.maxBound /= 2; + if (bounds.maxBound > 0) bounds.maxBound--; +# else + alignas(alignment) std::array boundMaskSize_; + alignas(alignment) std::array boundMask_; + + // todo try to find a simd implementation for sse2 + for (size_t i = 0; i < vec_width; ++i) { + size_t Bound = jaro_bounds(static_cast(s1_lengths[i]), s2.size()); + + if (Bound > bounds.maxBound) bounds.maxBound = Bound; + + boundMaskSize_[i] = bit_mask_lsb(2 * Bound); + boundMask_[i] = bit_mask_lsb(Bound + 1); + } + + bounds.boundMaskSize = native_simd(reinterpret_cast(boundMaskSize_.data())); + bounds.boundMask = native_simd(reinterpret_cast(boundMask_.data())); +# endif + + size_t lastRelevantChar = static_cast(maxLen) + bounds.maxBound; + if (s2.size() > lastRelevantChar) s2.remove_suffix(s2.size() - lastRelevantChar); + + return bounds; +} + +template +static inline auto jaro_similarity_prepare_bound_long_s2(const VecType* s1_lengths, Range& s2) +{ +# ifdef RAPIDFUZZ_AVX2 + using namespace simd_avx2; +# else + using namespace simd_sse2; +# endif + + static constexpr size_t vec_width = native_simd::size; + assert(s2.size() > sizeof(VecType) * 8); + + JaroSimilaritySimdBounds> bounds; + + VecType maxLen = 0; + // todo permutate + max to find maxLen + // side-note: we know only the first 8 bit are actually used + for (size_t i = 0; i < vec_width; ++i) + if (s1_lengths[i] > maxLen) maxLen = s1_lengths[i]; + + bounds.maxBound = s2.size() / 2 - 1; + bounds.boundMaskSize = native_simd(bit_mask_lsb(2 * bounds.maxBound)); + bounds.boundMask = native_simd(bit_mask_lsb(bounds.maxBound + 1)); + + size_t lastRelevantChar = static_cast(maxLen) + bounds.maxBound; + if (s2.size() > lastRelevantChar) s2.remove_suffix(s2.size() - lastRelevantChar); + + return bounds; +} + +template +static inline void +jaro_similarity_simd_long_s2(Range scores, const detail::BlockPatternMatchVector& block, + VecType* s1_lengths, Range s2, double score_cutoff) noexcept +{ +# ifdef RAPIDFUZZ_AVX2 + using namespace simd_avx2; +# else + using namespace simd_sse2; +# endif + + static constexpr size_t alignment = native_simd::alignment; + static constexpr size_t vec_width = native_simd::size; + static constexpr size_t vecs = native_simd::size; + assert(block.size() % vecs == 0); + assert(s2.size() > sizeof(VecType) * 8); + + struct AlignedAlloc { + AlignedAlloc(size_t size) : memory(rf_aligned_alloc(native_simd::alignment, size)) + {} + + ~AlignedAlloc() + { + rf_aligned_free(memory); + } + + void* memory = nullptr; + }; + + native_simd zero(VecType(0)); + native_simd one(1); + size_t result_index = 0; + + size_t s2_block_count = detail::ceil_div(s2.size(), sizeof(VecType) * 8); + AlignedAlloc memory(2 * s2_block_count * sizeof(native_simd)); + + native_simd* T_flag = static_cast*>(memory.memory); + // reuse the same memory since counter is only required in the first half of the algorithm while + // T_flags is required in the second half + native_simd* counter = static_cast*>(memory.memory) + s2_block_count; + VecType* T_flags = static_cast(memory.memory) + s2_block_count * vec_width; + + for (size_t cur_vec = 0; cur_vec < block.size(); cur_vec += vecs) { + auto s2_cur = s2; + auto bounds = jaro_similarity_prepare_bound_long_s2(s1_lengths + result_index, s2_cur); + + native_simd P_flag(VecType(0)); + + std::fill(T_flag, T_flag + detail::ceil_div(s2_cur.size(), sizeof(VecType) * 8), + native_simd(VecType(0))); + std::fill(counter, counter + detail::ceil_div(s2_cur.size(), sizeof(VecType) * 8), + native_simd(VecType(1))); + + // In case s2 is longer than all of the elements in s1_lengths boundMaskSize + // might have all bits set and therefor the condition ((boundMask <= boundMaskSize) & one) + // would incorrectly always set the first bit to 1. + // this is solved by splitting the loop into two parts where after this boundary is reached + // the first bit inside boundMask is no longer set + size_t j = 0; + for (; j < std::min(bounds.maxBound, s2_cur.size()); ++j) { + alignas(alignment) std::array stored; + unroll([&](auto i) { stored[i] = block.get(cur_vec + i, s2_cur[j]); }); + native_simd X(stored.data()); + native_simd PM_j = andnot(X & bounds.boundMask, P_flag); + + P_flag |= blsi(PM_j); + size_t T_word_index = j / (sizeof(VecType) * 8); + T_flag[T_word_index] |= andnot(counter[T_word_index], (PM_j == zero)); + + counter[T_word_index] = counter[T_word_index] << 1; + bounds.boundMask = (bounds.boundMask << 1) | ((bounds.boundMask <= bounds.boundMaskSize) & one); + } + + for (; j < s2_cur.size(); ++j) { + alignas(alignment) std::array stored; + unroll([&](auto i) { stored[i] = block.get(cur_vec + i, s2_cur[j]); }); + native_simd X(stored.data()); + native_simd PM_j = andnot(X & bounds.boundMask, P_flag); + + P_flag |= blsi(PM_j); + size_t T_word_index = j / (sizeof(VecType) * 8); + T_flag[T_word_index] |= andnot(counter[T_word_index], (PM_j == zero)); + + counter[T_word_index] = counter[T_word_index] << 1; + bounds.boundMask = bounds.boundMask << 1; + } + + auto counts = popcount(P_flag); + alignas(alignment) std::array P_flags; + P_flag.store(P_flags.data()); + + for (size_t i = 0; i < detail::ceil_div(s2_cur.size(), sizeof(VecType) * 8); ++i) + T_flag[i].store(T_flags + i * vec_width); + + for (size_t i = 0; i < vec_width; ++i) { + size_t CommonChars = static_cast(counts[i]); + if (!jaro_common_char_filter(static_cast(s1_lengths[result_index]), s2.size(), + CommonChars, score_cutoff)) + { + scores[result_index] = 0.0; + result_index++; + continue; + } + + VecType P_flag_cur = P_flags[i]; + size_t Transpositions = 0; + + static constexpr size_t vecs_per_word = vec_width / vecs; + size_t cur_block = i / vecs_per_word; + size_t offset = sizeof(VecType) * 8 * (i % vecs_per_word); + + { + size_t T_word_index = 0; + VecType T_flag_cur = T_flags[T_word_index * vec_width + i]; + while (P_flag_cur) { + while (!T_flag_cur) { + ++T_word_index; + T_flag_cur = T_flags[T_word_index * vec_width + i]; + } + + VecType PatternFlagMask = blsi(P_flag_cur); + + uint64_t PM_j = + block.get(cur_vec + cur_block, + s2[countr_zero(T_flag_cur) + T_word_index * sizeof(VecType) * 8]); + Transpositions += !(PM_j & (static_cast(PatternFlagMask) << offset)); + + T_flag_cur = blsr(T_flag_cur); + P_flag_cur ^= PatternFlagMask; + } + } + + double Sim = jaro_calculate_similarity(static_cast(s1_lengths[result_index]), s2.size(), + CommonChars, Transpositions); + + scores[result_index] = (Sim >= score_cutoff) ? Sim : 0; + result_index++; + } + } +} + +template +static inline void +jaro_similarity_simd_short_s2(Range scores, const detail::BlockPatternMatchVector& block, + VecType* s1_lengths, Range s2, double score_cutoff) noexcept +{ +# ifdef RAPIDFUZZ_AVX2 + using namespace simd_avx2; +# else + using namespace simd_sse2; +# endif + + static constexpr size_t alignment = native_simd::alignment; + static constexpr size_t vec_width = native_simd::size; + static constexpr size_t vecs = native_simd::size; + assert(block.size() % vecs == 0); + assert(s2.size() <= sizeof(VecType) * 8); + + native_simd zero(VecType(0)); + native_simd one(1); + size_t result_index = 0; + + for (size_t cur_vec = 0; cur_vec < block.size(); cur_vec += vecs) { + auto s2_cur = s2; + auto bounds = jaro_similarity_prepare_bound_short_s2(s1_lengths + result_index, s2_cur); + + native_simd P_flag(VecType(0)); + native_simd T_flag(VecType(0)); + native_simd counter(VecType(1)); + + // In case s2 is longer than all of the elements in s1_lengths boundMaskSize + // might have all bits set and therefor the condition ((boundMask <= boundMaskSize) & one) + // would incorrectly always set the first bit to 1. + // this is solved by splitting the loop into two parts where after this boundary is reached + // the first bit inside boundMask is no longer set + size_t j = 0; + for (; j < std::min(bounds.maxBound, s2_cur.size()); ++j) { + alignas(alignment) std::array stored; + unroll([&](auto i) { stored[i] = block.get(cur_vec + i, s2_cur[j]); }); + native_simd X(stored.data()); + native_simd PM_j = andnot(X & bounds.boundMask, P_flag); + + P_flag |= blsi(PM_j); + T_flag |= andnot(counter, (PM_j == zero)); + + counter = counter << 1; + bounds.boundMask = (bounds.boundMask << 1) | ((bounds.boundMask <= bounds.boundMaskSize) & one); + } + + for (; j < s2_cur.size(); ++j) { + alignas(alignment) std::array stored; + unroll([&](auto i) { stored[i] = block.get(cur_vec + i, s2_cur[j]); }); + native_simd X(stored.data()); + native_simd PM_j = andnot(X & bounds.boundMask, P_flag); + + P_flag |= blsi(PM_j); + T_flag |= andnot(counter, (PM_j == zero)); + + counter = counter << 1; + bounds.boundMask = bounds.boundMask << 1; + } + + auto counts = popcount(P_flag); + alignas(alignment) std::array P_flags; + P_flag.store(P_flags.data()); + alignas(alignment) std::array T_flags; + T_flag.store(T_flags.data()); + for (size_t i = 0; i < vec_width; ++i) { + size_t CommonChars = static_cast(counts[i]); + if (!jaro_common_char_filter(static_cast(s1_lengths[result_index]), s2.size(), + CommonChars, score_cutoff)) + { + scores[result_index] = 0.0; + result_index++; + continue; + } + + VecType P_flag_cur = P_flags[i]; + VecType T_flag_cur = T_flags[i]; + size_t Transpositions = 0; + + static constexpr size_t vecs_per_word = vec_width / vecs; + size_t cur_block = i / vecs_per_word; + size_t offset = sizeof(VecType) * 8 * (i % vecs_per_word); + while (P_flag_cur) { + VecType PatternFlagMask = blsi(P_flag_cur); + + uint64_t PM_j = block.get(cur_vec + cur_block, s2[countr_zero(T_flag_cur)]); + Transpositions += !(PM_j & (static_cast(PatternFlagMask) << offset)); + + T_flag_cur = blsr(T_flag_cur); + P_flag_cur ^= PatternFlagMask; + } + + double Sim = jaro_calculate_similarity(static_cast(s1_lengths[result_index]), s2.size(), + CommonChars, Transpositions); + + scores[result_index] = (Sim >= score_cutoff) ? Sim : 0; + result_index++; + } + } +} + +template +static inline void jaro_similarity_simd(Range scores, const detail::BlockPatternMatchVector& block, + VecType* s1_lengths, size_t s1_lengths_size, const Range& s2, + double score_cutoff) noexcept +{ + if (score_cutoff > 1.0) { + for (size_t i = 0; i < s1_lengths_size; i++) + scores[i] = 0.0; + + return; + } + + if (s2.empty()) { + for (size_t i = 0; i < s1_lengths_size; i++) + scores[i] = s1_lengths[i] ? 0.0 : 1.0; + + return; + } + + if (s2.size() > sizeof(VecType) * 8) + return jaro_similarity_simd_long_s2(scores, block, s1_lengths, s2, score_cutoff); + else + return jaro_similarity_simd_short_s2(scores, block, s1_lengths, s2, score_cutoff); +} + +#endif /* RAPIDFUZZ_SIMD */ + +class Jaro : public SimilarityBase { + friend SimilarityBase; + friend NormalizedMetricBase; + + template + static double maximum(const Range&, const Range&) noexcept + { + return 1.0; + } + + template + static double _similarity(const Range& s1, const Range& s2, double score_cutoff, + [[maybe_unused]] double score_hint) + { + return jaro_similarity(s1, s2, score_cutoff); + } +}; + +} // namespace rapidfuzz::detail + +#include + +namespace rapidfuzz { + +template +double jaro_distance(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + double score_cutoff = 1.0) +{ + return detail::Jaro::distance(first1, last1, first2, last2, score_cutoff, score_cutoff); +} + +template +double jaro_distance(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 1.0) +{ + return detail::Jaro::distance(s1, s2, score_cutoff, score_cutoff); +} + +template +double jaro_similarity(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + double score_cutoff = 0.0) +{ + return detail::Jaro::similarity(first1, last1, first2, last2, score_cutoff, score_cutoff); +} + +template +double jaro_similarity(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 0.0) +{ + return detail::Jaro::similarity(s1, s2, score_cutoff, score_cutoff); +} + +template +double jaro_normalized_distance(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + double score_cutoff = 1.0) +{ + return detail::Jaro::normalized_distance(first1, last1, first2, last2, score_cutoff, score_cutoff); +} + +template +double jaro_normalized_distance(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 1.0) +{ + return detail::Jaro::normalized_distance(s1, s2, score_cutoff, score_cutoff); +} + +template +double jaro_normalized_similarity(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + double score_cutoff = 0.0) +{ + return detail::Jaro::normalized_similarity(first1, last1, first2, last2, score_cutoff, score_cutoff); +} + +template +double jaro_normalized_similarity(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 0.0) +{ + return detail::Jaro::normalized_similarity(s1, s2, score_cutoff, score_cutoff); +} + +#ifdef RAPIDFUZZ_SIMD +namespace experimental { +template +struct MultiJaro : public detail::MultiSimilarityBase, double, 0, 1> { + +private: + friend detail::MultiSimilarityBase, double, 0, 1>; + friend detail::MultiNormalizedMetricBase, double>; + + static_assert(MaxLen == 8 || MaxLen == 16 || MaxLen == 32 || MaxLen == 64); + + using VecType = typename std::conditional_t< + MaxLen == 8, uint8_t, + typename std::conditional_t>>; + + constexpr static size_t get_vec_size() + { +# ifdef RAPIDFUZZ_AVX2 + return detail::simd_avx2::native_simd::size; +# else + return detail::simd_sse2::native_simd::size; +# endif + } + + constexpr static size_t get_vec_alignment() + { +# ifdef RAPIDFUZZ_AVX2 + return detail::simd_avx2::native_simd::alignment; +# else + return detail::simd_sse2::native_simd::alignment; +# endif + } + + constexpr static size_t find_block_count(size_t count) + { + size_t vec_size = get_vec_size(); + size_t simd_vec_count = detail::ceil_div(count, vec_size); + return detail::ceil_div(simd_vec_count * vec_size * MaxLen, 64); + } + +public: + MultiJaro(size_t count) : input_count(count), PM(find_block_count(count) * 64) + { + /* align for avx2 so we can directly load into avx2 registers */ + str_lens_size = result_count(); + + str_lens = static_cast( + detail::rf_aligned_alloc(get_vec_alignment(), sizeof(VecType) * str_lens_size)); + std::fill(str_lens, str_lens + str_lens_size, VecType(0)); + } + + ~MultiJaro() + { + detail::rf_aligned_free(str_lens); + } + + /** + * @brief get minimum size required for result vectors passed into + * - distance + * - similarity + * - normalized_distance + * - normalized_similarity + * + * @return minimum vector size + */ + size_t result_count() const + { + size_t vec_size = get_vec_size(); + size_t simd_vec_count = detail::ceil_div(input_count, vec_size); + return simd_vec_count * vec_size; + } + + template + void insert(const Sentence1& s1_) + { + insert(detail::to_begin(s1_), detail::to_end(s1_)); + } + + template + void insert(InputIt1 first1, InputIt1 last1) + { + auto len = std::distance(first1, last1); + int block_pos = static_cast((pos * MaxLen) % 64); + auto block = (pos * MaxLen) / 64; + assert(len <= MaxLen); + + if (pos >= input_count) throw std::invalid_argument("out of bounds insert"); + + str_lens[pos] = static_cast(len); + for (; first1 != last1; ++first1) { + PM.insert(block, *first1, block_pos); + block_pos++; + } + pos++; + } + +private: + template + void _similarity(double* scores, size_t score_count, const detail::Range& s2, + double score_cutoff = 0.0) const + { + if (score_count < result_count()) + throw std::invalid_argument("scores has to have >= result_count() elements"); + + detail::Range scores_(scores, scores + score_count); + detail::jaro_similarity_simd(scores_, PM, str_lens, str_lens_size, s2, score_cutoff); + } + + template + double maximum([[maybe_unused]] size_t s1_idx, const detail::Range&) const + { + return 1.0; + } + + size_t get_input_count() const noexcept + { + return input_count; + } + + size_t input_count; + size_t pos = 0; + detail::BlockPatternMatchVector PM; + VecType* str_lens; + size_t str_lens_size; +}; + +} /* namespace experimental */ +#endif /* RAPIDFUZZ_SIMD */ + +template +struct CachedJaro : public detail::CachedSimilarityBase, double, 0, 1> { + template + explicit CachedJaro(const Sentence1& s1_) : CachedJaro(detail::to_begin(s1_), detail::to_end(s1_)) + {} + + template + CachedJaro(InputIt1 first1, InputIt1 last1) : s1(first1, last1), PM(detail::Range(first1, last1)) + {} + +private: + friend detail::CachedSimilarityBase, double, 0, 1>; + friend detail::CachedNormalizedMetricBase>; + + template + double maximum(const detail::Range&) const + { + return 1.0; + } + + template + double _similarity(const detail::Range& s2, double score_cutoff, + [[maybe_unused]] double score_hint) const + { + return detail::jaro_similarity(PM, detail::Range(s1), s2, score_cutoff); + } + + std::vector s1; + detail::BlockPatternMatchVector PM; +}; + +template +explicit CachedJaro(const Sentence1& s1_) -> CachedJaro>; + +template +CachedJaro(InputIt1 first1, InputIt1 last1) -> CachedJaro>; + +} // namespace rapidfuzz + +namespace rapidfuzz::detail { + +template +double jaro_winkler_similarity(const Range& P, const Range& T, double prefix_weight, + double score_cutoff) +{ + size_t P_len = P.size(); + size_t T_len = T.size(); + size_t min_len = std::min(P_len, T_len); + size_t prefix = 0; + size_t max_prefix = std::min(min_len, size_t(4)); + + for (; prefix < max_prefix; ++prefix) + if (T[prefix] != P[prefix]) break; + + double jaro_score_cutoff = score_cutoff; + if (jaro_score_cutoff > 0.7) { + double prefix_sim = static_cast(prefix) * prefix_weight; + + if (prefix_sim >= 1.0) + jaro_score_cutoff = 0.7; + else + jaro_score_cutoff = std::max(0.7, (prefix_sim - jaro_score_cutoff) / (prefix_sim - 1.0)); + } + + double Sim = jaro_similarity(P, T, jaro_score_cutoff); + if (Sim > 0.7) { + Sim += static_cast(prefix) * prefix_weight * (1.0 - Sim); + Sim = std::min(Sim, 1.0); + } + + return (Sim >= score_cutoff) ? Sim : 0; +} + +template +double jaro_winkler_similarity(const BlockPatternMatchVector& PM, const Range& P, + const Range& T, double prefix_weight, double score_cutoff) +{ + size_t P_len = P.size(); + size_t T_len = T.size(); + size_t min_len = std::min(P_len, T_len); + size_t prefix = 0; + size_t max_prefix = std::min(min_len, size_t(4)); + + for (; prefix < max_prefix; ++prefix) + if (T[prefix] != P[prefix]) break; + + double jaro_score_cutoff = score_cutoff; + if (jaro_score_cutoff > 0.7) { + double prefix_sim = static_cast(prefix) * prefix_weight; + + if (prefix_sim >= 1.0) + jaro_score_cutoff = 0.7; + else + jaro_score_cutoff = std::max(0.7, (prefix_sim - jaro_score_cutoff) / (prefix_sim - 1.0)); + } + + double Sim = jaro_similarity(PM, P, T, jaro_score_cutoff); + if (Sim > 0.7) { + Sim += static_cast(prefix) * prefix_weight * (1.0 - Sim); + Sim = std::min(Sim, 1.0); + } + + return (Sim >= score_cutoff) ? Sim : 0; +} + +class JaroWinkler : public SimilarityBase { + friend SimilarityBase; + friend NormalizedMetricBase; + + template + static double maximum(const Range&, const Range&, double) noexcept + { + return 1.0; + } + + template + static double _similarity(const Range& s1, const Range& s2, double prefix_weight, + double score_cutoff, [[maybe_unused]] double score_hint) + { + return jaro_winkler_similarity(s1, s2, prefix_weight, score_cutoff); + } +}; + +} // namespace rapidfuzz::detail + +namespace rapidfuzz { + +template >> +double jaro_winkler_distance(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + double prefix_weight = 0.1, double score_cutoff = 1.0) +{ + return detail::JaroWinkler::distance(first1, last1, first2, last2, prefix_weight, score_cutoff, + score_cutoff); +} + +template +double jaro_winkler_distance(const Sentence1& s1, const Sentence2& s2, double prefix_weight = 0.1, + double score_cutoff = 1.0) +{ + return detail::JaroWinkler::distance(s1, s2, prefix_weight, score_cutoff, score_cutoff); +} + +template >> +double jaro_winkler_similarity(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + double prefix_weight = 0.1, double score_cutoff = 0.0) +{ + return detail::JaroWinkler::similarity(first1, last1, first2, last2, prefix_weight, score_cutoff, + score_cutoff); +} + +template +double jaro_winkler_similarity(const Sentence1& s1, const Sentence2& s2, double prefix_weight = 0.1, + double score_cutoff = 0.0) +{ + return detail::JaroWinkler::similarity(s1, s2, prefix_weight, score_cutoff, score_cutoff); +} + +template >> +double jaro_winkler_normalized_distance(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + double prefix_weight = 0.1, double score_cutoff = 1.0) +{ + return detail::JaroWinkler::normalized_distance(first1, last1, first2, last2, prefix_weight, score_cutoff, + score_cutoff); +} + +template +double jaro_winkler_normalized_distance(const Sentence1& s1, const Sentence2& s2, double prefix_weight = 0.1, + double score_cutoff = 1.0) +{ + return detail::JaroWinkler::normalized_distance(s1, s2, prefix_weight, score_cutoff, score_cutoff); +} + +template >> +double jaro_winkler_normalized_similarity(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + double prefix_weight = 0.1, double score_cutoff = 0.0) +{ + return detail::JaroWinkler::normalized_similarity(first1, last1, first2, last2, prefix_weight, + score_cutoff, score_cutoff); +} + +template +double jaro_winkler_normalized_similarity(const Sentence1& s1, const Sentence2& s2, + double prefix_weight = 0.1, double score_cutoff = 0.0) +{ + return detail::JaroWinkler::normalized_similarity(s1, s2, prefix_weight, score_cutoff, score_cutoff); +} + +#ifdef RAPIDFUZZ_SIMD +namespace experimental { +template +struct MultiJaroWinkler : public detail::MultiSimilarityBase, double, 0, 1> { + +private: + friend detail::MultiSimilarityBase, double, 0, 1>; + friend detail::MultiNormalizedMetricBase, double>; + +public: + MultiJaroWinkler(size_t count, double prefix_weight_ = 0.1) : scorer(count), prefix_weight(prefix_weight_) + {} + + /** + * @brief get minimum size required for result vectors passed into + * - distance + * - similarity + * - normalized_distance + * - normalized_similarity + * + * @return minimum vector size + */ + size_t result_count() const + { + return scorer.result_count(); + } + + template + void insert(const Sentence1& s1_) + { + insert(detail::to_begin(s1_), detail::to_end(s1_)); + } + + template + void insert(InputIt1 first1, InputIt1 last1) + { + scorer.insert(first1, last1); + size_t len = static_cast(std::distance(first1, last1)); + std::array prefix; + for (size_t i = 0; i < std::min(len, size_t(4)); ++i) + prefix[i] = static_cast(first1[static_cast(i)]); + + str_lens.push_back(len); + prefixes.push_back(prefix); + } + +private: + template + void _similarity(double* scores, size_t score_count, const detail::Range& s2, + double score_cutoff = 0.0) const + { + if (score_count < result_count()) + throw std::invalid_argument("scores has to have >= result_count() elements"); + + scorer.similarity(scores, score_count, s2, std::min(0.7, score_cutoff)); + + for (size_t i = 0; i < get_input_count(); ++i) { + if (scores[i] > 0.7) { + size_t min_len = std::min(s2.size(), str_lens[i]); + size_t max_prefix = std::min(min_len, size_t(4)); + size_t prefix = 0; + for (; prefix < max_prefix; ++prefix) + if (static_cast(s2[prefix]) != prefixes[i][prefix]) break; + + scores[i] += static_cast(prefix) * prefix_weight * (1.0 - scores[i]); + scores[i] = std::min(scores[i], 1.0); + } + + if (scores[i] < score_cutoff) scores[i] = 0.0; + } + } + + template + double maximum([[maybe_unused]] size_t s1_idx, const detail::Range&) const + { + return 1.0; + } + + size_t get_input_count() const noexcept + { + return str_lens.size(); + } + + std::vector str_lens; + // todo this could lead to incorrect results when comparing uint64_t with int64_t + std::vector> prefixes; + MultiJaro scorer; + double prefix_weight; +}; + +} /* namespace experimental */ +#endif /* RAPIDFUZZ_SIMD */ + +template +struct CachedJaroWinkler : public detail::CachedSimilarityBase, double, 0, 1> { + template + explicit CachedJaroWinkler(const Sentence1& s1_, double _prefix_weight = 0.1) + : CachedJaroWinkler(detail::to_begin(s1_), detail::to_end(s1_), _prefix_weight) + {} + + template + CachedJaroWinkler(InputIt1 first1, InputIt1 last1, double _prefix_weight = 0.1) + : prefix_weight(_prefix_weight), s1(first1, last1), PM(detail::Range(first1, last1)) + {} + +private: + friend detail::CachedSimilarityBase, double, 0, 1>; + friend detail::CachedNormalizedMetricBase>; + + template + double maximum(const detail::Range&) const + { + return 1.0; + } + + template + double _similarity(const detail::Range& s2, double score_cutoff, + [[maybe_unused]] double score_hint) const + { + return detail::jaro_winkler_similarity(PM, detail::Range(s1), s2, prefix_weight, score_cutoff); + } + + double prefix_weight; + std::vector s1; + detail::BlockPatternMatchVector PM; +}; + +template +explicit CachedJaroWinkler(const Sentence1& s1_, + double _prefix_weight = 0.1) -> CachedJaroWinkler>; + +template +CachedJaroWinkler(InputIt1 first1, InputIt1 last1, + double _prefix_weight = 0.1) -> CachedJaroWinkler>; + +} // namespace rapidfuzz + +#include + +#include +#include +#include +#include + +namespace rapidfuzz::detail { + +struct LevenshteinRow { + uint64_t VP; + uint64_t VN; + + LevenshteinRow() : VP(~UINT64_C(0)), VN(0) + {} + + LevenshteinRow(uint64_t VP_, uint64_t VN_) : VP(VP_), VN(VN_) + {} +}; + +template +struct LevenshteinResult; + +template <> +struct LevenshteinResult { + ShiftedBitMatrix VP; + ShiftedBitMatrix VN; + + size_t dist; +}; + +template <> +struct LevenshteinResult { + size_t first_block; + size_t last_block; + size_t prev_score; + std::vector vecs; + + size_t dist; +}; + +template <> +struct LevenshteinResult { + size_t dist; +}; + +template +size_t generalized_levenshtein_wagner_fischer(const Range& s1, const Range& s2, + LevenshteinWeightTable weights, size_t max) +{ + size_t cache_size = s1.size() + 1; + std::vector cache(cache_size); + assume(cache_size != 0); + + for (size_t i = 0; i < cache_size; ++i) + cache[i] = i * weights.delete_cost; + + for (const auto& ch2 : s2) { + auto cache_iter = cache.begin(); + size_t temp = *cache_iter; + *cache_iter += weights.insert_cost; + + for (const auto& ch1 : s1) { + if (ch1 != ch2) + temp = std::min({*cache_iter + weights.delete_cost, *(cache_iter + 1) + weights.insert_cost, + temp + weights.replace_cost}); + ++cache_iter; + std::swap(*cache_iter, temp); + } + } + + size_t dist = cache.back(); + return (dist <= max) ? dist : max + 1; +} + +/** + * @brief calculates the maximum possible Levenshtein distance based on + * string lengths and weights + */ +static inline size_t levenshtein_maximum(size_t len1, size_t len2, LevenshteinWeightTable weights) +{ + size_t max_dist = len1 * weights.delete_cost + len2 * weights.insert_cost; + + if (len1 >= len2) + max_dist = std::min(max_dist, len2 * weights.replace_cost + (len1 - len2) * weights.delete_cost); + else + max_dist = std::min(max_dist, len1 * weights.replace_cost + (len2 - len1) * weights.insert_cost); + + return max_dist; +} + +/** + * @brief calculates the minimal possible Levenshtein distance based on + * string lengths and weights + */ +template +size_t levenshtein_min_distance(const Range& s1, const Range& s2, + LevenshteinWeightTable weights) +{ + if (s1.size() > s2.size()) + return (s1.size() - s2.size()) * weights.delete_cost; + else + return (s2.size() - s1.size()) * weights.insert_cost; +} + +template +size_t generalized_levenshtein_distance(Range s1, Range s2, + LevenshteinWeightTable weights, size_t max) +{ + size_t min_edits = levenshtein_min_distance(s1, s2, weights); + if (min_edits > max) return max + 1; + + /* common affix does not effect Levenshtein distance */ + remove_common_affix(s1, s2); + + return generalized_levenshtein_wagner_fischer(s1, s2, weights, max); +} + +/* + * An encoded mbleven model table. + * + * Each 8-bit integer represents an edit sequence, with using two + * bits for a single operation. + * + * Each Row of 8 integers represent all possible combinations + * of edit sequences for a gived maximum edit distance and length + * difference between the two strings, that is below the maximum + * edit distance + * + * 01 = DELETE, 10 = INSERT, 11 = SUBSTITUTE + * + * For example, 3F -> 0b111111 means three substitutions + */ +static constexpr std::array, 9> levenshtein_mbleven2018_matrix = {{ + /* max edit distance 1 */ + {0x03}, /* len_diff 0 */ + {0x01}, /* len_diff 1 */ + /* max edit distance 2 */ + {0x0F, 0x09, 0x06}, /* len_diff 0 */ + {0x0D, 0x07}, /* len_diff 1 */ + {0x05}, /* len_diff 2 */ + /* max edit distance 3 */ + {0x3F, 0x27, 0x2D, 0x39, 0x36, 0x1E, 0x1B}, /* len_diff 0 */ + {0x3D, 0x37, 0x1F, 0x25, 0x19, 0x16}, /* len_diff 1 */ + {0x35, 0x1D, 0x17}, /* len_diff 2 */ + {0x15}, /* len_diff 3 */ +}}; + +template +size_t levenshtein_mbleven2018(const Range& s1, const Range& s2, size_t max) +{ + size_t len1 = s1.size(); + size_t len2 = s2.size(); + assert(len1 > 0); + assert(len2 > 0); + assert(*s1.begin() != *s2.begin()); + assert(*std::prev(s1.end()) != *std::prev(s2.end())); + + if (len1 < len2) return levenshtein_mbleven2018(s2, s1, max); + + size_t len_diff = len1 - len2; + + if (max == 1) return max + static_cast(len_diff == 1 || len1 != 1); + + size_t ops_index = (max + max * max) / 2 + len_diff - 1; + auto& possible_ops = levenshtein_mbleven2018_matrix[ops_index]; + size_t dist = max + 1; + + for (uint8_t ops : possible_ops) { + auto iter_s1 = s1.begin(); + auto iter_s2 = s2.begin(); + size_t cur_dist = 0; + + if (!ops) break; + + while (iter_s1 != s1.end() && iter_s2 != s2.end()) { + if (*iter_s1 != *iter_s2) { + cur_dist++; + if (!ops) break; + if (ops & 1) iter_s1++; + if (ops & 2) iter_s2++; +#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && __GNUC__ < 10 +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wconversion" +#endif + ops >>= 2; +#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && __GNUC__ < 10 +# pragma GCC diagnostic pop +#endif + } + else { + iter_s1++; + iter_s2++; + } + } + cur_dist += static_cast(std::distance(iter_s1, s1.end()) + std::distance(iter_s2, s2.end())); + dist = std::min(dist, cur_dist); + } + + return (dist <= max) ? dist : max + 1; +} + +/** + * @brief Bitparallel implementation of the Levenshtein distance. + * + * This implementation requires the first string to have a length <= 64. + * The algorithm used is described @cite hyrro_2002 and has a time complexity + * of O(N). Comments and variable names in the implementation follow the + * paper. This implementation is used internally when the strings are short enough + * + * @tparam CharT1 This is the char type of the first sentence + * @tparam CharT2 This is the char type of the second sentence + * + * @param s1 + * string to compare with s2 (for type info check Template parameters above) + * @param s2 + * string to compare with s1 (for type info check Template parameters above) + * + * @return returns the levenshtein distance between s1 and s2 + */ +template +auto levenshtein_hyrroe2003(const PM_Vec& PM, const Range& s1, const Range& s2, + size_t max = std::numeric_limits::max()) + -> LevenshteinResult +{ + assert(s1.size() != 0); + + /* VP is set to 1^m. Shifting by bitwidth would be undefined behavior */ + uint64_t VP = ~UINT64_C(0); + uint64_t VN = 0; + + LevenshteinResult res; + res.dist = s1.size(); + if constexpr (RecordMatrix) { + res.VP = ShiftedBitMatrix(s2.size(), 1, ~UINT64_C(0)); + res.VN = ShiftedBitMatrix(s2.size(), 1, 0); + } + + /* mask used when computing D[m,j] in the paper 10^(m-1) */ + uint64_t mask = UINT64_C(1) << (s1.size() - 1); + + /* Searching */ + auto iter_s2 = s2.begin(); + for (size_t i = 0; iter_s2 != s2.end(); ++iter_s2, ++i) { + /* Step 1: Computing D0 */ + uint64_t PM_j = PM.get(0, *iter_s2); + uint64_t X = PM_j; + uint64_t D0 = (((X & VP) + VP) ^ VP) | X | VN; + + /* Step 2: Computing HP and HN */ + uint64_t HP = VN | ~(D0 | VP); + uint64_t HN = D0 & VP; + + /* Step 3: Computing the value D[m,j] */ + res.dist += bool(HP & mask); + res.dist -= bool(HN & mask); + + /* Step 4: Computing Vp and VN */ + HP = (HP << 1) | 1; + HN = (HN << 1); + + VP = HN | ~(D0 | HP); + VN = HP & D0; + + if constexpr (RecordMatrix) { + res.VP[i][0] = VP; + res.VN[i][0] = VN; + } + } + + if (res.dist > max) res.dist = max + 1; + + if constexpr (RecordBitRow) { + res.first_block = 0; + res.last_block = 0; + res.prev_score = s2.size(); + res.vecs.emplace_back(VP, VN); + } + + return res; +} + +#ifdef RAPIDFUZZ_SIMD +template +void levenshtein_hyrroe2003_simd(Range scores, const detail::BlockPatternMatchVector& block, + const std::vector& s1_lengths, const Range& s2, + size_t score_cutoff) noexcept +{ +# ifdef RAPIDFUZZ_AVX2 + using namespace simd_avx2; +# else + using namespace simd_sse2; +# endif + static constexpr size_t alignment = native_simd::alignment; + static constexpr size_t vec_width = native_simd::size; + static constexpr size_t vecs = native_simd::size; + assert(block.size() % vecs == 0); + + native_simd zero(VecType(0)); + native_simd one(1); + size_t result_index = 0; + + for (size_t cur_vec = 0; cur_vec < block.size(); cur_vec += vecs) { + /* VP is set to 1^m */ + native_simd VP(static_cast(-1)); + native_simd VN(VecType(0)); + + alignas(alignment) std::array currDist_; + unroll( + [&](auto i) { currDist_[i] = static_cast(s1_lengths[result_index + i]); }); + native_simd currDist(reinterpret_cast(currDist_.data())); + /* mask used when computing D[m,j] in the paper 10^(m-1) */ + alignas(alignment) std::array mask_; + unroll([&](auto i) { + if (s1_lengths[result_index + i] == 0) + mask_[i] = 0; + else + mask_[i] = static_cast(UINT64_C(1) << (s1_lengths[result_index + i] - 1)); + }); + native_simd mask(reinterpret_cast(mask_.data())); + + for (const auto& ch : s2) { + /* Step 1: Computing D0 */ + alignas(alignment) std::array stored; + unroll([&](auto i) { stored[i] = block.get(cur_vec + i, ch); }); + + native_simd X(stored.data()); + auto D0 = (((X & VP) + VP) ^ VP) | X | VN; + + /* Step 2: Computing HP and HN */ + auto HP = VN | ~(D0 | VP); + auto HN = D0 & VP; + + /* Step 3: Computing the value D[m,j] */ + currDist += andnot(one, (HP & mask) == zero); + currDist -= andnot(one, (HN & mask) == zero); + + /* Step 4: Computing Vp and VN */ + HP = (HP << 1) | one; + HN = (HN << 1); + + VP = HN | ~(D0 | HP); + VN = HP & D0; + } + + alignas(alignment) std::array distances; + currDist.store(distances.data()); + + unroll([&](auto i) { + size_t score = 0; + /* strings of length 0 are not handled correctly */ + if (s1_lengths[result_index] == 0) { + score = s2.size(); + } + /* calculate score under consideration of wraparounds in parallel counter */ + else { + if constexpr (std::numeric_limits::max() < std::numeric_limits::max()) { + size_t min_dist = abs_diff(s1_lengths[result_index], s2.size()); + size_t wraparound_score = static_cast(std::numeric_limits::max()) + 1; + + score = (min_dist / wraparound_score) * wraparound_score; + VecType remainder = static_cast(min_dist % wraparound_score); + + if (distances[i] < remainder) score += wraparound_score; + } + + score += distances[i]; + } + scores[result_index] = (score <= score_cutoff) ? score : score_cutoff + 1; + result_index++; + }); + } +} +#endif + +template +size_t levenshtein_hyrroe2003_small_band(const BlockPatternMatchVector& PM, const Range& s1, + const Range& s2, size_t max) +{ + /* VP is set to 1^m. */ + uint64_t VP = ~UINT64_C(0) << (64 - max - 1); + uint64_t VN = 0; + + const auto words = PM.size(); + size_t currDist = max; + uint64_t diagonal_mask = UINT64_C(1) << 63; + uint64_t horizontal_mask = UINT64_C(1) << 62; + ptrdiff_t start_pos = static_cast(max) + 1 - 64; + + /* score can decrease along the horizontal, but not along the diagonal */ + size_t break_score = 2 * max + s2.size() - s1.size(); + + /* Searching */ + size_t i = 0; + if (s1.size() > max) { + for (; i < s1.size() - max; ++i, ++start_pos) { + /* Step 1: Computing D0 */ + uint64_t PM_j = 0; + if (start_pos < 0) { + PM_j = PM.get(0, s2[i]) << (-start_pos); + } + else { + size_t word = static_cast(start_pos) / 64; + size_t word_pos = static_cast(start_pos) % 64; + + PM_j = PM.get(word, s2[i]) >> word_pos; + + if (word + 1 < words && word_pos != 0) PM_j |= PM.get(word + 1, s2[i]) << (64 - word_pos); + } + uint64_t X = PM_j; + uint64_t D0 = (((X & VP) + VP) ^ VP) | X | VN; + + /* Step 2: Computing HP and HN */ + uint64_t HP = VN | ~(D0 | VP); + uint64_t HN = D0 & VP; + + /* Step 3: Computing the value D[m,j] */ + currDist += !bool(D0 & diagonal_mask); + + if (currDist > break_score) return max + 1; + + /* Step 4: Computing Vp and VN */ + VP = HN | ~((D0 >> 1) | HP); + VN = (D0 >> 1) & HP; + } + } + + for (; i < s2.size(); ++i, ++start_pos) { + /* Step 1: Computing D0 */ + uint64_t PM_j = 0; + if (start_pos < 0) { + PM_j = PM.get(0, s2[i]) << (-start_pos); + } + else { + size_t word = static_cast(start_pos) / 64; + size_t word_pos = static_cast(start_pos) % 64; + + PM_j = PM.get(word, s2[i]) >> word_pos; + + if (word + 1 < words && word_pos != 0) PM_j |= PM.get(word + 1, s2[i]) << (64 - word_pos); + } + uint64_t X = PM_j; + uint64_t D0 = (((X & VP) + VP) ^ VP) | X | VN; + + /* Step 2: Computing HP and HN */ + uint64_t HP = VN | ~(D0 | VP); + uint64_t HN = D0 & VP; + + /* Step 3: Computing the value D[m,j] */ + currDist += bool(HP & horizontal_mask); + currDist -= bool(HN & horizontal_mask); + horizontal_mask >>= 1; + + if (currDist > break_score) return max + 1; + + /* Step 4: Computing Vp and VN */ + VP = HN | ~((D0 >> 1) | HP); + VN = (D0 >> 1) & HP; + } + + return (currDist <= max) ? currDist : max + 1; +} + +template +auto levenshtein_hyrroe2003_small_band(const Range& s1, const Range& s2, + size_t max) -> LevenshteinResult +{ + assert(max <= s1.size()); + assert(max <= s2.size()); + assert(s2.size() >= s1.size() - max); + + /* VP is set to 1^m. Shifting by bitwidth would be undefined behavior */ + uint64_t VP = ~UINT64_C(0) << (64 - max - 1); + uint64_t VN = 0; + + LevenshteinResult res; + res.dist = max; + if constexpr (RecordMatrix) { + res.VP = ShiftedBitMatrix(s2.size(), 1, ~UINT64_C(0)); + res.VN = ShiftedBitMatrix(s2.size(), 1, 0); + + ptrdiff_t start_offset = static_cast(max) + 2 - 64; + for (size_t i = 0; i < s2.size(); ++i) { + res.VP.set_offset(i, start_offset + static_cast(i)); + res.VN.set_offset(i, start_offset + static_cast(i)); + } + } + + uint64_t diagonal_mask = UINT64_C(1) << 63; + uint64_t horizontal_mask = UINT64_C(1) << 62; + + /* score can decrease along the horizontal, but not along the diagonal */ + size_t break_score = 2 * max + s2.size() - (s1.size()); + HybridGrowingHashmap::value_type, std::pair> PM; + + auto iter_s1 = s1.begin(); + for (ptrdiff_t j = -static_cast(max); j < 0; ++iter_s1, ++j) { + auto& x = PM[*iter_s1]; + x.second = shr64(x.second, j - x.first) | (UINT64_C(1) << 63); + x.first = j; + } + + /* Searching */ + size_t i = 0; + auto iter_s2 = s2.begin(); + for (; i < s1.size() - max; ++iter_s2, ++iter_s1, ++i) { + /* Step 1: Computing D0 */ + /* update bitmasks online */ + uint64_t PM_j = 0; + { + auto& x = PM[*iter_s1]; + x.second = shr64(x.second, static_cast(i) - x.first) | (UINT64_C(1) << 63); + x.first = static_cast(i); + } + { + auto x = PM.get(*iter_s2); + PM_j = shr64(x.second, static_cast(i) - x.first); + } + + uint64_t X = PM_j; + uint64_t D0 = (((X & VP) + VP) ^ VP) | X | VN; + + /* Step 2: Computing HP and HN */ + uint64_t HP = VN | ~(D0 | VP); + uint64_t HN = D0 & VP; + + /* Step 3: Computing the value D[m,j] */ + res.dist += !bool(D0 & diagonal_mask); + + if (res.dist > break_score) { + res.dist = max + 1; + return res; + } + + /* Step 4: Computing Vp and VN */ + VP = HN | ~((D0 >> 1) | HP); + VN = (D0 >> 1) & HP; + + if constexpr (RecordMatrix) { + res.VP[i][0] = VP; + res.VN[i][0] = VN; + } + } + + for (; i < s2.size(); ++iter_s2, ++i) { + /* Step 1: Computing D0 */ + /* update bitmasks online */ + uint64_t PM_j = 0; + if (iter_s1 != s1.end()) { + auto& x = PM[*iter_s1]; + x.second = shr64(x.second, static_cast(i) - x.first) | (UINT64_C(1) << 63); + x.first = static_cast(i); + ++iter_s1; + } + { + auto x = PM.get(*iter_s2); + PM_j = shr64(x.second, static_cast(i) - x.first); + } + + uint64_t X = PM_j; + uint64_t D0 = (((X & VP) + VP) ^ VP) | X | VN; + + /* Step 2: Computing HP and HN */ + uint64_t HP = VN | ~(D0 | VP); + uint64_t HN = D0 & VP; + + /* Step 3: Computing the value D[m,j] */ + res.dist += bool(HP & horizontal_mask); + res.dist -= bool(HN & horizontal_mask); + horizontal_mask >>= 1; + + if (res.dist > break_score) { + res.dist = max + 1; + return res; + } + + /* Step 4: Computing Vp and VN */ + VP = HN | ~((D0 >> 1) | HP); + VN = (D0 >> 1) & HP; + + if constexpr (RecordMatrix) { + res.VP[i][0] = VP; + res.VN[i][0] = VN; + } + } + + if (res.dist > max) res.dist = max + 1; + + return res; +} + +/** + * @param stop_row specifies the row to record when using RecordBitRow + */ +template +auto levenshtein_hyrroe2003_block(const BlockPatternMatchVector& PM, const Range& s1, + const Range& s2, size_t max = std::numeric_limits::max(), + size_t stop_row = std::numeric_limits::max()) + -> LevenshteinResult +{ + LevenshteinResult res; + if (max < abs_diff(s1.size(), s2.size())) { + res.dist = max + 1; + return res; + } + + size_t word_size = sizeof(uint64_t) * 8; + size_t words = PM.size(); + std::vector vecs(words); + std::vector scores(words); + uint64_t Last = UINT64_C(1) << ((s1.size() - 1) % word_size); + + for (size_t i = 0; i < words - 1; ++i) + scores[i] = (i + 1) * word_size; + + scores[words - 1] = s1.size(); + + if constexpr (RecordMatrix) { + size_t full_band = std::min(s1.size(), 2 * max + 1); + size_t full_band_words = std::min(words, full_band / word_size + 2); + res.VP = ShiftedBitMatrix(s2.size(), full_band_words, ~UINT64_C(0)); + res.VN = ShiftedBitMatrix(s2.size(), full_band_words, 0); + } + + if constexpr (RecordBitRow) { + res.first_block = 0; + res.last_block = 0; + res.prev_score = 0; + } + + max = std::min(max, std::max(s1.size(), s2.size())); + + /* first_block is the index of the first block in Ukkonen band. */ + size_t first_block = 0; + /* last_block is the index of the last block in Ukkonen band. */ + size_t last_block = + std::min(words, ceil_div(std::min(max, (max + s1.size() - s2.size()) / 2) + 1, word_size)) - 1; + + /* Searching */ + auto iter_s2 = s2.begin(); + for (size_t row = 0; row < s2.size(); ++iter_s2, ++row) { + uint64_t HP_carry = 1; + uint64_t HN_carry = 0; + + if constexpr (RecordMatrix) { + res.VP.set_offset(row, static_cast(first_block * word_size)); + res.VN.set_offset(row, static_cast(first_block * word_size)); + } + + auto advance_block = [&](size_t word) { + /* Step 1: Computing D0 */ + uint64_t PM_j = PM.get(word, *iter_s2); + uint64_t VN = vecs[word].VN; + uint64_t VP = vecs[word].VP; + + uint64_t X = PM_j | HN_carry; + uint64_t D0 = (((X & VP) + VP) ^ VP) | X | VN; + + /* Step 2: Computing HP and HN */ + uint64_t HP = VN | ~(D0 | VP); + uint64_t HN = D0 & VP; + + uint64_t HP_carry_temp = HP_carry; + uint64_t HN_carry_temp = HN_carry; + if (word < words - 1) { + HP_carry = HP >> 63; + HN_carry = HN >> 63; + } + else { + HP_carry = bool(HP & Last); + HN_carry = bool(HN & Last); + } + + /* Step 4: Computing Vp and VN */ + HP = (HP << 1) | HP_carry_temp; + HN = (HN << 1) | HN_carry_temp; + + vecs[word].VP = HN | ~(D0 | HP); + vecs[word].VN = HP & D0; + + if constexpr (RecordMatrix) { + res.VP[row][word - first_block] = vecs[word].VP; + res.VN[row][word - first_block] = vecs[word].VN; + } + + return static_cast(HP_carry) - static_cast(HN_carry); + }; + + auto get_row_num = [&](size_t word) { + if (word + 1 == words) return s1.size() - 1; + return (word + 1) * word_size - 1; + }; + + for (size_t word = first_block; word <= last_block /* - 1*/; word++) { + /* Step 3: Computing the value D[m,j] */ + scores[word] = static_cast(static_cast(scores[word]) + advance_block(word)); + } + + max = static_cast( + std::min(static_cast(max), + static_cast(scores[last_block]) + + std::max(static_cast(s2.size()) - static_cast(row) - 1, + static_cast(s1.size()) - + (static_cast((1 + last_block) * word_size - 1) - 1)))); + + /*---------- Adjust number of blocks according to Ukkonen ----------*/ + // todo on the last word instead of word_size often s1.size() % 64 should be used + + /* Band adjustment: last_block */ + /* If block is not beneath band, calculate next block. Only next because others are certainly beneath + * band. */ + if (last_block + 1 < words) { + ptrdiff_t cond = static_cast(max + 2 * word_size + row + s1.size()) - + static_cast(scores[last_block] + 2 + s2.size()); + if (static_cast(get_row_num(last_block)) < cond) { + last_block++; + vecs[last_block].VP = ~UINT64_C(0); + vecs[last_block].VN = 0; + + size_t chars_in_block = (last_block + 1 == words) ? ((s1.size() - 1) % word_size + 1) : 64; + scores[last_block] = scores[last_block - 1] + chars_in_block - + opt_static_cast(HP_carry) + opt_static_cast(HN_carry); + // todo probably wrong types + scores[last_block] = static_cast(static_cast(scores[last_block]) + + advance_block(last_block)); + } + } + + for (; last_block >= first_block; --last_block) { + /* in band if score <= k where score >= score_last - word_size + 1 */ + bool in_band_cond1 = scores[last_block] < max + word_size; + + /* in band if row <= max - score - len2 + len1 + i + * if the condition is met for the first cell in the block, it + * is met for all other cells in the blocks as well + * + * this uses a more loose condition similar to edlib: + * https://github.com/Martinsos/edlib + */ + ptrdiff_t cond = static_cast(max + 2 * word_size + row + s1.size() + 1) - + static_cast(scores[last_block] + 2 + s2.size()); + bool in_band_cond2 = static_cast(get_row_num(last_block)) <= cond; + + if (in_band_cond1 && in_band_cond2) break; + } + + /* Band adjustment: first_block */ + for (; first_block <= last_block; ++first_block) { + /* in band if score <= k where score >= score_last - word_size + 1 */ + bool in_band_cond1 = scores[first_block] < max + word_size; + + /* in band if row >= score - max - len2 + len1 + i + * if this condition is met for the last cell in the block, it + * is met for all other cells in the blocks as well + */ + ptrdiff_t cond = static_cast(scores[first_block] + s1.size() + row) - + static_cast(max + s2.size()); + bool in_band_cond2 = static_cast(get_row_num(first_block)) >= cond; + + if (in_band_cond1 && in_band_cond2) break; + } + + /* distance is larger than max, so band stops to exist */ + if (last_block < first_block) { + res.dist = max + 1; + return res; + } + + if constexpr (RecordBitRow) { + if (row == stop_row) { + if (first_block == 0) + res.prev_score = stop_row + 1; + else { + /* count backwards to find score at last position in previous block */ + size_t relevant_bits = std::min((first_block + 1) * 64, s1.size()) % 64; + uint64_t mask = ~UINT64_C(0); + if (relevant_bits) mask >>= 64 - relevant_bits; + + res.prev_score = scores[first_block] + popcount(vecs[first_block].VN & mask) - + popcount(vecs[first_block].VP & mask); + } + + res.first_block = first_block; + res.last_block = last_block; + res.vecs = std::move(vecs); + + /* unknown so make sure it is <= max */ + res.dist = 0; + return res; + } + } + } + + res.dist = scores[words - 1]; + + if (res.dist > max) res.dist = max + 1; + + return res; +} + +template +size_t uniform_levenshtein_distance(const BlockPatternMatchVector& block, Range s1, + Range s2, size_t score_cutoff, size_t score_hint) +{ + /* upper bound */ + score_cutoff = std::min(score_cutoff, std::max(s1.size(), s2.size())); + if (score_hint < 31) score_hint = 31; + + // when no differences are allowed a direct comparision is sufficient + if (score_cutoff == 0) return !std::equal(s1.begin(), s1.end(), s2.begin(), s2.end()); + + if (score_cutoff < abs_diff(s1.size(), s2.size())) return score_cutoff + 1; + + // important to catch, since this causes block to be empty -> raises exception on access + if (s1.empty()) return (s2.size() <= score_cutoff) ? s2.size() : score_cutoff + 1; + + /* do this first, since we can not remove any affix in encoded form + * todo actually we could at least remove the common prefix and just shift the band + */ + if (score_cutoff >= 4) { + // todo could safe up to 25% even without max when ignoring irrelevant paths + // in the upper and lower corner + size_t full_band = std::min(s1.size(), 2 * score_cutoff + 1); + + if (s1.size() < 65) + return levenshtein_hyrroe2003(block, s1, s2, score_cutoff).dist; + else if (full_band <= 64) + return levenshtein_hyrroe2003_small_band(block, s1, s2, score_cutoff); + + while (score_hint < score_cutoff) { + full_band = std::min(s1.size(), 2 * score_hint + 1); + + size_t score; + if (full_band <= 64) + score = levenshtein_hyrroe2003_small_band(block, s1, s2, score_hint); + else + score = levenshtein_hyrroe2003_block(block, s1, s2, score_hint).dist; + + if (score <= score_hint) return score; + + if (std::numeric_limits::max() / 2 < score_hint) break; + + score_hint *= 2; + } + + return levenshtein_hyrroe2003_block(block, s1, s2, score_cutoff).dist; + } + + /* common affix does not effect Levenshtein distance */ + remove_common_affix(s1, s2); + if (s1.empty() || s2.empty()) return s1.size() + s2.size(); + + return levenshtein_mbleven2018(s1, s2, score_cutoff); +} + +template +size_t uniform_levenshtein_distance(Range s1, Range s2, size_t score_cutoff, + size_t score_hint) +{ + /* Swapping the strings so the second string is shorter */ + if (s1.size() < s2.size()) return uniform_levenshtein_distance(s2, s1, score_cutoff, score_hint); + + /* upper bound */ + score_cutoff = std::min(score_cutoff, std::max(s1.size(), s2.size())); + if (score_hint < 31) score_hint = 31; + + // when no differences are allowed a direct comparision is sufficient + if (score_cutoff == 0) return !std::equal(s1.begin(), s1.end(), s2.begin(), s2.end()); + + // at least length difference insertions/deletions required + if (score_cutoff < (s1.size() - s2.size())) return score_cutoff + 1; + + /* common affix does not effect Levenshtein distance */ + remove_common_affix(s1, s2); + if (s1.empty() || s2.empty()) return s1.size() + s2.size(); + + if (score_cutoff < 4) return levenshtein_mbleven2018(s1, s2, score_cutoff); + + // todo could safe up to 25% even without score_cutoff when ignoring irrelevant paths + // in the upper and lower corner + size_t full_band = std::min(s1.size(), 2 * score_cutoff + 1); + + /* when the short strings has less then 65 elements Hyyrös' algorithm can be used */ + if (s2.size() < 65) + return levenshtein_hyrroe2003(PatternMatchVector(s2), s2, s1, score_cutoff).dist; + else if (full_band <= 64) + return levenshtein_hyrroe2003_small_band(s1, s2, score_cutoff).dist; + else { + BlockPatternMatchVector PM(s1); + while (score_hint < score_cutoff) { + // todo use small band implementation if possible + size_t score = levenshtein_hyrroe2003_block(PM, s1, s2, score_hint).dist; + + if (score <= score_hint) return score; + + if (std::numeric_limits::max() / 2 < score_hint) break; + + score_hint *= 2; + } + + return levenshtein_hyrroe2003_block(PM, s1, s2, score_cutoff).dist; + } +} + +/** + * @brief recover alignment from bitparallel Levenshtein matrix + */ +template +void recover_alignment(Editops& editops, const Range& s1, const Range& s2, + const LevenshteinResult& matrix, size_t src_pos, size_t dest_pos, + size_t editop_pos) +{ + size_t dist = matrix.dist; + size_t col = s1.size(); + size_t row = s2.size(); + + while (row && col) { + /* Deletion */ + if (matrix.VP.test_bit(row - 1, col - 1)) { + assert(dist > 0); + dist--; + col--; + editops[editop_pos + dist].type = EditType::Delete; + editops[editop_pos + dist].src_pos = col + src_pos; + editops[editop_pos + dist].dest_pos = row + dest_pos; + } + else { + row--; + + /* Insertion */ + if (row && matrix.VN.test_bit(row - 1, col - 1)) { + assert(dist > 0); + dist--; + editops[editop_pos + dist].type = EditType::Insert; + editops[editop_pos + dist].src_pos = col + src_pos; + editops[editop_pos + dist].dest_pos = row + dest_pos; + } + /* Match/Mismatch */ + else { + col--; + + /* Replace (Matches are not recorded) */ + if (s1[col] != s2[row]) { + assert(dist > 0); + dist--; + editops[editop_pos + dist].type = EditType::Replace; + editops[editop_pos + dist].src_pos = col + src_pos; + editops[editop_pos + dist].dest_pos = row + dest_pos; + } + } + } + } + + while (col) { + dist--; + col--; + editops[editop_pos + dist].type = EditType::Delete; + editops[editop_pos + dist].src_pos = col + src_pos; + editops[editop_pos + dist].dest_pos = row + dest_pos; + } + + while (row) { + dist--; + row--; + editops[editop_pos + dist].type = EditType::Insert; + editops[editop_pos + dist].src_pos = col + src_pos; + editops[editop_pos + dist].dest_pos = row + dest_pos; + } +} + +template +void levenshtein_align(Editops& editops, const Range& s1, const Range& s2, + size_t max = std::numeric_limits::max(), size_t src_pos = 0, + size_t dest_pos = 0, size_t editop_pos = 0) +{ + /* upper bound */ + max = std::min(max, std::max(s1.size(), s2.size())); + size_t full_band = std::min(s1.size(), 2 * max + 1); + + LevenshteinResult matrix; + if (s1.empty() || s2.empty()) + matrix.dist = s1.size() + s2.size(); + else if (s1.size() <= 64) + matrix = levenshtein_hyrroe2003(PatternMatchVector(s1), s1, s2); + else if (full_band <= 64) + matrix = levenshtein_hyrroe2003_small_band(s1, s2, max); + else + matrix = levenshtein_hyrroe2003_block(BlockPatternMatchVector(s1), s1, s2, max); + + assert(matrix.dist <= max); + if (matrix.dist != 0) { + if (editops.size() == 0) editops.resize(matrix.dist); + + recover_alignment(editops, s1, s2, matrix, src_pos, dest_pos, editop_pos); + } +} + +template +LevenshteinResult levenshtein_row(const Range& s1, const Range& s2, + size_t max, size_t stop_row) +{ + return levenshtein_hyrroe2003_block(BlockPatternMatchVector(s1), s1, s2, max, stop_row); +} + +template +size_t levenshtein_distance(const Range& s1, const Range& s2, + LevenshteinWeightTable weights = {1, 1, 1}, + size_t score_cutoff = std::numeric_limits::max(), + size_t score_hint = std::numeric_limits::max()) +{ + if (weights.insert_cost == weights.delete_cost) { + /* when insertions + deletions operations are free there can not be any edit distance */ + if (weights.insert_cost == 0) return 0; + + /* uniform Levenshtein multiplied with the common factor */ + if (weights.insert_cost == weights.replace_cost) { + // score_cutoff can make use of the common divisor of the three weights + size_t new_score_cutoff = ceil_div(score_cutoff, weights.insert_cost); + size_t new_score_hint = ceil_div(score_hint, weights.insert_cost); + size_t distance = uniform_levenshtein_distance(s1, s2, new_score_cutoff, new_score_hint); + distance *= weights.insert_cost; + return (distance <= score_cutoff) ? distance : score_cutoff + 1; + } + /* + * when replace_cost >= insert_cost + delete_cost no substitutions are performed + * therefore this can be implemented as InDel distance multiplied with the common factor + */ + else if (weights.replace_cost >= weights.insert_cost + weights.delete_cost) { + // score_cutoff can make use of the common divisor of the three weights + size_t new_score_cutoff = ceil_div(score_cutoff, weights.insert_cost); + size_t distance = rapidfuzz::indel_distance(s1, s2, new_score_cutoff); + distance *= weights.insert_cost; + return (distance <= score_cutoff) ? distance : score_cutoff + 1; + } + } + + return generalized_levenshtein_distance(s1, s2, weights, score_cutoff); +} +struct HirschbergPos { + size_t left_score; + size_t right_score; + size_t s1_mid; + size_t s2_mid; +}; + +template +HirschbergPos find_hirschberg_pos(const Range& s1, const Range& s2, + size_t max = std::numeric_limits::max()) +{ + assert(s1.size() > 1); + assert(s2.size() > 1); + + HirschbergPos hpos = {}; + size_t left_size = s2.size() / 2; + size_t right_size = s2.size() - left_size; + hpos.s2_mid = left_size; + size_t s1_len = s1.size(); + size_t best_score = std::numeric_limits::max(); + size_t right_first_pos = 0; + size_t right_last_pos = 0; + // todo: we could avoid this allocation by counting up the right score twice + // not sure whats faster though + std::vector right_scores; + { + auto right_row = levenshtein_row(s1.reversed(), s2.reversed(), max, right_size - 1); + if (right_row.dist > max) return find_hirschberg_pos(s1, s2, max * 2); + + right_first_pos = right_row.first_block * 64; + right_last_pos = std::min(s1_len, right_row.last_block * 64 + 64); + + right_scores.resize(right_last_pos - right_first_pos + 1, 0); + assume(right_scores.size() != 0); + right_scores[0] = right_row.prev_score; + + for (size_t i = right_first_pos; i < right_last_pos; ++i) { + size_t col_pos = i % 64; + size_t col_word = i / 64; + uint64_t col_mask = UINT64_C(1) << col_pos; + + right_scores[i - right_first_pos + 1] = right_scores[i - right_first_pos]; + right_scores[i - right_first_pos + 1] -= bool(right_row.vecs[col_word].VN & col_mask); + right_scores[i - right_first_pos + 1] += bool(right_row.vecs[col_word].VP & col_mask); + } + } + + auto left_row = levenshtein_row(s1, s2, max, left_size - 1); + if (left_row.dist > max) return find_hirschberg_pos(s1, s2, max * 2); + + auto left_first_pos = left_row.first_block * 64; + auto left_last_pos = std::min(s1_len, left_row.last_block * 64 + 64); + + size_t left_score = left_row.prev_score; + // take boundary into account + if (s1_len >= left_first_pos + right_first_pos) { + size_t right_index = s1_len - left_first_pos - right_first_pos; + if (right_index < right_scores.size()) { + best_score = right_scores[right_index] + left_score; + hpos.left_score = left_score; + hpos.right_score = right_scores[right_index]; + hpos.s1_mid = left_first_pos; + } + } + + for (size_t i = left_first_pos; i < left_last_pos; ++i) { + size_t col_pos = i % 64; + size_t col_word = i / 64; + uint64_t col_mask = UINT64_C(1) << col_pos; + + left_score -= bool(left_row.vecs[col_word].VN & col_mask); + left_score += bool(left_row.vecs[col_word].VP & col_mask); + + if (s1_len < i + 1 + right_first_pos) continue; + + size_t right_index = s1_len - i - 1 - right_first_pos; + if (right_index >= right_scores.size()) continue; + + if (right_scores[right_index] + left_score < best_score) { + best_score = right_scores[right_index] + left_score; + hpos.left_score = left_score; + hpos.right_score = right_scores[right_index]; + hpos.s1_mid = i + 1; + } + } + + assert(hpos.left_score >= 0); + assert(hpos.right_score >= 0); + + if (hpos.left_score + hpos.right_score > max) + return find_hirschberg_pos(s1, s2, max * 2); + else { + assert(levenshtein_distance(s1, s2) == hpos.left_score + hpos.right_score); + return hpos; + } +} + +template +void levenshtein_align_hirschberg(Editops& editops, Range s1, Range s2, + size_t src_pos = 0, size_t dest_pos = 0, size_t editop_pos = 0, + size_t max = std::numeric_limits::max()) +{ + /* prefix and suffix are no-ops, which do not need to be added to the editops */ + StringAffix affix = remove_common_affix(s1, s2); + src_pos += affix.prefix_len; + dest_pos += affix.prefix_len; + + max = std::min(max, std::max(s1.size(), s2.size())); + size_t full_band = std::min(s1.size(), 2 * max + 1); + + size_t matrix_size = 2 * full_band * s2.size() / 8; + if (matrix_size < 1024 * 1024 || s1.size() < 65 || s2.size() < 10) { + levenshtein_align(editops, s1, s2, max, src_pos, dest_pos, editop_pos); + } + /* Hirschbergs algorithm */ + else { + auto hpos = find_hirschberg_pos(s1, s2, max); + + if (editops.size() == 0) editops.resize(hpos.left_score + hpos.right_score); + + levenshtein_align_hirschberg(editops, s1.subseq(0, hpos.s1_mid), s2.subseq(0, hpos.s2_mid), src_pos, + dest_pos, editop_pos, hpos.left_score); + levenshtein_align_hirschberg(editops, s1.subseq(hpos.s1_mid), s2.subseq(hpos.s2_mid), + src_pos + hpos.s1_mid, dest_pos + hpos.s2_mid, + editop_pos + hpos.left_score, hpos.right_score); + } +} + +class Levenshtein : public DistanceBase::max(), + LevenshteinWeightTable> { + friend DistanceBase::max(), LevenshteinWeightTable>; + friend NormalizedMetricBase; + + template + static size_t maximum(const Range& s1, const Range& s2, + LevenshteinWeightTable weights) + { + return levenshtein_maximum(s1.size(), s2.size(), weights); + } + + template + static size_t _distance(const Range& s1, const Range& s2, + LevenshteinWeightTable weights, size_t score_cutoff, size_t score_hint) + { + return levenshtein_distance(s1, s2, weights, score_cutoff, score_hint); + } +}; + +template +Editops levenshtein_editops(const Range& s1, const Range& s2, size_t score_hint) +{ + Editops editops; + if (score_hint < 31) score_hint = 31; + + size_t score_cutoff = std::max(s1.size(), s2.size()); + /* score_hint currently leads to calculating the levenshtein distance twice + * 1) to find the real distance + * 2) to find the alignment + * this is only worth it when at least 50% of the runtime could be saved + * todo: maybe there is a way to join these two calculations in the future + * so it is worth it in more cases + */ + if (std::numeric_limits::max() / 2 > score_hint && 2 * score_hint < score_cutoff) + score_cutoff = Levenshtein::distance(s1, s2, {1, 1, 1}, score_cutoff, score_hint); + + levenshtein_align_hirschberg(editops, s1, s2, 0, 0, 0, score_cutoff); + + editops.set_src_len(s1.size()); + editops.set_dest_len(s2.size()); + return editops; +} + +} // namespace rapidfuzz::detail + +namespace rapidfuzz { + +/** + * @brief Calculates the minimum number of insertions, deletions, and substitutions + * required to change one sequence into the other according to Levenshtein with custom + * costs for insertion, deletion and substitution + * + * @tparam Sentence1 This is a string that can be converted to + * basic_string_view + * @tparam Sentence2 This is a string that can be converted to + * basic_string_view + * + * @param s1 + * string to compare with s2 (for type info check Template parameters above) + * @param s2 + * string to compare with s1 (for type info check Template parameters above) + * @param weights + * The weights for the three operations in the form + * (insertion, deletion, substitution). Default is {1, 1, 1}, + * which gives all three operations a weight of 1. + * @param max + * Maximum Levenshtein distance between s1 and s2, that is + * considered as a result. If the distance is bigger than max, + * max + 1 is returned instead. Default is std::numeric_limits::max(), + * which deactivates this behaviour. + * + * @return returns the levenshtein distance between s1 and s2 + * + * @remarks + * @parblock + * Depending on the input parameters different optimized implementation are used + * to improve the performance. Worst-case performance is ``O(m * n)``. + * + * Insertion = Deletion = Substitution: + * + * This is known as uniform Levenshtein distance and is the distance most commonly + * referred to as Levenshtein distance. The following implementation is used + * with a worst-case performance of ``O([N/64]M)``. + * + * - if max is 0 the similarity can be calculated using a direct comparision, + * since no difference between the strings is allowed. The time complexity of + * this algorithm is ``O(N)``. + * + * - A common prefix/suffix of the two compared strings does not affect + * the Levenshtein distance, so the affix is removed before calculating the + * similarity. + * + * - If max is <= 3 the mbleven algorithm is used. This algorithm + * checks all possible edit operations that are possible under + * the threshold `max`. The time complexity of this algorithm is ``O(N)``. + * + * - If the length of the shorter string is <= 64 after removing the common affix + * Hyyrös' algorithm is used, which calculates the Levenshtein distance in + * parallel. The algorithm is described by @cite hyrro_2002. The time complexity of this + * algorithm is ``O(N)``. + * + * - If the length of the shorter string is >= 64 after removing the common affix + * a blockwise implementation of Myers' algorithm is used, which calculates + * the Levenshtein distance in parallel (64 characters at a time). + * The algorithm is described by @cite myers_1999. The time complexity of this + * algorithm is ``O([N/64]M)``. + * + * + * Insertion = Deletion, Substitution >= Insertion + Deletion: + * + * Since every Substitution can be performed as Insertion + Deletion, this variant + * of the Levenshtein distance only uses Insertions and Deletions. Therefore this + * variant is often referred to as InDel-Distance. The following implementation + * is used with a worst-case performance of ``O([N/64]M)``. + * + * - if max is 0 the similarity can be calculated using a direct comparision, + * since no difference between the strings is allowed. The time complexity of + * this algorithm is ``O(N)``. + * + * - if max is 1 and the two strings have a similar length, the similarity can be + * calculated using a direct comparision aswell, since a substitution would cause + * a edit distance higher than max. The time complexity of this algorithm + * is ``O(N)``. + * + * - A common prefix/suffix of the two compared strings does not affect + * the Levenshtein distance, so the affix is removed before calculating the + * similarity. + * + * - If max is <= 4 the mbleven algorithm is used. This algorithm + * checks all possible edit operations that are possible under + * the threshold `max`. As a difference to the normal Levenshtein distance this + * algorithm can even be used up to a threshold of 4 here, since the higher weight + * of substitutions decreases the amount of possible edit operations. + * The time complexity of this algorithm is ``O(N)``. + * + * - If the length of the shorter string is <= 64 after removing the common affix + * Hyyrös' lcs algorithm is used, which calculates the InDel distance in + * parallel. The algorithm is described by @cite hyrro_lcs_2004 and is extended with support + * for UTF32 in this implementation. The time complexity of this + * algorithm is ``O(N)``. + * + * - If the length of the shorter string is >= 64 after removing the common affix + * a blockwise implementation of Hyyrös' lcs algorithm is used, which calculates + * the Levenshtein distance in parallel (64 characters at a time). + * The algorithm is described by @cite hyrro_lcs_2004. The time complexity of this + * algorithm is ``O([N/64]M)``. + * + * Other weights: + * + * The implementation for other weights is based on Wagner-Fischer. + * It has a performance of ``O(N * M)`` and has a memory usage of ``O(N)``. + * Further details can be found in @cite wagner_fischer_1974. + * @endparblock + * + * @par Examples + * @parblock + * Find the Levenshtein distance between two strings: + * @code{.cpp} + * // dist is 2 + * size_t dist = levenshtein_distance("lewenstein", "levenshtein"); + * @endcode + * + * Setting a maximum distance allows the implementation to select + * a more efficient implementation: + * @code{.cpp} + * // dist is 2 + * size_t dist = levenshtein_distance("lewenstein", "levenshtein", {1, 1, 1}, 1); + * @endcode + * + * It is possible to select different weights by passing a `weight` struct. + * @code{.cpp} + * // dist is 3 + * size_t dist = levenshtein_distance("lewenstein", "levenshtein", {1, 1, 2}); + * @endcode + * @endparblock + */ +template +size_t levenshtein_distance(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + LevenshteinWeightTable weights = {1, 1, 1}, + size_t score_cutoff = std::numeric_limits::max(), + size_t score_hint = std::numeric_limits::max()) +{ + return detail::Levenshtein::distance(first1, last1, first2, last2, weights, score_cutoff, score_hint); +} + +template +size_t levenshtein_distance(const Sentence1& s1, const Sentence2& s2, + LevenshteinWeightTable weights = {1, 1, 1}, + size_t score_cutoff = std::numeric_limits::max(), + size_t score_hint = std::numeric_limits::max()) +{ + return detail::Levenshtein::distance(s1, s2, weights, score_cutoff, score_hint); +} + +template +size_t levenshtein_similarity(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + LevenshteinWeightTable weights = {1, 1, 1}, size_t score_cutoff = 0, + size_t score_hint = 0) +{ + return detail::Levenshtein::similarity(first1, last1, first2, last2, weights, score_cutoff, score_hint); +} + +template +size_t levenshtein_similarity(const Sentence1& s1, const Sentence2& s2, + LevenshteinWeightTable weights = {1, 1, 1}, size_t score_cutoff = 0, + size_t score_hint = 0) +{ + return detail::Levenshtein::similarity(s1, s2, weights, score_cutoff, score_hint); +} + +template +double levenshtein_normalized_distance(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + LevenshteinWeightTable weights = {1, 1, 1}, double score_cutoff = 1.0, + double score_hint = 1.0) +{ + return detail::Levenshtein::normalized_distance(first1, last1, first2, last2, weights, score_cutoff, + score_hint); +} + +template +double levenshtein_normalized_distance(const Sentence1& s1, const Sentence2& s2, + LevenshteinWeightTable weights = {1, 1, 1}, double score_cutoff = 1.0, + double score_hint = 1.0) +{ + return detail::Levenshtein::normalized_distance(s1, s2, weights, score_cutoff, score_hint); +} + +/** + * @brief Calculates a normalized levenshtein distance using custom + * costs for insertion, deletion and substitution. + * + * @tparam Sentence1 This is a string that can be converted to + * basic_string_view + * @tparam Sentence2 This is a string that can be converted to + * basic_string_view + * + * @param s1 + * string to compare with s2 (for type info check Template parameters above) + * @param s2 + * string to compare with s1 (for type info check Template parameters above) + * @param weights + * The weights for the three operations in the form + * (insertion, deletion, substitution). Default is {1, 1, 1}, + * which gives all three operations a weight of 1. + * @param score_cutoff + * Optional argument for a score threshold as a float between 0 and 1.0. + * For ratio < score_cutoff 0 is returned instead. Default is 0, + * which deactivates this behaviour. + * + * @return Normalized weighted levenshtein distance between s1 and s2 + * as a double between 0 and 1.0 + * + * @see levenshtein() + * + * @remarks + * @parblock + * The normalization of the Levenshtein distance is performed in the following way: + * + * \f{align*}{ + * ratio &= \frac{distance(s1, s2)}{max_dist} + * \f} + * @endparblock + * + * + * @par Examples + * @parblock + * Find the normalized Levenshtein distance between two strings: + * @code{.cpp} + * // ratio is 81.81818181818181 + * double ratio = normalized_levenshtein("lewenstein", "levenshtein"); + * @endcode + * + * Setting a score_cutoff allows the implementation to select + * a more efficient implementation: + * @code{.cpp} + * // ratio is 0.0 + * double ratio = normalized_levenshtein("lewenstein", "levenshtein", {1, 1, 1}, 85.0); + * @endcode + * + * It is possible to select different weights by passing a `weight` struct + * @code{.cpp} + * // ratio is 85.71428571428571 + * double ratio = normalized_levenshtein("lewenstein", "levenshtein", {1, 1, 2}); + * @endcode + * @endparblock + */ +template +double levenshtein_normalized_similarity(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + LevenshteinWeightTable weights = {1, 1, 1}, + double score_cutoff = 0.0, double score_hint = 0.0) +{ + return detail::Levenshtein::normalized_similarity(first1, last1, first2, last2, weights, score_cutoff, + score_hint); +} + +template +double levenshtein_normalized_similarity(const Sentence1& s1, const Sentence2& s2, + LevenshteinWeightTable weights = {1, 1, 1}, + double score_cutoff = 0.0, double score_hint = 0.0) +{ + return detail::Levenshtein::normalized_similarity(s1, s2, weights, score_cutoff, score_hint); +} + +/** + * @brief Return list of EditOp describing how to turn s1 into s2. + * + * @tparam Sentence1 This is a string that can be converted to + * basic_string_view + * @tparam Sentence2 This is a string that can be converted to + * basic_string_view + * + * @param s1 + * string to compare with s2 (for type info check Template parameters above) + * @param s2 + * string to compare with s1 (for type info check Template parameters above) + * + * @return Edit operations required to turn s1 into s2 + */ +template +Editops levenshtein_editops(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + size_t score_hint = std::numeric_limits::max()) +{ + return detail::levenshtein_editops(detail::Range(first1, last1), detail::Range(first2, last2), + score_hint); +} + +template +Editops levenshtein_editops(const Sentence1& s1, const Sentence2& s2, + size_t score_hint = std::numeric_limits::max()) +{ + return detail::levenshtein_editops(detail::Range(s1), detail::Range(s2), score_hint); +} + +#ifdef RAPIDFUZZ_SIMD +namespace experimental { +template +struct MultiLevenshtein : public detail::MultiDistanceBase, size_t, 0, + std::numeric_limits::max()> { +private: + friend detail::MultiDistanceBase, size_t, 0, + std::numeric_limits::max()>; + friend detail::MultiNormalizedMetricBase, size_t>; + + constexpr static size_t get_vec_size() + { +# ifdef RAPIDFUZZ_AVX2 + using namespace detail::simd_avx2; +# else + using namespace detail::simd_sse2; +# endif + if constexpr (MaxLen <= 8) + return native_simd::size; + else if constexpr (MaxLen <= 16) + return native_simd::size; + else if constexpr (MaxLen <= 32) + return native_simd::size; + else if constexpr (MaxLen <= 64) + return native_simd::size; + + static_assert(MaxLen <= 64); + } + + constexpr static size_t find_block_count(size_t count) + { + size_t vec_size = get_vec_size(); + size_t simd_vec_count = detail::ceil_div(count, vec_size); + return detail::ceil_div(simd_vec_count * vec_size * MaxLen, 64); + } + +public: + MultiLevenshtein(size_t count, LevenshteinWeightTable aWeights = {1, 1, 1}) + : input_count(count), PM(find_block_count(count) * 64), weights(aWeights) + { + str_lens.resize(result_count()); + if (weights.delete_cost != 1 || weights.insert_cost != 1 || weights.replace_cost > 2) + throw std::invalid_argument("unsupported weights"); + } + + /** + * @brief get minimum size required for result vectors passed into + * - distance + * - similarity + * - normalized_distance + * - normalized_similarity + * + * @return minimum vector size + */ + size_t result_count() const + { + size_t vec_size = get_vec_size(); + size_t simd_vec_count = detail::ceil_div(input_count, vec_size); + return simd_vec_count * vec_size; + } + + template + void insert(const Sentence1& s1_) + { + insert(detail::to_begin(s1_), detail::to_end(s1_)); + } + + template + void insert(InputIt1 first1, InputIt1 last1) + { + auto len = std::distance(first1, last1); + int block_pos = static_cast((pos * MaxLen) % 64); + auto block = (pos * MaxLen) / 64; + assert(len <= MaxLen); + + if (pos >= input_count) throw std::invalid_argument("out of bounds insert"); + + str_lens[pos] = static_cast(len); + for (; first1 != last1; ++first1) { + PM.insert(block, *first1, block_pos); + block_pos++; + } + pos++; + } + +private: + template + void _distance(size_t* scores, size_t score_count, const detail::Range& s2, + size_t score_cutoff = std::numeric_limits::max()) const + { + if (score_count < result_count()) + throw std::invalid_argument("scores has to have >= result_count() elements"); + + detail::Range scores_(scores, scores + score_count); + if constexpr (MaxLen == 8) + detail::levenshtein_hyrroe2003_simd(scores_, PM, str_lens, s2, score_cutoff); + else if constexpr (MaxLen == 16) + detail::levenshtein_hyrroe2003_simd(scores_, PM, str_lens, s2, score_cutoff); + else if constexpr (MaxLen == 32) + detail::levenshtein_hyrroe2003_simd(scores_, PM, str_lens, s2, score_cutoff); + else if constexpr (MaxLen == 64) + detail::levenshtein_hyrroe2003_simd(scores_, PM, str_lens, s2, score_cutoff); + } + + template + size_t maximum(size_t s1_idx, const detail::Range& s2) const + { + return detail::levenshtein_maximum(str_lens[s1_idx], s2.size(), weights); + } + + size_t get_input_count() const noexcept + { + return input_count; + } + + size_t input_count; + size_t pos = 0; + detail::BlockPatternMatchVector PM; + std::vector str_lens; + LevenshteinWeightTable weights; +}; +} /* namespace experimental */ +#endif /* RAPIDFUZZ_SIMD */ + +template +struct CachedLevenshtein : public detail::CachedDistanceBase, size_t, 0, + std::numeric_limits::max()> { + template + explicit CachedLevenshtein(const Sentence1& s1_, LevenshteinWeightTable aWeights = {1, 1, 1}) + : CachedLevenshtein(detail::to_begin(s1_), detail::to_end(s1_), aWeights) + {} + + template + CachedLevenshtein(InputIt1 first1, InputIt1 last1, LevenshteinWeightTable aWeights = {1, 1, 1}) + : s1(first1, last1), PM(detail::Range(first1, last1)), weights(aWeights) + {} + +private: + friend detail::CachedDistanceBase, size_t, 0, + std::numeric_limits::max()>; + friend detail::CachedNormalizedMetricBase>; + + template + size_t maximum(const detail::Range& s2) const + { + return detail::levenshtein_maximum(s1.size(), s2.size(), weights); + } + + template + size_t _distance(const detail::Range& s2, size_t score_cutoff, size_t score_hint) const + { + if (weights.insert_cost == weights.delete_cost) { + /* when insertions + deletions operations are free there can not be any edit distance */ + if (weights.insert_cost == 0) return 0; + + /* uniform Levenshtein multiplied with the common factor */ + if (weights.insert_cost == weights.replace_cost) { + // max can make use of the common divisor of the three weights + size_t new_score_cutoff = detail::ceil_div(score_cutoff, weights.insert_cost); + size_t new_score_hint = detail::ceil_div(score_hint, weights.insert_cost); + size_t dist = detail::uniform_levenshtein_distance(PM, detail::Range(s1), s2, + new_score_cutoff, new_score_hint); + dist *= weights.insert_cost; + + return (dist <= score_cutoff) ? dist : score_cutoff + 1; + } + /* + * when replace_cost >= insert_cost + delete_cost no substitutions are performed + * therefore this can be implemented as InDel distance multiplied with the common factor + */ + else if (weights.replace_cost >= weights.insert_cost + weights.delete_cost) { + // max can make use of the common divisor of the three weights + size_t new_max = detail::ceil_div(score_cutoff, weights.insert_cost); + size_t dist = detail::indel_distance(PM, detail::Range(s1), s2, new_max); + dist *= weights.insert_cost; + return (dist <= score_cutoff) ? dist : score_cutoff + 1; + } + } + + return detail::generalized_levenshtein_distance(detail::Range(s1), s2, weights, score_cutoff); + } + + std::vector s1; + detail::BlockPatternMatchVector PM; + LevenshteinWeightTable weights; +}; + +template +explicit CachedLevenshtein(const Sentence1& s1_, LevenshteinWeightTable aWeights = { + 1, 1, 1}) -> CachedLevenshtein>; + +template +CachedLevenshtein(InputIt1 first1, InputIt1 last1, + LevenshteinWeightTable aWeights = {1, 1, 1}) -> CachedLevenshtein>; + +} // namespace rapidfuzz + +#include + +#include + +namespace rapidfuzz::detail { + +/** + * @brief Bitparallel implementation of the OSA distance. + * + * This implementation requires the first string to have a length <= 64. + * The algorithm used is described @cite hyrro_2002 and has a time complexity + * of O(N). Comments and variable names in the implementation follow the + * paper. This implementation is used internally when the strings are short enough + * + * @tparam CharT1 This is the char type of the first sentence + * @tparam CharT2 This is the char type of the second sentence + * + * @param s1 + * string to compare with s2 (for type info check Template parameters above) + * @param s2 + * string to compare with s1 (for type info check Template parameters above) + * + * @return returns the OSA distance between s1 and s2 + */ +template +size_t osa_hyrroe2003(const PM_Vec& PM, const Range& s1, const Range& s2, size_t max) +{ + /* VP is set to 1^m. Shifting by bitwidth would be undefined behavior */ + uint64_t VP = ~UINT64_C(0); + uint64_t VN = 0; + uint64_t D0 = 0; + uint64_t PM_j_old = 0; + size_t currDist = s1.size(); + assert(s1.size() != 0); + + /* mask used when computing D[m,j] in the paper 10^(m-1) */ + uint64_t mask = UINT64_C(1) << (s1.size() - 1); + + /* Searching */ + for (const auto& ch : s2) { + /* Step 1: Computing D0 */ + uint64_t PM_j = PM.get(0, ch); + uint64_t TR = (((~D0) & PM_j) << 1) & PM_j_old; + D0 = (((PM_j & VP) + VP) ^ VP) | PM_j | VN; + D0 = D0 | TR; + + /* Step 2: Computing HP and HN */ + uint64_t HP = VN | ~(D0 | VP); + uint64_t HN = D0 & VP; + + /* Step 3: Computing the value D[m,j] */ + currDist += bool(HP & mask); + currDist -= bool(HN & mask); + + /* Step 4: Computing Vp and VN */ + HP = (HP << 1) | 1; + HN = (HN << 1); + + VP = HN | ~(D0 | HP); + VN = HP & D0; + PM_j_old = PM_j; + } + + return (currDist <= max) ? currDist : max + 1; +} + +#ifdef RAPIDFUZZ_SIMD +template +void osa_hyrroe2003_simd(Range scores, const detail::BlockPatternMatchVector& block, + const std::vector& s1_lengths, const Range& s2, + size_t score_cutoff) noexcept +{ +# ifdef RAPIDFUZZ_AVX2 + using namespace simd_avx2; +# else + using namespace simd_sse2; +# endif + static constexpr size_t alignment = native_simd::alignment; + static constexpr size_t vec_width = native_simd::size; + static constexpr size_t vecs = native_simd::size; + assert(block.size() % vecs == 0); + + native_simd zero(VecType(0)); + native_simd one(1); + size_t result_index = 0; + + for (size_t cur_vec = 0; cur_vec < block.size(); cur_vec += vecs) { + /* VP is set to 1^m */ + native_simd VP(static_cast(-1)); + native_simd VN(VecType(0)); + native_simd D0(VecType(0)); + native_simd PM_j_old(VecType(0)); + + alignas(alignment) std::array currDist_; + unroll( + [&](auto i) { currDist_[i] = static_cast(s1_lengths[result_index + i]); }); + native_simd currDist(reinterpret_cast(currDist_.data())); + /* mask used when computing D[m,j] in the paper 10^(m-1) */ + alignas(alignment) std::array mask_; + unroll([&](auto i) { + if (s1_lengths[result_index + i] == 0) + mask_[i] = 0; + else + mask_[i] = static_cast(UINT64_C(1) << (s1_lengths[result_index + i] - 1)); + }); + native_simd mask(reinterpret_cast(mask_.data())); + + for (const auto& ch : s2) { + /* Step 1: Computing D0 */ + alignas(alignment) std::array stored; + unroll([&](auto i) { stored[i] = block.get(cur_vec + i, ch); }); + + native_simd PM_j(stored.data()); + auto TR = (andnot(PM_j, D0) << 1) & PM_j_old; + D0 = (((PM_j & VP) + VP) ^ VP) | PM_j | VN; + D0 = D0 | TR; + + /* Step 2: Computing HP and HN */ + auto HP = VN | ~(D0 | VP); + auto HN = D0 & VP; + + /* Step 3: Computing the value D[m,j] */ + currDist += andnot(one, (HP & mask) == zero); + currDist -= andnot(one, (HN & mask) == zero); + + /* Step 4: Computing Vp and VN */ + HP = (HP << 1) | one; + HN = (HN << 1); + + VP = HN | ~(D0 | HP); + VN = HP & D0; + PM_j_old = PM_j; + } + + alignas(alignment) std::array distances; + currDist.store(distances.data()); + + unroll([&](auto i) { + size_t score = 0; + /* strings of length 0 are not handled correctly */ + if (s1_lengths[result_index] == 0) { + score = s2.size(); + } + /* calculate score under consideration of wraparounds in parallel counter */ + else { + if constexpr (std::numeric_limits::max() < std::numeric_limits::max()) { + size_t min_dist = abs_diff(s1_lengths[result_index], s2.size()); + size_t wraparound_score = static_cast(std::numeric_limits::max()) + 1; + + score = (min_dist / wraparound_score) * wraparound_score; + VecType remainder = static_cast(min_dist % wraparound_score); + + if (distances[i] < remainder) score += wraparound_score; + } + + score += distances[i]; + } + scores[result_index] = (score <= score_cutoff) ? score : score_cutoff + 1; + result_index++; + }); + } +} +#endif + +template +size_t osa_hyrroe2003_block(const BlockPatternMatchVector& PM, const Range& s1, + const Range& s2, size_t max = std::numeric_limits::max()) +{ + struct Row { + uint64_t VP; + uint64_t VN; + uint64_t D0; + uint64_t PM; + + Row() : VP(~UINT64_C(0)), VN(0), D0(0), PM(0) + {} + }; + + size_t word_size = sizeof(uint64_t) * 8; + size_t words = PM.size(); + uint64_t Last = UINT64_C(1) << ((s1.size() - 1) % word_size); + + size_t currDist = s1.size(); + std::vector old_vecs(words + 1); + std::vector new_vecs(words + 1); + + /* Searching */ + auto iter_s2 = s2.begin(); + for (size_t row = 0; row < s2.size(); ++iter_s2, ++row) { + uint64_t HP_carry = 1; + uint64_t HN_carry = 0; + + for (size_t word = 0; word < words; word++) { + /* retrieve bit vectors from last iterations */ + uint64_t VN = old_vecs[word + 1].VN; + uint64_t VP = old_vecs[word + 1].VP; + uint64_t D0 = old_vecs[word + 1].D0; + /* D0 last word */ + uint64_t D0_last = old_vecs[word].D0; + + /* PM of last char same word */ + uint64_t PM_j_old = old_vecs[word + 1].PM; + /* PM of last word */ + uint64_t PM_last = new_vecs[word].PM; + + uint64_t PM_j = PM.get(word, *iter_s2); + uint64_t X = PM_j; + uint64_t TR = ((((~D0) & X) << 1) | (((~D0_last) & PM_last) >> 63)) & PM_j_old; + + X |= HN_carry; + D0 = (((X & VP) + VP) ^ VP) | X | VN | TR; + + uint64_t HP = VN | ~(D0 | VP); + uint64_t HN = D0 & VP; + + if (word == words - 1) { + currDist += bool(HP & Last); + currDist -= bool(HN & Last); + } + + uint64_t HP_carry_temp = HP_carry; + HP_carry = HP >> 63; + HP = (HP << 1) | HP_carry_temp; + uint64_t HN_carry_temp = HN_carry; + HN_carry = HN >> 63; + HN = (HN << 1) | HN_carry_temp; + + new_vecs[word + 1].VP = HN | ~(D0 | HP); + new_vecs[word + 1].VN = HP & D0; + new_vecs[word + 1].D0 = D0; + new_vecs[word + 1].PM = PM_j; + } + + std::swap(new_vecs, old_vecs); + } + + return (currDist <= max) ? currDist : max + 1; +} + +class OSA : public DistanceBase::max()> { + friend DistanceBase::max()>; + friend NormalizedMetricBase; + + template + static size_t maximum(const Range& s1, const Range& s2) + { + return std::max(s1.size(), s2.size()); + } + + template + static size_t _distance(Range s1, Range s2, size_t score_cutoff, size_t score_hint) + { + if (s2.size() < s1.size()) return _distance(s2, s1, score_cutoff, score_hint); + + remove_common_affix(s1, s2); + if (s1.empty()) + return (s2.size() <= score_cutoff) ? s2.size() : score_cutoff + 1; + else if (s1.size() < 64) + return osa_hyrroe2003(PatternMatchVector(s1), s1, s2, score_cutoff); + else + return osa_hyrroe2003_block(BlockPatternMatchVector(s1), s1, s2, score_cutoff); + } +}; + +} // namespace rapidfuzz::detail + +namespace rapidfuzz { + +/** + * @brief Calculates the optimal string alignment (OSA) distance between two strings. + * + * @details + * Both strings require a similar length + * + * + * @tparam Sentence1 This is a string that can be converted to + * basic_string_view + * @tparam Sentence2 This is a string that can be converted to + * basic_string_view + * + * @param s1 + * string to compare with s2 (for type info check Template parameters above) + * @param s2 + * string to compare with s1 (for type info check Template parameters above) + * @param max + * Maximum OSA distance between s1 and s2, that is + * considered as a result. If the distance is bigger than max, + * max + 1 is returned instead. Default is std::numeric_limits::max(), + * which deactivates this behaviour. + * + * @return OSA distance between s1 and s2 + */ +template +size_t osa_distance(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + size_t score_cutoff = std::numeric_limits::max()) +{ + return detail::OSA::distance(first1, last1, first2, last2, score_cutoff, score_cutoff); +} + +template +size_t osa_distance(const Sentence1& s1, const Sentence2& s2, + size_t score_cutoff = std::numeric_limits::max()) +{ + return detail::OSA::distance(s1, s2, score_cutoff, score_cutoff); +} + +template +size_t osa_similarity(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + size_t score_cutoff = 0) +{ + return detail::OSA::similarity(first1, last1, first2, last2, score_cutoff, score_cutoff); +} + +template +size_t osa_similarity(const Sentence1& s1, const Sentence2& s2, size_t score_cutoff = 0) +{ + return detail::OSA::similarity(s1, s2, score_cutoff, score_cutoff); +} + +template +double osa_normalized_distance(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + double score_cutoff = 1.0) +{ + return detail::OSA::normalized_distance(first1, last1, first2, last2, score_cutoff, score_cutoff); +} + +template +double osa_normalized_distance(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 1.0) +{ + return detail::OSA::normalized_distance(s1, s2, score_cutoff, score_cutoff); +} + +/** + * @brief Calculates a normalized hamming similarity + * + * @details + * Both string require a similar length + * + * + * @tparam Sentence1 This is a string that can be converted to + * basic_string_view + * @tparam Sentence2 This is a string that can be converted to + * basic_string_view + * + * @param s1 + * string to compare with s2 (for type info check Template parameters above) + * @param s2 + * string to compare with s1 (for type info check Template parameters above) + * @param score_cutoff + * Optional argument for a score threshold as a float between 0 and 1.0. + * For ratio < score_cutoff 0 is returned instead. Default is 0, + * which deactivates this behaviour. + * + * @return Normalized hamming distance between s1 and s2 + * as a float between 0 and 1.0 + */ +template +double osa_normalized_similarity(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + double score_cutoff = 0.0) +{ + return detail::OSA::normalized_similarity(first1, last1, first2, last2, score_cutoff, score_cutoff); +} + +template +double osa_normalized_similarity(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 0.0) +{ + return detail::OSA::normalized_similarity(s1, s2, score_cutoff, score_cutoff); +} + +#ifdef RAPIDFUZZ_SIMD +namespace experimental { +template +struct MultiOSA + : public detail::MultiDistanceBase, size_t, 0, std::numeric_limits::max()> { +private: + friend detail::MultiDistanceBase, size_t, 0, std::numeric_limits::max()>; + friend detail::MultiNormalizedMetricBase, size_t>; + + constexpr static size_t get_vec_size() + { +# ifdef RAPIDFUZZ_AVX2 + using namespace detail::simd_avx2; +# else + using namespace detail::simd_sse2; +# endif + if constexpr (MaxLen <= 8) + return native_simd::size; + else if constexpr (MaxLen <= 16) + return native_simd::size; + else if constexpr (MaxLen <= 32) + return native_simd::size; + else if constexpr (MaxLen <= 64) + return native_simd::size; + + static_assert(MaxLen <= 64); + } + + constexpr static size_t find_block_count(size_t count) + { + size_t vec_size = get_vec_size(); + size_t simd_vec_count = detail::ceil_div(count, vec_size); + return detail::ceil_div(simd_vec_count * vec_size * MaxLen, 64); + } + +public: + MultiOSA(size_t count) : input_count(count), PM(find_block_count(count) * 64) + { + str_lens.resize(result_count()); + } + + /** + * @brief get minimum size required for result vectors passed into + * - distance + * - similarity + * - normalized_distance + * - normalized_similarity + * + * @return minimum vector size + */ + size_t result_count() const + { + size_t vec_size = get_vec_size(); + size_t simd_vec_count = detail::ceil_div(input_count, vec_size); + return simd_vec_count * vec_size; + } + + template + void insert(const Sentence1& s1_) + { + insert(detail::to_begin(s1_), detail::to_end(s1_)); + } + + template + void insert(InputIt1 first1, InputIt1 last1) + { + auto len = std::distance(first1, last1); + int block_pos = static_cast((pos * MaxLen) % 64); + auto block = (pos * MaxLen) / 64; + assert(len <= MaxLen); + + if (pos >= input_count) throw std::invalid_argument("out of bounds insert"); + + str_lens[pos] = static_cast(len); + for (; first1 != last1; ++first1) { + PM.insert(block, *first1, block_pos); + block_pos++; + } + pos++; + } + +private: + template + void _distance(size_t* scores, size_t score_count, const detail::Range& s2, + size_t score_cutoff = std::numeric_limits::max()) const + { + if (score_count < result_count()) + throw std::invalid_argument("scores has to have >= result_count() elements"); + + detail::Range scores_(scores, scores + score_count); + if constexpr (MaxLen == 8) + detail::osa_hyrroe2003_simd(scores_, PM, str_lens, s2, score_cutoff); + else if constexpr (MaxLen == 16) + detail::osa_hyrroe2003_simd(scores_, PM, str_lens, s2, score_cutoff); + else if constexpr (MaxLen == 32) + detail::osa_hyrroe2003_simd(scores_, PM, str_lens, s2, score_cutoff); + else if constexpr (MaxLen == 64) + detail::osa_hyrroe2003_simd(scores_, PM, str_lens, s2, score_cutoff); + } + + template + size_t maximum(size_t s1_idx, const detail::Range& s2) const + { + return std::max(str_lens[s1_idx], s2.size()); + } + + size_t get_input_count() const noexcept + { + return input_count; + } + + size_t input_count; + size_t pos = 0; + detail::BlockPatternMatchVector PM; + std::vector str_lens; +}; +} /* namespace experimental */ +#endif + +template +struct CachedOSA + : public detail::CachedDistanceBase, size_t, 0, std::numeric_limits::max()> { + template + explicit CachedOSA(const Sentence1& s1_) : CachedOSA(detail::to_begin(s1_), detail::to_end(s1_)) + {} + + template + CachedOSA(InputIt1 first1, InputIt1 last1) : s1(first1, last1), PM(detail::Range(first1, last1)) + {} + +private: + friend detail::CachedDistanceBase, size_t, 0, std::numeric_limits::max()>; + friend detail::CachedNormalizedMetricBase>; + + template + size_t maximum(const detail::Range& s2) const + { + return std::max(s1.size(), s2.size()); + } + + template + size_t _distance(const detail::Range& s2, size_t score_cutoff, + [[maybe_unused]] size_t score_hint) const + { + size_t res; + if (s1.empty()) + res = s2.size(); + else if (s2.empty()) + res = s1.size(); + else if (s1.size() < 64) + res = detail::osa_hyrroe2003(PM, detail::Range(s1), s2, score_cutoff); + else + res = detail::osa_hyrroe2003_block(PM, detail::Range(s1), s2, score_cutoff); + + return (res <= score_cutoff) ? res : score_cutoff + 1; + } + + std::vector s1; + detail::BlockPatternMatchVector PM; +}; + +template +CachedOSA(const Sentence1& s1_) -> CachedOSA>; + +template +CachedOSA(InputIt1 first1, InputIt1 last1) -> CachedOSA>; +/**@}*/ + +} // namespace rapidfuzz + +#include + +namespace rapidfuzz::detail { + +class Postfix : public SimilarityBase::max()> { + friend SimilarityBase::max()>; + friend NormalizedMetricBase; + + template + static size_t maximum(const Range& s1, const Range& s2) + { + return std::max(s1.size(), s2.size()); + } + + template + static size_t _similarity(Range s1, Range s2, size_t score_cutoff, + [[maybe_unused]] size_t score_hint) + { + size_t dist = remove_common_suffix(s1, s2); + return (dist >= score_cutoff) ? dist : 0; + } +}; + +} // namespace rapidfuzz::detail + +namespace rapidfuzz { + +template +size_t postfix_distance(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + size_t score_cutoff = std::numeric_limits::max()) +{ + return detail::Postfix::distance(first1, last1, first2, last2, score_cutoff, score_cutoff); +} + +template +size_t postfix_distance(const Sentence1& s1, const Sentence2& s2, + size_t score_cutoff = std::numeric_limits::max()) +{ + return detail::Postfix::distance(s1, s2, score_cutoff, score_cutoff); +} + +template +size_t postfix_similarity(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + size_t score_cutoff = 0) +{ + return detail::Postfix::similarity(first1, last1, first2, last2, score_cutoff, score_cutoff); +} + +template +size_t postfix_similarity(const Sentence1& s1, const Sentence2& s2, size_t score_cutoff = 0) +{ + return detail::Postfix::similarity(s1, s2, score_cutoff, score_cutoff); +} + +template +double postfix_normalized_distance(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + double score_cutoff = 1.0) +{ + return detail::Postfix::normalized_distance(first1, last1, first2, last2, score_cutoff, score_cutoff); +} + +template +double postfix_normalized_distance(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 1.0) +{ + return detail::Postfix::normalized_distance(s1, s2, score_cutoff, score_cutoff); +} + +template +double postfix_normalized_similarity(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + double score_cutoff = 0.0) +{ + return detail::Postfix::normalized_similarity(first1, last1, first2, last2, score_cutoff, score_cutoff); +} + +template +double postfix_normalized_similarity(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 0.0) +{ + return detail::Postfix::normalized_similarity(s1, s2, score_cutoff, score_cutoff); +} + +template +struct CachedPostfix : public detail::CachedSimilarityBase, size_t, 0, + std::numeric_limits::max()> { + template + explicit CachedPostfix(const Sentence1& s1_) : CachedPostfix(detail::to_begin(s1_), detail::to_end(s1_)) + {} + + template + CachedPostfix(InputIt1 first1, InputIt1 last1) : s1(first1, last1) + {} + +private: + friend detail::CachedSimilarityBase, size_t, 0, + std::numeric_limits::max()>; + friend detail::CachedNormalizedMetricBase>; + + template + size_t maximum(const detail::Range& s2) const + { + return std::max(s1.size(), s2.size()); + } + + template + size_t _similarity(detail::Range s2, size_t score_cutoff, + [[maybe_unused]] size_t score_hint) const + { + return detail::Postfix::similarity(s1, s2, score_cutoff, score_hint); + } + + std::vector s1; +}; + +template +explicit CachedPostfix(const Sentence1& s1_) -> CachedPostfix>; + +template +CachedPostfix(InputIt1 first1, InputIt1 last1) -> CachedPostfix>; + +/**@}*/ + +} // namespace rapidfuzz + +#include + +namespace rapidfuzz::detail { + +class Prefix : public SimilarityBase::max()> { + friend SimilarityBase::max()>; + friend NormalizedMetricBase; + + template + static size_t maximum(const Range& s1, const Range& s2) + { + return std::max(s1.size(), s2.size()); + } + + template + static size_t _similarity(Range s1, Range s2, size_t score_cutoff, + [[maybe_unused]] size_t score_hint) + { + size_t dist = remove_common_prefix(s1, s2); + return (dist >= score_cutoff) ? dist : 0; + } +}; + +} // namespace rapidfuzz::detail + +namespace rapidfuzz { + +template +size_t prefix_distance(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + size_t score_cutoff = std::numeric_limits::max()) +{ + return detail::Prefix::distance(first1, last1, first2, last2, score_cutoff, score_cutoff); +} + +template +size_t prefix_distance(const Sentence1& s1, const Sentence2& s2, + size_t score_cutoff = std::numeric_limits::max()) +{ + return detail::Prefix::distance(s1, s2, score_cutoff, score_cutoff); +} + +template +size_t prefix_similarity(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + size_t score_cutoff = 0) +{ + return detail::Prefix::similarity(first1, last1, first2, last2, score_cutoff, score_cutoff); +} + +template +size_t prefix_similarity(const Sentence1& s1, const Sentence2& s2, size_t score_cutoff = 0) +{ + return detail::Prefix::similarity(s1, s2, score_cutoff, score_cutoff); +} + +template +double prefix_normalized_distance(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + double score_cutoff = 1.0) +{ + return detail::Prefix::normalized_distance(first1, last1, first2, last2, score_cutoff, score_cutoff); +} + +template +double prefix_normalized_distance(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 1.0) +{ + return detail::Prefix::normalized_distance(s1, s2, score_cutoff, score_cutoff); +} + +template +double prefix_normalized_similarity(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + double score_cutoff = 0.0) +{ + return detail::Prefix::normalized_similarity(first1, last1, first2, last2, score_cutoff, score_cutoff); +} + +template +double prefix_normalized_similarity(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 0.0) +{ + return detail::Prefix::normalized_similarity(s1, s2, score_cutoff, score_cutoff); +} + +template +struct CachedPrefix : public detail::CachedSimilarityBase, size_t, 0, + std::numeric_limits::max()> { + template + explicit CachedPrefix(const Sentence1& s1_) : CachedPrefix(detail::to_begin(s1_), detail::to_end(s1_)) + {} + + template + CachedPrefix(InputIt1 first1, InputIt1 last1) : s1(first1, last1) + {} + +private: + friend detail::CachedSimilarityBase, size_t, 0, std::numeric_limits::max()>; + friend detail::CachedNormalizedMetricBase>; + + template + size_t maximum(const detail::Range& s2) const + { + return std::max(s1.size(), s2.size()); + } + + template + size_t _similarity(detail::Range s2, size_t score_cutoff, + [[maybe_unused]] size_t score_hint) const + { + return detail::Prefix::similarity(s1, s2, score_cutoff, score_cutoff); + } + + std::vector s1; +}; + +template +explicit CachedPrefix(const Sentence1& s1_) -> CachedPrefix>; + +template +CachedPrefix(InputIt1 first1, InputIt1 last1) -> CachedPrefix>; + +/**@}*/ + +} // namespace rapidfuzz + +namespace rapidfuzz { + +namespace detail { +template +ReturnType editops_apply_impl(const Editops& ops, InputIt1 first1, InputIt1 last1, InputIt2 first2, + InputIt2 last2) +{ + auto len1 = static_cast(std::distance(first1, last1)); + auto len2 = static_cast(std::distance(first2, last2)); + + ReturnType res_str; + res_str.resize(len1 + len2); + size_t src_pos = 0; + size_t dest_pos = 0; + + for (const auto& op : ops) { + /* matches between last and current editop */ + while (src_pos < op.src_pos) { + res_str[dest_pos] = + static_cast(first1[static_cast(src_pos)]); + src_pos++; + dest_pos++; + } + + switch (op.type) { + case EditType::None: + case EditType::Replace: + res_str[dest_pos] = + static_cast(first2[static_cast(op.dest_pos)]); + src_pos++; + dest_pos++; + break; + case EditType::Insert: + res_str[dest_pos] = + static_cast(first2[static_cast(op.dest_pos)]); + dest_pos++; + break; + case EditType::Delete: src_pos++; break; + } + } + + /* matches after the last editop */ + while (src_pos < len1) { + res_str[dest_pos] = + static_cast(first1[static_cast(src_pos)]); + src_pos++; + dest_pos++; + } + + res_str.resize(dest_pos); + return res_str; +} + +template +ReturnType opcodes_apply_impl(const Opcodes& ops, InputIt1 first1, InputIt1 last1, InputIt2 first2, + InputIt2 last2) +{ + auto len1 = static_cast(std::distance(first1, last1)); + auto len2 = static_cast(std::distance(first2, last2)); + + ReturnType res_str; + res_str.resize(len1 + len2); + size_t dest_pos = 0; + + for (const auto& op : ops) { + switch (op.type) { + case EditType::None: + for (auto i = op.src_begin; i < op.src_end; ++i) { + res_str[dest_pos++] = + static_cast(first1[static_cast(i)]); + } + break; + case EditType::Replace: + case EditType::Insert: + for (auto i = op.dest_begin; i < op.dest_end; ++i) { + res_str[dest_pos++] = + static_cast(first2[static_cast(i)]); + } + break; + case EditType::Delete: break; + } + } + + res_str.resize(dest_pos); + return res_str; +} + +} // namespace detail + +template +std::basic_string editops_apply_str(const Editops& ops, InputIt1 first1, InputIt1 last1, + InputIt2 first2, InputIt2 last2) +{ + return detail::editops_apply_impl>(ops, first1, last1, first2, last2); +} + +template +std::basic_string editops_apply_str(const Editops& ops, const Sentence1& s1, const Sentence2& s2) +{ + return detail::editops_apply_impl>(ops, detail::to_begin(s1), detail::to_end(s1), + detail::to_begin(s2), detail::to_end(s2)); +} + +template +std::basic_string opcodes_apply_str(const Opcodes& ops, InputIt1 first1, InputIt1 last1, + InputIt2 first2, InputIt2 last2) +{ + return detail::opcodes_apply_impl>(ops, first1, last1, first2, last2); +} + +template +std::basic_string opcodes_apply_str(const Opcodes& ops, const Sentence1& s1, const Sentence2& s2) +{ + return detail::opcodes_apply_impl>(ops, detail::to_begin(s1), detail::to_end(s1), + detail::to_begin(s2), detail::to_end(s2)); +} + +template +std::vector editops_apply_vec(const Editops& ops, InputIt1 first1, InputIt1 last1, InputIt2 first2, + InputIt2 last2) +{ + return detail::editops_apply_impl>(ops, first1, last1, first2, last2); +} + +template +std::vector editops_apply_vec(const Editops& ops, const Sentence1& s1, const Sentence2& s2) +{ + return detail::editops_apply_impl>(ops, detail::to_begin(s1), detail::to_end(s1), + detail::to_begin(s2), detail::to_end(s2)); +} + +template +std::vector opcodes_apply_vec(const Opcodes& ops, InputIt1 first1, InputIt1 last1, InputIt2 first2, + InputIt2 last2) +{ + return detail::opcodes_apply_impl>(ops, first1, last1, first2, last2); +} + +template +std::vector opcodes_apply_vec(const Opcodes& ops, const Sentence1& s1, const Sentence2& s2) +{ + return detail::opcodes_apply_impl>(ops, detail::to_begin(s1), detail::to_end(s1), + detail::to_begin(s2), detail::to_end(s2)); +} + +} // namespace rapidfuzz + +#include +#include +#include +#include +#include +#include + +namespace rapidfuzz::detail { + +/* + * taken from https://stackoverflow.com/a/17251989/11335032 + */ +template +bool CanTypeFitValue(const U value) +{ + const intmax_t botT = intmax_t(std::numeric_limits::min()); + const intmax_t botU = intmax_t(std::numeric_limits::min()); + const uintmax_t topT = uintmax_t(std::numeric_limits::max()); + const uintmax_t topU = uintmax_t(std::numeric_limits::max()); + return !((botT > botU && value < static_cast(botT)) || (topT < topU && value > static_cast(topT))); +} + +template +struct CharSet; + +template +struct CharSet { + using UCharT1 = typename std::make_unsigned::type; + + std::array::max() + 1> m_val; + + CharSet() : m_val{} + {} + + void insert(CharT1 ch) + { + m_val[UCharT1(ch)] = true; + } + + template + bool find(CharT2 ch) const + { + if (!CanTypeFitValue(ch)) return false; + + return m_val[UCharT1(ch)]; + } +}; + +template +struct CharSet { + std::unordered_set m_val; + + CharSet() : m_val{} + {} + + void insert(CharT1 ch) + { + m_val.insert(ch); + } + + template + bool find(CharT2 ch) const + { + if (!CanTypeFitValue(ch)) return false; + + return m_val.find(CharT1(ch)) != m_val.end(); + } +}; + +} // namespace rapidfuzz::detail + +namespace rapidfuzz::fuzz { + +/** + * @defgroup Fuzz Fuzz + * A collection of string matching algorithms from FuzzyWuzzy + * @{ + */ + +/** + * @brief calculates a simple ratio between two strings + * + * @details + * @code{.cpp} + * // score is 96.55 + * double score = ratio("this is a test", "this is a test!") + * @endcode + * + * @tparam Sentence1 This is a string that can be converted to + * basic_string_view + * @tparam Sentence2 This is a string that can be converted to + * basic_string_view + * + * @param s1 string to compare with s2 (for type info check Template parameters + * above) + * @param s2 string to compare with s1 (for type info check Template parameters + * above) + * @param score_cutoff Optional argument for a score threshold between 0% and + * 100%. Matches with a lower score than this number will not be returned. + * Defaults to 0. + * + * @return returns the ratio between s1 and s2 or 0 when ratio < score_cutoff + */ +template +double ratio(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 0); + +template +double ratio(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, double score_cutoff = 0); + +#ifdef RAPIDFUZZ_SIMD +namespace experimental { +template +struct MultiRatio { +public: + MultiRatio(size_t count) : input_count(count), scorer(count) + {} + + size_t result_count() const + { + return scorer.result_count(); + } + + template + void insert(const Sentence1& s1_) + { + insert(detail::to_begin(s1_), detail::to_end(s1_)); + } + + template + void insert(InputIt1 first1, InputIt1 last1) + { + scorer.insert(first1, last1); + } + + template + void similarity(double* scores, size_t score_count, InputIt2 first2, InputIt2 last2, + double score_cutoff = 0.0) const + { + similarity(scores, score_count, detail::Range(first2, last2), score_cutoff); + } + + template + void similarity(double* scores, size_t score_count, const Sentence2& s2, double score_cutoff = 0) const + { + scorer.normalized_similarity(scores, score_count, s2, score_cutoff / 100.0); + + for (size_t i = 0; i < input_count; ++i) + scores[i] *= 100.0; + } + +private: + size_t input_count; + rapidfuzz::experimental::MultiIndel scorer; +}; +} /* namespace experimental */ +#endif + +// TODO documentation +template +struct CachedRatio { + template + CachedRatio(InputIt1 first1, InputIt1 last1) : cached_indel(first1, last1) + {} + + template + CachedRatio(const Sentence1& s1) : cached_indel(s1) + {} + + template + double similarity(InputIt2 first2, InputIt2 last2, double score_cutoff = 0.0, + double score_hint = 0.0) const; + + template + double similarity(const Sentence2& s2, double score_cutoff = 0.0, double score_hint = 0.0) const; + + // private: + CachedIndel cached_indel; +}; + +template +CachedRatio(const Sentence1& s1) -> CachedRatio>; + +template +CachedRatio(InputIt1 first1, InputIt1 last1) -> CachedRatio>; + +template +ScoreAlignment partial_ratio_alignment(InputIt1 first1, InputIt1 last1, InputIt2 first2, + InputIt2 last2, double score_cutoff = 0); + +template +ScoreAlignment partial_ratio_alignment(const Sentence1& s1, const Sentence2& s2, + double score_cutoff = 0); + +/** + * @brief calculates the fuzz::ratio of the optimal string alignment + * + * @details + * test @cite hyrro_2004 @cite wagner_fischer_1974 + * @code{.cpp} + * // score is 100 + * double score = partial_ratio("this is a test", "this is a test!") + * @endcode + * + * @tparam Sentence1 This is a string that can be converted to + * basic_string_view + * @tparam Sentence2 This is a string that can be converted to + * basic_string_view + * + * @param s1 string to compare with s2 (for type info check Template parameters + * above) + * @param s2 string to compare with s1 (for type info check Template parameters + * above) + * @param score_cutoff Optional argument for a score threshold between 0% and + * 100%. Matches with a lower score than this number will not be returned. + * Defaults to 0. + * + * @return returns the ratio between s1 and s2 or 0 when ratio < score_cutoff + */ +template +double partial_ratio(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 0); + +template +double partial_ratio(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + double score_cutoff = 0); + +// todo add real implementation +template +struct CachedPartialRatio { + template + friend struct CachedWRatio; + + template + CachedPartialRatio(InputIt1 first1, InputIt1 last1); + + template + explicit CachedPartialRatio(const Sentence1& s1_) + : CachedPartialRatio(detail::to_begin(s1_), detail::to_end(s1_)) + {} + + template + double similarity(InputIt2 first2, InputIt2 last2, double score_cutoff = 0.0, + double score_hint = 0.0) const; + + template + double similarity(const Sentence2& s2, double score_cutoff = 0.0, double score_hint = 0.0) const; + +private: + std::vector s1; + rapidfuzz::detail::CharSet s1_char_set; + CachedRatio cached_ratio; +}; + +template +explicit CachedPartialRatio(const Sentence1& s1) -> CachedPartialRatio>; + +template +CachedPartialRatio(InputIt1 first1, InputIt1 last1) -> CachedPartialRatio>; + +/** + * @brief Sorts the words in the strings and calculates the fuzz::ratio between + * them + * + * @details + * @code{.cpp} + * // score is 100 + * double score = token_sort_ratio("fuzzy wuzzy was a bear", "wuzzy fuzzy was a + * bear") + * @endcode + * + * @tparam Sentence1 This is a string that can be converted to + * basic_string_view + * @tparam Sentence2 This is a string that can be converted to + * basic_string_view + * + * @param s1 string to compare with s2 (for type info check Template parameters + * above) + * @param s2 string to compare with s1 (for type info check Template parameters + * above) + * @param score_cutoff Optional argument for a score threshold between 0% and + * 100%. Matches with a lower score than this number will not be returned. + * Defaults to 0. + * + * @return returns the ratio between s1 and s2 or 0 when ratio < score_cutoff + */ +template +double token_sort_ratio(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 0); + +template +double token_sort_ratio(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + double score_cutoff = 0); + +#ifdef RAPIDFUZZ_SIMD +namespace experimental { +template +struct MultiTokenSortRatio { +public: + MultiTokenSortRatio(size_t count) : scorer(count) + {} + + size_t result_count() const + { + return scorer.result_count(); + } + + template + void insert(const Sentence1& s1_) + { + insert(detail::to_begin(s1_), detail::to_end(s1_)); + } + + template + void insert(InputIt1 first1, InputIt1 last1) + { + scorer.insert(detail::sorted_split(first1, last1).join()); + } + + template + void similarity(double* scores, size_t score_count, InputIt2 first2, InputIt2 last2, + double score_cutoff = 0.0) const + { + scorer.similarity(scores, score_count, detail::sorted_split(first2, last2).join(), score_cutoff); + } + + template + void similarity(double* scores, size_t score_count, const Sentence2& s2, double score_cutoff = 0) const + { + similarity(scores, score_count, detail::to_begin(s2), detail::to_end(s2), score_cutoff); + } + +private: + MultiRatio scorer; +}; +} /* namespace experimental */ +#endif + +// todo CachedRatio speed for equal strings vs original implementation +// TODO documentation +template +struct CachedTokenSortRatio { + template + CachedTokenSortRatio(InputIt1 first1, InputIt1 last1) + : s1_sorted(detail::sorted_split(first1, last1).join()), cached_ratio(s1_sorted) + {} + + template + explicit CachedTokenSortRatio(const Sentence1& s1) + : CachedTokenSortRatio(detail::to_begin(s1), detail::to_end(s1)) + {} + + template + double similarity(InputIt2 first2, InputIt2 last2, double score_cutoff = 0.0, + double score_hint = 0.0) const; + + template + double similarity(const Sentence2& s2, double score_cutoff = 0.0, double score_hint = 0.0) const; + +private: + std::vector s1_sorted; + CachedRatio cached_ratio; +}; + +template +explicit CachedTokenSortRatio(const Sentence1& s1) -> CachedTokenSortRatio>; + +template +CachedTokenSortRatio(InputIt1 first1, InputIt1 last1) -> CachedTokenSortRatio>; + +/** + * @brief Sorts the words in the strings and calculates the fuzz::partial_ratio + * between them + * + * + * @tparam Sentence1 This is a string that can be converted to + * basic_string_view + * @tparam Sentence2 This is a string that can be converted to + * basic_string_view + * + * @param s1 string to compare with s2 (for type info check Template parameters + * above) + * @param s2 string to compare with s1 (for type info check Template parameters + * above) + * @param score_cutoff Optional argument for a score threshold between 0% and + * 100%. Matches with a lower score than this number will not be returned. + * Defaults to 0. + * + * @return returns the ratio between s1 and s2 or 0 when ratio < score_cutoff + */ +template +double partial_token_sort_ratio(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 0); + +template +double partial_token_sort_ratio(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + double score_cutoff = 0); + +// TODO documentation +template +struct CachedPartialTokenSortRatio { + template + CachedPartialTokenSortRatio(InputIt1 first1, InputIt1 last1) + : s1_sorted(detail::sorted_split(first1, last1).join()), cached_partial_ratio(s1_sorted) + {} + + template + explicit CachedPartialTokenSortRatio(const Sentence1& s1) + : CachedPartialTokenSortRatio(detail::to_begin(s1), detail::to_end(s1)) + {} + + template + double similarity(InputIt2 first2, InputIt2 last2, double score_cutoff = 0.0, + double score_hint = 0.0) const; + + template + double similarity(const Sentence2& s2, double score_cutoff = 0.0, double score_hint = 0.0) const; + +private: + std::vector s1_sorted; + CachedPartialRatio cached_partial_ratio; +}; + +template +explicit CachedPartialTokenSortRatio(const Sentence1& s1) + -> CachedPartialTokenSortRatio>; + +template +CachedPartialTokenSortRatio(InputIt1 first1, + InputIt1 last1) -> CachedPartialTokenSortRatio>; + +/** + * @brief Compares the words in the strings based on unique and common words + * between them using fuzz::ratio + * + * @details + * @code{.cpp} + * // score1 is 83.87 + * double score1 = token_sort_ratio("fuzzy was a bear", "fuzzy fuzzy was a + * bear") + * // score2 is 100 + * double score2 = token_set_ratio("fuzzy was a bear", "fuzzy fuzzy was a bear") + * @endcode + * + * @tparam Sentence1 This is a string that can be converted to + * basic_string_view + * @tparam Sentence2 This is a string that can be converted to + * basic_string_view + * + * @param s1 string to compare with s2 (for type info check Template parameters + * above) + * @param s2 string to compare with s1 (for type info check Template parameters + * above) + * @param score_cutoff Optional argument for a score threshold between 0% and + * 100%. Matches with a lower score than this number will not be returned. + * Defaults to 0. + * + * @return returns the ratio between s1 and s2 or 0 when ratio < score_cutoff + */ +template +double token_set_ratio(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 0); + +template +double token_set_ratio(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + double score_cutoff = 0); + +// TODO documentation +template +struct CachedTokenSetRatio { + template + CachedTokenSetRatio(InputIt1 first1, InputIt1 last1) + : s1(first1, last1), tokens_s1(detail::sorted_split(std::begin(s1), std::end(s1))) + {} + + template + explicit CachedTokenSetRatio(const Sentence1& s1_) + : CachedTokenSetRatio(detail::to_begin(s1_), detail::to_end(s1_)) + {} + + template + double similarity(InputIt2 first2, InputIt2 last2, double score_cutoff = 0.0, + double score_hint = 0.0) const; + + template + double similarity(const Sentence2& s2, double score_cutoff = 0.0, double score_hint = 0.0) const; + +private: + std::vector s1; + detail::SplittedSentenceView::iterator> tokens_s1; +}; + +template +explicit CachedTokenSetRatio(const Sentence1& s1) -> CachedTokenSetRatio>; + +template +CachedTokenSetRatio(InputIt1 first1, InputIt1 last1) -> CachedTokenSetRatio>; + +/** + * @brief Compares the words in the strings based on unique and common words + * between them using fuzz::partial_ratio + * + * @tparam Sentence1 This is a string that can be converted to + * basic_string_view + * @tparam Sentence2 This is a string that can be converted to + * basic_string_view + * + * @param s1 string to compare with s2 (for type info check Template parameters + * above) + * @param s2 string to compare with s1 (for type info check Template parameters + * above) + * @param score_cutoff Optional argument for a score threshold between 0% and + * 100%. Matches with a lower score than this number will not be returned. + * Defaults to 0. + * + * @return returns the ratio between s1 and s2 or 0 when ratio < score_cutoff + */ +template +double partial_token_set_ratio(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 0); + +template +double partial_token_set_ratio(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + double score_cutoff = 0); + +// TODO documentation +template +struct CachedPartialTokenSetRatio { + template + CachedPartialTokenSetRatio(InputIt1 first1, InputIt1 last1) + : s1(first1, last1), tokens_s1(detail::sorted_split(std::begin(s1), std::end(s1))) + {} + + template + explicit CachedPartialTokenSetRatio(const Sentence1& s1_) + : CachedPartialTokenSetRatio(detail::to_begin(s1_), detail::to_end(s1_)) + {} + + template + double similarity(InputIt2 first2, InputIt2 last2, double score_cutoff = 0.0, + double score_hint = 0.0) const; + + template + double similarity(const Sentence2& s2, double score_cutoff = 0.0, double score_hint = 0.0) const; + +private: + std::vector s1; + detail::SplittedSentenceView::iterator> tokens_s1; +}; + +template +explicit CachedPartialTokenSetRatio(const Sentence1& s1) -> CachedPartialTokenSetRatio>; + +template +CachedPartialTokenSetRatio(InputIt1 first1, + InputIt1 last1) -> CachedPartialTokenSetRatio>; + +/** + * @brief Helper method that returns the maximum of fuzz::token_set_ratio and + * fuzz::token_sort_ratio (faster than manually executing the two functions) + * + * @tparam Sentence1 This is a string that can be converted to + * basic_string_view + * @tparam Sentence2 This is a string that can be converted to + * basic_string_view + * + * @param s1 string to compare with s2 (for type info check Template parameters + * above) + * @param s2 string to compare with s1 (for type info check Template parameters + * above) + * @param score_cutoff Optional argument for a score threshold between 0% and + * 100%. Matches with a lower score than this number will not be returned. + * Defaults to 0. + * + * @return returns the ratio between s1 and s2 or 0 when ratio < score_cutoff + */ +template +double token_ratio(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 0); + +template +double token_ratio(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, double score_cutoff = 0); + +// todo add real implementation +template +struct CachedTokenRatio { + template + CachedTokenRatio(InputIt1 first1, InputIt1 last1) + : s1(first1, last1), + s1_tokens(detail::sorted_split(std::begin(s1), std::end(s1))), + s1_sorted(s1_tokens.join()), + cached_ratio_s1_sorted(s1_sorted) + {} + + template + explicit CachedTokenRatio(const Sentence1& s1_) + : CachedTokenRatio(detail::to_begin(s1_), detail::to_end(s1_)) + {} + + template + double similarity(InputIt2 first2, InputIt2 last2, double score_cutoff = 0.0, + double score_hint = 0.0) const; + + template + double similarity(const Sentence2& s2, double score_cutoff = 0.0, double score_hint = 0.0) const; + +private: + std::vector s1; + detail::SplittedSentenceView::iterator> s1_tokens; + std::vector s1_sorted; + CachedRatio cached_ratio_s1_sorted; +}; + +template +explicit CachedTokenRatio(const Sentence1& s1) -> CachedTokenRatio>; + +template +CachedTokenRatio(InputIt1 first1, InputIt1 last1) -> CachedTokenRatio>; + +/** + * @brief Helper method that returns the maximum of + * fuzz::partial_token_set_ratio and fuzz::partial_token_sort_ratio (faster than + * manually executing the two functions) + * + * @tparam Sentence1 This is a string that can be converted to + * basic_string_view + * @tparam Sentence2 This is a string that can be converted to + * basic_string_view + * + * @param s1 string to compare with s2 (for type info check Template parameters + * above) + * @param s2 string to compare with s1 (for type info check Template parameters + * above) + * @param score_cutoff Optional argument for a score threshold between 0% and + * 100%. Matches with a lower score than this number will not be returned. + * Defaults to 0. + * + * @return returns the ratio between s1 and s2 or 0 when ratio < score_cutoff + */ +template +double partial_token_ratio(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 0); + +template +double partial_token_ratio(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + double score_cutoff = 0); + +// todo add real implementation +template +struct CachedPartialTokenRatio { + template + CachedPartialTokenRatio(InputIt1 first1, InputIt1 last1) + : s1(first1, last1), + tokens_s1(detail::sorted_split(std::begin(s1), std::end(s1))), + s1_sorted(tokens_s1.join()) + {} + + template + explicit CachedPartialTokenRatio(const Sentence1& s1_) + : CachedPartialTokenRatio(detail::to_begin(s1_), detail::to_end(s1_)) + {} + + template + double similarity(InputIt2 first2, InputIt2 last2, double score_cutoff = 0.0, + double score_hint = 0.0) const; + + template + double similarity(const Sentence2& s2, double score_cutoff = 0.0, double score_hint = 0.0) const; + +private: + std::vector s1; + detail::SplittedSentenceView::iterator> tokens_s1; + std::vector s1_sorted; +}; + +template +explicit CachedPartialTokenRatio(const Sentence1& s1) -> CachedPartialTokenRatio>; + +template +CachedPartialTokenRatio(InputIt1 first1, InputIt1 last1) -> CachedPartialTokenRatio>; + +/** + * @brief Calculates a weighted ratio based on the other ratio algorithms + * + * @details + * @todo add a detailed description + * + * @tparam Sentence1 This is a string that can be converted to + * basic_string_view + * @tparam Sentence2 This is a string that can be converted to + * basic_string_view + * + * @param s1 string to compare with s2 (for type info check Template parameters + * above) + * @param s2 string to compare with s1 (for type info check Template parameters + * above) + * @param score_cutoff Optional argument for a score threshold between 0% and + * 100%. Matches with a lower score than this number will not be returned. + * Defaults to 0. + * + * @return returns the ratio between s1 and s2 or 0 when ratio < score_cutoff + */ +template +double WRatio(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 0); + +template +double WRatio(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, double score_cutoff = 0); + +// todo add real implementation +template +struct CachedWRatio { + template + explicit CachedWRatio(InputIt1 first1, InputIt1 last1); + + template + CachedWRatio(const Sentence1& s1_) : CachedWRatio(detail::to_begin(s1_), detail::to_end(s1_)) + {} + + template + double similarity(InputIt2 first2, InputIt2 last2, double score_cutoff = 0.0, + double score_hint = 0.0) const; + + template + double similarity(const Sentence2& s2, double score_cutoff = 0.0, double score_hint = 0.0) const; + +private: + // todo somehow implement this using other ratios with creating PatternMatchVector + // multiple times + std::vector s1; + CachedPartialRatio cached_partial_ratio; + detail::SplittedSentenceView::iterator> tokens_s1; + std::vector s1_sorted; + rapidfuzz::detail::BlockPatternMatchVector blockmap_s1_sorted; +}; + +template +explicit CachedWRatio(const Sentence1& s1) -> CachedWRatio>; + +template +CachedWRatio(InputIt1 first1, InputIt1 last1) -> CachedWRatio>; + +/** + * @brief Calculates a quick ratio between two strings using fuzz.ratio + * + * @details + * @todo add a detailed description + * + * @tparam Sentence1 This is a string that can be converted to + * basic_string_view + * @tparam Sentence2 This is a string that can be converted to + * basic_string_view + * + * @param s1 string to compare with s2 (for type info check Template parameters + * above) + * @param s2 string to compare with s1 (for type info check Template parameters + * above) + * @param score_cutoff Optional argument for a score threshold between 0% and + * 100%. Matches with a lower score than this number will not be returned. + * Defaults to 0. + * + * @return returns the ratio between s1 and s2 or 0 when ratio < score_cutoff + */ +template +double QRatio(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 0); + +template +double QRatio(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, double score_cutoff = 0); + +#ifdef RAPIDFUZZ_SIMD +namespace experimental { +template +struct MultiQRatio { +public: + MultiQRatio(size_t count) : scorer(count) + {} + + size_t result_count() const + { + return scorer.result_count(); + } + + template + void insert(const Sentence1& s1_) + { + insert(detail::to_begin(s1_), detail::to_end(s1_)); + } + + template + void insert(InputIt1 first1, InputIt1 last1) + { + scorer.insert(first1, last1); + str_lens.push_back(static_cast(std::distance(first1, last1))); + } + + template + void similarity(double* scores, size_t score_count, InputIt2 first2, InputIt2 last2, + double score_cutoff = 0.0) const + { + similarity(scores, score_count, detail::Range(first2, last2), score_cutoff); + } + + template + void similarity(double* scores, size_t score_count, const Sentence2& s2, double score_cutoff = 0) const + { + rapidfuzz::detail::Range s2_(s2); + if (s2_.empty()) { + for (size_t i = 0; i < str_lens.size(); ++i) + scores[i] = 0; + + return; + } + + scorer.similarity(scores, score_count, s2, score_cutoff); + + for (size_t i = 0; i < str_lens.size(); ++i) + if (str_lens[i] == 0) scores[i] = 0; + } + +private: + std::vector str_lens; + MultiRatio scorer; +}; +} /* namespace experimental */ +#endif + +template +struct CachedQRatio { + template + CachedQRatio(InputIt1 first1, InputIt1 last1) : s1(first1, last1), cached_ratio(first1, last1) + {} + + template + explicit CachedQRatio(const Sentence1& s1_) : CachedQRatio(detail::to_begin(s1_), detail::to_end(s1_)) + {} + + template + double similarity(InputIt2 first2, InputIt2 last2, double score_cutoff = 0.0, + double score_hint = 0.0) const; + + template + double similarity(const Sentence2& s2, double score_cutoff = 0.0, double score_hint = 0.0) const; + +private: + std::vector s1; + CachedRatio cached_ratio; +}; + +template +explicit CachedQRatio(const Sentence1& s1) -> CachedQRatio>; + +template +CachedQRatio(InputIt1 first1, InputIt1 last1) -> CachedQRatio>; + +/**@}*/ + +} // namespace rapidfuzz::fuzz + +#include + +#include +#include +#include +#include +#include + +namespace rapidfuzz::fuzz { + +/********************************************** + * ratio + *********************************************/ + +template +double ratio(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, double score_cutoff) +{ + return ratio(detail::Range(first1, last1), detail::Range(first2, last2), score_cutoff); +} + +template +double ratio(const Sentence1& s1, const Sentence2& s2, const double score_cutoff) +{ + return indel_normalized_similarity(s1, s2, score_cutoff / 100) * 100; +} + +template +template +double CachedRatio::similarity(InputIt2 first2, InputIt2 last2, double score_cutoff, + double score_hint) const +{ + return similarity(detail::Range(first2, last2), score_cutoff, score_hint); +} + +template +template +double CachedRatio::similarity(const Sentence2& s2, double score_cutoff, double score_hint) const +{ + return cached_indel.normalized_similarity(s2, score_cutoff / 100, score_hint / 100) * 100; +} + +/********************************************** + * partial_ratio + *********************************************/ + +namespace fuzz_detail { + +static constexpr double norm_distance(size_t dist, size_t lensum, double score_cutoff = 0) +{ + double score = + (lensum > 0) ? (100.0 - 100.0 * static_cast(dist) / static_cast(lensum)) : 100.0; + + return (score >= score_cutoff) ? score : 0; +} + +static inline size_t score_cutoff_to_distance(double score_cutoff, size_t lensum) +{ + return static_cast(std::ceil(static_cast(lensum) * (1.0 - score_cutoff / 100))); +} + +template +ScoreAlignment +partial_ratio_impl(const detail::Range& s1, const detail::Range& s2, + const CachedRatio& cached_ratio, + const detail::CharSet>& s1_char_set, double score_cutoff) +{ + ScoreAlignment res; + size_t len1 = s1.size(); + size_t len2 = s2.size(); + res.src_start = 0; + res.src_end = len1; + res.dest_start = 0; + res.dest_end = len1; + + if (len2 > len1) { + size_t maximum = len1 * 2; + double norm_cutoff_sim = rapidfuzz::detail::NormSim_to_NormDist(score_cutoff / 100); + size_t cutoff_dist = static_cast(std::ceil(static_cast(maximum) * norm_cutoff_sim)); + size_t best_dist = std::numeric_limits::max(); + std::vector scores(len2 - len1, std::numeric_limits::max()); + std::vector> windows = {{0, len2 - len1 - 1}}; + std::vector> new_windows; + + while (!windows.empty()) { + for (const auto& window : windows) { + auto subseq1_first = s2.begin() + static_cast(window.first); + auto subseq2_first = s2.begin() + static_cast(window.second); + detail::Range subseq1(subseq1_first, subseq1_first + static_cast(len1)); + detail::Range subseq2(subseq2_first, subseq2_first + static_cast(len1)); + + if (scores[window.first] == std::numeric_limits::max()) { + scores[window.first] = cached_ratio.cached_indel.distance(subseq1); + if (scores[window.first] < cutoff_dist) { + cutoff_dist = best_dist = scores[window.first]; + res.dest_start = window.first; + res.dest_end = window.first + len1; + if (best_dist == 0) { + res.score = 100; + return res; + } + } + } + if (scores[window.second] == std::numeric_limits::max()) { + scores[window.second] = cached_ratio.cached_indel.distance(subseq2); + if (scores[window.second] < cutoff_dist) { + cutoff_dist = best_dist = scores[window.second]; + res.dest_start = window.second; + res.dest_end = window.second + len1; + if (best_dist == 0) { + res.score = 100; + return res; + } + } + } + + size_t cell_diff = window.second - window.first; + if (cell_diff == 1) continue; + + /* find the minimum score possible in the range first <-> last */ + size_t known_edits = detail::abs_diff(scores[window.first], scores[window.second]); + /* half of the cells that are not needed for known_edits can lead to a better score */ + size_t max_score_improvement = (cell_diff - known_edits / 2) / 2 * 2; + ptrdiff_t min_score = + static_cast(std::min(scores[window.first], scores[window.second])) - + static_cast(max_score_improvement); + if (min_score < static_cast(cutoff_dist)) { + size_t center = cell_diff / 2; + new_windows.emplace_back(window.first, window.first + center); + new_windows.emplace_back(window.first + center, window.second); + } + } + + std::swap(windows, new_windows); + new_windows.clear(); + } + + double score = 1.0 - (static_cast(best_dist) / static_cast(maximum)); + score *= 100; + if (score >= score_cutoff) score_cutoff = res.score = score; + } + + for (size_t i = 1; i < len1; ++i) { + rapidfuzz::detail::Range subseq(s2.begin(), s2.begin() + static_cast(i)); + if (!s1_char_set.find(subseq.back())) continue; + + double ls_ratio = cached_ratio.similarity(subseq, score_cutoff); + if (ls_ratio > res.score) { + score_cutoff = res.score = ls_ratio; + res.dest_start = 0; + res.dest_end = i; + if (res.score == 100.0) return res; + } + } + + for (size_t i = len2 - len1; i < len2; ++i) { + rapidfuzz::detail::Range subseq(s2.begin() + static_cast(i), s2.end()); + if (!s1_char_set.find(subseq.front())) continue; + + double ls_ratio = cached_ratio.similarity(subseq, score_cutoff); + if (ls_ratio > res.score) { + score_cutoff = res.score = ls_ratio; + res.dest_start = i; + res.dest_end = len2; + if (res.score == 100.0) return res; + } + } + + return res; +} + +template > +ScoreAlignment partial_ratio_impl(const detail::Range& s1, + const detail::Range& s2, double score_cutoff) +{ + CachedRatio cached_ratio(s1); + + detail::CharSet s1_char_set; + for (auto ch : s1) + s1_char_set.insert(ch); + + return partial_ratio_impl(s1, s2, cached_ratio, s1_char_set, score_cutoff); +} + +} // namespace fuzz_detail + +template +ScoreAlignment partial_ratio_alignment(InputIt1 first1, InputIt1 last1, InputIt2 first2, + InputIt2 last2, double score_cutoff) +{ + size_t len1 = static_cast(std::distance(first1, last1)); + size_t len2 = static_cast(std::distance(first2, last2)); + + if (len1 > len2) { + ScoreAlignment result = partial_ratio_alignment(first2, last2, first1, last1, score_cutoff); + std::swap(result.src_start, result.dest_start); + std::swap(result.src_end, result.dest_end); + return result; + } + + if (score_cutoff > 100) return ScoreAlignment(0, 0, len1, 0, len1); + + if (!len1 || !len2) + return ScoreAlignment(static_cast(len1 == len2) * 100.0, 0, len1, 0, len1); + + auto s1 = detail::Range(first1, last1); + auto s2 = detail::Range(first2, last2); + + auto alignment = fuzz_detail::partial_ratio_impl(s1, s2, score_cutoff); + if (alignment.score != 100 && s1.size() == s2.size()) { + score_cutoff = std::max(score_cutoff, alignment.score); + auto alignment2 = fuzz_detail::partial_ratio_impl(s2, s1, score_cutoff); + if (alignment2.score > alignment.score) { + std::swap(alignment2.src_start, alignment2.dest_start); + std::swap(alignment2.src_end, alignment2.dest_end); + return alignment2; + } + } + + return alignment; +} + +template +ScoreAlignment partial_ratio_alignment(const Sentence1& s1, const Sentence2& s2, double score_cutoff) +{ + return partial_ratio_alignment(detail::to_begin(s1), detail::to_end(s1), detail::to_begin(s2), + detail::to_end(s2), score_cutoff); +} + +template +double partial_ratio(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, double score_cutoff) +{ + return partial_ratio_alignment(first1, last1, first2, last2, score_cutoff).score; +} + +template +double partial_ratio(const Sentence1& s1, const Sentence2& s2, double score_cutoff) +{ + return partial_ratio_alignment(s1, s2, score_cutoff).score; +} + +template +template +CachedPartialRatio::CachedPartialRatio(InputIt1 first1, InputIt1 last1) + : s1(first1, last1), cached_ratio(first1, last1) +{ + for (const auto& ch : s1) + s1_char_set.insert(ch); +} + +template +template +double CachedPartialRatio::similarity(InputIt2 first2, InputIt2 last2, double score_cutoff, + [[maybe_unused]] double score_hint) const +{ + size_t len1 = s1.size(); + size_t len2 = static_cast(std::distance(first2, last2)); + + if (len1 > len2) + return partial_ratio(detail::to_begin(s1), detail::to_end(s1), first2, last2, score_cutoff); + + if (score_cutoff > 100) return 0; + + if (!len1 || !len2) return static_cast(len1 == len2) * 100.0; + + auto s1_ = detail::Range(s1); + auto s2 = detail::Range(first2, last2); + + double score = fuzz_detail::partial_ratio_impl(s1_, s2, cached_ratio, s1_char_set, score_cutoff).score; + if (score != 100 && s1_.size() == s2.size()) { + score_cutoff = std::max(score_cutoff, score); + double score2 = fuzz_detail::partial_ratio_impl(s2, s1_, score_cutoff).score; + if (score2 > score) return score2; + } + + return score; +} + +template +template +double CachedPartialRatio::similarity(const Sentence2& s2, double score_cutoff, + [[maybe_unused]] double score_hint) const +{ + return similarity(detail::to_begin(s2), detail::to_end(s2), score_cutoff); +} + +/********************************************** + * token_sort_ratio + *********************************************/ +template +double token_sort_ratio(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, double score_cutoff) +{ + if (score_cutoff > 100) return 0; + + return ratio(detail::sorted_split(first1, last1).join(), detail::sorted_split(first2, last2).join(), + score_cutoff); +} + +template +double token_sort_ratio(const Sentence1& s1, const Sentence2& s2, double score_cutoff) +{ + return token_sort_ratio(detail::to_begin(s1), detail::to_end(s1), detail::to_begin(s2), + detail::to_end(s2), score_cutoff); +} + +template +template +double CachedTokenSortRatio::similarity(InputIt2 first2, InputIt2 last2, double score_cutoff, + [[maybe_unused]] double score_hint) const +{ + if (score_cutoff > 100) return 0; + + return cached_ratio.similarity(detail::sorted_split(first2, last2).join(), score_cutoff); +} + +template +template +double CachedTokenSortRatio::similarity(const Sentence2& s2, double score_cutoff, + [[maybe_unused]] double score_hint) const +{ + return similarity(detail::to_begin(s2), detail::to_end(s2), score_cutoff); +} + +/********************************************** + * partial_token_sort_ratio + *********************************************/ + +template +double partial_token_sort_ratio(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + double score_cutoff) +{ + if (score_cutoff > 100) return 0; + + return partial_ratio(detail::sorted_split(first1, last1).join(), + detail::sorted_split(first2, last2).join(), score_cutoff); +} + +template +double partial_token_sort_ratio(const Sentence1& s1, const Sentence2& s2, double score_cutoff) +{ + return partial_token_sort_ratio(detail::to_begin(s1), detail::to_end(s1), detail::to_begin(s2), + detail::to_end(s2), score_cutoff); +} + +template +template +double CachedPartialTokenSortRatio::similarity(InputIt2 first2, InputIt2 last2, double score_cutoff, + [[maybe_unused]] double score_hint) const +{ + if (score_cutoff > 100) return 0; + + return cached_partial_ratio.similarity(detail::sorted_split(first2, last2).join(), score_cutoff); +} + +template +template +double CachedPartialTokenSortRatio::similarity(const Sentence2& s2, double score_cutoff, + [[maybe_unused]] double score_hint) const +{ + return similarity(detail::to_begin(s2), detail::to_end(s2), score_cutoff); +} + +/********************************************** + * token_set_ratio + *********************************************/ + +namespace fuzz_detail { +template +double token_set_ratio(const rapidfuzz::detail::SplittedSentenceView& tokens_a, + const rapidfuzz::detail::SplittedSentenceView& tokens_b, + const double score_cutoff) +{ + /* in FuzzyWuzzy this returns 0. For sake of compatibility return 0 here as well + * see https://github.com/rapidfuzz/RapidFuzz/issues/110 */ + if (tokens_a.empty() || tokens_b.empty()) return 0; + + auto decomposition = detail::set_decomposition(tokens_a, tokens_b); + auto intersect = decomposition.intersection; + auto diff_ab = decomposition.difference_ab; + auto diff_ba = decomposition.difference_ba; + + // one sentence is part of the other one + if (!intersect.empty() && (diff_ab.empty() || diff_ba.empty())) return 100; + + auto diff_ab_joined = diff_ab.join(); + auto diff_ba_joined = diff_ba.join(); + + size_t ab_len = diff_ab_joined.size(); + size_t ba_len = diff_ba_joined.size(); + size_t sect_len = intersect.length(); + + // string length sect+ab <-> sect and sect+ba <-> sect + size_t sect_ab_len = sect_len + bool(sect_len) + ab_len; + size_t sect_ba_len = sect_len + bool(sect_len) + ba_len; + + double result = 0; + size_t cutoff_distance = score_cutoff_to_distance(score_cutoff, sect_ab_len + sect_ba_len); + size_t dist = indel_distance(diff_ab_joined, diff_ba_joined, cutoff_distance); + + if (dist <= cutoff_distance) result = norm_distance(dist, sect_ab_len + sect_ba_len, score_cutoff); + + // exit early since the other ratios are 0 + if (!sect_len) return result; + + // levenshtein distance sect+ab <-> sect and sect+ba <-> sect + // since only sect is similar in them the distance can be calculated based on + // the length difference + size_t sect_ab_dist = bool(sect_len) + ab_len; + double sect_ab_ratio = norm_distance(sect_ab_dist, sect_len + sect_ab_len, score_cutoff); + + size_t sect_ba_dist = bool(sect_len) + ba_len; + double sect_ba_ratio = norm_distance(sect_ba_dist, sect_len + sect_ba_len, score_cutoff); + + return std::max({result, sect_ab_ratio, sect_ba_ratio}); +} +} // namespace fuzz_detail + +template +double token_set_ratio(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, double score_cutoff) +{ + if (score_cutoff > 100) return 0; + + return fuzz_detail::token_set_ratio(detail::sorted_split(first1, last1), + detail::sorted_split(first2, last2), score_cutoff); +} + +template +double token_set_ratio(const Sentence1& s1, const Sentence2& s2, double score_cutoff) +{ + return token_set_ratio(detail::to_begin(s1), detail::to_end(s1), detail::to_begin(s2), detail::to_end(s2), + score_cutoff); +} + +template +template +double CachedTokenSetRatio::similarity(InputIt2 first2, InputIt2 last2, double score_cutoff, + [[maybe_unused]] double score_hint) const +{ + if (score_cutoff > 100) return 0; + + return fuzz_detail::token_set_ratio(tokens_s1, detail::sorted_split(first2, last2), score_cutoff); +} + +template +template +double CachedTokenSetRatio::similarity(const Sentence2& s2, double score_cutoff, + [[maybe_unused]] double score_hint) const +{ + return similarity(detail::to_begin(s2), detail::to_end(s2), score_cutoff); +} + +/********************************************** + * partial_token_set_ratio + *********************************************/ + +namespace fuzz_detail { +template +double partial_token_set_ratio(const rapidfuzz::detail::SplittedSentenceView& tokens_a, + const rapidfuzz::detail::SplittedSentenceView& tokens_b, + const double score_cutoff) +{ + /* in FuzzyWuzzy this returns 0. For sake of compatibility return 0 here as well + * see https://github.com/rapidfuzz/RapidFuzz/issues/110 */ + if (tokens_a.empty() || tokens_b.empty()) return 0; + + auto decomposition = detail::set_decomposition(tokens_a, tokens_b); + + // exit early when there is a common word in both sequences + if (!decomposition.intersection.empty()) return 100; + + return partial_ratio(decomposition.difference_ab.join(), decomposition.difference_ba.join(), + score_cutoff); +} +} // namespace fuzz_detail + +template +double partial_token_set_ratio(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + double score_cutoff) +{ + if (score_cutoff > 100) return 0; + + return fuzz_detail::partial_token_set_ratio(detail::sorted_split(first1, last1), + detail::sorted_split(first2, last2), score_cutoff); +} + +template +double partial_token_set_ratio(const Sentence1& s1, const Sentence2& s2, double score_cutoff) +{ + return partial_token_set_ratio(detail::to_begin(s1), detail::to_end(s1), detail::to_begin(s2), + detail::to_end(s2), score_cutoff); +} + +template +template +double CachedPartialTokenSetRatio::similarity(InputIt2 first2, InputIt2 last2, double score_cutoff, + [[maybe_unused]] double score_hint) const +{ + if (score_cutoff > 100) return 0; + + return fuzz_detail::partial_token_set_ratio(tokens_s1, detail::sorted_split(first2, last2), score_cutoff); +} + +template +template +double CachedPartialTokenSetRatio::similarity(const Sentence2& s2, double score_cutoff, + [[maybe_unused]] double score_hint) const +{ + return similarity(detail::to_begin(s2), detail::to_end(s2), score_cutoff); +} + +/********************************************** + * token_ratio + *********************************************/ + +template +double token_ratio(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, double score_cutoff) +{ + if (score_cutoff > 100) return 0; + + auto tokens_a = detail::sorted_split(first1, last1); + auto tokens_b = detail::sorted_split(first2, last2); + + auto decomposition = detail::set_decomposition(tokens_a, tokens_b); + auto intersect = decomposition.intersection; + auto diff_ab = decomposition.difference_ab; + auto diff_ba = decomposition.difference_ba; + + if (!intersect.empty() && (diff_ab.empty() || diff_ba.empty())) return 100; + + auto diff_ab_joined = diff_ab.join(); + auto diff_ba_joined = diff_ba.join(); + + size_t ab_len = diff_ab_joined.size(); + size_t ba_len = diff_ba_joined.size(); + size_t sect_len = intersect.length(); + + double result = ratio(tokens_a.join(), tokens_b.join(), score_cutoff); + + // string length sect+ab <-> sect and sect+ba <-> sect + size_t sect_ab_len = sect_len + bool(sect_len) + ab_len; + size_t sect_ba_len = sect_len + bool(sect_len) + ba_len; + + size_t cutoff_distance = fuzz_detail::score_cutoff_to_distance(score_cutoff, sect_ab_len + sect_ba_len); + size_t dist = indel_distance(diff_ab_joined, diff_ba_joined, cutoff_distance); + if (dist <= cutoff_distance) + result = std::max(result, fuzz_detail::norm_distance(dist, sect_ab_len + sect_ba_len, score_cutoff)); + + // exit early since the other ratios are 0 + if (!sect_len) return result; + + // levenshtein distance sect+ab <-> sect and sect+ba <-> sect + // since only sect is similar in them the distance can be calculated based on + // the length difference + size_t sect_ab_dist = bool(sect_len) + ab_len; + double sect_ab_ratio = fuzz_detail::norm_distance(sect_ab_dist, sect_len + sect_ab_len, score_cutoff); + + size_t sect_ba_dist = bool(sect_len) + ba_len; + double sect_ba_ratio = fuzz_detail::norm_distance(sect_ba_dist, sect_len + sect_ba_len, score_cutoff); + + return std::max({result, sect_ab_ratio, sect_ba_ratio}); +} + +template +double token_ratio(const Sentence1& s1, const Sentence2& s2, double score_cutoff) +{ + return token_ratio(detail::to_begin(s1), detail::to_end(s1), detail::to_begin(s2), detail::to_end(s2), + score_cutoff); +} + +namespace fuzz_detail { +template +double token_ratio(const rapidfuzz::detail::SplittedSentenceView& s1_tokens, + const CachedRatio& cached_ratio_s1_sorted, InputIt2 first2, InputIt2 last2, + double score_cutoff) +{ + if (score_cutoff > 100) return 0; + + auto s2_tokens = detail::sorted_split(first2, last2); + + auto decomposition = detail::set_decomposition(s1_tokens, s2_tokens); + auto intersect = decomposition.intersection; + auto diff_ab = decomposition.difference_ab; + auto diff_ba = decomposition.difference_ba; + + if (!intersect.empty() && (diff_ab.empty() || diff_ba.empty())) return 100; + + auto diff_ab_joined = diff_ab.join(); + auto diff_ba_joined = diff_ba.join(); + + size_t ab_len = diff_ab_joined.size(); + size_t ba_len = diff_ba_joined.size(); + size_t sect_len = intersect.length(); + + double result = cached_ratio_s1_sorted.similarity(s2_tokens.join(), score_cutoff); + + // string length sect+ab <-> sect and sect+ba <-> sect + size_t sect_ab_len = sect_len + bool(sect_len) + ab_len; + size_t sect_ba_len = sect_len + bool(sect_len) + ba_len; + + size_t cutoff_distance = score_cutoff_to_distance(score_cutoff, sect_ab_len + sect_ba_len); + size_t dist = indel_distance(diff_ab_joined, diff_ba_joined, cutoff_distance); + if (dist <= cutoff_distance) + result = std::max(result, norm_distance(dist, sect_ab_len + sect_ba_len, score_cutoff)); + + // exit early since the other ratios are 0 + if (!sect_len) return result; + + // levenshtein distance sect+ab <-> sect and sect+ba <-> sect + // since only sect is similar in them the distance can be calculated based on + // the length difference + size_t sect_ab_dist = bool(sect_len) + ab_len; + double sect_ab_ratio = norm_distance(sect_ab_dist, sect_len + sect_ab_len, score_cutoff); + + size_t sect_ba_dist = bool(sect_len) + ba_len; + double sect_ba_ratio = norm_distance(sect_ba_dist, sect_len + sect_ba_len, score_cutoff); + + return std::max({result, sect_ab_ratio, sect_ba_ratio}); +} + +// todo this is a temporary solution until WRatio is properly implemented using other scorers +template +double token_ratio(const std::vector& s1_sorted, + const rapidfuzz::detail::SplittedSentenceView& tokens_s1, + const detail::BlockPatternMatchVector& blockmap_s1_sorted, InputIt2 first2, InputIt2 last2, + double score_cutoff) +{ + if (score_cutoff > 100) return 0; + + auto tokens_b = detail::sorted_split(first2, last2); + + auto decomposition = detail::set_decomposition(tokens_s1, tokens_b); + auto intersect = decomposition.intersection; + auto diff_ab = decomposition.difference_ab; + auto diff_ba = decomposition.difference_ba; + + if (!intersect.empty() && (diff_ab.empty() || diff_ba.empty())) return 100; + + auto diff_ab_joined = diff_ab.join(); + auto diff_ba_joined = diff_ba.join(); + + size_t ab_len = diff_ab_joined.size(); + size_t ba_len = diff_ba_joined.size(); + size_t sect_len = intersect.length(); + + double result = 0; + auto s2_sorted = tokens_b.join(); + if (s1_sorted.size() < 65) { + double norm_sim = detail::indel_normalized_similarity(blockmap_s1_sorted, detail::Range(s1_sorted), + detail::Range(s2_sorted), score_cutoff / 100); + result = norm_sim * 100; + } + else { + result = fuzz::ratio(s1_sorted, s2_sorted, score_cutoff); + } + + // string length sect+ab <-> sect and sect+ba <-> sect + size_t sect_ab_len = sect_len + bool(sect_len) + ab_len; + size_t sect_ba_len = sect_len + bool(sect_len) + ba_len; + + size_t cutoff_distance = score_cutoff_to_distance(score_cutoff, sect_ab_len + sect_ba_len); + size_t dist = indel_distance(diff_ab_joined, diff_ba_joined, cutoff_distance); + if (dist <= cutoff_distance) + result = std::max(result, norm_distance(dist, sect_ab_len + sect_ba_len, score_cutoff)); + + // exit early since the other ratios are 0 + if (!sect_len) return result; + + // levenshtein distance sect+ab <-> sect and sect+ba <-> sect + // since only sect is similar in them the distance can be calculated based on + // the length difference + size_t sect_ab_dist = bool(sect_len) + ab_len; + double sect_ab_ratio = norm_distance(sect_ab_dist, sect_len + sect_ab_len, score_cutoff); + + size_t sect_ba_dist = bool(sect_len) + ba_len; + double sect_ba_ratio = norm_distance(sect_ba_dist, sect_len + sect_ba_len, score_cutoff); + + return std::max({result, sect_ab_ratio, sect_ba_ratio}); +} +} // namespace fuzz_detail + +template +template +double CachedTokenRatio::similarity(InputIt2 first2, InputIt2 last2, double score_cutoff, + [[maybe_unused]] double score_hint) const +{ + return fuzz_detail::token_ratio(s1_tokens, cached_ratio_s1_sorted, first2, last2, score_cutoff); +} + +template +template +double CachedTokenRatio::similarity(const Sentence2& s2, double score_cutoff, + [[maybe_unused]] double score_hint) const +{ + return similarity(detail::to_begin(s2), detail::to_end(s2), score_cutoff); +} + +/********************************************** + * partial_token_ratio + *********************************************/ + +template +double partial_token_ratio(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + double score_cutoff) +{ + if (score_cutoff > 100) return 0; + + auto tokens_a = detail::sorted_split(first1, last1); + auto tokens_b = detail::sorted_split(first2, last2); + + auto decomposition = detail::set_decomposition(tokens_a, tokens_b); + + // exit early when there is a common word in both sequences + if (!decomposition.intersection.empty()) return 100; + + auto diff_ab = decomposition.difference_ab; + auto diff_ba = decomposition.difference_ba; + + double result = partial_ratio(tokens_a.join(), tokens_b.join(), score_cutoff); + + // do not calculate the same partial_ratio twice + if (tokens_a.word_count() == diff_ab.word_count() && tokens_b.word_count() == diff_ba.word_count()) { + return result; + } + + score_cutoff = std::max(score_cutoff, result); + return std::max(result, partial_ratio(diff_ab.join(), diff_ba.join(), score_cutoff)); +} + +template +double partial_token_ratio(const Sentence1& s1, const Sentence2& s2, double score_cutoff) +{ + return partial_token_ratio(detail::to_begin(s1), detail::to_end(s1), detail::to_begin(s2), + detail::to_end(s2), score_cutoff); +} + +namespace fuzz_detail { +template +double partial_token_ratio(const std::vector& s1_sorted, + const rapidfuzz::detail::SplittedSentenceView& tokens_s1, + InputIt2 first2, InputIt2 last2, double score_cutoff) +{ + if (score_cutoff > 100) return 0; + + auto tokens_b = detail::sorted_split(first2, last2); + + auto decomposition = detail::set_decomposition(tokens_s1, tokens_b); + + // exit early when there is a common word in both sequences + if (!decomposition.intersection.empty()) return 100; + + auto diff_ab = decomposition.difference_ab; + auto diff_ba = decomposition.difference_ba; + + double result = partial_ratio(s1_sorted, tokens_b.join(), score_cutoff); + + // do not calculate the same partial_ratio twice + if (tokens_s1.word_count() == diff_ab.word_count() && tokens_b.word_count() == diff_ba.word_count()) { + return result; + } + + score_cutoff = std::max(score_cutoff, result); + return std::max(result, partial_ratio(diff_ab.join(), diff_ba.join(), score_cutoff)); +} + +} // namespace fuzz_detail + +template +template +double CachedPartialTokenRatio::similarity(InputIt2 first2, InputIt2 last2, double score_cutoff, + [[maybe_unused]] double score_hint) const +{ + return fuzz_detail::partial_token_ratio(s1_sorted, tokens_s1, first2, last2, score_cutoff); +} + +template +template +double CachedPartialTokenRatio::similarity(const Sentence2& s2, double score_cutoff, + [[maybe_unused]] double score_hint) const +{ + return similarity(detail::to_begin(s2), detail::to_end(s2), score_cutoff); +} + +/********************************************** + * WRatio + *********************************************/ + +template +double WRatio(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, double score_cutoff) +{ + if (score_cutoff > 100) return 0; + + constexpr double UNBASE_SCALE = 0.95; + + auto len1 = std::distance(first1, last1); + auto len2 = std::distance(first2, last2); + + /* in FuzzyWuzzy this returns 0. For sake of compatibility return 0 here as well + * see https://github.com/rapidfuzz/RapidFuzz/issues/110 */ + if (!len1 || !len2) return 0; + + double len_ratio = (len1 > len2) ? static_cast(len1) / static_cast(len2) + : static_cast(len2) / static_cast(len1); + + double end_ratio = ratio(first1, last1, first2, last2, score_cutoff); + + if (len_ratio < 1.5) { + score_cutoff = std::max(score_cutoff, end_ratio) / UNBASE_SCALE; + return std::max(end_ratio, token_ratio(first1, last1, first2, last2, score_cutoff) * UNBASE_SCALE); + } + + const double PARTIAL_SCALE = (len_ratio < 8.0) ? 0.9 : 0.6; + + score_cutoff = std::max(score_cutoff, end_ratio) / PARTIAL_SCALE; + end_ratio = + std::max(end_ratio, partial_ratio(first1, last1, first2, last2, score_cutoff) * PARTIAL_SCALE); + + score_cutoff = std::max(score_cutoff, end_ratio) / UNBASE_SCALE; + return std::max(end_ratio, partial_token_ratio(first1, last1, first2, last2, score_cutoff) * + UNBASE_SCALE * PARTIAL_SCALE); +} + +template +double WRatio(const Sentence1& s1, const Sentence2& s2, double score_cutoff) +{ + return WRatio(detail::to_begin(s1), detail::to_end(s1), detail::to_begin(s2), detail::to_end(s2), + score_cutoff); +} + +template +template +CachedWRatio::CachedWRatio(InputIt1 first1, InputIt1 last1) + : s1(first1, last1), + cached_partial_ratio(first1, last1), + tokens_s1(detail::sorted_split(std::begin(s1), std::end(s1))), + s1_sorted(tokens_s1.join()), + blockmap_s1_sorted(detail::Range(s1_sorted)) +{} + +template +template +double CachedWRatio::similarity(InputIt2 first2, InputIt2 last2, double score_cutoff, + [[maybe_unused]] double score_hint) const +{ + if (score_cutoff > 100) return 0; + + constexpr double UNBASE_SCALE = 0.95; + + size_t len1 = s1.size(); + size_t len2 = static_cast(std::distance(first2, last2)); + + /* in FuzzyWuzzy this returns 0. For sake of compatibility return 0 here as well + * see https://github.com/rapidfuzz/RapidFuzz/issues/110 */ + if (!len1 || !len2) return 0; + + double len_ratio = (len1 > len2) ? static_cast(len1) / static_cast(len2) + : static_cast(len2) / static_cast(len1); + + double end_ratio = cached_partial_ratio.cached_ratio.similarity(first2, last2, score_cutoff); + + if (len_ratio < 1.5) { + score_cutoff = std::max(score_cutoff, end_ratio) / UNBASE_SCALE; + // use pre calculated values + auto r = + fuzz_detail::token_ratio(s1_sorted, tokens_s1, blockmap_s1_sorted, first2, last2, score_cutoff); + return std::max(end_ratio, r * UNBASE_SCALE); + } + + const double PARTIAL_SCALE = (len_ratio < 8.0) ? 0.9 : 0.6; + + score_cutoff = std::max(score_cutoff, end_ratio) / PARTIAL_SCALE; + end_ratio = + std::max(end_ratio, cached_partial_ratio.similarity(first2, last2, score_cutoff) * PARTIAL_SCALE); + + score_cutoff = std::max(score_cutoff, end_ratio) / UNBASE_SCALE; + auto r = fuzz_detail::partial_token_ratio(s1_sorted, tokens_s1, first2, last2, score_cutoff); + return std::max(end_ratio, r * UNBASE_SCALE * PARTIAL_SCALE); +} + +template +template +double CachedWRatio::similarity(const Sentence2& s2, double score_cutoff, + [[maybe_unused]] double score_hint) const +{ + return similarity(detail::to_begin(s2), detail::to_end(s2), score_cutoff); +} + +/********************************************** + * QRatio + *********************************************/ + +template +double QRatio(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, double score_cutoff) +{ + ptrdiff_t len1 = std::distance(first1, last1); + ptrdiff_t len2 = std::distance(first2, last2); + + /* in FuzzyWuzzy this returns 0. For sake of compatibility return 0 here as well + * see https://github.com/rapidfuzz/RapidFuzz/issues/110 */ + if (!len1 || !len2) return 0; + + return ratio(first1, last1, first2, last2, score_cutoff); +} + +template +double QRatio(const Sentence1& s1, const Sentence2& s2, double score_cutoff) +{ + return QRatio(detail::to_begin(s1), detail::to_end(s1), detail::to_begin(s2), detail::to_end(s2), + score_cutoff); +} + +template +template +double CachedQRatio::similarity(InputIt2 first2, InputIt2 last2, double score_cutoff, + [[maybe_unused]] double score_hint) const +{ + auto len2 = std::distance(first2, last2); + + /* in FuzzyWuzzy this returns 0. For sake of compatibility return 0 here as well + * see https://github.com/rapidfuzz/RapidFuzz/issues/110 */ + if (s1.empty() || !len2) return 0; + + return cached_ratio.similarity(first2, last2, score_cutoff); +} + +template +template +double CachedQRatio::similarity(const Sentence2& s2, double score_cutoff, + [[maybe_unused]] double score_hint) const +{ + return similarity(detail::to_begin(s2), detail::to_end(s2), score_cutoff); +} + +} // namespace rapidfuzz::fuzz + +#endif // RAPIDFUZZ_AMALGAMATED_HPP_INCLUDED diff --git a/src/external/rapidfuzz-cpp/fuzzing/CMakeLists.txt b/src/external/rapidfuzz-cpp/fuzzing/CMakeLists.txt new file mode 100644 index 00000000..b3b5efcd --- /dev/null +++ b/src/external/rapidfuzz-cpp/fuzzing/CMakeLists.txt @@ -0,0 +1,24 @@ +function(create_fuzzer fuzzer) + add_executable(fuzz_${fuzzer} fuzz_${fuzzer}.cpp) + target_compile_features(fuzz_${fuzzer} PUBLIC cxx_std_17) + target_link_libraries(fuzz_${fuzzer} PRIVATE rapidfuzz::rapidfuzz) + + target_compile_options(fuzz_${fuzzer} PRIVATE -g -O1 -fsanitize=fuzzer,address -march=native) + target_link_libraries(fuzz_${fuzzer} PRIVATE -fsanitize=fuzzer,address) +endfunction(create_fuzzer) + +create_fuzzer(lcs_similarity) + +create_fuzzer(levenshtein_distance) +create_fuzzer(levenshtein_editops) + +create_fuzzer(indel_distance) +create_fuzzer(indel_editops) + +create_fuzzer(osa_distance) + +create_fuzzer(damerau_levenshtein_distance) + +create_fuzzer(jaro_similarity) + +create_fuzzer(partial_ratio) diff --git a/src/external/rapidfuzz-cpp/fuzzing/fuzz_damerau_levenshtein_distance.cpp b/src/external/rapidfuzz-cpp/fuzzing/fuzz_damerau_levenshtein_distance.cpp new file mode 100644 index 00000000..743cf74a --- /dev/null +++ b/src/external/rapidfuzz-cpp/fuzzing/fuzz_damerau_levenshtein_distance.cpp @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright © 2021 Max Bachmann */ + +#include "../rapidfuzz_reference/DamerauLevenshtein.hpp" +#include "fuzzing.hpp" +#include +#include +#include +#include + +void validate_distance(size_t reference_dist, const std::vector& s1, const std::vector& s2, + size_t score_cutoff) +{ + if (reference_dist > score_cutoff) reference_dist = score_cutoff + 1; + + auto dist = rapidfuzz::experimental::damerau_levenshtein_distance(s1, s2, score_cutoff); + if (dist != reference_dist) { + print_seq("s1", s1); + print_seq("s2", s2); + throw std::logic_error(std::string("osa distance failed (score_cutoff = ") + + std::to_string(score_cutoff) + std::string(", reference_score = ") + + std::to_string(reference_dist) + std::string(", score = ") + + std::to_string(dist) + ")"); + } +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) +{ + std::vector s1, s2; + if (!extract_strings(data, size, s1, s2)) return 0; + + size_t reference_dist = rapidfuzz_reference::damerau_levenshtein_distance(s1, s2); + + /* test small band */ + for (size_t i = 4; i < 32; ++i) + validate_distance(reference_dist, s1, s2, i); + + /* unrestricted */ + validate_distance(reference_dist, s1, s2, std::numeric_limits::max()); + + /* test long sequences */ + for (unsigned int i = 2; i < 9; ++i) { + std::vector s1_ = vec_multiply(s1, pow(2, i)); + std::vector s2_ = vec_multiply(s2, pow(2, i)); + + if (s1_.size() > 10000 || s2_.size() > 10000) break; + + reference_dist = rapidfuzz_reference::damerau_levenshtein_distance(s1_, s2_); + validate_distance(reference_dist, s1_, s2_, std::numeric_limits::max()); + } + + return 0; +} diff --git a/src/external/rapidfuzz-cpp/fuzzing/fuzz_indel_distance.cpp b/src/external/rapidfuzz-cpp/fuzzing/fuzz_indel_distance.cpp new file mode 100644 index 00000000..546bcd14 --- /dev/null +++ b/src/external/rapidfuzz-cpp/fuzzing/fuzz_indel_distance.cpp @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright © 2021 Max Bachmann */ + +#include "../rapidfuzz_reference/Indel.hpp" +#include "fuzzing.hpp" +#include +#include +#include +#include + +void validate_distance(const std::vector& s1, const std::vector& s2, size_t score_cutoff) +{ + auto dist = rapidfuzz::indel_distance(s1, s2, score_cutoff); + auto reference_dist = rapidfuzz_reference::indel_distance(s1, s2, score_cutoff); + if (dist != reference_dist) { + print_seq("s1: ", s1); + print_seq("s2: ", s2); + throw std::logic_error(std::string("indel distance failed (score_cutoff = ") + + std::to_string(score_cutoff) + std::string(", reference_score = ") + + std::to_string(reference_dist) + std::string(", score = ") + + std::to_string(dist) + ")"); + } +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) +{ + std::vector s1, s2; + if (!extract_strings(data, size, s1, s2)) return 0; + + validate_distance(s1, s2, 0); + validate_distance(s1, s2, 1); + validate_distance(s1, s2, 2); + validate_distance(s1, s2, 3); + validate_distance(s1, s2, 4); + /* score_cutoff to trigger banded implementation */ + validate_distance(s1, s2, s1.size() / 2); + validate_distance(s1, s2, s2.size() / 2); + + /* unrestricted */ + validate_distance(s1, s2, std::numeric_limits::max()); + + return 0; +} diff --git a/src/external/rapidfuzz-cpp/fuzzing/fuzz_indel_editops.cpp b/src/external/rapidfuzz-cpp/fuzzing/fuzz_indel_editops.cpp new file mode 100644 index 00000000..4d99c300 --- /dev/null +++ b/src/external/rapidfuzz-cpp/fuzzing/fuzz_indel_editops.cpp @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright © 2021 Max Bachmann */ + +#include "../rapidfuzz_reference/Indel.hpp" +#include "fuzzing.hpp" +#include +#include +#include + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) +{ + std::vector s1, s2; + if (!extract_strings(data, size, s1, s2)) return 0; + + size_t score = rapidfuzz_reference::indel_distance(s1, s2); + rapidfuzz::Editops ops = rapidfuzz::indel_editops(s1, s2); + + if (ops.size() == score && s2 != rapidfuzz::editops_apply_vec(ops, s1, s2)) + throw std::logic_error("levenshtein_editops failed"); + + return 0; +} diff --git a/src/external/rapidfuzz-cpp/fuzzing/fuzz_jaro_similarity.cpp b/src/external/rapidfuzz-cpp/fuzzing/fuzz_jaro_similarity.cpp new file mode 100644 index 00000000..6ba5a046 --- /dev/null +++ b/src/external/rapidfuzz-cpp/fuzzing/fuzz_jaro_similarity.cpp @@ -0,0 +1,99 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright © 2021 Max Bachmann */ + +#include "../rapidfuzz_reference/Jaro.hpp" +#include "fuzzing.hpp" +#include +#include +#include +#include + +bool is_close(double a, double b, double epsilon) +{ + return fabs(a - b) <= ((fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon); +} + +template +void validate_simd(const std::vector& s1, const std::vector& s2) +{ +#ifdef RAPIDFUZZ_SIMD + size_t count = s1.size() / MaxLen + ((s1.size() % MaxLen) != 0); + if (count == 0) return; + + rapidfuzz::experimental::MultiJaro scorer(count); + + std::vector> strings; + + for (auto it1 = s1.begin(); it1 != s1.end(); it1 += MaxLen) { + if (std::distance(it1, s1.end()) < static_cast(MaxLen)) { + strings.emplace_back(it1, s1.end()); + break; + } + else { + strings.emplace_back(it1, it1 + MaxLen); + } + } + + for (const auto& s : strings) + scorer.insert(s); + + std::vector simd_results(scorer.result_count()); + scorer.similarity(&simd_results[0], simd_results.size(), s2); + + for (size_t i = 0; i < strings.size(); ++i) { + double reference_sim = rapidfuzz_reference::jaro_similarity(strings[i], s2); + + if (!is_close(simd_results[i], reference_sim, 0.0001)) { + print_seq("s1", strings[i]); + print_seq("s2", s2); + throw std::logic_error(std::string("jaro similarity using simd failed (reference_score = ") + + std::to_string(reference_sim) + std::string(", score = ") + + std::to_string(simd_results[i]) + std::string(", i = ") + + std::to_string(i) + ")"); + } + } + +#else + (void)s1; + (void)s2; +#endif +} + +void validate_distance(const std::vector& s1, const std::vector& s2) +{ + double reference_sim = rapidfuzz_reference::jaro_similarity(s1, s2); + double sim = rapidfuzz::jaro_similarity(s1, s2); + + if (!is_close(sim, reference_sim, 0.0001)) { + print_seq("s1", s1); + print_seq("s2", s2); + throw std::logic_error(std::string("jaro similarity failed (reference_score = ") + + std::to_string(reference_sim) + std::string(", score = ") + + std::to_string(sim) + ")"); + } + + validate_simd<8>(s1, s2); + validate_simd<16>(s1, s2); + validate_simd<32>(s1, s2); + validate_simd<64>(s1, s2); +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) +{ + std::vector s1, s2; + if (!extract_strings(data, size, s1, s2)) return 0; + + validate_distance(s1, s2); + + /* test long sequences */ + for (unsigned int i = 2; i < 9; ++i) { + std::vector s1_ = vec_multiply(s1, pow(2, i)); + std::vector s2_ = vec_multiply(s2, pow(2, i)); + + if (s1_.size() > 10000 || s2_.size() > 10000) break; + + validate_distance(s1_, s2_); + } + + return 0; +} diff --git a/src/external/rapidfuzz-cpp/fuzzing/fuzz_lcs_similarity.cpp b/src/external/rapidfuzz-cpp/fuzzing/fuzz_lcs_similarity.cpp new file mode 100644 index 00000000..7f833c51 --- /dev/null +++ b/src/external/rapidfuzz-cpp/fuzzing/fuzz_lcs_similarity.cpp @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright © 2021 Max Bachmann */ + +#include "../rapidfuzz_reference/LCSseq.hpp" +#include "fuzzing.hpp" +#include +#include +#include +#include + +template +void validate_simd(const std::vector& s1, const std::vector& s2) +{ +#ifdef RAPIDFUZZ_SIMD + size_t count = s1.size() / MaxLen + ((s1.size() % MaxLen) != 0); + rapidfuzz::experimental::MultiLCSseq scorer(count); + + std::vector> strings; + + for (auto it1 = s1.begin(); it1 != s1.end(); it1 += MaxLen) { + if (std::distance(it1, s1.end()) < static_cast(MaxLen)) { + strings.emplace_back(it1, s1.end()); + break; + } + else { + strings.emplace_back(it1, it1 + MaxLen); + } + } + + for (const auto& s : strings) + scorer.insert(s); + + std::vector simd_results(scorer.result_count()); + scorer.similarity(&simd_results[0], simd_results.size(), s2); + + for (size_t i = 0; i < strings.size(); ++i) { + size_t reference_score = rapidfuzz_reference::lcs_seq_similarity(strings[i], s2); + if (reference_score != simd_results[i]) { + print_seq("s1: ", s1); + print_seq("s2: ", s2); + throw std::logic_error(std::string("lcs distance using simd failed (score_cutoff = ") + + std::string(", reference_score = ") + std::to_string(reference_score) + + std::string(", score = ") + std::to_string(simd_results[i]) + ")"); + } + } +#else + (void)s1; + (void)s2; +#endif +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) +{ + std::vector s1, s2; + if (!extract_strings(data, size, s1, s2)) { + return 0; + } + + if (s1.size() == 0) { + return 0; + } + + validate_simd<8>(s1, s2); + validate_simd<16>(s1, s2); + validate_simd<32>(s1, s2); + validate_simd<64>(s1, s2); + + return 0; +} diff --git a/src/external/rapidfuzz-cpp/fuzzing/fuzz_levenshtein_distance.cpp b/src/external/rapidfuzz-cpp/fuzzing/fuzz_levenshtein_distance.cpp new file mode 100644 index 00000000..a577c608 --- /dev/null +++ b/src/external/rapidfuzz-cpp/fuzzing/fuzz_levenshtein_distance.cpp @@ -0,0 +1,99 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright © 2021 Max Bachmann */ + +#include "../rapidfuzz_reference/Levenshtein.hpp" +#include "fuzzing.hpp" +#include +#include +#include +#include + +template +void validate_simd(const std::vector& s1, const std::vector& s2) +{ +#ifdef RAPIDFUZZ_SIMD + size_t count = s1.size() / MaxLen + ((s1.size() % MaxLen) != 0); + if (count == 0) return; + + rapidfuzz::experimental::MultiLevenshtein scorer(count); + + std::vector> strings; + + for (auto it1 = s1.begin(); it1 != s1.end(); it1 += MaxLen) { + if (std::distance(it1, s1.end()) < static_cast(MaxLen)) { + strings.emplace_back(it1, s1.end()); + break; + } + else { + strings.emplace_back(it1, it1 + MaxLen); + } + } + + for (const auto& s : strings) + scorer.insert(s); + + std::vector simd_results(scorer.result_count()); + scorer.distance(&simd_results[0], simd_results.size(), s2); + + for (size_t i = 0; i < strings.size(); ++i) { + size_t reference_score = rapidfuzz_reference::levenshtein_distance(strings[i], s2); + if (reference_score != simd_results[i]) { + print_seq("s1: ", strings[i]); + print_seq("s2: ", s2); + throw std::logic_error(std::string("levenshtein distance using simd failed (reference_score = ") + + std::to_string(reference_score) + std::string(", score = ") + + std::to_string(simd_results[i]) + std::string(", i = ") + + std::to_string(i) + ")"); + } + } +#else + (void)s1; + (void)s2; +#endif +} + +void validate_distance(size_t reference_dist, const std::vector& s1, const std::vector& s2, + size_t score_cutoff) +{ + if (reference_dist > score_cutoff) reference_dist = score_cutoff + 1; + + auto dist = rapidfuzz::levenshtein_distance(s1, s2, {1, 1, 1}, score_cutoff); + if (dist != reference_dist) { + print_seq("s1: ", s1); + print_seq("s2: ", s2); + throw std::logic_error(std::string("levenshtein distance failed (score_cutoff = ") + + std::to_string(score_cutoff) + std::string(", reference_score = ") + + std::to_string(reference_dist) + std::string(", score = ") + + std::to_string(dist) + ")"); + } + + validate_simd<8>(s1, s2); + validate_simd<16>(s1, s2); + validate_simd<32>(s1, s2); + validate_simd<64>(s1, s2); +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) +{ + std::vector s1, s2; + if (!extract_strings(data, size, s1, s2)) return 0; + + size_t reference_dist = rapidfuzz_reference::levenshtein_distance(s1, s2); + + /* test mbleven */ + for (size_t i = 0; i < 4; ++i) + validate_distance(reference_dist, s1, s2, i); + + /* test small band */ + for (size_t i = 4; i < 32; ++i) + validate_distance(reference_dist, s1, s2, i); + + /* unrestricted */ + validate_distance(reference_dist, s1, s2, std::numeric_limits::max()); + + /* score_cutoff to trigger banded implementation */ + validate_distance(reference_dist, s1, s2, s1.size() / 2); + validate_distance(reference_dist, s1, s2, s2.size() / 2); + + return 0; +} diff --git a/src/external/rapidfuzz-cpp/fuzzing/fuzz_levenshtein_editops.cpp b/src/external/rapidfuzz-cpp/fuzzing/fuzz_levenshtein_editops.cpp new file mode 100644 index 00000000..fe09cb5e --- /dev/null +++ b/src/external/rapidfuzz-cpp/fuzzing/fuzz_levenshtein_editops.cpp @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright © 2021 Max Bachmann */ + +#include "../rapidfuzz_reference/Levenshtein.hpp" +#include "fuzzing.hpp" +#include +#include +#include + +void validate_editops(const std::vector& s1, const std::vector& s2, size_t score, + size_t score_hint = std::numeric_limits::max()) +{ + rapidfuzz::Editops ops = rapidfuzz::levenshtein_editops(s1, s2, score_hint); + if (ops.size() == score && s2 != rapidfuzz::editops_apply_vec(ops, s1, s2)) + throw std::logic_error("levenshtein_editops failed"); +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) +{ + std::vector s1, s2; + if (!extract_strings(data, size, s1, s2)) return 0; + + /* hirschbergs algorithm is only used for very long sequences which are apparently not generated a lot by + * the fuzzer */ + for (int i = 0; i < 10; i++) { + size_t score = rapidfuzz_reference::levenshtein_distance(s1, s2); + validate_editops(s1, s2, score); + validate_editops(s1, s2, score, 64); + validate_editops(s1, s2, score, score != 0 ? score - 1 : 0); + validate_editops(s1, s2, score, score); + + if (s1.size() > 1 && s2.size() > 1) { + auto hpos = rapidfuzz::detail::find_hirschberg_pos(rapidfuzz::detail::Range(s1), + rapidfuzz::detail::Range(s2)); + if (hpos.left_score + hpos.right_score != score) + throw std::logic_error("find_hirschberg_pos failed"); + } + + s1 = vec_multiply(s1, 2); + s2 = vec_multiply(s2, 2); + } + + return 0; +} diff --git a/src/external/rapidfuzz-cpp/fuzzing/fuzz_osa_distance.cpp b/src/external/rapidfuzz-cpp/fuzzing/fuzz_osa_distance.cpp new file mode 100644 index 00000000..5cd75020 --- /dev/null +++ b/src/external/rapidfuzz-cpp/fuzzing/fuzz_osa_distance.cpp @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright © 2021 Max Bachmann */ + +#include "../rapidfuzz_reference/OSA.hpp" +#include "fuzzing.hpp" +#include +#include +#include +#include + +void validate_distance(size_t reference_dist, const std::vector& s1, const std::vector& s2, + size_t score_cutoff) +{ + if (reference_dist > score_cutoff) reference_dist = score_cutoff + 1; + + auto dist = rapidfuzz::osa_distance(s1, s2, score_cutoff); + if (dist != reference_dist) { + print_seq("s1", s1); + print_seq("s2", s2); + throw std::logic_error(std::string("osa distance failed (score_cutoff = ") + + std::to_string(score_cutoff) + std::string(", reference_score = ") + + std::to_string(reference_dist) + std::string(", score = ") + + std::to_string(dist) + ")"); + } +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) +{ + std::vector s1, s2; + if (!extract_strings(data, size, s1, s2)) return 0; + + size_t reference_dist = rapidfuzz_reference::osa_distance(s1, s2); + + /* test small band */ + for (size_t i = 4; i < 32; ++i) + validate_distance(reference_dist, s1, s2, i); + + /* unrestricted */ + validate_distance(reference_dist, s1, s2, std::numeric_limits::max()); + + /* test long sequences */ + for (unsigned int i = 2; i < 9; ++i) { + std::vector s1_ = vec_multiply(s1, pow(2, i)); + std::vector s2_ = vec_multiply(s2, pow(2, i)); + + if (s1_.size() > 10000 || s2_.size() > 10000) break; + + reference_dist = rapidfuzz_reference::osa_distance(s1_, s2_); + validate_distance(reference_dist, s1_, s2_, std::numeric_limits::max()); + } + + return 0; +} diff --git a/src/external/rapidfuzz-cpp/fuzzing/fuzz_partial_ratio.cpp b/src/external/rapidfuzz-cpp/fuzzing/fuzz_partial_ratio.cpp new file mode 100644 index 00000000..c250ea2e --- /dev/null +++ b/src/external/rapidfuzz-cpp/fuzzing/fuzz_partial_ratio.cpp @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright © 2021 Max Bachmann */ + +#include "../rapidfuzz_reference/fuzz.hpp" +#include "fuzzing.hpp" +#include +#include +#include +#include + +bool is_close(double a, double b, double epsilon) +{ + return fabs(a - b) <= ((fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon); +} + +void validate_distance(const std::vector& s1, const std::vector& s2) +{ + auto sim = rapidfuzz::fuzz::partial_ratio(s1, s2); + auto reference_sim = rapidfuzz_reference::partial_ratio(s1, s2); + if (!is_close(sim, reference_sim, 0.0001)) { + print_seq("s1: ", s1); + print_seq("s2: ", s2); + throw std::logic_error(std::string("partial_ratio failed (reference_score = ") + + std::to_string(reference_sim) + std::string(", score = ") + + std::to_string(sim) + ")"); + } +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) +{ + std::vector s1, s2; + if (!extract_strings(data, size, s1, s2)) return 0; + + validate_distance(s1, s2); + validate_distance(s2, s1); + + /* test long sequences */ + for (unsigned int i = 2; i < 9; ++i) { + std::vector s1_ = vec_multiply(s1, pow(2, i)); + std::vector s2_ = vec_multiply(s2, pow(2, i)); + + if (s1_.size() > 10000 || s2_.size() > 10000) break; + + validate_distance(s1_, s2_); + validate_distance(s2_, s1_); + validate_distance(s1, s2_); + validate_distance(s2_, s1); + validate_distance(s1_, s2); + validate_distance(s2, s1_); + } + + return 0; +} diff --git a/src/external/rapidfuzz-cpp/fuzzing/fuzzing.hpp b/src/external/rapidfuzz-cpp/fuzzing/fuzzing.hpp new file mode 100644 index 00000000..282baf98 --- /dev/null +++ b/src/external/rapidfuzz-cpp/fuzzing/fuzzing.hpp @@ -0,0 +1,55 @@ +#pragma once +#include +#include +#include + +static inline bool extract_strings(const uint8_t* data, size_t size, std::vector& s1, + std::vector& s2) +{ + if (size <= sizeof(uint32_t)) { + return false; + } + uint32_t len1 = *(uint32_t*)data; + + if (len1 > size - sizeof(len1)) { + return false; + } + + data += sizeof(len1); + size -= sizeof(len1); + s1 = std::vector(data, data + len1); + s2 = std::vector(data + len1, data + size); + return true; +} + +template +static inline T pow(T x, unsigned int p) +{ + if (p == 0) return 1; + if (p == 1) return x; + + T tmp = pow(x, p / 2); + if (p % 2 == 0) + return tmp * tmp; + else + return x * tmp * tmp; +} + +template +std::vector vec_multiply(const std::vector& a, size_t b) +{ + std::vector output; + while (b--) + output.insert(output.end(), a.begin(), a.end()); + + return output; +} + +template +void print_seq(const std::string& name, const std::vector& seq) +{ + std::cout << name << " len: " << seq.size() << " content: "; + for (const auto& ch : seq) + std::cout << static_cast(ch) << " "; + std::cout << std::endl; +} diff --git a/src/external/rapidfuzz-cpp/rapidfuzz/details/CharSet.hpp b/src/external/rapidfuzz-cpp/rapidfuzz/details/CharSet.hpp new file mode 100644 index 00000000..a00e3ee1 --- /dev/null +++ b/src/external/rapidfuzz-cpp/rapidfuzz/details/CharSet.hpp @@ -0,0 +1,74 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright (c) 2022 Max Bachmann */ + +#pragma once +#include +#include +#include +#include +#include +#include + +namespace rapidfuzz::detail { + +/* + * taken from https://stackoverflow.com/a/17251989/11335032 + */ +template +bool CanTypeFitValue(const U value) +{ + const intmax_t botT = intmax_t(std::numeric_limits::min()); + const intmax_t botU = intmax_t(std::numeric_limits::min()); + const uintmax_t topT = uintmax_t(std::numeric_limits::max()); + const uintmax_t topU = uintmax_t(std::numeric_limits::max()); + return !((botT > botU && value < static_cast(botT)) || (topT < topU && value > static_cast(topT))); +} + +template +struct CharSet; + +template +struct CharSet { + using UCharT1 = typename std::make_unsigned::type; + + std::array::max() + 1> m_val; + + CharSet() : m_val{} + {} + + void insert(CharT1 ch) + { + m_val[UCharT1(ch)] = true; + } + + template + bool find(CharT2 ch) const + { + if (!CanTypeFitValue(ch)) return false; + + return m_val[UCharT1(ch)]; + } +}; + +template +struct CharSet { + std::unordered_set m_val; + + CharSet() : m_val{} + {} + + void insert(CharT1 ch) + { + m_val.insert(ch); + } + + template + bool find(CharT2 ch) const + { + if (!CanTypeFitValue(ch)) return false; + + return m_val.find(CharT1(ch)) != m_val.end(); + } +}; + +} // namespace rapidfuzz::detail \ No newline at end of file diff --git a/src/external/rapidfuzz-cpp/rapidfuzz/details/GrowingHashmap.hpp b/src/external/rapidfuzz-cpp/rapidfuzz/details/GrowingHashmap.hpp new file mode 100644 index 00000000..ba0edebc --- /dev/null +++ b/src/external/rapidfuzz-cpp/rapidfuzz/details/GrowingHashmap.hpp @@ -0,0 +1,203 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright (c) 2022 Max Bachmann */ + +#pragma once + +#include +#include +#include + +namespace rapidfuzz::detail { + +/* hashmap for integers which can only grow, but can't remove elements */ +template +struct GrowingHashmap { + using key_type = T_Key; + using value_type = T_Entry; + using size_type = unsigned int; + +private: + static constexpr size_type min_size = 8; + struct MapElem { + key_type key; + value_type value = value_type(); + }; + + int used; + int fill; + int mask; + MapElem* m_map; + +public: + GrowingHashmap() : used(0), fill(0), mask(-1), m_map(nullptr) + {} + ~GrowingHashmap() + { + delete[] m_map; + } + + GrowingHashmap(const GrowingHashmap& other) : used(other.used), fill(other.fill), mask(other.mask) + { + int size = mask + 1; + m_map = new MapElem[size]; + std::copy(other.m_map, other.m_map + size, m_map); + } + + GrowingHashmap(GrowingHashmap&& other) noexcept : GrowingHashmap() + { + swap(*this, other); + } + + GrowingHashmap& operator=(GrowingHashmap other) + { + swap(*this, other); + return *this; + } + + friend void swap(GrowingHashmap& first, GrowingHashmap& second) noexcept + { + std::swap(first.used, second.used); + std::swap(first.fill, second.fill); + std::swap(first.mask, second.mask); + std::swap(first.m_map, second.m_map); + } + + size_type size() const + { + return used; + } + size_type capacity() const + { + return mask + 1; + } + bool empty() const + { + return used == 0; + } + + value_type get(key_type key) const noexcept + { + if (m_map == nullptr) return value_type(); + + return m_map[lookup(key)].value; + } + + value_type& operator[](key_type key) noexcept + { + if (m_map == nullptr) allocate(); + + size_t i = lookup(key); + + if (m_map[i].value == value_type()) { + /* resize when 2/3 full */ + if (++fill * 3 >= (mask + 1) * 2) { + grow((used + 1) * 2); + i = lookup(key); + } + + used++; + } + + m_map[i].key = key; + return m_map[i].value; + } + +private: + void allocate() + { + mask = min_size - 1; + m_map = new MapElem[min_size]; + } + + /** + * lookup key inside the hashmap using a similar collision resolution + * strategy to CPython and Ruby + */ + size_t lookup(key_type key) const + { + size_t hash = static_cast(key); + size_t i = hash & static_cast(mask); + + if (m_map[i].value == value_type() || m_map[i].key == key) return i; + + size_t perturb = hash; + while (true) { + i = (i * 5 + perturb + 1) & static_cast(mask); + if (m_map[i].value == value_type() || m_map[i].key == key) return i; + + perturb >>= 5; + } + } + + void grow(int minUsed) + { + int newSize = mask + 1; + while (newSize <= minUsed) + newSize <<= 1; + + MapElem* oldMap = m_map; + m_map = new MapElem[static_cast(newSize)]; + + fill = used; + mask = newSize - 1; + + for (int i = 0; used > 0; i++) + if (oldMap[i].value != value_type()) { + size_t j = lookup(oldMap[i].key); + + m_map[j].key = oldMap[i].key; + m_map[j].value = oldMap[i].value; + used--; + } + + used = fill; + delete[] oldMap; + } +}; + +template +struct HybridGrowingHashmap { + using key_type = T_Key; + using value_type = T_Entry; + + HybridGrowingHashmap() + { + m_extendedAscii.fill(value_type()); + } + + value_type get(char key) const noexcept + { + /** treat char as value between 0 and 127 for performance reasons */ + return m_extendedAscii[static_cast(key)]; + } + + template + value_type get(CharT key) const noexcept + { + if (key >= 0 && key <= 255) + return m_extendedAscii[static_cast(key)]; + else + return m_map.get(static_cast(key)); + } + + value_type& operator[](char key) noexcept + { + /** treat char as value between 0 and 127 for performance reasons */ + return m_extendedAscii[static_cast(key)]; + } + + template + value_type& operator[](CharT key) + { + if (key >= 0 && key <= 255) + return m_extendedAscii[static_cast(key)]; + else + return m_map[static_cast(key)]; + } + +private: + GrowingHashmap m_map; + std::array m_extendedAscii; +}; + +} // namespace rapidfuzz::detail \ No newline at end of file diff --git a/src/external/rapidfuzz-cpp/rapidfuzz/details/Matrix.hpp b/src/external/rapidfuzz-cpp/rapidfuzz/details/Matrix.hpp new file mode 100644 index 00000000..7525f193 --- /dev/null +++ b/src/external/rapidfuzz-cpp/rapidfuzz/details/Matrix.hpp @@ -0,0 +1,199 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright (c) 2022 Max Bachmann */ + +#pragma once +#include +#include +#include +#include +#include + +namespace rapidfuzz::detail { + +template +struct BitMatrixView { + + using value_type = T; + using size_type = size_t; + using pointer = std::conditional_t; + using reference = std::conditional_t; + + BitMatrixView(pointer vector, size_type cols) noexcept : m_vector(vector), m_cols(cols) + {} + + reference operator[](size_type col) noexcept + { + assert(col < m_cols); + return m_vector[col]; + } + + size_type size() const noexcept + { + return m_cols; + } + +private: + pointer m_vector; + size_type m_cols; +}; + +template +struct BitMatrix { + + using value_type = T; + + BitMatrix() : m_rows(0), m_cols(0), m_matrix(nullptr) + {} + + BitMatrix(size_t rows, size_t cols, T val) : m_rows(rows), m_cols(cols), m_matrix(nullptr) + { + if (m_rows && m_cols) m_matrix = new T[m_rows * m_cols]; + std::fill_n(m_matrix, m_rows * m_cols, val); + } + + BitMatrix(const BitMatrix& other) : m_rows(other.m_rows), m_cols(other.m_cols), m_matrix(nullptr) + { + if (m_rows && m_cols) m_matrix = new T[m_rows * m_cols]; + std::copy(other.m_matrix, other.m_matrix + m_rows * m_cols, m_matrix); + } + + BitMatrix(BitMatrix&& other) noexcept : m_rows(0), m_cols(0), m_matrix(nullptr) + { + other.swap(*this); + } + + BitMatrix& operator=(BitMatrix&& other) noexcept + { + other.swap(*this); + return *this; + } + + BitMatrix& operator=(const BitMatrix& other) + { + BitMatrix temp = other; + temp.swap(*this); + return *this; + } + + void swap(BitMatrix& rhs) noexcept + { + using std::swap; + swap(m_rows, rhs.m_rows); + swap(m_cols, rhs.m_cols); + swap(m_matrix, rhs.m_matrix); + } + + ~BitMatrix() + { + delete[] m_matrix; + } + + BitMatrixView operator[](size_t row) noexcept + { + assert(row < m_rows); + return {&m_matrix[row * m_cols], m_cols}; + } + + BitMatrixView operator[](size_t row) const noexcept + { + assert(row < m_rows); + return {&m_matrix[row * m_cols], m_cols}; + } + + size_t rows() const noexcept + { + return m_rows; + } + + size_t cols() const noexcept + { + return m_cols; + } + +private: + size_t m_rows; + size_t m_cols; + T* m_matrix; +}; + +template +struct ShiftedBitMatrix { + using value_type = T; + + ShiftedBitMatrix() + {} + + ShiftedBitMatrix(size_t rows, size_t cols, T val) : m_matrix(rows, cols, val), m_offsets(rows) + {} + + ShiftedBitMatrix(const ShiftedBitMatrix& other) : m_matrix(other.m_matrix), m_offsets(other.m_offsets) + {} + + ShiftedBitMatrix(ShiftedBitMatrix&& other) noexcept + { + other.swap(*this); + } + + ShiftedBitMatrix& operator=(ShiftedBitMatrix&& other) noexcept + { + other.swap(*this); + return *this; + } + + ShiftedBitMatrix& operator=(const ShiftedBitMatrix& other) + { + ShiftedBitMatrix temp = other; + temp.swap(*this); + return *this; + } + + void swap(ShiftedBitMatrix& rhs) noexcept + { + using std::swap; + swap(m_matrix, rhs.m_matrix); + swap(m_offsets, rhs.m_offsets); + } + + bool test_bit(size_t row, size_t col, bool default_ = false) const noexcept + { + ptrdiff_t offset = m_offsets[row]; + + if (offset < 0) { + col += static_cast(-offset); + } + else if (col >= static_cast(offset)) { + col -= static_cast(offset); + } + /* bit on the left of the band */ + else { + return default_; + } + + size_t word_size = sizeof(value_type) * 8; + size_t col_word = col / word_size; + value_type col_mask = value_type(1) << (col % word_size); + + return bool(m_matrix[row][col_word] & col_mask); + } + + auto operator[](size_t row) noexcept + { + return m_matrix[row]; + } + + auto operator[](size_t row) const noexcept + { + return m_matrix[row]; + } + + void set_offset(size_t row, ptrdiff_t offset) + { + m_offsets[row] = offset; + } + +private: + BitMatrix m_matrix; + std::vector m_offsets; +}; + +} // namespace rapidfuzz::detail \ No newline at end of file diff --git a/src/external/rapidfuzz-cpp/rapidfuzz/details/PatternMatchVector.hpp b/src/external/rapidfuzz-cpp/rapidfuzz/details/PatternMatchVector.hpp new file mode 100644 index 00000000..9c56a656 --- /dev/null +++ b/src/external/rapidfuzz-cpp/rapidfuzz/details/PatternMatchVector.hpp @@ -0,0 +1,222 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright (c) 2022 Max Bachmann */ + +#pragma once +#include +#include +#include + +#include +#include +#include +#include + +namespace rapidfuzz::detail { + +struct BitvectorHashmap { + BitvectorHashmap() : m_map() + {} + + template + uint64_t get(CharT key) const noexcept + { + return m_map[lookup(static_cast(key))].value; + } + + template + uint64_t& operator[](CharT key) noexcept + { + uint32_t i = lookup(static_cast(key)); + m_map[i].key = static_cast(key); + return m_map[i].value; + } + +private: + /** + * lookup key inside the hashmap using a similar collision resolution + * strategy to CPython and Ruby + */ + uint32_t lookup(uint64_t key) const noexcept + { + uint32_t i = key % 128; + + if (!m_map[i].value || m_map[i].key == key) return i; + + uint64_t perturb = key; + while (true) { + i = (static_cast(i) * 5 + perturb + 1) % 128; + if (!m_map[i].value || m_map[i].key == key) return i; + + perturb >>= 5; + } + } + + struct MapElem { + uint64_t key = 0; + uint64_t value = 0; + }; + std::array m_map; +}; + +struct PatternMatchVector { + PatternMatchVector() : m_extendedAscii() + {} + + template + PatternMatchVector(const Range& s) : m_extendedAscii() + { + insert(s); + } + + size_t size() const noexcept + { + return 1; + } + + template + void insert(const Range& s) noexcept + { + uint64_t mask = 1; + for (const auto& ch : s) { + insert_mask(ch, mask); + mask <<= 1; + } + } + + template + void insert(CharT key, int64_t pos) noexcept + { + insert_mask(key, UINT64_C(1) << pos); + } + + uint64_t get(char key) const noexcept + { + /** treat char as value between 0 and 127 for performance reasons */ + return m_extendedAscii[static_cast(key)]; + } + + template + uint64_t get(CharT key) const noexcept + { + if (key >= 0 && key <= 255) + return m_extendedAscii[static_cast(key)]; + else + return m_map.get(key); + } + + template + uint64_t get(size_t block, CharT key) const noexcept + { + assert(block == 0); + (void)block; + return get(key); + } + + void insert_mask(char key, uint64_t mask) noexcept + { + /** treat char as value between 0 and 127 for performance reasons */ + m_extendedAscii[static_cast(key)] |= mask; + } + + template + void insert_mask(CharT key, uint64_t mask) noexcept + { + if (key >= 0 && key <= 255) + m_extendedAscii[static_cast(key)] |= mask; + else + m_map[key] |= mask; + } + +private: + BitvectorHashmap m_map; + std::array m_extendedAscii; +}; + +struct BlockPatternMatchVector { + BlockPatternMatchVector() = delete; + + BlockPatternMatchVector(size_t str_len) + : m_block_count(ceil_div(str_len, 64)), m_map(nullptr), m_extendedAscii(256, m_block_count, 0) + {} + + template + BlockPatternMatchVector(const Range& s) : BlockPatternMatchVector(s.size()) + { + insert(s); + } + + ~BlockPatternMatchVector() + { + delete[] m_map; + } + + size_t size() const noexcept + { + return m_block_count; + } + + template + void insert(size_t block, CharT ch, int pos) noexcept + { + uint64_t mask = UINT64_C(1) << pos; + insert_mask(block, ch, mask); + } + + /** + * @warning undefined behavior if iterator \p first is greater than \p last + * @tparam InputIt + * @param first + * @param last + */ + template + void insert(const Range& s) noexcept + { + uint64_t mask = 1; + size_t i = 0; + for (auto iter = s.begin(); iter != s.end(); ++iter, ++i) { + size_t block = i / 64; + insert_mask(block, *iter, mask); + mask = rotl(mask, 1); + } + } + + template + void insert_mask(size_t block, CharT key, uint64_t mask) noexcept + { + assert(block < size()); + if (key >= 0 && key <= 255) + m_extendedAscii[static_cast(key)][block] |= mask; + else { + if (!m_map) m_map = new BitvectorHashmap[m_block_count]; + m_map[block][key] |= mask; + } + } + + void insert_mask(size_t block, char key, uint64_t mask) noexcept + { + insert_mask(block, static_cast(key), mask); + } + + template + uint64_t get(size_t block, CharT key) const noexcept + { + if (key >= 0 && key <= 255) + return m_extendedAscii[static_cast(key)][block]; + else if (m_map) + return m_map[block].get(key); + else + return 0; + } + + uint64_t get(size_t block, char ch) const noexcept + { + return get(block, static_cast(ch)); + } + +private: + size_t m_block_count; + BitvectorHashmap* m_map; + BitMatrix m_extendedAscii; +}; + +} // namespace rapidfuzz::detail diff --git a/src/external/rapidfuzz-cpp/rapidfuzz/details/Range.hpp b/src/external/rapidfuzz-cpp/rapidfuzz/details/Range.hpp new file mode 100644 index 00000000..d8ac443e --- /dev/null +++ b/src/external/rapidfuzz-cpp/rapidfuzz/details/Range.hpp @@ -0,0 +1,230 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright (c) 2022 Max Bachmann */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace rapidfuzz::detail { + +static inline void assume(bool b) +{ +#if defined(__clang__) + __builtin_assume(b); +#elif defined(__GNUC__) || defined(__GNUG__) + if (!b) __builtin_unreachable(); +#elif defined(_MSC_VER) + __assume(b); +#endif +} + +template +CharT* to_begin(CharT* s) +{ + return s; +} + +template +auto to_begin(T& x) +{ + using std::begin; + return begin(x); +} + +template +CharT* to_end(CharT* s) +{ + assume(s != nullptr); + while (*s != 0) + ++s; + + return s; +} + +template +auto to_end(T& x) +{ + using std::end; + return end(x); +} + +template +class Range { + Iter _first; + Iter _last; + // todo we might not want to cache the size for iterators + // that can can retrieve the size in O(1) time + size_t _size; + +public: + using value_type = typename std::iterator_traits::value_type; + using iterator = Iter; + using reverse_iterator = std::reverse_iterator; + + constexpr Range(Iter first, Iter last) : _first(first), _last(last) + { + assert(std::distance(_first, _last) >= 0); + _size = static_cast(std::distance(_first, _last)); + } + + constexpr Range(Iter first, Iter last, size_t size) : _first(first), _last(last), _size(size) + {} + + template + constexpr Range(T& x) : _first(to_begin(x)), _last(to_end(x)) + { + assert(std::distance(_first, _last) >= 0); + _size = static_cast(std::distance(_first, _last)); + } + + constexpr iterator begin() const noexcept + { + return _first; + } + constexpr iterator end() const noexcept + { + return _last; + } + + constexpr reverse_iterator rbegin() const noexcept + { + return reverse_iterator(end()); + } + constexpr reverse_iterator rend() const noexcept + { + return reverse_iterator(begin()); + } + + constexpr size_t size() const + { + return _size; + } + + constexpr bool empty() const + { + return size() == 0; + } + explicit constexpr operator bool() const + { + return !empty(); + } + + template < + typename... Dummy, typename IterCopy = Iter, + typename = std::enable_if_t::iterator_category>>> + constexpr decltype(auto) operator[](size_t n) const + { + return _first[static_cast(n)]; + } + + constexpr void remove_prefix(size_t n) + { + if constexpr (std::is_base_of_v::iterator_category>) + _first += static_cast(n); + else + for (size_t i = 0; i < n; ++i) + _first++; + + _size -= n; + } + constexpr void remove_suffix(size_t n) + { + if constexpr (std::is_base_of_v::iterator_category>) + _last -= static_cast(n); + else + for (size_t i = 0; i < n; ++i) + _last--; + + _size -= n; + } + + constexpr Range subseq(size_t pos = 0, size_t count = std::numeric_limits::max()) + { + if (pos > size()) throw std::out_of_range("Index out of range in Range::substr"); + + Range res = *this; + res.remove_prefix(pos); + if (count < res.size()) res.remove_suffix(res.size() - count); + + return res; + } + + constexpr decltype(auto) front() const + { + return *(_first); + } + + constexpr decltype(auto) back() const + { + return *(_last - 1); + } + + constexpr Range reversed() const + { + return {rbegin(), rend(), _size}; + } + + friend std::ostream& operator<<(std::ostream& os, const Range& seq) + { + os << "["; + for (auto x : seq) + os << static_cast(x) << ", "; + os << "]"; + return os; + } +}; + +template +Range(T& x) -> Range; + +template +inline bool operator==(const Range& a, const Range& b) +{ + return std::equal(a.begin(), a.end(), b.begin(), b.end()); +} + +template +inline bool operator!=(const Range& a, const Range& b) +{ + return !(a == b); +} + +template +inline bool operator<(const Range& a, const Range& b) +{ + return (std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end())); +} + +template +inline bool operator>(const Range& a, const Range& b) +{ + return b < a; +} + +template +inline bool operator<=(const Range& a, const Range& b) +{ + return !(b < a); +} + +template +inline bool operator>=(const Range& a, const Range& b) +{ + return !(a < b); +} + +template +using RangeVec = std::vector>; + +} // namespace rapidfuzz::detail diff --git a/src/external/rapidfuzz-cpp/rapidfuzz/details/SplittedSentenceView.hpp b/src/external/rapidfuzz-cpp/rapidfuzz/details/SplittedSentenceView.hpp new file mode 100644 index 00000000..a6b06955 --- /dev/null +++ b/src/external/rapidfuzz-cpp/rapidfuzz/details/SplittedSentenceView.hpp @@ -0,0 +1,86 @@ +#pragma once +#include +#include +#include + +namespace rapidfuzz::detail { + +template +class SplittedSentenceView { +public: + using CharT = iter_value_t; + + SplittedSentenceView(RangeVec sentence) noexcept( + std::is_nothrow_move_constructible_v>) + : m_sentence(std::move(sentence)) + {} + + size_t dedupe(); + size_t size() const; + + size_t length() const + { + return size(); + } + + bool empty() const + { + return m_sentence.empty(); + } + + size_t word_count() const + { + return m_sentence.size(); + } + + std::vector join() const; + + const RangeVec& words() const + { + return m_sentence; + } + +private: + RangeVec m_sentence; +}; + +template +size_t SplittedSentenceView::dedupe() +{ + size_t old_word_count = word_count(); + m_sentence.erase(std::unique(m_sentence.begin(), m_sentence.end()), m_sentence.end()); + return old_word_count - word_count(); +} + +template +size_t SplittedSentenceView::size() const +{ + if (m_sentence.empty()) return 0; + + // there is a whitespace between each word + size_t result = m_sentence.size() - 1; + for (const auto& word : m_sentence) { + result += static_cast(std::distance(word.begin(), word.end())); + } + + return result; +} + +template +auto SplittedSentenceView::join() const -> std::vector +{ + if (m_sentence.empty()) { + return std::vector(); + } + + auto sentence_iter = m_sentence.begin(); + std::vector joined(sentence_iter->begin(), sentence_iter->end()); + ++sentence_iter; + for (; sentence_iter != m_sentence.end(); ++sentence_iter) { + joined.push_back(0x20); + joined.insert(joined.end(), sentence_iter->begin(), sentence_iter->end()); + } + return joined; +} + +} // namespace rapidfuzz::detail diff --git a/src/external/rapidfuzz-cpp/rapidfuzz/details/common.hpp b/src/external/rapidfuzz-cpp/rapidfuzz/details/common.hpp new file mode 100644 index 00000000..f157a5ae --- /dev/null +++ b/src/external/rapidfuzz-cpp/rapidfuzz/details/common.hpp @@ -0,0 +1,101 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright © 2021 Max Bachmann */ + +#pragma once +#include +#include +#include +#include +#include +#include + +#if defined(__APPLE__) && !defined(_LIBCPP_HAS_C11_FEATURES) +# include +#endif + +namespace rapidfuzz::detail { + +template +struct DecomposedSet { + SplittedSentenceView difference_ab; + SplittedSentenceView difference_ba; + SplittedSentenceView intersection; + DecomposedSet(SplittedSentenceView diff_ab, SplittedSentenceView diff_ba, + SplittedSentenceView intersect) + : difference_ab(std::move(diff_ab)), + difference_ba(std::move(diff_ba)), + intersection(std::move(intersect)) + {} +}; + +static inline size_t abs_diff(size_t a, size_t b) +{ + return a > b ? a - b : b - a; +} + +template +TO opt_static_cast(const FROM &value) +{ + if constexpr (std::is_same_v) + return value; + else + return static_cast(value); +} + +/** + * @defgroup Common Common + * Common utilities shared among multiple functions + * @{ + */ + +static inline double NormSim_to_NormDist(double score_cutoff, double imprecision = 0.00001) +{ + return std::min(1.0, 1.0 - score_cutoff + imprecision); +} + +template +DecomposedSet set_decomposition(SplittedSentenceView a, + SplittedSentenceView b); + +template +StringAffix remove_common_affix(Range& s1, Range& s2); + +template +size_t remove_common_prefix(Range& s1, Range& s2); + +template +size_t remove_common_suffix(Range& s1, Range& s2); + +template > +SplittedSentenceView sorted_split(InputIt first, InputIt last); + +static inline void* rf_aligned_alloc(size_t alignment, size_t size) +{ +#if defined(_WIN32) + return _aligned_malloc(size, alignment); +#elif defined(__APPLE__) && !defined(_LIBCPP_HAS_C11_FEATURES) + return _mm_malloc(size, alignment); +#elif defined(__ANDROID__) && __ANDROID_API__ > 16 + void* ptr = nullptr; + return posix_memalign(&ptr, alignment, size) ? nullptr : ptr; +#else + return aligned_alloc(alignment, size); +#endif +} + +static inline void rf_aligned_free(void* ptr) +{ +#if defined(_WIN32) + _aligned_free(ptr); +#elif defined(__APPLE__) && !defined(_LIBCPP_HAS_C11_FEATURES) + _mm_free(ptr); +#else + free(ptr); +#endif +} + +/**@}*/ + +} // namespace rapidfuzz::detail + +#include diff --git a/src/external/rapidfuzz-cpp/rapidfuzz/details/common_impl.hpp b/src/external/rapidfuzz-cpp/rapidfuzz/details/common_impl.hpp new file mode 100644 index 00000000..2d803442 --- /dev/null +++ b/src/external/rapidfuzz-cpp/rapidfuzz/details/common_impl.hpp @@ -0,0 +1,172 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright © 2020 Max Bachmann */ + +#include +#include +#include + +namespace rapidfuzz::detail { + +template +DecomposedSet set_decomposition(SplittedSentenceView a, + SplittedSentenceView b) +{ + a.dedupe(); + b.dedupe(); + + RangeVec intersection; + RangeVec difference_ab; + RangeVec difference_ba = b.words(); + + for (const auto& current_a : a.words()) { + auto element_b = std::find(difference_ba.begin(), difference_ba.end(), current_a); + + if (element_b != difference_ba.end()) { + difference_ba.erase(element_b); + intersection.push_back(current_a); + } + else { + difference_ab.push_back(current_a); + } + } + + return {difference_ab, difference_ba, intersection}; +} + +/** + * Removes common prefix of two string views + */ +template +size_t remove_common_prefix(Range& s1, Range& s2) +{ + auto first1 = std::begin(s1); + size_t prefix = static_cast( + std::distance(first1, std::mismatch(first1, std::end(s1), std::begin(s2), std::end(s2)).first)); + s1.remove_prefix(prefix); + s2.remove_prefix(prefix); + return prefix; +} + +/** + * Removes common suffix of two string views + */ +template +size_t remove_common_suffix(Range& s1, Range& s2) +{ + auto rfirst1 = std::rbegin(s1); + size_t suffix = static_cast( + std::distance(rfirst1, std::mismatch(rfirst1, std::rend(s1), std::rbegin(s2), std::rend(s2)).first)); + s1.remove_suffix(suffix); + s2.remove_suffix(suffix); + return suffix; +} + +/** + * Removes common affix of two string views + */ +template +StringAffix remove_common_affix(Range& s1, Range& s2) +{ + return StringAffix{remove_common_prefix(s1, s2), remove_common_suffix(s1, s2)}; +} + +template +struct is_space_dispatch_tag : std::integral_constant {}; + +template +struct is_space_dispatch_tag::type> + : std::integral_constant {}; + +/* + * Implementation of is_space for char types that are at least 2 Byte in size + */ +template +bool is_space_impl(const CharT ch, std::integral_constant) +{ + switch (ch) { + case 0x0009: + case 0x000A: + case 0x000B: + case 0x000C: + case 0x000D: + case 0x001C: + case 0x001D: + case 0x001E: + case 0x001F: + case 0x0020: + case 0x0085: + case 0x00A0: + case 0x1680: + case 0x2000: + case 0x2001: + case 0x2002: + case 0x2003: + case 0x2004: + case 0x2005: + case 0x2006: + case 0x2007: + case 0x2008: + case 0x2009: + case 0x200A: + case 0x2028: + case 0x2029: + case 0x202F: + case 0x205F: + case 0x3000: return true; + } + return false; +} + +/* + * Implementation of is_space for char types that are 1 Byte in size + */ +template +bool is_space_impl(const CharT ch, std::integral_constant) +{ + switch (ch) { + case 0x0009: + case 0x000A: + case 0x000B: + case 0x000C: + case 0x000D: + case 0x001C: + case 0x001D: + case 0x001E: + case 0x001F: + case 0x0020: return true; + } + return false; +} + +/* + * checks whether unicode characters have the bidirectional + * type 'WS', 'B' or 'S' or the category 'Zs' + */ +template +bool is_space(const CharT ch) +{ + return is_space_impl(ch, is_space_dispatch_tag{}); +} + +template +SplittedSentenceView sorted_split(InputIt first, InputIt last) +{ + RangeVec splitted; + auto second = first; + + for (; first != last; first = second + 1) { + second = std::find_if(first, last, is_space); + + if (first != second) { + splitted.emplace_back(first, second); + } + + if (second == last) break; + } + + std::sort(splitted.begin(), splitted.end()); + + return SplittedSentenceView(splitted); +} + +} // namespace rapidfuzz::detail diff --git a/src/external/rapidfuzz-cpp/rapidfuzz/details/distance.hpp b/src/external/rapidfuzz-cpp/rapidfuzz/details/distance.hpp new file mode 100644 index 00000000..263af7b2 --- /dev/null +++ b/src/external/rapidfuzz-cpp/rapidfuzz/details/distance.hpp @@ -0,0 +1,548 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright © 2022 Max Bachmann */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace rapidfuzz::detail { + +template +struct NormalizedMetricBase { + template >> + static double normalized_distance(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + Args... args, double score_cutoff, double score_hint) + { + return _normalized_distance(Range(first1, last1), Range(first2, last2), std::forward(args)..., + score_cutoff, score_hint); + } + + template + static double normalized_distance(const Sentence1& s1, const Sentence2& s2, Args... args, + double score_cutoff, double score_hint) + { + return _normalized_distance(Range(s1), Range(s2), std::forward(args)..., score_cutoff, + score_hint); + } + + template >> + static double normalized_similarity(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + Args... args, double score_cutoff, double score_hint) + { + return _normalized_similarity(Range(first1, last1), Range(first2, last2), std::forward(args)..., + score_cutoff, score_hint); + } + + template + static double normalized_similarity(const Sentence1& s1, const Sentence2& s2, Args... args, + double score_cutoff, double score_hint) + { + return _normalized_similarity(Range(s1), Range(s2), std::forward(args)..., score_cutoff, + score_hint); + } + +protected: + template + static double _normalized_distance(const Range& s1, const Range& s2, Args... args, + double score_cutoff, double score_hint) + { + auto maximum = T::maximum(s1, s2, args...); + auto cutoff_distance = + static_cast(std::ceil(static_cast(maximum) * score_cutoff)); + auto hint_distance = + static_cast(std::ceil(static_cast(maximum) * score_hint)); + auto dist = T::_distance(s1, s2, std::forward(args)..., cutoff_distance, hint_distance); + double norm_dist = (maximum != 0) ? static_cast(dist) / static_cast(maximum) : 0.0; + return (norm_dist <= score_cutoff) ? norm_dist : 1.0; + } + + template + static double _normalized_similarity(const Range& s1, const Range& s2, Args... args, + double score_cutoff, double score_hint) + { + double cutoff_score = NormSim_to_NormDist(score_cutoff); + double hint_score = NormSim_to_NormDist(score_hint); + double norm_dist = + _normalized_distance(s1, s2, std::forward(args)..., cutoff_score, hint_score); + double norm_sim = 1.0 - norm_dist; + return (norm_sim >= score_cutoff) ? norm_sim : 0.0; + } + + NormalizedMetricBase() + {} + friend T; +}; + +template +struct DistanceBase : public NormalizedMetricBase { + template >> + static ResType distance(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, Args... args, + ResType score_cutoff, ResType score_hint) + { + return T::_distance(Range(first1, last1), Range(first2, last2), std::forward(args)..., + score_cutoff, score_hint); + } + + template + static ResType distance(const Sentence1& s1, const Sentence2& s2, Args... args, ResType score_cutoff, + ResType score_hint) + { + return T::_distance(Range(s1), Range(s2), std::forward(args)..., score_cutoff, score_hint); + } + + template >> + static ResType similarity(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, Args... args, + ResType score_cutoff, ResType score_hint) + { + return _similarity(Range(first1, last1), Range(first2, last2), std::forward(args)..., + score_cutoff, score_hint); + } + + template + static ResType similarity(const Sentence1& s1, const Sentence2& s2, Args... args, ResType score_cutoff, + ResType score_hint) + { + return _similarity(Range(s1), Range(s2), std::forward(args)..., score_cutoff, score_hint); + } + +protected: + template + static ResType _similarity(Range s1, Range s2, Args... args, ResType score_cutoff, + ResType score_hint) + { + auto maximum = T::maximum(s1, s2, args...); + if (score_cutoff > maximum) return 0; + + score_hint = std::min(score_cutoff, score_hint); + ResType cutoff_distance = maximum - score_cutoff; + ResType hint_distance = maximum - score_hint; + ResType dist = T::_distance(s1, s2, std::forward(args)..., cutoff_distance, hint_distance); + ResType sim = maximum - dist; + return (sim >= score_cutoff) ? sim : 0; + } + + DistanceBase() + {} + friend T; +}; + +template +struct SimilarityBase : public NormalizedMetricBase { + template >> + static ResType distance(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, Args... args, + ResType score_cutoff, ResType score_hint) + { + return _distance(Range(first1, last1), Range(first2, last2), std::forward(args)..., + score_cutoff, score_hint); + } + + template + static ResType distance(const Sentence1& s1, const Sentence2& s2, Args... args, ResType score_cutoff, + ResType score_hint) + { + return _distance(Range(s1), Range(s2), std::forward(args)..., score_cutoff, score_hint); + } + + template >> + static ResType similarity(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, Args... args, + ResType score_cutoff, ResType score_hint) + { + return T::_similarity(Range(first1, last1), Range(first2, last2), std::forward(args)..., + score_cutoff, score_hint); + } + + template + static ResType similarity(const Sentence1& s1, const Sentence2& s2, Args... args, ResType score_cutoff, + ResType score_hint) + { + return T::_similarity(Range(s1), Range(s2), std::forward(args)..., score_cutoff, score_hint); + } + +protected: + template + static ResType _distance(const Range& s1, const Range& s2, Args... args, + ResType score_cutoff, ResType score_hint) + { + auto maximum = T::maximum(s1, s2, args...); + ResType cutoff_similarity = + (maximum >= score_cutoff) ? maximum - score_cutoff : static_cast(WorstSimilarity); + ResType hint_similarity = + (maximum >= score_hint) ? maximum - score_hint : static_cast(WorstSimilarity); + ResType sim = T::_similarity(s1, s2, std::forward(args)..., cutoff_similarity, hint_similarity); + ResType dist = maximum - sim; + + if constexpr (std::is_floating_point_v) + return (dist <= score_cutoff) ? dist : 1.0; + else + return (dist <= score_cutoff) ? dist : score_cutoff + 1; + } + + SimilarityBase() + {} + friend T; +}; + +template +struct CachedNormalizedMetricBase { + template + double normalized_distance(InputIt2 first2, InputIt2 last2, double score_cutoff = 1.0, + double score_hint = 1.0) const + { + return _normalized_distance(Range(first2, last2), score_cutoff, score_hint); + } + + template + double normalized_distance(const Sentence2& s2, double score_cutoff = 1.0, double score_hint = 1.0) const + { + return _normalized_distance(Range(s2), score_cutoff, score_hint); + } + + template + double normalized_similarity(InputIt2 first2, InputIt2 last2, double score_cutoff = 0.0, + double score_hint = 0.0) const + { + return _normalized_similarity(Range(first2, last2), score_cutoff, score_hint); + } + + template + double normalized_similarity(const Sentence2& s2, double score_cutoff = 0.0, + double score_hint = 0.0) const + { + return _normalized_similarity(Range(s2), score_cutoff, score_hint); + } + +protected: + template + double _normalized_distance(const Range& s2, double score_cutoff, double score_hint) const + { + const T& derived = static_cast(*this); + auto maximum = derived.maximum(s2); + auto cutoff_distance = + static_cast(std::ceil(static_cast(maximum) * score_cutoff)); + auto hint_distance = + static_cast(std::ceil(static_cast(maximum) * score_hint)); + double dist = static_cast(derived._distance(s2, cutoff_distance, hint_distance)); + double norm_dist = (maximum != 0) ? dist / static_cast(maximum) : 0.0; + return (norm_dist <= score_cutoff) ? norm_dist : 1.0; + } + + template + double _normalized_similarity(const Range& s2, double score_cutoff, double score_hint) const + { + double cutoff_score = NormSim_to_NormDist(score_cutoff); + double hint_score = NormSim_to_NormDist(score_hint); + double norm_dist = _normalized_distance(s2, cutoff_score, hint_score); + double norm_sim = 1.0 - norm_dist; + return (norm_sim >= score_cutoff) ? norm_sim : 0.0; + } + + CachedNormalizedMetricBase() + {} + friend T; +}; + +template +struct CachedDistanceBase : public CachedNormalizedMetricBase { + template + ResType distance(InputIt2 first2, InputIt2 last2, + ResType score_cutoff = static_cast(WorstDistance), + ResType score_hint = static_cast(WorstDistance)) const + { + const T& derived = static_cast(*this); + return derived._distance(Range(first2, last2), score_cutoff, score_hint); + } + + template + ResType distance(const Sentence2& s2, ResType score_cutoff = static_cast(WorstDistance), + ResType score_hint = static_cast(WorstDistance)) const + { + const T& derived = static_cast(*this); + return derived._distance(Range(s2), score_cutoff, score_hint); + } + + template + ResType similarity(InputIt2 first2, InputIt2 last2, + ResType score_cutoff = static_cast(WorstSimilarity), + ResType score_hint = static_cast(WorstSimilarity)) const + { + return _similarity(Range(first2, last2), score_cutoff, score_hint); + } + + template + ResType similarity(const Sentence2& s2, ResType score_cutoff = static_cast(WorstSimilarity), + ResType score_hint = static_cast(WorstSimilarity)) const + { + return _similarity(Range(s2), score_cutoff, score_hint); + } + +protected: + template + ResType _similarity(const Range& s2, ResType score_cutoff, ResType score_hint) const + { + const T& derived = static_cast(*this); + ResType maximum = derived.maximum(s2); + if (score_cutoff > maximum) return 0; + + score_hint = std::min(score_cutoff, score_hint); + ResType cutoff_distance = maximum - score_cutoff; + ResType hint_distance = maximum - score_hint; + ResType dist = derived._distance(s2, cutoff_distance, hint_distance); + ResType sim = maximum - dist; + return (sim >= score_cutoff) ? sim : 0; + } + + CachedDistanceBase() + {} + friend T; +}; + +template +struct CachedSimilarityBase : public CachedNormalizedMetricBase { + template + ResType distance(InputIt2 first2, InputIt2 last2, + ResType score_cutoff = static_cast(WorstDistance), + ResType score_hint = static_cast(WorstDistance)) const + { + return _distance(Range(first2, last2), score_cutoff, score_hint); + } + + template + ResType distance(const Sentence2& s2, ResType score_cutoff = static_cast(WorstDistance), + ResType score_hint = static_cast(WorstDistance)) const + { + return _distance(Range(s2), score_cutoff, score_hint); + } + + template + ResType similarity(InputIt2 first2, InputIt2 last2, + ResType score_cutoff = static_cast(WorstSimilarity), + ResType score_hint = static_cast(WorstSimilarity)) const + { + const T& derived = static_cast(*this); + return derived._similarity(Range(first2, last2), score_cutoff, score_hint); + } + + template + ResType similarity(const Sentence2& s2, ResType score_cutoff = static_cast(WorstSimilarity), + ResType score_hint = static_cast(WorstSimilarity)) const + { + const T& derived = static_cast(*this); + return derived._similarity(Range(s2), score_cutoff, score_hint); + } + +protected: + template + ResType _distance(const Range& s2, ResType score_cutoff, ResType score_hint) const + { + const T& derived = static_cast(*this); + ResType maximum = derived.maximum(s2); + ResType cutoff_similarity = (maximum > score_cutoff) ? maximum - score_cutoff : 0; + ResType hint_similarity = (maximum > score_hint) ? maximum - score_hint : 0; + ResType sim = derived._similarity(s2, cutoff_similarity, hint_similarity); + ResType dist = maximum - sim; + + if constexpr (std::is_floating_point_v) + return (dist <= score_cutoff) ? dist : 1.0; + else + return (dist <= score_cutoff) ? dist : score_cutoff + 1; + } + + CachedSimilarityBase() + {} + friend T; +}; + +template +struct MultiNormalizedMetricBase { + template + void normalized_distance(double* scores, size_t score_count, InputIt2 first2, InputIt2 last2, + double score_cutoff = 1.0) const + { + _normalized_distance(scores, score_count, Range(first2, last2), score_cutoff); + } + + template + void normalized_distance(double* scores, size_t score_count, const Sentence2& s2, + double score_cutoff = 1.0) const + { + _normalized_distance(scores, score_count, Range(s2), score_cutoff); + } + + template + void normalized_similarity(double* scores, size_t score_count, InputIt2 first2, InputIt2 last2, + double score_cutoff = 0.0) const + { + _normalized_similarity(scores, score_count, Range(first2, last2), score_cutoff); + } + + template + void normalized_similarity(double* scores, size_t score_count, const Sentence2& s2, + double score_cutoff = 0.0) const + { + _normalized_similarity(scores, score_count, Range(s2), score_cutoff); + } + +protected: + template + void _normalized_distance(double* scores, size_t score_count, const Range& s2, + double score_cutoff = 1.0) const + { + const T& derived = static_cast(*this); + if (score_count < derived.result_count()) + throw std::invalid_argument("scores has to have >= result_count() elements"); + + // reinterpretation only works when the types have the same size + ResType* scores_orig = nullptr; + if constexpr (sizeof(double) == sizeof(ResType)) + scores_orig = reinterpret_cast(scores); + else + scores_orig = new ResType[derived.result_count()]; + + derived.distance(scores_orig, derived.result_count(), s2); + + for (size_t i = 0; i < derived.get_input_count(); ++i) { + auto maximum = derived.maximum(i, s2); + double norm_dist = + (maximum != 0) ? static_cast(scores_orig[i]) / static_cast(maximum) : 0.0; + scores[i] = (norm_dist <= score_cutoff) ? norm_dist : 1.0; + } + + if constexpr (sizeof(double) != sizeof(ResType)) delete[] scores_orig; + } + + template + void _normalized_similarity(double* scores, size_t score_count, const Range& s2, + double score_cutoff) const + { + const T& derived = static_cast(*this); + _normalized_distance(scores, score_count, s2); + + for (size_t i = 0; i < derived.get_input_count(); ++i) { + double norm_sim = 1.0 - scores[i]; + scores[i] = (norm_sim >= score_cutoff) ? norm_sim : 0.0; + } + } + + MultiNormalizedMetricBase() + {} + friend T; +}; + +template +struct MultiDistanceBase : public MultiNormalizedMetricBase { + template + void distance(ResType* scores, size_t score_count, InputIt2 first2, InputIt2 last2, + ResType score_cutoff = static_cast(WorstDistance)) const + { + const T& derived = static_cast(*this); + derived._distance(scores, score_count, Range(first2, last2), score_cutoff); + } + + template + void distance(ResType* scores, size_t score_count, const Sentence2& s2, + ResType score_cutoff = static_cast(WorstDistance)) const + { + const T& derived = static_cast(*this); + derived._distance(scores, score_count, Range(s2), score_cutoff); + } + + template + void similarity(ResType* scores, size_t score_count, InputIt2 first2, InputIt2 last2, + ResType score_cutoff = static_cast(WorstSimilarity)) const + { + _similarity(scores, score_count, Range(first2, last2), score_cutoff); + } + + template + void similarity(ResType* scores, size_t score_count, const Sentence2& s2, + ResType score_cutoff = static_cast(WorstSimilarity)) const + { + _similarity(scores, score_count, Range(s2), score_cutoff); + } + +protected: + template + void _similarity(ResType* scores, size_t score_count, const Range& s2, + ResType score_cutoff) const + { + const T& derived = static_cast(*this); + derived._distance(scores, score_count, s2); + + for (size_t i = 0; i < derived.get_input_count(); ++i) { + ResType maximum = derived.maximum(i, s2); + ResType sim = maximum - scores[i]; + scores[i] = (sim >= score_cutoff) ? sim : 0; + } + } + + MultiDistanceBase() + {} + friend T; +}; + +template +struct MultiSimilarityBase : public MultiNormalizedMetricBase { + template + void distance(ResType* scores, size_t score_count, InputIt2 first2, InputIt2 last2, + ResType score_cutoff = static_cast(WorstDistance)) const + { + _distance(scores, score_count, Range(first2, last2), score_cutoff); + } + + template + void distance(ResType* scores, size_t score_count, const Sentence2& s2, + ResType score_cutoff = static_cast(WorstDistance)) const + { + _distance(scores, score_count, Range(s2), score_cutoff); + } + + template + void similarity(ResType* scores, size_t score_count, InputIt2 first2, InputIt2 last2, + ResType score_cutoff = static_cast(WorstSimilarity)) const + { + const T& derived = static_cast(*this); + derived._similarity(scores, score_count, Range(first2, last2), score_cutoff); + } + + template + void similarity(ResType* scores, size_t score_count, const Sentence2& s2, + ResType score_cutoff = static_cast(WorstSimilarity)) const + { + const T& derived = static_cast(*this); + derived._similarity(scores, score_count, Range(s2), score_cutoff); + } + +protected: + template + void _distance(ResType* scores, size_t score_count, const Range& s2, ResType score_cutoff) const + { + const T& derived = static_cast(*this); + derived._similarity(scores, score_count, s2); + + for (size_t i = 0; i < derived.get_input_count(); ++i) { + ResType maximum = derived.maximum(i, s2); + ResType dist = maximum - scores[i]; + + if constexpr (std::is_floating_point_v) + scores[i] = (dist <= score_cutoff) ? dist : 1.0; + else + scores[i] = (dist <= score_cutoff) ? dist : score_cutoff + 1; + } + } + + MultiSimilarityBase() + {} + friend T; +}; + +} // namespace rapidfuzz::detail diff --git a/src/external/rapidfuzz-cpp/rapidfuzz/details/intrinsics.hpp b/src/external/rapidfuzz-cpp/rapidfuzz/details/intrinsics.hpp new file mode 100644 index 00000000..d5bd0a14 --- /dev/null +++ b/src/external/rapidfuzz-cpp/rapidfuzz/details/intrinsics.hpp @@ -0,0 +1,212 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright © 2021 Max Bachmann */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +#if defined(_MSC_VER) && !defined(__clang__) +# include +#endif + +namespace rapidfuzz::detail { + +template +T bit_mask_lsb(size_t n) +{ + T mask = static_cast(-1); + if (n < sizeof(T) * 8) { + mask += static_cast(static_cast(1) << n); + } + return mask; +} + +template +bool bittest(T a, int bit) +{ + return (a >> bit) & 1; +} + +/* + * shift right without undefined behavior for shifts > bit width + */ +template +constexpr uint64_t shr64(uint64_t a, U shift) +{ + return (shift < 64) ? a >> shift : 0; +} + +/* + * shift left without undefined behavior for shifts > bit width + */ +template +constexpr uint64_t shl64(uint64_t a, U shift) +{ + return (shift < 64) ? a << shift : 0; +} + +constexpr uint64_t addc64(uint64_t a, uint64_t b, uint64_t carryin, uint64_t* carryout) +{ + /* todo should use _addcarry_u64 when available */ + a += carryin; + *carryout = a < carryin; + a += b; + *carryout |= a < b; + return a; +} + +template +constexpr T ceil_div(T a, U divisor) +{ + T _div = static_cast(divisor); + return a / _div + static_cast(a % _div != 0); +} + +static inline size_t popcount(uint64_t x) +{ + return std::bitset<64>(x).count(); +} + +static inline size_t popcount(uint32_t x) +{ + return std::bitset<32>(x).count(); +} + +static inline size_t popcount(uint16_t x) +{ + return std::bitset<16>(x).count(); +} + +static inline size_t popcount(uint8_t x) +{ + static constexpr uint8_t bit_count[256] = { + 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, + 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, + 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, + 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8}; + return bit_count[x]; +} + +template +constexpr T rotl(T x, unsigned int n) +{ + unsigned int num_bits = std::numeric_limits::digits; + assert(n < num_bits); + unsigned int count_mask = num_bits - 1; + +#if _MSC_VER && !defined(__clang__) +# pragma warning(push) +/* unary minus operator applied to unsigned type, result still unsigned */ +# pragma warning(disable : 4146) +#endif + return (x << n) | (x >> (-n & count_mask)); +#if _MSC_VER && !defined(__clang__) +# pragma warning(pop) +#endif +} + +/** + * Extract the lowest set bit from a. If no bits are set in a returns 0. + */ +template +constexpr T blsi(T a) +{ +#if _MSC_VER && !defined(__clang__) +# pragma warning(push) +/* unary minus operator applied to unsigned type, result still unsigned */ +# pragma warning(disable : 4146) +#endif + return a & -a; +#if _MSC_VER && !defined(__clang__) +# pragma warning(pop) +#endif +} + +/** + * Clear the lowest set bit in a. + */ +template +constexpr T blsr(T x) +{ + return x & (x - 1); +} + +/** + * Sets all the lower bits of the result to 1 up to and including lowest set bit (=1) in a. + * If a is zero, blsmsk sets all bits to 1. + */ +template +constexpr T blsmsk(T a) +{ + return a ^ (a - 1); +} + +#if defined(_MSC_VER) && !defined(__clang__) +static inline unsigned int countr_zero(uint32_t x) +{ + unsigned long trailing_zero = 0; + _BitScanForward(&trailing_zero, x); + return trailing_zero; +} + +# if defined(_M_ARM) || defined(_M_X64) +static inline unsigned int countr_zero(uint64_t x) +{ + unsigned long trailing_zero = 0; + _BitScanForward64(&trailing_zero, x); + return trailing_zero; +} +# else +static inline unsigned int countr_zero(uint64_t x) +{ + uint32_t msh = (uint32_t)(x >> 32); + uint32_t lsh = (uint32_t)(x & 0xFFFFFFFF); + if (lsh != 0) return countr_zero(lsh); + return 32 + countr_zero(msh); +} +# endif + +#else /* gcc / clang */ +static inline unsigned int countr_zero(uint32_t x) +{ + return static_cast(__builtin_ctz(x)); +} + +static inline unsigned int countr_zero(uint64_t x) +{ + return static_cast(__builtin_ctzll(x)); +} +#endif + +static inline unsigned int countr_zero(uint16_t x) +{ + return countr_zero(static_cast(x)); +} + +static inline unsigned int countr_zero(uint8_t x) +{ + return countr_zero(static_cast(x)); +} + +template +constexpr void unroll_impl(std::integer_sequence, F&& f) +{ + (f(std::integral_constant{}), ...); +} + +template +constexpr void unroll(F&& f) +{ + unroll_impl(std::make_integer_sequence{}, std::forward(f)); +} + +} // namespace rapidfuzz::detail diff --git a/src/external/rapidfuzz-cpp/rapidfuzz/details/simd.hpp b/src/external/rapidfuzz-cpp/rapidfuzz/details/simd.hpp new file mode 100644 index 00000000..358ebec6 --- /dev/null +++ b/src/external/rapidfuzz-cpp/rapidfuzz/details/simd.hpp @@ -0,0 +1,21 @@ + +/* SPDX-License-Identifier: MIT */ +/* Copyright © 2022 Max Bachmann */ +#pragma once + +/* RAPIDFUZZ_LTO_HACK is used to differentiate functions between different + * translation units to avoid warnings when using lto */ +#ifndef RAPIDFUZZ_EXCLUDE_SIMD +# if __AVX2__ +# define RAPIDFUZZ_SIMD +# define RAPIDFUZZ_AVX2 +# define RAPIDFUZZ_LTO_HACK 0 +# include + +# elif (defined(_M_AMD64) || defined(_M_X64)) || defined(__SSE2__) +# define RAPIDFUZZ_SIMD +# define RAPIDFUZZ_SSE2 +# define RAPIDFUZZ_LTO_HACK 1 +# include +# endif +#endif \ No newline at end of file diff --git a/src/external/rapidfuzz-cpp/rapidfuzz/details/simd_avx2.hpp b/src/external/rapidfuzz-cpp/rapidfuzz/details/simd_avx2.hpp new file mode 100644 index 00000000..65a446cd --- /dev/null +++ b/src/external/rapidfuzz-cpp/rapidfuzz/details/simd_avx2.hpp @@ -0,0 +1,647 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright © 2022 Max Bachmann */ +#pragma once + +#include +#include +#include +#include +#include + +namespace rapidfuzz { +namespace detail { +namespace simd_avx2 { + +template +class native_simd; + +template <> +class native_simd { +public: + using value_type = uint64_t; + + static constexpr int alignment = 32; + static const int size = 4; + __m256i xmm; + + native_simd() noexcept + {} + + native_simd(__m256i val) noexcept : xmm(val) + {} + + native_simd(uint64_t a) noexcept + { + xmm = _mm256_set1_epi64x(static_cast(a)); + } + + native_simd(const uint64_t* p) noexcept + { + load(p); + } + + operator __m256i() const noexcept + { + return xmm; + } + + native_simd load(const uint64_t* p) noexcept + { + xmm = _mm256_set_epi64x(static_cast(p[3]), static_cast(p[2]), + static_cast(p[1]), static_cast(p[0])); + return *this; + } + + void store(uint64_t* p) const noexcept + { + _mm256_store_si256(reinterpret_cast<__m256i*>(p), xmm); + } + + native_simd operator+(const native_simd b) const noexcept + { + return _mm256_add_epi64(xmm, b); + } + + native_simd& operator+=(const native_simd b) noexcept + { + xmm = _mm256_add_epi64(xmm, b); + return *this; + } + + native_simd operator-(const native_simd b) const noexcept + { + return _mm256_sub_epi64(xmm, b); + } + + native_simd operator-() const noexcept + { + return _mm256_sub_epi64(_mm256_setzero_si256(), xmm); + } + + native_simd& operator-=(const native_simd b) noexcept + { + xmm = _mm256_sub_epi64(xmm, b); + return *this; + } +}; + +template <> +class native_simd { +public: + using value_type = uint32_t; + + static constexpr int alignment = 32; + static const int size = 8; + __m256i xmm; + + native_simd() noexcept + {} + + native_simd(__m256i val) noexcept : xmm(val) + {} + + native_simd(uint32_t a) noexcept + { + xmm = _mm256_set1_epi32(static_cast(a)); + } + + native_simd(const uint64_t* p) noexcept + { + load(p); + } + + operator __m256i() const + { + return xmm; + } + + native_simd load(const uint64_t* p) noexcept + { + xmm = _mm256_set_epi64x(static_cast(p[3]), static_cast(p[2]), + static_cast(p[1]), static_cast(p[0])); + return *this; + } + + void store(uint32_t* p) const noexcept + { + _mm256_store_si256(reinterpret_cast<__m256i*>(p), xmm); + } + + native_simd operator+(const native_simd b) const noexcept + { + return _mm256_add_epi32(xmm, b); + } + + native_simd& operator+=(const native_simd b) noexcept + { + xmm = _mm256_add_epi32(xmm, b); + return *this; + } + + native_simd operator-() const noexcept + { + return _mm256_sub_epi32(_mm256_setzero_si256(), xmm); + } + + native_simd operator-(const native_simd b) const noexcept + { + return _mm256_sub_epi32(xmm, b); + } + + native_simd& operator-=(const native_simd b) noexcept + { + xmm = _mm256_sub_epi32(xmm, b); + return *this; + } +}; + +template <> +class native_simd { +public: + using value_type = uint16_t; + + static constexpr int alignment = 32; + static const int size = 16; + __m256i xmm; + + native_simd() noexcept + {} + + native_simd(__m256i val) : xmm(val) + {} + + native_simd(uint16_t a) noexcept + { + xmm = _mm256_set1_epi16(static_cast(a)); + } + + native_simd(const uint64_t* p) noexcept + { + load(p); + } + + operator __m256i() const noexcept + { + return xmm; + } + + native_simd load(const uint64_t* p) noexcept + { + xmm = _mm256_set_epi64x(static_cast(p[3]), static_cast(p[2]), + static_cast(p[1]), static_cast(p[0])); + return *this; + } + + void store(uint16_t* p) const noexcept + { + _mm256_store_si256(reinterpret_cast<__m256i*>(p), xmm); + } + + native_simd operator+(const native_simd b) const noexcept + { + return _mm256_add_epi16(xmm, b); + } + + native_simd& operator+=(const native_simd b) noexcept + { + xmm = _mm256_add_epi16(xmm, b); + return *this; + } + + native_simd operator-(const native_simd b) const noexcept + { + return _mm256_sub_epi16(xmm, b); + } + + native_simd operator-() const noexcept + { + return _mm256_sub_epi16(_mm256_setzero_si256(), xmm); + } + + native_simd& operator-=(const native_simd b) noexcept + { + xmm = _mm256_sub_epi16(xmm, b); + return *this; + } +}; + +template <> +class native_simd { +public: + using value_type = uint8_t; + + static constexpr int alignment = 32; + static const int size = 32; + __m256i xmm; + + native_simd() noexcept + {} + + native_simd(__m256i val) noexcept : xmm(val) + {} + + native_simd(uint8_t a) noexcept + { + xmm = _mm256_set1_epi8(static_cast(a)); + } + + native_simd(const uint64_t* p) noexcept + { + load(p); + } + + operator __m256i() const noexcept + { + return xmm; + } + + native_simd load(const uint64_t* p) noexcept + { + xmm = _mm256_set_epi64x(static_cast(p[3]), static_cast(p[2]), + static_cast(p[1]), static_cast(p[0])); + return *this; + } + + void store(uint8_t* p) const noexcept + { + _mm256_store_si256(reinterpret_cast<__m256i*>(p), xmm); + } + + native_simd operator+(const native_simd b) const noexcept + { + return _mm256_add_epi8(xmm, b); + } + + native_simd& operator+=(const native_simd b) noexcept + { + xmm = _mm256_add_epi8(xmm, b); + return *this; + } + + native_simd operator-(const native_simd b) const noexcept + { + return _mm256_sub_epi8(xmm, b); + } + + native_simd operator-() const noexcept + { + return _mm256_sub_epi8(_mm256_setzero_si256(), xmm); + } + + native_simd& operator-=(const native_simd b) noexcept + { + xmm = _mm256_sub_epi8(xmm, b); + return *this; + } +}; + +template +std::ostream& operator<<(std::ostream& os, const native_simd& a) +{ + alignas(native_simd::alignment) std::array::size> res; + a.store(&res[0]); + + for (size_t i = res.size() - 1; i != 0; i--) + os << std::bitset::digits>(res[i]) << "|"; + + os << std::bitset::digits>(res[0]); + return os; +} + +template +__m256i hadd_impl(__m256i x) noexcept; + +template <> +inline __m256i hadd_impl(__m256i x) noexcept +{ + return x; +} + +template <> +inline __m256i hadd_impl(__m256i x) noexcept +{ + const __m256i mask = _mm256_set1_epi16(0x001f); + __m256i y = _mm256_srli_si256(x, 1); + x = _mm256_add_epi16(x, y); + return _mm256_and_si256(x, mask); +} + +template <> +inline __m256i hadd_impl(__m256i x) noexcept +{ + const __m256i mask = _mm256_set1_epi32(0x0000003F); + x = hadd_impl(x); + __m256i y = _mm256_srli_si256(x, 2); + x = _mm256_add_epi32(x, y); + return _mm256_and_si256(x, mask); +} + +template <> +inline __m256i hadd_impl(__m256i x) noexcept +{ + return _mm256_sad_epu8(x, _mm256_setzero_si256()); +} + +/* based on the paper `Faster Population Counts Using AVX2 Instructions` */ +template +native_simd popcount_impl(const native_simd& v) noexcept +{ + __m256i lookup = _mm256_setr_epi8(0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 0, 1, 1, 2, 1, 2, 2, 3, + 1, 2, 2, 3, 2, 3, 3, 4); + const __m256i low_mask = _mm256_set1_epi8(0x0F); + __m256i lo = _mm256_and_si256(v, low_mask); + __m256i hi = _mm256_and_si256(_mm256_srli_epi32(v, 4), low_mask); + __m256i popcnt1 = _mm256_shuffle_epi8(lookup, lo); + __m256i popcnt2 = _mm256_shuffle_epi8(lookup, hi); + __m256i total = _mm256_add_epi8(popcnt1, popcnt2); + return hadd_impl(total); +} + +template +std::array::size> popcount(const native_simd& a) noexcept +{ + alignas(native_simd::alignment) std::array::size> res; + popcount_impl(a).store(&res[0]); + return res; +} + +// function andnot: a & ~ b +template +native_simd andnot(const native_simd& a, const native_simd& b) +{ + return _mm256_andnot_si256(b, a); +} + +static inline native_simd operator==(const native_simd& a, + const native_simd& b) noexcept +{ + return _mm256_cmpeq_epi8(a, b); +} + +static inline native_simd operator==(const native_simd& a, + const native_simd& b) noexcept +{ + return _mm256_cmpeq_epi16(a, b); +} + +static inline native_simd operator==(const native_simd& a, + const native_simd& b) noexcept +{ + return _mm256_cmpeq_epi32(a, b); +} + +static inline native_simd operator==(const native_simd& a, + const native_simd& b) noexcept +{ + return _mm256_cmpeq_epi64(a, b); +} + +template +static inline native_simd operator!=(const native_simd& a, const native_simd& b) noexcept +{ + return ~(a == b); +} + +static inline native_simd operator<<(const native_simd& a, int b) noexcept +{ + char mask = static_cast(0xFF >> b); + __m256i am = _mm256_and_si256(a, _mm256_set1_epi8(mask)); + return _mm256_slli_epi16(am, b); +} + +static inline native_simd operator<<(const native_simd& a, int b) noexcept +{ + return _mm256_slli_epi16(a, b); +} + +static inline native_simd operator<<(const native_simd& a, int b) noexcept +{ + return _mm256_slli_epi32(a, b); +} + +static inline native_simd operator<<(const native_simd& a, int b) noexcept +{ + return _mm256_slli_epi64(a, b); +} + +static inline native_simd operator>>(const native_simd& a, int b) noexcept +{ + char mask = static_cast(0xFF << b); + __m256i am = _mm256_and_si256(a, _mm256_set1_epi8(mask)); + return _mm256_srli_epi16(am, b); +} + +static inline native_simd operator>>(const native_simd& a, int b) noexcept +{ + return _mm256_srli_epi16(a, b); +} + +static inline native_simd operator>>(const native_simd& a, int b) noexcept +{ + return _mm256_srli_epi32(a, b); +} + +static inline native_simd operator>>(const native_simd& a, int b) noexcept +{ + return _mm256_srli_epi64(a, b); +} + +template +native_simd operator&(const native_simd& a, const native_simd& b) noexcept +{ + return _mm256_and_si256(a, b); +} + +template +native_simd operator&=(native_simd& a, const native_simd& b) noexcept +{ + a = a & b; + return a; +} + +template +native_simd operator|(const native_simd& a, const native_simd& b) noexcept +{ + return _mm256_or_si256(a, b); +} + +template +native_simd operator|=(native_simd& a, const native_simd& b) noexcept +{ + a = a | b; + return a; +} + +template +native_simd operator^(const native_simd& a, const native_simd& b) noexcept +{ + return _mm256_xor_si256(a, b); +} + +template +native_simd operator^=(native_simd& a, const native_simd& b) noexcept +{ + a = a ^ b; + return a; +} + +template +native_simd operator~(const native_simd& a) noexcept +{ + return _mm256_xor_si256(a, _mm256_set1_epi32(-1)); +} + +// potentially we want a special native_simd for this +static inline native_simd operator>=(const native_simd& a, + const native_simd& b) noexcept +{ + return _mm256_cmpeq_epi8(_mm256_max_epu8(a, b), a); // a == max(a,b) +} + +static inline native_simd operator>=(const native_simd& a, + const native_simd& b) noexcept +{ + return _mm256_cmpeq_epi16(_mm256_max_epu16(a, b), a); // a == max(a,b) +} + +static inline native_simd operator>=(const native_simd& a, + const native_simd& b) noexcept +{ + return _mm256_cmpeq_epi32(_mm256_max_epu32(a, b), a); // a == max(a,b) +} + +static inline native_simd operator>(const native_simd& a, + const native_simd& b) noexcept; + +static inline native_simd operator>=(const native_simd& a, + const native_simd& b) noexcept +{ + return ~(b > a); +} + +template +static inline native_simd operator<=(const native_simd& a, const native_simd& b) noexcept +{ + return b >= a; +} + +static inline native_simd operator>(const native_simd& a, + const native_simd& b) noexcept +{ + return ~(b >= a); +} + +static inline native_simd operator>(const native_simd& a, + const native_simd& b) noexcept +{ + return ~(b >= a); +} + +static inline native_simd operator>(const native_simd& a, + const native_simd& b) noexcept +{ + __m256i signbit = _mm256_set1_epi32(static_cast(0x80000000)); + __m256i a1 = _mm256_xor_si256(a, signbit); + __m256i b1 = _mm256_xor_si256(b, signbit); + return _mm256_cmpgt_epi32(a1, b1); // signed compare +} + +static inline native_simd operator>(const native_simd& a, + const native_simd& b) noexcept +{ + __m256i sign64 = native_simd(0x8000000000000000); + __m256i aflip = _mm256_xor_si256(a, sign64); + __m256i bflip = _mm256_xor_si256(b, sign64); + return _mm256_cmpgt_epi64(aflip, bflip); // signed compare +} + +template +static inline native_simd operator<(const native_simd& a, const native_simd& b) noexcept +{ + return b > a; +} + +template +static inline native_simd max8(const native_simd& a, const native_simd& b) noexcept +{ + return _mm256_max_epu8(a, b); +} + +template +static inline native_simd max16(const native_simd& a, const native_simd& b) noexcept +{ + return _mm256_max_epu16(a, b); +} + +template +static inline native_simd max32(const native_simd& a, const native_simd& b) noexcept +{ + return _mm256_max_epu32(a, b); +} + +template +static inline native_simd min8(const native_simd& a, const native_simd& b) noexcept +{ + return _mm256_min_epu8(a, b); +} + +template +static inline native_simd min16(const native_simd& a, const native_simd& b) noexcept +{ + return _mm256_min_epu16(a, b); +} + +template +static inline native_simd min32(const native_simd& a, const native_simd& b) noexcept +{ + return _mm256_min_epu32(a, b); +} + +/* taken from https://stackoverflow.com/a/51807800/11335032 */ +static inline native_simd sllv(const native_simd& a, + const native_simd& count_) noexcept +{ + __m256i mask_hi = _mm256_set1_epi32(static_cast(0xFF00FF00)); + __m256i multiplier_lut = _mm256_set_epi8(0, 0, 0, 0, 0, 0, 0, 0, char(128), 64, 32, 16, 8, 4, 2, 1, 0, 0, + 0, 0, 0, 0, 0, 0, char(128), 64, 32, 16, 8, 4, 2, 1); + + __m256i count_sat = + _mm256_min_epu8(count_, _mm256_set1_epi8(8)); /* AVX shift counts are not masked. So a_i << n_i = 0 + for n_i >= 8. count_sat is always less than 9.*/ + __m256i multiplier = _mm256_shuffle_epi8( + multiplier_lut, count_sat); /* Select the right multiplication factor in the lookup table. */ + __m256i x_lo = _mm256_mullo_epi16(a, multiplier); /* Unfortunately _mm256_mullo_epi8 doesn't exist. Split + the 16 bit elements in a high and low part. */ + + __m256i multiplier_hi = _mm256_srli_epi16(multiplier, 8); /* The multiplier of the high bits. */ + __m256i a_hi = _mm256_and_si256(a, mask_hi); /* Mask off the low bits. */ + __m256i x_hi = _mm256_mullo_epi16(a_hi, multiplier_hi); + __m256i x = _mm256_blendv_epi8(x_lo, x_hi, mask_hi); /* Merge the high and low part. */ + return x; +} + +/* taken from https://stackoverflow.com/a/51805592/11335032 */ +static inline native_simd sllv(const native_simd& a, + const native_simd& count) noexcept +{ + const __m256i mask = _mm256_set1_epi32(static_cast(0xFFFF0000)); + __m256i low_half = _mm256_sllv_epi32(a, _mm256_andnot_si256(mask, count)); + __m256i high_half = _mm256_sllv_epi32(_mm256_and_si256(mask, a), _mm256_srli_epi32(count, 16)); + return _mm256_blend_epi16(low_half, high_half, 0xAA); +} + +static inline native_simd sllv(const native_simd& a, + const native_simd& count) noexcept +{ + return _mm256_sllv_epi32(a, count); +} + +static inline native_simd sllv(const native_simd& a, + const native_simd& count) noexcept +{ + return _mm256_sllv_epi64(a, count); +} + +} // namespace simd_avx2 +} // namespace detail +} // namespace rapidfuzz diff --git a/src/external/rapidfuzz-cpp/rapidfuzz/details/simd_sse2.hpp b/src/external/rapidfuzz-cpp/rapidfuzz/details/simd_sse2.hpp new file mode 100644 index 00000000..da0a5378 --- /dev/null +++ b/src/external/rapidfuzz-cpp/rapidfuzz/details/simd_sse2.hpp @@ -0,0 +1,602 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright © 2022 Max Bachmann */ +#pragma once + +#include +#include +#include +#include +#include + +namespace rapidfuzz { +namespace detail { +namespace simd_sse2 { + +template +class native_simd; + +template <> +class native_simd { +public: + static constexpr int alignment = 16; + static const int size = 2; + __m128i xmm; + + native_simd() noexcept + {} + + native_simd(__m128i val) noexcept : xmm(val) + {} + + native_simd(uint64_t a) noexcept + { + xmm = _mm_set1_epi64x(static_cast(a)); + } + + native_simd(const uint64_t* p) noexcept + { + load(p); + } + + operator __m128i() const noexcept + { + return xmm; + } + + native_simd load(const uint64_t* p) noexcept + { + xmm = _mm_set_epi64x(static_cast(p[1]), static_cast(p[0])); + return *this; + } + + void store(uint64_t* p) const noexcept + { + _mm_store_si128(reinterpret_cast<__m128i*>(p), xmm); + } + + native_simd operator+(const native_simd b) const noexcept + { + return _mm_add_epi64(xmm, b); + } + + native_simd& operator+=(const native_simd b) noexcept + { + xmm = _mm_add_epi64(xmm, b); + return *this; + } + + native_simd operator-(const native_simd b) const noexcept + { + return _mm_sub_epi64(xmm, b); + } + + native_simd operator-() const noexcept + { + return _mm_sub_epi64(_mm_setzero_si128(), xmm); + } + + native_simd& operator-=(const native_simd b) noexcept + { + xmm = _mm_sub_epi64(xmm, b); + return *this; + } +}; + +template <> +class native_simd { +public: + static constexpr int alignment = 16; + static const int size = 4; + __m128i xmm; + + native_simd() noexcept + {} + + native_simd(__m128i val) noexcept : xmm(val) + {} + + native_simd(uint32_t a) noexcept + { + xmm = _mm_set1_epi32(static_cast(a)); + } + + native_simd(const uint64_t* p) noexcept + { + load(p); + } + + operator __m128i() const noexcept + { + return xmm; + } + + native_simd load(const uint64_t* p) noexcept + { + xmm = _mm_set_epi64x(static_cast(p[1]), static_cast(p[0])); + return *this; + } + + void store(uint32_t* p) const noexcept + { + _mm_store_si128(reinterpret_cast<__m128i*>(p), xmm); + } + + native_simd operator+(const native_simd b) const noexcept + { + return _mm_add_epi32(xmm, b); + } + + native_simd& operator+=(const native_simd b) noexcept + { + xmm = _mm_add_epi32(xmm, b); + return *this; + } + + native_simd operator-(const native_simd b) const noexcept + { + return _mm_sub_epi32(xmm, b); + } + + native_simd operator-() const noexcept + { + return _mm_sub_epi32(_mm_setzero_si128(), xmm); + } + + native_simd& operator-=(const native_simd b) noexcept + { + xmm = _mm_sub_epi32(xmm, b); + return *this; + } +}; + +template <> +class native_simd { +public: + static constexpr int alignment = 16; + static const int size = 8; + __m128i xmm; + + native_simd() noexcept + {} + + native_simd(__m128i val) noexcept : xmm(val) + {} + + native_simd(uint16_t a) noexcept + { + xmm = _mm_set1_epi16(static_cast(a)); + } + + native_simd(const uint64_t* p) noexcept + { + load(p); + } + + operator __m128i() const noexcept + { + return xmm; + } + + native_simd load(const uint64_t* p) noexcept + { + xmm = _mm_set_epi64x(static_cast(p[1]), static_cast(p[0])); + return *this; + } + + void store(uint16_t* p) const noexcept + { + _mm_store_si128(reinterpret_cast<__m128i*>(p), xmm); + } + + native_simd operator+(const native_simd b) const noexcept + { + return _mm_add_epi16(xmm, b); + } + + native_simd& operator+=(const native_simd b) noexcept + { + xmm = _mm_add_epi16(xmm, b); + return *this; + } + + native_simd operator-(const native_simd b) const noexcept + { + return _mm_sub_epi16(xmm, b); + } + + native_simd operator-() const noexcept + { + return _mm_sub_epi16(_mm_setzero_si128(), xmm); + } + + native_simd& operator-=(const native_simd b) noexcept + { + xmm = _mm_sub_epi16(xmm, b); + return *this; + } +}; + +template <> +class native_simd { +public: + static constexpr int alignment = 16; + static const int size = 16; + __m128i xmm; + + native_simd() noexcept + {} + + native_simd(__m128i val) noexcept : xmm(val) + {} + + native_simd(uint8_t a) noexcept + { + xmm = _mm_set1_epi8(static_cast(a)); + } + + native_simd(const uint64_t* p) noexcept + { + load(p); + } + + operator __m128i() const noexcept + { + return xmm; + } + + native_simd load(const uint64_t* p) noexcept + { + xmm = _mm_set_epi64x(static_cast(p[1]), static_cast(p[0])); + return *this; + } + + void store(uint8_t* p) const noexcept + { + _mm_store_si128(reinterpret_cast<__m128i*>(p), xmm); + } + + native_simd operator+(const native_simd b) const noexcept + { + return _mm_add_epi8(xmm, b); + } + + native_simd& operator+=(const native_simd b) noexcept + { + xmm = _mm_add_epi8(xmm, b); + return *this; + } + + native_simd operator-(const native_simd b) const noexcept + { + return _mm_sub_epi8(xmm, b); + } + + native_simd operator-() const noexcept + { + return _mm_sub_epi8(_mm_setzero_si128(), xmm); + } + + native_simd& operator-=(const native_simd b) noexcept + { + xmm = _mm_sub_epi8(xmm, b); + return *this; + } +}; + +template +std::ostream& operator<<(std::ostream& os, const native_simd& a) +{ + alignas(native_simd::alignment) std::array::size> res; + a.store(&res[0]); + + for (size_t i = res.size() - 1; i != 0; i--) + os << std::bitset::digits>(res[i]) << "|"; + + os << std::bitset::digits>(res[0]); + return os; +} + +template +__m128i hadd_impl(__m128i x) noexcept; + +template <> +inline __m128i hadd_impl(__m128i x) noexcept +{ + return x; +} + +template <> +inline __m128i hadd_impl(__m128i x) noexcept +{ + const __m128i mask = _mm_set1_epi16(0x001f); + __m128i y = _mm_srli_si128(x, 1); + x = _mm_add_epi16(x, y); + return _mm_and_si128(x, mask); +} + +template <> +inline __m128i hadd_impl(__m128i x) noexcept +{ + const __m128i mask = _mm_set1_epi32(0x0000003f); + x = hadd_impl(x); + __m128i y = _mm_srli_si128(x, 2); + x = _mm_add_epi32(x, y); + return _mm_and_si128(x, mask); +} + +template <> +inline __m128i hadd_impl(__m128i x) noexcept +{ + return _mm_sad_epu8(x, _mm_setzero_si128()); +} + +template +native_simd popcount_impl(const native_simd& v) noexcept +{ + const __m128i m1 = _mm_set1_epi8(0x55); + const __m128i m2 = _mm_set1_epi8(0x33); + const __m128i m3 = _mm_set1_epi8(0x0F); + + /* Note: if we returned x here it would be like _mm_popcnt_epi1(x) */ + __m128i y; + __m128i x = v; + /* add even and odd bits*/ + y = _mm_srli_epi64(x, 1); // put even bits in odd place + y = _mm_and_si128(y, m1); // mask out the even bits (0x55) + x = _mm_subs_epu8(x, y); // shortcut to mask even bits and add + /* if we just returned x here it would be like popcnt_epi2(x) */ + /* now add the half nibbles */ + y = _mm_srli_epi64(x, 2); // move half nibbles in place to add + y = _mm_and_si128(y, m2); // mask off the extra half nibbles (0x0f) + x = _mm_and_si128(x, m2); // ditto + x = _mm_adds_epu8(x, y); // totals are a maximum of 5 bits (0x1f) + /* if we just returned x here it would be like popcnt_epi4(x) */ + /* now add the nibbles */ + y = _mm_srli_epi64(x, 4); // move nibbles in place to add + x = _mm_adds_epu8(x, y); // totals are a maximum of 6 bits (0x3f) + x = _mm_and_si128(x, m3); // mask off the extra bits + + /* todo use when sse3 available + __m128i lookup = _mm_setr_epi8(0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4); + const __m128i low_mask = _mm_set1_epi8(0x0F); + __m128i lo = _mm_and_si128(v, low_mask); + __m128i hi = _mm_and_si256(_mm_srli_epi32(v, 4), low_mask); + __m128i popcnt1 = _mm_shuffle_epi8(lookup, lo); + __m128i popcnt2 = _mm_shuffle_epi8(lookup, hi); + __m128i total = _mm_add_epi8(popcnt1, popcnt2);*/ + + return hadd_impl(x); +} + +template +std::array::size> popcount(const native_simd& a) noexcept +{ + alignas(native_simd::alignment) std::array::size> res; + popcount_impl(a).store(&res[0]); + return res; +} + +// function andnot: a & ~ b +template +native_simd andnot(const native_simd& a, const native_simd& b) +{ + return _mm_andnot_si128(b, a); +} + +static inline native_simd operator==(const native_simd& a, + const native_simd& b) noexcept +{ + return _mm_cmpeq_epi8(a, b); +} + +static inline native_simd operator==(const native_simd& a, + const native_simd& b) noexcept +{ + return _mm_cmpeq_epi16(a, b); +} + +static inline native_simd operator==(const native_simd& a, + const native_simd& b) noexcept +{ + return _mm_cmpeq_epi32(a, b); +} + +static inline native_simd operator==(const native_simd& a, + const native_simd& b) noexcept +{ + // no 64 compare instruction. Do two 32 bit compares + __m128i com32 = _mm_cmpeq_epi32(a, b); // 32 bit compares + __m128i com32s = _mm_shuffle_epi32(com32, 0xB1); // swap low and high dwords + __m128i test = _mm_and_si128(com32, com32s); // low & high + __m128i teste = _mm_srai_epi32(test, 31); // extend sign bit to 32 bits + __m128i testee = _mm_shuffle_epi32(teste, 0xF5); // extend sign bit to 64 bits + return testee; +} + +template +static inline native_simd operator!=(const native_simd& a, const native_simd& b) noexcept +{ + return ~(a == b); +} + +static inline native_simd operator<<(const native_simd& a, int b) noexcept +{ + char mask = static_cast(0xFF >> b); + __m128i am = _mm_and_si128(a, _mm_set1_epi8(mask)); + return _mm_slli_epi16(am, b); +} + +static inline native_simd operator<<(const native_simd& a, int b) noexcept +{ + return _mm_slli_epi16(a, b); +} + +static inline native_simd operator<<(const native_simd& a, int b) noexcept +{ + return _mm_slli_epi32(a, b); +} + +static inline native_simd operator<<(const native_simd& a, int b) noexcept +{ + return _mm_slli_epi64(a, b); +} + +static inline native_simd operator>>(const native_simd& a, int b) noexcept +{ + char mask = static_cast(0xFF << b); + __m128i am = _mm_and_si128(a, _mm_set1_epi8(mask)); + return _mm_srli_epi16(am, b); +} + +static inline native_simd operator>>(const native_simd& a, int b) noexcept +{ + return _mm_srli_epi16(a, b); +} + +static inline native_simd operator>>(const native_simd& a, int b) noexcept +{ + return _mm_srli_epi32(a, b); +} + +static inline native_simd operator>>(const native_simd& a, int b) noexcept +{ + return _mm_srli_epi64(a, b); +} + +template +native_simd operator&(const native_simd& a, const native_simd& b) noexcept +{ + return _mm_and_si128(a, b); +} + +template +native_simd operator&=(native_simd& a, const native_simd& b) noexcept +{ + a = a & b; + return a; +} + +template +native_simd operator|(const native_simd& a, const native_simd& b) noexcept +{ + return _mm_or_si128(a, b); +} + +template +native_simd operator|=(native_simd& a, const native_simd& b) noexcept +{ + a = a | b; + return a; +} + +template +native_simd operator^(const native_simd& a, const native_simd& b) noexcept +{ + return _mm_xor_si128(a, b); +} + +template +native_simd operator^=(native_simd& a, const native_simd& b) noexcept +{ + a = a ^ b; + return a; +} + +template +native_simd operator~(const native_simd& a) noexcept +{ + return _mm_xor_si128(a, _mm_set1_epi32(-1)); +} + +// potentially we want a special native_simd for this +static inline native_simd operator>=(const native_simd& a, + const native_simd& b) noexcept +{ + return _mm_cmpeq_epi8(_mm_max_epu8(a, b), a); // a == max(a,b) +} + +static inline native_simd operator>=(const native_simd& a, + const native_simd& b) noexcept +{ + /* sse4.1 */ +#if 0 + return _mm_cmpeq_epi16(_mm_max_epu16(a, b), a); // a == max(a,b) +#endif + + __m128i s = _mm_subs_epu16(b, a); // b-a, saturated + return _mm_cmpeq_epi16(s, _mm_setzero_si128()); // s == 0 +} + +static inline native_simd operator>(const native_simd& a, + const native_simd& b) noexcept; +static inline native_simd operator>(const native_simd& a, + const native_simd& b) noexcept; + +static inline native_simd operator>=(const native_simd& a, + const native_simd& b) noexcept +{ + /* sse4.1 */ +#if 0 + return (Vec4ib)_mm_cmpeq_epi32(_mm_max_epu32(a, b), a); // a == max(a,b) +#endif + + return ~(b > a); +} + +static inline native_simd operator>=(const native_simd& a, + const native_simd& b) noexcept +{ + return ~(b > a); +} + +template +static inline native_simd operator<=(const native_simd& a, const native_simd& b) noexcept +{ + return b >= a; +} + +static inline native_simd operator>(const native_simd& a, + const native_simd& b) noexcept +{ + return ~(b >= a); +} + +static inline native_simd operator>(const native_simd& a, + const native_simd& b) noexcept +{ + return ~(b >= a); +} + +static inline native_simd operator>(const native_simd& a, + const native_simd& b) noexcept +{ + __m128i signbit = _mm_set1_epi32(static_cast(0x80000000)); + __m128i a1 = _mm_xor_si128(a, signbit); + __m128i b1 = _mm_xor_si128(b, signbit); + return _mm_cmpgt_epi32(a1, b1); // signed compare +} + +static inline native_simd operator>(const native_simd& a, + const native_simd& b) noexcept +{ + __m128i sign32 = _mm_set1_epi32(static_cast(0x80000000)); // sign bit of each dword + __m128i aflip = _mm_xor_si128(a, sign32); // a with sign bits flipped to use signed compare + __m128i bflip = _mm_xor_si128(b, sign32); // b with sign bits flipped to use signed compare + __m128i equal = _mm_cmpeq_epi32(a, b); // a == b, dwords + __m128i bigger = _mm_cmpgt_epi32(aflip, bflip); // a > b, dwords + __m128i biggerl = _mm_shuffle_epi32(bigger, 0xA0); // a > b, low dwords copied to high dwords + __m128i eqbig = _mm_and_si128(equal, biggerl); // high part equal and low part bigger + __m128i hibig = _mm_or_si128(bigger, eqbig); // high part bigger or high part equal and low part bigger + __m128i big = _mm_shuffle_epi32(hibig, 0xF5); // result copied to low part + return big; +} + +template +static inline native_simd operator<(const native_simd& a, const native_simd& b) noexcept +{ + return b > a; +} + +} // namespace simd_sse2 +} // namespace detail +} // namespace rapidfuzz diff --git a/src/external/rapidfuzz-cpp/rapidfuzz/details/type_traits.hpp b/src/external/rapidfuzz-cpp/rapidfuzz/details/type_traits.hpp new file mode 100644 index 00000000..06b6b1e2 --- /dev/null +++ b/src/external/rapidfuzz-cpp/rapidfuzz/details/type_traits.hpp @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright © 2020 Max Bachmann */ + +#pragma once +#include + +#include +#include + +namespace rapidfuzz { + +namespace detail { +template +auto inner_type(T const*) -> T; + +template +auto inner_type(T const&) -> typename T::value_type; +} // namespace detail + +template +using char_type = decltype(detail::inner_type(std::declval())); + +/* backport of std::iter_value_t from C++20 + * This does not cover the complete functionality, but should be enough for + * the use cases in this library + */ +template +using iter_value_t = typename std::iterator_traits::value_type; + +// taken from +// https://stackoverflow.com/questions/16893992/check-if-type-can-be-explicitly-converted +template +struct is_explicitly_convertible { + template + static void f(T); + + template + static constexpr auto test(int /*unused*/) -> decltype(f(static_cast(std::declval())), true) + { + return true; + } + + template + static constexpr auto test(...) -> bool + { + return false; + } + + static bool const value = test(0); +}; + +} // namespace rapidfuzz diff --git a/src/external/rapidfuzz-cpp/rapidfuzz/details/types.hpp b/src/external/rapidfuzz-cpp/rapidfuzz/details/types.hpp new file mode 100644 index 00000000..ac3c4559 --- /dev/null +++ b/src/external/rapidfuzz-cpp/rapidfuzz/details/types.hpp @@ -0,0 +1,596 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright © 2020 Max Bachmann */ + +#pragma once + +#include +#include +#include +#include + +namespace rapidfuzz { + +struct StringAffix { + size_t prefix_len; + size_t suffix_len; +}; + +struct LevenshteinWeightTable { + size_t insert_cost; + size_t delete_cost; + size_t replace_cost; +}; + +/** + * @brief Edit operation types used by the Levenshtein distance + */ +enum class EditType { + None = 0, /**< No Operation required */ + Replace = 1, /**< Replace a character if a string by another character */ + Insert = 2, /**< Insert a character into a string */ + Delete = 3 /**< Delete a character from a string */ +}; + +/** + * @brief Edit operations used by the Levenshtein distance + * + * This represents an edit operation of type type which is applied to + * the source string + * + * Replace: replace character at src_pos with character at dest_pos + * Insert: insert character from dest_pos at src_pos + * Delete: delete character at src_pos + */ +struct EditOp { + EditType type; /**< type of the edit operation */ + size_t src_pos; /**< index into the source string */ + size_t dest_pos; /**< index into the destination string */ + + EditOp() : type(EditType::None), src_pos(0), dest_pos(0) + {} + + EditOp(EditType type_, size_t src_pos_, size_t dest_pos_) + : type(type_), src_pos(src_pos_), dest_pos(dest_pos_) + {} +}; + +inline bool operator==(EditOp a, EditOp b) +{ + return (a.type == b.type) && (a.src_pos == b.src_pos) && (a.dest_pos == b.dest_pos); +} + +inline bool operator!=(EditOp a, EditOp b) +{ + return !(a == b); +} + +/** + * @brief Edit operations used by the Levenshtein distance + * + * This represents an edit operation of type type which is applied to + * the source string + * + * None: s1[src_begin:src_end] == s1[dest_begin:dest_end] + * Replace: s1[i1:i2] should be replaced by s2[dest_begin:dest_end] + * Insert: s2[dest_begin:dest_end] should be inserted at s1[src_begin:src_begin]. + * Note that src_begin==src_end in this case. + * Delete: s1[src_begin:src_end] should be deleted. + * Note that dest_begin==dest_end in this case. + */ +struct Opcode { + EditType type; /**< type of the edit operation */ + size_t src_begin; /**< index into the source string */ + size_t src_end; /**< index into the source string */ + size_t dest_begin; /**< index into the destination string */ + size_t dest_end; /**< index into the destination string */ + + Opcode() : type(EditType::None), src_begin(0), src_end(0), dest_begin(0), dest_end(0) + {} + + Opcode(EditType type_, size_t src_begin_, size_t src_end_, size_t dest_begin_, size_t dest_end_) + : type(type_), src_begin(src_begin_), src_end(src_end_), dest_begin(dest_begin_), dest_end(dest_end_) + {} +}; + +inline bool operator==(Opcode a, Opcode b) +{ + return (a.type == b.type) && (a.src_begin == b.src_begin) && (a.src_end == b.src_end) && + (a.dest_begin == b.dest_begin) && (a.dest_end == b.dest_end); +} + +inline bool operator!=(Opcode a, Opcode b) +{ + return !(a == b); +} + +namespace detail { +template +auto vector_slice(const Vec& vec, int start, int stop, int step) -> Vec +{ + Vec new_vec; + + if (step == 0) throw std::invalid_argument("slice step cannot be zero"); + if (step < 0) throw std::invalid_argument("step sizes below 0 lead to an invalid order of editops"); + + if (start < 0) + start = std::max(start + static_cast(vec.size()), 0); + else if (start > static_cast(vec.size())) + start = static_cast(vec.size()); + + if (stop < 0) + stop = std::max(stop + static_cast(vec.size()), 0); + else if (stop > static_cast(vec.size())) + stop = static_cast(vec.size()); + + if (start >= stop) return new_vec; + + int count = (stop - 1 - start) / step + 1; + new_vec.reserve(static_cast(count)); + + for (int i = start; i < stop; i += step) + new_vec.push_back(vec[static_cast(i)]); + + return new_vec; +} + +template +void vector_remove_slice(Vec& vec, int start, int stop, int step) +{ + if (step == 0) throw std::invalid_argument("slice step cannot be zero"); + if (step < 0) throw std::invalid_argument("step sizes below 0 lead to an invalid order of editops"); + + if (start < 0) + start = std::max(start + static_cast(vec.size()), 0); + else if (start > static_cast(vec.size())) + start = static_cast(vec.size()); + + if (stop < 0) + stop = std::max(stop + static_cast(vec.size()), 0); + else if (stop > static_cast(vec.size())) + stop = static_cast(vec.size()); + + if (start >= stop) return; + + auto iter = vec.begin() + start; + for (int i = start; i < static_cast(vec.size()); i++) + if (i >= stop || ((i - start) % step != 0)) *(iter++) = vec[static_cast(i)]; + + vec.resize(static_cast(std::distance(vec.begin(), iter))); + vec.shrink_to_fit(); +} + +} // namespace detail + +class Opcodes; + +class Editops : private std::vector { +public: + using std::vector::size_type; + + Editops() noexcept : src_len(0), dest_len(0) + {} + + Editops(size_type count, const EditOp& value) : std::vector(count, value), src_len(0), dest_len(0) + {} + + explicit Editops(size_type count) : std::vector(count), src_len(0), dest_len(0) + {} + + Editops(const Editops& other) + : std::vector(other), src_len(other.src_len), dest_len(other.dest_len) + {} + + Editops(const Opcodes& other); + + Editops(Editops&& other) noexcept + { + swap(other); + } + + Editops& operator=(Editops other) noexcept + { + swap(other); + return *this; + } + + /* Element access */ + using std::vector::at; + using std::vector::operator[]; + using std::vector::front; + using std::vector::back; + using std::vector::data; + + /* Iterators */ + using std::vector::begin; + using std::vector::cbegin; + using std::vector::end; + using std::vector::cend; + using std::vector::rbegin; + using std::vector::crbegin; + using std::vector::rend; + using std::vector::crend; + + /* Capacity */ + using std::vector::empty; + using std::vector::size; + using std::vector::max_size; + using std::vector::reserve; + using std::vector::capacity; + using std::vector::shrink_to_fit; + + /* Modifiers */ + using std::vector::clear; + using std::vector::insert; + using std::vector::emplace; + using std::vector::erase; + using std::vector::push_back; + using std::vector::emplace_back; + using std::vector::pop_back; + using std::vector::resize; + + void swap(Editops& rhs) noexcept + { + std::swap(src_len, rhs.src_len); + std::swap(dest_len, rhs.dest_len); + std::vector::swap(rhs); + } + + Editops slice(int start, int stop, int step = 1) const + { + Editops ed_slice = detail::vector_slice(*this, start, stop, step); + ed_slice.src_len = src_len; + ed_slice.dest_len = dest_len; + return ed_slice; + } + + void remove_slice(int start, int stop, int step = 1) + { + detail::vector_remove_slice(*this, start, stop, step); + } + + Editops reverse() const + { + Editops reversed = *this; + std::reverse(reversed.begin(), reversed.end()); + return reversed; + } + + size_t get_src_len() const noexcept + { + return src_len; + } + void set_src_len(size_t len) noexcept + { + src_len = len; + } + size_t get_dest_len() const noexcept + { + return dest_len; + } + void set_dest_len(size_t len) noexcept + { + dest_len = len; + } + + Editops inverse() const + { + Editops inv_ops = *this; + std::swap(inv_ops.src_len, inv_ops.dest_len); + for (auto& op : inv_ops) { + std::swap(op.src_pos, op.dest_pos); + if (op.type == EditType::Delete) + op.type = EditType::Insert; + else if (op.type == EditType::Insert) + op.type = EditType::Delete; + } + return inv_ops; + } + + Editops remove_subsequence(const Editops& subsequence) const + { + Editops result; + result.set_src_len(src_len); + result.set_dest_len(dest_len); + + if (subsequence.size() > size()) throw std::invalid_argument("subsequence is not a subsequence"); + + result.resize(size() - subsequence.size()); + + /* offset to correct removed edit operations */ + int offset = 0; + auto op_iter = begin(); + auto op_end = end(); + size_t result_pos = 0; + for (const auto& sop : subsequence) { + for (; op_iter != op_end && sop != *op_iter; op_iter++) { + result[result_pos] = *op_iter; + result[result_pos].src_pos = + static_cast(static_cast(result[result_pos].src_pos) + offset); + result_pos++; + } + /* element of subsequence not part of the sequence */ + if (op_iter == op_end) throw std::invalid_argument("subsequence is not a subsequence"); + + if (sop.type == EditType::Insert) + offset++; + else if (sop.type == EditType::Delete) + offset--; + op_iter++; + } + + /* add remaining elements */ + for (; op_iter != op_end; op_iter++) { + result[result_pos] = *op_iter; + result[result_pos].src_pos = + static_cast(static_cast(result[result_pos].src_pos) + offset); + result_pos++; + } + + return result; + } + +private: + size_t src_len; + size_t dest_len; +}; + +inline bool operator==(const Editops& lhs, const Editops& rhs) +{ + if (lhs.get_src_len() != rhs.get_src_len() || lhs.get_dest_len() != rhs.get_dest_len()) { + return false; + } + + if (lhs.size() != rhs.size()) { + return false; + } + return std::equal(lhs.begin(), lhs.end(), rhs.begin()); +} + +inline bool operator!=(const Editops& lhs, const Editops& rhs) +{ + return !(lhs == rhs); +} + +inline void swap(Editops& lhs, Editops& rhs) noexcept(noexcept(lhs.swap(rhs))) +{ + lhs.swap(rhs); +} + +class Opcodes : private std::vector { +public: + using std::vector::size_type; + + Opcodes() noexcept : src_len(0), dest_len(0) + {} + + Opcodes(size_type count, const Opcode& value) : std::vector(count, value), src_len(0), dest_len(0) + {} + + explicit Opcodes(size_type count) : std::vector(count), src_len(0), dest_len(0) + {} + + Opcodes(const Opcodes& other) + : std::vector(other), src_len(other.src_len), dest_len(other.dest_len) + {} + + Opcodes(const Editops& other); + + Opcodes(Opcodes&& other) noexcept + { + swap(other); + } + + Opcodes& operator=(Opcodes other) noexcept + { + swap(other); + return *this; + } + + /* Element access */ + using std::vector::at; + using std::vector::operator[]; + using std::vector::front; + using std::vector::back; + using std::vector::data; + + /* Iterators */ + using std::vector::begin; + using std::vector::cbegin; + using std::vector::end; + using std::vector::cend; + using std::vector::rbegin; + using std::vector::crbegin; + using std::vector::rend; + using std::vector::crend; + + /* Capacity */ + using std::vector::empty; + using std::vector::size; + using std::vector::max_size; + using std::vector::reserve; + using std::vector::capacity; + using std::vector::shrink_to_fit; + + /* Modifiers */ + using std::vector::clear; + using std::vector::insert; + using std::vector::emplace; + using std::vector::erase; + using std::vector::push_back; + using std::vector::emplace_back; + using std::vector::pop_back; + using std::vector::resize; + + void swap(Opcodes& rhs) noexcept + { + std::swap(src_len, rhs.src_len); + std::swap(dest_len, rhs.dest_len); + std::vector::swap(rhs); + } + + Opcodes slice(int start, int stop, int step = 1) const + { + Opcodes ed_slice = detail::vector_slice(*this, start, stop, step); + ed_slice.src_len = src_len; + ed_slice.dest_len = dest_len; + return ed_slice; + } + + Opcodes reverse() const + { + Opcodes reversed = *this; + std::reverse(reversed.begin(), reversed.end()); + return reversed; + } + + size_t get_src_len() const noexcept + { + return src_len; + } + void set_src_len(size_t len) noexcept + { + src_len = len; + } + size_t get_dest_len() const noexcept + { + return dest_len; + } + void set_dest_len(size_t len) noexcept + { + dest_len = len; + } + + Opcodes inverse() const + { + Opcodes inv_ops = *this; + std::swap(inv_ops.src_len, inv_ops.dest_len); + for (auto& op : inv_ops) { + std::swap(op.src_begin, op.dest_begin); + std::swap(op.src_end, op.dest_end); + if (op.type == EditType::Delete) + op.type = EditType::Insert; + else if (op.type == EditType::Insert) + op.type = EditType::Delete; + } + return inv_ops; + } + +private: + size_t src_len; + size_t dest_len; +}; + +inline bool operator==(const Opcodes& lhs, const Opcodes& rhs) +{ + if (lhs.get_src_len() != rhs.get_src_len() || lhs.get_dest_len() != rhs.get_dest_len()) return false; + + if (lhs.size() != rhs.size()) return false; + + return std::equal(lhs.begin(), lhs.end(), rhs.begin()); +} + +inline bool operator!=(const Opcodes& lhs, const Opcodes& rhs) +{ + return !(lhs == rhs); +} + +inline void swap(Opcodes& lhs, Opcodes& rhs) noexcept(noexcept(lhs.swap(rhs))) +{ + lhs.swap(rhs); +} + +inline Editops::Editops(const Opcodes& other) +{ + src_len = other.get_src_len(); + dest_len = other.get_dest_len(); + for (const auto& op : other) { + switch (op.type) { + case EditType::None: break; + + case EditType::Replace: + for (size_t j = 0; j < op.src_end - op.src_begin; j++) + push_back({EditType::Replace, op.src_begin + j, op.dest_begin + j}); + break; + + case EditType::Insert: + for (size_t j = 0; j < op.dest_end - op.dest_begin; j++) + push_back({EditType::Insert, op.src_begin, op.dest_begin + j}); + break; + + case EditType::Delete: + for (size_t j = 0; j < op.src_end - op.src_begin; j++) + push_back({EditType::Delete, op.src_begin + j, op.dest_begin}); + break; + } + } +} + +inline Opcodes::Opcodes(const Editops& other) +{ + src_len = other.get_src_len(); + dest_len = other.get_dest_len(); + size_t src_pos = 0; + size_t dest_pos = 0; + for (size_t i = 0; i < other.size();) { + if (src_pos < other[i].src_pos || dest_pos < other[i].dest_pos) { + push_back({EditType::None, src_pos, other[i].src_pos, dest_pos, other[i].dest_pos}); + src_pos = other[i].src_pos; + dest_pos = other[i].dest_pos; + } + + size_t src_begin = src_pos; + size_t dest_begin = dest_pos; + EditType type = other[i].type; + do { + switch (type) { + case EditType::None: break; + + case EditType::Replace: + src_pos++; + dest_pos++; + break; + + case EditType::Insert: dest_pos++; break; + + case EditType::Delete: src_pos++; break; + } + i++; + } while (i < other.size() && other[i].type == type && src_pos == other[i].src_pos && + dest_pos == other[i].dest_pos); + + push_back({type, src_begin, src_pos, dest_begin, dest_pos}); + } + + if (src_pos < other.get_src_len() || dest_pos < other.get_dest_len()) { + push_back({EditType::None, src_pos, other.get_src_len(), dest_pos, other.get_dest_len()}); + } +} + +template +struct ScoreAlignment { + T score; /**< resulting score of the algorithm */ + size_t src_start; /**< index into the source string */ + size_t src_end; /**< index into the source string */ + size_t dest_start; /**< index into the destination string */ + size_t dest_end; /**< index into the destination string */ + + ScoreAlignment() : score(T()), src_start(0), src_end(0), dest_start(0), dest_end(0) + {} + + ScoreAlignment(T score_, size_t src_start_, size_t src_end_, size_t dest_start_, size_t dest_end_) + : score(score_), + src_start(src_start_), + src_end(src_end_), + dest_start(dest_start_), + dest_end(dest_end_) + {} +}; + +template +inline bool operator==(const ScoreAlignment& a, const ScoreAlignment& b) +{ + return (a.score == b.score) && (a.src_start == b.src_start) && (a.src_end == b.src_end) && + (a.dest_start == b.dest_start) && (a.dest_end == b.dest_end); +} + +} // namespace rapidfuzz diff --git a/src/external/rapidfuzz-cpp/rapidfuzz/distance.hpp b/src/external/rapidfuzz-cpp/rapidfuzz/distance.hpp new file mode 100644 index 00000000..da686f7b --- /dev/null +++ b/src/external/rapidfuzz-cpp/rapidfuzz/distance.hpp @@ -0,0 +1,161 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright © 2022-present Max Bachmann */ + +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace rapidfuzz { + +namespace detail { +template +ReturnType editops_apply_impl(const Editops& ops, InputIt1 first1, InputIt1 last1, InputIt2 first2, + InputIt2 last2) +{ + auto len1 = static_cast(std::distance(first1, last1)); + auto len2 = static_cast(std::distance(first2, last2)); + + ReturnType res_str; + res_str.resize(len1 + len2); + size_t src_pos = 0; + size_t dest_pos = 0; + + for (const auto& op : ops) { + /* matches between last and current editop */ + while (src_pos < op.src_pos) { + res_str[dest_pos] = + static_cast(first1[static_cast(src_pos)]); + src_pos++; + dest_pos++; + } + + switch (op.type) { + case EditType::None: + case EditType::Replace: + res_str[dest_pos] = + static_cast(first2[static_cast(op.dest_pos)]); + src_pos++; + dest_pos++; + break; + case EditType::Insert: + res_str[dest_pos] = + static_cast(first2[static_cast(op.dest_pos)]); + dest_pos++; + break; + case EditType::Delete: src_pos++; break; + } + } + + /* matches after the last editop */ + while (src_pos < len1) { + res_str[dest_pos] = + static_cast(first1[static_cast(src_pos)]); + src_pos++; + dest_pos++; + } + + res_str.resize(dest_pos); + return res_str; +} + +template +ReturnType opcodes_apply_impl(const Opcodes& ops, InputIt1 first1, InputIt1 last1, InputIt2 first2, + InputIt2 last2) +{ + auto len1 = static_cast(std::distance(first1, last1)); + auto len2 = static_cast(std::distance(first2, last2)); + + ReturnType res_str; + res_str.resize(len1 + len2); + size_t dest_pos = 0; + + for (const auto& op : ops) { + switch (op.type) { + case EditType::None: + for (auto i = op.src_begin; i < op.src_end; ++i) { + res_str[dest_pos++] = + static_cast(first1[static_cast(i)]); + } + break; + case EditType::Replace: + case EditType::Insert: + for (auto i = op.dest_begin; i < op.dest_end; ++i) { + res_str[dest_pos++] = + static_cast(first2[static_cast(i)]); + } + break; + case EditType::Delete: break; + } + } + + res_str.resize(dest_pos); + return res_str; +} + +} // namespace detail + +template +std::basic_string editops_apply_str(const Editops& ops, InputIt1 first1, InputIt1 last1, + InputIt2 first2, InputIt2 last2) +{ + return detail::editops_apply_impl>(ops, first1, last1, first2, last2); +} + +template +std::basic_string editops_apply_str(const Editops& ops, const Sentence1& s1, const Sentence2& s2) +{ + return detail::editops_apply_impl>(ops, detail::to_begin(s1), detail::to_end(s1), + detail::to_begin(s2), detail::to_end(s2)); +} + +template +std::basic_string opcodes_apply_str(const Opcodes& ops, InputIt1 first1, InputIt1 last1, + InputIt2 first2, InputIt2 last2) +{ + return detail::opcodes_apply_impl>(ops, first1, last1, first2, last2); +} + +template +std::basic_string opcodes_apply_str(const Opcodes& ops, const Sentence1& s1, const Sentence2& s2) +{ + return detail::opcodes_apply_impl>(ops, detail::to_begin(s1), detail::to_end(s1), + detail::to_begin(s2), detail::to_end(s2)); +} + +template +std::vector editops_apply_vec(const Editops& ops, InputIt1 first1, InputIt1 last1, InputIt2 first2, + InputIt2 last2) +{ + return detail::editops_apply_impl>(ops, first1, last1, first2, last2); +} + +template +std::vector editops_apply_vec(const Editops& ops, const Sentence1& s1, const Sentence2& s2) +{ + return detail::editops_apply_impl>(ops, detail::to_begin(s1), detail::to_end(s1), + detail::to_begin(s2), detail::to_end(s2)); +} + +template +std::vector opcodes_apply_vec(const Opcodes& ops, InputIt1 first1, InputIt1 last1, InputIt2 first2, + InputIt2 last2) +{ + return detail::opcodes_apply_impl>(ops, first1, last1, first2, last2); +} + +template +std::vector opcodes_apply_vec(const Opcodes& ops, const Sentence1& s1, const Sentence2& s2) +{ + return detail::opcodes_apply_impl>(ops, detail::to_begin(s1), detail::to_end(s1), + detail::to_begin(s2), detail::to_end(s2)); +} + +} // namespace rapidfuzz diff --git a/src/external/rapidfuzz-cpp/rapidfuzz/distance/DamerauLevenshtein.hpp b/src/external/rapidfuzz-cpp/rapidfuzz/distance/DamerauLevenshtein.hpp new file mode 100644 index 00000000..b1209ed7 --- /dev/null +++ b/src/external/rapidfuzz-cpp/rapidfuzz/distance/DamerauLevenshtein.hpp @@ -0,0 +1,152 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright © 2022-present Max Bachmann */ + +#include +#include + +namespace rapidfuzz { +/* the API will require a change when adding custom weights */ +namespace experimental { +/** + * @brief Calculates the Damerau Levenshtein distance between two strings. + * + * + * @tparam Sentence1 This is a string that can be converted to + * basic_string_view + * @tparam Sentence2 This is a string that can be converted to + * basic_string_view + * + * @param s1 + * string to compare with s2 (for type info check Template parameters above) + * @param s2 + * string to compare with s1 (for type info check Template parameters above) + * @param max + * Maximum Damerau Levenshtein distance between s1 and s2, that is + * considered as a result. If the distance is bigger than max, + * max + 1 is returned instead. Default is std::numeric_limits::max(), + * which deactivates this behaviour. + * + * @return Damerau Levenshtein distance between s1 and s2 + */ +template +size_t damerau_levenshtein_distance(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + size_t score_cutoff = std::numeric_limits::max()) +{ + return detail::DamerauLevenshtein::distance(first1, last1, first2, last2, score_cutoff, score_cutoff); +} + +template +size_t damerau_levenshtein_distance(const Sentence1& s1, const Sentence2& s2, + size_t score_cutoff = std::numeric_limits::max()) +{ + return detail::DamerauLevenshtein::distance(s1, s2, score_cutoff, score_cutoff); +} + +template +size_t damerau_levenshtein_similarity(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + size_t score_cutoff = 0) +{ + return detail::DamerauLevenshtein::similarity(first1, last1, first2, last2, score_cutoff, score_cutoff); +} + +template +size_t damerau_levenshtein_similarity(const Sentence1& s1, const Sentence2& s2, size_t score_cutoff = 0) +{ + return detail::DamerauLevenshtein::similarity(s1, s2, score_cutoff, score_cutoff); +} + +template +double damerau_levenshtein_normalized_distance(InputIt1 first1, InputIt1 last1, InputIt2 first2, + InputIt2 last2, double score_cutoff = 1.0) +{ + return detail::DamerauLevenshtein::normalized_distance(first1, last1, first2, last2, score_cutoff, + score_cutoff); +} + +template +double damerau_levenshtein_normalized_distance(const Sentence1& s1, const Sentence2& s2, + double score_cutoff = 1.0) +{ + return detail::DamerauLevenshtein::normalized_distance(s1, s2, score_cutoff, score_cutoff); +} + +/** + * @brief Calculates a normalized Damerau Levenshtein similarity + * + * @details + * Both string require a similar length + * + * + * @tparam Sentence1 This is a string that can be converted to + * basic_string_view + * @tparam Sentence2 This is a string that can be converted to + * basic_string_view + * + * @param s1 + * string to compare with s2 (for type info check Template parameters above) + * @param s2 + * string to compare with s1 (for type info check Template parameters above) + * @param score_cutoff + * Optional argument for a score threshold as a float between 0 and 1.0. + * For ratio < score_cutoff 0 is returned instead. Default is 0, + * which deactivates this behaviour. + * + * @return Normalized Damerau Levenshtein distance between s1 and s2 + * as a float between 0 and 1.0 + */ +template +double damerau_levenshtein_normalized_similarity(InputIt1 first1, InputIt1 last1, InputIt2 first2, + InputIt2 last2, double score_cutoff = 0.0) +{ + return detail::DamerauLevenshtein::normalized_similarity(first1, last1, first2, last2, score_cutoff, + score_cutoff); +} + +template +double damerau_levenshtein_normalized_similarity(const Sentence1& s1, const Sentence2& s2, + double score_cutoff = 0.0) +{ + return detail::DamerauLevenshtein::normalized_similarity(s1, s2, score_cutoff, score_cutoff); +} + +template +struct CachedDamerauLevenshtein : public detail::CachedDistanceBase, size_t, + 0, std::numeric_limits::max()> { + template + explicit CachedDamerauLevenshtein(const Sentence1& s1_) + : CachedDamerauLevenshtein(detail::to_begin(s1_), detail::to_end(s1_)) + {} + + template + CachedDamerauLevenshtein(InputIt1 first1, InputIt1 last1) : s1(first1, last1) + {} + +private: + friend detail::CachedDistanceBase, size_t, 0, + std::numeric_limits::max()>; + friend detail::CachedNormalizedMetricBase>; + + template + size_t maximum(const detail::Range& s2) const + { + return std::max(s1.size(), s2.size()); + } + + template + size_t _distance(const detail::Range& s2, size_t score_cutoff, + [[maybe_unused]] size_t score_hint) const + { + return rapidfuzz::experimental::damerau_levenshtein_distance(s1, s2, score_cutoff); + } + + std::vector s1; +}; + +template +explicit CachedDamerauLevenshtein(const Sentence1& s1_) -> CachedDamerauLevenshtein>; + +template +CachedDamerauLevenshtein(InputIt1 first1, InputIt1 last1) -> CachedDamerauLevenshtein>; + +} // namespace experimental +} // namespace rapidfuzz diff --git a/src/external/rapidfuzz-cpp/rapidfuzz/distance/DamerauLevenshtein_impl.hpp b/src/external/rapidfuzz-cpp/rapidfuzz/distance/DamerauLevenshtein_impl.hpp new file mode 100644 index 00000000..5a122872 --- /dev/null +++ b/src/external/rapidfuzz-cpp/rapidfuzz/distance/DamerauLevenshtein_impl.hpp @@ -0,0 +1,140 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright © 2022-present Max Bachmann */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace rapidfuzz::detail { + +template +struct RowId { + IntType val = -1; + friend bool operator==(const RowId& lhs, const RowId& rhs) + { + return lhs.val == rhs.val; + } + + friend bool operator!=(const RowId& lhs, const RowId& rhs) + { + return !(lhs == rhs); + } +}; + +/* + * based on the paper + * "Linear space string correction algorithm using the Damerau-Levenshtein distance" + * from Chunchun Zhao and Sartaj Sahni + */ +template +size_t damerau_levenshtein_distance_zhao(const Range& s1, const Range& s2, size_t max) +{ + // todo check types + IntType len1 = static_cast(s1.size()); + IntType len2 = static_cast(s2.size()); + IntType maxVal = static_cast(std::max(len1, len2) + 1); + assert(std::numeric_limits::max() > maxVal); + + HybridGrowingHashmap::value_type, RowId> last_row_id; + size_t size = s2.size() + 2; + assume(size != 0); + std::vector FR_arr(size, maxVal); + std::vector R1_arr(size, maxVal); + std::vector R_arr(size); + R_arr[0] = maxVal; + std::iota(R_arr.begin() + 1, R_arr.end(), IntType(0)); + + IntType* R = &R_arr[1]; + IntType* R1 = &R1_arr[1]; + IntType* FR = &FR_arr[1]; + + auto iter_s1 = s1.begin(); + for (IntType i = 1; i <= len1; i++) { + std::swap(R, R1); + IntType last_col_id = -1; + IntType last_i2l1 = R[0]; + R[0] = i; + IntType T = maxVal; + + auto iter_s2 = s2.begin(); + for (IntType j = 1; j <= len2; j++) { + int64_t diag = R1[j - 1] + static_cast(*iter_s1 != *iter_s2); + int64_t left = R[j - 1] + 1; + int64_t up = R1[j] + 1; + int64_t temp = std::min({diag, left, up}); + + if (*iter_s1 == *iter_s2) { + last_col_id = j; // last occurence of s1_i + FR[j] = R1[j - 2]; // save H_k-1,j-2 + T = last_i2l1; // save H_i-2,l-1 + } + else { + int64_t k = last_row_id.get(static_cast(*iter_s2)).val; + int64_t l = last_col_id; + + if ((j - l) == 1) { + int64_t transpose = FR[j] + (i - k); + temp = std::min(temp, transpose); + } + else if ((i - k) == 1) { + int64_t transpose = T + (j - l); + temp = std::min(temp, transpose); + } + } + + last_i2l1 = R[j]; + R[j] = static_cast(temp); + iter_s2++; + } + last_row_id[*iter_s1].val = i; + iter_s1++; + } + + size_t dist = static_cast(R[s2.size()]); + return (dist <= max) ? dist : max + 1; +} + +template +size_t damerau_levenshtein_distance(Range s1, Range s2, size_t max) +{ + size_t min_edits = abs_diff(s1.size(), s2.size()); + if (min_edits > max) return max + 1; + + /* common affix does not effect Levenshtein distance */ + remove_common_affix(s1, s2); + + size_t maxVal = std::max(s1.size(), s2.size()) + 1; + if (std::numeric_limits::max() > maxVal) + return damerau_levenshtein_distance_zhao(s1, s2, max); + else if (std::numeric_limits::max() > maxVal) + return damerau_levenshtein_distance_zhao(s1, s2, max); + else + return damerau_levenshtein_distance_zhao(s1, s2, max); +} + +class DamerauLevenshtein + : public DistanceBase::max()> { + friend DistanceBase::max()>; + friend NormalizedMetricBase; + + template + static size_t maximum(const Range& s1, const Range& s2) + { + return std::max(s1.size(), s2.size()); + } + + template + static size_t _distance(const Range& s1, const Range& s2, size_t score_cutoff, + [[maybe_unused]] size_t score_hint) + { + return damerau_levenshtein_distance(s1, s2, score_cutoff); + } +}; + +} // namespace rapidfuzz::detail \ No newline at end of file diff --git a/src/external/rapidfuzz-cpp/rapidfuzz/distance/Hamming.hpp b/src/external/rapidfuzz-cpp/rapidfuzz/distance/Hamming.hpp new file mode 100644 index 00000000..d5160722 --- /dev/null +++ b/src/external/rapidfuzz-cpp/rapidfuzz/distance/Hamming.hpp @@ -0,0 +1,172 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright © 2021 Max Bachmann */ + +#pragma once +#include +#include +#include + +namespace rapidfuzz { + +/** + * @brief Calculates the Hamming distance between two strings. + * + * @details + * Both strings require a similar length + * + * + * @tparam Sentence1 This is a string that can be converted to + * basic_string_view + * @tparam Sentence2 This is a string that can be converted to + * basic_string_view + * + * @param s1 + * string to compare with s2 (for type info check Template parameters above) + * @param s2 + * string to compare with s1 (for type info check Template parameters above) + * @param max + * Maximum Hamming distance between s1 and s2, that is + * considered as a result. If the distance is bigger than max, + * max + 1 is returned instead. Default is std::numeric_limits::max(), + * which deactivates this behaviour. + * + * @return Hamming distance between s1 and s2 + */ +template +size_t hamming_distance(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, bool pad_ = true, + size_t score_cutoff = std::numeric_limits::max()) +{ + return detail::Hamming::distance(first1, last1, first2, last2, pad_, score_cutoff, score_cutoff); +} + +template +size_t hamming_distance(const Sentence1& s1, const Sentence2& s2, bool pad_ = true, + size_t score_cutoff = std::numeric_limits::max()) +{ + return detail::Hamming::distance(s1, s2, pad_, score_cutoff, score_cutoff); +} + +template +size_t hamming_similarity(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, bool pad_ = true, + size_t score_cutoff = 0) +{ + return detail::Hamming::similarity(first1, last1, first2, last2, pad_, score_cutoff, score_cutoff); +} + +template +size_t hamming_similarity(const Sentence1& s1, const Sentence2& s2, bool pad_ = true, size_t score_cutoff = 0) +{ + return detail::Hamming::similarity(s1, s2, pad_, score_cutoff, score_cutoff); +} + +template +double hamming_normalized_distance(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + bool pad_ = true, double score_cutoff = 1.0) +{ + return detail::Hamming::normalized_distance(first1, last1, first2, last2, pad_, score_cutoff, + score_cutoff); +} + +template +double hamming_normalized_distance(const Sentence1& s1, const Sentence2& s2, bool pad_ = true, + double score_cutoff = 1.0) +{ + return detail::Hamming::normalized_distance(s1, s2, pad_, score_cutoff, score_cutoff); +} + +template +Editops hamming_editops(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, bool pad_ = true, + size_t score_hint = std::numeric_limits::max()) +{ + return detail::hamming_editops(detail::Range(first1, last1), detail::Range(first2, last2), pad_, + score_hint); +} + +template +Editops hamming_editops(const Sentence1& s1, const Sentence2& s2, bool pad_ = true, + size_t score_hint = std::numeric_limits::max()) +{ + return detail::hamming_editops(detail::Range(s1), detail::Range(s2), pad_, score_hint); +} + +/** + * @brief Calculates a normalized hamming similarity + * + * @details + * Both string require a similar length + * + * + * @tparam Sentence1 This is a string that can be converted to + * basic_string_view + * @tparam Sentence2 This is a string that can be converted to + * basic_string_view + * + * @param s1 + * string to compare with s2 (for type info check Template parameters above) + * @param s2 + * string to compare with s1 (for type info check Template parameters above) + * @param score_cutoff + * Optional argument for a score threshold as a float between 0 and 1.0. + * For ratio < score_cutoff 0 is returned instead. Default is 0, + * which deactivates this behaviour. + * + * @return Normalized hamming distance between s1 and s2 + * as a float between 0 and 1.0 + */ +template +double hamming_normalized_similarity(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + bool pad_ = true, double score_cutoff = 0.0) +{ + return detail::Hamming::normalized_similarity(first1, last1, first2, last2, pad_, score_cutoff, + score_cutoff); +} + +template +double hamming_normalized_similarity(const Sentence1& s1, const Sentence2& s2, bool pad_ = true, + double score_cutoff = 0.0) +{ + return detail::Hamming::normalized_similarity(s1, s2, pad_, score_cutoff, score_cutoff); +} + +template +struct CachedHamming : public detail::CachedDistanceBase, size_t, 0, + std::numeric_limits::max()> { + template + explicit CachedHamming(const Sentence1& s1_, bool pad_ = true) + : CachedHamming(detail::to_begin(s1_), detail::to_end(s1_), pad_) + {} + + template + CachedHamming(InputIt1 first1, InputIt1 last1, bool pad_ = true) : s1(first1, last1), pad(pad_) + {} + +private: + friend detail::CachedDistanceBase, size_t, 0, std::numeric_limits::max()>; + friend detail::CachedNormalizedMetricBase>; + + template + size_t maximum(const detail::Range& s2) const + { + return std::max(s1.size(), s2.size()); + } + + template + size_t _distance(const detail::Range& s2, size_t score_cutoff, + [[maybe_unused]] size_t score_hint) const + { + return detail::Hamming::distance(s1, s2, pad, score_cutoff, score_hint); + } + + std::vector s1; + bool pad; +}; + +template +explicit CachedHamming(const Sentence1& s1_, bool pad_ = true) -> CachedHamming>; + +template +CachedHamming(InputIt1 first1, InputIt1 last1, bool pad_ = true) -> CachedHamming>; + +/**@}*/ + +} // namespace rapidfuzz diff --git a/src/external/rapidfuzz-cpp/rapidfuzz/distance/Hamming_impl.hpp b/src/external/rapidfuzz-cpp/rapidfuzz/distance/Hamming_impl.hpp new file mode 100644 index 00000000..8389f902 --- /dev/null +++ b/src/external/rapidfuzz-cpp/rapidfuzz/distance/Hamming_impl.hpp @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright © 2021 Max Bachmann */ + +#pragma once +#include +#include +#include + +namespace rapidfuzz::detail { + +class Hamming : public DistanceBase::max(), bool> { + friend DistanceBase::max(), bool>; + friend NormalizedMetricBase; + + template + static size_t maximum(const Range& s1, const Range& s2, bool) + { + return std::max(s1.size(), s2.size()); + } + + template + static size_t _distance(const Range& s1, const Range& s2, bool pad, + size_t score_cutoff, [[maybe_unused]] size_t score_hint) + { + if (!pad && s1.size() != s2.size()) throw std::invalid_argument("Sequences are not the same length."); + + size_t min_len = std::min(s1.size(), s2.size()); + size_t dist = std::max(s1.size(), s2.size()); + auto iter_s1 = s1.begin(); + auto iter_s2 = s2.begin(); + for (size_t i = 0; i < min_len; ++i) + dist -= bool(*(iter_s1++) == *(iter_s2++)); + + return (dist <= score_cutoff) ? dist : score_cutoff + 1; + } +}; + +template +Editops hamming_editops(const Range& s1, const Range& s2, bool pad, size_t) +{ + if (!pad && s1.size() != s2.size()) throw std::invalid_argument("Sequences are not the same length."); + + Editops ops; + size_t min_len = std::min(s1.size(), s2.size()); + size_t i = 0; + for (; i < min_len; ++i) + if (s1[i] != s2[i]) ops.emplace_back(EditType::Replace, i, i); + + for (; i < s1.size(); ++i) + ops.emplace_back(EditType::Delete, i, s2.size()); + + for (; i < s2.size(); ++i) + ops.emplace_back(EditType::Insert, s1.size(), i); + + ops.set_src_len(s1.size()); + ops.set_dest_len(s2.size()); + return ops; +} + +} // namespace rapidfuzz::detail diff --git a/src/external/rapidfuzz-cpp/rapidfuzz/distance/Indel.hpp b/src/external/rapidfuzz-cpp/rapidfuzz/distance/Indel.hpp new file mode 100644 index 00000000..9cfa902b --- /dev/null +++ b/src/external/rapidfuzz-cpp/rapidfuzz/distance/Indel.hpp @@ -0,0 +1,191 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright © 2022-present Max Bachmann */ + +#pragma once + +#include +#include +#include + +namespace rapidfuzz { + +template +size_t indel_distance(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + size_t score_cutoff = std::numeric_limits::max()) +{ + return detail::Indel::distance(first1, last1, first2, last2, score_cutoff, score_cutoff); +} + +template +size_t indel_distance(const Sentence1& s1, const Sentence2& s2, + size_t score_cutoff = std::numeric_limits::max()) +{ + return detail::Indel::distance(s1, s2, score_cutoff, score_cutoff); +} + +template +size_t indel_similarity(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + size_t score_cutoff = 0.0) +{ + return detail::Indel::similarity(first1, last1, first2, last2, score_cutoff, score_cutoff); +} + +template +size_t indel_similarity(const Sentence1& s1, const Sentence2& s2, size_t score_cutoff = 0.0) +{ + return detail::Indel::similarity(s1, s2, score_cutoff, score_cutoff); +} + +template +double indel_normalized_distance(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + double score_cutoff = 1.0) +{ + return detail::Indel::normalized_distance(first1, last1, first2, last2, score_cutoff, score_cutoff); +} + +template +double indel_normalized_distance(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 1.0) +{ + return detail::Indel::normalized_distance(s1, s2, score_cutoff, score_cutoff); +} + +template +double indel_normalized_similarity(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + double score_cutoff = 0.0) +{ + return detail::Indel::normalized_similarity(first1, last1, first2, last2, score_cutoff, score_cutoff); +} + +template +double indel_normalized_similarity(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 0.0) +{ + return detail::Indel::normalized_similarity(s1, s2, score_cutoff, score_cutoff); +} + +template +Editops indel_editops(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2) +{ + return lcs_seq_editops(first1, last1, first2, last2); +} + +template +Editops indel_editops(const Sentence1& s1, const Sentence2& s2) +{ + return lcs_seq_editops(s1, s2); +} + +#ifdef RAPIDFUZZ_SIMD +namespace experimental { +template +struct MultiIndel + : public detail::MultiDistanceBase, size_t, 0, std::numeric_limits::max()> { +private: + friend detail::MultiDistanceBase, size_t, 0, std::numeric_limits::max()>; + friend detail::MultiNormalizedMetricBase, size_t>; + +public: + MultiIndel(size_t count) : scorer(count) + {} + + /** + * @brief get minimum size required for result vectors passed into + * - distance + * - similarity + * - normalized_distance + * - normalized_similarity + * + * @return minimum vector size + */ + size_t result_count() const + { + return scorer.result_count(); + } + + template + void insert(const Sentence1& s1_) + { + insert(detail::to_begin(s1_), detail::to_end(s1_)); + } + + template + void insert(InputIt1 first1, InputIt1 last1) + { + scorer.insert(first1, last1); + str_lens.push_back(static_cast(std::distance(first1, last1))); + } + +private: + template + void _distance(size_t* scores, size_t score_count, const detail::Range& s2, + size_t score_cutoff = std::numeric_limits::max()) const + { + scorer.similarity(scores, score_count, s2); + + for (size_t i = 0; i < get_input_count(); ++i) { + size_t maximum_ = maximum(i, s2); + size_t dist = maximum_ - 2 * scores[i]; + scores[i] = (dist <= score_cutoff) ? dist : score_cutoff + 1; + } + } + + template + size_t maximum(size_t s1_idx, const detail::Range& s2) const + { + return str_lens[s1_idx] + s2.size(); + } + + size_t get_input_count() const noexcept + { + return str_lens.size(); + } + + std::vector str_lens; + MultiLCSseq scorer; +}; +} /* namespace experimental */ +#endif + +template +struct CachedIndel + : public detail::CachedDistanceBase, size_t, 0, std::numeric_limits::max()> { + template + explicit CachedIndel(const Sentence1& s1_) : CachedIndel(detail::to_begin(s1_), detail::to_end(s1_)) + {} + + template + CachedIndel(InputIt1 first1, InputIt1 last1) + : s1_len(static_cast(std::distance(first1, last1))), scorer(first1, last1) + {} + +private: + friend detail::CachedDistanceBase, size_t, 0, std::numeric_limits::max()>; + friend detail::CachedNormalizedMetricBase>; + + template + size_t maximum(const detail::Range& s2) const + { + return s1_len + s2.size(); + } + + template + size_t _distance(const detail::Range& s2, size_t score_cutoff, size_t score_hint) const + { + size_t maximum_ = maximum(s2); + size_t lcs_cutoff = (maximum_ / 2 >= score_cutoff) ? maximum_ / 2 - score_cutoff : 0; + size_t lcs_cutoff_hint = (maximum_ / 2 >= score_hint) ? maximum_ / 2 - score_hint : 0; + size_t lcs_sim = scorer.similarity(s2, lcs_cutoff, lcs_cutoff_hint); + size_t dist = maximum_ - 2 * lcs_sim; + return (dist <= score_cutoff) ? dist : score_cutoff + 1; + } + + size_t s1_len; + CachedLCSseq scorer; +}; + +template +explicit CachedIndel(const Sentence1& s1_) -> CachedIndel>; + +template +CachedIndel(InputIt1 first1, InputIt1 last1) -> CachedIndel>; + +} // namespace rapidfuzz diff --git a/src/external/rapidfuzz-cpp/rapidfuzz/distance/Indel_impl.hpp b/src/external/rapidfuzz-cpp/rapidfuzz/distance/Indel_impl.hpp new file mode 100644 index 00000000..d0ab9d50 --- /dev/null +++ b/src/external/rapidfuzz-cpp/rapidfuzz/distance/Indel_impl.hpp @@ -0,0 +1,68 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright © 2022-present Max Bachmann */ + +#include +#include +#include +#include +#include +#include + +namespace rapidfuzz::detail { + +template +size_t indel_distance(const BlockPatternMatchVector& block, const Range& s1, + const Range& s2, size_t score_cutoff) +{ + size_t maximum = s1.size() + s2.size(); + size_t lcs_cutoff = (maximum / 2 >= score_cutoff) ? maximum / 2 - score_cutoff : 0; + size_t lcs_sim = lcs_seq_similarity(block, s1, s2, lcs_cutoff); + size_t dist = maximum - 2 * lcs_sim; + return (dist <= score_cutoff) ? dist : score_cutoff + 1; +} + +template +double indel_normalized_distance(const BlockPatternMatchVector& block, const Range& s1, + const Range& s2, double score_cutoff) +{ + size_t maximum = s1.size() + s2.size(); + size_t cutoff_distance = static_cast(std::ceil(static_cast(maximum) * score_cutoff)); + size_t dist = indel_distance(block, s1, s2, cutoff_distance); + double norm_dist = (maximum) ? static_cast(dist) / static_cast(maximum) : 0.0; + return (norm_dist <= score_cutoff) ? norm_dist : 1.0; +} + +template +double indel_normalized_similarity(const BlockPatternMatchVector& block, const Range& s1, + const Range& s2, double score_cutoff) +{ + double cutoff_score = NormSim_to_NormDist(score_cutoff); + double norm_dist = indel_normalized_distance(block, s1, s2, cutoff_score); + double norm_sim = 1.0 - norm_dist; + return (norm_sim >= score_cutoff) ? norm_sim : 0.0; +} + +class Indel : public DistanceBase::max()> { + friend DistanceBase::max()>; + friend NormalizedMetricBase; + + template + static size_t maximum(const Range& s1, const Range& s2) + { + return s1.size() + s2.size(); + } + + template + static size_t _distance(const Range& s1, const Range& s2, size_t score_cutoff, + size_t score_hint) + { + size_t maximum = Indel::maximum(s1, s2); + size_t lcs_cutoff = (maximum / 2 >= score_cutoff) ? maximum / 2 - score_cutoff : 0; + size_t lcs_hint = (maximum / 2 >= score_hint) ? maximum / 2 - score_hint : 0; + size_t lcs_sim = LCSseq::similarity(s1, s2, lcs_cutoff, lcs_hint); + size_t dist = maximum - 2 * lcs_sim; + return (dist <= score_cutoff) ? dist : score_cutoff + 1; + } +}; + +} // namespace rapidfuzz::detail diff --git a/src/external/rapidfuzz-cpp/rapidfuzz/distance/Jaro.hpp b/src/external/rapidfuzz-cpp/rapidfuzz/distance/Jaro.hpp new file mode 100644 index 00000000..764332cd --- /dev/null +++ b/src/external/rapidfuzz-cpp/rapidfuzz/distance/Jaro.hpp @@ -0,0 +1,231 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright © 2022-present Max Bachmann */ + +#pragma once + +#include +#include +#include + +namespace rapidfuzz { + +template +double jaro_distance(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + double score_cutoff = 1.0) +{ + return detail::Jaro::distance(first1, last1, first2, last2, score_cutoff, score_cutoff); +} + +template +double jaro_distance(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 1.0) +{ + return detail::Jaro::distance(s1, s2, score_cutoff, score_cutoff); +} + +template +double jaro_similarity(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + double score_cutoff = 0.0) +{ + return detail::Jaro::similarity(first1, last1, first2, last2, score_cutoff, score_cutoff); +} + +template +double jaro_similarity(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 0.0) +{ + return detail::Jaro::similarity(s1, s2, score_cutoff, score_cutoff); +} + +template +double jaro_normalized_distance(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + double score_cutoff = 1.0) +{ + return detail::Jaro::normalized_distance(first1, last1, first2, last2, score_cutoff, score_cutoff); +} + +template +double jaro_normalized_distance(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 1.0) +{ + return detail::Jaro::normalized_distance(s1, s2, score_cutoff, score_cutoff); +} + +template +double jaro_normalized_similarity(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + double score_cutoff = 0.0) +{ + return detail::Jaro::normalized_similarity(first1, last1, first2, last2, score_cutoff, score_cutoff); +} + +template +double jaro_normalized_similarity(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 0.0) +{ + return detail::Jaro::normalized_similarity(s1, s2, score_cutoff, score_cutoff); +} + +#ifdef RAPIDFUZZ_SIMD +namespace experimental { +template +struct MultiJaro : public detail::MultiSimilarityBase, double, 0, 1> { + +private: + friend detail::MultiSimilarityBase, double, 0, 1>; + friend detail::MultiNormalizedMetricBase, double>; + + static_assert(MaxLen == 8 || MaxLen == 16 || MaxLen == 32 || MaxLen == 64); + + using VecType = typename std::conditional_t< + MaxLen == 8, uint8_t, + typename std::conditional_t>>; + + constexpr static size_t get_vec_size() + { +# ifdef RAPIDFUZZ_AVX2 + return detail::simd_avx2::native_simd::size; +# else + return detail::simd_sse2::native_simd::size; +# endif + } + + constexpr static size_t get_vec_alignment() + { +# ifdef RAPIDFUZZ_AVX2 + return detail::simd_avx2::native_simd::alignment; +# else + return detail::simd_sse2::native_simd::alignment; +# endif + } + + constexpr static size_t find_block_count(size_t count) + { + size_t vec_size = get_vec_size(); + size_t simd_vec_count = detail::ceil_div(count, vec_size); + return detail::ceil_div(simd_vec_count * vec_size * MaxLen, 64); + } + +public: + MultiJaro(size_t count) : input_count(count), PM(find_block_count(count) * 64) + { + /* align for avx2 so we can directly load into avx2 registers */ + str_lens_size = result_count(); + + str_lens = static_cast( + detail::rf_aligned_alloc(get_vec_alignment(), sizeof(VecType) * str_lens_size)); + std::fill(str_lens, str_lens + str_lens_size, VecType(0)); + } + + ~MultiJaro() + { + detail::rf_aligned_free(str_lens); + } + + /** + * @brief get minimum size required for result vectors passed into + * - distance + * - similarity + * - normalized_distance + * - normalized_similarity + * + * @return minimum vector size + */ + size_t result_count() const + { + size_t vec_size = get_vec_size(); + size_t simd_vec_count = detail::ceil_div(input_count, vec_size); + return simd_vec_count * vec_size; + } + + template + void insert(const Sentence1& s1_) + { + insert(detail::to_begin(s1_), detail::to_end(s1_)); + } + + template + void insert(InputIt1 first1, InputIt1 last1) + { + auto len = std::distance(first1, last1); + int block_pos = static_cast((pos * MaxLen) % 64); + auto block = (pos * MaxLen) / 64; + assert(len <= MaxLen); + + if (pos >= input_count) throw std::invalid_argument("out of bounds insert"); + + str_lens[pos] = static_cast(len); + for (; first1 != last1; ++first1) { + PM.insert(block, *first1, block_pos); + block_pos++; + } + pos++; + } + +private: + template + void _similarity(double* scores, size_t score_count, const detail::Range& s2, + double score_cutoff = 0.0) const + { + if (score_count < result_count()) + throw std::invalid_argument("scores has to have >= result_count() elements"); + + detail::Range scores_(scores, scores + score_count); + detail::jaro_similarity_simd(scores_, PM, str_lens, str_lens_size, s2, score_cutoff); + } + + template + double maximum([[maybe_unused]] size_t s1_idx, const detail::Range&) const + { + return 1.0; + } + + size_t get_input_count() const noexcept + { + return input_count; + } + + size_t input_count; + size_t pos = 0; + detail::BlockPatternMatchVector PM; + VecType* str_lens; + size_t str_lens_size; +}; + +} /* namespace experimental */ +#endif /* RAPIDFUZZ_SIMD */ + +template +struct CachedJaro : public detail::CachedSimilarityBase, double, 0, 1> { + template + explicit CachedJaro(const Sentence1& s1_) : CachedJaro(detail::to_begin(s1_), detail::to_end(s1_)) + {} + + template + CachedJaro(InputIt1 first1, InputIt1 last1) : s1(first1, last1), PM(detail::Range(first1, last1)) + {} + +private: + friend detail::CachedSimilarityBase, double, 0, 1>; + friend detail::CachedNormalizedMetricBase>; + + template + double maximum(const detail::Range&) const + { + return 1.0; + } + + template + double _similarity(const detail::Range& s2, double score_cutoff, + [[maybe_unused]] double score_hint) const + { + return detail::jaro_similarity(PM, detail::Range(s1), s2, score_cutoff); + } + + std::vector s1; + detail::BlockPatternMatchVector PM; +}; + +template +explicit CachedJaro(const Sentence1& s1_) -> CachedJaro>; + +template +CachedJaro(InputIt1 first1, InputIt1 last1) -> CachedJaro>; + +} // namespace rapidfuzz diff --git a/src/external/rapidfuzz-cpp/rapidfuzz/distance/JaroWinkler.hpp b/src/external/rapidfuzz-cpp/rapidfuzz/distance/JaroWinkler.hpp new file mode 100644 index 00000000..d2306df3 --- /dev/null +++ b/src/external/rapidfuzz-cpp/rapidfuzz/distance/JaroWinkler.hpp @@ -0,0 +1,210 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright © 2022-present Max Bachmann */ + +#pragma once + +#include +#include + +namespace rapidfuzz { + +template >> +double jaro_winkler_distance(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + double prefix_weight = 0.1, double score_cutoff = 1.0) +{ + return detail::JaroWinkler::distance(first1, last1, first2, last2, prefix_weight, score_cutoff, + score_cutoff); +} + +template +double jaro_winkler_distance(const Sentence1& s1, const Sentence2& s2, double prefix_weight = 0.1, + double score_cutoff = 1.0) +{ + return detail::JaroWinkler::distance(s1, s2, prefix_weight, score_cutoff, score_cutoff); +} + +template >> +double jaro_winkler_similarity(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + double prefix_weight = 0.1, double score_cutoff = 0.0) +{ + return detail::JaroWinkler::similarity(first1, last1, first2, last2, prefix_weight, score_cutoff, + score_cutoff); +} + +template +double jaro_winkler_similarity(const Sentence1& s1, const Sentence2& s2, double prefix_weight = 0.1, + double score_cutoff = 0.0) +{ + return detail::JaroWinkler::similarity(s1, s2, prefix_weight, score_cutoff, score_cutoff); +} + +template >> +double jaro_winkler_normalized_distance(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + double prefix_weight = 0.1, double score_cutoff = 1.0) +{ + return detail::JaroWinkler::normalized_distance(first1, last1, first2, last2, prefix_weight, score_cutoff, + score_cutoff); +} + +template +double jaro_winkler_normalized_distance(const Sentence1& s1, const Sentence2& s2, double prefix_weight = 0.1, + double score_cutoff = 1.0) +{ + return detail::JaroWinkler::normalized_distance(s1, s2, prefix_weight, score_cutoff, score_cutoff); +} + +template >> +double jaro_winkler_normalized_similarity(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + double prefix_weight = 0.1, double score_cutoff = 0.0) +{ + return detail::JaroWinkler::normalized_similarity(first1, last1, first2, last2, prefix_weight, + score_cutoff, score_cutoff); +} + +template +double jaro_winkler_normalized_similarity(const Sentence1& s1, const Sentence2& s2, + double prefix_weight = 0.1, double score_cutoff = 0.0) +{ + return detail::JaroWinkler::normalized_similarity(s1, s2, prefix_weight, score_cutoff, score_cutoff); +} + +#ifdef RAPIDFUZZ_SIMD +namespace experimental { +template +struct MultiJaroWinkler : public detail::MultiSimilarityBase, double, 0, 1> { + +private: + friend detail::MultiSimilarityBase, double, 0, 1>; + friend detail::MultiNormalizedMetricBase, double>; + +public: + MultiJaroWinkler(size_t count, double prefix_weight_ = 0.1) : scorer(count), prefix_weight(prefix_weight_) + {} + + /** + * @brief get minimum size required for result vectors passed into + * - distance + * - similarity + * - normalized_distance + * - normalized_similarity + * + * @return minimum vector size + */ + size_t result_count() const + { + return scorer.result_count(); + } + + template + void insert(const Sentence1& s1_) + { + insert(detail::to_begin(s1_), detail::to_end(s1_)); + } + + template + void insert(InputIt1 first1, InputIt1 last1) + { + scorer.insert(first1, last1); + size_t len = static_cast(std::distance(first1, last1)); + std::array prefix; + for (size_t i = 0; i < std::min(len, size_t(4)); ++i) + prefix[i] = static_cast(first1[static_cast(i)]); + + str_lens.push_back(len); + prefixes.push_back(prefix); + } + +private: + template + void _similarity(double* scores, size_t score_count, const detail::Range& s2, + double score_cutoff = 0.0) const + { + if (score_count < result_count()) + throw std::invalid_argument("scores has to have >= result_count() elements"); + + scorer.similarity(scores, score_count, s2, std::min(0.7, score_cutoff)); + + for (size_t i = 0; i < get_input_count(); ++i) { + if (scores[i] > 0.7) { + size_t min_len = std::min(s2.size(), str_lens[i]); + size_t max_prefix = std::min(min_len, size_t(4)); + size_t prefix = 0; + for (; prefix < max_prefix; ++prefix) + if (static_cast(s2[prefix]) != prefixes[i][prefix]) break; + + scores[i] += static_cast(prefix) * prefix_weight * (1.0 - scores[i]); + scores[i] = std::min(scores[i], 1.0); + } + + if (scores[i] < score_cutoff) scores[i] = 0.0; + } + } + + template + double maximum([[maybe_unused]] size_t s1_idx, const detail::Range&) const + { + return 1.0; + } + + size_t get_input_count() const noexcept + { + return str_lens.size(); + } + + std::vector str_lens; + // todo this could lead to incorrect results when comparing uint64_t with int64_t + std::vector> prefixes; + MultiJaro scorer; + double prefix_weight; +}; + +} /* namespace experimental */ +#endif /* RAPIDFUZZ_SIMD */ + +template +struct CachedJaroWinkler : public detail::CachedSimilarityBase, double, 0, 1> { + template + explicit CachedJaroWinkler(const Sentence1& s1_, double _prefix_weight = 0.1) + : CachedJaroWinkler(detail::to_begin(s1_), detail::to_end(s1_), _prefix_weight) + {} + + template + CachedJaroWinkler(InputIt1 first1, InputIt1 last1, double _prefix_weight = 0.1) + : prefix_weight(_prefix_weight), s1(first1, last1), PM(detail::Range(first1, last1)) + {} + +private: + friend detail::CachedSimilarityBase, double, 0, 1>; + friend detail::CachedNormalizedMetricBase>; + + template + double maximum(const detail::Range&) const + { + return 1.0; + } + + template + double _similarity(const detail::Range& s2, double score_cutoff, + [[maybe_unused]] double score_hint) const + { + return detail::jaro_winkler_similarity(PM, detail::Range(s1), s2, prefix_weight, score_cutoff); + } + + double prefix_weight; + std::vector s1; + detail::BlockPatternMatchVector PM; +}; + +template +explicit CachedJaroWinkler(const Sentence1& s1_, + double _prefix_weight = 0.1) -> CachedJaroWinkler>; + +template +CachedJaroWinkler(InputIt1 first1, InputIt1 last1, + double _prefix_weight = 0.1) -> CachedJaroWinkler>; + +} // namespace rapidfuzz diff --git a/src/external/rapidfuzz-cpp/rapidfuzz/distance/JaroWinkler_impl.hpp b/src/external/rapidfuzz-cpp/rapidfuzz/distance/JaroWinkler_impl.hpp new file mode 100644 index 00000000..c8eb6575 --- /dev/null +++ b/src/external/rapidfuzz-cpp/rapidfuzz/distance/JaroWinkler_impl.hpp @@ -0,0 +1,90 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright © 2022-present Max Bachmann */ + +#include + +namespace rapidfuzz::detail { + +template +double jaro_winkler_similarity(const Range& P, const Range& T, double prefix_weight, + double score_cutoff) +{ + size_t P_len = P.size(); + size_t T_len = T.size(); + size_t min_len = std::min(P_len, T_len); + size_t prefix = 0; + size_t max_prefix = std::min(min_len, size_t(4)); + + for (; prefix < max_prefix; ++prefix) + if (T[prefix] != P[prefix]) break; + + double jaro_score_cutoff = score_cutoff; + if (jaro_score_cutoff > 0.7) { + double prefix_sim = static_cast(prefix) * prefix_weight; + + if (prefix_sim >= 1.0) + jaro_score_cutoff = 0.7; + else + jaro_score_cutoff = std::max(0.7, (prefix_sim - jaro_score_cutoff) / (prefix_sim - 1.0)); + } + + double Sim = jaro_similarity(P, T, jaro_score_cutoff); + if (Sim > 0.7) { + Sim += static_cast(prefix) * prefix_weight * (1.0 - Sim); + Sim = std::min(Sim, 1.0); + } + + return (Sim >= score_cutoff) ? Sim : 0; +} + +template +double jaro_winkler_similarity(const BlockPatternMatchVector& PM, const Range& P, + const Range& T, double prefix_weight, double score_cutoff) +{ + size_t P_len = P.size(); + size_t T_len = T.size(); + size_t min_len = std::min(P_len, T_len); + size_t prefix = 0; + size_t max_prefix = std::min(min_len, size_t(4)); + + for (; prefix < max_prefix; ++prefix) + if (T[prefix] != P[prefix]) break; + + double jaro_score_cutoff = score_cutoff; + if (jaro_score_cutoff > 0.7) { + double prefix_sim = static_cast(prefix) * prefix_weight; + + if (prefix_sim >= 1.0) + jaro_score_cutoff = 0.7; + else + jaro_score_cutoff = std::max(0.7, (prefix_sim - jaro_score_cutoff) / (prefix_sim - 1.0)); + } + + double Sim = jaro_similarity(PM, P, T, jaro_score_cutoff); + if (Sim > 0.7) { + Sim += static_cast(prefix) * prefix_weight * (1.0 - Sim); + Sim = std::min(Sim, 1.0); + } + + return (Sim >= score_cutoff) ? Sim : 0; +} + +class JaroWinkler : public SimilarityBase { + friend SimilarityBase; + friend NormalizedMetricBase; + + template + static double maximum(const Range&, const Range&, double) noexcept + { + return 1.0; + } + + template + static double _similarity(const Range& s1, const Range& s2, double prefix_weight, + double score_cutoff, [[maybe_unused]] double score_hint) + { + return jaro_winkler_similarity(s1, s2, prefix_weight, score_cutoff); + } +}; + +} // namespace rapidfuzz::detail diff --git a/src/external/rapidfuzz-cpp/rapidfuzz/distance/Jaro_impl.hpp b/src/external/rapidfuzz-cpp/rapidfuzz/distance/Jaro_impl.hpp new file mode 100644 index 00000000..1c4d946a --- /dev/null +++ b/src/external/rapidfuzz-cpp/rapidfuzz/distance/Jaro_impl.hpp @@ -0,0 +1,845 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright © 2022-present Max Bachmann */ + +#include +#include +#include +#include +#include +#include +#include + +namespace rapidfuzz::detail { + +struct FlaggedCharsWord { + uint64_t P_flag; + uint64_t T_flag; +}; + +struct FlaggedCharsMultiword { + std::vector P_flag; + std::vector T_flag; +}; + +struct SearchBoundMask { + size_t words = 0; + size_t empty_words = 0; + uint64_t last_mask = 0; + uint64_t first_mask = 0; +}; + +static inline double jaro_calculate_similarity(size_t P_len, size_t T_len, size_t CommonChars, + size_t Transpositions) +{ + Transpositions /= 2; + double Sim = 0; + Sim += static_cast(CommonChars) / static_cast(P_len); + Sim += static_cast(CommonChars) / static_cast(T_len); + Sim += (static_cast(CommonChars) - static_cast(Transpositions)) / + static_cast(CommonChars); + return Sim / 3.0; +} + +/** + * @brief filter matches below score_cutoff based on string lengths + */ +static inline bool jaro_length_filter(size_t P_len, size_t T_len, double score_cutoff) +{ + if (!T_len || !P_len) return false; + + double min_len = static_cast(std::min(P_len, T_len)); + double Sim = min_len / static_cast(P_len) + min_len / static_cast(T_len) + 1.0; + Sim /= 3.0; + return Sim >= score_cutoff; +} + +/** + * @brief filter matches below score_cutoff based on string lengths and common characters + */ +static inline bool jaro_common_char_filter(size_t P_len, size_t T_len, size_t CommonChars, + double score_cutoff) +{ + if (!CommonChars) return false; + + double Sim = 0; + Sim += static_cast(CommonChars) / static_cast(P_len); + Sim += static_cast(CommonChars) / static_cast(T_len); + Sim += 1.0; + Sim /= 3.0; + return Sim >= score_cutoff; +} + +static inline size_t count_common_chars(const FlaggedCharsWord& flagged) +{ + return popcount(flagged.P_flag); +} + +static inline size_t count_common_chars(const FlaggedCharsMultiword& flagged) +{ + size_t CommonChars = 0; + if (flagged.P_flag.size() < flagged.T_flag.size()) { + for (uint64_t flag : flagged.P_flag) { + CommonChars += popcount(flag); + } + } + else { + for (uint64_t flag : flagged.T_flag) { + CommonChars += popcount(flag); + } + } + return CommonChars; +} + +template +static inline FlaggedCharsWord flag_similar_characters_word(const PM_Vec& PM, + [[maybe_unused]] const Range& P, + const Range& T, size_t Bound) +{ + assert(P.size() <= 64); + assert(T.size() <= 64); + assert(Bound > P.size() || P.size() - Bound <= T.size()); + + FlaggedCharsWord flagged = {0, 0}; + + uint64_t BoundMask = bit_mask_lsb(Bound + 1); + + size_t j = 0; + auto T_iter = T.begin(); + for (; j < std::min(Bound, T.size()); ++j, ++T_iter) { + uint64_t PM_j = PM.get(0, *T_iter) & BoundMask & (~flagged.P_flag); + + flagged.P_flag |= blsi(PM_j); + flagged.T_flag |= static_cast(PM_j != 0) << j; + + BoundMask = (BoundMask << 1) | 1; + } + + for (; j < T.size(); ++j, ++T_iter) { + uint64_t PM_j = PM.get(0, *T_iter) & BoundMask & (~flagged.P_flag); + + flagged.P_flag |= blsi(PM_j); + flagged.T_flag |= static_cast(PM_j != 0) << j; + + BoundMask <<= 1; + } + + return flagged; +} + +template +static inline void flag_similar_characters_step(const BlockPatternMatchVector& PM, CharT T_j, + FlaggedCharsMultiword& flagged, size_t j, + SearchBoundMask BoundMask) +{ + size_t j_word = j / 64; + size_t j_pos = j % 64; + size_t word = BoundMask.empty_words; + size_t last_word = word + BoundMask.words; + + if (BoundMask.words == 1) { + uint64_t PM_j = + PM.get(word, T_j) & BoundMask.last_mask & BoundMask.first_mask & (~flagged.P_flag[word]); + + flagged.P_flag[word] |= blsi(PM_j); + flagged.T_flag[j_word] |= static_cast(PM_j != 0) << j_pos; + return; + } + + if (BoundMask.first_mask) { + uint64_t PM_j = PM.get(word, T_j) & BoundMask.first_mask & (~flagged.P_flag[word]); + + if (PM_j) { + flagged.P_flag[word] |= blsi(PM_j); + flagged.T_flag[j_word] |= 1ull << j_pos; + return; + } + word++; + } + + /* unroll for better performance on long sequences when access is fast */ + if (T_j >= 0 && T_j < 256) { + for (; word + 3 < last_word - 1; word += 4) { + uint64_t PM_j[4]; + unroll([&](auto i) { + PM_j[i] = PM.get(word + i, static_cast(T_j)) & (~flagged.P_flag[word + i]); + }); + + if (PM_j[0]) { + flagged.P_flag[word] |= blsi(PM_j[0]); + flagged.T_flag[j_word] |= 1ull << j_pos; + return; + } + if (PM_j[1]) { + flagged.P_flag[word + 1] |= blsi(PM_j[1]); + flagged.T_flag[j_word] |= 1ull << j_pos; + return; + } + if (PM_j[2]) { + flagged.P_flag[word + 2] |= blsi(PM_j[2]); + flagged.T_flag[j_word] |= 1ull << j_pos; + return; + } + if (PM_j[3]) { + flagged.P_flag[word + 3] |= blsi(PM_j[3]); + flagged.T_flag[j_word] |= 1ull << j_pos; + return; + } + } + } + + for (; word < last_word - 1; ++word) { + uint64_t PM_j = PM.get(word, T_j) & (~flagged.P_flag[word]); + + if (PM_j) { + flagged.P_flag[word] |= blsi(PM_j); + flagged.T_flag[j_word] |= 1ull << j_pos; + return; + } + } + + if (BoundMask.last_mask) { + uint64_t PM_j = PM.get(word, T_j) & BoundMask.last_mask & (~flagged.P_flag[word]); + + flagged.P_flag[word] |= blsi(PM_j); + flagged.T_flag[j_word] |= static_cast(PM_j != 0) << j_pos; + } +} + +template +static inline FlaggedCharsMultiword flag_similar_characters_block(const BlockPatternMatchVector& PM, + const Range& P, + const Range& T, size_t Bound) +{ + assert(P.size() > 64 || T.size() > 64); + assert(Bound > P.size() || P.size() - Bound <= T.size()); + assert(Bound >= 31); + + FlaggedCharsMultiword flagged; + flagged.T_flag.resize(ceil_div(T.size(), 64)); + flagged.P_flag.resize(ceil_div(P.size(), 64)); + + SearchBoundMask BoundMask; + size_t start_range = std::min(Bound + 1, P.size()); + BoundMask.words = 1 + start_range / 64; + BoundMask.empty_words = 0; + BoundMask.last_mask = (1ull << (start_range % 64)) - 1; + BoundMask.first_mask = ~UINT64_C(0); + + auto T_iter = T.begin(); + for (size_t j = 0; j < T.size(); ++j, ++T_iter) { + flag_similar_characters_step(PM, *T_iter, flagged, j, BoundMask); + + if (j + Bound + 1 < P.size()) { + BoundMask.last_mask = (BoundMask.last_mask << 1) | 1; + if (j + Bound + 2 < P.size() && BoundMask.last_mask == ~UINT64_C(0)) { + BoundMask.last_mask = 0; + BoundMask.words++; + } + } + + if (j >= Bound) { + BoundMask.first_mask <<= 1; + if (BoundMask.first_mask == 0) { + BoundMask.first_mask = ~UINT64_C(0); + BoundMask.words--; + BoundMask.empty_words++; + } + } + } + + return flagged; +} + +template +static inline size_t count_transpositions_word(const PM_Vec& PM, const Range& T, + const FlaggedCharsWord& flagged) +{ + uint64_t P_flag = flagged.P_flag; + uint64_t T_flag = flagged.T_flag; + + size_t Transpositions = 0; + while (T_flag) { + uint64_t PatternFlagMask = blsi(P_flag); + + Transpositions += !(PM.get(0, T[countr_zero(T_flag)]) & PatternFlagMask); + + T_flag = blsr(T_flag); + P_flag ^= PatternFlagMask; + } + + return Transpositions; +} + +template +static inline size_t count_transpositions_block(const BlockPatternMatchVector& PM, const Range& T, + const FlaggedCharsMultiword& flagged, size_t FlaggedChars) +{ + size_t TextWord = 0; + size_t PatternWord = 0; + uint64_t T_flag = flagged.T_flag[TextWord]; + uint64_t P_flag = flagged.P_flag[PatternWord]; + + auto T_first = T.begin(); + size_t Transpositions = 0; + while (FlaggedChars) { + while (!T_flag) { + TextWord++; + T_first += 64; + T_flag = flagged.T_flag[TextWord]; + } + + while (T_flag) { + while (!P_flag) { + PatternWord++; + P_flag = flagged.P_flag[PatternWord]; + } + + uint64_t PatternFlagMask = blsi(P_flag); + + Transpositions += !(PM.get(PatternWord, T_first[static_cast(countr_zero(T_flag))]) & + PatternFlagMask); + + T_flag = blsr(T_flag); + P_flag ^= PatternFlagMask; + + FlaggedChars--; + } + } + + return Transpositions; +} + +// todo cleanup the split between jaro_bounds +/** + * @brief find bounds + */ +static inline size_t jaro_bounds(size_t P_len, size_t T_len) +{ + /* since jaro uses a sliding window some parts of T/P might never be in + * range an can be removed ahead of time + */ + size_t Bound = (T_len > P_len) ? T_len : P_len; + Bound /= 2; + if (Bound > 0) Bound--; + + return Bound; +} + +/** + * @brief find bounds and skip out of bound parts of the sequences + */ +template +static inline size_t jaro_bounds(Range& P, Range& T) +{ + size_t P_len = P.size(); + size_t T_len = T.size(); + + // this is currently an early exit condition + // if this is changed handle this below, so Bound is never below 0 + assert(P_len != 0 || T_len != 0); + + /* since jaro uses a sliding window some parts of T/P might never be in + * range an can be removed ahead of time + */ + size_t Bound = 0; + if (T_len > P_len) { + Bound = T_len / 2 - 1; + if (T_len > P_len + Bound) T.remove_suffix(T_len - (P_len + Bound)); + } + else { + Bound = P_len / 2 - 1; + if (P_len > T_len + Bound) P.remove_suffix(P_len - (T_len + Bound)); + } + return Bound; +} + +template +static inline double jaro_similarity(Range P, Range T, double score_cutoff) +{ + size_t P_len = P.size(); + size_t T_len = T.size(); + + if (score_cutoff > 1.0) return 0.0; + + if (!P_len && !T_len) return 1.0; + + /* filter out based on the length difference between the two strings */ + if (!jaro_length_filter(P_len, T_len, score_cutoff)) return 0.0; + + if (P_len == 1 && T_len == 1) return static_cast(P.front() == T.front()); + + size_t Bound = jaro_bounds(P, T); + + /* common prefix never includes Transpositions */ + size_t CommonChars = remove_common_prefix(P, T); + size_t Transpositions = 0; + + if (P.empty() || T.empty()) { + /* already has correct number of common chars and transpositions */ + } + else if (P.size() <= 64 && T.size() <= 64) { + PatternMatchVector PM(P); + auto flagged = flag_similar_characters_word(PM, P, T, Bound); + CommonChars += count_common_chars(flagged); + + if (!jaro_common_char_filter(P_len, T_len, CommonChars, score_cutoff)) return 0.0; + + Transpositions = count_transpositions_word(PM, T, flagged); + } + else { + BlockPatternMatchVector PM(P); + auto flagged = flag_similar_characters_block(PM, P, T, Bound); + size_t FlaggedChars = count_common_chars(flagged); + CommonChars += FlaggedChars; + + if (!jaro_common_char_filter(P_len, T_len, CommonChars, score_cutoff)) return 0.0; + + Transpositions = count_transpositions_block(PM, T, flagged, FlaggedChars); + } + + double Sim = jaro_calculate_similarity(P_len, T_len, CommonChars, Transpositions); + return (Sim >= score_cutoff) ? Sim : 0; +} + +template +static inline double jaro_similarity(const BlockPatternMatchVector& PM, Range P, Range T, + double score_cutoff) +{ + size_t P_len = P.size(); + size_t T_len = T.size(); + + if (score_cutoff > 1.0) return 0.0; + + if (!P_len && !T_len) return 1.0; + + /* filter out based on the length difference between the two strings */ + if (!jaro_length_filter(P_len, T_len, score_cutoff)) return 0.0; + + if (P_len == 1 && T_len == 1) return static_cast(P[0] == T[0]); + + size_t Bound = jaro_bounds(P, T); + + /* common prefix never includes Transpositions */ + size_t CommonChars = 0; + size_t Transpositions = 0; + + if (P.empty() || T.empty()) { + /* already has correct number of common chars and transpositions */ + } + else if (P.size() <= 64 && T.size() <= 64) { + auto flagged = flag_similar_characters_word(PM, P, T, Bound); + CommonChars += count_common_chars(flagged); + + if (!jaro_common_char_filter(P_len, T_len, CommonChars, score_cutoff)) return 0.0; + + Transpositions = count_transpositions_word(PM, T, flagged); + } + else { + auto flagged = flag_similar_characters_block(PM, P, T, Bound); + size_t FlaggedChars = count_common_chars(flagged); + CommonChars += FlaggedChars; + + if (!jaro_common_char_filter(P_len, T_len, CommonChars, score_cutoff)) return 0.0; + + Transpositions = count_transpositions_block(PM, T, flagged, FlaggedChars); + } + + double Sim = jaro_calculate_similarity(P_len, T_len, CommonChars, Transpositions); + return (Sim >= score_cutoff) ? Sim : 0; +} + +#ifdef RAPIDFUZZ_SIMD + +template +struct JaroSimilaritySimdBounds { + size_t maxBound = 0; + VecType boundMaskSize; + VecType boundMask; +}; + +template +static inline auto jaro_similarity_prepare_bound_short_s2(const VecType* s1_lengths, Range& s2) +{ +# ifdef RAPIDFUZZ_AVX2 + using namespace simd_avx2; +# else + using namespace simd_sse2; +# endif + + [[maybe_unused]] static constexpr size_t alignment = native_simd::alignment; + static constexpr size_t vec_width = native_simd::size; + assert(s2.size() <= sizeof(VecType) * 8); + + JaroSimilaritySimdBounds> bounds; + + VecType maxLen = 0; + // todo permutate + max to find maxLen + // side-note: we know only the first 8 bit are actually used + for (size_t i = 0; i < vec_width; ++i) + if (s1_lengths[i] > maxLen) maxLen = s1_lengths[i]; + +# ifdef RAPIDFUZZ_AVX2 + native_simd zero(VecType(0)); + native_simd one(1); + + native_simd s1_lengths_simd(reinterpret_cast(s1_lengths)); + native_simd s2_length_simd(static_cast(s2.size())); + + // we always know that the number does not exceed 64, so we can operate on smaller vectors if this + // proves to be faster + native_simd boundSizes = max8(s1_lengths_simd, s2_length_simd) >> 1; // divide by two + // todo there could be faster options since comparisions can be relatively expensive for some vector sizes + boundSizes -= (boundSizes > zero) & one; + + // this can never overflow even when using larger vectors for shifting here, since in the worst case of + // 8bit vectors this shifts by (8/2-1)*2=6 bits todo << 1 performs unneeded masking here sllv is pretty + // expensive for 8 / 16 bit since it has to be emulated maybe there is a better solution + bounds.boundMaskSize = sllv(one, boundSizes << 1) - one; + bounds.boundMask = sllv(one, boundSizes + one) - one; + + bounds.maxBound = (s2.size() > maxLen) ? s2.size() : maxLen; + bounds.maxBound /= 2; + if (bounds.maxBound > 0) bounds.maxBound--; +# else + alignas(alignment) std::array boundMaskSize_; + alignas(alignment) std::array boundMask_; + + // todo try to find a simd implementation for sse2 + for (size_t i = 0; i < vec_width; ++i) { + size_t Bound = jaro_bounds(static_cast(s1_lengths[i]), s2.size()); + + if (Bound > bounds.maxBound) bounds.maxBound = Bound; + + boundMaskSize_[i] = bit_mask_lsb(2 * Bound); + boundMask_[i] = bit_mask_lsb(Bound + 1); + } + + bounds.boundMaskSize = native_simd(reinterpret_cast(boundMaskSize_.data())); + bounds.boundMask = native_simd(reinterpret_cast(boundMask_.data())); +# endif + + size_t lastRelevantChar = static_cast(maxLen) + bounds.maxBound; + if (s2.size() > lastRelevantChar) s2.remove_suffix(s2.size() - lastRelevantChar); + + return bounds; +} + +template +static inline auto jaro_similarity_prepare_bound_long_s2(const VecType* s1_lengths, Range& s2) +{ +# ifdef RAPIDFUZZ_AVX2 + using namespace simd_avx2; +# else + using namespace simd_sse2; +# endif + + static constexpr size_t vec_width = native_simd::size; + assert(s2.size() > sizeof(VecType) * 8); + + JaroSimilaritySimdBounds> bounds; + + VecType maxLen = 0; + // todo permutate + max to find maxLen + // side-note: we know only the first 8 bit are actually used + for (size_t i = 0; i < vec_width; ++i) + if (s1_lengths[i] > maxLen) maxLen = s1_lengths[i]; + + bounds.maxBound = s2.size() / 2 - 1; + bounds.boundMaskSize = native_simd(bit_mask_lsb(2 * bounds.maxBound)); + bounds.boundMask = native_simd(bit_mask_lsb(bounds.maxBound + 1)); + + size_t lastRelevantChar = static_cast(maxLen) + bounds.maxBound; + if (s2.size() > lastRelevantChar) s2.remove_suffix(s2.size() - lastRelevantChar); + + return bounds; +} + +template +static inline void +jaro_similarity_simd_long_s2(Range scores, const detail::BlockPatternMatchVector& block, + VecType* s1_lengths, Range s2, double score_cutoff) noexcept +{ +# ifdef RAPIDFUZZ_AVX2 + using namespace simd_avx2; +# else + using namespace simd_sse2; +# endif + + static constexpr size_t alignment = native_simd::alignment; + static constexpr size_t vec_width = native_simd::size; + static constexpr size_t vecs = native_simd::size; + assert(block.size() % vecs == 0); + assert(s2.size() > sizeof(VecType) * 8); + + struct AlignedAlloc { + AlignedAlloc(size_t size) : memory(rf_aligned_alloc(native_simd::alignment, size)) + {} + + ~AlignedAlloc() + { + rf_aligned_free(memory); + } + + void* memory = nullptr; + }; + + native_simd zero(VecType(0)); + native_simd one(1); + size_t result_index = 0; + + size_t s2_block_count = detail::ceil_div(s2.size(), sizeof(VecType) * 8); + AlignedAlloc memory(2 * s2_block_count * sizeof(native_simd)); + + native_simd* T_flag = static_cast*>(memory.memory); + // reuse the same memory since counter is only required in the first half of the algorithm while + // T_flags is required in the second half + native_simd* counter = static_cast*>(memory.memory) + s2_block_count; + VecType* T_flags = static_cast(memory.memory) + s2_block_count * vec_width; + + for (size_t cur_vec = 0; cur_vec < block.size(); cur_vec += vecs) { + auto s2_cur = s2; + auto bounds = jaro_similarity_prepare_bound_long_s2(s1_lengths + result_index, s2_cur); + + native_simd P_flag(VecType(0)); + + std::fill(T_flag, T_flag + detail::ceil_div(s2_cur.size(), sizeof(VecType) * 8), + native_simd(VecType(0))); + std::fill(counter, counter + detail::ceil_div(s2_cur.size(), sizeof(VecType) * 8), + native_simd(VecType(1))); + + // In case s2 is longer than all of the elements in s1_lengths boundMaskSize + // might have all bits set and therefor the condition ((boundMask <= boundMaskSize) & one) + // would incorrectly always set the first bit to 1. + // this is solved by splitting the loop into two parts where after this boundary is reached + // the first bit inside boundMask is no longer set + size_t j = 0; + for (; j < std::min(bounds.maxBound, s2_cur.size()); ++j) { + alignas(alignment) std::array stored; + unroll([&](auto i) { stored[i] = block.get(cur_vec + i, s2_cur[j]); }); + native_simd X(stored.data()); + native_simd PM_j = andnot(X & bounds.boundMask, P_flag); + + P_flag |= blsi(PM_j); + size_t T_word_index = j / (sizeof(VecType) * 8); + T_flag[T_word_index] |= andnot(counter[T_word_index], (PM_j == zero)); + + counter[T_word_index] = counter[T_word_index] << 1; + bounds.boundMask = (bounds.boundMask << 1) | ((bounds.boundMask <= bounds.boundMaskSize) & one); + } + + for (; j < s2_cur.size(); ++j) { + alignas(alignment) std::array stored; + unroll([&](auto i) { stored[i] = block.get(cur_vec + i, s2_cur[j]); }); + native_simd X(stored.data()); + native_simd PM_j = andnot(X & bounds.boundMask, P_flag); + + P_flag |= blsi(PM_j); + size_t T_word_index = j / (sizeof(VecType) * 8); + T_flag[T_word_index] |= andnot(counter[T_word_index], (PM_j == zero)); + + counter[T_word_index] = counter[T_word_index] << 1; + bounds.boundMask = bounds.boundMask << 1; + } + + auto counts = popcount(P_flag); + alignas(alignment) std::array P_flags; + P_flag.store(P_flags.data()); + + for (size_t i = 0; i < detail::ceil_div(s2_cur.size(), sizeof(VecType) * 8); ++i) + T_flag[i].store(T_flags + i * vec_width); + + for (size_t i = 0; i < vec_width; ++i) { + size_t CommonChars = static_cast(counts[i]); + if (!jaro_common_char_filter(static_cast(s1_lengths[result_index]), s2.size(), + CommonChars, score_cutoff)) + { + scores[result_index] = 0.0; + result_index++; + continue; + } + + VecType P_flag_cur = P_flags[i]; + size_t Transpositions = 0; + + static constexpr size_t vecs_per_word = vec_width / vecs; + size_t cur_block = i / vecs_per_word; + size_t offset = sizeof(VecType) * 8 * (i % vecs_per_word); + + { + size_t T_word_index = 0; + VecType T_flag_cur = T_flags[T_word_index * vec_width + i]; + while (P_flag_cur) { + while (!T_flag_cur) { + ++T_word_index; + T_flag_cur = T_flags[T_word_index * vec_width + i]; + } + + VecType PatternFlagMask = blsi(P_flag_cur); + + uint64_t PM_j = + block.get(cur_vec + cur_block, + s2[countr_zero(T_flag_cur) + T_word_index * sizeof(VecType) * 8]); + Transpositions += !(PM_j & (static_cast(PatternFlagMask) << offset)); + + T_flag_cur = blsr(T_flag_cur); + P_flag_cur ^= PatternFlagMask; + } + } + + double Sim = jaro_calculate_similarity(static_cast(s1_lengths[result_index]), s2.size(), + CommonChars, Transpositions); + + scores[result_index] = (Sim >= score_cutoff) ? Sim : 0; + result_index++; + } + } +} + +template +static inline void +jaro_similarity_simd_short_s2(Range scores, const detail::BlockPatternMatchVector& block, + VecType* s1_lengths, Range s2, double score_cutoff) noexcept +{ +# ifdef RAPIDFUZZ_AVX2 + using namespace simd_avx2; +# else + using namespace simd_sse2; +# endif + + static constexpr size_t alignment = native_simd::alignment; + static constexpr size_t vec_width = native_simd::size; + static constexpr size_t vecs = native_simd::size; + assert(block.size() % vecs == 0); + assert(s2.size() <= sizeof(VecType) * 8); + + native_simd zero(VecType(0)); + native_simd one(1); + size_t result_index = 0; + + for (size_t cur_vec = 0; cur_vec < block.size(); cur_vec += vecs) { + auto s2_cur = s2; + auto bounds = jaro_similarity_prepare_bound_short_s2(s1_lengths + result_index, s2_cur); + + native_simd P_flag(VecType(0)); + native_simd T_flag(VecType(0)); + native_simd counter(VecType(1)); + + // In case s2 is longer than all of the elements in s1_lengths boundMaskSize + // might have all bits set and therefor the condition ((boundMask <= boundMaskSize) & one) + // would incorrectly always set the first bit to 1. + // this is solved by splitting the loop into two parts where after this boundary is reached + // the first bit inside boundMask is no longer set + size_t j = 0; + for (; j < std::min(bounds.maxBound, s2_cur.size()); ++j) { + alignas(alignment) std::array stored; + unroll([&](auto i) { stored[i] = block.get(cur_vec + i, s2_cur[j]); }); + native_simd X(stored.data()); + native_simd PM_j = andnot(X & bounds.boundMask, P_flag); + + P_flag |= blsi(PM_j); + T_flag |= andnot(counter, (PM_j == zero)); + + counter = counter << 1; + bounds.boundMask = (bounds.boundMask << 1) | ((bounds.boundMask <= bounds.boundMaskSize) & one); + } + + for (; j < s2_cur.size(); ++j) { + alignas(alignment) std::array stored; + unroll([&](auto i) { stored[i] = block.get(cur_vec + i, s2_cur[j]); }); + native_simd X(stored.data()); + native_simd PM_j = andnot(X & bounds.boundMask, P_flag); + + P_flag |= blsi(PM_j); + T_flag |= andnot(counter, (PM_j == zero)); + + counter = counter << 1; + bounds.boundMask = bounds.boundMask << 1; + } + + auto counts = popcount(P_flag); + alignas(alignment) std::array P_flags; + P_flag.store(P_flags.data()); + alignas(alignment) std::array T_flags; + T_flag.store(T_flags.data()); + for (size_t i = 0; i < vec_width; ++i) { + size_t CommonChars = static_cast(counts[i]); + if (!jaro_common_char_filter(static_cast(s1_lengths[result_index]), s2.size(), + CommonChars, score_cutoff)) + { + scores[result_index] = 0.0; + result_index++; + continue; + } + + VecType P_flag_cur = P_flags[i]; + VecType T_flag_cur = T_flags[i]; + size_t Transpositions = 0; + + static constexpr size_t vecs_per_word = vec_width / vecs; + size_t cur_block = i / vecs_per_word; + size_t offset = sizeof(VecType) * 8 * (i % vecs_per_word); + while (P_flag_cur) { + VecType PatternFlagMask = blsi(P_flag_cur); + + uint64_t PM_j = block.get(cur_vec + cur_block, s2[countr_zero(T_flag_cur)]); + Transpositions += !(PM_j & (static_cast(PatternFlagMask) << offset)); + + T_flag_cur = blsr(T_flag_cur); + P_flag_cur ^= PatternFlagMask; + } + + double Sim = jaro_calculate_similarity(static_cast(s1_lengths[result_index]), s2.size(), + CommonChars, Transpositions); + + scores[result_index] = (Sim >= score_cutoff) ? Sim : 0; + result_index++; + } + } +} + +template +static inline void jaro_similarity_simd(Range scores, const detail::BlockPatternMatchVector& block, + VecType* s1_lengths, size_t s1_lengths_size, const Range& s2, + double score_cutoff) noexcept +{ + if (score_cutoff > 1.0) { + for (size_t i = 0; i < s1_lengths_size; i++) + scores[i] = 0.0; + + return; + } + + if (s2.empty()) { + for (size_t i = 0; i < s1_lengths_size; i++) + scores[i] = s1_lengths[i] ? 0.0 : 1.0; + + return; + } + + if (s2.size() > sizeof(VecType) * 8) + return jaro_similarity_simd_long_s2(scores, block, s1_lengths, s2, score_cutoff); + else + return jaro_similarity_simd_short_s2(scores, block, s1_lengths, s2, score_cutoff); +} + +#endif /* RAPIDFUZZ_SIMD */ + +class Jaro : public SimilarityBase { + friend SimilarityBase; + friend NormalizedMetricBase; + + template + static double maximum(const Range&, const Range&) noexcept + { + return 1.0; + } + + template + static double _similarity(const Range& s1, const Range& s2, double score_cutoff, + [[maybe_unused]] double score_hint) + { + return jaro_similarity(s1, s2, score_cutoff); + } +}; + +} // namespace rapidfuzz::detail diff --git a/src/external/rapidfuzz-cpp/rapidfuzz/distance/LCSseq.hpp b/src/external/rapidfuzz-cpp/rapidfuzz/distance/LCSseq.hpp new file mode 100644 index 00000000..9082dc37 --- /dev/null +++ b/src/external/rapidfuzz-cpp/rapidfuzz/distance/LCSseq.hpp @@ -0,0 +1,235 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright © 2022-present Max Bachmann */ + +#pragma once +#include + +#include +#include + +namespace rapidfuzz { + +template +size_t lcs_seq_distance(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + size_t score_cutoff = std::numeric_limits::max()) +{ + return detail::LCSseq::distance(first1, last1, first2, last2, score_cutoff, score_cutoff); +} + +template +size_t lcs_seq_distance(const Sentence1& s1, const Sentence2& s2, + size_t score_cutoff = std::numeric_limits::max()) +{ + return detail::LCSseq::distance(s1, s2, score_cutoff, score_cutoff); +} + +template +size_t lcs_seq_similarity(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + size_t score_cutoff = 0) +{ + return detail::LCSseq::similarity(first1, last1, first2, last2, score_cutoff, score_cutoff); +} + +template +size_t lcs_seq_similarity(const Sentence1& s1, const Sentence2& s2, size_t score_cutoff = 0) +{ + return detail::LCSseq::similarity(s1, s2, score_cutoff, score_cutoff); +} + +template +double lcs_seq_normalized_distance(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + double score_cutoff = 1.0) +{ + return detail::LCSseq::normalized_distance(first1, last1, first2, last2, score_cutoff, score_cutoff); +} + +template +double lcs_seq_normalized_distance(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 1.0) +{ + return detail::LCSseq::normalized_distance(s1, s2, score_cutoff, score_cutoff); +} + +template +double lcs_seq_normalized_similarity(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + double score_cutoff = 0.0) +{ + return detail::LCSseq::normalized_similarity(first1, last1, first2, last2, score_cutoff, score_cutoff); +} + +template +double lcs_seq_normalized_similarity(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 0.0) +{ + return detail::LCSseq::normalized_similarity(s1, s2, score_cutoff, score_cutoff); +} + +template +Editops lcs_seq_editops(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2) +{ + return detail::lcs_seq_editops(detail::Range(first1, last1), detail::Range(first2, last2)); +} + +template +Editops lcs_seq_editops(const Sentence1& s1, const Sentence2& s2) +{ + return detail::lcs_seq_editops(detail::Range(s1), detail::Range(s2)); +} + +#ifdef RAPIDFUZZ_SIMD +namespace experimental { +template +struct MultiLCSseq : public detail::MultiSimilarityBase, size_t, 0, + std::numeric_limits::max()> { +private: + friend detail::MultiSimilarityBase, size_t, 0, std::numeric_limits::max()>; + friend detail::MultiNormalizedMetricBase, size_t>; + + constexpr static size_t get_vec_size() + { +# ifdef RAPIDFUZZ_AVX2 + using namespace detail::simd_avx2; +# else + using namespace detail::simd_sse2; +# endif + if constexpr (MaxLen <= 8) + return native_simd::size; + else if constexpr (MaxLen <= 16) + return native_simd::size; + else if constexpr (MaxLen <= 32) + return native_simd::size; + else if constexpr (MaxLen <= 64) + return native_simd::size; + + static_assert(MaxLen <= 64); + } + + constexpr static size_t find_block_count(size_t count) + { + size_t vec_size = get_vec_size(); + size_t simd_vec_count = detail::ceil_div(count, vec_size); + return detail::ceil_div(simd_vec_count * vec_size * MaxLen, 64); + } + +public: + MultiLCSseq(size_t count) : input_count(count), pos(0), PM(find_block_count(count) * 64) + { + str_lens.resize(result_count()); + } + + /** + * @brief get minimum size required for result vectors passed into + * - distance + * - similarity + * - normalized_distance + * - normalized_similarity + * + * @return minimum vector size + */ + size_t result_count() const + { + size_t vec_size = get_vec_size(); + size_t simd_vec_count = detail::ceil_div(input_count, vec_size); + return simd_vec_count * vec_size; + } + + template + void insert(const Sentence1& s1_) + { + insert(detail::to_begin(s1_), detail::to_end(s1_)); + } + + template + void insert(InputIt1 first1, InputIt1 last1) + { + auto len = std::distance(first1, last1); + int block_pos = static_cast((pos * MaxLen) % 64); + auto block = (pos * MaxLen) / 64; + assert(len <= MaxLen); + + if (pos >= input_count) throw std::invalid_argument("out of bounds insert"); + + str_lens[pos] = static_cast(len); + + for (; first1 != last1; ++first1) { + PM.insert(block, *first1, block_pos); + block_pos++; + } + pos++; + } + +private: + template + void _similarity(size_t* scores, size_t score_count, const detail::Range& s2, + size_t score_cutoff = 0) const + { + if (score_count < result_count()) + throw std::invalid_argument("scores has to have >= result_count() elements"); + + detail::Range scores_(scores, scores + score_count); + if constexpr (MaxLen == 8) + detail::lcs_simd(scores_, PM, s2, score_cutoff); + else if constexpr (MaxLen == 16) + detail::lcs_simd(scores_, PM, s2, score_cutoff); + else if constexpr (MaxLen == 32) + detail::lcs_simd(scores_, PM, s2, score_cutoff); + else if constexpr (MaxLen == 64) + detail::lcs_simd(scores_, PM, s2, score_cutoff); + } + + template + size_t maximum(size_t s1_idx, const detail::Range& s2) const + { + return std::max(str_lens[s1_idx], s2.size()); + } + + size_t get_input_count() const noexcept + { + return input_count; + } + + size_t input_count; + size_t pos; + detail::BlockPatternMatchVector PM; + std::vector str_lens; +}; +} /* namespace experimental */ +#endif + +template +struct CachedLCSseq + : detail::CachedSimilarityBase, size_t, 0, std::numeric_limits::max()> { + template + explicit CachedLCSseq(const Sentence1& s1_) : CachedLCSseq(detail::to_begin(s1_), detail::to_end(s1_)) + {} + + template + CachedLCSseq(InputIt1 first1, InputIt1 last1) : s1(first1, last1), PM(detail::Range(first1, last1)) + {} + +private: + friend detail::CachedSimilarityBase, size_t, 0, std::numeric_limits::max()>; + friend detail::CachedNormalizedMetricBase>; + + template + size_t maximum(const detail::Range& s2) const + { + return std::max(s1.size(), s2.size()); + } + + template + size_t _similarity(const detail::Range& s2, size_t score_cutoff, + [[maybe_unused]] size_t score_hint) const + { + return detail::lcs_seq_similarity(PM, detail::Range(s1), s2, score_cutoff); + } + + std::vector s1; + detail::BlockPatternMatchVector PM; +}; + +template +explicit CachedLCSseq(const Sentence1& s1_) -> CachedLCSseq>; + +template +CachedLCSseq(InputIt1 first1, InputIt1 last1) -> CachedLCSseq>; + +} // namespace rapidfuzz diff --git a/src/external/rapidfuzz-cpp/rapidfuzz/distance/LCSseq_impl.hpp b/src/external/rapidfuzz-cpp/rapidfuzz/distance/LCSseq_impl.hpp new file mode 100644 index 00000000..20d10b1a --- /dev/null +++ b/src/external/rapidfuzz-cpp/rapidfuzz/distance/LCSseq_impl.hpp @@ -0,0 +1,529 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright © 2022-present Max Bachmann */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace rapidfuzz::detail { + +template +struct LCSseqResult; + +template <> +struct LCSseqResult { + ShiftedBitMatrix S; + + size_t sim; +}; + +template <> +struct LCSseqResult { + size_t sim; +}; + +/* + * An encoded mbleven model table. + * + * Each 8-bit integer represents an edit sequence, with using two + * bits for a single operation. + * + * Each Row of 8 integers represent all possible combinations + * of edit sequences for a gived maximum edit distance and length + * difference between the two strings, that is below the maximum + * edit distance + * + * 0x1 = 01 = DELETE, + * 0x2 = 10 = INSERT + * + * 0x5 -> DEL + DEL + * 0x6 -> DEL + INS + * 0x9 -> INS + DEL + * 0xA -> INS + INS + */ +static constexpr std::array, 14> lcs_seq_mbleven2018_matrix = {{ + /* max edit distance 1 */ + {0}, + /* case does not occur */ /* len_diff 0 */ + {0x01}, /* len_diff 1 */ + /* max edit distance 2 */ + {0x09, 0x06}, /* len_diff 0 */ + {0x01}, /* len_diff 1 */ + {0x05}, /* len_diff 2 */ + /* max edit distance 3 */ + {0x09, 0x06}, /* len_diff 0 */ + {0x25, 0x19, 0x16}, /* len_diff 1 */ + {0x05}, /* len_diff 2 */ + {0x15}, /* len_diff 3 */ + /* max edit distance 4 */ + {0x96, 0x66, 0x5A, 0x99, 0x69, 0xA5}, /* len_diff 0 */ + {0x25, 0x19, 0x16}, /* len_diff 1 */ + {0x65, 0x56, 0x95, 0x59}, /* len_diff 2 */ + {0x15}, /* len_diff 3 */ + {0x55}, /* len_diff 4 */ +}}; + +template +size_t lcs_seq_mbleven2018(const Range& s1, const Range& s2, size_t score_cutoff) +{ + auto len1 = s1.size(); + auto len2 = s2.size(); + assert(len1 != 0); + assert(len2 != 0); + + if (len1 < len2) return lcs_seq_mbleven2018(s2, s1, score_cutoff); + + auto len_diff = len1 - len2; + size_t max_misses = len1 + len2 - 2 * score_cutoff; + size_t ops_index = (max_misses + max_misses * max_misses) / 2 + len_diff - 1; + auto& possible_ops = lcs_seq_mbleven2018_matrix[ops_index]; + size_t max_len = 0; + + for (uint8_t ops : possible_ops) { + auto iter_s1 = s1.begin(); + auto iter_s2 = s2.begin(); + size_t cur_len = 0; + + if (!ops) break; + + while (iter_s1 != s1.end() && iter_s2 != s2.end()) { + if (*iter_s1 != *iter_s2) { + if (!ops) break; + if (ops & 1) + iter_s1++; + else if (ops & 2) + iter_s2++; +#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && __GNUC__ < 10 +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wconversion" +#endif + ops >>= 2; +#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && __GNUC__ < 10 +# pragma GCC diagnostic pop +#endif + } + else { + cur_len++; + iter_s1++; + iter_s2++; + } + } + + max_len = std::max(max_len, cur_len); + } + + return (max_len >= score_cutoff) ? max_len : 0; +} + +#ifdef RAPIDFUZZ_SIMD +template +void lcs_simd(Range scores, const BlockPatternMatchVector& block, const Range& s2, + size_t score_cutoff) noexcept +{ +# ifdef RAPIDFUZZ_AVX2 + using namespace simd_avx2; +# else + using namespace simd_sse2; +# endif + auto score_iter = scores.begin(); + static constexpr size_t alignment = native_simd::alignment; + static constexpr size_t vecs = native_simd::size; + assert(block.size() % vecs == 0); + + static constexpr size_t interleaveCount = 3; + + size_t cur_vec = 0; + for (; cur_vec + interleaveCount * vecs <= block.size(); cur_vec += interleaveCount * vecs) { + std::array, interleaveCount> S; + unroll([&](auto j) { S[j] = static_cast(-1); }); + + for (const auto& ch : s2) { + unroll([&](auto j) { + alignas(32) std::array stored; + unroll([&](auto i) { stored[i] = block.get(cur_vec + j * vecs + i, ch); }); + + native_simd Matches(stored.data()); + native_simd u = S[j] & Matches; + S[j] = (S[j] + u) | (S[j] - u); + }); + } + + unroll([&](auto j) { + auto counts = popcount(~S[j]); + unroll([&](auto i) { + *score_iter = (counts[i] >= score_cutoff) ? static_cast(counts[i]) : 0; + score_iter++; + }); + }); + } + + for (; cur_vec < block.size(); cur_vec += vecs) { + native_simd S = static_cast(-1); + + for (const auto& ch : s2) { + alignas(alignment) std::array stored; + unroll([&](auto i) { stored[i] = block.get(cur_vec + i, ch); }); + + native_simd Matches(stored.data()); + native_simd u = S & Matches; + S = (S + u) | (S - u); + } + + auto counts = popcount(~S); + unroll([&](auto i) { + *score_iter = (counts[i] >= score_cutoff) ? static_cast(counts[i]) : 0; + score_iter++; + }); + } +} + +#endif + +template +auto lcs_unroll(const PMV& block, const Range&, const Range& s2, + size_t score_cutoff = 0) -> LCSseqResult +{ + uint64_t S[N]; + unroll([&](size_t i) { S[i] = ~UINT64_C(0); }); + + LCSseqResult res; + if constexpr (RecordMatrix) res.S = ShiftedBitMatrix(s2.size(), N, ~UINT64_C(0)); + + auto iter_s2 = s2.begin(); + for (size_t i = 0; i < s2.size(); ++i) { + uint64_t carry = 0; + + static constexpr size_t unroll_factor = 3; + for (unsigned int j = 0; j < N / unroll_factor; ++j) { + unroll([&](size_t word_) { + size_t word = word_ + j * unroll_factor; + uint64_t Matches = block.get(word, *iter_s2); + uint64_t u = S[word] & Matches; + uint64_t x = addc64(S[word], u, carry, &carry); + S[word] = x | (S[word] - u); + + if constexpr (RecordMatrix) res.S[i][word] = S[word]; + }); + } + + unroll([&](size_t word_) { + size_t word = word_ + N / unroll_factor * unroll_factor; + uint64_t Matches = block.get(word, *iter_s2); + uint64_t u = S[word] & Matches; + uint64_t x = addc64(S[word], u, carry, &carry); + S[word] = x | (S[word] - u); + + if constexpr (RecordMatrix) res.S[i][word] = S[word]; + }); + + iter_s2++; + } + + res.sim = 0; + unroll([&](size_t i) { res.sim += popcount(~S[i]); }); + + if (res.sim < score_cutoff) res.sim = 0; + + return res; +} + +/** + * implementation is following the paper Bit-Parallel LCS-length Computation Revisited + * from Heikki Hyyrö + * + * The paper refers to s1 as m and s2 as n + */ +template +auto lcs_blockwise(const PMV& PM, const Range& s1, const Range& s2, + size_t score_cutoff = 0) -> LCSseqResult +{ + assert(score_cutoff <= s1.size()); + assert(score_cutoff <= s2.size()); + + size_t word_size = sizeof(uint64_t) * 8; + size_t words = PM.size(); + std::vector S(words, ~UINT64_C(0)); + + size_t band_width_left = s1.size() - score_cutoff; + size_t band_width_right = s2.size() - score_cutoff; + + LCSseqResult res; + if constexpr (RecordMatrix) { + size_t full_band = band_width_left + 1 + band_width_right; + size_t full_band_words = std::min(words, full_band / word_size + 2); + res.S = ShiftedBitMatrix(s2.size(), full_band_words, ~UINT64_C(0)); + } + + /* first_block is the index of the first block in Ukkonen band. */ + size_t first_block = 0; + size_t last_block = std::min(words, ceil_div(band_width_left + 1, word_size)); + + auto iter_s2 = s2.begin(); + for (size_t row = 0; row < s2.size(); ++row) { + uint64_t carry = 0; + + if constexpr (RecordMatrix) res.S.set_offset(row, static_cast(first_block * word_size)); + + for (size_t word = first_block; word < last_block; ++word) { + const uint64_t Matches = PM.get(word, *iter_s2); + uint64_t Stemp = S[word]; + + uint64_t u = Stemp & Matches; + + uint64_t x = addc64(Stemp, u, carry, &carry); + S[word] = x | (Stemp - u); + + if constexpr (RecordMatrix) res.S[row][word - first_block] = S[word]; + } + + if (row > band_width_right) first_block = (row - band_width_right) / word_size; + + if (row + 1 + band_width_left <= s1.size()) + last_block = ceil_div(row + 1 + band_width_left, word_size); + + iter_s2++; + } + + res.sim = 0; + for (uint64_t Stemp : S) + res.sim += popcount(~Stemp); + + if (res.sim < score_cutoff) res.sim = 0; + + return res; +} + +template +size_t longest_common_subsequence(const PMV& PM, const Range& s1, const Range& s2, + size_t score_cutoff) +{ + assert(score_cutoff <= s1.size()); + assert(score_cutoff <= s2.size()); + + size_t word_size = sizeof(uint64_t) * 8; + size_t words = PM.size(); + size_t band_width_left = s1.size() - score_cutoff; + size_t band_width_right = s2.size() - score_cutoff; + size_t full_band = band_width_left + 1 + band_width_right; + size_t full_band_words = std::min(words, full_band / word_size + 2); + + if (full_band_words < words) return lcs_blockwise(PM, s1, s2, score_cutoff).sim; + + auto nr = ceil_div(s1.size(), 64); + switch (nr) { + case 0: return 0; + case 1: return lcs_unroll<1, false>(PM, s1, s2, score_cutoff).sim; + case 2: return lcs_unroll<2, false>(PM, s1, s2, score_cutoff).sim; + case 3: return lcs_unroll<3, false>(PM, s1, s2, score_cutoff).sim; + case 4: return lcs_unroll<4, false>(PM, s1, s2, score_cutoff).sim; + case 5: return lcs_unroll<5, false>(PM, s1, s2, score_cutoff).sim; + case 6: return lcs_unroll<6, false>(PM, s1, s2, score_cutoff).sim; + case 7: return lcs_unroll<7, false>(PM, s1, s2, score_cutoff).sim; + case 8: return lcs_unroll<8, false>(PM, s1, s2, score_cutoff).sim; + default: return lcs_blockwise(PM, s1, s2, score_cutoff).sim; + } +} + +template +size_t longest_common_subsequence(const Range& s1, const Range& s2, size_t score_cutoff) +{ + if (s1.empty()) return 0; + if (s1.size() <= 64) return longest_common_subsequence(PatternMatchVector(s1), s1, s2, score_cutoff); + + return longest_common_subsequence(BlockPatternMatchVector(s1), s1, s2, score_cutoff); +} + +template +size_t lcs_seq_similarity(const BlockPatternMatchVector& block, Range s1, Range s2, + size_t score_cutoff) +{ + auto len1 = s1.size(); + auto len2 = s2.size(); + + if (score_cutoff > len1 || score_cutoff > len2) return 0; + + size_t max_misses = len1 + len2 - 2 * score_cutoff; + + /* no edits are allowed */ + if (max_misses == 0 || (max_misses == 1 && len1 == len2)) + return std::equal(s1.begin(), s1.end(), s2.begin(), s2.end()) ? len1 : 0; + + if (max_misses < abs_diff(len1, len2)) return 0; + + // do this first, since we can not remove any affix in encoded form + if (max_misses >= 5) return longest_common_subsequence(block, s1, s2, score_cutoff); + + /* common affix does not effect Levenshtein distance */ + StringAffix affix = remove_common_affix(s1, s2); + size_t lcs_sim = affix.prefix_len + affix.suffix_len; + if (!s1.empty() && !s2.empty()) { + size_t adjusted_cutoff = score_cutoff >= lcs_sim ? score_cutoff - lcs_sim : 0; + lcs_sim += lcs_seq_mbleven2018(s1, s2, adjusted_cutoff); + } + + return (lcs_sim >= score_cutoff) ? lcs_sim : 0; +} + +template +size_t lcs_seq_similarity(Range s1, Range s2, size_t score_cutoff) +{ + auto len1 = s1.size(); + auto len2 = s2.size(); + + // Swapping the strings so the second string is shorter + if (len1 < len2) return lcs_seq_similarity(s2, s1, score_cutoff); + + if (score_cutoff > len1 || score_cutoff > len2) return 0; + + size_t max_misses = len1 + len2 - 2 * score_cutoff; + + /* no edits are allowed */ + if (max_misses == 0 || (max_misses == 1 && len1 == len2)) + return std::equal(s1.begin(), s1.end(), s2.begin(), s2.end()) ? len1 : 0; + + if (max_misses < abs_diff(len1, len2)) return 0; + + /* common affix does not effect Levenshtein distance */ + StringAffix affix = remove_common_affix(s1, s2); + size_t lcs_sim = affix.prefix_len + affix.suffix_len; + if (s1.size() && s2.size()) { + size_t adjusted_cutoff = score_cutoff >= lcs_sim ? score_cutoff - lcs_sim : 0; + if (max_misses < 5) + lcs_sim += lcs_seq_mbleven2018(s1, s2, adjusted_cutoff); + else + lcs_sim += longest_common_subsequence(s1, s2, adjusted_cutoff); + } + + return (lcs_sim >= score_cutoff) ? lcs_sim : 0; +} + +/** + * @brief recover alignment from bitparallel Levenshtein matrix + */ +template +Editops recover_alignment(const Range& s1, const Range& s2, + const LCSseqResult& matrix, StringAffix affix) +{ + size_t len1 = s1.size(); + size_t len2 = s2.size(); + size_t dist = len1 + len2 - 2 * matrix.sim; + Editops editops(dist); + editops.set_src_len(len1 + affix.prefix_len + affix.suffix_len); + editops.set_dest_len(len2 + affix.prefix_len + affix.suffix_len); + + if (dist == 0) return editops; + + [[maybe_unused]] size_t band_width_right = s2.size() - matrix.sim; + + auto col = len1; + auto row = len2; + + while (row && col) { + /* Deletion */ + if (matrix.S.test_bit(row - 1, col - 1)) { + assert(dist > 0); + assert(static_cast(col) >= + static_cast(row) - static_cast(band_width_right)); + dist--; + col--; + editops[dist].type = EditType::Delete; + editops[dist].src_pos = col + affix.prefix_len; + editops[dist].dest_pos = row + affix.prefix_len; + } + else { + row--; + + /* Insertion */ + if (row && !(matrix.S.test_bit(row - 1, col - 1))) { + assert(dist > 0); + dist--; + editops[dist].type = EditType::Insert; + editops[dist].src_pos = col + affix.prefix_len; + editops[dist].dest_pos = row + affix.prefix_len; + } + /* Match */ + else { + col--; + assert(s1[col] == s2[row]); + } + } + } + + while (col) { + dist--; + col--; + editops[dist].type = EditType::Delete; + editops[dist].src_pos = col + affix.prefix_len; + editops[dist].dest_pos = row + affix.prefix_len; + } + + while (row) { + dist--; + row--; + editops[dist].type = EditType::Insert; + editops[dist].src_pos = col + affix.prefix_len; + editops[dist].dest_pos = row + affix.prefix_len; + } + + return editops; +} + +template +LCSseqResult lcs_matrix(const Range& s1, const Range& s2) +{ + size_t nr = ceil_div(s1.size(), 64); + switch (nr) { + case 0: + { + LCSseqResult res; + res.sim = 0; + return res; + } + case 1: return lcs_unroll<1, true>(PatternMatchVector(s1), s1, s2); + case 2: return lcs_unroll<2, true>(BlockPatternMatchVector(s1), s1, s2); + case 3: return lcs_unroll<3, true>(BlockPatternMatchVector(s1), s1, s2); + case 4: return lcs_unroll<4, true>(BlockPatternMatchVector(s1), s1, s2); + case 5: return lcs_unroll<5, true>(BlockPatternMatchVector(s1), s1, s2); + case 6: return lcs_unroll<6, true>(BlockPatternMatchVector(s1), s1, s2); + case 7: return lcs_unroll<7, true>(BlockPatternMatchVector(s1), s1, s2); + case 8: return lcs_unroll<8, true>(BlockPatternMatchVector(s1), s1, s2); + default: return lcs_blockwise(BlockPatternMatchVector(s1), s1, s2); + } +} + +template +Editops lcs_seq_editops(Range s1, Range s2) +{ + /* prefix and suffix are no-ops, which do not need to be added to the editops */ + StringAffix affix = remove_common_affix(s1, s2); + + return recover_alignment(s1, s2, lcs_matrix(s1, s2), affix); +} + +class LCSseq : public SimilarityBase::max()> { + friend SimilarityBase::max()>; + friend NormalizedMetricBase; + + template + static size_t maximum(const Range& s1, const Range& s2) + { + return std::max(s1.size(), s2.size()); + } + + template + static size_t _similarity(const Range& s1, const Range& s2, size_t score_cutoff, + [[maybe_unused]] size_t score_hint) + { + return lcs_seq_similarity(s1, s2, score_cutoff); + } +}; + +} // namespace rapidfuzz::detail diff --git a/src/external/rapidfuzz-cpp/rapidfuzz/distance/Levenshtein.hpp b/src/external/rapidfuzz-cpp/rapidfuzz/distance/Levenshtein.hpp new file mode 100644 index 00000000..3d61a212 --- /dev/null +++ b/src/external/rapidfuzz-cpp/rapidfuzz/distance/Levenshtein.hpp @@ -0,0 +1,492 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright © 2022-present Max Bachmann */ + +#pragma once +#include +#include +#include + +namespace rapidfuzz { + +/** + * @brief Calculates the minimum number of insertions, deletions, and substitutions + * required to change one sequence into the other according to Levenshtein with custom + * costs for insertion, deletion and substitution + * + * @tparam Sentence1 This is a string that can be converted to + * basic_string_view + * @tparam Sentence2 This is a string that can be converted to + * basic_string_view + * + * @param s1 + * string to compare with s2 (for type info check Template parameters above) + * @param s2 + * string to compare with s1 (for type info check Template parameters above) + * @param weights + * The weights for the three operations in the form + * (insertion, deletion, substitution). Default is {1, 1, 1}, + * which gives all three operations a weight of 1. + * @param max + * Maximum Levenshtein distance between s1 and s2, that is + * considered as a result. If the distance is bigger than max, + * max + 1 is returned instead. Default is std::numeric_limits::max(), + * which deactivates this behaviour. + * + * @return returns the levenshtein distance between s1 and s2 + * + * @remarks + * @parblock + * Depending on the input parameters different optimized implementation are used + * to improve the performance. Worst-case performance is ``O(m * n)``. + * + * Insertion = Deletion = Substitution: + * + * This is known as uniform Levenshtein distance and is the distance most commonly + * referred to as Levenshtein distance. The following implementation is used + * with a worst-case performance of ``O([N/64]M)``. + * + * - if max is 0 the similarity can be calculated using a direct comparision, + * since no difference between the strings is allowed. The time complexity of + * this algorithm is ``O(N)``. + * + * - A common prefix/suffix of the two compared strings does not affect + * the Levenshtein distance, so the affix is removed before calculating the + * similarity. + * + * - If max is <= 3 the mbleven algorithm is used. This algorithm + * checks all possible edit operations that are possible under + * the threshold `max`. The time complexity of this algorithm is ``O(N)``. + * + * - If the length of the shorter string is <= 64 after removing the common affix + * Hyyrös' algorithm is used, which calculates the Levenshtein distance in + * parallel. The algorithm is described by @cite hyrro_2002. The time complexity of this + * algorithm is ``O(N)``. + * + * - If the length of the shorter string is >= 64 after removing the common affix + * a blockwise implementation of Myers' algorithm is used, which calculates + * the Levenshtein distance in parallel (64 characters at a time). + * The algorithm is described by @cite myers_1999. The time complexity of this + * algorithm is ``O([N/64]M)``. + * + * + * Insertion = Deletion, Substitution >= Insertion + Deletion: + * + * Since every Substitution can be performed as Insertion + Deletion, this variant + * of the Levenshtein distance only uses Insertions and Deletions. Therefore this + * variant is often referred to as InDel-Distance. The following implementation + * is used with a worst-case performance of ``O([N/64]M)``. + * + * - if max is 0 the similarity can be calculated using a direct comparision, + * since no difference between the strings is allowed. The time complexity of + * this algorithm is ``O(N)``. + * + * - if max is 1 and the two strings have a similar length, the similarity can be + * calculated using a direct comparision aswell, since a substitution would cause + * a edit distance higher than max. The time complexity of this algorithm + * is ``O(N)``. + * + * - A common prefix/suffix of the two compared strings does not affect + * the Levenshtein distance, so the affix is removed before calculating the + * similarity. + * + * - If max is <= 4 the mbleven algorithm is used. This algorithm + * checks all possible edit operations that are possible under + * the threshold `max`. As a difference to the normal Levenshtein distance this + * algorithm can even be used up to a threshold of 4 here, since the higher weight + * of substitutions decreases the amount of possible edit operations. + * The time complexity of this algorithm is ``O(N)``. + * + * - If the length of the shorter string is <= 64 after removing the common affix + * Hyyrös' lcs algorithm is used, which calculates the InDel distance in + * parallel. The algorithm is described by @cite hyrro_lcs_2004 and is extended with support + * for UTF32 in this implementation. The time complexity of this + * algorithm is ``O(N)``. + * + * - If the length of the shorter string is >= 64 after removing the common affix + * a blockwise implementation of Hyyrös' lcs algorithm is used, which calculates + * the Levenshtein distance in parallel (64 characters at a time). + * The algorithm is described by @cite hyrro_lcs_2004. The time complexity of this + * algorithm is ``O([N/64]M)``. + * + * Other weights: + * + * The implementation for other weights is based on Wagner-Fischer. + * It has a performance of ``O(N * M)`` and has a memory usage of ``O(N)``. + * Further details can be found in @cite wagner_fischer_1974. + * @endparblock + * + * @par Examples + * @parblock + * Find the Levenshtein distance between two strings: + * @code{.cpp} + * // dist is 2 + * size_t dist = levenshtein_distance("lewenstein", "levenshtein"); + * @endcode + * + * Setting a maximum distance allows the implementation to select + * a more efficient implementation: + * @code{.cpp} + * // dist is 2 + * size_t dist = levenshtein_distance("lewenstein", "levenshtein", {1, 1, 1}, 1); + * @endcode + * + * It is possible to select different weights by passing a `weight` struct. + * @code{.cpp} + * // dist is 3 + * size_t dist = levenshtein_distance("lewenstein", "levenshtein", {1, 1, 2}); + * @endcode + * @endparblock + */ +template +size_t levenshtein_distance(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + LevenshteinWeightTable weights = {1, 1, 1}, + size_t score_cutoff = std::numeric_limits::max(), + size_t score_hint = std::numeric_limits::max()) +{ + return detail::Levenshtein::distance(first1, last1, first2, last2, weights, score_cutoff, score_hint); +} + +template +size_t levenshtein_distance(const Sentence1& s1, const Sentence2& s2, + LevenshteinWeightTable weights = {1, 1, 1}, + size_t score_cutoff = std::numeric_limits::max(), + size_t score_hint = std::numeric_limits::max()) +{ + return detail::Levenshtein::distance(s1, s2, weights, score_cutoff, score_hint); +} + +template +size_t levenshtein_similarity(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + LevenshteinWeightTable weights = {1, 1, 1}, size_t score_cutoff = 0, + size_t score_hint = 0) +{ + return detail::Levenshtein::similarity(first1, last1, first2, last2, weights, score_cutoff, score_hint); +} + +template +size_t levenshtein_similarity(const Sentence1& s1, const Sentence2& s2, + LevenshteinWeightTable weights = {1, 1, 1}, size_t score_cutoff = 0, + size_t score_hint = 0) +{ + return detail::Levenshtein::similarity(s1, s2, weights, score_cutoff, score_hint); +} + +template +double levenshtein_normalized_distance(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + LevenshteinWeightTable weights = {1, 1, 1}, double score_cutoff = 1.0, + double score_hint = 1.0) +{ + return detail::Levenshtein::normalized_distance(first1, last1, first2, last2, weights, score_cutoff, + score_hint); +} + +template +double levenshtein_normalized_distance(const Sentence1& s1, const Sentence2& s2, + LevenshteinWeightTable weights = {1, 1, 1}, double score_cutoff = 1.0, + double score_hint = 1.0) +{ + return detail::Levenshtein::normalized_distance(s1, s2, weights, score_cutoff, score_hint); +} + +/** + * @brief Calculates a normalized levenshtein distance using custom + * costs for insertion, deletion and substitution. + * + * @tparam Sentence1 This is a string that can be converted to + * basic_string_view + * @tparam Sentence2 This is a string that can be converted to + * basic_string_view + * + * @param s1 + * string to compare with s2 (for type info check Template parameters above) + * @param s2 + * string to compare with s1 (for type info check Template parameters above) + * @param weights + * The weights for the three operations in the form + * (insertion, deletion, substitution). Default is {1, 1, 1}, + * which gives all three operations a weight of 1. + * @param score_cutoff + * Optional argument for a score threshold as a float between 0 and 1.0. + * For ratio < score_cutoff 0 is returned instead. Default is 0, + * which deactivates this behaviour. + * + * @return Normalized weighted levenshtein distance between s1 and s2 + * as a double between 0 and 1.0 + * + * @see levenshtein() + * + * @remarks + * @parblock + * The normalization of the Levenshtein distance is performed in the following way: + * + * \f{align*}{ + * ratio &= \frac{distance(s1, s2)}{max_dist} + * \f} + * @endparblock + * + * + * @par Examples + * @parblock + * Find the normalized Levenshtein distance between two strings: + * @code{.cpp} + * // ratio is 81.81818181818181 + * double ratio = normalized_levenshtein("lewenstein", "levenshtein"); + * @endcode + * + * Setting a score_cutoff allows the implementation to select + * a more efficient implementation: + * @code{.cpp} + * // ratio is 0.0 + * double ratio = normalized_levenshtein("lewenstein", "levenshtein", {1, 1, 1}, 85.0); + * @endcode + * + * It is possible to select different weights by passing a `weight` struct + * @code{.cpp} + * // ratio is 85.71428571428571 + * double ratio = normalized_levenshtein("lewenstein", "levenshtein", {1, 1, 2}); + * @endcode + * @endparblock + */ +template +double levenshtein_normalized_similarity(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + LevenshteinWeightTable weights = {1, 1, 1}, + double score_cutoff = 0.0, double score_hint = 0.0) +{ + return detail::Levenshtein::normalized_similarity(first1, last1, first2, last2, weights, score_cutoff, + score_hint); +} + +template +double levenshtein_normalized_similarity(const Sentence1& s1, const Sentence2& s2, + LevenshteinWeightTable weights = {1, 1, 1}, + double score_cutoff = 0.0, double score_hint = 0.0) +{ + return detail::Levenshtein::normalized_similarity(s1, s2, weights, score_cutoff, score_hint); +} + +/** + * @brief Return list of EditOp describing how to turn s1 into s2. + * + * @tparam Sentence1 This is a string that can be converted to + * basic_string_view + * @tparam Sentence2 This is a string that can be converted to + * basic_string_view + * + * @param s1 + * string to compare with s2 (for type info check Template parameters above) + * @param s2 + * string to compare with s1 (for type info check Template parameters above) + * + * @return Edit operations required to turn s1 into s2 + */ +template +Editops levenshtein_editops(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + size_t score_hint = std::numeric_limits::max()) +{ + return detail::levenshtein_editops(detail::Range(first1, last1), detail::Range(first2, last2), + score_hint); +} + +template +Editops levenshtein_editops(const Sentence1& s1, const Sentence2& s2, + size_t score_hint = std::numeric_limits::max()) +{ + return detail::levenshtein_editops(detail::Range(s1), detail::Range(s2), score_hint); +} + +#ifdef RAPIDFUZZ_SIMD +namespace experimental { +template +struct MultiLevenshtein : public detail::MultiDistanceBase, size_t, 0, + std::numeric_limits::max()> { +private: + friend detail::MultiDistanceBase, size_t, 0, + std::numeric_limits::max()>; + friend detail::MultiNormalizedMetricBase, size_t>; + + constexpr static size_t get_vec_size() + { +# ifdef RAPIDFUZZ_AVX2 + using namespace detail::simd_avx2; +# else + using namespace detail::simd_sse2; +# endif + if constexpr (MaxLen <= 8) + return native_simd::size; + else if constexpr (MaxLen <= 16) + return native_simd::size; + else if constexpr (MaxLen <= 32) + return native_simd::size; + else if constexpr (MaxLen <= 64) + return native_simd::size; + + static_assert(MaxLen <= 64); + } + + constexpr static size_t find_block_count(size_t count) + { + size_t vec_size = get_vec_size(); + size_t simd_vec_count = detail::ceil_div(count, vec_size); + return detail::ceil_div(simd_vec_count * vec_size * MaxLen, 64); + } + +public: + MultiLevenshtein(size_t count, LevenshteinWeightTable aWeights = {1, 1, 1}) + : input_count(count), PM(find_block_count(count) * 64), weights(aWeights) + { + str_lens.resize(result_count()); + if (weights.delete_cost != 1 || weights.insert_cost != 1 || weights.replace_cost > 2) + throw std::invalid_argument("unsupported weights"); + } + + /** + * @brief get minimum size required for result vectors passed into + * - distance + * - similarity + * - normalized_distance + * - normalized_similarity + * + * @return minimum vector size + */ + size_t result_count() const + { + size_t vec_size = get_vec_size(); + size_t simd_vec_count = detail::ceil_div(input_count, vec_size); + return simd_vec_count * vec_size; + } + + template + void insert(const Sentence1& s1_) + { + insert(detail::to_begin(s1_), detail::to_end(s1_)); + } + + template + void insert(InputIt1 first1, InputIt1 last1) + { + auto len = std::distance(first1, last1); + int block_pos = static_cast((pos * MaxLen) % 64); + auto block = (pos * MaxLen) / 64; + assert(len <= MaxLen); + + if (pos >= input_count) throw std::invalid_argument("out of bounds insert"); + + str_lens[pos] = static_cast(len); + for (; first1 != last1; ++first1) { + PM.insert(block, *first1, block_pos); + block_pos++; + } + pos++; + } + +private: + template + void _distance(size_t* scores, size_t score_count, const detail::Range& s2, + size_t score_cutoff = std::numeric_limits::max()) const + { + if (score_count < result_count()) + throw std::invalid_argument("scores has to have >= result_count() elements"); + + detail::Range scores_(scores, scores + score_count); + if constexpr (MaxLen == 8) + detail::levenshtein_hyrroe2003_simd(scores_, PM, str_lens, s2, score_cutoff); + else if constexpr (MaxLen == 16) + detail::levenshtein_hyrroe2003_simd(scores_, PM, str_lens, s2, score_cutoff); + else if constexpr (MaxLen == 32) + detail::levenshtein_hyrroe2003_simd(scores_, PM, str_lens, s2, score_cutoff); + else if constexpr (MaxLen == 64) + detail::levenshtein_hyrroe2003_simd(scores_, PM, str_lens, s2, score_cutoff); + } + + template + size_t maximum(size_t s1_idx, const detail::Range& s2) const + { + return detail::levenshtein_maximum(str_lens[s1_idx], s2.size(), weights); + } + + size_t get_input_count() const noexcept + { + return input_count; + } + + size_t input_count; + size_t pos = 0; + detail::BlockPatternMatchVector PM; + std::vector str_lens; + LevenshteinWeightTable weights; +}; +} /* namespace experimental */ +#endif /* RAPIDFUZZ_SIMD */ + +template +struct CachedLevenshtein : public detail::CachedDistanceBase, size_t, 0, + std::numeric_limits::max()> { + template + explicit CachedLevenshtein(const Sentence1& s1_, LevenshteinWeightTable aWeights = {1, 1, 1}) + : CachedLevenshtein(detail::to_begin(s1_), detail::to_end(s1_), aWeights) + {} + + template + CachedLevenshtein(InputIt1 first1, InputIt1 last1, LevenshteinWeightTable aWeights = {1, 1, 1}) + : s1(first1, last1), PM(detail::Range(first1, last1)), weights(aWeights) + {} + +private: + friend detail::CachedDistanceBase, size_t, 0, + std::numeric_limits::max()>; + friend detail::CachedNormalizedMetricBase>; + + template + size_t maximum(const detail::Range& s2) const + { + return detail::levenshtein_maximum(s1.size(), s2.size(), weights); + } + + template + size_t _distance(const detail::Range& s2, size_t score_cutoff, size_t score_hint) const + { + if (weights.insert_cost == weights.delete_cost) { + /* when insertions + deletions operations are free there can not be any edit distance */ + if (weights.insert_cost == 0) return 0; + + /* uniform Levenshtein multiplied with the common factor */ + if (weights.insert_cost == weights.replace_cost) { + // max can make use of the common divisor of the three weights + size_t new_score_cutoff = detail::ceil_div(score_cutoff, weights.insert_cost); + size_t new_score_hint = detail::ceil_div(score_hint, weights.insert_cost); + size_t dist = detail::uniform_levenshtein_distance(PM, detail::Range(s1), s2, + new_score_cutoff, new_score_hint); + dist *= weights.insert_cost; + + return (dist <= score_cutoff) ? dist : score_cutoff + 1; + } + /* + * when replace_cost >= insert_cost + delete_cost no substitutions are performed + * therefore this can be implemented as InDel distance multiplied with the common factor + */ + else if (weights.replace_cost >= weights.insert_cost + weights.delete_cost) { + // max can make use of the common divisor of the three weights + size_t new_max = detail::ceil_div(score_cutoff, weights.insert_cost); + size_t dist = detail::indel_distance(PM, detail::Range(s1), s2, new_max); + dist *= weights.insert_cost; + return (dist <= score_cutoff) ? dist : score_cutoff + 1; + } + } + + return detail::generalized_levenshtein_distance(detail::Range(s1), s2, weights, score_cutoff); + } + + std::vector s1; + detail::BlockPatternMatchVector PM; + LevenshteinWeightTable weights; +}; + +template +explicit CachedLevenshtein(const Sentence1& s1_, LevenshteinWeightTable aWeights = { + 1, 1, 1}) -> CachedLevenshtein>; + +template +CachedLevenshtein(InputIt1 first1, InputIt1 last1, + LevenshteinWeightTable aWeights = {1, 1, 1}) -> CachedLevenshtein>; + +} // namespace rapidfuzz diff --git a/src/external/rapidfuzz-cpp/rapidfuzz/distance/Levenshtein_impl.hpp b/src/external/rapidfuzz-cpp/rapidfuzz/distance/Levenshtein_impl.hpp new file mode 100644 index 00000000..38758413 --- /dev/null +++ b/src/external/rapidfuzz-cpp/rapidfuzz/distance/Levenshtein_impl.hpp @@ -0,0 +1,1220 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright © 2022-present Max Bachmann */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace rapidfuzz::detail { + +struct LevenshteinRow { + uint64_t VP; + uint64_t VN; + + LevenshteinRow() : VP(~UINT64_C(0)), VN(0) + {} + + LevenshteinRow(uint64_t VP_, uint64_t VN_) : VP(VP_), VN(VN_) + {} +}; + +template +struct LevenshteinResult; + +template <> +struct LevenshteinResult { + ShiftedBitMatrix VP; + ShiftedBitMatrix VN; + + size_t dist; +}; + +template <> +struct LevenshteinResult { + size_t first_block; + size_t last_block; + size_t prev_score; + std::vector vecs; + + size_t dist; +}; + +template <> +struct LevenshteinResult { + size_t dist; +}; + +template +size_t generalized_levenshtein_wagner_fischer(const Range& s1, const Range& s2, + LevenshteinWeightTable weights, size_t max) +{ + size_t cache_size = s1.size() + 1; + std::vector cache(cache_size); + assume(cache_size != 0); + + for (size_t i = 0; i < cache_size; ++i) + cache[i] = i * weights.delete_cost; + + for (const auto& ch2 : s2) { + auto cache_iter = cache.begin(); + size_t temp = *cache_iter; + *cache_iter += weights.insert_cost; + + for (const auto& ch1 : s1) { + if (ch1 != ch2) + temp = std::min({*cache_iter + weights.delete_cost, *(cache_iter + 1) + weights.insert_cost, + temp + weights.replace_cost}); + ++cache_iter; + std::swap(*cache_iter, temp); + } + } + + size_t dist = cache.back(); + return (dist <= max) ? dist : max + 1; +} + +/** + * @brief calculates the maximum possible Levenshtein distance based on + * string lengths and weights + */ +static inline size_t levenshtein_maximum(size_t len1, size_t len2, LevenshteinWeightTable weights) +{ + size_t max_dist = len1 * weights.delete_cost + len2 * weights.insert_cost; + + if (len1 >= len2) + max_dist = std::min(max_dist, len2 * weights.replace_cost + (len1 - len2) * weights.delete_cost); + else + max_dist = std::min(max_dist, len1 * weights.replace_cost + (len2 - len1) * weights.insert_cost); + + return max_dist; +} + +/** + * @brief calculates the minimal possible Levenshtein distance based on + * string lengths and weights + */ +template +size_t levenshtein_min_distance(const Range& s1, const Range& s2, + LevenshteinWeightTable weights) +{ + if (s1.size() > s2.size()) + return (s1.size() - s2.size()) * weights.delete_cost; + else + return (s2.size() - s1.size()) * weights.insert_cost; +} + +template +size_t generalized_levenshtein_distance(Range s1, Range s2, + LevenshteinWeightTable weights, size_t max) +{ + size_t min_edits = levenshtein_min_distance(s1, s2, weights); + if (min_edits > max) return max + 1; + + /* common affix does not effect Levenshtein distance */ + remove_common_affix(s1, s2); + + return generalized_levenshtein_wagner_fischer(s1, s2, weights, max); +} + +/* + * An encoded mbleven model table. + * + * Each 8-bit integer represents an edit sequence, with using two + * bits for a single operation. + * + * Each Row of 8 integers represent all possible combinations + * of edit sequences for a gived maximum edit distance and length + * difference between the two strings, that is below the maximum + * edit distance + * + * 01 = DELETE, 10 = INSERT, 11 = SUBSTITUTE + * + * For example, 3F -> 0b111111 means three substitutions + */ +static constexpr std::array, 9> levenshtein_mbleven2018_matrix = {{ + /* max edit distance 1 */ + {0x03}, /* len_diff 0 */ + {0x01}, /* len_diff 1 */ + /* max edit distance 2 */ + {0x0F, 0x09, 0x06}, /* len_diff 0 */ + {0x0D, 0x07}, /* len_diff 1 */ + {0x05}, /* len_diff 2 */ + /* max edit distance 3 */ + {0x3F, 0x27, 0x2D, 0x39, 0x36, 0x1E, 0x1B}, /* len_diff 0 */ + {0x3D, 0x37, 0x1F, 0x25, 0x19, 0x16}, /* len_diff 1 */ + {0x35, 0x1D, 0x17}, /* len_diff 2 */ + {0x15}, /* len_diff 3 */ +}}; + +template +size_t levenshtein_mbleven2018(const Range& s1, const Range& s2, size_t max) +{ + size_t len1 = s1.size(); + size_t len2 = s2.size(); + assert(len1 > 0); + assert(len2 > 0); + assert(*s1.begin() != *s2.begin()); + assert(*std::prev(s1.end()) != *std::prev(s2.end())); + + if (len1 < len2) return levenshtein_mbleven2018(s2, s1, max); + + size_t len_diff = len1 - len2; + + if (max == 1) return max + static_cast(len_diff == 1 || len1 != 1); + + size_t ops_index = (max + max * max) / 2 + len_diff - 1; + auto& possible_ops = levenshtein_mbleven2018_matrix[ops_index]; + size_t dist = max + 1; + + for (uint8_t ops : possible_ops) { + auto iter_s1 = s1.begin(); + auto iter_s2 = s2.begin(); + size_t cur_dist = 0; + + if (!ops) break; + + while (iter_s1 != s1.end() && iter_s2 != s2.end()) { + if (*iter_s1 != *iter_s2) { + cur_dist++; + if (!ops) break; + if (ops & 1) iter_s1++; + if (ops & 2) iter_s2++; +#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && __GNUC__ < 10 +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wconversion" +#endif + ops >>= 2; +#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && __GNUC__ < 10 +# pragma GCC diagnostic pop +#endif + } + else { + iter_s1++; + iter_s2++; + } + } + cur_dist += static_cast(std::distance(iter_s1, s1.end()) + std::distance(iter_s2, s2.end())); + dist = std::min(dist, cur_dist); + } + + return (dist <= max) ? dist : max + 1; +} + +/** + * @brief Bitparallel implementation of the Levenshtein distance. + * + * This implementation requires the first string to have a length <= 64. + * The algorithm used is described @cite hyrro_2002 and has a time complexity + * of O(N). Comments and variable names in the implementation follow the + * paper. This implementation is used internally when the strings are short enough + * + * @tparam CharT1 This is the char type of the first sentence + * @tparam CharT2 This is the char type of the second sentence + * + * @param s1 + * string to compare with s2 (for type info check Template parameters above) + * @param s2 + * string to compare with s1 (for type info check Template parameters above) + * + * @return returns the levenshtein distance between s1 and s2 + */ +template +auto levenshtein_hyrroe2003(const PM_Vec& PM, const Range& s1, const Range& s2, + size_t max = std::numeric_limits::max()) + -> LevenshteinResult +{ + assert(s1.size() != 0); + + /* VP is set to 1^m. Shifting by bitwidth would be undefined behavior */ + uint64_t VP = ~UINT64_C(0); + uint64_t VN = 0; + + LevenshteinResult res; + res.dist = s1.size(); + if constexpr (RecordMatrix) { + res.VP = ShiftedBitMatrix(s2.size(), 1, ~UINT64_C(0)); + res.VN = ShiftedBitMatrix(s2.size(), 1, 0); + } + + /* mask used when computing D[m,j] in the paper 10^(m-1) */ + uint64_t mask = UINT64_C(1) << (s1.size() - 1); + + /* Searching */ + auto iter_s2 = s2.begin(); + for (size_t i = 0; iter_s2 != s2.end(); ++iter_s2, ++i) { + /* Step 1: Computing D0 */ + uint64_t PM_j = PM.get(0, *iter_s2); + uint64_t X = PM_j; + uint64_t D0 = (((X & VP) + VP) ^ VP) | X | VN; + + /* Step 2: Computing HP and HN */ + uint64_t HP = VN | ~(D0 | VP); + uint64_t HN = D0 & VP; + + /* Step 3: Computing the value D[m,j] */ + res.dist += bool(HP & mask); + res.dist -= bool(HN & mask); + + /* Step 4: Computing Vp and VN */ + HP = (HP << 1) | 1; + HN = (HN << 1); + + VP = HN | ~(D0 | HP); + VN = HP & D0; + + if constexpr (RecordMatrix) { + res.VP[i][0] = VP; + res.VN[i][0] = VN; + } + } + + if (res.dist > max) res.dist = max + 1; + + if constexpr (RecordBitRow) { + res.first_block = 0; + res.last_block = 0; + res.prev_score = s2.size(); + res.vecs.emplace_back(VP, VN); + } + + return res; +} + +#ifdef RAPIDFUZZ_SIMD +template +void levenshtein_hyrroe2003_simd(Range scores, const detail::BlockPatternMatchVector& block, + const std::vector& s1_lengths, const Range& s2, + size_t score_cutoff) noexcept +{ +# ifdef RAPIDFUZZ_AVX2 + using namespace simd_avx2; +# else + using namespace simd_sse2; +# endif + static constexpr size_t alignment = native_simd::alignment; + static constexpr size_t vec_width = native_simd::size; + static constexpr size_t vecs = native_simd::size; + assert(block.size() % vecs == 0); + + native_simd zero(VecType(0)); + native_simd one(1); + size_t result_index = 0; + + for (size_t cur_vec = 0; cur_vec < block.size(); cur_vec += vecs) { + /* VP is set to 1^m */ + native_simd VP(static_cast(-1)); + native_simd VN(VecType(0)); + + alignas(alignment) std::array currDist_; + unroll( + [&](auto i) { currDist_[i] = static_cast(s1_lengths[result_index + i]); }); + native_simd currDist(reinterpret_cast(currDist_.data())); + /* mask used when computing D[m,j] in the paper 10^(m-1) */ + alignas(alignment) std::array mask_; + unroll([&](auto i) { + if (s1_lengths[result_index + i] == 0) + mask_[i] = 0; + else + mask_[i] = static_cast(UINT64_C(1) << (s1_lengths[result_index + i] - 1)); + }); + native_simd mask(reinterpret_cast(mask_.data())); + + for (const auto& ch : s2) { + /* Step 1: Computing D0 */ + alignas(alignment) std::array stored; + unroll([&](auto i) { stored[i] = block.get(cur_vec + i, ch); }); + + native_simd X(stored.data()); + auto D0 = (((X & VP) + VP) ^ VP) | X | VN; + + /* Step 2: Computing HP and HN */ + auto HP = VN | ~(D0 | VP); + auto HN = D0 & VP; + + /* Step 3: Computing the value D[m,j] */ + currDist += andnot(one, (HP & mask) == zero); + currDist -= andnot(one, (HN & mask) == zero); + + /* Step 4: Computing Vp and VN */ + HP = (HP << 1) | one; + HN = (HN << 1); + + VP = HN | ~(D0 | HP); + VN = HP & D0; + } + + alignas(alignment) std::array distances; + currDist.store(distances.data()); + + unroll([&](auto i) { + size_t score = 0; + /* strings of length 0 are not handled correctly */ + if (s1_lengths[result_index] == 0) { + score = s2.size(); + } + /* calculate score under consideration of wraparounds in parallel counter */ + else { + if constexpr (std::numeric_limits::max() < std::numeric_limits::max()) { + size_t min_dist = abs_diff(s1_lengths[result_index], s2.size()); + size_t wraparound_score = static_cast(std::numeric_limits::max()) + 1; + + score = (min_dist / wraparound_score) * wraparound_score; + VecType remainder = static_cast(min_dist % wraparound_score); + + if (distances[i] < remainder) score += wraparound_score; + } + + score += distances[i]; + } + scores[result_index] = (score <= score_cutoff) ? score : score_cutoff + 1; + result_index++; + }); + } +} +#endif + +template +size_t levenshtein_hyrroe2003_small_band(const BlockPatternMatchVector& PM, const Range& s1, + const Range& s2, size_t max) +{ + /* VP is set to 1^m. */ + uint64_t VP = ~UINT64_C(0) << (64 - max - 1); + uint64_t VN = 0; + + const auto words = PM.size(); + size_t currDist = max; + uint64_t diagonal_mask = UINT64_C(1) << 63; + uint64_t horizontal_mask = UINT64_C(1) << 62; + ptrdiff_t start_pos = static_cast(max) + 1 - 64; + + /* score can decrease along the horizontal, but not along the diagonal */ + size_t break_score = 2 * max + s2.size() - s1.size(); + + /* Searching */ + size_t i = 0; + if (s1.size() > max) { + for (; i < s1.size() - max; ++i, ++start_pos) { + /* Step 1: Computing D0 */ + uint64_t PM_j = 0; + if (start_pos < 0) { + PM_j = PM.get(0, s2[i]) << (-start_pos); + } + else { + size_t word = static_cast(start_pos) / 64; + size_t word_pos = static_cast(start_pos) % 64; + + PM_j = PM.get(word, s2[i]) >> word_pos; + + if (word + 1 < words && word_pos != 0) PM_j |= PM.get(word + 1, s2[i]) << (64 - word_pos); + } + uint64_t X = PM_j; + uint64_t D0 = (((X & VP) + VP) ^ VP) | X | VN; + + /* Step 2: Computing HP and HN */ + uint64_t HP = VN | ~(D0 | VP); + uint64_t HN = D0 & VP; + + /* Step 3: Computing the value D[m,j] */ + currDist += !bool(D0 & diagonal_mask); + + if (currDist > break_score) return max + 1; + + /* Step 4: Computing Vp and VN */ + VP = HN | ~((D0 >> 1) | HP); + VN = (D0 >> 1) & HP; + } + } + + for (; i < s2.size(); ++i, ++start_pos) { + /* Step 1: Computing D0 */ + uint64_t PM_j = 0; + if (start_pos < 0) { + PM_j = PM.get(0, s2[i]) << (-start_pos); + } + else { + size_t word = static_cast(start_pos) / 64; + size_t word_pos = static_cast(start_pos) % 64; + + PM_j = PM.get(word, s2[i]) >> word_pos; + + if (word + 1 < words && word_pos != 0) PM_j |= PM.get(word + 1, s2[i]) << (64 - word_pos); + } + uint64_t X = PM_j; + uint64_t D0 = (((X & VP) + VP) ^ VP) | X | VN; + + /* Step 2: Computing HP and HN */ + uint64_t HP = VN | ~(D0 | VP); + uint64_t HN = D0 & VP; + + /* Step 3: Computing the value D[m,j] */ + currDist += bool(HP & horizontal_mask); + currDist -= bool(HN & horizontal_mask); + horizontal_mask >>= 1; + + if (currDist > break_score) return max + 1; + + /* Step 4: Computing Vp and VN */ + VP = HN | ~((D0 >> 1) | HP); + VN = (D0 >> 1) & HP; + } + + return (currDist <= max) ? currDist : max + 1; +} + +template +auto levenshtein_hyrroe2003_small_band(const Range& s1, const Range& s2, + size_t max) -> LevenshteinResult +{ + assert(max <= s1.size()); + assert(max <= s2.size()); + assert(s2.size() >= s1.size() - max); + + /* VP is set to 1^m. Shifting by bitwidth would be undefined behavior */ + uint64_t VP = ~UINT64_C(0) << (64 - max - 1); + uint64_t VN = 0; + + LevenshteinResult res; + res.dist = max; + if constexpr (RecordMatrix) { + res.VP = ShiftedBitMatrix(s2.size(), 1, ~UINT64_C(0)); + res.VN = ShiftedBitMatrix(s2.size(), 1, 0); + + ptrdiff_t start_offset = static_cast(max) + 2 - 64; + for (size_t i = 0; i < s2.size(); ++i) { + res.VP.set_offset(i, start_offset + static_cast(i)); + res.VN.set_offset(i, start_offset + static_cast(i)); + } + } + + uint64_t diagonal_mask = UINT64_C(1) << 63; + uint64_t horizontal_mask = UINT64_C(1) << 62; + + /* score can decrease along the horizontal, but not along the diagonal */ + size_t break_score = 2 * max + s2.size() - (s1.size()); + HybridGrowingHashmap::value_type, std::pair> PM; + + auto iter_s1 = s1.begin(); + for (ptrdiff_t j = -static_cast(max); j < 0; ++iter_s1, ++j) { + auto& x = PM[*iter_s1]; + x.second = shr64(x.second, j - x.first) | (UINT64_C(1) << 63); + x.first = j; + } + + /* Searching */ + size_t i = 0; + auto iter_s2 = s2.begin(); + for (; i < s1.size() - max; ++iter_s2, ++iter_s1, ++i) { + /* Step 1: Computing D0 */ + /* update bitmasks online */ + uint64_t PM_j = 0; + { + auto& x = PM[*iter_s1]; + x.second = shr64(x.second, static_cast(i) - x.first) | (UINT64_C(1) << 63); + x.first = static_cast(i); + } + { + auto x = PM.get(*iter_s2); + PM_j = shr64(x.second, static_cast(i) - x.first); + } + + uint64_t X = PM_j; + uint64_t D0 = (((X & VP) + VP) ^ VP) | X | VN; + + /* Step 2: Computing HP and HN */ + uint64_t HP = VN | ~(D0 | VP); + uint64_t HN = D0 & VP; + + /* Step 3: Computing the value D[m,j] */ + res.dist += !bool(D0 & diagonal_mask); + + if (res.dist > break_score) { + res.dist = max + 1; + return res; + } + + /* Step 4: Computing Vp and VN */ + VP = HN | ~((D0 >> 1) | HP); + VN = (D0 >> 1) & HP; + + if constexpr (RecordMatrix) { + res.VP[i][0] = VP; + res.VN[i][0] = VN; + } + } + + for (; i < s2.size(); ++iter_s2, ++i) { + /* Step 1: Computing D0 */ + /* update bitmasks online */ + uint64_t PM_j = 0; + if (iter_s1 != s1.end()) { + auto& x = PM[*iter_s1]; + x.second = shr64(x.second, static_cast(i) - x.first) | (UINT64_C(1) << 63); + x.first = static_cast(i); + ++iter_s1; + } + { + auto x = PM.get(*iter_s2); + PM_j = shr64(x.second, static_cast(i) - x.first); + } + + uint64_t X = PM_j; + uint64_t D0 = (((X & VP) + VP) ^ VP) | X | VN; + + /* Step 2: Computing HP and HN */ + uint64_t HP = VN | ~(D0 | VP); + uint64_t HN = D0 & VP; + + /* Step 3: Computing the value D[m,j] */ + res.dist += bool(HP & horizontal_mask); + res.dist -= bool(HN & horizontal_mask); + horizontal_mask >>= 1; + + if (res.dist > break_score) { + res.dist = max + 1; + return res; + } + + /* Step 4: Computing Vp and VN */ + VP = HN | ~((D0 >> 1) | HP); + VN = (D0 >> 1) & HP; + + if constexpr (RecordMatrix) { + res.VP[i][0] = VP; + res.VN[i][0] = VN; + } + } + + if (res.dist > max) res.dist = max + 1; + + return res; +} + +/** + * @param stop_row specifies the row to record when using RecordBitRow + */ +template +auto levenshtein_hyrroe2003_block(const BlockPatternMatchVector& PM, const Range& s1, + const Range& s2, size_t max = std::numeric_limits::max(), + size_t stop_row = std::numeric_limits::max()) + -> LevenshteinResult +{ + LevenshteinResult res; + if (max < abs_diff(s1.size(), s2.size())) { + res.dist = max + 1; + return res; + } + + size_t word_size = sizeof(uint64_t) * 8; + size_t words = PM.size(); + std::vector vecs(words); + std::vector scores(words); + uint64_t Last = UINT64_C(1) << ((s1.size() - 1) % word_size); + + for (size_t i = 0; i < words - 1; ++i) + scores[i] = (i + 1) * word_size; + + scores[words - 1] = s1.size(); + + if constexpr (RecordMatrix) { + size_t full_band = std::min(s1.size(), 2 * max + 1); + size_t full_band_words = std::min(words, full_band / word_size + 2); + res.VP = ShiftedBitMatrix(s2.size(), full_band_words, ~UINT64_C(0)); + res.VN = ShiftedBitMatrix(s2.size(), full_band_words, 0); + } + + if constexpr (RecordBitRow) { + res.first_block = 0; + res.last_block = 0; + res.prev_score = 0; + } + + max = std::min(max, std::max(s1.size(), s2.size())); + + /* first_block is the index of the first block in Ukkonen band. */ + size_t first_block = 0; + /* last_block is the index of the last block in Ukkonen band. */ + size_t last_block = + std::min(words, ceil_div(std::min(max, (max + s1.size() - s2.size()) / 2) + 1, word_size)) - 1; + + /* Searching */ + auto iter_s2 = s2.begin(); + for (size_t row = 0; row < s2.size(); ++iter_s2, ++row) { + uint64_t HP_carry = 1; + uint64_t HN_carry = 0; + + if constexpr (RecordMatrix) { + res.VP.set_offset(row, static_cast(first_block * word_size)); + res.VN.set_offset(row, static_cast(first_block * word_size)); + } + + auto advance_block = [&](size_t word) { + /* Step 1: Computing D0 */ + uint64_t PM_j = PM.get(word, *iter_s2); + uint64_t VN = vecs[word].VN; + uint64_t VP = vecs[word].VP; + + uint64_t X = PM_j | HN_carry; + uint64_t D0 = (((X & VP) + VP) ^ VP) | X | VN; + + /* Step 2: Computing HP and HN */ + uint64_t HP = VN | ~(D0 | VP); + uint64_t HN = D0 & VP; + + uint64_t HP_carry_temp = HP_carry; + uint64_t HN_carry_temp = HN_carry; + if (word < words - 1) { + HP_carry = HP >> 63; + HN_carry = HN >> 63; + } + else { + HP_carry = bool(HP & Last); + HN_carry = bool(HN & Last); + } + + /* Step 4: Computing Vp and VN */ + HP = (HP << 1) | HP_carry_temp; + HN = (HN << 1) | HN_carry_temp; + + vecs[word].VP = HN | ~(D0 | HP); + vecs[word].VN = HP & D0; + + if constexpr (RecordMatrix) { + res.VP[row][word - first_block] = vecs[word].VP; + res.VN[row][word - first_block] = vecs[word].VN; + } + + return static_cast(HP_carry) - static_cast(HN_carry); + }; + + auto get_row_num = [&](size_t word) { + if (word + 1 == words) return s1.size() - 1; + return (word + 1) * word_size - 1; + }; + + for (size_t word = first_block; word <= last_block /* - 1*/; word++) { + /* Step 3: Computing the value D[m,j] */ + scores[word] = static_cast(static_cast(scores[word]) + advance_block(word)); + } + + max = static_cast( + std::min(static_cast(max), + static_cast(scores[last_block]) + + std::max(static_cast(s2.size()) - static_cast(row) - 1, + static_cast(s1.size()) - + (static_cast((1 + last_block) * word_size - 1) - 1)))); + + /*---------- Adjust number of blocks according to Ukkonen ----------*/ + // todo on the last word instead of word_size often s1.size() % 64 should be used + + /* Band adjustment: last_block */ + /* If block is not beneath band, calculate next block. Only next because others are certainly beneath + * band. */ + if (last_block + 1 < words) { + ptrdiff_t cond = static_cast(max + 2 * word_size + row + s1.size()) - + static_cast(scores[last_block] + 2 + s2.size()); + if (static_cast(get_row_num(last_block)) < cond) { + last_block++; + vecs[last_block].VP = ~UINT64_C(0); + vecs[last_block].VN = 0; + + size_t chars_in_block = (last_block + 1 == words) ? ((s1.size() - 1) % word_size + 1) : 64; + scores[last_block] = scores[last_block - 1] + chars_in_block - + opt_static_cast(HP_carry) + opt_static_cast(HN_carry); + // todo probably wrong types + scores[last_block] = static_cast(static_cast(scores[last_block]) + + advance_block(last_block)); + } + } + + for (; last_block >= first_block; --last_block) { + /* in band if score <= k where score >= score_last - word_size + 1 */ + bool in_band_cond1 = scores[last_block] < max + word_size; + + /* in band if row <= max - score - len2 + len1 + i + * if the condition is met for the first cell in the block, it + * is met for all other cells in the blocks as well + * + * this uses a more loose condition similar to edlib: + * https://github.com/Martinsos/edlib + */ + ptrdiff_t cond = static_cast(max + 2 * word_size + row + s1.size() + 1) - + static_cast(scores[last_block] + 2 + s2.size()); + bool in_band_cond2 = static_cast(get_row_num(last_block)) <= cond; + + if (in_band_cond1 && in_band_cond2) break; + } + + /* Band adjustment: first_block */ + for (; first_block <= last_block; ++first_block) { + /* in band if score <= k where score >= score_last - word_size + 1 */ + bool in_band_cond1 = scores[first_block] < max + word_size; + + /* in band if row >= score - max - len2 + len1 + i + * if this condition is met for the last cell in the block, it + * is met for all other cells in the blocks as well + */ + ptrdiff_t cond = static_cast(scores[first_block] + s1.size() + row) - + static_cast(max + s2.size()); + bool in_band_cond2 = static_cast(get_row_num(first_block)) >= cond; + + if (in_band_cond1 && in_band_cond2) break; + } + + /* distance is larger than max, so band stops to exist */ + if (last_block < first_block) { + res.dist = max + 1; + return res; + } + + if constexpr (RecordBitRow) { + if (row == stop_row) { + if (first_block == 0) + res.prev_score = stop_row + 1; + else { + /* count backwards to find score at last position in previous block */ + size_t relevant_bits = std::min((first_block + 1) * 64, s1.size()) % 64; + uint64_t mask = ~UINT64_C(0); + if (relevant_bits) mask >>= 64 - relevant_bits; + + res.prev_score = scores[first_block] + popcount(vecs[first_block].VN & mask) - + popcount(vecs[first_block].VP & mask); + } + + res.first_block = first_block; + res.last_block = last_block; + res.vecs = std::move(vecs); + + /* unknown so make sure it is <= max */ + res.dist = 0; + return res; + } + } + } + + res.dist = scores[words - 1]; + + if (res.dist > max) res.dist = max + 1; + + return res; +} + +template +size_t uniform_levenshtein_distance(const BlockPatternMatchVector& block, Range s1, + Range s2, size_t score_cutoff, size_t score_hint) +{ + /* upper bound */ + score_cutoff = std::min(score_cutoff, std::max(s1.size(), s2.size())); + if (score_hint < 31) score_hint = 31; + + // when no differences are allowed a direct comparision is sufficient + if (score_cutoff == 0) return !std::equal(s1.begin(), s1.end(), s2.begin(), s2.end()); + + if (score_cutoff < abs_diff(s1.size(), s2.size())) return score_cutoff + 1; + + // important to catch, since this causes block to be empty -> raises exception on access + if (s1.empty()) return (s2.size() <= score_cutoff) ? s2.size() : score_cutoff + 1; + + /* do this first, since we can not remove any affix in encoded form + * todo actually we could at least remove the common prefix and just shift the band + */ + if (score_cutoff >= 4) { + // todo could safe up to 25% even without max when ignoring irrelevant paths + // in the upper and lower corner + size_t full_band = std::min(s1.size(), 2 * score_cutoff + 1); + + if (s1.size() < 65) + return levenshtein_hyrroe2003(block, s1, s2, score_cutoff).dist; + else if (full_band <= 64) + return levenshtein_hyrroe2003_small_band(block, s1, s2, score_cutoff); + + while (score_hint < score_cutoff) { + full_band = std::min(s1.size(), 2 * score_hint + 1); + + size_t score; + if (full_band <= 64) + score = levenshtein_hyrroe2003_small_band(block, s1, s2, score_hint); + else + score = levenshtein_hyrroe2003_block(block, s1, s2, score_hint).dist; + + if (score <= score_hint) return score; + + if (std::numeric_limits::max() / 2 < score_hint) break; + + score_hint *= 2; + } + + return levenshtein_hyrroe2003_block(block, s1, s2, score_cutoff).dist; + } + + /* common affix does not effect Levenshtein distance */ + remove_common_affix(s1, s2); + if (s1.empty() || s2.empty()) return s1.size() + s2.size(); + + return levenshtein_mbleven2018(s1, s2, score_cutoff); +} + +template +size_t uniform_levenshtein_distance(Range s1, Range s2, size_t score_cutoff, + size_t score_hint) +{ + /* Swapping the strings so the second string is shorter */ + if (s1.size() < s2.size()) return uniform_levenshtein_distance(s2, s1, score_cutoff, score_hint); + + /* upper bound */ + score_cutoff = std::min(score_cutoff, std::max(s1.size(), s2.size())); + if (score_hint < 31) score_hint = 31; + + // when no differences are allowed a direct comparision is sufficient + if (score_cutoff == 0) return !std::equal(s1.begin(), s1.end(), s2.begin(), s2.end()); + + // at least length difference insertions/deletions required + if (score_cutoff < (s1.size() - s2.size())) return score_cutoff + 1; + + /* common affix does not effect Levenshtein distance */ + remove_common_affix(s1, s2); + if (s1.empty() || s2.empty()) return s1.size() + s2.size(); + + if (score_cutoff < 4) return levenshtein_mbleven2018(s1, s2, score_cutoff); + + // todo could safe up to 25% even without score_cutoff when ignoring irrelevant paths + // in the upper and lower corner + size_t full_band = std::min(s1.size(), 2 * score_cutoff + 1); + + /* when the short strings has less then 65 elements Hyyrös' algorithm can be used */ + if (s2.size() < 65) + return levenshtein_hyrroe2003(PatternMatchVector(s2), s2, s1, score_cutoff).dist; + else if (full_band <= 64) + return levenshtein_hyrroe2003_small_band(s1, s2, score_cutoff).dist; + else { + BlockPatternMatchVector PM(s1); + while (score_hint < score_cutoff) { + // todo use small band implementation if possible + size_t score = levenshtein_hyrroe2003_block(PM, s1, s2, score_hint).dist; + + if (score <= score_hint) return score; + + if (std::numeric_limits::max() / 2 < score_hint) break; + + score_hint *= 2; + } + + return levenshtein_hyrroe2003_block(PM, s1, s2, score_cutoff).dist; + } +} + +/** + * @brief recover alignment from bitparallel Levenshtein matrix + */ +template +void recover_alignment(Editops& editops, const Range& s1, const Range& s2, + const LevenshteinResult& matrix, size_t src_pos, size_t dest_pos, + size_t editop_pos) +{ + size_t dist = matrix.dist; + size_t col = s1.size(); + size_t row = s2.size(); + + while (row && col) { + /* Deletion */ + if (matrix.VP.test_bit(row - 1, col - 1)) { + assert(dist > 0); + dist--; + col--; + editops[editop_pos + dist].type = EditType::Delete; + editops[editop_pos + dist].src_pos = col + src_pos; + editops[editop_pos + dist].dest_pos = row + dest_pos; + } + else { + row--; + + /* Insertion */ + if (row && matrix.VN.test_bit(row - 1, col - 1)) { + assert(dist > 0); + dist--; + editops[editop_pos + dist].type = EditType::Insert; + editops[editop_pos + dist].src_pos = col + src_pos; + editops[editop_pos + dist].dest_pos = row + dest_pos; + } + /* Match/Mismatch */ + else { + col--; + + /* Replace (Matches are not recorded) */ + if (s1[col] != s2[row]) { + assert(dist > 0); + dist--; + editops[editop_pos + dist].type = EditType::Replace; + editops[editop_pos + dist].src_pos = col + src_pos; + editops[editop_pos + dist].dest_pos = row + dest_pos; + } + } + } + } + + while (col) { + dist--; + col--; + editops[editop_pos + dist].type = EditType::Delete; + editops[editop_pos + dist].src_pos = col + src_pos; + editops[editop_pos + dist].dest_pos = row + dest_pos; + } + + while (row) { + dist--; + row--; + editops[editop_pos + dist].type = EditType::Insert; + editops[editop_pos + dist].src_pos = col + src_pos; + editops[editop_pos + dist].dest_pos = row + dest_pos; + } +} + +template +void levenshtein_align(Editops& editops, const Range& s1, const Range& s2, + size_t max = std::numeric_limits::max(), size_t src_pos = 0, + size_t dest_pos = 0, size_t editop_pos = 0) +{ + /* upper bound */ + max = std::min(max, std::max(s1.size(), s2.size())); + size_t full_band = std::min(s1.size(), 2 * max + 1); + + LevenshteinResult matrix; + if (s1.empty() || s2.empty()) + matrix.dist = s1.size() + s2.size(); + else if (s1.size() <= 64) + matrix = levenshtein_hyrroe2003(PatternMatchVector(s1), s1, s2); + else if (full_band <= 64) + matrix = levenshtein_hyrroe2003_small_band(s1, s2, max); + else + matrix = levenshtein_hyrroe2003_block(BlockPatternMatchVector(s1), s1, s2, max); + + assert(matrix.dist <= max); + if (matrix.dist != 0) { + if (editops.size() == 0) editops.resize(matrix.dist); + + recover_alignment(editops, s1, s2, matrix, src_pos, dest_pos, editop_pos); + } +} + +template +LevenshteinResult levenshtein_row(const Range& s1, const Range& s2, + size_t max, size_t stop_row) +{ + return levenshtein_hyrroe2003_block(BlockPatternMatchVector(s1), s1, s2, max, stop_row); +} + +template +size_t levenshtein_distance(const Range& s1, const Range& s2, + LevenshteinWeightTable weights = {1, 1, 1}, + size_t score_cutoff = std::numeric_limits::max(), + size_t score_hint = std::numeric_limits::max()) +{ + if (weights.insert_cost == weights.delete_cost) { + /* when insertions + deletions operations are free there can not be any edit distance */ + if (weights.insert_cost == 0) return 0; + + /* uniform Levenshtein multiplied with the common factor */ + if (weights.insert_cost == weights.replace_cost) { + // score_cutoff can make use of the common divisor of the three weights + size_t new_score_cutoff = ceil_div(score_cutoff, weights.insert_cost); + size_t new_score_hint = ceil_div(score_hint, weights.insert_cost); + size_t distance = uniform_levenshtein_distance(s1, s2, new_score_cutoff, new_score_hint); + distance *= weights.insert_cost; + return (distance <= score_cutoff) ? distance : score_cutoff + 1; + } + /* + * when replace_cost >= insert_cost + delete_cost no substitutions are performed + * therefore this can be implemented as InDel distance multiplied with the common factor + */ + else if (weights.replace_cost >= weights.insert_cost + weights.delete_cost) { + // score_cutoff can make use of the common divisor of the three weights + size_t new_score_cutoff = ceil_div(score_cutoff, weights.insert_cost); + size_t distance = rapidfuzz::indel_distance(s1, s2, new_score_cutoff); + distance *= weights.insert_cost; + return (distance <= score_cutoff) ? distance : score_cutoff + 1; + } + } + + return generalized_levenshtein_distance(s1, s2, weights, score_cutoff); +} +struct HirschbergPos { + size_t left_score; + size_t right_score; + size_t s1_mid; + size_t s2_mid; +}; + +template +HirschbergPos find_hirschberg_pos(const Range& s1, const Range& s2, + size_t max = std::numeric_limits::max()) +{ + assert(s1.size() > 1); + assert(s2.size() > 1); + + HirschbergPos hpos = {}; + size_t left_size = s2.size() / 2; + size_t right_size = s2.size() - left_size; + hpos.s2_mid = left_size; + size_t s1_len = s1.size(); + size_t best_score = std::numeric_limits::max(); + size_t right_first_pos = 0; + size_t right_last_pos = 0; + // todo: we could avoid this allocation by counting up the right score twice + // not sure whats faster though + std::vector right_scores; + { + auto right_row = levenshtein_row(s1.reversed(), s2.reversed(), max, right_size - 1); + if (right_row.dist > max) return find_hirschberg_pos(s1, s2, max * 2); + + right_first_pos = right_row.first_block * 64; + right_last_pos = std::min(s1_len, right_row.last_block * 64 + 64); + + right_scores.resize(right_last_pos - right_first_pos + 1, 0); + assume(right_scores.size() != 0); + right_scores[0] = right_row.prev_score; + + for (size_t i = right_first_pos; i < right_last_pos; ++i) { + size_t col_pos = i % 64; + size_t col_word = i / 64; + uint64_t col_mask = UINT64_C(1) << col_pos; + + right_scores[i - right_first_pos + 1] = right_scores[i - right_first_pos]; + right_scores[i - right_first_pos + 1] -= bool(right_row.vecs[col_word].VN & col_mask); + right_scores[i - right_first_pos + 1] += bool(right_row.vecs[col_word].VP & col_mask); + } + } + + auto left_row = levenshtein_row(s1, s2, max, left_size - 1); + if (left_row.dist > max) return find_hirschberg_pos(s1, s2, max * 2); + + auto left_first_pos = left_row.first_block * 64; + auto left_last_pos = std::min(s1_len, left_row.last_block * 64 + 64); + + size_t left_score = left_row.prev_score; + // take boundary into account + if (s1_len >= left_first_pos + right_first_pos) { + size_t right_index = s1_len - left_first_pos - right_first_pos; + if (right_index < right_scores.size()) { + best_score = right_scores[right_index] + left_score; + hpos.left_score = left_score; + hpos.right_score = right_scores[right_index]; + hpos.s1_mid = left_first_pos; + } + } + + for (size_t i = left_first_pos; i < left_last_pos; ++i) { + size_t col_pos = i % 64; + size_t col_word = i / 64; + uint64_t col_mask = UINT64_C(1) << col_pos; + + left_score -= bool(left_row.vecs[col_word].VN & col_mask); + left_score += bool(left_row.vecs[col_word].VP & col_mask); + + if (s1_len < i + 1 + right_first_pos) continue; + + size_t right_index = s1_len - i - 1 - right_first_pos; + if (right_index >= right_scores.size()) continue; + + if (right_scores[right_index] + left_score < best_score) { + best_score = right_scores[right_index] + left_score; + hpos.left_score = left_score; + hpos.right_score = right_scores[right_index]; + hpos.s1_mid = i + 1; + } + } + + assert(hpos.left_score >= 0); + assert(hpos.right_score >= 0); + + if (hpos.left_score + hpos.right_score > max) + return find_hirschberg_pos(s1, s2, max * 2); + else { + assert(levenshtein_distance(s1, s2) == hpos.left_score + hpos.right_score); + return hpos; + } +} + +template +void levenshtein_align_hirschberg(Editops& editops, Range s1, Range s2, + size_t src_pos = 0, size_t dest_pos = 0, size_t editop_pos = 0, + size_t max = std::numeric_limits::max()) +{ + /* prefix and suffix are no-ops, which do not need to be added to the editops */ + StringAffix affix = remove_common_affix(s1, s2); + src_pos += affix.prefix_len; + dest_pos += affix.prefix_len; + + max = std::min(max, std::max(s1.size(), s2.size())); + size_t full_band = std::min(s1.size(), 2 * max + 1); + + size_t matrix_size = 2 * full_band * s2.size() / 8; + if (matrix_size < 1024 * 1024 || s1.size() < 65 || s2.size() < 10) { + levenshtein_align(editops, s1, s2, max, src_pos, dest_pos, editop_pos); + } + /* Hirschbergs algorithm */ + else { + auto hpos = find_hirschberg_pos(s1, s2, max); + + if (editops.size() == 0) editops.resize(hpos.left_score + hpos.right_score); + + levenshtein_align_hirschberg(editops, s1.subseq(0, hpos.s1_mid), s2.subseq(0, hpos.s2_mid), src_pos, + dest_pos, editop_pos, hpos.left_score); + levenshtein_align_hirschberg(editops, s1.subseq(hpos.s1_mid), s2.subseq(hpos.s2_mid), + src_pos + hpos.s1_mid, dest_pos + hpos.s2_mid, + editop_pos + hpos.left_score, hpos.right_score); + } +} + +class Levenshtein : public DistanceBase::max(), + LevenshteinWeightTable> { + friend DistanceBase::max(), LevenshteinWeightTable>; + friend NormalizedMetricBase; + + template + static size_t maximum(const Range& s1, const Range& s2, + LevenshteinWeightTable weights) + { + return levenshtein_maximum(s1.size(), s2.size(), weights); + } + + template + static size_t _distance(const Range& s1, const Range& s2, + LevenshteinWeightTable weights, size_t score_cutoff, size_t score_hint) + { + return levenshtein_distance(s1, s2, weights, score_cutoff, score_hint); + } +}; + +template +Editops levenshtein_editops(const Range& s1, const Range& s2, size_t score_hint) +{ + Editops editops; + if (score_hint < 31) score_hint = 31; + + size_t score_cutoff = std::max(s1.size(), s2.size()); + /* score_hint currently leads to calculating the levenshtein distance twice + * 1) to find the real distance + * 2) to find the alignment + * this is only worth it when at least 50% of the runtime could be saved + * todo: maybe there is a way to join these two calculations in the future + * so it is worth it in more cases + */ + if (std::numeric_limits::max() / 2 > score_hint && 2 * score_hint < score_cutoff) + score_cutoff = Levenshtein::distance(s1, s2, {1, 1, 1}, score_cutoff, score_hint); + + levenshtein_align_hirschberg(editops, s1, s2, 0, 0, 0, score_cutoff); + + editops.set_src_len(s1.size()); + editops.set_dest_len(s2.size()); + return editops; +} + +} // namespace rapidfuzz::detail diff --git a/src/external/rapidfuzz-cpp/rapidfuzz/distance/OSA.hpp b/src/external/rapidfuzz-cpp/rapidfuzz/distance/OSA.hpp new file mode 100644 index 00000000..0e56eadd --- /dev/null +++ b/src/external/rapidfuzz-cpp/rapidfuzz/distance/OSA.hpp @@ -0,0 +1,281 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright © 2021 Max Bachmann */ + +#pragma once + +#include +#include +#include + +namespace rapidfuzz { + +/** + * @brief Calculates the optimal string alignment (OSA) distance between two strings. + * + * @details + * Both strings require a similar length + * + * + * @tparam Sentence1 This is a string that can be converted to + * basic_string_view + * @tparam Sentence2 This is a string that can be converted to + * basic_string_view + * + * @param s1 + * string to compare with s2 (for type info check Template parameters above) + * @param s2 + * string to compare with s1 (for type info check Template parameters above) + * @param max + * Maximum OSA distance between s1 and s2, that is + * considered as a result. If the distance is bigger than max, + * max + 1 is returned instead. Default is std::numeric_limits::max(), + * which deactivates this behaviour. + * + * @return OSA distance between s1 and s2 + */ +template +size_t osa_distance(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + size_t score_cutoff = std::numeric_limits::max()) +{ + return detail::OSA::distance(first1, last1, first2, last2, score_cutoff, score_cutoff); +} + +template +size_t osa_distance(const Sentence1& s1, const Sentence2& s2, + size_t score_cutoff = std::numeric_limits::max()) +{ + return detail::OSA::distance(s1, s2, score_cutoff, score_cutoff); +} + +template +size_t osa_similarity(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + size_t score_cutoff = 0) +{ + return detail::OSA::similarity(first1, last1, first2, last2, score_cutoff, score_cutoff); +} + +template +size_t osa_similarity(const Sentence1& s1, const Sentence2& s2, size_t score_cutoff = 0) +{ + return detail::OSA::similarity(s1, s2, score_cutoff, score_cutoff); +} + +template +double osa_normalized_distance(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + double score_cutoff = 1.0) +{ + return detail::OSA::normalized_distance(first1, last1, first2, last2, score_cutoff, score_cutoff); +} + +template +double osa_normalized_distance(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 1.0) +{ + return detail::OSA::normalized_distance(s1, s2, score_cutoff, score_cutoff); +} + +/** + * @brief Calculates a normalized hamming similarity + * + * @details + * Both string require a similar length + * + * + * @tparam Sentence1 This is a string that can be converted to + * basic_string_view + * @tparam Sentence2 This is a string that can be converted to + * basic_string_view + * + * @param s1 + * string to compare with s2 (for type info check Template parameters above) + * @param s2 + * string to compare with s1 (for type info check Template parameters above) + * @param score_cutoff + * Optional argument for a score threshold as a float between 0 and 1.0. + * For ratio < score_cutoff 0 is returned instead. Default is 0, + * which deactivates this behaviour. + * + * @return Normalized hamming distance between s1 and s2 + * as a float between 0 and 1.0 + */ +template +double osa_normalized_similarity(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + double score_cutoff = 0.0) +{ + return detail::OSA::normalized_similarity(first1, last1, first2, last2, score_cutoff, score_cutoff); +} + +template +double osa_normalized_similarity(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 0.0) +{ + return detail::OSA::normalized_similarity(s1, s2, score_cutoff, score_cutoff); +} + +#ifdef RAPIDFUZZ_SIMD +namespace experimental { +template +struct MultiOSA + : public detail::MultiDistanceBase, size_t, 0, std::numeric_limits::max()> { +private: + friend detail::MultiDistanceBase, size_t, 0, std::numeric_limits::max()>; + friend detail::MultiNormalizedMetricBase, size_t>; + + constexpr static size_t get_vec_size() + { +# ifdef RAPIDFUZZ_AVX2 + using namespace detail::simd_avx2; +# else + using namespace detail::simd_sse2; +# endif + if constexpr (MaxLen <= 8) + return native_simd::size; + else if constexpr (MaxLen <= 16) + return native_simd::size; + else if constexpr (MaxLen <= 32) + return native_simd::size; + else if constexpr (MaxLen <= 64) + return native_simd::size; + + static_assert(MaxLen <= 64); + } + + constexpr static size_t find_block_count(size_t count) + { + size_t vec_size = get_vec_size(); + size_t simd_vec_count = detail::ceil_div(count, vec_size); + return detail::ceil_div(simd_vec_count * vec_size * MaxLen, 64); + } + +public: + MultiOSA(size_t count) : input_count(count), PM(find_block_count(count) * 64) + { + str_lens.resize(result_count()); + } + + /** + * @brief get minimum size required for result vectors passed into + * - distance + * - similarity + * - normalized_distance + * - normalized_similarity + * + * @return minimum vector size + */ + size_t result_count() const + { + size_t vec_size = get_vec_size(); + size_t simd_vec_count = detail::ceil_div(input_count, vec_size); + return simd_vec_count * vec_size; + } + + template + void insert(const Sentence1& s1_) + { + insert(detail::to_begin(s1_), detail::to_end(s1_)); + } + + template + void insert(InputIt1 first1, InputIt1 last1) + { + auto len = std::distance(first1, last1); + int block_pos = static_cast((pos * MaxLen) % 64); + auto block = (pos * MaxLen) / 64; + assert(len <= MaxLen); + + if (pos >= input_count) throw std::invalid_argument("out of bounds insert"); + + str_lens[pos] = static_cast(len); + for (; first1 != last1; ++first1) { + PM.insert(block, *first1, block_pos); + block_pos++; + } + pos++; + } + +private: + template + void _distance(size_t* scores, size_t score_count, const detail::Range& s2, + size_t score_cutoff = std::numeric_limits::max()) const + { + if (score_count < result_count()) + throw std::invalid_argument("scores has to have >= result_count() elements"); + + detail::Range scores_(scores, scores + score_count); + if constexpr (MaxLen == 8) + detail::osa_hyrroe2003_simd(scores_, PM, str_lens, s2, score_cutoff); + else if constexpr (MaxLen == 16) + detail::osa_hyrroe2003_simd(scores_, PM, str_lens, s2, score_cutoff); + else if constexpr (MaxLen == 32) + detail::osa_hyrroe2003_simd(scores_, PM, str_lens, s2, score_cutoff); + else if constexpr (MaxLen == 64) + detail::osa_hyrroe2003_simd(scores_, PM, str_lens, s2, score_cutoff); + } + + template + size_t maximum(size_t s1_idx, const detail::Range& s2) const + { + return std::max(str_lens[s1_idx], s2.size()); + } + + size_t get_input_count() const noexcept + { + return input_count; + } + + size_t input_count; + size_t pos = 0; + detail::BlockPatternMatchVector PM; + std::vector str_lens; +}; +} /* namespace experimental */ +#endif + +template +struct CachedOSA + : public detail::CachedDistanceBase, size_t, 0, std::numeric_limits::max()> { + template + explicit CachedOSA(const Sentence1& s1_) : CachedOSA(detail::to_begin(s1_), detail::to_end(s1_)) + {} + + template + CachedOSA(InputIt1 first1, InputIt1 last1) : s1(first1, last1), PM(detail::Range(first1, last1)) + {} + +private: + friend detail::CachedDistanceBase, size_t, 0, std::numeric_limits::max()>; + friend detail::CachedNormalizedMetricBase>; + + template + size_t maximum(const detail::Range& s2) const + { + return std::max(s1.size(), s2.size()); + } + + template + size_t _distance(const detail::Range& s2, size_t score_cutoff, + [[maybe_unused]] size_t score_hint) const + { + size_t res; + if (s1.empty()) + res = s2.size(); + else if (s2.empty()) + res = s1.size(); + else if (s1.size() < 64) + res = detail::osa_hyrroe2003(PM, detail::Range(s1), s2, score_cutoff); + else + res = detail::osa_hyrroe2003_block(PM, detail::Range(s1), s2, score_cutoff); + + return (res <= score_cutoff) ? res : score_cutoff + 1; + } + + std::vector s1; + detail::BlockPatternMatchVector PM; +}; + +template +CachedOSA(const Sentence1& s1_) -> CachedOSA>; + +template +CachedOSA(InputIt1 first1, InputIt1 last1) -> CachedOSA>; +/**@}*/ + +} // namespace rapidfuzz diff --git a/src/external/rapidfuzz-cpp/rapidfuzz/distance/OSA_impl.hpp b/src/external/rapidfuzz-cpp/rapidfuzz/distance/OSA_impl.hpp new file mode 100644 index 00000000..a4707dd0 --- /dev/null +++ b/src/external/rapidfuzz-cpp/rapidfuzz/distance/OSA_impl.hpp @@ -0,0 +1,273 @@ + +/* SPDX-License-Identifier: MIT */ +/* Copyright © 2022-present Max Bachmann */ + +#pragma once +#include +#include +#include +#include +#include +#include + +namespace rapidfuzz::detail { + +/** + * @brief Bitparallel implementation of the OSA distance. + * + * This implementation requires the first string to have a length <= 64. + * The algorithm used is described @cite hyrro_2002 and has a time complexity + * of O(N). Comments and variable names in the implementation follow the + * paper. This implementation is used internally when the strings are short enough + * + * @tparam CharT1 This is the char type of the first sentence + * @tparam CharT2 This is the char type of the second sentence + * + * @param s1 + * string to compare with s2 (for type info check Template parameters above) + * @param s2 + * string to compare with s1 (for type info check Template parameters above) + * + * @return returns the OSA distance between s1 and s2 + */ +template +size_t osa_hyrroe2003(const PM_Vec& PM, const Range& s1, const Range& s2, size_t max) +{ + /* VP is set to 1^m. Shifting by bitwidth would be undefined behavior */ + uint64_t VP = ~UINT64_C(0); + uint64_t VN = 0; + uint64_t D0 = 0; + uint64_t PM_j_old = 0; + size_t currDist = s1.size(); + assert(s1.size() != 0); + + /* mask used when computing D[m,j] in the paper 10^(m-1) */ + uint64_t mask = UINT64_C(1) << (s1.size() - 1); + + /* Searching */ + for (const auto& ch : s2) { + /* Step 1: Computing D0 */ + uint64_t PM_j = PM.get(0, ch); + uint64_t TR = (((~D0) & PM_j) << 1) & PM_j_old; + D0 = (((PM_j & VP) + VP) ^ VP) | PM_j | VN; + D0 = D0 | TR; + + /* Step 2: Computing HP and HN */ + uint64_t HP = VN | ~(D0 | VP); + uint64_t HN = D0 & VP; + + /* Step 3: Computing the value D[m,j] */ + currDist += bool(HP & mask); + currDist -= bool(HN & mask); + + /* Step 4: Computing Vp and VN */ + HP = (HP << 1) | 1; + HN = (HN << 1); + + VP = HN | ~(D0 | HP); + VN = HP & D0; + PM_j_old = PM_j; + } + + return (currDist <= max) ? currDist : max + 1; +} + +#ifdef RAPIDFUZZ_SIMD +template +void osa_hyrroe2003_simd(Range scores, const detail::BlockPatternMatchVector& block, + const std::vector& s1_lengths, const Range& s2, + size_t score_cutoff) noexcept +{ +# ifdef RAPIDFUZZ_AVX2 + using namespace simd_avx2; +# else + using namespace simd_sse2; +# endif + static constexpr size_t alignment = native_simd::alignment; + static constexpr size_t vec_width = native_simd::size; + static constexpr size_t vecs = native_simd::size; + assert(block.size() % vecs == 0); + + native_simd zero(VecType(0)); + native_simd one(1); + size_t result_index = 0; + + for (size_t cur_vec = 0; cur_vec < block.size(); cur_vec += vecs) { + /* VP is set to 1^m */ + native_simd VP(static_cast(-1)); + native_simd VN(VecType(0)); + native_simd D0(VecType(0)); + native_simd PM_j_old(VecType(0)); + + alignas(alignment) std::array currDist_; + unroll( + [&](auto i) { currDist_[i] = static_cast(s1_lengths[result_index + i]); }); + native_simd currDist(reinterpret_cast(currDist_.data())); + /* mask used when computing D[m,j] in the paper 10^(m-1) */ + alignas(alignment) std::array mask_; + unroll([&](auto i) { + if (s1_lengths[result_index + i] == 0) + mask_[i] = 0; + else + mask_[i] = static_cast(UINT64_C(1) << (s1_lengths[result_index + i] - 1)); + }); + native_simd mask(reinterpret_cast(mask_.data())); + + for (const auto& ch : s2) { + /* Step 1: Computing D0 */ + alignas(alignment) std::array stored; + unroll([&](auto i) { stored[i] = block.get(cur_vec + i, ch); }); + + native_simd PM_j(stored.data()); + auto TR = (andnot(PM_j, D0) << 1) & PM_j_old; + D0 = (((PM_j & VP) + VP) ^ VP) | PM_j | VN; + D0 = D0 | TR; + + /* Step 2: Computing HP and HN */ + auto HP = VN | ~(D0 | VP); + auto HN = D0 & VP; + + /* Step 3: Computing the value D[m,j] */ + currDist += andnot(one, (HP & mask) == zero); + currDist -= andnot(one, (HN & mask) == zero); + + /* Step 4: Computing Vp and VN */ + HP = (HP << 1) | one; + HN = (HN << 1); + + VP = HN | ~(D0 | HP); + VN = HP & D0; + PM_j_old = PM_j; + } + + alignas(alignment) std::array distances; + currDist.store(distances.data()); + + unroll([&](auto i) { + size_t score = 0; + /* strings of length 0 are not handled correctly */ + if (s1_lengths[result_index] == 0) { + score = s2.size(); + } + /* calculate score under consideration of wraparounds in parallel counter */ + else { + if constexpr (std::numeric_limits::max() < std::numeric_limits::max()) { + size_t min_dist = abs_diff(s1_lengths[result_index], s2.size()); + size_t wraparound_score = static_cast(std::numeric_limits::max()) + 1; + + score = (min_dist / wraparound_score) * wraparound_score; + VecType remainder = static_cast(min_dist % wraparound_score); + + if (distances[i] < remainder) score += wraparound_score; + } + + score += distances[i]; + } + scores[result_index] = (score <= score_cutoff) ? score : score_cutoff + 1; + result_index++; + }); + } +} +#endif + +template +size_t osa_hyrroe2003_block(const BlockPatternMatchVector& PM, const Range& s1, + const Range& s2, size_t max = std::numeric_limits::max()) +{ + struct Row { + uint64_t VP; + uint64_t VN; + uint64_t D0; + uint64_t PM; + + Row() : VP(~UINT64_C(0)), VN(0), D0(0), PM(0) + {} + }; + + size_t word_size = sizeof(uint64_t) * 8; + size_t words = PM.size(); + uint64_t Last = UINT64_C(1) << ((s1.size() - 1) % word_size); + + size_t currDist = s1.size(); + std::vector old_vecs(words + 1); + std::vector new_vecs(words + 1); + + /* Searching */ + auto iter_s2 = s2.begin(); + for (size_t row = 0; row < s2.size(); ++iter_s2, ++row) { + uint64_t HP_carry = 1; + uint64_t HN_carry = 0; + + for (size_t word = 0; word < words; word++) { + /* retrieve bit vectors from last iterations */ + uint64_t VN = old_vecs[word + 1].VN; + uint64_t VP = old_vecs[word + 1].VP; + uint64_t D0 = old_vecs[word + 1].D0; + /* D0 last word */ + uint64_t D0_last = old_vecs[word].D0; + + /* PM of last char same word */ + uint64_t PM_j_old = old_vecs[word + 1].PM; + /* PM of last word */ + uint64_t PM_last = new_vecs[word].PM; + + uint64_t PM_j = PM.get(word, *iter_s2); + uint64_t X = PM_j; + uint64_t TR = ((((~D0) & X) << 1) | (((~D0_last) & PM_last) >> 63)) & PM_j_old; + + X |= HN_carry; + D0 = (((X & VP) + VP) ^ VP) | X | VN | TR; + + uint64_t HP = VN | ~(D0 | VP); + uint64_t HN = D0 & VP; + + if (word == words - 1) { + currDist += bool(HP & Last); + currDist -= bool(HN & Last); + } + + uint64_t HP_carry_temp = HP_carry; + HP_carry = HP >> 63; + HP = (HP << 1) | HP_carry_temp; + uint64_t HN_carry_temp = HN_carry; + HN_carry = HN >> 63; + HN = (HN << 1) | HN_carry_temp; + + new_vecs[word + 1].VP = HN | ~(D0 | HP); + new_vecs[word + 1].VN = HP & D0; + new_vecs[word + 1].D0 = D0; + new_vecs[word + 1].PM = PM_j; + } + + std::swap(new_vecs, old_vecs); + } + + return (currDist <= max) ? currDist : max + 1; +} + +class OSA : public DistanceBase::max()> { + friend DistanceBase::max()>; + friend NormalizedMetricBase; + + template + static size_t maximum(const Range& s1, const Range& s2) + { + return std::max(s1.size(), s2.size()); + } + + template + static size_t _distance(Range s1, Range s2, size_t score_cutoff, size_t score_hint) + { + if (s2.size() < s1.size()) return _distance(s2, s1, score_cutoff, score_hint); + + remove_common_affix(s1, s2); + if (s1.empty()) + return (s2.size() <= score_cutoff) ? s2.size() : score_cutoff + 1; + else if (s1.size() < 64) + return osa_hyrroe2003(PatternMatchVector(s1), s1, s2, score_cutoff); + else + return osa_hyrroe2003_block(BlockPatternMatchVector(s1), s1, s2, score_cutoff); + } +}; + +} // namespace rapidfuzz::detail \ No newline at end of file diff --git a/src/external/rapidfuzz-cpp/rapidfuzz/distance/Postfix.hpp b/src/external/rapidfuzz-cpp/rapidfuzz/distance/Postfix.hpp new file mode 100644 index 00000000..0da830f7 --- /dev/null +++ b/src/external/rapidfuzz-cpp/rapidfuzz/distance/Postfix.hpp @@ -0,0 +1,105 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright © 2021 Max Bachmann */ + +#pragma once + +#include +#include +#include + +namespace rapidfuzz { + +template +size_t postfix_distance(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + size_t score_cutoff = std::numeric_limits::max()) +{ + return detail::Postfix::distance(first1, last1, first2, last2, score_cutoff, score_cutoff); +} + +template +size_t postfix_distance(const Sentence1& s1, const Sentence2& s2, + size_t score_cutoff = std::numeric_limits::max()) +{ + return detail::Postfix::distance(s1, s2, score_cutoff, score_cutoff); +} + +template +size_t postfix_similarity(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + size_t score_cutoff = 0) +{ + return detail::Postfix::similarity(first1, last1, first2, last2, score_cutoff, score_cutoff); +} + +template +size_t postfix_similarity(const Sentence1& s1, const Sentence2& s2, size_t score_cutoff = 0) +{ + return detail::Postfix::similarity(s1, s2, score_cutoff, score_cutoff); +} + +template +double postfix_normalized_distance(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + double score_cutoff = 1.0) +{ + return detail::Postfix::normalized_distance(first1, last1, first2, last2, score_cutoff, score_cutoff); +} + +template +double postfix_normalized_distance(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 1.0) +{ + return detail::Postfix::normalized_distance(s1, s2, score_cutoff, score_cutoff); +} + +template +double postfix_normalized_similarity(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + double score_cutoff = 0.0) +{ + return detail::Postfix::normalized_similarity(first1, last1, first2, last2, score_cutoff, score_cutoff); +} + +template +double postfix_normalized_similarity(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 0.0) +{ + return detail::Postfix::normalized_similarity(s1, s2, score_cutoff, score_cutoff); +} + +template +struct CachedPostfix : public detail::CachedSimilarityBase, size_t, 0, + std::numeric_limits::max()> { + template + explicit CachedPostfix(const Sentence1& s1_) : CachedPostfix(detail::to_begin(s1_), detail::to_end(s1_)) + {} + + template + CachedPostfix(InputIt1 first1, InputIt1 last1) : s1(first1, last1) + {} + +private: + friend detail::CachedSimilarityBase, size_t, 0, + std::numeric_limits::max()>; + friend detail::CachedNormalizedMetricBase>; + + template + size_t maximum(const detail::Range& s2) const + { + return std::max(s1.size(), s2.size()); + } + + template + size_t _similarity(detail::Range s2, size_t score_cutoff, + [[maybe_unused]] size_t score_hint) const + { + return detail::Postfix::similarity(s1, s2, score_cutoff, score_hint); + } + + std::vector s1; +}; + +template +explicit CachedPostfix(const Sentence1& s1_) -> CachedPostfix>; + +template +CachedPostfix(InputIt1 first1, InputIt1 last1) -> CachedPostfix>; + +/**@}*/ + +} // namespace rapidfuzz diff --git a/src/external/rapidfuzz-cpp/rapidfuzz/distance/Postfix_impl.hpp b/src/external/rapidfuzz-cpp/rapidfuzz/distance/Postfix_impl.hpp new file mode 100644 index 00000000..0be3abf6 --- /dev/null +++ b/src/external/rapidfuzz-cpp/rapidfuzz/distance/Postfix_impl.hpp @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright © 2021 Max Bachmann */ + +#pragma once +#include +#include +#include + +namespace rapidfuzz::detail { + +class Postfix : public SimilarityBase::max()> { + friend SimilarityBase::max()>; + friend NormalizedMetricBase; + + template + static size_t maximum(const Range& s1, const Range& s2) + { + return std::max(s1.size(), s2.size()); + } + + template + static size_t _similarity(Range s1, Range s2, size_t score_cutoff, + [[maybe_unused]] size_t score_hint) + { + size_t dist = remove_common_suffix(s1, s2); + return (dist >= score_cutoff) ? dist : 0; + } +}; + +} // namespace rapidfuzz::detail diff --git a/src/external/rapidfuzz-cpp/rapidfuzz/distance/Prefix.hpp b/src/external/rapidfuzz-cpp/rapidfuzz/distance/Prefix.hpp new file mode 100644 index 00000000..64173dc7 --- /dev/null +++ b/src/external/rapidfuzz-cpp/rapidfuzz/distance/Prefix.hpp @@ -0,0 +1,104 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright © 2021 Max Bachmann */ + +#pragma once + +#include +#include +#include + +namespace rapidfuzz { + +template +size_t prefix_distance(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + size_t score_cutoff = std::numeric_limits::max()) +{ + return detail::Prefix::distance(first1, last1, first2, last2, score_cutoff, score_cutoff); +} + +template +size_t prefix_distance(const Sentence1& s1, const Sentence2& s2, + size_t score_cutoff = std::numeric_limits::max()) +{ + return detail::Prefix::distance(s1, s2, score_cutoff, score_cutoff); +} + +template +size_t prefix_similarity(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + size_t score_cutoff = 0) +{ + return detail::Prefix::similarity(first1, last1, first2, last2, score_cutoff, score_cutoff); +} + +template +size_t prefix_similarity(const Sentence1& s1, const Sentence2& s2, size_t score_cutoff = 0) +{ + return detail::Prefix::similarity(s1, s2, score_cutoff, score_cutoff); +} + +template +double prefix_normalized_distance(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + double score_cutoff = 1.0) +{ + return detail::Prefix::normalized_distance(first1, last1, first2, last2, score_cutoff, score_cutoff); +} + +template +double prefix_normalized_distance(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 1.0) +{ + return detail::Prefix::normalized_distance(s1, s2, score_cutoff, score_cutoff); +} + +template +double prefix_normalized_similarity(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + double score_cutoff = 0.0) +{ + return detail::Prefix::normalized_similarity(first1, last1, first2, last2, score_cutoff, score_cutoff); +} + +template +double prefix_normalized_similarity(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 0.0) +{ + return detail::Prefix::normalized_similarity(s1, s2, score_cutoff, score_cutoff); +} + +template +struct CachedPrefix : public detail::CachedSimilarityBase, size_t, 0, + std::numeric_limits::max()> { + template + explicit CachedPrefix(const Sentence1& s1_) : CachedPrefix(detail::to_begin(s1_), detail::to_end(s1_)) + {} + + template + CachedPrefix(InputIt1 first1, InputIt1 last1) : s1(first1, last1) + {} + +private: + friend detail::CachedSimilarityBase, size_t, 0, std::numeric_limits::max()>; + friend detail::CachedNormalizedMetricBase>; + + template + size_t maximum(const detail::Range& s2) const + { + return std::max(s1.size(), s2.size()); + } + + template + size_t _similarity(detail::Range s2, size_t score_cutoff, + [[maybe_unused]] size_t score_hint) const + { + return detail::Prefix::similarity(s1, s2, score_cutoff, score_cutoff); + } + + std::vector s1; +}; + +template +explicit CachedPrefix(const Sentence1& s1_) -> CachedPrefix>; + +template +CachedPrefix(InputIt1 first1, InputIt1 last1) -> CachedPrefix>; + +/**@}*/ + +} // namespace rapidfuzz diff --git a/src/external/rapidfuzz-cpp/rapidfuzz/distance/Prefix_impl.hpp b/src/external/rapidfuzz-cpp/rapidfuzz/distance/Prefix_impl.hpp new file mode 100644 index 00000000..41ab1f69 --- /dev/null +++ b/src/external/rapidfuzz-cpp/rapidfuzz/distance/Prefix_impl.hpp @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright © 2021 Max Bachmann */ + +#pragma once +#include +#include +#include + +namespace rapidfuzz::detail { + +class Prefix : public SimilarityBase::max()> { + friend SimilarityBase::max()>; + friend NormalizedMetricBase; + + template + static size_t maximum(const Range& s1, const Range& s2) + { + return std::max(s1.size(), s2.size()); + } + + template + static size_t _similarity(Range s1, Range s2, size_t score_cutoff, + [[maybe_unused]] size_t score_hint) + { + size_t dist = remove_common_prefix(s1, s2); + return (dist >= score_cutoff) ? dist : 0; + } +}; + +} // namespace rapidfuzz::detail diff --git a/src/external/rapidfuzz-cpp/rapidfuzz/fuzz.hpp b/src/external/rapidfuzz-cpp/rapidfuzz/fuzz.hpp new file mode 100644 index 00000000..d303722c --- /dev/null +++ b/src/external/rapidfuzz-cpp/rapidfuzz/fuzz.hpp @@ -0,0 +1,789 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright © 2021 Max Bachmann */ +/* Copyright © 2011 Adam Cohen */ + +#pragma once +#include +#include +#include +#include + +namespace rapidfuzz::fuzz { + +/** + * @defgroup Fuzz Fuzz + * A collection of string matching algorithms from FuzzyWuzzy + * @{ + */ + +/** + * @brief calculates a simple ratio between two strings + * + * @details + * @code{.cpp} + * // score is 96.55 + * double score = ratio("this is a test", "this is a test!") + * @endcode + * + * @tparam Sentence1 This is a string that can be converted to + * basic_string_view + * @tparam Sentence2 This is a string that can be converted to + * basic_string_view + * + * @param s1 string to compare with s2 (for type info check Template parameters + * above) + * @param s2 string to compare with s1 (for type info check Template parameters + * above) + * @param score_cutoff Optional argument for a score threshold between 0% and + * 100%. Matches with a lower score than this number will not be returned. + * Defaults to 0. + * + * @return returns the ratio between s1 and s2 or 0 when ratio < score_cutoff + */ +template +double ratio(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 0); + +template +double ratio(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, double score_cutoff = 0); + +#ifdef RAPIDFUZZ_SIMD +namespace experimental { +template +struct MultiRatio { +public: + MultiRatio(size_t count) : input_count(count), scorer(count) + {} + + size_t result_count() const + { + return scorer.result_count(); + } + + template + void insert(const Sentence1& s1_) + { + insert(detail::to_begin(s1_), detail::to_end(s1_)); + } + + template + void insert(InputIt1 first1, InputIt1 last1) + { + scorer.insert(first1, last1); + } + + template + void similarity(double* scores, size_t score_count, InputIt2 first2, InputIt2 last2, + double score_cutoff = 0.0) const + { + similarity(scores, score_count, detail::Range(first2, last2), score_cutoff); + } + + template + void similarity(double* scores, size_t score_count, const Sentence2& s2, double score_cutoff = 0) const + { + scorer.normalized_similarity(scores, score_count, s2, score_cutoff / 100.0); + + for (size_t i = 0; i < input_count; ++i) + scores[i] *= 100.0; + } + +private: + size_t input_count; + rapidfuzz::experimental::MultiIndel scorer; +}; +} /* namespace experimental */ +#endif + +// TODO documentation +template +struct CachedRatio { + template + CachedRatio(InputIt1 first1, InputIt1 last1) : cached_indel(first1, last1) + {} + + template + CachedRatio(const Sentence1& s1) : cached_indel(s1) + {} + + template + double similarity(InputIt2 first2, InputIt2 last2, double score_cutoff = 0.0, + double score_hint = 0.0) const; + + template + double similarity(const Sentence2& s2, double score_cutoff = 0.0, double score_hint = 0.0) const; + + // private: + CachedIndel cached_indel; +}; + +template +CachedRatio(const Sentence1& s1) -> CachedRatio>; + +template +CachedRatio(InputIt1 first1, InputIt1 last1) -> CachedRatio>; + +template +ScoreAlignment partial_ratio_alignment(InputIt1 first1, InputIt1 last1, InputIt2 first2, + InputIt2 last2, double score_cutoff = 0); + +template +ScoreAlignment partial_ratio_alignment(const Sentence1& s1, const Sentence2& s2, + double score_cutoff = 0); + +/** + * @brief calculates the fuzz::ratio of the optimal string alignment + * + * @details + * test @cite hyrro_2004 @cite wagner_fischer_1974 + * @code{.cpp} + * // score is 100 + * double score = partial_ratio("this is a test", "this is a test!") + * @endcode + * + * @tparam Sentence1 This is a string that can be converted to + * basic_string_view + * @tparam Sentence2 This is a string that can be converted to + * basic_string_view + * + * @param s1 string to compare with s2 (for type info check Template parameters + * above) + * @param s2 string to compare with s1 (for type info check Template parameters + * above) + * @param score_cutoff Optional argument for a score threshold between 0% and + * 100%. Matches with a lower score than this number will not be returned. + * Defaults to 0. + * + * @return returns the ratio between s1 and s2 or 0 when ratio < score_cutoff + */ +template +double partial_ratio(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 0); + +template +double partial_ratio(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + double score_cutoff = 0); + +// todo add real implementation +template +struct CachedPartialRatio { + template + friend struct CachedWRatio; + + template + CachedPartialRatio(InputIt1 first1, InputIt1 last1); + + template + explicit CachedPartialRatio(const Sentence1& s1_) + : CachedPartialRatio(detail::to_begin(s1_), detail::to_end(s1_)) + {} + + template + double similarity(InputIt2 first2, InputIt2 last2, double score_cutoff = 0.0, + double score_hint = 0.0) const; + + template + double similarity(const Sentence2& s2, double score_cutoff = 0.0, double score_hint = 0.0) const; + +private: + std::vector s1; + rapidfuzz::detail::CharSet s1_char_set; + CachedRatio cached_ratio; +}; + +template +explicit CachedPartialRatio(const Sentence1& s1) -> CachedPartialRatio>; + +template +CachedPartialRatio(InputIt1 first1, InputIt1 last1) -> CachedPartialRatio>; + +/** + * @brief Sorts the words in the strings and calculates the fuzz::ratio between + * them + * + * @details + * @code{.cpp} + * // score is 100 + * double score = token_sort_ratio("fuzzy wuzzy was a bear", "wuzzy fuzzy was a + * bear") + * @endcode + * + * @tparam Sentence1 This is a string that can be converted to + * basic_string_view + * @tparam Sentence2 This is a string that can be converted to + * basic_string_view + * + * @param s1 string to compare with s2 (for type info check Template parameters + * above) + * @param s2 string to compare with s1 (for type info check Template parameters + * above) + * @param score_cutoff Optional argument for a score threshold between 0% and + * 100%. Matches with a lower score than this number will not be returned. + * Defaults to 0. + * + * @return returns the ratio between s1 and s2 or 0 when ratio < score_cutoff + */ +template +double token_sort_ratio(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 0); + +template +double token_sort_ratio(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + double score_cutoff = 0); + +#ifdef RAPIDFUZZ_SIMD +namespace experimental { +template +struct MultiTokenSortRatio { +public: + MultiTokenSortRatio(size_t count) : scorer(count) + {} + + size_t result_count() const + { + return scorer.result_count(); + } + + template + void insert(const Sentence1& s1_) + { + insert(detail::to_begin(s1_), detail::to_end(s1_)); + } + + template + void insert(InputIt1 first1, InputIt1 last1) + { + scorer.insert(detail::sorted_split(first1, last1).join()); + } + + template + void similarity(double* scores, size_t score_count, InputIt2 first2, InputIt2 last2, + double score_cutoff = 0.0) const + { + scorer.similarity(scores, score_count, detail::sorted_split(first2, last2).join(), score_cutoff); + } + + template + void similarity(double* scores, size_t score_count, const Sentence2& s2, double score_cutoff = 0) const + { + similarity(scores, score_count, detail::to_begin(s2), detail::to_end(s2), score_cutoff); + } + +private: + MultiRatio scorer; +}; +} /* namespace experimental */ +#endif + +// todo CachedRatio speed for equal strings vs original implementation +// TODO documentation +template +struct CachedTokenSortRatio { + template + CachedTokenSortRatio(InputIt1 first1, InputIt1 last1) + : s1_sorted(detail::sorted_split(first1, last1).join()), cached_ratio(s1_sorted) + {} + + template + explicit CachedTokenSortRatio(const Sentence1& s1) + : CachedTokenSortRatio(detail::to_begin(s1), detail::to_end(s1)) + {} + + template + double similarity(InputIt2 first2, InputIt2 last2, double score_cutoff = 0.0, + double score_hint = 0.0) const; + + template + double similarity(const Sentence2& s2, double score_cutoff = 0.0, double score_hint = 0.0) const; + +private: + std::vector s1_sorted; + CachedRatio cached_ratio; +}; + +template +explicit CachedTokenSortRatio(const Sentence1& s1) -> CachedTokenSortRatio>; + +template +CachedTokenSortRatio(InputIt1 first1, InputIt1 last1) -> CachedTokenSortRatio>; + +/** + * @brief Sorts the words in the strings and calculates the fuzz::partial_ratio + * between them + * + * + * @tparam Sentence1 This is a string that can be converted to + * basic_string_view + * @tparam Sentence2 This is a string that can be converted to + * basic_string_view + * + * @param s1 string to compare with s2 (for type info check Template parameters + * above) + * @param s2 string to compare with s1 (for type info check Template parameters + * above) + * @param score_cutoff Optional argument for a score threshold between 0% and + * 100%. Matches with a lower score than this number will not be returned. + * Defaults to 0. + * + * @return returns the ratio between s1 and s2 or 0 when ratio < score_cutoff + */ +template +double partial_token_sort_ratio(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 0); + +template +double partial_token_sort_ratio(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + double score_cutoff = 0); + +// TODO documentation +template +struct CachedPartialTokenSortRatio { + template + CachedPartialTokenSortRatio(InputIt1 first1, InputIt1 last1) + : s1_sorted(detail::sorted_split(first1, last1).join()), cached_partial_ratio(s1_sorted) + {} + + template + explicit CachedPartialTokenSortRatio(const Sentence1& s1) + : CachedPartialTokenSortRatio(detail::to_begin(s1), detail::to_end(s1)) + {} + + template + double similarity(InputIt2 first2, InputIt2 last2, double score_cutoff = 0.0, + double score_hint = 0.0) const; + + template + double similarity(const Sentence2& s2, double score_cutoff = 0.0, double score_hint = 0.0) const; + +private: + std::vector s1_sorted; + CachedPartialRatio cached_partial_ratio; +}; + +template +explicit CachedPartialTokenSortRatio(const Sentence1& s1) + -> CachedPartialTokenSortRatio>; + +template +CachedPartialTokenSortRatio(InputIt1 first1, + InputIt1 last1) -> CachedPartialTokenSortRatio>; + +/** + * @brief Compares the words in the strings based on unique and common words + * between them using fuzz::ratio + * + * @details + * @code{.cpp} + * // score1 is 83.87 + * double score1 = token_sort_ratio("fuzzy was a bear", "fuzzy fuzzy was a + * bear") + * // score2 is 100 + * double score2 = token_set_ratio("fuzzy was a bear", "fuzzy fuzzy was a bear") + * @endcode + * + * @tparam Sentence1 This is a string that can be converted to + * basic_string_view + * @tparam Sentence2 This is a string that can be converted to + * basic_string_view + * + * @param s1 string to compare with s2 (for type info check Template parameters + * above) + * @param s2 string to compare with s1 (for type info check Template parameters + * above) + * @param score_cutoff Optional argument for a score threshold between 0% and + * 100%. Matches with a lower score than this number will not be returned. + * Defaults to 0. + * + * @return returns the ratio between s1 and s2 or 0 when ratio < score_cutoff + */ +template +double token_set_ratio(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 0); + +template +double token_set_ratio(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + double score_cutoff = 0); + +// TODO documentation +template +struct CachedTokenSetRatio { + template + CachedTokenSetRatio(InputIt1 first1, InputIt1 last1) + : s1(first1, last1), tokens_s1(detail::sorted_split(std::begin(s1), std::end(s1))) + {} + + template + explicit CachedTokenSetRatio(const Sentence1& s1_) + : CachedTokenSetRatio(detail::to_begin(s1_), detail::to_end(s1_)) + {} + + template + double similarity(InputIt2 first2, InputIt2 last2, double score_cutoff = 0.0, + double score_hint = 0.0) const; + + template + double similarity(const Sentence2& s2, double score_cutoff = 0.0, double score_hint = 0.0) const; + +private: + std::vector s1; + detail::SplittedSentenceView::iterator> tokens_s1; +}; + +template +explicit CachedTokenSetRatio(const Sentence1& s1) -> CachedTokenSetRatio>; + +template +CachedTokenSetRatio(InputIt1 first1, InputIt1 last1) -> CachedTokenSetRatio>; + +/** + * @brief Compares the words in the strings based on unique and common words + * between them using fuzz::partial_ratio + * + * @tparam Sentence1 This is a string that can be converted to + * basic_string_view + * @tparam Sentence2 This is a string that can be converted to + * basic_string_view + * + * @param s1 string to compare with s2 (for type info check Template parameters + * above) + * @param s2 string to compare with s1 (for type info check Template parameters + * above) + * @param score_cutoff Optional argument for a score threshold between 0% and + * 100%. Matches with a lower score than this number will not be returned. + * Defaults to 0. + * + * @return returns the ratio between s1 and s2 or 0 when ratio < score_cutoff + */ +template +double partial_token_set_ratio(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 0); + +template +double partial_token_set_ratio(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + double score_cutoff = 0); + +// TODO documentation +template +struct CachedPartialTokenSetRatio { + template + CachedPartialTokenSetRatio(InputIt1 first1, InputIt1 last1) + : s1(first1, last1), tokens_s1(detail::sorted_split(std::begin(s1), std::end(s1))) + {} + + template + explicit CachedPartialTokenSetRatio(const Sentence1& s1_) + : CachedPartialTokenSetRatio(detail::to_begin(s1_), detail::to_end(s1_)) + {} + + template + double similarity(InputIt2 first2, InputIt2 last2, double score_cutoff = 0.0, + double score_hint = 0.0) const; + + template + double similarity(const Sentence2& s2, double score_cutoff = 0.0, double score_hint = 0.0) const; + +private: + std::vector s1; + detail::SplittedSentenceView::iterator> tokens_s1; +}; + +template +explicit CachedPartialTokenSetRatio(const Sentence1& s1) -> CachedPartialTokenSetRatio>; + +template +CachedPartialTokenSetRatio(InputIt1 first1, + InputIt1 last1) -> CachedPartialTokenSetRatio>; + +/** + * @brief Helper method that returns the maximum of fuzz::token_set_ratio and + * fuzz::token_sort_ratio (faster than manually executing the two functions) + * + * @tparam Sentence1 This is a string that can be converted to + * basic_string_view + * @tparam Sentence2 This is a string that can be converted to + * basic_string_view + * + * @param s1 string to compare with s2 (for type info check Template parameters + * above) + * @param s2 string to compare with s1 (for type info check Template parameters + * above) + * @param score_cutoff Optional argument for a score threshold between 0% and + * 100%. Matches with a lower score than this number will not be returned. + * Defaults to 0. + * + * @return returns the ratio between s1 and s2 or 0 when ratio < score_cutoff + */ +template +double token_ratio(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 0); + +template +double token_ratio(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, double score_cutoff = 0); + +// todo add real implementation +template +struct CachedTokenRatio { + template + CachedTokenRatio(InputIt1 first1, InputIt1 last1) + : s1(first1, last1), + s1_tokens(detail::sorted_split(std::begin(s1), std::end(s1))), + s1_sorted(s1_tokens.join()), + cached_ratio_s1_sorted(s1_sorted) + {} + + template + explicit CachedTokenRatio(const Sentence1& s1_) + : CachedTokenRatio(detail::to_begin(s1_), detail::to_end(s1_)) + {} + + template + double similarity(InputIt2 first2, InputIt2 last2, double score_cutoff = 0.0, + double score_hint = 0.0) const; + + template + double similarity(const Sentence2& s2, double score_cutoff = 0.0, double score_hint = 0.0) const; + +private: + std::vector s1; + detail::SplittedSentenceView::iterator> s1_tokens; + std::vector s1_sorted; + CachedRatio cached_ratio_s1_sorted; +}; + +template +explicit CachedTokenRatio(const Sentence1& s1) -> CachedTokenRatio>; + +template +CachedTokenRatio(InputIt1 first1, InputIt1 last1) -> CachedTokenRatio>; + +/** + * @brief Helper method that returns the maximum of + * fuzz::partial_token_set_ratio and fuzz::partial_token_sort_ratio (faster than + * manually executing the two functions) + * + * @tparam Sentence1 This is a string that can be converted to + * basic_string_view + * @tparam Sentence2 This is a string that can be converted to + * basic_string_view + * + * @param s1 string to compare with s2 (for type info check Template parameters + * above) + * @param s2 string to compare with s1 (for type info check Template parameters + * above) + * @param score_cutoff Optional argument for a score threshold between 0% and + * 100%. Matches with a lower score than this number will not be returned. + * Defaults to 0. + * + * @return returns the ratio between s1 and s2 or 0 when ratio < score_cutoff + */ +template +double partial_token_ratio(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 0); + +template +double partial_token_ratio(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + double score_cutoff = 0); + +// todo add real implementation +template +struct CachedPartialTokenRatio { + template + CachedPartialTokenRatio(InputIt1 first1, InputIt1 last1) + : s1(first1, last1), + tokens_s1(detail::sorted_split(std::begin(s1), std::end(s1))), + s1_sorted(tokens_s1.join()) + {} + + template + explicit CachedPartialTokenRatio(const Sentence1& s1_) + : CachedPartialTokenRatio(detail::to_begin(s1_), detail::to_end(s1_)) + {} + + template + double similarity(InputIt2 first2, InputIt2 last2, double score_cutoff = 0.0, + double score_hint = 0.0) const; + + template + double similarity(const Sentence2& s2, double score_cutoff = 0.0, double score_hint = 0.0) const; + +private: + std::vector s1; + detail::SplittedSentenceView::iterator> tokens_s1; + std::vector s1_sorted; +}; + +template +explicit CachedPartialTokenRatio(const Sentence1& s1) -> CachedPartialTokenRatio>; + +template +CachedPartialTokenRatio(InputIt1 first1, InputIt1 last1) -> CachedPartialTokenRatio>; + +/** + * @brief Calculates a weighted ratio based on the other ratio algorithms + * + * @details + * @todo add a detailed description + * + * @tparam Sentence1 This is a string that can be converted to + * basic_string_view + * @tparam Sentence2 This is a string that can be converted to + * basic_string_view + * + * @param s1 string to compare with s2 (for type info check Template parameters + * above) + * @param s2 string to compare with s1 (for type info check Template parameters + * above) + * @param score_cutoff Optional argument for a score threshold between 0% and + * 100%. Matches with a lower score than this number will not be returned. + * Defaults to 0. + * + * @return returns the ratio between s1 and s2 or 0 when ratio < score_cutoff + */ +template +double WRatio(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 0); + +template +double WRatio(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, double score_cutoff = 0); + +// todo add real implementation +template +struct CachedWRatio { + template + explicit CachedWRatio(InputIt1 first1, InputIt1 last1); + + template + CachedWRatio(const Sentence1& s1_) : CachedWRatio(detail::to_begin(s1_), detail::to_end(s1_)) + {} + + template + double similarity(InputIt2 first2, InputIt2 last2, double score_cutoff = 0.0, + double score_hint = 0.0) const; + + template + double similarity(const Sentence2& s2, double score_cutoff = 0.0, double score_hint = 0.0) const; + +private: + // todo somehow implement this using other ratios with creating PatternMatchVector + // multiple times + std::vector s1; + CachedPartialRatio cached_partial_ratio; + detail::SplittedSentenceView::iterator> tokens_s1; + std::vector s1_sorted; + rapidfuzz::detail::BlockPatternMatchVector blockmap_s1_sorted; +}; + +template +explicit CachedWRatio(const Sentence1& s1) -> CachedWRatio>; + +template +CachedWRatio(InputIt1 first1, InputIt1 last1) -> CachedWRatio>; + +/** + * @brief Calculates a quick ratio between two strings using fuzz.ratio + * + * @details + * @todo add a detailed description + * + * @tparam Sentence1 This is a string that can be converted to + * basic_string_view + * @tparam Sentence2 This is a string that can be converted to + * basic_string_view + * + * @param s1 string to compare with s2 (for type info check Template parameters + * above) + * @param s2 string to compare with s1 (for type info check Template parameters + * above) + * @param score_cutoff Optional argument for a score threshold between 0% and + * 100%. Matches with a lower score than this number will not be returned. + * Defaults to 0. + * + * @return returns the ratio between s1 and s2 or 0 when ratio < score_cutoff + */ +template +double QRatio(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 0); + +template +double QRatio(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, double score_cutoff = 0); + +#ifdef RAPIDFUZZ_SIMD +namespace experimental { +template +struct MultiQRatio { +public: + MultiQRatio(size_t count) : scorer(count) + {} + + size_t result_count() const + { + return scorer.result_count(); + } + + template + void insert(const Sentence1& s1_) + { + insert(detail::to_begin(s1_), detail::to_end(s1_)); + } + + template + void insert(InputIt1 first1, InputIt1 last1) + { + scorer.insert(first1, last1); + str_lens.push_back(static_cast(std::distance(first1, last1))); + } + + template + void similarity(double* scores, size_t score_count, InputIt2 first2, InputIt2 last2, + double score_cutoff = 0.0) const + { + similarity(scores, score_count, detail::Range(first2, last2), score_cutoff); + } + + template + void similarity(double* scores, size_t score_count, const Sentence2& s2, double score_cutoff = 0) const + { + rapidfuzz::detail::Range s2_(s2); + if (s2_.empty()) { + for (size_t i = 0; i < str_lens.size(); ++i) + scores[i] = 0; + + return; + } + + scorer.similarity(scores, score_count, s2, score_cutoff); + + for (size_t i = 0; i < str_lens.size(); ++i) + if (str_lens[i] == 0) scores[i] = 0; + } + +private: + std::vector str_lens; + MultiRatio scorer; +}; +} /* namespace experimental */ +#endif + +template +struct CachedQRatio { + template + CachedQRatio(InputIt1 first1, InputIt1 last1) : s1(first1, last1), cached_ratio(first1, last1) + {} + + template + explicit CachedQRatio(const Sentence1& s1_) : CachedQRatio(detail::to_begin(s1_), detail::to_end(s1_)) + {} + + template + double similarity(InputIt2 first2, InputIt2 last2, double score_cutoff = 0.0, + double score_hint = 0.0) const; + + template + double similarity(const Sentence2& s2, double score_cutoff = 0.0, double score_hint = 0.0) const; + +private: + std::vector s1; + CachedRatio cached_ratio; +}; + +template +explicit CachedQRatio(const Sentence1& s1) -> CachedQRatio>; + +template +CachedQRatio(InputIt1 first1, InputIt1 last1) -> CachedQRatio>; + +/**@}*/ + +} // namespace rapidfuzz::fuzz + +#include diff --git a/src/external/rapidfuzz-cpp/rapidfuzz/fuzz_impl.hpp b/src/external/rapidfuzz-cpp/rapidfuzz/fuzz_impl.hpp new file mode 100644 index 00000000..1d2eb463 --- /dev/null +++ b/src/external/rapidfuzz-cpp/rapidfuzz/fuzz_impl.hpp @@ -0,0 +1,937 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright © 2021-present Max Bachmann */ +/* Copyright © 2011 Adam Cohen */ + +#include +#include + +#include +#include +#include +#include +#include + +namespace rapidfuzz::fuzz { + +/********************************************** + * ratio + *********************************************/ + +template +double ratio(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, double score_cutoff) +{ + return ratio(detail::Range(first1, last1), detail::Range(first2, last2), score_cutoff); +} + +template +double ratio(const Sentence1& s1, const Sentence2& s2, const double score_cutoff) +{ + return indel_normalized_similarity(s1, s2, score_cutoff / 100) * 100; +} + +template +template +double CachedRatio::similarity(InputIt2 first2, InputIt2 last2, double score_cutoff, + double score_hint) const +{ + return similarity(detail::Range(first2, last2), score_cutoff, score_hint); +} + +template +template +double CachedRatio::similarity(const Sentence2& s2, double score_cutoff, double score_hint) const +{ + return cached_indel.normalized_similarity(s2, score_cutoff / 100, score_hint / 100) * 100; +} + +/********************************************** + * partial_ratio + *********************************************/ + +namespace fuzz_detail { + +static constexpr double norm_distance(size_t dist, size_t lensum, double score_cutoff = 0) +{ + double score = + (lensum > 0) ? (100.0 - 100.0 * static_cast(dist) / static_cast(lensum)) : 100.0; + + return (score >= score_cutoff) ? score : 0; +} + +static inline size_t score_cutoff_to_distance(double score_cutoff, size_t lensum) +{ + return static_cast(std::ceil(static_cast(lensum) * (1.0 - score_cutoff / 100))); +} + +template +ScoreAlignment +partial_ratio_impl(const detail::Range& s1, const detail::Range& s2, + const CachedRatio& cached_ratio, + const detail::CharSet>& s1_char_set, double score_cutoff) +{ + ScoreAlignment res; + size_t len1 = s1.size(); + size_t len2 = s2.size(); + res.src_start = 0; + res.src_end = len1; + res.dest_start = 0; + res.dest_end = len1; + + if (len2 > len1) { + size_t maximum = len1 * 2; + double norm_cutoff_sim = rapidfuzz::detail::NormSim_to_NormDist(score_cutoff / 100); + size_t cutoff_dist = static_cast(std::ceil(static_cast(maximum) * norm_cutoff_sim)); + size_t best_dist = std::numeric_limits::max(); + std::vector scores(len2 - len1, std::numeric_limits::max()); + std::vector> windows = {{0, len2 - len1 - 1}}; + std::vector> new_windows; + + while (!windows.empty()) { + for (const auto& window : windows) { + auto subseq1_first = s2.begin() + static_cast(window.first); + auto subseq2_first = s2.begin() + static_cast(window.second); + detail::Range subseq1(subseq1_first, subseq1_first + static_cast(len1)); + detail::Range subseq2(subseq2_first, subseq2_first + static_cast(len1)); + + if (scores[window.first] == std::numeric_limits::max()) { + scores[window.first] = cached_ratio.cached_indel.distance(subseq1); + if (scores[window.first] < cutoff_dist) { + cutoff_dist = best_dist = scores[window.first]; + res.dest_start = window.first; + res.dest_end = window.first + len1; + if (best_dist == 0) { + res.score = 100; + return res; + } + } + } + if (scores[window.second] == std::numeric_limits::max()) { + scores[window.second] = cached_ratio.cached_indel.distance(subseq2); + if (scores[window.second] < cutoff_dist) { + cutoff_dist = best_dist = scores[window.second]; + res.dest_start = window.second; + res.dest_end = window.second + len1; + if (best_dist == 0) { + res.score = 100; + return res; + } + } + } + + size_t cell_diff = window.second - window.first; + if (cell_diff == 1) continue; + + /* find the minimum score possible in the range first <-> last */ + size_t known_edits = detail::abs_diff(scores[window.first], scores[window.second]); + /* half of the cells that are not needed for known_edits can lead to a better score */ + size_t max_score_improvement = (cell_diff - known_edits / 2) / 2 * 2; + ptrdiff_t min_score = + static_cast(std::min(scores[window.first], scores[window.second])) - + static_cast(max_score_improvement); + if (min_score < static_cast(cutoff_dist)) { + size_t center = cell_diff / 2; + new_windows.emplace_back(window.first, window.first + center); + new_windows.emplace_back(window.first + center, window.second); + } + } + + std::swap(windows, new_windows); + new_windows.clear(); + } + + double score = 1.0 - (static_cast(best_dist) / static_cast(maximum)); + score *= 100; + if (score >= score_cutoff) score_cutoff = res.score = score; + } + + for (size_t i = 1; i < len1; ++i) { + rapidfuzz::detail::Range subseq(s2.begin(), s2.begin() + static_cast(i)); + if (!s1_char_set.find(subseq.back())) continue; + + double ls_ratio = cached_ratio.similarity(subseq, score_cutoff); + if (ls_ratio > res.score) { + score_cutoff = res.score = ls_ratio; + res.dest_start = 0; + res.dest_end = i; + if (res.score == 100.0) return res; + } + } + + for (size_t i = len2 - len1; i < len2; ++i) { + rapidfuzz::detail::Range subseq(s2.begin() + static_cast(i), s2.end()); + if (!s1_char_set.find(subseq.front())) continue; + + double ls_ratio = cached_ratio.similarity(subseq, score_cutoff); + if (ls_ratio > res.score) { + score_cutoff = res.score = ls_ratio; + res.dest_start = i; + res.dest_end = len2; + if (res.score == 100.0) return res; + } + } + + return res; +} + +template > +ScoreAlignment partial_ratio_impl(const detail::Range& s1, + const detail::Range& s2, double score_cutoff) +{ + CachedRatio cached_ratio(s1); + + detail::CharSet s1_char_set; + for (auto ch : s1) + s1_char_set.insert(ch); + + return partial_ratio_impl(s1, s2, cached_ratio, s1_char_set, score_cutoff); +} + +} // namespace fuzz_detail + +template +ScoreAlignment partial_ratio_alignment(InputIt1 first1, InputIt1 last1, InputIt2 first2, + InputIt2 last2, double score_cutoff) +{ + size_t len1 = static_cast(std::distance(first1, last1)); + size_t len2 = static_cast(std::distance(first2, last2)); + + if (len1 > len2) { + ScoreAlignment result = partial_ratio_alignment(first2, last2, first1, last1, score_cutoff); + std::swap(result.src_start, result.dest_start); + std::swap(result.src_end, result.dest_end); + return result; + } + + if (score_cutoff > 100) return ScoreAlignment(0, 0, len1, 0, len1); + + if (!len1 || !len2) + return ScoreAlignment(static_cast(len1 == len2) * 100.0, 0, len1, 0, len1); + + auto s1 = detail::Range(first1, last1); + auto s2 = detail::Range(first2, last2); + + auto alignment = fuzz_detail::partial_ratio_impl(s1, s2, score_cutoff); + if (alignment.score != 100 && s1.size() == s2.size()) { + score_cutoff = std::max(score_cutoff, alignment.score); + auto alignment2 = fuzz_detail::partial_ratio_impl(s2, s1, score_cutoff); + if (alignment2.score > alignment.score) { + std::swap(alignment2.src_start, alignment2.dest_start); + std::swap(alignment2.src_end, alignment2.dest_end); + return alignment2; + } + } + + return alignment; +} + +template +ScoreAlignment partial_ratio_alignment(const Sentence1& s1, const Sentence2& s2, double score_cutoff) +{ + return partial_ratio_alignment(detail::to_begin(s1), detail::to_end(s1), detail::to_begin(s2), + detail::to_end(s2), score_cutoff); +} + +template +double partial_ratio(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, double score_cutoff) +{ + return partial_ratio_alignment(first1, last1, first2, last2, score_cutoff).score; +} + +template +double partial_ratio(const Sentence1& s1, const Sentence2& s2, double score_cutoff) +{ + return partial_ratio_alignment(s1, s2, score_cutoff).score; +} + +template +template +CachedPartialRatio::CachedPartialRatio(InputIt1 first1, InputIt1 last1) + : s1(first1, last1), cached_ratio(first1, last1) +{ + for (const auto& ch : s1) + s1_char_set.insert(ch); +} + +template +template +double CachedPartialRatio::similarity(InputIt2 first2, InputIt2 last2, double score_cutoff, + [[maybe_unused]] double score_hint) const +{ + size_t len1 = s1.size(); + size_t len2 = static_cast(std::distance(first2, last2)); + + if (len1 > len2) + return partial_ratio(detail::to_begin(s1), detail::to_end(s1), first2, last2, score_cutoff); + + if (score_cutoff > 100) return 0; + + if (!len1 || !len2) return static_cast(len1 == len2) * 100.0; + + auto s1_ = detail::Range(s1); + auto s2 = detail::Range(first2, last2); + + double score = fuzz_detail::partial_ratio_impl(s1_, s2, cached_ratio, s1_char_set, score_cutoff).score; + if (score != 100 && s1_.size() == s2.size()) { + score_cutoff = std::max(score_cutoff, score); + double score2 = fuzz_detail::partial_ratio_impl(s2, s1_, score_cutoff).score; + if (score2 > score) return score2; + } + + return score; +} + +template +template +double CachedPartialRatio::similarity(const Sentence2& s2, double score_cutoff, + [[maybe_unused]] double score_hint) const +{ + return similarity(detail::to_begin(s2), detail::to_end(s2), score_cutoff); +} + +/********************************************** + * token_sort_ratio + *********************************************/ +template +double token_sort_ratio(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, double score_cutoff) +{ + if (score_cutoff > 100) return 0; + + return ratio(detail::sorted_split(first1, last1).join(), detail::sorted_split(first2, last2).join(), + score_cutoff); +} + +template +double token_sort_ratio(const Sentence1& s1, const Sentence2& s2, double score_cutoff) +{ + return token_sort_ratio(detail::to_begin(s1), detail::to_end(s1), detail::to_begin(s2), + detail::to_end(s2), score_cutoff); +} + +template +template +double CachedTokenSortRatio::similarity(InputIt2 first2, InputIt2 last2, double score_cutoff, + [[maybe_unused]] double score_hint) const +{ + if (score_cutoff > 100) return 0; + + return cached_ratio.similarity(detail::sorted_split(first2, last2).join(), score_cutoff); +} + +template +template +double CachedTokenSortRatio::similarity(const Sentence2& s2, double score_cutoff, + [[maybe_unused]] double score_hint) const +{ + return similarity(detail::to_begin(s2), detail::to_end(s2), score_cutoff); +} + +/********************************************** + * partial_token_sort_ratio + *********************************************/ + +template +double partial_token_sort_ratio(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + double score_cutoff) +{ + if (score_cutoff > 100) return 0; + + return partial_ratio(detail::sorted_split(first1, last1).join(), + detail::sorted_split(first2, last2).join(), score_cutoff); +} + +template +double partial_token_sort_ratio(const Sentence1& s1, const Sentence2& s2, double score_cutoff) +{ + return partial_token_sort_ratio(detail::to_begin(s1), detail::to_end(s1), detail::to_begin(s2), + detail::to_end(s2), score_cutoff); +} + +template +template +double CachedPartialTokenSortRatio::similarity(InputIt2 first2, InputIt2 last2, double score_cutoff, + [[maybe_unused]] double score_hint) const +{ + if (score_cutoff > 100) return 0; + + return cached_partial_ratio.similarity(detail::sorted_split(first2, last2).join(), score_cutoff); +} + +template +template +double CachedPartialTokenSortRatio::similarity(const Sentence2& s2, double score_cutoff, + [[maybe_unused]] double score_hint) const +{ + return similarity(detail::to_begin(s2), detail::to_end(s2), score_cutoff); +} + +/********************************************** + * token_set_ratio + *********************************************/ + +namespace fuzz_detail { +template +double token_set_ratio(const rapidfuzz::detail::SplittedSentenceView& tokens_a, + const rapidfuzz::detail::SplittedSentenceView& tokens_b, + const double score_cutoff) +{ + /* in FuzzyWuzzy this returns 0. For sake of compatibility return 0 here as well + * see https://github.com/rapidfuzz/RapidFuzz/issues/110 */ + if (tokens_a.empty() || tokens_b.empty()) return 0; + + auto decomposition = detail::set_decomposition(tokens_a, tokens_b); + auto intersect = decomposition.intersection; + auto diff_ab = decomposition.difference_ab; + auto diff_ba = decomposition.difference_ba; + + // one sentence is part of the other one + if (!intersect.empty() && (diff_ab.empty() || diff_ba.empty())) return 100; + + auto diff_ab_joined = diff_ab.join(); + auto diff_ba_joined = diff_ba.join(); + + size_t ab_len = diff_ab_joined.size(); + size_t ba_len = diff_ba_joined.size(); + size_t sect_len = intersect.length(); + + // string length sect+ab <-> sect and sect+ba <-> sect + size_t sect_ab_len = sect_len + bool(sect_len) + ab_len; + size_t sect_ba_len = sect_len + bool(sect_len) + ba_len; + + double result = 0; + size_t cutoff_distance = score_cutoff_to_distance(score_cutoff, sect_ab_len + sect_ba_len); + size_t dist = indel_distance(diff_ab_joined, diff_ba_joined, cutoff_distance); + + if (dist <= cutoff_distance) result = norm_distance(dist, sect_ab_len + sect_ba_len, score_cutoff); + + // exit early since the other ratios are 0 + if (!sect_len) return result; + + // levenshtein distance sect+ab <-> sect and sect+ba <-> sect + // since only sect is similar in them the distance can be calculated based on + // the length difference + size_t sect_ab_dist = bool(sect_len) + ab_len; + double sect_ab_ratio = norm_distance(sect_ab_dist, sect_len + sect_ab_len, score_cutoff); + + size_t sect_ba_dist = bool(sect_len) + ba_len; + double sect_ba_ratio = norm_distance(sect_ba_dist, sect_len + sect_ba_len, score_cutoff); + + return std::max({result, sect_ab_ratio, sect_ba_ratio}); +} +} // namespace fuzz_detail + +template +double token_set_ratio(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, double score_cutoff) +{ + if (score_cutoff > 100) return 0; + + return fuzz_detail::token_set_ratio(detail::sorted_split(first1, last1), + detail::sorted_split(first2, last2), score_cutoff); +} + +template +double token_set_ratio(const Sentence1& s1, const Sentence2& s2, double score_cutoff) +{ + return token_set_ratio(detail::to_begin(s1), detail::to_end(s1), detail::to_begin(s2), detail::to_end(s2), + score_cutoff); +} + +template +template +double CachedTokenSetRatio::similarity(InputIt2 first2, InputIt2 last2, double score_cutoff, + [[maybe_unused]] double score_hint) const +{ + if (score_cutoff > 100) return 0; + + return fuzz_detail::token_set_ratio(tokens_s1, detail::sorted_split(first2, last2), score_cutoff); +} + +template +template +double CachedTokenSetRatio::similarity(const Sentence2& s2, double score_cutoff, + [[maybe_unused]] double score_hint) const +{ + return similarity(detail::to_begin(s2), detail::to_end(s2), score_cutoff); +} + +/********************************************** + * partial_token_set_ratio + *********************************************/ + +namespace fuzz_detail { +template +double partial_token_set_ratio(const rapidfuzz::detail::SplittedSentenceView& tokens_a, + const rapidfuzz::detail::SplittedSentenceView& tokens_b, + const double score_cutoff) +{ + /* in FuzzyWuzzy this returns 0. For sake of compatibility return 0 here as well + * see https://github.com/rapidfuzz/RapidFuzz/issues/110 */ + if (tokens_a.empty() || tokens_b.empty()) return 0; + + auto decomposition = detail::set_decomposition(tokens_a, tokens_b); + + // exit early when there is a common word in both sequences + if (!decomposition.intersection.empty()) return 100; + + return partial_ratio(decomposition.difference_ab.join(), decomposition.difference_ba.join(), + score_cutoff); +} +} // namespace fuzz_detail + +template +double partial_token_set_ratio(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + double score_cutoff) +{ + if (score_cutoff > 100) return 0; + + return fuzz_detail::partial_token_set_ratio(detail::sorted_split(first1, last1), + detail::sorted_split(first2, last2), score_cutoff); +} + +template +double partial_token_set_ratio(const Sentence1& s1, const Sentence2& s2, double score_cutoff) +{ + return partial_token_set_ratio(detail::to_begin(s1), detail::to_end(s1), detail::to_begin(s2), + detail::to_end(s2), score_cutoff); +} + +template +template +double CachedPartialTokenSetRatio::similarity(InputIt2 first2, InputIt2 last2, double score_cutoff, + [[maybe_unused]] double score_hint) const +{ + if (score_cutoff > 100) return 0; + + return fuzz_detail::partial_token_set_ratio(tokens_s1, detail::sorted_split(first2, last2), score_cutoff); +} + +template +template +double CachedPartialTokenSetRatio::similarity(const Sentence2& s2, double score_cutoff, + [[maybe_unused]] double score_hint) const +{ + return similarity(detail::to_begin(s2), detail::to_end(s2), score_cutoff); +} + +/********************************************** + * token_ratio + *********************************************/ + +template +double token_ratio(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, double score_cutoff) +{ + if (score_cutoff > 100) return 0; + + auto tokens_a = detail::sorted_split(first1, last1); + auto tokens_b = detail::sorted_split(first2, last2); + + auto decomposition = detail::set_decomposition(tokens_a, tokens_b); + auto intersect = decomposition.intersection; + auto diff_ab = decomposition.difference_ab; + auto diff_ba = decomposition.difference_ba; + + if (!intersect.empty() && (diff_ab.empty() || diff_ba.empty())) return 100; + + auto diff_ab_joined = diff_ab.join(); + auto diff_ba_joined = diff_ba.join(); + + size_t ab_len = diff_ab_joined.size(); + size_t ba_len = diff_ba_joined.size(); + size_t sect_len = intersect.length(); + + double result = ratio(tokens_a.join(), tokens_b.join(), score_cutoff); + + // string length sect+ab <-> sect and sect+ba <-> sect + size_t sect_ab_len = sect_len + bool(sect_len) + ab_len; + size_t sect_ba_len = sect_len + bool(sect_len) + ba_len; + + size_t cutoff_distance = fuzz_detail::score_cutoff_to_distance(score_cutoff, sect_ab_len + sect_ba_len); + size_t dist = indel_distance(diff_ab_joined, diff_ba_joined, cutoff_distance); + if (dist <= cutoff_distance) + result = std::max(result, fuzz_detail::norm_distance(dist, sect_ab_len + sect_ba_len, score_cutoff)); + + // exit early since the other ratios are 0 + if (!sect_len) return result; + + // levenshtein distance sect+ab <-> sect and sect+ba <-> sect + // since only sect is similar in them the distance can be calculated based on + // the length difference + size_t sect_ab_dist = bool(sect_len) + ab_len; + double sect_ab_ratio = fuzz_detail::norm_distance(sect_ab_dist, sect_len + sect_ab_len, score_cutoff); + + size_t sect_ba_dist = bool(sect_len) + ba_len; + double sect_ba_ratio = fuzz_detail::norm_distance(sect_ba_dist, sect_len + sect_ba_len, score_cutoff); + + return std::max({result, sect_ab_ratio, sect_ba_ratio}); +} + +template +double token_ratio(const Sentence1& s1, const Sentence2& s2, double score_cutoff) +{ + return token_ratio(detail::to_begin(s1), detail::to_end(s1), detail::to_begin(s2), detail::to_end(s2), + score_cutoff); +} + +namespace fuzz_detail { +template +double token_ratio(const rapidfuzz::detail::SplittedSentenceView& s1_tokens, + const CachedRatio& cached_ratio_s1_sorted, InputIt2 first2, InputIt2 last2, + double score_cutoff) +{ + if (score_cutoff > 100) return 0; + + auto s2_tokens = detail::sorted_split(first2, last2); + + auto decomposition = detail::set_decomposition(s1_tokens, s2_tokens); + auto intersect = decomposition.intersection; + auto diff_ab = decomposition.difference_ab; + auto diff_ba = decomposition.difference_ba; + + if (!intersect.empty() && (diff_ab.empty() || diff_ba.empty())) return 100; + + auto diff_ab_joined = diff_ab.join(); + auto diff_ba_joined = diff_ba.join(); + + size_t ab_len = diff_ab_joined.size(); + size_t ba_len = diff_ba_joined.size(); + size_t sect_len = intersect.length(); + + double result = cached_ratio_s1_sorted.similarity(s2_tokens.join(), score_cutoff); + + // string length sect+ab <-> sect and sect+ba <-> sect + size_t sect_ab_len = sect_len + bool(sect_len) + ab_len; + size_t sect_ba_len = sect_len + bool(sect_len) + ba_len; + + size_t cutoff_distance = score_cutoff_to_distance(score_cutoff, sect_ab_len + sect_ba_len); + size_t dist = indel_distance(diff_ab_joined, diff_ba_joined, cutoff_distance); + if (dist <= cutoff_distance) + result = std::max(result, norm_distance(dist, sect_ab_len + sect_ba_len, score_cutoff)); + + // exit early since the other ratios are 0 + if (!sect_len) return result; + + // levenshtein distance sect+ab <-> sect and sect+ba <-> sect + // since only sect is similar in them the distance can be calculated based on + // the length difference + size_t sect_ab_dist = bool(sect_len) + ab_len; + double sect_ab_ratio = norm_distance(sect_ab_dist, sect_len + sect_ab_len, score_cutoff); + + size_t sect_ba_dist = bool(sect_len) + ba_len; + double sect_ba_ratio = norm_distance(sect_ba_dist, sect_len + sect_ba_len, score_cutoff); + + return std::max({result, sect_ab_ratio, sect_ba_ratio}); +} + +// todo this is a temporary solution until WRatio is properly implemented using other scorers +template +double token_ratio(const std::vector& s1_sorted, + const rapidfuzz::detail::SplittedSentenceView& tokens_s1, + const detail::BlockPatternMatchVector& blockmap_s1_sorted, InputIt2 first2, InputIt2 last2, + double score_cutoff) +{ + if (score_cutoff > 100) return 0; + + auto tokens_b = detail::sorted_split(first2, last2); + + auto decomposition = detail::set_decomposition(tokens_s1, tokens_b); + auto intersect = decomposition.intersection; + auto diff_ab = decomposition.difference_ab; + auto diff_ba = decomposition.difference_ba; + + if (!intersect.empty() && (diff_ab.empty() || diff_ba.empty())) return 100; + + auto diff_ab_joined = diff_ab.join(); + auto diff_ba_joined = diff_ba.join(); + + size_t ab_len = diff_ab_joined.size(); + size_t ba_len = diff_ba_joined.size(); + size_t sect_len = intersect.length(); + + double result = 0; + auto s2_sorted = tokens_b.join(); + if (s1_sorted.size() < 65) { + double norm_sim = detail::indel_normalized_similarity(blockmap_s1_sorted, detail::Range(s1_sorted), + detail::Range(s2_sorted), score_cutoff / 100); + result = norm_sim * 100; + } + else { + result = fuzz::ratio(s1_sorted, s2_sorted, score_cutoff); + } + + // string length sect+ab <-> sect and sect+ba <-> sect + size_t sect_ab_len = sect_len + bool(sect_len) + ab_len; + size_t sect_ba_len = sect_len + bool(sect_len) + ba_len; + + size_t cutoff_distance = score_cutoff_to_distance(score_cutoff, sect_ab_len + sect_ba_len); + size_t dist = indel_distance(diff_ab_joined, diff_ba_joined, cutoff_distance); + if (dist <= cutoff_distance) + result = std::max(result, norm_distance(dist, sect_ab_len + sect_ba_len, score_cutoff)); + + // exit early since the other ratios are 0 + if (!sect_len) return result; + + // levenshtein distance sect+ab <-> sect and sect+ba <-> sect + // since only sect is similar in them the distance can be calculated based on + // the length difference + size_t sect_ab_dist = bool(sect_len) + ab_len; + double sect_ab_ratio = norm_distance(sect_ab_dist, sect_len + sect_ab_len, score_cutoff); + + size_t sect_ba_dist = bool(sect_len) + ba_len; + double sect_ba_ratio = norm_distance(sect_ba_dist, sect_len + sect_ba_len, score_cutoff); + + return std::max({result, sect_ab_ratio, sect_ba_ratio}); +} +} // namespace fuzz_detail + +template +template +double CachedTokenRatio::similarity(InputIt2 first2, InputIt2 last2, double score_cutoff, + [[maybe_unused]] double score_hint) const +{ + return fuzz_detail::token_ratio(s1_tokens, cached_ratio_s1_sorted, first2, last2, score_cutoff); +} + +template +template +double CachedTokenRatio::similarity(const Sentence2& s2, double score_cutoff, + [[maybe_unused]] double score_hint) const +{ + return similarity(detail::to_begin(s2), detail::to_end(s2), score_cutoff); +} + +/********************************************** + * partial_token_ratio + *********************************************/ + +template +double partial_token_ratio(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + double score_cutoff) +{ + if (score_cutoff > 100) return 0; + + auto tokens_a = detail::sorted_split(first1, last1); + auto tokens_b = detail::sorted_split(first2, last2); + + auto decomposition = detail::set_decomposition(tokens_a, tokens_b); + + // exit early when there is a common word in both sequences + if (!decomposition.intersection.empty()) return 100; + + auto diff_ab = decomposition.difference_ab; + auto diff_ba = decomposition.difference_ba; + + double result = partial_ratio(tokens_a.join(), tokens_b.join(), score_cutoff); + + // do not calculate the same partial_ratio twice + if (tokens_a.word_count() == diff_ab.word_count() && tokens_b.word_count() == diff_ba.word_count()) { + return result; + } + + score_cutoff = std::max(score_cutoff, result); + return std::max(result, partial_ratio(diff_ab.join(), diff_ba.join(), score_cutoff)); +} + +template +double partial_token_ratio(const Sentence1& s1, const Sentence2& s2, double score_cutoff) +{ + return partial_token_ratio(detail::to_begin(s1), detail::to_end(s1), detail::to_begin(s2), + detail::to_end(s2), score_cutoff); +} + +namespace fuzz_detail { +template +double partial_token_ratio(const std::vector& s1_sorted, + const rapidfuzz::detail::SplittedSentenceView& tokens_s1, + InputIt2 first2, InputIt2 last2, double score_cutoff) +{ + if (score_cutoff > 100) return 0; + + auto tokens_b = detail::sorted_split(first2, last2); + + auto decomposition = detail::set_decomposition(tokens_s1, tokens_b); + + // exit early when there is a common word in both sequences + if (!decomposition.intersection.empty()) return 100; + + auto diff_ab = decomposition.difference_ab; + auto diff_ba = decomposition.difference_ba; + + double result = partial_ratio(s1_sorted, tokens_b.join(), score_cutoff); + + // do not calculate the same partial_ratio twice + if (tokens_s1.word_count() == diff_ab.word_count() && tokens_b.word_count() == diff_ba.word_count()) { + return result; + } + + score_cutoff = std::max(score_cutoff, result); + return std::max(result, partial_ratio(diff_ab.join(), diff_ba.join(), score_cutoff)); +} + +} // namespace fuzz_detail + +template +template +double CachedPartialTokenRatio::similarity(InputIt2 first2, InputIt2 last2, double score_cutoff, + [[maybe_unused]] double score_hint) const +{ + return fuzz_detail::partial_token_ratio(s1_sorted, tokens_s1, first2, last2, score_cutoff); +} + +template +template +double CachedPartialTokenRatio::similarity(const Sentence2& s2, double score_cutoff, + [[maybe_unused]] double score_hint) const +{ + return similarity(detail::to_begin(s2), detail::to_end(s2), score_cutoff); +} + +/********************************************** + * WRatio + *********************************************/ + +template +double WRatio(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, double score_cutoff) +{ + if (score_cutoff > 100) return 0; + + constexpr double UNBASE_SCALE = 0.95; + + auto len1 = std::distance(first1, last1); + auto len2 = std::distance(first2, last2); + + /* in FuzzyWuzzy this returns 0. For sake of compatibility return 0 here as well + * see https://github.com/rapidfuzz/RapidFuzz/issues/110 */ + if (!len1 || !len2) return 0; + + double len_ratio = (len1 > len2) ? static_cast(len1) / static_cast(len2) + : static_cast(len2) / static_cast(len1); + + double end_ratio = ratio(first1, last1, first2, last2, score_cutoff); + + if (len_ratio < 1.5) { + score_cutoff = std::max(score_cutoff, end_ratio) / UNBASE_SCALE; + return std::max(end_ratio, token_ratio(first1, last1, first2, last2, score_cutoff) * UNBASE_SCALE); + } + + const double PARTIAL_SCALE = (len_ratio < 8.0) ? 0.9 : 0.6; + + score_cutoff = std::max(score_cutoff, end_ratio) / PARTIAL_SCALE; + end_ratio = + std::max(end_ratio, partial_ratio(first1, last1, first2, last2, score_cutoff) * PARTIAL_SCALE); + + score_cutoff = std::max(score_cutoff, end_ratio) / UNBASE_SCALE; + return std::max(end_ratio, partial_token_ratio(first1, last1, first2, last2, score_cutoff) * + UNBASE_SCALE * PARTIAL_SCALE); +} + +template +double WRatio(const Sentence1& s1, const Sentence2& s2, double score_cutoff) +{ + return WRatio(detail::to_begin(s1), detail::to_end(s1), detail::to_begin(s2), detail::to_end(s2), + score_cutoff); +} + +template +template +CachedWRatio::CachedWRatio(InputIt1 first1, InputIt1 last1) + : s1(first1, last1), + cached_partial_ratio(first1, last1), + tokens_s1(detail::sorted_split(std::begin(s1), std::end(s1))), + s1_sorted(tokens_s1.join()), + blockmap_s1_sorted(detail::Range(s1_sorted)) +{} + +template +template +double CachedWRatio::similarity(InputIt2 first2, InputIt2 last2, double score_cutoff, + [[maybe_unused]] double score_hint) const +{ + if (score_cutoff > 100) return 0; + + constexpr double UNBASE_SCALE = 0.95; + + size_t len1 = s1.size(); + size_t len2 = static_cast(std::distance(first2, last2)); + + /* in FuzzyWuzzy this returns 0. For sake of compatibility return 0 here as well + * see https://github.com/rapidfuzz/RapidFuzz/issues/110 */ + if (!len1 || !len2) return 0; + + double len_ratio = (len1 > len2) ? static_cast(len1) / static_cast(len2) + : static_cast(len2) / static_cast(len1); + + double end_ratio = cached_partial_ratio.cached_ratio.similarity(first2, last2, score_cutoff); + + if (len_ratio < 1.5) { + score_cutoff = std::max(score_cutoff, end_ratio) / UNBASE_SCALE; + // use pre calculated values + auto r = + fuzz_detail::token_ratio(s1_sorted, tokens_s1, blockmap_s1_sorted, first2, last2, score_cutoff); + return std::max(end_ratio, r * UNBASE_SCALE); + } + + const double PARTIAL_SCALE = (len_ratio < 8.0) ? 0.9 : 0.6; + + score_cutoff = std::max(score_cutoff, end_ratio) / PARTIAL_SCALE; + end_ratio = + std::max(end_ratio, cached_partial_ratio.similarity(first2, last2, score_cutoff) * PARTIAL_SCALE); + + score_cutoff = std::max(score_cutoff, end_ratio) / UNBASE_SCALE; + auto r = fuzz_detail::partial_token_ratio(s1_sorted, tokens_s1, first2, last2, score_cutoff); + return std::max(end_ratio, r * UNBASE_SCALE * PARTIAL_SCALE); +} + +template +template +double CachedWRatio::similarity(const Sentence2& s2, double score_cutoff, + [[maybe_unused]] double score_hint) const +{ + return similarity(detail::to_begin(s2), detail::to_end(s2), score_cutoff); +} + +/********************************************** + * QRatio + *********************************************/ + +template +double QRatio(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, double score_cutoff) +{ + ptrdiff_t len1 = std::distance(first1, last1); + ptrdiff_t len2 = std::distance(first2, last2); + + /* in FuzzyWuzzy this returns 0. For sake of compatibility return 0 here as well + * see https://github.com/rapidfuzz/RapidFuzz/issues/110 */ + if (!len1 || !len2) return 0; + + return ratio(first1, last1, first2, last2, score_cutoff); +} + +template +double QRatio(const Sentence1& s1, const Sentence2& s2, double score_cutoff) +{ + return QRatio(detail::to_begin(s1), detail::to_end(s1), detail::to_begin(s2), detail::to_end(s2), + score_cutoff); +} + +template +template +double CachedQRatio::similarity(InputIt2 first2, InputIt2 last2, double score_cutoff, + [[maybe_unused]] double score_hint) const +{ + auto len2 = std::distance(first2, last2); + + /* in FuzzyWuzzy this returns 0. For sake of compatibility return 0 here as well + * see https://github.com/rapidfuzz/RapidFuzz/issues/110 */ + if (s1.empty() || !len2) return 0; + + return cached_ratio.similarity(first2, last2, score_cutoff); +} + +template +template +double CachedQRatio::similarity(const Sentence2& s2, double score_cutoff, + [[maybe_unused]] double score_hint) const +{ + return similarity(detail::to_begin(s2), detail::to_end(s2), score_cutoff); +} + +} // namespace rapidfuzz::fuzz diff --git a/src/external/rapidfuzz-cpp/rapidfuzz/rapidfuzz_all.hpp b/src/external/rapidfuzz-cpp/rapidfuzz/rapidfuzz_all.hpp new file mode 100644 index 00000000..3992ebb0 --- /dev/null +++ b/src/external/rapidfuzz-cpp/rapidfuzz/rapidfuzz_all.hpp @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright © 2022-present Max Bachmann */ + +#pragma once +#include +#include \ No newline at end of file diff --git a/src/external/rapidfuzz-cpp/rapidfuzz_reference/DamerauLevenshtein.hpp b/src/external/rapidfuzz-cpp/rapidfuzz_reference/DamerauLevenshtein.hpp new file mode 100644 index 00000000..8068cd81 --- /dev/null +++ b/src/external/rapidfuzz-cpp/rapidfuzz_reference/DamerauLevenshtein.hpp @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright © 2022-present Max Bachmann */ + +#pragma once +#include "common.hpp" +#include +#include +#include +#include +#include +#include + +namespace rapidfuzz_reference { + +template +Matrix damerau_levenshtein_matrix(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2) +{ + size_t len1 = std::distance(first1, last1); + size_t len2 = std::distance(first2, last2); + size_t infinite = len1 + len2; + + std::unordered_map da; + Matrix matrix(len1 + 2, len2 + 2); + matrix(0, 0) = infinite; + + for (size_t i = 0; i <= len1; ++i) { + matrix(i + 1, 0) = infinite; + matrix(i + 1, 1) = i; + } + for (size_t i = 0; i <= len2; ++i) { + matrix(0, i + 1) = infinite; + matrix(1, i + 1) = i; + } + + for (size_t pos1 = 0; pos1 < len1; ++pos1) { + size_t db = 0; + for (size_t pos2 = 0; pos2 < len2; ++pos2) { + size_t i1 = da[static_cast(first2[pos2])]; + size_t j1 = db; + size_t cost = 1; + if (first1[pos1] == first2[pos2]) { + cost = 0; + db = pos2 + 1; + } + + matrix(pos1 + 2, pos2 + 2) = + std::min({matrix(pos1 + 1, pos2 + 1) + cost, matrix(pos1 + 2, pos2 + 1) + 1, + matrix(pos1 + 1, pos2 + 2) + 1, matrix(i1, j1) + (pos1 - i1) + 1 + (pos2 - j1) + + }); + } + + da[first1[pos1]] = pos1 + 1; + } + + return matrix; +} + +template +size_t damerau_levenshtein_distance(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + size_t score_cutoff = std::numeric_limits::max()) +{ + auto matrix = damerau_levenshtein_matrix(first1, last1, first2, last2); + size_t dist = matrix.back(); + return (dist <= score_cutoff) ? dist : score_cutoff + 1; +} + +template +size_t damerau_levenshtein_distance(const Sentence1& s1, const Sentence2& s2, + size_t score_cutoff = std::numeric_limits::max()) +{ + return damerau_levenshtein_distance(std::begin(s1), std::end(s1), std::begin(s2), std::end(s2), + score_cutoff); +} + +} // namespace rapidfuzz_reference diff --git a/src/external/rapidfuzz-cpp/rapidfuzz_reference/Hamming.hpp b/src/external/rapidfuzz-cpp/rapidfuzz_reference/Hamming.hpp new file mode 100644 index 00000000..d5c9291a --- /dev/null +++ b/src/external/rapidfuzz-cpp/rapidfuzz_reference/Hamming.hpp @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright © 2021 Max Bachmann */ + +#pragma once +#include +#include + +namespace rapidfuzz_reference { + +template +size_t hamming_distance(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + size_t score_cutoff = std::numeric_limits::max()) +{ + ptrdiff_t len1 = std::distance(first1, last1); + ptrdiff_t len2 = std::distance(first2, last2); + if (len1 != len2) throw std::invalid_argument("Sequences are not the same length."); + + size_t dist = 0; + for (ptrdiff_t i = 0; i < len1; ++i) + dist += bool(first1[i] != first2[i]); + + return (dist <= score_cutoff) ? dist : score_cutoff + 1; +} + +template +size_t hamming_distance(const Sentence1& s1, const Sentence2& s2, + size_t score_cutoff = std::numeric_limits::max()) +{ + return hamming_similarity(std::begin(s1), std::end(s1), std::begin(s2), std::end(s2), score_cutoff); +} + +} // namespace rapidfuzz_reference diff --git a/src/external/rapidfuzz-cpp/rapidfuzz_reference/Indel.hpp b/src/external/rapidfuzz-cpp/rapidfuzz_reference/Indel.hpp new file mode 100644 index 00000000..ec3bf3f6 --- /dev/null +++ b/src/external/rapidfuzz-cpp/rapidfuzz_reference/Indel.hpp @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright © 2022-present Max Bachmann */ + +#pragma once + +#include "Levenshtein.hpp" +#include + +namespace rapidfuzz_reference { + +template +size_t indel_distance(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + size_t score_cutoff = std::numeric_limits::max()) +{ + return levenshtein_distance(first1, last1, first2, last2, {1, 1, 2}, score_cutoff); +} + +template +size_t indel_distance(const Sentence1& s1, const Sentence2& s2, + size_t score_cutoff = std::numeric_limits::max()) +{ + return levenshtein_distance(s1, s2, {1, 1, 2}, score_cutoff); +} + +template +double indel_similarity(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + double score_cutoff = 0.0) +{ + return levenshtein_similarity(first1, last1, first2, last2, {1, 1, 2}, score_cutoff); +} + +template +double indel_similarity(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 0.0) +{ + return levenshtein_similarity(s1, s2, {1, 1, 2}, score_cutoff); +} + +} // namespace rapidfuzz_reference diff --git a/src/external/rapidfuzz-cpp/rapidfuzz_reference/Jaro.hpp b/src/external/rapidfuzz-cpp/rapidfuzz_reference/Jaro.hpp new file mode 100644 index 00000000..0ce8e971 --- /dev/null +++ b/src/external/rapidfuzz-cpp/rapidfuzz_reference/Jaro.hpp @@ -0,0 +1,74 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright © 2022-present Max Bachmann */ + +#pragma once +#include +#include +#include + +namespace rapidfuzz_reference { + +template +double jaro_similarity(InputIt1 P_first, InputIt1 P_last, InputIt2 T_first, InputIt2 T_last, + double score_cutoff = 0.0) +{ + size_t P_len = static_cast(std::distance(P_first, P_last)); + size_t T_len = static_cast(std::distance(T_first, T_last)); + + if (score_cutoff > 1.0) return 0.0; + + if (!P_len || !T_len) return double(!P_len && !T_len); + + std::vector P_flag(P_len + 1); + std::vector T_flag(T_len + 1); + + size_t Bound = std::max(P_len, T_len) / 2; + if (Bound > 0) Bound--; + + size_t CommonChars = 0; + for (size_t i = 0; i < T_len; i++) { + size_t lowlim = (i >= Bound) ? i - Bound : 0; + size_t hilim = (i + Bound <= P_len - 1) ? (i + Bound) : P_len - 1; + for (size_t j = lowlim; j <= hilim; j++) { + if (!P_flag[j] && (P_first[static_cast(j)] == T_first[static_cast(i)])) { + T_flag[i] = 1; + P_flag[j] = 1; + CommonChars++; + break; + } + } + } + + // Count the number of transpositions + size_t Transpositions = 0; + size_t k = 0; + for (size_t i = 0; i < T_len; i++) { + if (T_flag[i]) { + size_t j = k; + for (; j < P_len; j++) { + if (P_flag[j]) { + k = j + 1; + break; + } + } + if (T_first[static_cast(i)] != P_first[static_cast(j)]) Transpositions++; + } + } + + Transpositions /= 2; + double Sim = 0; + Sim += static_cast(CommonChars) / static_cast(P_len); + Sim += static_cast(CommonChars) / static_cast(T_len); + Sim += (static_cast(CommonChars) - static_cast(Transpositions)) / + static_cast(CommonChars); + Sim /= 3.0; + return (Sim >= score_cutoff) ? Sim : 0; +} + +template +double jaro_similarity(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 0.0) +{ + return jaro_similarity(std::begin(s1), std::end(s1), std::begin(s2), std::end(s2), score_cutoff); +} + +} /* namespace rapidfuzz_reference */ diff --git a/src/external/rapidfuzz-cpp/rapidfuzz_reference/JaroWinkler.hpp b/src/external/rapidfuzz-cpp/rapidfuzz_reference/JaroWinkler.hpp new file mode 100644 index 00000000..3b717d8e --- /dev/null +++ b/src/external/rapidfuzz-cpp/rapidfuzz_reference/JaroWinkler.hpp @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright © 2022-present Max Bachmann */ + +#pragma once +#include "Jaro.hpp" + +namespace rapidfuzz_reference { + +template >> +double jaro_winkler_similarity(InputIt1 P_first, InputIt1 P_last, InputIt2 T_first, InputIt2 T_last, + double prefix_weight = 0.1, double score_cutoff = 0.0) +{ + int64_t min_len = std::min(std::distance(P_first, P_last), std::distance(T_first, T_last)); + size_t max_prefix = std::min(static_cast(min_len), size_t(4)); + + size_t prefix = 0; + for (; prefix < max_prefix; ++prefix) + if (T_first[static_cast(prefix)] != P_first[static_cast(prefix)]) break; + + double Sim = jaro_similarity(P_first, P_last, T_first, T_last); + if (Sim > 0.7) Sim += static_cast(prefix) * prefix_weight * (1.0 - Sim); + + return (Sim >= score_cutoff) ? Sim : 0; +} + +template +double jaro_winkler_similarity(const Sentence1& s1, const Sentence2& s2, double prefix_weight = 0.1, + double score_cutoff = 0.0) +{ + return jaro_winkler_similarity(std::begin(s1), std::end(s1), std::begin(s2), std::end(s2), prefix_weight, + score_cutoff); +} + +} /* namespace rapidfuzz_reference */ diff --git a/src/external/rapidfuzz-cpp/rapidfuzz_reference/LCSseq.hpp b/src/external/rapidfuzz-cpp/rapidfuzz_reference/LCSseq.hpp new file mode 100644 index 00000000..cee6a2ef --- /dev/null +++ b/src/external/rapidfuzz-cpp/rapidfuzz_reference/LCSseq.hpp @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright © 2022-present Max Bachmann */ + +#pragma once +#include "Indel.hpp" + +namespace rapidfuzz_reference { + +template +size_t lcs_seq_similarity(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + size_t score_cutoff = 0) +{ + size_t maximum = static_cast(std::distance(first1, last1) + std::distance(first2, last2)); + size_t dist = indel_distance(first1, last1, first2, last2); + size_t sim = (maximum - dist) / 2; + return (sim >= score_cutoff) ? sim : 0; +} + +template +size_t lcs_seq_similarity(const Sentence1& s1, const Sentence2& s2, size_t score_cutoff = 0) +{ + return lcs_seq_similarity(std::begin(s1), std::end(s1), std::begin(s2), std::end(s2), score_cutoff); +} + +} // namespace rapidfuzz_reference diff --git a/src/external/rapidfuzz-cpp/rapidfuzz_reference/Levenshtein.hpp b/src/external/rapidfuzz-cpp/rapidfuzz_reference/Levenshtein.hpp new file mode 100644 index 00000000..90a9c56b --- /dev/null +++ b/src/external/rapidfuzz-cpp/rapidfuzz_reference/Levenshtein.hpp @@ -0,0 +1,104 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright © 2022-present Max Bachmann */ + +#pragma once +#include "common.hpp" +#include +#include +#include +#include + +namespace rapidfuzz_reference { + +struct LevenshteinWeightTable { + size_t insert_cost; + size_t delete_cost; + size_t replace_cost; +}; + +static inline size_t levenshtein_maximum(size_t len1, size_t len2, LevenshteinWeightTable weights) +{ + size_t max_dist = len1 * weights.delete_cost + len2 * weights.insert_cost; + + if (len1 >= len2) + max_dist = std::min(max_dist, len2 * weights.replace_cost + (len1 - len2) * weights.delete_cost); + else + max_dist = std::min(max_dist, len1 * weights.replace_cost + (len2 - len1) * weights.insert_cost); + + return max_dist; +} + +template +Matrix levenshtein_matrix(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + LevenshteinWeightTable weights = {1, 1, 1}) +{ + size_t len1 = static_cast(std::distance(first1, last1)); + size_t len2 = static_cast(std::distance(first2, last2)); + + Matrix matrix(len1 + 1, len2 + 1); + + for (size_t i = 0; i <= len1; ++i) + matrix(i, 0) = i * weights.delete_cost; + for (size_t i = 0; i <= len2; ++i) + matrix(0, i) = i * weights.insert_cost; + + for (size_t pos1 = 0; pos1 < len1; ++pos1) { + for (size_t pos2 = 0; pos2 < len2; ++pos2) { + size_t cost = (first1[pos1] == first2[pos2]) ? 0 : weights.replace_cost; + + matrix(pos1 + 1, pos2 + 1) = + std::min({matrix(pos1, pos2 + 1) + weights.delete_cost, + matrix(pos1 + 1, pos2) + weights.insert_cost, matrix(pos1, pos2) + cost}); + } + } + + return matrix; +} + +template +Matrix levenshtein_matrix(const Sentence1& s1, const Sentence2& s2, + LevenshteinWeightTable weights = {1, 1, 1}) +{ + return levenshtein_matrix(std::begin(s1), std::end(s1), std::begin(s2), std::end(s2), weights); +} + +template +size_t levenshtein_distance(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + LevenshteinWeightTable weights = {1, 1, 1}, + size_t score_cutoff = std::numeric_limits::max()) +{ + auto matrix = levenshtein_matrix(first1, last1, first2, last2, weights); + size_t dist = matrix.back(); + return (dist <= score_cutoff) ? dist : score_cutoff + 1; +} + +template +size_t levenshtein_distance(const Sentence1& s1, const Sentence2& s2, + LevenshteinWeightTable weights = {1, 1, 1}, + size_t score_cutoff = std::numeric_limits::max()) +{ + return levenshtein_distance(std::begin(s1), std::end(s1), std::begin(s2), std::end(s2), weights, + score_cutoff); +} + +template +double levenshtein_similarity(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + LevenshteinWeightTable weights = {1, 1, 1}, double score_cutoff = 0.0) +{ + size_t len1 = static_cast(std::distance(first1, last1)); + size_t len2 = static_cast(std::distance(first2, last2)); + size_t dist = levenshtein_distance(first1, last1, first2, last2, weights); + size_t max = levenshtein_maximum(len1, len2, weights); + double sim = 1.0 - (double)dist / max; + return (sim >= score_cutoff) ? sim : 0.0; +} + +template +double levenshtein_similarity(const Sentence1& s1, const Sentence2& s2, + LevenshteinWeightTable weights = {1, 1, 1}, double score_cutoff = 0.0) +{ + return levenshtein_similarity(std::begin(s1), std::end(s1), std::begin(s2), std::end(s2), weights, + score_cutoff); +} + +} // namespace rapidfuzz_reference diff --git a/src/external/rapidfuzz-cpp/rapidfuzz_reference/OSA.hpp b/src/external/rapidfuzz-cpp/rapidfuzz_reference/OSA.hpp new file mode 100644 index 00000000..45263b0b --- /dev/null +++ b/src/external/rapidfuzz-cpp/rapidfuzz_reference/OSA.hpp @@ -0,0 +1,61 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright © 2022-present Max Bachmann */ + +#pragma once +#include "common.hpp" +#include +#include +#include +#include + +namespace rapidfuzz_reference { + +template +Matrix osa_matrix(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2) +{ + size_t len1 = static_cast(std::distance(first1, last1)); + size_t len2 = static_cast(std::distance(first2, last2)); + + Matrix matrix(static_cast(len1) + 1, static_cast(len2) + 1); + + for (size_t i = 0; i <= len1; ++i) + matrix(i, 0) = i; + for (size_t i = 0; i <= len2; ++i) + matrix(0, i) = i; + + for (size_t pos1 = 0; pos1 < len1; ++pos1) { + for (size_t pos2 = 0; pos2 < len2; ++pos2) { + size_t cost = (first1[pos1] == first2[pos2]) ? 0 : 1; + + matrix(pos1 + 1, pos2 + 1) = + std::min({matrix(pos1, pos2 + 1) + 1, matrix(pos1 + 1, pos2) + 1, matrix(pos1, pos2) + cost}); + + if (pos1 == 0 || pos2 == 0) continue; + if (first1[pos1] != first2[pos2 - 1]) continue; + if (first1[pos1 - 1] != first2[pos2]) continue; + + matrix(pos1 + 1, pos2 + 1) = + std::min(matrix(pos1 + 1, pos2 + 1), matrix(pos1 - 1, pos2 - 1) + cost); + } + } + + return matrix; +} + +template +size_t osa_distance(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + size_t score_cutoff = std::numeric_limits::max()) +{ + auto matrix = osa_matrix(first1, last1, first2, last2); + size_t dist = matrix.back(); + return (dist <= score_cutoff) ? dist : score_cutoff + 1; +} + +template +size_t osa_distance(const Sentence1& s1, const Sentence2& s2, + size_t score_cutoff = std::numeric_limits::max()) +{ + return osa_distance(std::begin(s1), std::end(s1), std::begin(s2), std::end(s2), score_cutoff); +} + +} // namespace rapidfuzz_reference diff --git a/src/external/rapidfuzz-cpp/rapidfuzz_reference/README.md b/src/external/rapidfuzz-cpp/rapidfuzz_reference/README.md new file mode 100644 index 00000000..93b03be8 --- /dev/null +++ b/src/external/rapidfuzz-cpp/rapidfuzz_reference/README.md @@ -0,0 +1,4 @@ +## rapidfuzz_reference + +This includes reference implementations of various string matching algorithms, +which can be used to validate the results of faster implementations. \ No newline at end of file diff --git a/src/external/rapidfuzz-cpp/rapidfuzz_reference/common.hpp b/src/external/rapidfuzz-cpp/rapidfuzz_reference/common.hpp new file mode 100644 index 00000000..add8fe1d --- /dev/null +++ b/src/external/rapidfuzz-cpp/rapidfuzz_reference/common.hpp @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright © 2021 Max Bachmann */ + +#pragma once +#include + +namespace rapidfuzz_reference { + +template +class Matrix { +public: + Matrix(size_t _rows, size_t _cols) : rows(_rows), cols(_cols) + { + matrix = new T[rows * cols]; + std::fill(matrix, matrix + rows * cols, T()); + } + + ~Matrix() + { + delete[] matrix; + } + + T& operator()(size_t row, size_t col) + { + return matrix[row + col * rows]; + } + + T& back() + { + return matrix[rows * cols - 1]; + } + + size_t rows; + size_t cols; + T* matrix; +}; + +} // namespace rapidfuzz_reference diff --git a/src/external/rapidfuzz-cpp/rapidfuzz_reference/fuzz.hpp b/src/external/rapidfuzz-cpp/rapidfuzz_reference/fuzz.hpp new file mode 100644 index 00000000..693a6043 --- /dev/null +++ b/src/external/rapidfuzz-cpp/rapidfuzz_reference/fuzz.hpp @@ -0,0 +1,63 @@ +/* SPDX-License-Identifier: MIT */ +/* Copyright © 2022-present Max Bachmann */ + +#pragma once + +#include "Indel.hpp" + +namespace rapidfuzz_reference { + +template +double ratio(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, double score_cutoff = 0.0) +{ + return indel_similarity(first1, last1, first2, last2, score_cutoff / 100.0) * 100; +} + +template +double ratio(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 0.0) +{ + return indel_similarity(s1, s2, score_cutoff / 100.0) * 100; +} + +template +double partial_ratio_impl(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + double score_cutoff = 0.0) +{ + size_t len1 = static_cast(std::distance(first1, last1)); + size_t len2 = static_cast(std::distance(first2, last2)); + if (len1 == 0 && len2 == 0) return 100.0; + + if (len1 == 0 || len2 == 0) return 0.0; + + if (len1 > len2) return partial_ratio_impl(first2, last2, first1, last1, score_cutoff); + + double res = 0.0; + for (ptrdiff_t i = -1 * (ptrdiff_t)len1; i < (ptrdiff_t)len2; i++) { + ptrdiff_t start = std::max(ptrdiff_t(0), i); + ptrdiff_t end = std::min(ptrdiff_t(len2), i + ptrdiff_t(len1)); + InputIt2 first2_ = first2 + start; + InputIt2 last2_ = first2 + end; + res = std::max(res, ratio(first1, last1, first2_, last2_, score_cutoff)); + } + return res; +} + +template +double partial_ratio(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, + double score_cutoff = 0.0) +{ + size_t len1 = static_cast(std::distance(first1, last1)); + size_t len2 = static_cast(std::distance(first2, last2)); + if (len1 != len2) return partial_ratio_impl(first1, last1, first2, last2, score_cutoff); + + return std::max(partial_ratio_impl(first1, last1, first2, last2, score_cutoff), + partial_ratio_impl(first2, last2, first1, last1, score_cutoff)); +} + +template +double partial_ratio(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 0.0) +{ + return partial_ratio(std::begin(s1), std::end(s1), std::begin(s2), std::end(s2), score_cutoff); +} + +} // namespace rapidfuzz_reference diff --git a/src/external/rapidfuzz-cpp/test/CMakeLists.txt b/src/external/rapidfuzz-cpp/test/CMakeLists.txt new file mode 100644 index 00000000..784503d9 --- /dev/null +++ b/src/external/rapidfuzz-cpp/test/CMakeLists.txt @@ -0,0 +1,65 @@ +#find_package(Catch2 2 QUIET) +if (Catch2_FOUND) + message("Using system supplied version of Catch2") +else() + message("Using FetchContent to load Catch2") + include(FetchContent) + FetchContent_Declare( + Catch2 + GIT_REPOSITORY https://github.com/catchorg/Catch2.git + GIT_TAG v2.13.10 + ) + FetchContent_MakeAvailable(Catch2) +endif() + +if (RAPIDFUZZ_ENABLE_LINTERS) + # include aminya & jason turner's C++ best practices recommended cmake project utilities + message("Enable Linters on test build") + include(FetchContent) + + if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.20) + FetchContent_Declare(_project_options URL https://github.com/aminya/project_options/archive/refs/tags/v0.26.2.zip) + else() + FetchContent_Declare(_project_options URL https://github.com/aminya/project_options/archive/refs/tags/v0.25.2.zip) + endif() + FetchContent_MakeAvailable(_project_options) + include(${_project_options_SOURCE_DIR}/Index.cmake) + + project_options( + # ENABLE_CACHE + # ENABLE_CONAN + WARNINGS_AS_ERRORS + # ENABLE_CPPCHECK + # ENABLE_CLANG_TIDY + # ENABLE_INCLUDE_WHAT_YOU_USE + # ENABLE_COVERAGE + # ENABLE_PCH + # PCH_HEADERS + # ENABLE_DOXYGEN + # ENABLE_IPO + # ENABLE_USER_LINKER + # ENABLE_BUILD_WITH_TIME_TRACE + # ENABLE_UNITY + # ENABLE_SANITIZER_ADDRESS + # ENABLE_SANITIZER_LEAK + # ENABLE_SANITIZER_UNDEFINED_BEHAVIOR + # ENABLE_SANITIZER_THREAD + # ENABLE_SANITIZER_MEMORY + # CLANG_WARNINGS "-Weverything" + ) +endif() + +function(rapidfuzz_add_test test) + add_executable(test_${test} tests-main.cpp tests-${test}.cpp) + target_link_libraries(test_${test} ${PROJECT_NAME}) + target_link_libraries(test_${test} Catch2::Catch2) + if (RAPIDFUZZ_ENABLE_LINTERS) + target_link_libraries(test_${test} project_warnings) + endif() + add_test(NAME ${test} COMMAND test_${test}) +endfunction() + +rapidfuzz_add_test(fuzz) +rapidfuzz_add_test(common) + +add_subdirectory(distance) diff --git a/src/external/rapidfuzz-cpp/test/common.hpp b/src/external/rapidfuzz-cpp/test/common.hpp new file mode 100644 index 00000000..427f0906 --- /dev/null +++ b/src/external/rapidfuzz-cpp/test/common.hpp @@ -0,0 +1,69 @@ +#pragma once + +template +class BidirectionalIterWrapper { +public: + using difference_type = typename T::difference_type; + using value_type = typename T::value_type; + using pointer = typename T::pointer; + using reference = typename T::reference; + using iterator_category = std::bidirectional_iterator_tag; + + BidirectionalIterWrapper() : iter() + {} + + BidirectionalIterWrapper(T iter_) : iter(iter_) + {} + + bool operator==(const BidirectionalIterWrapper& i) const + { + return iter == i.iter; + } + + bool operator!=(const BidirectionalIterWrapper& i) const + { + return !(*this == i); + } + + BidirectionalIterWrapper operator++(int) + { + BidirectionalIterWrapper cur(iter); + ++iter; + return cur; + } + BidirectionalIterWrapper operator--(int) + { + BidirectionalIterWrapper cur(iter); + --iter; + return cur; + } + + BidirectionalIterWrapper& operator++() + { + ++iter; + return *this; + } + BidirectionalIterWrapper& operator--() + { + --iter; + return *this; + } + + const auto& operator*() const + { + return *iter; + } + +private: + T iter; +}; + +template >> +std::basic_string str_multiply(std::basic_string a, size_t b) +{ + std::basic_string output; + while (b--) + output += a; + + return output; +} diff --git a/src/external/rapidfuzz-cpp/test/distance/CMakeLists.txt b/src/external/rapidfuzz-cpp/test/distance/CMakeLists.txt new file mode 100644 index 00000000..93870c86 --- /dev/null +++ b/src/external/rapidfuzz-cpp/test/distance/CMakeLists.txt @@ -0,0 +1,22 @@ +function(rapidfuzz_add_test test) + add_executable(test_${test} ../tests-main.cpp tests-${test}.cpp examples/ocr.cpp examples/pythonLevenshteinIssue9.cpp) + target_link_libraries(test_${test} PRIVATE ${PROJECT_NAME}) + target_link_libraries(test_${test} PRIVATE Catch2::Catch2) + if (RAPIDFUZZ_ENABLE_LINTERS) + target_link_libraries(test_${test} PRIVATE project_warnings) + endif() + + #target_compile_options(test_${test} PRIVATE -g -fsanitize=address) + #target_link_libraries(test_${test} PRIVATE -fsanitize=address) + + add_test(NAME ${test} COMMAND test_${test}) +endfunction() + +rapidfuzz_add_test(Hamming) +rapidfuzz_add_test(Indel) +rapidfuzz_add_test(LCSseq) +rapidfuzz_add_test(Levenshtein) +rapidfuzz_add_test(DamerauLevenshtein) +rapidfuzz_add_test(OSA) +rapidfuzz_add_test(Jaro) +rapidfuzz_add_test(JaroWinkler) diff --git a/src/external/rapidfuzz-cpp/test/distance/examples/ocr.cpp b/src/external/rapidfuzz-cpp/test/distance/examples/ocr.cpp new file mode 100644 index 00000000..96b9f5c2 --- /dev/null +++ b/src/external/rapidfuzz-cpp/test/distance/examples/ocr.cpp @@ -0,0 +1,10185 @@ +#include "ocr.hpp" + +std::vector ocr_example1 = { + 22, 18, 27, 22, 8, 23, 23, 18, 29, 27, 8, 23, 28, 18, 29, 27, 8, 24, 18, 27, 31, + 8, 24, 18, 29, 22, 8, 24, 24, 18, 31, 24, 8, 23, 24, 18, 25, 25, 8, 24, 26, 18, + 30, 24, 8, 23, 26, 18, 25, 30, 8, 29, 11, 2, 22, 18, 27, 22, 8, 23, 23, 18, 29, + 27, 8, 23, 28, 18, 29, 27, 8, 24, 18, 27, 31, 8, 24, 18, 29, 22, 8, 24, 24, 18, + 31, 24, 8, 23, 24, 18, 25, 25, 8, 24, 26, 18, 30, 24, 8, 23, 26, 18, 25, 30, 8, + 0, 136, 2, 22, 18, 28, 27, 8, 23, 23, 18, 24, 29, 8, 23, 30, 18, 24, 22, 8, 7, + 11, 8, 25, 18, 24, 24, 8, 25, 18, 25, 24, 8, 22, 18, 29, 23, 8, 23, 22, 18, 31, + 22, 8, 24, 18, 31, 22, 8, 23, 24, 18, 28, 22, 2, 22, 18, 28, 27, 8, 23, 23, 18, + 24, 29, 8, 23, 30, 18, 24, 22, 8, 7, 29, 8, 25, 18, 24, 24, 8, 25, 18, 25, 24, + 8, 22, 18, 29, 23, 8, 23, 22, 18, 31, 22, 8, 24, 18, 31, 22, 8, 23, 24, 18, 28, + 22, 2, 22, 18, 27, 26, 8, 23, 22, 18, 30, 23, 8, 23, 31, 18, 22, 22, 8, 24, 18, + 31, 25, 8, 7, 29, 24, 18, 24, 22, 8, 24, 22, 18, 25, 25, 8, 23, 22, 18, 22, 24, + 8, 24, 24, 18, 30, 28, 20, 23, 24, 18, 25, 23, 2, 22, 18, 27, 26, 8, 23, 22, 18, + 30, 23, 8, 23, 31, 18, 22, 22, 8, 24, 18, 31, 25, 8, 7, 8, 24, 18, 24, 22, 8, + 24, 22, 18, 25, 25, 8, 23, 22, 18, 22, 24, 8, 24, 24, 18, 30, 28, 8, 23, 24, 18, + 25, 23, 2, 7, 27, 23, 8, 23, 24, 18, 23, 31, 8, 23, 31, 18, 22, 26, 8, 24, 18, + 26, 24, 8, 24, 22, 18, 23, 23, 8, 23, 23, 18, 29, 8, 24, 30, 18, 26, 30, 8, 23, + 30, 18, 29, 28, 8, 11, 2, 22, 18, 27, 23, 8, 23, 24, 18, 23, 31, 8, 23, 31, 18, + 22, 26, 8, 24, 18, 26, 24, 8, 24, 22, 18, 23, 23, 8, 23, 23, 18, 29, 8, 24, 30, + 18, 26, 30, 8, 23, 30, 18, 29, 28, 8, 0, 136, 2, 22, 18, 27, 22, 8, 23, 24, 18, + 25, 24, 8, 24, 22, 18, 28, 24, 8, 30, 25, 18, 24, 27, 8, 23, 18, 31, 22, 8, 23, + 27, 18, 27, 27, 8, 30, 18, 22, 8, 24, 24, 18, 29, 28, 8, 23, 23, 18, 28, 22, 2, + 22, 18, 27, 22, 8, 23, 24, 18, 25, 24, 8, 24, 22, 18, 28, 24, 8, 30, 25, 18, 24, + 27, 8, 23, 18, 31, 22, 8, 23, 27, 18, 27, 27, 8, 30, 18, 22, 8, 24, 24, 18, 29, + 28, 8, 23, 23, 18, 28, 22, 2, 7, 30, 8, 23, 23, 18, 29, 28, 8, 24, 18, 26, 24, + 8, 30, 25, 31, 22, 23, 18, 27, 23, 8, 23, 24, 30, 26, 8, 30, 30, 27, 8, 24, 25, + 18, 25, 26, 8, 23, 23, 18, 30, 23, 2, 22, 18, 30, 8, 23, 23, 18, 29, 28, 8, 24, + 18, 26, 24, 8, 30, 25, 31, 22, 23, 18, 27, 23, 8, 23, 24, 30, 26, 8, 30, 30, 27, + 8, 24, 25, 18, 25, 26, 8, 23, 23, 18, 30, 23, 2, 22, 18, 30, 25, 24, 8, 23, 26, + 18, 28, 25, 8, 23, 31, 18, 30, 28, 8, 24, 18, 31, 29, 8, 23, 18, 27, 30, 8, 23, + 22, 18, 29, 23, 8, 23, 22, 28, 25, 24, 8, 24, 30, 18, 26, 31, 8, 23, 26, 18, 23, + 24, 8, 29, 0, 76, 2, 22, 18, 30, 25, 24, 8, 23, 26, 18, 28, 25, 8, 23, 31, 18, + 30, 28, 8, 24, 18, 31, 29, 8, 23, 18, 27, 30, 8, 23, 22, 18, 29, 23, 8, 23, 22, + 18, 25, 24, 8, 24, 30, 18, 26, 31, 8, 23, 26, 18, 23, 24, 8, 11, 2, 22, 70, 25, + 24, 8, 23, 28, 22, 24, 23, 29, 26, 8, 24, 30, 27, 8, 23, 18, 27, 28, 8, 31, 18, + 29, 27, 8, 23, 24, 30, 8, 24, 27, 18, 26, 24, 8, 7, 29, 8, 23, 26, 31, 22, 2, + 22, 50, 18, 25, 24, 8, 23, 28, 22, 24, 23, 29, 26, 8, 24, 18, 30, 27, 8, 23, 18, + 27, 28, 8, 31, 18, 29, 27, 8, 23, 24, 18, 30, 8, 24, 27, 18, 26, 24, 8, 11, 8, + 23, 26, 31, 22, 2, 25, 18, 25, 31, 8, 11, 8, 23, 28, 18, 23, 25, 8, 24, 20, 30, + 28, 23, 18, 27, 29, 8, 23, 23, 18, 30, 25, 8, 23, 18, 28, 31, 8, 24, 29, 18, 24, + 27, 28, 23, 26, 18, 26, 25, 2, 23, 25, 18, 25, 31, 8, 11, 8, 23, 28, 18, 23, 25, + 8, 24, 20, 30, 28, 23, 18, 27, 29, 8, 23, 23, 18, 30, 25, 8, 23, 18, 28, 31, 8, + 24, 29, 18, 25, 27, 8, 23, 26, 18, 26, 25, 2, 24, 29, 18, 28, 31, 8, 23, 28, 18, + 22, 27, 8, 7, 29, 8, 24, 18, 31, 8, 22, 18, 27, 26, 8, 23, 25, 18, 27, 29, 8, + 23, 27, 18, 25, 26, 8, 23, 24, 18, 26, 23, 8, 30, 18, 30, 24, 8, 24, 24, 18, 22, + 30, 8, 23, 22, 18, 29, 29, 22, 11, 2, 24, 29, 18, 28, 31, 8, 23, 28, 18, 22, 27, + 8, 11, 8, 24, 18, 26, 31, 8, 22, 18, 27, 26, 8, 23, 25, 18, 27, 29, 8, 23, 27, + 18, 25, 26, 8, 23, 24, 18, 26, 23, 8, 30, 18, 30, 24, 8, 24, 24, 18, 22, 30, 8, + 23, 22, 18, 29, 29, 11, 2, 39, 69, 65, 8, 60, 61, 68, 72, 8, 64, 65, 79, 8, 40, + 68, 65, 132, 63, 68, 72, 69, 65, 102, 82, 74, 67, 65, 74, 8, 84, 61, 83, 23, 31, 23, + 27, 8, 61, 82, 66, 8, 82, 74, 67, 65, 66, 103, 68, 79, 8, 87, 84, 65, 69, 8, 39, + 79, 69, 81, 81, 65, 72, 8, 64, 65, 79, 8, 69, 74, 2, 39, 69, 65, 8, 60, 61, 68, + 72, 8, 64, 65, 79, 8, 40, 68, 65, 132, 63, 68, 72, 69, 65, 102, 82, 74, 67, 65, 74, + 8, 84, 61, 79, 8, 23, 31, 23, 27, 8, 61, 82, 66, 8, 82, 74, 67, 65, 66, 103, 68, + 79, 8, 87, 84, 65, 69, 8, 39, 79, 69, 81, 81, 65, 72, 8, 64, 65, 79, 8, 69, 74, + 2, 64, 65, 74, 8, 41, 79, 69, 65, 64, 65, 74, 80, 66, 61, 68, 79, 65, 74, 8, 14, + 23, 31, 23, 23, 3, 23, 31, 23, 25, 15, 8, 64, 82, 79, 63, 68, 132, 63, 68, 74, 69, + 71, 81, 72, 69, 63, 68, 8, 67, 65, 132, 63, 68, 72, 75, 132, 132, 65, 74, 65, 74, 8, + 40, 68, 65, 74, 8, 87, 82, 79, 110, 63, 71, 67, 65, 67, 61, 74, 67, 65, 74, 18, 8, + 65, 79, 66, 82, 68, 79, 8, 69, 74, 2, 64, 65, 74, 8, 41, 79, 69, 65, 64, 65, 74, + 80, 66, 61, 68, 79, 65, 74, 8, 14, 23, 31, 23, 23, 3, 23, 31, 23, 25, 15, 8, 64, + 82, 79, 63, 68, 132, 63, 68, 74, 69, 81, 81, 72, 69, 63, 68, 8, 67, 65, 132, 63, 68, + 72, 75, 132, 132, 65, 74, 65, 74, 8, 40, 68, 65, 74, 8, 87, 82, 79, 110, 63, 71, 67, + 65, 67, 61, 74, 67, 65, 74, 18, 8, 65, 79, 66, 82, 68, 79, 8, 69, 74, 2, 64, 65, + 74, 8, 62, 65, 69, 64, 65, 74, 8, 66, 75, 72, 67, 65, 74, 64, 65, 74, 8, 45, 61, + 68, 79, 65, 74, 8, 74, 82, 79, 8, 65, 69, 74, 65, 8, 67, 65, 79, 69, 74, 67, 65, + 8, 60, 82, 74, 61, 68, 73, 65, 8, 82, 74, 64, 8, 132, 81, 69, 65, 67, 8, 64, 61, + 74, 74, 8, 83, 75, 74, 8, 23, 31, 23, 30, 8, 62, 69, 80, 8, 23, 31, 24, 22, 2, + 64, 65, 74, 8, 62, 65, 69, 64, 65, 74, 8, 66, 75, 72, 67, 65, 74, 64, 65, 74, 8, + 45, 61, 68, 79, 65, 74, 8, 74, 82, 79, 8, 65, 69, 74, 65, 8, 67, 65, 79, 69, 74, + 67, 65, 8, 60, 82, 74, 61, 68, 73, 65, 8, 82, 74, 64, 8, 132, 81, 69, 65, 67, 8, + 64, 61, 74, 74, 8, 83, 75, 74, 8, 23, 31, 23, 30, 8, 62, 69, 80, 8, 23, 31, 24, + 22, 2, 84, 69, 65, 64, 65, 79, 8, 65, 79, 68, 65, 62, 72, 69, 63, 68, 18, 8, 36, + 82, 66, 8, 23, 22, 22, 22, 8, 40, 69, 74, 84, 75, 68, 74, 65, 79, 8, 73, 69, 81, + 81, 72, 65, 79, 65, 79, 8, 45, 61, 68, 79, 65, 80, 62, 65, 83, 109, 72, 71, 65, 79, + 82, 74, 67, 8, 62, 65, 87, 75, 67, 65, 74, 18, 8, 132, 81, 65, 72, 72, 81, 65, 8, + 132, 69, 63, 68, 8, 64, 69, 65, 2, 84, 69, 65, 64, 65, 79, 8, 65, 79, 68, 65, 62, + 72, 69, 63, 68, 20, 8, 36, 82, 66, 8, 23, 22, 22, 22, 8, 40, 69, 74, 84, 75, 68, + 74, 65, 79, 8, 73, 69, 81, 81, 72, 65, 79, 65, 79, 8, 45, 61, 68, 79, 65, 80, 62, + 65, 83, 109, 72, 71, 65, 79, 82, 74, 67, 8, 62, 65, 87, 75, 67, 65, 74, 18, 8, 132, + 81, 65, 72, 72, 81, 65, 8, 132, 69, 63, 68, 8, 64, 69, 65, 2, 68, 72, 8, 64, 65, + 79, 8, 40, 68, 65, 132, 63, 68, 72, 69, 65, 102, 65, 74, 64, 65, 74, 8, 23, 31, 23, + 27, 3, 19, 23, 31, 23, 30, 8, 61, 82, 66, 8, 23, 27, 18, 28, 24, 18, 8, 23, 31, + 23, 27, 3, 19, 23, 31, 24, 22, 8, 61, 82, 66, 8, 23, 31, 18, 30, 22, 8, 67, 65, + 67, 65, 74, 8, 23, 31, 18, 24, 22, 8, 69, 74, 8, 64, 65, 74, 2, 60, 61, 68, 72, + 8, 64, 65, 79, 8, 40, 68, 65, 132, 63, 68, 72, 69, 65, 102, 65, 74, 64, 65, 74, 8, + 23, 31, 23, 27, 23, 31, 23, 30, 8, 61, 82, 66, 8, 23, 27, 18, 28, 24, 18, 8, 23, + 31, 23, 27, 3, 23, 31, 24, 22, 8, 61, 82, 66, 8, 23, 31, 18, 30, 22, 8, 67, 65, + 67, 65, 74, 8, 23, 31, 18, 24, 22, 8, 69, 74, 8, 64, 65, 74, 2, 64, 79, 65, 69, + 8, 72, 65, 81, 87, 81, 65, 74, 20, 41, 79, 69, 65, 64, 65, 74, 80, 70, 61, 68, 79, + 65, 74, 20, 8, 39, 82, 79, 63, 68, 8, 79, 69, 63, 68, 81, 65, 79, 72, 69, 63, 68, + 65, 74, 8, 53, 78, 79, 82, 63, 68, 8, 84, 82, 79, 64, 65, 74, 8, 23, 31, 23, 27, + 19, 23, 22, 23, 30, 8, 28, 29, 25, 8, 40, 68, 65, 74, 8, 67, 65, 4, 2, 64, 79, + 65, 69, 8, 72, 65, 81, 87, 81, 65, 74, 8, 41, 79, 69, 65, 64, 65, 74, 80, 70, 61, + 68, 79, 65, 74, 20, 8, 39, 82, 79, 63, 68, 8, 79, 69, 63, 68, 81, 65, 79, 72, 69, + 63, 68, 65, 74, 8, 53, 78, 79, 82, 63, 68, 8, 84, 82, 79, 64, 65, 74, 8, 23, 31, + 23, 27, 23, 22, 23, 30, 8, 28, 29, 25, 8, 40, 68, 65, 74, 8, 67, 65, 3, 2, 63, + 68, 69, 65, 64, 65, 74, 18, 8, 64, 20, 8, 69, 20, 8, 29, 18, 24, 22, 8, 61, 82, + 66, 8, 70, 65, 8, 23, 22, 22, 18, 8, 69, 74, 8, 64, 65, 79, 8, 67, 72, 65, 69, + 63, 68, 65, 74, 8, 60, 65, 69, 81, 8, 74, 65, 82, 8, 65, 69, 74, 67, 65, 67, 61, + 74, 67, 65, 74, 65, 8, 40, 68, 65, 74, 18, 8, 23, 31, 23, 31, 8, 61, 72, 72, 65, + 69, 74, 2, 132, 63, 68, 69, 65, 64, 65, 74, 18, 8, 64, 20, 8, 69, 20, 8, 29, 18, + 24, 22, 8, 61, 82, 66, 8, 70, 65, 8, 23, 22, 22, 18, 8, 69, 74, 8, 64, 65, 79, + 8, 67, 72, 65, 69, 63, 68, 65, 74, 8, 60, 65, 69, 81, 8, 74, 65, 82, 8, 65, 69, + 74, 67, 65, 67, 61, 74, 67, 65, 74, 65, 8, 40, 68, 65, 74, 18, 8, 23, 31, 23, 31, + 8, 61, 72, 72, 65, 69, 74, 2, 25, 27, 31, 8, 53, 8, 30, 18, 24, 8, 11, 8, 67, + 65, 67, 65, 74, 8, 29, 18, 24, 21, 8, 14, 23, 31, 23, 23, 3, 23, 31, 23, 25, 15, + 20, 2, 25, 27, 31, 8, 53, 8, 30, 18, 24, 8, 29, 8, 67, 65, 67, 65, 74, 8, 29, + 18, 24, 11, 8, 14, 23, 31, 23, 23, 3, 19, 8, 23, 31, 23, 25, 15, 20, 2, 40, 69, + 74, 65, 74, 8, 79, 65, 63, 68, 81, 8, 47, 79, 68, 65, 62, 72, 69, 63, 68, 65, 74, + 8, 52, 110, 63, 71, 67, 61, 74, 67, 8, 65, 79, 66, 82, 68, 79, 8, 64, 69, 65, 8, + 60, 61, 68, 72, 8, 64, 65, 79, 8, 42, 65, 62, 75, 79, 65, 74, 65, 74, 20, 8, 37, + 65, 81, 79, 82, 67, 8, 132, 69, 65, 2, 40, 69, 74, 65, 74, 8, 79, 65, 63, 68, 81, + 8, 65, 79, 68, 65, 62, 72, 69, 63, 68, 65, 74, 8, 52, 110, 63, 71, 67, 61, 74, 67, + 8, 65, 79, 66, 82, 68, 79, 8, 64, 69, 65, 8, 60, 61, 68, 72, 8, 64, 65, 79, 8, + 42, 65, 62, 75, 79, 65, 74, 65, 74, 20, 8, 37, 65, 81, 79, 82, 67, 8, 132, 69, 65, + 2, 31, 23, 27, 8, 74, 75, 63, 68, 8, 27, 29, 25, 31, 8, 82, 74, 64, 8, 78, 72, + 69, 65, 62, 8, 132, 69, 65, 8, 132, 75, 73, 69, 81, 8, 74, 82, 79, 8, 82, 73, 8, + 23, 29, 26, 8, 68, 69, 74, 81, 65, 79, 8, 64, 65, 79, 8, 23, 31, 23, 25, 8, 67, + 65, 73, 65, 72, 64, 65, 81, 65, 74, 8, 60, 61, 68, 72, 8, 64, 65, 79, 8, 42, 65, + 3, 2, 23, 31, 23, 27, 8, 74, 75, 63, 68, 8, 27, 29, 25, 31, 8, 82, 74, 64, 8, + 62, 72, 69, 65, 62, 8, 132, 69, 65, 8, 132, 75, 73, 69, 81, 8, 74, 82, 79, 8, 82, + 73, 8, 23, 29, 26, 8, 68, 69, 74, 81, 65, 79, 8, 64, 65, 79, 8, 23, 31, 23, 25, + 8, 67, 65, 73, 65, 72, 64, 65, 81, 65, 74, 8, 60, 61, 68, 72, 8, 64, 65, 79, 8, + 42, 65, 3, 2, 62, 75, 79, 65, 74, 65, 74, 8, 87, 82, 79, 110, 63, 71, 18, 8, 132, + 75, 8, 132, 61, 74, 71, 8, 132, 69, 65, 8, 14, 23, 31, 15, 23, 27, 15, 8, 61, 82, + 66, 8, 26, 28, 29, 27, 18, 8, 14, 23, 31, 23, 28, 15, 8, 61, 82, 66, 8, 25, 29, + 24, 23, 8, 82, 74, 64, 8, 14, 23, 31, 23, 29, 15, 8, 67, 61, 79, 8, 61, 82, 66, + 8, 27, 23, 31, 25, 18, 8, 82, 73, 8, 64, 61, 74, 74, 2, 62, 75, 79, 65, 74, 65, + 74, 8, 87, 82, 79, 110, 63, 71, 18, 8, 132, 75, 8, 132, 61, 74, 71, 8, 132, 69, 65, + 8, 14, 23, 31, 23, 27, 15, 8, 61, 82, 66, 8, 26, 28, 29, 27, 18, 8, 14, 23, 31, + 23, 28, 15, 8, 61, 82, 66, 8, 25, 29, 24, 23, 8, 82, 74, 64, 8, 14, 23, 31, 23, + 29, 15, 8, 67, 61, 79, 8, 61, 82, 66, 8, 25, 23, 31, 25, 18, 8, 82, 73, 8, 64, + 61, 74, 74, 2, 39, 69, 65, 8, 48, 65, 81, 68, 75, 64, 65, 8, 87, 82, 79, 8, 40, + 79, 79, 65, 63, 68, 74, 82, 74, 67, 8, 64, 69, 65, 132, 65, 79, 8, 60, 69, 66, 66, + 65, 79, 8, 82, 74, 64, 8, 64, 69, 65, 8, 60, 69, 132, 132, 65, 79, 74, 8, 132, 65, + 72, 62, 132, 81, 8, 66, 69, 74, 64, 65, 74, 18, 8, 132, 69, 63, 68, 8, 69, 74, 8, + 64, 65, 79, 8, 69, 73, 8, 48, 61, 69, 4, 2, 39, 69, 65, 8, 48, 65, 81, 68, 75, + 64, 65, 8, 87, 82, 79, 8, 40, 79, 79, 65, 63, 68, 74, 82, 74, 67, 8, 64, 69, 65, + 132, 65, 79, 8, 60, 69, 66, 66, 65, 79, 8, 82, 74, 64, 8, 64, 69, 65, 8, 60, 69, + 132, 132, 65, 79, 74, 8, 132, 65, 72, 62, 132, 81, 8, 66, 69, 74, 64, 65, 74, 18, 8, + 132, 69, 63, 68, 8, 69, 74, 8, 64, 65, 79, 8, 69, 73, 8, 48, 61, 69, 3, 2, 68, + 65, 66, 81, 8, 23, 31, 24, 23, 8, 64, 65, 79, 20, 8, 39, 65, 82, 81, 132, 63, 68, + 65, 74, 8, 48, 75, 74, 61, 81, 80, 132, 63, 68, 79, 69, 66, 81, 8, 66, 110, 79, 8, + 109, 66, 66, 65, 74, 81, 72, 69, 63, 68, 65, 8, 42, 65, 132, 82, 74, 64, 68, 65, 69, + 81, 80, 78, 66, 72, 65, 67, 65, 8, 83, 75, 73, 8, 53, 81, 61, 81, 69, 132, 81, 69, + 132, 63, 68, 65, 74, 8, 36, 73, 81, 8, 64, 65, 79, 8, 53, 81, 61, 64, 81, 2, 68, + 65, 66, 81, 8, 23, 31, 24, 23, 8, 64, 65, 79, 8, 39, 65, 82, 81, 132, 63, 68, 65, + 74, 8, 48, 75, 74, 61, 81, 80, 132, 63, 68, 79, 69, 66, 81, 8, 66, 110, 79, 8, 109, + 66, 66, 65, 74, 81, 72, 69, 63, 68, 65, 8, 42, 65, 132, 82, 74, 64, 68, 65, 69, 81, + 80, 78, 66, 72, 65, 67, 65, 8, 83, 75, 73, 8, 53, 81, 61, 81, 69, 132, 81, 69, 132, + 63, 68, 65, 74, 8, 36, 73, 81, 8, 64, 65, 79, 8, 53, 81, 61, 64, 81, 2, 38, 68, + 61, 79, 72, 75, 81, 81, 65, 74, 62, 82, 79, 67, 8, 83, 65, 79, 109, 66, 66, 65, 74, + 81, 72, 69, 63, 68, 81, 65, 74, 8, 36, 79, 62, 65, 69, 81, 32, 8, 7, 39, 69, 65, + 8, 53, 103, 82, 67, 72, 69, 74, 67, 80, 132, 81, 65, 79, 62, 72, 69, 63, 68, 81, 65, + 69, 81, 8, 69, 74, 8, 38, 62, 61, 79, 72, 75, 81, 81, 65, 74, 62, 82, 79, 67, 2, + 38, 68, 61, 79, 72, 75, 81, 81, 65, 74, 62, 82, 79, 67, 8, 83, 65, 79, 109, 66, 66, + 65, 74, 81, 72, 69, 63, 68, 81, 65, 74, 8, 36, 79, 62, 65, 69, 81, 32, 8, 7, 39, + 69, 65, 8, 53, 103, 82, 67, 72, 69, 74, 67, 80, 132, 81, 65, 79, 62, 72, 69, 63, 68, + 81, 65, 69, 81, 8, 69, 74, 8, 38, 62, 61, 79, 72, 75, 81, 81, 65, 74, 62, 82, 79, + 67, 2, 23, 31, 23, 24, 23, 31, 8, 73, 69, 81, 8, 71, 79, 69, 81, 69, 132, 63, 68, + 65, 74, 8, 37, 65, 73, 65, 79, 71, 82, 74, 67, 65, 74, 8, 87, 82, 79, 8, 48, 65, + 81, 68, 75, 64, 65, 8, 64, 65, 79, 8, 48, 65, 80, 80, 82, 74, 67, 8, 64, 65, 79, + 8, 53, 103, 82, 67, 72, 69, 74, 67, 80, 132, 81, 65, 79, 62, 72, 69, 63, 68, 71, 65, + 69, 81, 6, 18, 8, 53, 20, 8, 23, 26, 27, 8, 66, 66, 20, 20, 7, 2, 23, 31, 23, + 24, 3, 23, 31, 8, 73, 69, 81, 8, 71, 79, 69, 81, 69, 132, 63, 68, 65, 74, 8, 37, + 65, 73, 65, 79, 71, 82, 74, 67, 65, 74, 8, 87, 82, 79, 8, 48, 65, 81, 68, 75, 64, + 65, 8, 64, 65, 79, 8, 48, 65, 80, 80, 82, 74, 67, 8, 64, 65, 79, 8, 53, 103, 82, + 67, 72, 69, 74, 67, 80, 132, 81, 65, 79, 62, 72, 69, 63, 68, 71, 65, 69, 81, 6, 18, + 8, 53, 20, 8, 23, 26, 27, 8, 66, 66, 20, 2, 62, 8, 69, 74, 8, 64, 65, 74, 8, + 62, 65, 69, 64, 65, 74, 8, 48, 75, 74, 61, 81, 65, 74, 8, 49, 75, 78, 65, 73, 62, + 65, 79, 8, 82, 74, 64, 8, 39, 65, 87, 65, 73, 62, 65, 79, 8, 84, 61, 74, 64, 65, + 79, 81, 65, 74, 8, 61, 72, 72, 65, 69, 74, 8, 23, 31, 24, 25, 25, 8, 73, 103, 74, + 74, 72, 69, 63, 68, 65, 8, 51, 65, 79, 4, 8, 0, 130, 2, 69, 74, 8, 64, 65, 74, + 8, 62, 65, 69, 64, 65, 74, 8, 48, 75, 74, 61, 81, 65, 74, 8, 49, 75, 83, 65, 73, + 62, 65, 79, 8, 82, 74, 64, 8, 39, 65, 87, 65, 73, 62, 65, 79, 8, 84, 61, 74, 64, + 65, 79, 81, 65, 74, 8, 61, 72, 72, 65, 69, 74, 8, 23, 31, 24, 25, 25, 8, 73, 103, + 74, 74, 72, 69, 63, 68, 65, 8, 51, 65, 79, 3, 2, 132, 75, 74, 65, 74, 8, 69, 74, + 66, 75, 72, 67, 65, 8, 64, 65, 79, 8, 39, 65, 73, 75, 62, 69, 72, 73, 61, 63, 68, + 82, 74, 67, 8, 73, 65, 68, 79, 8, 87, 82, 8, 61, 72, 80, 8, 61, 62, 20, 8, 44, + 73, 8, 45, 61, 68, 79, 65, 8, 23, 31, 23, 31, 8, 65, 79, 67, 61, 62, 8, 132, 69, + 63, 68, 8, 65, 69, 74, 8, 62, 65, 4, 2, 132, 75, 74, 65, 74, 8, 69, 74, 66, 75, + 72, 67, 65, 8, 64, 65, 79, 8, 39, 65, 73, 75, 62, 69, 72, 73, 61, 63, 68, 82, 74, + 67, 8, 73, 65, 68, 79, 8, 87, 82, 8, 61, 72, 80, 8, 61, 62, 20, 8, 44, 73, 8, + 45, 61, 68, 79, 65, 8, 23, 31, 23, 31, 8, 65, 79, 67, 61, 62, 8, 132, 69, 63, 68, + 8, 65, 69, 74, 8, 62, 65, 3, 2, 64, 65, 82, 81, 65, 74, 64, 65, 79, 8, 55, 65, + 62, 65, 79, 132, 63, 68, 82, 102, 8, 14, 64, 65, 79, 8, 60, 82, 67, 65, 87, 75, 67, + 65, 74, 65, 74, 15, 8, 62, 65, 69, 8, 65, 79, 68, 109, 68, 81, 65, 79, 8, 57, 61, + 74, 64, 65, 79, 62, 65, 84, 65, 67, 82, 74, 67, 18, 8, 84, 103, 68, 79, 65, 74, 64, + 8, 64, 61, 80, 8, 45, 61, 68, 79, 8, 23, 31, 24, 22, 2, 64, 65, 82, 81, 65, 74, + 64, 65, 79, 8, 55, 65, 62, 65, 79, 132, 63, 68, 82, 102, 8, 14, 64, 65, 79, 8, 60, + 82, 67, 65, 87, 75, 67, 65, 74, 65, 74, 15, 8, 62, 65, 69, 8, 65, 79, 68, 109, 68, + 81, 65, 79, 8, 57, 61, 74, 64, 65, 79, 62, 65, 84, 65, 67, 82, 74, 67, 18, 8, 84, + 103, 68, 79, 65, 74, 64, 8, 64, 61, 80, 8, 45, 61, 68, 79, 8, 23, 31, 24, 22, 2, + 69, 69, 8, 65, 69, 74, 67, 65, 132, 63, 68, 79, 103, 74, 71, 81, 65, 79, 8, 57, 61, + 74, 64, 65, 79, 82, 74, 67, 8, 65, 69, 74, 65, 74, 19, 61, 74, 132, 65, 68, 74, 72, + 69, 63, 68, 65, 74, 8, 60, 82, 84, 61, 63, 68, 80, 18, 8, 83, 75, 79, 8, 61, 72, + 72, 65, 73, 8, 83, 75, 74, 8, 73, 103, 74, 74, 72, 69, 63, 68, 65, 74, 8, 51, 65, + 79, 2, 62, 65, 69, 8, 65, 69, 74, 67, 65, 132, 63, 68, 79, 103, 74, 71, 81, 65, 79, + 8, 57, 61, 74, 64, 65, 79, 82, 74, 67, 8, 65, 69, 74, 65, 74, 8, 61, 74, 132, 65, + 68, 74, 72, 69, 63, 68, 65, 74, 8, 60, 82, 84, 61, 63, 68, 80, 18, 8, 83, 75, 79, + 8, 61, 72, 72, 65, 73, 8, 83, 75, 74, 8, 73, 103, 74, 74, 72, 69, 63, 68, 65, 74, + 8, 51, 65, 79, 3, 2, 132, 75, 74, 65, 74, 8, 61, 82, 66, 84, 69, 65, 80, 20, 8, + 36, 72, 80, 8, 46, 79, 69, 65, 86, 80, 62, 69, 72, 61, 74, 87, 8, 65, 79, 67, 69, + 62, 81, 8, 132, 69, 63, 68, 8, 66, 110, 79, 8, 64, 69, 65, 8, 38, 68, 61, 79, 72, + 75, 81, 81, 65, 74, 62, 82, 79, 67, 65, 79, 8, 37, 65, 83, 109, 72, 71, 65, 79, 82, + 74, 67, 15, 8, 84, 103, 68, 79, 65, 74, 64, 2, 132, 75, 74, 65, 74, 8, 61, 82, 66, + 84, 69, 65, 80, 20, 8, 36, 72, 80, 8, 46, 79, 69, 65, 67, 80, 62, 69, 72, 61, 74, + 87, 8, 65, 79, 67, 69, 62, 81, 8, 132, 69, 63, 68, 8, 66, 110, 79, 8, 64, 69, 65, + 8, 14, 38, 68, 61, 79, 72, 75, 81, 81, 65, 74, 62, 82, 79, 67, 65, 79, 8, 37, 65, + 83, 109, 72, 71, 65, 79, 82, 74, 67, 15, 8, 84, 103, 68, 79, 65, 74, 64, 2, 64, 65, + 79, 8, 60, 65, 69, 81, 8, 83, 75, 73, 8, 23, 4, 36, 82, 67, 82, 132, 81, 8, 23, + 31, 23, 26, 8, 62, 69, 80, 8, 87, 82, 73, 8, 25, 23, 20, 8, 50, 71, 81, 75, 62, + 65, 79, 8, 23, 31, 23, 30, 8, 65, 69, 74, 65, 8, 36, 62, 74, 61, 68, 73, 65, 8, + 64, 65, 79, 8, 73, 103, 74, 74, 72, 69, 63, 68, 65, 74, 8, 37, 65, 4, 2, 64, 65, + 79, 8, 60, 65, 69, 81, 8, 83, 75, 73, 8, 23, 20, 8, 36, 82, 67, 82, 132, 81, 8, + 23, 31, 23, 26, 8, 62, 69, 80, 8, 87, 82, 73, 8, 25, 23, 20, 8, 50, 71, 81, 75, + 62, 65, 79, 8, 23, 31, 23, 30, 8, 65, 69, 74, 65, 8, 36, 62, 74, 61, 68, 73, 65, + 8, 64, 65, 79, 8, 73, 103, 74, 74, 72, 69, 63, 68, 65, 74, 8, 37, 65, 4, 3, 2, + 83, 109, 72, 71, 65, 79, 82, 74, 67, 8, 82, 73, 8, 79, 82, 74, 64, 8, 24, 29, 8, + 28, 22, 22, 8, 23, 31, 18, 29, 25, 11, 8, 82, 74, 64, 8, 65, 69, 74, 65, 8, 60, + 82, 74, 61, 68, 73, 65, 8, 64, 65, 80, 8, 84, 65, 69, 62, 72, 69, 63, 68, 65, 74, + 8, 37, 65, 83, 109, 72, 71, 65, 79, 82, 74, 67, 80, 3, 2, 83, 109, 72, 71, 65, 79, + 82, 74, 67, 8, 82, 73, 8, 79, 82, 74, 64, 8, 24, 29, 8, 28, 22, 22, 8, 53, 8, + 23, 31, 18, 29, 25, 11, 8, 82, 74, 64, 8, 65, 69, 74, 65, 8, 60, 82, 74, 61, 68, + 73, 65, 8, 64, 65, 80, 8, 84, 65, 69, 62, 72, 69, 63, 68, 65, 74, 8, 37, 65, 83, + 109, 72, 71, 65, 79, 82, 74, 67, 80, 3, 2, 81, 65, 69, 72, 65, 80, 8, 82, 73, 8, + 79, 82, 74, 64, 8, 23, 23, 8, 24, 22, 22, 8, 19, 8, 28, 18, 25, 27, 18, 18, 8, + 61, 72, 132, 75, 8, 65, 69, 74, 65, 8, 36, 62, 74, 61, 68, 73, 65, 8, 64, 65, 79, + 8, 42, 65, 132, 61, 73, 81, 62, 65, 83, 109, 72, 71, 65, 79, 82, 74, 67, 8, 82, 73, + 8, 79, 82, 74, 64, 2, 81, 65, 69, 72, 65, 80, 8, 82, 73, 8, 79, 82, 74, 64, 8, + 23, 23, 8, 24, 22, 22, 8, 53, 8, 28, 18, 25, 27, 22, 18, 8, 61, 72, 132, 75, 8, + 65, 69, 74, 65, 8, 36, 62, 74, 61, 68, 73, 65, 8, 64, 65, 79, 8, 42, 65, 132, 61, + 73, 81, 62, 65, 83, 109, 72, 71, 65, 79, 82, 74, 67, 8, 82, 73, 8, 79, 82, 74, 64, + 2, 7, 8, 23, 28, 26, 22, 22, 8, 53, 8, 27, 18, 23, 29, 8, 31, 11, 20, 8, 39, + 69, 65, 8, 49, 61, 63, 68, 71, 79, 69, 65, 67, 80, 87, 65, 69, 81, 8, 62, 69, 80, + 8, 25, 23, 20, 8, 39, 65, 87, 65, 73, 62, 65, 79, 8, 23, 31, 23, 31, 8, 84, 69, + 65, 80, 8, 64, 61, 74, 74, 8, 65, 69, 74, 65, 8, 60, 82, 74, 61, 68, 73, 65, 2, + 23, 28, 26, 22, 22, 8, 34, 8, 27, 18, 23, 29, 8, 11, 8, 39, 69, 65, 8, 49, 61, + 63, 68, 71, 79, 69, 65, 67, 80, 87, 65, 69, 81, 8, 62, 69, 80, 8, 25, 23, 20, 8, + 39, 65, 87, 65, 73, 62, 65, 79, 8, 23, 31, 23, 31, 8, 84, 69, 65, 80, 8, 64, 61, + 74, 74, 8, 65, 69, 74, 65, 8, 60, 82, 74, 61, 68, 73, 65, 2, 13, 79, 8, 73, 103, + 74, 74, 72, 69, 63, 68, 65, 74, 8, 37, 65, 83, 109, 72, 71, 65, 79, 82, 74, 67, 8, + 82, 73, 8, 25, 23, 8, 23, 22, 22, 8, 19, 8, 24, 29, 18, 28, 28, 21, 8, 82, 74, + 64, 8, 65, 69, 74, 65, 8, 36, 62, 74, 61, 68, 73, 65, 8, 64, 65, 79, 8, 84, 65, + 69, 62, 72, 69, 63, 68, 65, 74, 8, 37, 65, 3, 2, 64, 65, 79, 8, 73, 103, 74, 74, + 72, 69, 63, 68, 65, 74, 8, 37, 65, 83, 109, 72, 71, 65, 79, 82, 74, 67, 8, 82, 73, + 8, 25, 23, 8, 23, 22, 22, 8, 24, 29, 18, 28, 28, 11, 8, 82, 74, 64, 8, 65, 69, + 74, 65, 8, 36, 62, 74, 61, 68, 73, 65, 8, 64, 65, 79, 8, 84, 65, 69, 62, 72, 69, + 63, 68, 65, 74, 8, 37, 65, 3, 2, 85, 8, 83, 109, 72, 71, 65, 79, 82, 74, 67, 8, + 82, 73, 13, 23, 24, 27, 22, 8, 34, 8, 31, 18, 28, 28, 31, 18, 8, 61, 82, 66, 18, + 8, 84, 103, 68, 79, 65, 74, 64, 8, 64, 69, 65, 8, 42, 65, 132, 61, 73, 81, 62, 65, + 83, 109, 72, 71, 65, 79, 82, 74, 67, 8, 82, 73, 8, 24, 31, 8, 30, 27, 22, 8, 19, + 8, 31, 18, 31, 24, 11, 21, 8, 87, 82, 74, 61, 68, 73, 20, 8, 0, 130, 2, 83, 109, + 72, 71, 65, 79, 82, 74, 67, 8, 82, 73, 8, 23, 24, 27, 22, 8, 19, 8, 22, 18, 28, + 28, 31, 22, 8, 61, 82, 66, 18, 8, 84, 103, 68, 79, 65, 74, 64, 8, 64, 69, 65, 8, + 42, 65, 132, 61, 73, 81, 62, 65, 83, 109, 72, 71, 65, 79, 82, 74, 67, 8, 82, 73, 8, + 24, 31, 8, 30, 27, 22, 8, 19, 8, 31, 18, 31, 24, 11, 8, 87, 82, 74, 61, 68, 73, + 20, 2, 39, 69, 65, 8, 73, 69, 81, 81, 72, 65, 79, 65, 8, 40, 69, 74, 84, 75, 68, + 74, 65, 79, 87, 61, 68, 72, 8, 38, 68, 61, 79, 72, 75, 81, 81, 65, 74, 62, 82, 79, + 67, 80, 8, 62, 65, 81, 79, 82, 67, 8, 23, 31, 23, 27, 8, 79, 82, 74, 64, 8, 25, + 22, 26, 8, 31, 22, 22, 18, 2, 39, 69, 65, 8, 73, 69, 81, 81, 72, 65, 79, 65, 8, + 40, 69, 74, 84, 75, 68, 74, 65, 79, 87, 61, 68, 72, 8, 38, 68, 61, 79, 72, 75, 81, + 81, 65, 74, 62, 82, 79, 67, 80, 8, 62, 65, 81, 79, 82, 67, 8, 23, 31, 23, 27, 8, + 79, 82, 74, 64, 8, 25, 22, 26, 8, 31, 22, 22, 18, 2, 66, 69, 65, 72, 8, 23, 31, + 23, 28, 8, 61, 82, 66, 8, 25, 22, 22, 8, 23, 22, 22, 18, 8, 132, 61, 74, 71, 8, + 23, 31, 23, 29, 8, 84, 65, 69, 81, 65, 79, 8, 61, 82, 66, 8, 24, 31, 29, 20, 22, + 22, 22, 8, 82, 74, 64, 8, 132, 81, 69, 65, 67, 8, 64, 61, 74, 74, 8, 84, 69, 65, + 64, 65, 79, 8, 23, 31, 23, 30, 8, 61, 82, 66, 8, 25, 22, 22, 8, 26, 22, 22, 18, + 2, 66, 69, 65, 72, 8, 23, 31, 23, 28, 8, 61, 82, 66, 8, 25, 22, 22, 8, 23, 22, + 22, 18, 8, 132, 61, 74, 71, 8, 23, 31, 23, 29, 8, 84, 65, 69, 81, 65, 79, 8, 61, + 82, 66, 8, 24, 31, 29, 8, 22, 22, 22, 8, 82, 74, 64, 8, 132, 81, 69, 65, 67, 8, + 64, 61, 74, 74, 8, 84, 69, 65, 64, 65, 79, 8, 23, 31, 23, 30, 8, 61, 82, 66, 8, + 25, 22, 22, 8, 26, 22, 22, 18, 2, 23, 31, 23, 31, 8, 61, 82, 66, 8, 25, 24, 23, + 8, 26, 22, 22, 8, 82, 74, 64, 8, 23, 31, 24, 22, 8, 61, 82, 66, 8, 25, 25, 26, + 8, 27, 22, 22, 20, 2, 23, 31, 23, 31, 8, 61, 82, 66, 8, 25, 24, 23, 8, 26, 22, + 22, 8, 82, 74, 64, 8, 23, 31, 24, 22, 8, 61, 82, 66, 8, 25, 25, 26, 8, 27, 22, + 22, 20, 2, 39, 69, 65, 8, 74, 61, 81, 110, 79, 72, 69, 63, 68, 65, 8, 37, 65, 83, + 109, 72, 71, 65, 79, 82, 74, 67, 80, 62, 65, 84, 65, 67, 82, 74, 67, 18, 8, 62, 65, + 64, 69, 74, 67, 81, 8, 64, 82, 79, 63, 68, 8, 64, 69, 65, 8, 40, 68, 65, 132, 63, + 68, 72, 69, 65, 4, 2, 39, 69, 65, 8, 74, 61, 81, 110, 79, 72, 69, 63, 68, 65, 8, + 37, 65, 83, 109, 72, 71, 65, 79, 82, 74, 67, 80, 62, 65, 84, 65, 67, 82, 74, 67, 18, + 8, 62, 65, 64, 69, 74, 67, 81, 8, 64, 82, 79, 63, 68, 8, 64, 69, 65, 8, 40, 68, + 65, 132, 63, 68, 72, 69, 65, 3, 2, 102, 82, 74, 67, 65, 74, 18, 8, 42, 65, 62, 82, + 79, 81, 65, 74, 8, 82, 74, 64, 8, 53, 81, 65, 63, 62, 65, 66, 103, 72, 72, 65, 18, + 8, 84, 61, 79, 8, 66, 75, 72, 67, 65, 74, 64, 65, 32, 8, 3, 2, 102, 82, 74, 67, + 65, 74, 18, 8, 42, 65, 62, 82, 79, 81, 65, 74, 8, 82, 74, 64, 8, 53, 81, 65, 79, + 62, 65, 66, 103, 72, 72, 65, 18, 8, 84, 61, 79, 8, 66, 75, 72, 67, 65, 74, 64, 65, + 32, 2, 84, 82, 79, 64, 65, 74, 8, 64, 69, 65, 8, 37, 65, 132, 81, 69, 73, 73, 82, + 74, 67, 65, 74, 8, 66, 110, 79, 8, 64, 61, 80, 8, 38, 68, 61, 79, 72, 75, 81, 81, + 65, 74, 62, 82, 79, 67, 65, 79, 8, 48, 69, 65, 81, 65, 69, 74, 69, 67, 82, 74, 67, + 80, 61, 73, 81, 8, 69, 73, 8, 48, 61, 69, 8, 23, 31, 23, 27, 2, 84, 82, 79, 64, + 65, 74, 8, 64, 69, 65, 8, 37, 65, 132, 81, 69, 73, 73, 82, 74, 67, 65, 74, 8, 66, + 110, 79, 8, 64, 61, 80, 8, 38, 68, 61, 79, 72, 75, 81, 81, 65, 74, 62, 82, 79, 67, + 65, 79, 8, 48, 69, 65, 81, 65, 69, 74, 69, 67, 82, 74, 67, 80, 61, 73, 81, 8, 69, + 73, 8, 48, 61, 69, 8, 23, 31, 23, 27, 2, 64, 61, 68, 69, 74, 8, 61, 62, 67, 65, + 103, 74, 64, 65, 79, 81, 18, 8, 64, 61, 102, 8, 64, 69, 65, 132, 65, 80, 8, 87, 82, + 67, 72, 65, 69, 63, 68, 8, 61, 72, 80, 8, 43, 86, 78, 75, 81, 68, 65, 71, 65, 74, + 65, 69, 74, 69, 67, 82, 74, 67, 80, 61, 73, 81, 8, 69, 74, 8, 54, 103, 81, 69, 67, + 4, 2, 64, 61, 68, 69, 74, 8, 61, 62, 67, 65, 103, 74, 64, 65, 79, 81, 18, 8, 64, + 61, 102, 8, 64, 69, 65, 132, 65, 80, 8, 87, 82, 67, 72, 65, 69, 63, 68, 8, 61, 72, + 80, 8, 43, 86, 78, 75, 81, 68, 65, 71, 65, 74, 65, 69, 74, 69, 67, 82, 74, 67, 80, + 61, 73, 81, 8, 69, 74, 8, 54, 103, 81, 69, 67, 3, 2, 71, 65, 69, 81, 8, 81, 79, + 61, 81, 20, 8, 43, 72, 65, 79, 87, 82, 8, 84, 82, 79, 64, 65, 74, 8, 64, 65, 73, + 8, 36, 73, 81, 8, 61, 82, 63, 68, 8, 64, 69, 65, 8, 132, 75, 67, 65, 74, 61, 74, + 74, 81, 65, 74, 8, 60, 84, 61, 74, 67, 80, 62, 65, 66, 82, 67, 74, 69, 132, 132, 65, + 8, 83, 65, 79, 4, 2, 71, 65, 69, 81, 8, 81, 79, 61, 81, 20, 8, 43, 69, 65, 79, + 87, 82, 8, 84, 82, 79, 64, 65, 74, 8, 64, 65, 73, 8, 36, 73, 81, 8, 61, 82, 63, + 68, 8, 64, 69, 65, 8, 132, 75, 67, 65, 74, 61, 74, 74, 81, 65, 74, 8, 60, 84, 61, + 74, 67, 80, 62, 65, 66, 82, 67, 74, 69, 132, 132, 65, 8, 83, 65, 79, 3, 2, 68, 65, + 74, 18, 8, 14, 84, 75, 74, 61, 63, 68, 8, 64, 69, 65, 18, 8, 37, 65, 81, 65, 69, + 72, 69, 67, 81, 65, 74, 8, 83, 65, 79, 78, 66, 72, 69, 63, 68, 81, 104, 65, 81, 8, + 132, 69, 74, 64, 15, 18, 8, 83, 75, 79, 8, 64, 65, 73, 8, 36, 73, 81, 8, 87, 82, + 8, 65, 79, 132, 63, 68, 65, 69, 74, 65, 74, 8, 82, 74, 64, 8, 36, 82, 80, 71, 82, + 74, 66, 2, 72, 69, 65, 68, 65, 74, 18, 8, 14, 84, 75, 74, 61, 63, 68, 8, 64, 69, + 65, 8, 37, 65, 81, 65, 69, 72, 69, 67, 81, 65, 74, 8, 83, 65, 79, 78, 66, 72, 69, + 63, 68, 81, 65, 81, 8, 132, 69, 74, 64, 15, 18, 8, 83, 75, 79, 8, 64, 65, 73, 8, + 36, 73, 81, 8, 87, 82, 8, 65, 79, 132, 63, 68, 65, 69, 74, 65, 74, 8, 82, 74, 64, + 8, 36, 82, 80, 71, 82, 74, 66, 81, 2, 87, 82, 8, 65, 79, 81, 65, 69, 72, 65, 74, + 20, 8, 39, 82, 79, 63, 68, 8, 64, 69, 65, 8, 37, 82, 74, 64, 65, 80, 79, 61, 81, + 80, 83, 65, 79, 75, 79, 64, 74, 82, 74, 67, 8, 14, 87, 82, 73, 8, 53, 63, 68, 82, + 81, 87, 80, 8, 64, 65, 79, 8, 48, 69, 65, 81, 65, 79, 15, 8, 64, 75, 73, 8, 24, + 28, 20, 8, 45, 82, 72, 69, 8, 23, 31, 23, 29, 8, 83, 2, 87, 82, 8, 65, 79, 81, + 65, 69, 72, 65, 74, 20, 8, 39, 82, 79, 63, 68, 8, 64, 69, 65, 8, 37, 82, 74, 64, + 65, 80, 79, 61, 81, 80, 83, 65, 79, 75, 79, 64, 74, 82, 74, 67, 8, 14, 87, 82, 73, + 8, 53, 63, 68, 82, 81, 87, 65, 8, 64, 65, 79, 8, 48, 69, 65, 81, 65, 79, 15, 8, + 83, 75, 73, 8, 24, 28, 20, 8, 45, 82, 72, 69, 8, 23, 31, 23, 29, 2, 60, 53, 65, + 79, 62, 69, 74, 64, 82, 74, 67, 8, 73, 69, 81, 8, 64, 65, 73, 8, 40, 79, 72, 61, + 102, 8, 64, 65, 80, 8, 48, 69, 74, 69, 132, 81, 65, 79, 80, 8, 64, 65, 80, 8, 44, + 74, 74, 65, 79, 74, 8, 83, 75, 73, 8, 25, 22, 20, 8, 36, 82, 67, 82, 132, 81, 8, + 23, 31, 23, 29, 8, 82, 74, 64, 8, 64, 65, 79, 8, 48, 69, 65, 81, 65, 79, 3, 2, + 69, 74, 8, 56, 65, 79, 62, 69, 74, 64, 82, 74, 67, 8, 73, 69, 81, 8, 64, 65, 73, + 8, 40, 79, 72, 61, 102, 8, 64, 65, 80, 8, 48, 69, 74, 69, 132, 81, 65, 79, 80, 8, + 64, 65, 80, 8, 44, 74, 74, 65, 79, 74, 8, 83, 75, 73, 8, 25, 22, 20, 8, 36, 82, + 67, 82, 132, 81, 8, 23, 31, 23, 29, 8, 82, 74, 64, 8, 64, 65, 79, 8, 48, 69, 65, + 81, 65, 79, 3, 2, 132, 63, 68, 82, 81, 87, 83, 65, 79, 75, 79, 64, 74, 82, 74, 67, + 8, 14, 83, 75, 73, 8, 24, 25, 20, 8, 53, 65, 78, 81, 65, 73, 62, 65, 79, 8, 23, + 31, 23, 30, 15, 8, 84, 82, 79, 64, 65, 8, 64, 61, 80, 8, 40, 69, 74, 69, 67, 82, + 74, 67, 80, 61, 73, 81, 8, 66, 65, 79, 74, 65, 79, 8, 65, 79, 73, 103, 63, 68, 81, + 69, 67, 81, 18, 8, 61, 82, 66, 2, 132, 63, 68, 82, 81, 87, 83, 65, 79, 75, 79, 64, + 74, 82, 74, 67, 8, 14, 83, 75, 73, 8, 24, 25, 20, 8, 53, 65, 78, 81, 65, 73, 62, + 65, 79, 8, 23, 31, 23, 30, 15, 8, 84, 82, 79, 64, 65, 8, 64, 61, 80, 8, 40, 69, + 74, 69, 67, 82, 74, 67, 80, 61, 73, 81, 8, 66, 65, 79, 74, 65, 79, 8, 65, 79, 73, + 103, 63, 68, 81, 69, 67, 81, 18, 8, 61, 82, 66, 2, 36, 74, 79, 82, 66, 65, 74, 20, + 8, 65, 69, 74, 65, 80, 8, 48, 69, 65, 81, 65, 79, 80, 8, 110, 62, 65, 79, 8, 64, + 69, 65, 8, 57, 69, 79, 71, 132, 61, 73, 71, 65, 69, 81, 8, 65, 69, 74, 65, 79, 8, + 46, 110, 74, 64, 69, 67, 82, 74, 67, 8, 64, 65, 80, 8, 56, 65, 79, 73, 69, 65, 81, + 65, 79, 80, 18, 8, 110, 62, 65, 79, 8, 64, 69, 65, 2, 36, 74, 79, 82, 66, 65, 74, + 8, 65, 69, 74, 65, 80, 8, 48, 69, 65, 81, 65, 79, 80, 8, 110, 62, 65, 79, 8, 64, + 69, 65, 8, 57, 69, 79, 71, 132, 61, 73, 71, 65, 69, 81, 8, 65, 69, 74, 65, 79, 8, + 46, 110, 74, 64, 69, 67, 82, 74, 67, 8, 64, 65, 80, 8, 56, 65, 79, 73, 69, 65, 81, + 65, 79, 80, 18, 8, 110, 62, 65, 79, 8, 64, 69, 65, 2, 83, 75, 79, 81, 132, 65, 81, + 87, 82, 74, 67, 8, 64, 65, 80, 8, 48, 69, 65, 81, 83, 65, 79, 68, 103, 72, 81, 74, + 69, 132, 132, 65, 80, 18, 8, 61, 82, 63, 68, 8, 84, 65, 74, 74, 8, 71, 65, 69, 74, + 65, 8, 46, 110, 74, 64, 69, 67, 82, 74, 67, 8, 83, 75, 79, 72, 69, 65, 67, 81, 18, + 8, 62, 69, 80, 8, 87, 82, 79, 8, 39, 61, 82, 65, 79, 8, 65, 69, 74, 65, 80, 2, + 41, 75, 79, 81, 132, 65, 81, 87, 82, 74, 67, 8, 64, 65, 80, 8, 48, 69, 65, 81, 83, + 65, 79, 68, 103, 72, 81, 74, 69, 132, 132, 65, 80, 18, 8, 61, 82, 63, 68, 8, 84, 65, + 74, 74, 8, 71, 65, 69, 74, 65, 8, 46, 110, 74, 64, 69, 67, 82, 74, 67, 8, 83, 75, + 79, 72, 69, 65, 67, 81, 18, 8, 62, 69, 80, 8, 87, 82, 79, 8, 39, 61, 82, 65, 79, + 8, 65, 69, 74, 65, 80, 2, 45, 61, 68, 79, 65, 80, 8, 132, 75, 84, 69, 65, 8, 110, + 62, 65, 79, 8, 65, 69, 74, 65, 8, 40, 79, 68, 109, 68, 82, 74, 67, 8, 64, 65, 80, + 8, 48, 69, 65, 81, 87, 69, 74, 132, 65, 80, 8, 14, 69, 73, 8, 41, 61, 72, 72, 65, + 8, 64, 65, 79, 8, 41, 75, 79, 81, 132, 65, 81, 87, 82, 74, 67, 15, 8, 87, 82, 62, + 62, 65, 80, 81, 69, 73, 73, 65, 74, 18, 2, 45, 61, 68, 79, 65, 80, 8, 132, 75, 84, + 69, 65, 8, 110, 62, 65, 79, 8, 65, 69, 74, 65, 8, 40, 79, 68, 109, 68, 82, 74, 67, + 8, 64, 65, 80, 8, 48, 69, 65, 81, 87, 69, 74, 132, 65, 80, 8, 14, 69, 73, 8, 41, + 61, 72, 72, 65, 8, 64, 65, 79, 8, 41, 75, 79, 81, 132, 65, 81, 87, 82, 74, 67, 15, + 8, 87, 82, 8, 62, 65, 80, 81, 69, 73, 73, 65, 74, 18, 2, 61, 82, 66, 8, 36, 74, + 79, 82, 66, 65, 74, 8, 65, 69, 74, 65, 80, 8, 56, 65, 79, 73, 69, 65, 81, 65, 79, + 80, 8, 65, 69, 74, 65, 74, 8, 73, 69, 81, 8, 65, 69, 74, 65, 73, 8, 74, 65, 82, + 65, 74, 8, 48, 69, 65, 81, 65, 79, 8, 61, 62, 67, 65, 132, 63, 68, 72, 75, 132, 132, + 65, 74, 65, 74, 8, 48, 69, 65, 81, 83, 65, 79, 81, 79, 61, 67, 18, 2, 61, 82, 66, + 8, 36, 74, 79, 82, 66, 65, 74, 8, 65, 69, 74, 65, 80, 8, 56, 65, 79, 73, 69, 65, + 81, 65, 79, 80, 8, 65, 69, 74, 65, 74, 8, 73, 69, 81, 8, 65, 69, 74, 65, 73, 8, + 74, 65, 82, 65, 74, 8, 48, 69, 65, 81, 65, 79, 8, 61, 62, 67, 65, 132, 63, 68, 72, + 75, 132, 132, 65, 74, 65, 74, 8, 48, 69, 65, 81, 83, 65, 79, 81, 79, 61, 67, 18, 2, + 7, 64, 65, 132, 132, 65, 74, 8, 40, 79, 66, 110, 72, 72, 82, 74, 67, 8, 83, 75, 74, + 8, 65, 69, 74, 65, 79, 8, 40, 74, 81, 132, 63, 68, 65, 69, 64, 82, 74, 67, 8, 110, + 62, 65, 81, 8, 64, 69, 65, 8, 64, 79, 65, 69, 8, 65, 62, 65, 74, 8, 67, 65, 74, + 61, 74, 74, 81, 65, 74, 8, 41, 103, 72, 72, 65, 8, 75, 64, 65, 79, 8, 83, 75, 79, + 8, 65, 69, 74, 65, 73, 2, 64, 65, 132, 132, 65, 74, 8, 40, 79, 66, 110, 72, 72, 82, + 74, 67, 8, 83, 75, 74, 8, 65, 69, 74, 65, 79, 8, 40, 74, 81, 132, 63, 68, 65, 69, + 64, 82, 74, 67, 8, 110, 62, 65, 79, 8, 64, 69, 65, 8, 64, 79, 65, 69, 8, 65, 62, + 65, 74, 8, 67, 65, 74, 61, 74, 74, 81, 65, 74, 8, 41, 103, 72, 72, 65, 8, 75, 64, + 65, 79, 8, 83, 75, 79, 8, 65, 69, 74, 65, 73, 2, 56, 65, 79, 67, 72, 65, 69, 63, + 68, 8, 83, 75, 79, 8, 64, 65, 73, 8, 48, 69, 65, 81, 65, 69, 74, 69, 67, 82, 74, + 67, 80, 61, 73, 81, 8, 67, 65, 81, 79, 75, 66, 66, 65, 74, 8, 84, 69, 79, 64, 18, + 8, 73, 69, 81, 8, 79, 110, 63, 71, 84, 69, 79, 71, 65, 74, 64, 65, 79, 8, 46, 79, + 61, 66, 81, 8, 61, 82, 66, 87, 82, 68, 65, 62, 65, 74, 20, 2, 56, 65, 79, 67, 72, + 65, 69, 63, 68, 8, 83, 75, 79, 8, 64, 65, 73, 8, 48, 69, 65, 81, 65, 69, 74, 69, + 67, 82, 74, 67, 80, 61, 73, 81, 8, 67, 65, 81, 79, 75, 66, 66, 65, 74, 8, 84, 69, + 79, 64, 18, 8, 73, 69, 81, 8, 79, 110, 63, 71, 84, 69, 79, 71, 65, 74, 64, 65, 79, + 8, 46, 79, 61, 66, 81, 8, 61, 82, 66, 87, 82, 68, 65, 62, 65, 74, 20, 2, 102, 65, + 79, 74, 65, 79, 8, 84, 82, 79, 64, 65, 8, 64, 61, 80, 8, 48, 69, 65, 81, 65, 69, + 74, 69, 67, 82, 74, 67, 80, 61, 73, 81, 8, 69, 74, 8, 64, 69, 65, 8, 47, 61, 67, + 65, 20, 67, 65, 132, 65, 81, 87, 81, 18, 8, 64, 69, 65, 8, 40, 79, 72, 61, 82, 62, + 74, 69, 80, 8, 64, 65, 80, 8, 56, 65, 79, 4, 2, 41, 65, 79, 74, 65, 79, 8, 84, + 82, 79, 64, 65, 8, 64, 61, 80, 8, 48, 69, 65, 81, 65, 69, 74, 69, 67, 82, 74, 67, + 80, 61, 73, 81, 8, 69, 74, 8, 64, 69, 65, 8, 47, 61, 67, 65, 8, 67, 65, 132, 65, + 81, 87, 81, 18, 8, 64, 69, 65, 8, 40, 79, 72, 61, 82, 62, 74, 69, 80, 8, 64, 65, + 80, 8, 56, 65, 79, 3, 2, 81, 65, 79, 80, 18, 8, 64, 65, 74, 8, 42, 65, 62, 79, + 61, 82, 63, 68, 8, 64, 65, 79, 8, 67, 65, 73, 69, 65, 81, 65, 81, 65, 74, 8, 53, + 61, 63, 68, 65, 8, 65, 69, 74, 65, 73, 8, 39, 79, 69, 81, 81, 65, 74, 8, 87, 82, + 80, 110, 62, 65, 79, 72, 61, 132, 132, 65, 74, 18, 8, 69, 74, 80, 62, 65, 132, 75, 72, + 64, 65, 79, 65, 8, 64, 69, 65, 2, 73, 69, 65, 81, 65, 79, 80, 18, 8, 64, 65, 74, + 8, 42, 65, 62, 79, 61, 82, 63, 68, 8, 64, 65, 79, 8, 67, 65, 73, 69, 65, 81, 65, + 81, 65, 74, 8, 53, 61, 63, 68, 65, 8, 65, 69, 74, 65, 73, 8, 39, 79, 69, 81, 81, + 65, 74, 8, 87, 82, 8, 110, 62, 65, 79, 72, 61, 132, 132, 65, 74, 18, 8, 69, 74, 80, + 62, 65, 132, 75, 74, 64, 65, 79, 65, 8, 64, 69, 65, 2, 53, 61, 63, 68, 65, 8, 84, + 65, 69, 81, 65, 79, 8, 87, 82, 8, 83, 65, 79, 73, 69, 65, 81, 65, 73, 8, 14, 91, + 8, 27, 26, 31, 8, 36, 62, 132, 20, 8, 23, 8, 64, 65, 80, 8, 37, 42, 37, 20, 15, + 18, 8, 87, 82, 8, 65, 79, 132, 65, 81, 87, 65, 74, 20, 8, 36, 82, 66, 8, 42, 79, + 82, 74, 64, 8, 62, 65, 132, 75, 74, 64, 65, 79, 65, 79, 2, 53, 61, 63, 68, 65, 8, + 84, 65, 69, 81, 65, 79, 8, 87, 82, 8, 83, 65, 79, 73, 69, 65, 81, 65, 74, 8, 14, + 91, 8, 27, 26, 31, 8, 36, 62, 132, 20, 8, 23, 8, 64, 65, 80, 8, 37, 42, 37, 20, + 15, 18, 8, 87, 82, 8, 65, 79, 132, 65, 81, 87, 65, 74, 20, 8, 36, 82, 66, 8, 42, + 79, 82, 74, 64, 8, 62, 65, 132, 75, 74, 64, 65, 79, 65, 79, 2, 39, 82, 79, 63, 68, + 8, 64, 61, 80, 8, 42, 65, 132, 65, 81, 87, 8, 83, 75, 73, 8, 23, 23, 20, 8, 48, + 61, 69, 8, 23, 31, 24, 22, 8, 84, 82, 79, 64, 65, 8, 64, 69, 65, 8, 37, 65, 71, + 61, 74, 74, 81, 73, 61, 63, 68, 82, 74, 67, 8, 87, 82, 73, 8, 53, 63, 68, 82, 81, + 87, 65, 8, 64, 65, 79, 8, 20, 2, 39, 82, 79, 63, 68, 8, 64, 61, 80, 8, 42, 65, + 132, 65, 81, 87, 8, 83, 75, 73, 8, 23, 23, 20, 8, 48, 61, 69, 8, 23, 31, 24, 22, + 8, 84, 82, 79, 64, 65, 8, 64, 69, 65, 8, 37, 65, 71, 61, 74, 74, 81, 73, 61, 63, + 68, 82, 74, 67, 8, 87, 82, 73, 8, 53, 63, 68, 82, 81, 87, 65, 8, 64, 65, 79, 2, + 48, 69, 65, 81, 65, 79, 8, 83, 75, 73, 8, 14, 24, 25, 20, 8, 53, 65, 78, 81, 65, + 73, 62, 65, 79, 8, 23, 31, 23, 30, 8, 21, 8, 24, 24, 20, 8, 45, 82, 74, 69, 8, + 23, 31, 23, 31, 8, 110, 62, 65, 79, 8, 64, 65, 74, 8, 25, 23, 20, 8, 39, 65, 87, + 65, 73, 62, 65, 79, 8, 23, 31, 24, 22, 8, 68, 69, 74, 61, 82, 80, 2, 48, 69, 65, + 81, 65, 79, 8, 83, 75, 73, 8, 14, 24, 25, 20, 8, 53, 65, 78, 81, 65, 73, 62, 65, + 79, 8, 23, 31, 23, 30, 8, 21, 8, 24, 24, 20, 8, 45, 82, 74, 69, 8, 23, 31, 23, + 31, 15, 8, 110, 62, 65, 79, 8, 64, 65, 74, 8, 25, 23, 20, 8, 39, 65, 87, 65, 73, + 62, 65, 79, 8, 23, 31, 24, 22, 8, 68, 69, 74, 61, 82, 80, 2, 62, 69, 80, 8, 61, + 82, 66, 8, 84, 65, 69, 81, 65, 79, 65, 80, 8, 83, 65, 79, 72, 103, 74, 67, 65, 79, + 81, 8, 82, 74, 64, 8, 64, 61, 80, 8, 40, 69, 74, 69, 67, 82, 74, 67, 80, 61, 73, + 81, 8, 87, 82, 79, 8, 40, 74, 81, 132, 63, 68, 65, 69, 64, 82, 74, 67, 8, 110, 62, + 65, 79, 8, 37, 65, 132, 63, 68, 84, 65, 79, 64, 65, 74, 8, 67, 65, 67, 65, 74, 2, + 62, 69, 80, 8, 61, 82, 66, 8, 84, 65, 69, 81, 65, 79, 65, 80, 8, 83, 65, 79, 72, + 103, 74, 67, 65, 79, 81, 8, 82, 74, 64, 8, 64, 61, 80, 8, 40, 69, 74, 69, 67, 82, + 74, 67, 80, 61, 73, 81, 8, 87, 82, 79, 8, 40, 74, 81, 132, 63, 68, 65, 69, 64, 82, + 74, 67, 8, 110, 62, 65, 79, 8, 37, 65, 132, 63, 68, 84, 65, 79, 64, 65, 74, 8, 67, + 65, 67, 65, 74, 2, 65, 69, 74, 65, 8, 83, 75, 74, 8, 64, 65, 79, 8, 42, 65, 73, + 71, 69, 74, 64, 65, 62, 65, 68, 109, 79, 64, 65, 8, 61, 82, 66, 8, 42, 79, 82, 74, + 64, 8, 64, 65, 79, 8, 57, 75, 68, 74, 82, 74, 67, 80, 73, 61, 74, 67, 65, 72, 83, + 65, 79, 75, 79, 64, 74, 82, 74, 67, 8, 69, 73, 8, 40, 69, 74, 87, 65, 72, 66, 61, + 72, 72, 65, 8, 0, 16, 2, 65, 69, 74, 65, 8, 83, 75, 74, 8, 64, 65, 79, 8, 42, + 65, 73, 65, 69, 74, 64, 65, 62, 65, 68, 109, 79, 64, 65, 8, 61, 82, 66, 8, 42, 79, + 82, 74, 64, 8, 64, 65, 79, 8, 57, 75, 68, 74, 82, 74, 67, 80, 73, 61, 74, 67, 65, + 72, 83, 65, 79, 75, 79, 64, 74, 82, 74, 67, 8, 69, 73, 8, 40, 69, 74, 87, 65, 72, + 66, 61, 72, 72, 65, 2, 67, 65, 81, 79, 75, 66, 66, 65, 74, 65, 8, 42, 65, 79, 66, + 110, 74, 82, 74, 67, 8, 66, 110, 79, 8, 87, 82, 132, 81, 103, 74, 64, 69, 67, 8, 65, + 79, 71, 72, 103, 79, 81, 20, 2, 67, 65, 81, 79, 75, 66, 66, 65, 74, 65, 8, 42, 65, + 79, 66, 110, 74, 82, 74, 67, 8, 66, 110, 79, 8, 87, 82, 132, 81, 103, 74, 64, 69, 67, + 8, 65, 79, 71, 72, 103, 79, 81, 20, 2, 53, 75, 64, 61, 74, 74, 8, 132, 69, 74, 64, + 8, 64, 69, 65, 8, 40, 69, 74, 69, 67, 82, 74, 67, 80, 103, 73, 81, 65, 79, 8, 67, + 65, 73, 103, 102, 8, 91, 8, 29, 26, 8, 64, 65, 79, 8, 36, 82, 80, 66, 110, 68, 79, + 82, 74, 67, 80, 62, 65, 132, 81, 69, 73, 73, 82, 74, 67, 65, 74, 8, 87, 82, 73, 2, + 53, 75, 64, 61, 74, 74, 8, 132, 69, 74, 64, 8, 64, 69, 65, 8, 40, 69, 74, 69, 67, + 82, 74, 67, 80, 103, 73, 81, 65, 79, 8, 67, 65, 73, 103, 102, 8, 91, 8, 29, 26, 8, + 64, 65, 79, 8, 36, 82, 80, 66, 110, 68, 79, 82, 74, 67, 80, 62, 65, 132, 81, 69, 73, + 73, 82, 74, 67, 65, 74, 8, 87, 82, 73, 2, 83, 75, 68, 72, 65, 74, 132, 81, 65, 82, + 65, 79, 67, 65, 132, 65, 81, 87, 8, 14, 83, 75, 73, 8, 30, 20, 8, 36, 78, 79, 69, + 72, 8, 23, 31, 23, 29, 15, 8, 61, 72, 80, 8, 53, 63, 68, 69, 65, 64, 80, 132, 81, + 65, 72, 72, 65, 8, 65, 79, 79, 69, 63, 68, 81, 65, 81, 8, 66, 110, 79, 8, 64, 69, + 65, 8, 61, 82, 80, 8, 64, 65, 79, 8, 36, 74, 3, 4, 2, 46, 75, 68, 72, 65, 74, + 132, 81, 65, 82, 65, 79, 67, 65, 132, 65, 81, 87, 8, 14, 83, 75, 73, 8, 30, 20, 8, + 36, 78, 79, 69, 72, 8, 23, 31, 23, 29, 15, 8, 61, 72, 80, 8, 53, 63, 68, 69, 65, + 64, 80, 132, 81, 65, 72, 72, 65, 8, 65, 79, 79, 69, 63, 68, 81, 65, 81, 8, 66, 110, + 79, 8, 64, 69, 65, 8, 61, 82, 80, 8, 64, 65, 79, 8, 36, 74, 3, 2, 84, 65, 74, + 64, 82, 74, 67, 8, 64, 65, 80, 8, 91, 86, 25, 29, 8, 36, 62, 132, 20, 8, 25, 8, + 64, 65, 80, 8, 46, 75, 68, 72, 65, 74, 132, 81, 65, 82, 65, 79, 74, 65, 132, 65, 81, + 87, 65, 80, 8, 68, 65, 79, 79, 110, 68, 79, 65, 74, 64, 65, 74, 8, 53, 81, 79, 65, + 69, 81, 69, 67, 71, 65, 69, 81, 65, 74, 8, 87, 84, 69, 132, 63, 68, 65, 74, 2, 84, + 65, 74, 64, 82, 74, 67, 8, 64, 65, 80, 8, 91, 8, 25, 29, 8, 36, 62, 132, 20, 8, + 25, 8, 64, 65, 80, 8, 46, 75, 68, 72, 65, 74, 132, 81, 65, 82, 65, 79, 74, 65, 132, + 65, 81, 87, 65, 80, 8, 68, 65, 79, 79, 110, 68, 79, 65, 74, 64, 65, 74, 8, 53, 81, + 79, 65, 69, 81, 69, 67, 71, 65, 69, 81, 65, 74, 8, 87, 84, 69, 132, 63, 68, 65, 74, + 2, 110, 62, 65, 79, 8, 53, 61, 73, 73, 65, 72, 68, 65, 69, 87, 82, 74, 67, 8, 82, + 74, 64, 8, 57, 61, 79, 73, 84, 103, 132, 132, 65, 79, 83, 65, 79, 132, 75, 79, 67, 82, + 74, 67, 80, 61, 74, 72, 61, 67, 65, 74, 8, 69, 74, 8, 48, 69, 65, 81, 79, 103, 82, + 73, 65, 74, 8, 83, 75, 73, 8, 24, 20, 8, 49, 75, 83, 65, 73, 62, 65, 79, 2, 110, + 62, 65, 79, 8, 53, 61, 73, 73, 65, 72, 68, 65, 69, 87, 82, 74, 67, 8, 82, 74, 64, + 8, 57, 61, 79, 73, 84, 61, 132, 132, 65, 79, 83, 65, 79, 132, 75, 79, 67, 82, 74, 67, + 80, 61, 74, 72, 61, 67, 65, 74, 8, 69, 74, 8, 48, 69, 65, 81, 79, 103, 82, 73, 65, + 74, 8, 83, 75, 73, 8, 24, 20, 8, 49, 75, 83, 65, 73, 62, 65, 79, 2, 29, 8, 68, + 65, 132, 81, 69, 73, 73, 81, 65, 8, 64, 69, 65, 8, 48, 69, 65, 81, 65, 69, 74, 69, + 67, 82, 74, 67, 80, 103, 73, 81, 65, 79, 8, 61, 72, 80, 8, 53, 63, 68, 69, 65, 62, + 80, 132, 81, 65, 72, 72, 65, 74, 8, 66, 110, 79, 8, 53, 81, 79, 65, 69, 81, 69, 67, + 71, 65, 69, 81, 65, 74, 18, 2, 23, 31, 23, 29, 8, 62, 65, 132, 81, 69, 73, 73, 81, + 65, 8, 64, 69, 65, 8, 48, 69, 65, 81, 65, 69, 74, 69, 67, 82, 74, 67, 80, 103, 73, + 81, 65, 79, 8, 61, 72, 80, 8, 53, 63, 68, 69, 65, 64, 80, 132, 81, 65, 72, 72, 65, + 74, 8, 66, 110, 79, 8, 53, 81, 79, 65, 69, 81, 69, 67, 71, 65, 69, 81, 65, 74, 18, + 2, 56, 65, 79, 75, 79, 64, 74, 82, 74, 67, 8, 110, 62, 65, 79, 8, 48, 69, 65, 81, + 65, 79, 132, 63, 68, 82, 81, 87, 8, 82, 74, 64, 8, 53, 61, 73, 73, 65, 72, 68, 65, + 69, 87, 82, 74, 67, 8, 65, 79, 67, 69, 74, 67, 8, 61, 73, 8, 24, 24, 20, 8, 45, + 82, 74, 69, 8, 23, 31, 23, 31, 20, 8, 3, 8, 3, 2, 56, 65, 79, 75, 79, 64, 74, + 82, 74, 67, 8, 110, 62, 65, 79, 8, 48, 69, 65, 81, 65, 79, 132, 63, 68, 82, 81, 87, + 8, 82, 74, 64, 8, 53, 61, 73, 73, 65, 72, 68, 65, 69, 87, 82, 74, 67, 8, 65, 79, + 67, 69, 74, 67, 8, 61, 73, 8, 24, 24, 20, 8, 45, 82, 74, 69, 8, 23, 31, 23, 31, + 20, 8, 3, 8, 3, 2, 46, 75, 68, 72, 65, 74, 132, 81, 65, 82, 65, 79, 4, 40, 79, + 132, 81, 61, 81, 81, 82, 74, 67, 80, 61, 74, 132, 78, 79, 110, 63, 68, 65, 8, 64, 65, + 79, 8, 56, 65, 79, 73, 69, 65, 81, 65, 79, 8, 61, 72, 80, 8, 61, 82, 63, 68, 8, + 48, 69, 74, 64, 65, 79, 82, 74, 67, 80, 61, 74, 132, 78, 79, 110, 63, 68, 65, 8, 64, + 65, 79, 8, 48, 69, 2, 46, 75, 68, 72, 65, 74, 132, 81, 65, 82, 65, 79, 19, 40, 79, + 132, 81, 61, 81, 81, 82, 74, 67, 80, 61, 74, 132, 78, 79, 110, 63, 68, 65, 8, 64, 65, + 79, 8, 56, 65, 79, 73, 69, 65, 81, 65, 79, 8, 61, 72, 80, 8, 61, 82, 63, 68, 8, + 48, 69, 74, 64, 65, 79, 82, 74, 67, 80, 61, 74, 132, 78, 79, 110, 63, 68, 65, 8, 64, + 65, 79, 8, 48, 69, 69, 2, 52, 65, 61, 72, 132, 63, 68, 82, 72, 65, 8, 45, 44, 44, + 8, 84, 82, 79, 64, 65, 8, 48, 69, 63, 68, 61, 65, 72, 69, 80, 8, 23, 31, 23, 28, + 8, 64, 69, 65, 8, 45, 56, 8, 48, 18, 8, 48, 69, 63, 68, 61, 65, 72, 69, 80, 8, + 23, 31, 23, 29, 8, 64, 69, 65, 8, 55, 8, 44, 44, 45, 8, 48, 8, 82, 74, 64, 2, + 52, 65, 61, 72, 132, 63, 68, 82, 72, 65, 8, 44, 45, 44, 45, 8, 84, 82, 79, 64, 65, + 8, 48, 69, 63, 68, 61, 65, 72, 69, 80, 8, 23, 31, 23, 28, 8, 64, 69, 65, 8, 44, + 45, 56, 8, 48, 18, 8, 48, 69, 63, 68, 61, 65, 72, 69, 80, 8, 23, 31, 23, 29, 8, + 64, 69, 65, 8, 55, 8, 44, 44, 44, 45, 8, 48, 8, 82, 74, 64, 2, 48, 69, 63, 68, + 61, 65, 72, 69, 80, 8, 23, 31, 23, 31, 8, 64, 69, 65, 8, 50, 8, 45, 44, 45, 8, + 48, 65, 79, 109, 66, 66, 74, 65, 81, 20, 8, 41, 65, 79, 74, 65, 85, 8, 84, 82, 79, + 64, 65, 74, 8, 50, 132, 81, 65, 79, 74, 8, 23, 31, 23, 30, 18, 8, 23, 31, 23, 31, + 8, 82, 74, 64, 8, 23, 31, 24, 22, 8, 65, 69, 74, 65, 2, 48, 69, 63, 68, 61, 65, + 72, 69, 80, 8, 23, 31, 23, 31, 8, 64, 69, 65, 8, 50, 8, 44, 45, 44, 45, 8, 48, + 8, 65, 79, 109, 66, 66, 74, 65, 81, 20, 8, 41, 65, 79, 74, 65, 79, 8, 84, 82, 79, + 64, 65, 74, 8, 50, 132, 81, 65, 79, 74, 8, 23, 31, 23, 30, 18, 8, 23, 31, 23, 31, + 8, 82, 74, 64, 8, 23, 31, 24, 22, 8, 65, 69, 74, 65, 2, 56, 44, 8, 50, 18, 8, + 57, 8, 82, 74, 64, 8, 45, 56, 8, 50, 8, 65, 79, 109, 66, 66, 74, 65, 81, 18, 8, + 82, 73, 8, 14, 51, 72, 61, 81, 87, 8, 66, 110, 79, 8, 64, 69, 65, 8, 53, 63, 68, + 110, 72, 65, 79, 8, 87, 82, 8, 132, 63, 68, 61, 66, 66, 65, 74, 15, 18, 8, 64, 69, + 65, 8, 84, 65, 67, 65, 74, 8, 51, 72, 61, 81, 87, 73, 61, 74, 67, 65, 72, 80, 2, + 56, 44, 8, 50, 18, 8, 56, 50, 8, 82, 74, 64, 8, 44, 57, 56, 8, 50, 8, 65, 79, + 109, 66, 66, 74, 65, 81, 18, 8, 82, 73, 8, 14, 51, 72, 61, 81, 87, 8, 66, 110, 79, + 8, 64, 69, 65, 8, 53, 63, 68, 110, 72, 65, 79, 8, 87, 82, 8, 132, 63, 68, 61, 66, + 66, 65, 74, 15, 18, 8, 64, 69, 65, 8, 84, 65, 67, 65, 74, 8, 51, 72, 61, 81, 87, + 73, 61, 74, 67, 65, 72, 80, 2, 69, 74, 8, 61, 74, 64, 65, 79, 65, 8, 68, 109, 68, + 65, 79, 65, 8, 53, 63, 68, 82, 72, 65, 74, 8, 74, 69, 63, 68, 81, 8, 61, 82, 66, + 67, 65, 74, 75, 73, 73, 65, 74, 8, 84, 65, 79, 64, 65, 74, 8, 71, 75, 74, 74, 81, + 65, 74, 20, 2, 69, 74, 8, 61, 74, 64, 65, 79, 65, 8, 68, 109, 68, 65, 79, 65, 8, + 53, 63, 68, 82, 72, 65, 74, 8, 74, 69, 63, 68, 81, 8, 61, 82, 66, 67, 65, 74, 75, + 73, 73, 65, 74, 8, 84, 65, 79, 64, 65, 74, 8, 71, 75, 74, 74, 81, 65, 74, 20, 2, + 40, 74, 64, 65, 8, 23, 31, 23, 30, 8, 84, 61, 79, 65, 74, 8, 61, 72, 132, 75, 8, + 66, 75, 72, 67, 65, 74, 64, 65, 8, 132, 81, 103, 64, 81, 69, 132, 63, 68, 65, 8, 53, + 63, 68, 82, 72, 65, 74, 8, 83, 75, 79, 68, 61, 74, 64, 65, 74, 32, 8, 39, 61, 80, + 8, 48, 75, 73, 73, 132, 65, 74, 19, 6, 2, 40, 74, 64, 65, 8, 23, 31, 23, 30, 8, + 84, 61, 79, 65, 74, 8, 61, 72, 132, 75, 8, 66, 75, 72, 67, 65, 74, 64, 65, 8, 132, + 81, 103, 64, 81, 69, 132, 63, 68, 65, 8, 53, 63, 68, 82, 72, 65, 74, 8, 83, 75, 79, + 68, 61, 74, 64, 65, 74, 32, 8, 39, 61, 80, 8, 48, 75, 73, 73, 132, 65, 74, 3, 2, + 67, 86, 73, 74, 61, 132, 69, 82, 73, 32, 8, 73, 69, 81, 8, 52, 65, 61, 72, 132, 63, + 68, 82, 72, 65, 18, 8, 64, 61, 80, 8, 53, 63, 68, 69, 72, 72, 65, 79, 19, 52, 65, + 61, 72, 67, 86, 73, 74, 61, 132, 69, 82, 73, 18, 8, 64, 69, 65, 8, 53, 69, 65, 73, + 65, 74, 80, 4, 2, 67, 86, 73, 74, 61, 132, 69, 82, 73, 8, 73, 69, 81, 8, 52, 65, + 61, 72, 132, 63, 68, 82, 72, 65, 18, 8, 64, 61, 80, 8, 53, 63, 68, 69, 72, 72, 65, + 79, 19, 52, 65, 61, 72, 67, 86, 73, 74, 61, 132, 69, 82, 73, 18, 8, 64, 69, 65, 8, + 53, 69, 65, 73, 65, 74, 80, 3, 2, 82, 74, 64, 8, 64, 69, 65, 8, 47, 65, 72, 62, + 74, 69, 87, 4, 50, 62, 65, 79, 79, 65, 61, 72, 132, 63, 68, 82, 72, 65, 18, 8, 64, + 69, 65, 8, 43, 69, 74, 64, 65, 74, 62, 82, 79, 67, 4, 52, 65, 61, 72, 132, 63, 68, + 82, 72, 65, 18, 8, 64, 69, 65, 2, 82, 74, 64, 8, 64, 69, 65, 8, 47, 65, 69, 62, + 74, 69, 87, 19, 50, 62, 65, 79, 79, 65, 61, 72, 132, 63, 68, 82, 72, 65, 18, 8, 64, + 69, 65, 8, 43, 69, 74, 64, 65, 74, 62, 82, 79, 67, 19, 52, 65, 61, 72, 132, 63, 68, + 82, 72, 65, 18, 8, 64, 69, 65, 2, 52, 65, 61, 72, 132, 63, 68, 82, 72, 65, 8, 44, + 45, 8, 82, 74, 64, 8, 64, 69, 65, 8, 52, 65, 61, 72, 132, 63, 68, 82, 72, 65, 8, + 45, 44, 44, 8, 73, 69, 81, 8, 64, 65, 74, 8, 46, 72, 61, 132, 132, 65, 74, 8, 56, + 44, 8, 62, 69, 80, 8, 50, 8, 45, 44, 44, 33, 8, 64, 69, 65, 8, 46, 61, 69, 132, + 65, 79, 19, 2, 52, 65, 61, 72, 132, 63, 68, 82, 72, 65, 8, 44, 45, 8, 82, 74, 64, + 8, 64, 69, 65, 8, 52, 65, 61, 72, 132, 63, 68, 82, 72, 65, 8, 44, 44, 45, 8, 73, + 69, 81, 8, 64, 65, 74, 8, 46, 72, 61, 132, 132, 65, 74, 8, 56, 44, 8, 62, 69, 80, + 8, 50, 8, 45, 45, 45, 33, 8, 64, 69, 65, 8, 46, 61, 69, 132, 65, 79, 4, 2, 41, + 79, 69, 65, 64, 79, 69, 63, 68, 132, 63, 68, 82, 72, 65, 8, 14, 42, 86, 73, 74, 61, + 132, 69, 82, 73, 8, 82, 74, 64, 8, 52, 65, 61, 72, 132, 63, 68, 82, 72, 65, 8, 73, + 69, 81, 8, 67, 65, 73, 65, 69, 74, 132, 61, 73, 65, 73, 8, 55, 74, 81, 65, 79, 62, + 61, 82, 15, 8, 82, 74, 64, 8, 64, 69, 65, 2, 41, 79, 69, 65, 64, 79, 69, 63, 68, + 132, 63, 68, 82, 72, 65, 8, 14, 42, 86, 73, 74, 61, 132, 69, 82, 73, 8, 82, 74, 64, + 8, 52, 65, 61, 72, 132, 63, 68, 82, 72, 65, 8, 73, 69, 81, 8, 67, 65, 73, 65, 69, + 74, 132, 61, 73, 65, 73, 8, 55, 74, 81, 65, 79, 62, 61, 82, 15, 8, 82, 74, 64, 8, + 64, 69, 65, 2, 43, 65, 79, 64, 65, 79, 132, 63, 68, 82, 72, 65, 8, 14, 52, 65, 61, + 72, 67, 86, 73, 74, 61, 132, 69, 82, 73, 8, 82, 74, 64, 8, 52, 65, 61, 72, 132, 63, + 68, 82, 72, 65, 8, 73, 69, 81, 8, 67, 65, 73, 65, 69, 74, 132, 61, 73, 65, 73, 8, + 55, 74, 81, 65, 79, 62, 61, 82, 15, 20, 8, 44, 74, 8, 64, 65, 74, 2, 43, 65, 79, + 64, 65, 79, 132, 63, 68, 82, 72, 65, 8, 14, 52, 65, 61, 72, 67, 86, 73, 74, 61, 132, + 69, 82, 73, 8, 82, 74, 64, 8, 52, 65, 61, 72, 132, 63, 68, 82, 72, 65, 8, 73, 69, + 81, 8, 67, 65, 73, 65, 69, 74, 132, 61, 73, 65, 73, 8, 55, 74, 81, 65, 79, 62, 61, + 82, 15, 20, 8, 44, 74, 8, 64, 65, 74, 2, 65, 79, 132, 81, 65, 74, 8, 29, 8, 53, + 63, 68, 82, 72, 65, 74, 8, 84, 82, 79, 64, 65, 8, 74, 61, 63, 68, 8, 64, 65, 74, + 8, 61, 72, 72, 67, 65, 73, 65, 69, 74, 65, 74, 8, 47, 65, 68, 79, 78, 72, 103, 74, + 65, 74, 18, 8, 69, 74, 8, 64, 65, 74, 8, 72, 65, 81, 87, 81, 65, 74, 8, 62, 65, + 69, 64, 65, 74, 8, 74, 61, 63, 68, 20, 64, 65, 74, 2, 65, 79, 132, 81, 65, 74, 8, + 29, 8, 53, 63, 68, 82, 72, 65, 74, 8, 84, 82, 79, 64, 65, 8, 74, 61, 63, 68, 8, + 64, 65, 74, 8, 61, 72, 72, 67, 65, 73, 65, 69, 74, 65, 74, 8, 47, 65, 68, 79, 78, + 72, 103, 74, 65, 74, 18, 8, 69, 74, 8, 64, 65, 74, 8, 72, 65, 81, 87, 81, 65, 74, + 8, 62, 65, 69, 64, 65, 74, 8, 74, 61, 63, 68, 8, 64, 65, 74, 2, 41, 79, 61, 74, + 71, 66, 82, 79, 81, 65, 79, 8, 47, 65, 68, 79, 78, 72, 103, 74, 65, 74, 8, 82, 74, + 81, 65, 79, 79, 69, 63, 68, 81, 65, 81, 20, 2, 41, 79, 61, 74, 71, 66, 82, 79, 81, + 65, 79, 8, 47, 65, 68, 79, 78, 72, 103, 74, 65, 74, 8, 82, 74, 81, 65, 79, 79, 69, + 63, 68, 81, 65, 81, 20, 2, 54, 79, 75, 81, 87, 8, 87, 61, 68, 72, 79, 65, 69, 63, + 68, 65, 79, 8, 40, 69, 74, 62, 65, 79, 82, 66, 82, 74, 67, 65, 74, 8, 83, 75, 74, + 8, 47, 65, 68, 79, 71, 79, 103, 66, 81, 65, 74, 8, 87, 82, 73, 81, 8, 43, 65, 65, + 79, 65, 80, 64, 69, 65, 74, 132, 81, 65, 8, 71, 75, 74, 74, 81, 65, 8, 64, 65, 79, + 8, 55, 74, 81, 65, 79, 79, 69, 63, 68, 81, 80, 62, 65, 81, 79, 69, 65, 62, 2, 54, + 79, 75, 81, 87, 8, 87, 61, 68, 72, 79, 65, 69, 63, 68, 65, 79, 8, 40, 69, 74, 62, + 65, 79, 82, 66, 82, 74, 67, 65, 74, 8, 83, 75, 74, 8, 47, 65, 68, 79, 71, 79, 103, + 66, 81, 65, 74, 8, 87, 82, 73, 8, 43, 65, 65, 79, 65, 80, 64, 69, 65, 74, 132, 81, + 65, 8, 71, 75, 74, 74, 81, 65, 8, 64, 65, 79, 8, 55, 74, 81, 65, 79, 79, 69, 63, + 68, 81, 80, 62, 65, 81, 79, 69, 65, 62, 2, 61, 74, 8, 61, 72, 72, 65, 74, 8, 68, + 109, 68, 65, 79, 65, 74, 8, 46, 74, 61, 62, 65, 74, 132, 63, 68, 82, 72, 65, 74, 8, + 61, 82, 66, 79, 65, 63, 68, 81, 8, 65, 79, 68, 61, 72, 81, 65, 74, 8, 84, 65, 79, + 64, 65, 74, 20, 2, 61, 74, 8, 61, 72, 72, 65, 74, 8, 68, 109, 68, 65, 79, 65, 74, + 8, 46, 74, 61, 62, 65, 74, 132, 63, 68, 82, 72, 65, 74, 8, 61, 82, 66, 79, 65, 63, + 68, 81, 8, 65, 79, 68, 61, 72, 81, 65, 74, 8, 84, 65, 79, 64, 65, 74, 20, 2, 39, + 69, 65, 8, 37, 65, 132, 63, 68, 61, 66, 66, 82, 74, 67, 8, 83, 75, 74, 8, 43, 69, + 72, 66, 80, 72, 65, 68, 79, 71, 79, 103, 66, 81, 65, 74, 8, 67, 65, 132, 81, 61, 72, + 81, 65, 81, 65, 8, 132, 69, 63, 68, 2, 39, 69, 65, 8, 37, 65, 132, 63, 68, 61, 66, + 66, 82, 74, 67, 8, 83, 75, 74, 8, 43, 69, 72, 66, 80, 72, 65, 68, 79, 71, 79, 103, + 66, 81, 65, 74, 8, 67, 65, 132, 81, 61, 72, 81, 65, 81, 65, 8, 132, 69, 63, 68, 2, + 61, 62, 65, 79, 8, 132, 65, 68, 79, 8, 132, 63, 68, 84, 69, 65, 79, 69, 67, 20, 8, + 14, 44, 73, 18, 8, 57, 69, 74, 81, 65, 79, 68, 61, 72, 62, 70, 61, 68, 79, 8, 23, + 31, 23, 30, 8, 84, 82, 79, 64, 65, 74, 8, 73, 65, 68, 79, 65, 79, 65, 8, 53, 63, + 68, 82, 72, 65, 74, 2, 61, 62, 65, 79, 8, 132, 65, 68, 79, 8, 132, 63, 68, 84, 69, + 65, 79, 69, 67, 20, 8, 14, 44, 73, 8, 57, 69, 74, 81, 65, 79, 68, 61, 72, 62, 70, + 61, 68, 79, 8, 23, 31, 23, 30, 8, 84, 82, 79, 64, 65, 74, 8, 73, 65, 68, 79, 65, + 79, 65, 8, 53, 63, 68, 82, 72, 65, 74, 2, 82, 79, 8, 55, 74, 81, 65, 79, 62, 79, + 69, 74, 67, 82, 74, 67, 8, 83, 75, 74, 8, 48, 69, 72, 69, 81, 103, 79, 18, 8, 74, + 61, 73, 65, 74, 81, 72, 69, 63, 68, 8, 74, 61, 63, 68, 8, 64, 65, 73, 8, 40, 69, + 74, 81, 79, 65, 66, 66, 65, 74, 8, 64, 65, 79, 8, 41, 65, 72, 64, 81, 79, 82, 78, + 78, 65, 74, 18, 2, 87, 82, 79, 8, 55, 74, 81, 65, 79, 62, 79, 69, 74, 67, 82, 74, + 67, 8, 83, 75, 74, 8, 48, 69, 72, 69, 81, 103, 79, 18, 8, 74, 61, 73, 65, 74, 81, + 72, 69, 63, 68, 8, 74, 61, 63, 68, 8, 64, 65, 73, 8, 40, 69, 74, 81, 79, 65, 66, + 66, 65, 74, 8, 64, 65, 79, 8, 41, 65, 72, 64, 81, 79, 82, 78, 78, 65, 74, 18, 2, + 69, 74, 8, 36, 72, 69, 132, 78, 79, 82, 63, 68, 8, 67, 65, 74, 75, 73, 73, 65, 74, + 20, 15, 8, 44, 74, 66, 75, 72, 67, 65, 64, 65, 132, 132, 65, 74, 8, 73, 82, 102, 81, + 65, 74, 18, 61, 74, 64, 65, 79, 65, 8, 53, 63, 68, 82, 72, 67, 65, 62, 103, 82, 64, + 65, 8, 64, 75, 78, 78, 65, 72, 81, 8, 62, 65, 72, 65, 67, 81, 18, 2, 69, 74, 8, + 36, 74, 132, 78, 79, 82, 63, 68, 8, 67, 65, 74, 75, 73, 73, 65, 74, 20, 15, 8, 44, + 74, 66, 75, 72, 67, 65, 64, 65, 132, 132, 65, 74, 8, 73, 82, 102, 81, 65, 74, 8, 61, + 74, 64, 65, 79, 65, 8, 53, 63, 68, 82, 72, 67, 65, 62, 103, 82, 64, 65, 8, 64, 75, + 78, 78, 65, 72, 81, 8, 62, 65, 72, 65, 67, 81, 18, 2, 64, 65, 79, 8, 55, 74, 81, + 65, 79, 79, 69, 63, 68, 81, 8, 61, 82, 66, 8, 25, 22, 8, 46, 82, 79, 87, 132, 81, + 82, 74, 64, 65, 74, 8, 62, 65, 132, 63, 68, 79, 103, 74, 71, 81, 8, 82, 74, 64, 8, + 69, 74, 8, 56, 75, 79, 73, 69, 81, 81, 61, 67, 80, 19, 8, 82, 74, 64, 8, 49, 61, + 63, 68, 4, 2, 64, 65, 79, 8, 55, 74, 81, 65, 79, 79, 69, 63, 68, 81, 8, 61, 82, + 66, 8, 25, 22, 8, 46, 82, 79, 87, 132, 81, 82, 74, 64, 65, 74, 8, 62, 65, 132, 63, + 68, 79, 103, 74, 71, 81, 8, 82, 74, 64, 8, 69, 74, 8, 56, 75, 79, 73, 69, 81, 81, + 61, 67, 80, 19, 8, 82, 74, 64, 8, 49, 61, 63, 68, 4, 2, 73, 69, 81, 81, 61, 67, + 80, 82, 74, 81, 65, 79, 79, 69, 63, 68, 81, 8, 67, 65, 81, 65, 69, 72, 81, 8, 84, + 65, 79, 64, 65, 74, 20, 2, 73, 69, 81, 81, 61, 67, 80, 82, 74, 81, 65, 79, 79, 69, + 63, 68, 81, 8, 67, 65, 81, 65, 69, 72, 81, 8, 84, 65, 79, 64, 65, 74, 20, 2, 36, + 82, 66, 8, 42, 65, 73, 65, 69, 74, 64, 65, 62, 65, 132, 63, 68, 72, 82, 102, 8, 83, + 75, 73, 8, 26, 20, 21, 23, 24, 20, 8, 24, 20, 8, 23, 31, 24, 22, 8, 84, 82, 79, + 64, 65, 8, 50, 132, 81, 65, 79, 74, 8, 23, 31, 24, 22, 8, 73, 69, 81, 8, 64, 65, + 73, 8, 36, 62, 62, 61, 82, 8, 64, 65, 79, 8, 56, 75, 79, 132, 63, 68, 82, 72, 65, + 74, 2, 36, 82, 66, 8, 42, 65, 73, 65, 69, 74, 64, 65, 62, 65, 132, 63, 68, 72, 82, + 102, 8, 83, 75, 73, 8, 26, 20, 21, 23, 24, 20, 8, 24, 20, 8, 23, 31, 24, 22, 8, + 84, 82, 79, 64, 65, 8, 50, 132, 81, 65, 79, 74, 8, 23, 31, 24, 22, 8, 73, 69, 81, + 8, 64, 65, 73, 8, 36, 62, 62, 61, 82, 8, 64, 65, 79, 8, 56, 75, 79, 132, 63, 68, + 82, 72, 65, 74, 2, 62, 65, 67, 75, 74, 74, 65, 74, 20, 8, 14, 60, 82, 79, 8, 65, + 79, 132, 81, 65, 74, 8, 36, 82, 80, 132, 81, 61, 81, 81, 82, 74, 67, 8, 73, 69, 81, + 8, 55, 74, 81, 65, 79, 79, 69, 63, 68, 81, 80, 73, 69, 81, 81, 65, 72, 74, 8, 65, + 79, 68, 69, 65, 72, 81, 8, 64, 69, 65, 2, 62, 65, 67, 75, 74, 74, 65, 74, 20, 8, + 14, 60, 82, 79, 8, 65, 79, 132, 81, 65, 74, 8, 36, 82, 80, 132, 81, 61, 81, 81, 82, + 74, 67, 8, 73, 69, 81, 8, 55, 74, 81, 65, 79, 79, 69, 63, 68, 81, 80, 73, 69, 81, + 81, 65, 72, 74, 8, 65, 79, 68, 69, 65, 72, 81, 8, 64, 69, 65, 2, 52, 65, 61, 72, + 132, 63, 68, 82, 72, 65, 8, 45, 45, 8, 54, 65, 69, 72, 68, 65, 81, 79, 103, 67, 65, + 15, 8, 83, 75, 74, 8, 25, 22, 22, 22, 8, 18, 8, 26, 22, 22, 22, 8, 25, 22, 22, + 22, 8, 82, 74, 64, 8, 24, 27, 22, 22, 8, 48, 18, 2, 52, 65, 61, 72, 132, 63, 68, + 82, 72, 65, 8, 44, 45, 8, 54, 65, 69, 72, 62, 65, 81, 79, 103, 67, 65, 15, 8, 83, + 75, 74, 8, 25, 22, 22, 22, 8, 11, 18, 8, 26, 22, 22, 22, 8, 7, 18, 8, 25, 22, + 22, 22, 8, 7, 8, 82, 74, 64, 8, 24, 27, 22, 22, 8, 36, 18, 2, 64, 69, 65, 8, + 52, 65, 61, 72, 132, 63, 68, 82, 72, 65, 8, 44, 44, 8, 64, 65, 74, 8, 65, 79, 132, + 81, 65, 74, 8, 82, 74, 64, 8, 87, 84, 65, 69, 81, 65, 74, 8, 54, 65, 69, 72, 62, + 65, 81, 79, 61, 67, 2, 64, 69, 65, 8, 52, 65, 61, 72, 132, 63, 68, 82, 72, 65, 8, + 44, 44, 45, 8, 64, 65, 74, 8, 65, 79, 132, 81, 65, 74, 8, 82, 74, 64, 8, 87, 84, + 65, 69, 81, 65, 74, 8, 54, 65, 69, 72, 62, 65, 81, 79, 61, 67, 2, 83, 75, 74, 8, + 70, 65, 8, 24, 22, 22, 22, 8, 0, 12, 8, 53, 81, 61, 64, 81, 83, 65, 79, 75, 79, + 64, 74, 65, 81, 65, 74, 4, 56, 65, 79, 132, 61, 73, 73, 72, 82, 74, 67, 20, 8, 67, + 8, 31, 20, 15, 2, 83, 75, 74, 8, 70, 65, 8, 24, 22, 22, 22, 8, 53, 81, 61, 64, + 81, 83, 65, 79, 75, 79, 64, 74, 65, 81, 65, 74, 19, 56, 65, 79, 132, 61, 73, 73, 72, + 82, 74, 67, 20, 8, 14, 91, 8, 31, 20, 15, 2, 37, 61, 64, 65, 18, 8, 43, 65, 79, + 73, 61, 74, 74, 18, 8, 53, 63, 68, 61, 74, 71, 84, 69, 79, 81, 18, 8, 46, 61, 69, + 132, 65, 79, 69, 74, 4, 2, 37, 61, 64, 65, 18, 8, 43, 65, 79, 73, 61, 74, 74, 18, + 8, 53, 63, 68, 61, 74, 71, 84, 69, 79, 81, 18, 8, 46, 61, 69, 132, 65, 79, 69, 74, + 3, 2, 36, 82, 67, 82, 132, 81, 61, 19, 36, 72, 72, 65, 65, 8, 27, 24, 20, 8, 45, + 44, 44, 13, 35, 20, 8, 23, 31, 23, 24, 21, 23, 29, 20, 2, 36, 82, 67, 82, 132, 81, + 61, 4, 36, 72, 72, 65, 65, 8, 27, 24, 20, 8, 44, 45, 44, 35, 24, 20, 8, 23, 31, + 23, 24, 21, 23, 29, 20, 2, 14, 23, 22, 20, 8, 23, 20, 8, 23, 24, 20, 15, 2, 14, + 11, 2, 39, 79, 20, 8, 37, 61, 82, 65, 79, 18, 8, 43, 82, 67, 75, 18, 8, 53, 61, + 74, 69, 81, 103, 81, 80, 79, 61, 81, 18, 8, 52, 75, 4, 2, 39, 79, 20, 8, 37, 61, + 82, 65, 79, 18, 8, 43, 82, 67, 75, 18, 8, 53, 61, 74, 69, 81, 103, 81, 80, 79, 61, + 81, 18, 8, 52, 75, 4, 2, 132, 69, 74, 65, 74, 132, 81, 79, 20, 8, 23, 20, 8, 44, + 44, 81, 18, 8, 23, 31, 23, 24, 21, 23, 29, 20, 8, 14, 24, 20, 8, 23, 24, 20, 8, + 22, 30, 20, 15, 2, 132, 69, 74, 65, 74, 132, 81, 79, 20, 8, 23, 20, 8, 44, 45, 82, + 18, 8, 23, 31, 23, 24, 21, 23, 29, 20, 8, 14, 24, 20, 8, 23, 24, 20, 8, 22, 30, + 20, 15, 2, 37, 61, 82, 73, 61, 74, 74, 18, 8, 40, 82, 67, 65, 74, 18, 8, 46, 61, + 82, 66, 73, 61, 74, 74, 18, 8, 42, 79, 75, 72, 4, 8, 20, 2, 37, 61, 82, 73, 61, + 74, 74, 18, 8, 40, 82, 67, 65, 74, 18, 8, 46, 61, 82, 66, 73, 61, 74, 74, 18, 8, + 42, 79, 75, 72, 3, 2, 73, 61, 74, 132, 81, 79, 20, 8, 26, 21, 27, 20, 8, 45, 13, + 20, 8, 23, 31, 22, 30, 21, 23, 25, 20, 8, 14, 23, 22, 20, 8, 23, 20, 8, 23, 24, + 20, 15, 2, 73, 61, 74, 132, 81, 79, 20, 8, 26, 21, 27, 20, 8, 45, 80, 20, 8, 23, + 31, 22, 30, 21, 23, 25, 20, 8, 14, 23, 22, 20, 8, 23, 20, 8, 23, 24, 20, 15, 2, + 37, 65, 63, 71, 65, 79, 18, 8, 42, 82, 132, 81, 61, 83, 18, 8, 42, 65, 74, 65, 79, + 61, 72, 73, 61, 70, 75, 79, 8, 87, 20, 8, 39, 20, 18, 20, 2, 37, 65, 63, 71, 65, + 79, 18, 8, 42, 82, 132, 81, 61, 83, 18, 8, 42, 65, 74, 65, 79, 61, 72, 73, 61, 70, + 75, 79, 8, 87, 20, 8, 39, 20, 18, 2, 46, 61, 132, 81, 61, 74, 69, 65, 74, 61, 72, + 72, 65, 65, 8, 29, 20, 8, 44, 44, 69, 20, 8, 23, 31, 22, 30, 21, 23, 25, 20, 2, + 46, 61, 132, 81, 61, 74, 69, 65, 74, 61, 72, 72, 65, 65, 8, 29, 20, 8, 44, 44, 72, + 20, 8, 23, 31, 22, 30, 21, 23, 25, 20, 2, 14, 30, 20, 8, 23, 20, 8, 22, 24, 20, + 15, 2, 14, 30, 20, 8, 23, 20, 8, 50, 22, 24, 20, 15, 2, 37, 65, 79, 67, 73, 61, + 74, 74, 18, 8, 48, 61, 85, 18, 8, 52, 65, 74, 81, 69, 65, 79, 18, 8, 46, 82, 79, + 66, 110, 79, 132, 81, 65, 74, 4, 2, 37, 65, 79, 67, 73, 61, 74, 74, 18, 8, 48, 61, + 85, 18, 8, 52, 65, 74, 81, 69, 65, 79, 18, 8, 46, 82, 79, 66, 110, 79, 132, 81, 65, + 74, 3, 2, 39, 61, 73, 73, 8, 26, 24, 20, 8, 57, 20, 8, 26, 27, 20, 8, 45, 44, + 44, 45, 6, 20, 8, 23, 31, 23, 24, 21, 23, 29, 20, 2, 64, 61, 73, 73, 8, 26, 24, + 20, 8, 57, 20, 8, 23, 27, 20, 8, 45, 44, 44, 29, 20, 8, 23, 31, 23, 24, 21, 23, + 29, 20, 2, 14, 24, 20, 8, 23, 24, 20, 8, 22, 30, 20, 15, 2, 14, 8, 24, 8, 22, + 30, 2, 37, 75, 72, 72, 73, 61, 74, 74, 18, 8, 43, 65, 69, 74, 79, 69, 63, 68, 18, + 8, 37, 82, 79, 65, 61, 82, 64, 69, 79, 65, 71, 81, 75, 79, 18, 2, 37, 75, 72, 72, + 73, 61, 74, 74, 18, 8, 43, 65, 69, 74, 79, 69, 63, 68, 18, 8, 37, 82, 79, 65, 61, + 82, 64, 69, 79, 65, 71, 81, 75, 79, 18, 2, 0, 95, 57, 69, 72, 73, 65, 79, 80, 64, + 109, 79, 66, 65, 79, 8, 53, 81, 79, 20, 8, 27, 24, 20, 8, 45, 45, 45, 20, 8, 23, + 31, 22, 30, 21, 23, 25, 20, 2, 57, 69, 72, 73, 65, 79, 80, 64, 75, 79, 66, 65, 79, + 8, 53, 81, 79, 20, 8, 27, 24, 20, 8, 44, 45, 45, 80, 20, 8, 23, 31, 22, 30, 21, + 23, 25, 20, 2, 14, 23, 22, 20, 8, 23, 20, 8, 22, 28, 20, 15, 2, 14, 23, 22, 20, + 8, 23, 20, 8, 22, 28, 20, 15, 2, 51, 39, 79, 20, 8, 37, 75, 79, 63, 68, 61, 79, + 64, 81, 18, 8, 37, 79, 82, 74, 75, 18, 8, 53, 63, 68, 79, 69, 66, 81, 132, 81, 65, + 72, 72, 65, 79, 18, 2, 39, 79, 20, 8, 37, 75, 79, 63, 68, 61, 79, 64, 81, 18, 8, + 37, 79, 82, 74, 75, 18, 8, 53, 63, 68, 79, 69, 66, 81, 132, 81, 65, 72, 72, 65, 79, + 18, 2, 46, 61, 74, 81, 132, 81, 79, 20, 8, 23, 24, 22, 21, 23, 24, 23, 20, 8, 45, + 45, 45, 20, 8, 23, 31, 23, 24, 21, 23, 29, 20, 2, 46, 61, 74, 81, 132, 81, 79, 20, + 8, 23, 24, 22, 21, 23, 24, 23, 20, 8, 44, 45, 45, 80, 20, 8, 23, 31, 23, 24, 21, + 23, 29, 20, 2, 14, 30, 20, 8, 23, 20, 8, 22, 24, 20, 15, 2, 14, 30, 20, 8, 23, + 20, 8, 50, 22, 24, 20, 15, 2, 37, 79, 61, 82, 74, 65, 18, 8, 38, 61, 79, 72, 18, + 8, 52, 65, 74, 81, 69, 65, 79, 18, 8, 57, 69, 65, 72, 61, 74, 64, 132, 81, 79, 20, + 8, 25, 29, 20, 2, 37, 79, 61, 82, 74, 65, 18, 8, 38, 61, 79, 72, 18, 8, 52, 65, + 74, 81, 69, 65, 79, 18, 8, 57, 69, 65, 72, 61, 74, 64, 132, 81, 79, 20, 8, 25, 29, + 20, 2, 44, 69, 20, 8, 23, 31, 23, 22, 21, 23, 27, 20, 8, 14, 24, 20, 8, 23, 24, + 20, 8, 22, 30, 20, 15, 2, 44, 69, 20, 8, 23, 31, 23, 22, 21, 23, 27, 20, 8, 14, + 24, 20, 8, 23, 24, 20, 8, 22, 30, 20, 15, 2, 37, 79, 75, 64, 65, 18, 8, 57, 69, + 72, 68, 65, 72, 73, 18, 8, 52, 65, 74, 81, 69, 65, 79, 18, 8, 39, 61, 68, 72, 73, + 61, 74, 69, 69, 4, 2, 37, 79, 75, 64, 65, 18, 8, 57, 69, 72, 68, 65, 72, 73, 18, + 8, 52, 65, 74, 81, 69, 65, 79, 18, 8, 39, 61, 68, 72, 73, 61, 74, 74, 3, 2, 132, + 81, 79, 20, 8, 24, 31, 20, 8, 45, 69, 20, 8, 23, 31, 23, 22, 21, 27, 20, 14, 24, + 20, 8, 23, 24, 20, 8, 22, 30, 20, 15, 2, 132, 81, 79, 20, 8, 24, 31, 20, 8, 45, + 26, 20, 8, 23, 31, 23, 22, 21, 23, 27, 20, 8, 14, 24, 20, 8, 23, 24, 20, 8, 22, + 30, 20, 15, 2, 39, 79, 20, 8, 37, 86, 71, 18, 8, 47, 65, 75, 78, 75, 72, 64, 18, + 8, 36, 79, 87, 81, 18, 8, 57, 69, 65, 72, 61, 74, 64, 4, 2, 39, 79, 20, 8, 37, + 86, 71, 18, 8, 47, 65, 75, 78, 75, 72, 64, 18, 8, 36, 79, 87, 81, 18, 8, 57, 69, + 65, 72, 61, 74, 64, 3, 2, 132, 81, 79, 20, 8, 23, 27, 18, 8, 44, 45, 0, 131, 20, + 8, 23, 31, 23, 24, 21, 23, 29, 20, 8, 14, 23, 22, 20, 8, 23, 20, 8, 23, 24, 20, + 15, 2, 132, 81, 79, 20, 8, 23, 27, 20, 8, 44, 45, 35, 20, 8, 23, 31, 23, 24, 21, + 23, 29, 20, 8, 14, 23, 22, 20, 8, 23, 20, 8, 23, 24, 20, 15, 2, 42, 75, 62, 65, + 79, 81, 18, 8, 36, 82, 67, 82, 132, 81, 18, 8, 42, 65, 84, 65, 79, 71, 132, 63, 68, + 61, 66, 81, 80, 62, 65, 61, 73, 81, 65, 79, 18, 2, 42, 65, 62, 65, 79, 81, 18, 8, + 36, 82, 67, 82, 132, 81, 18, 8, 42, 65, 84, 65, 79, 71, 132, 63, 68, 61, 66, 81, 80, + 62, 65, 61, 73, 81, 65, 79, 18, 2, 52, 75, 132, 69, 74, 65, 74, 132, 81, 79, 20, 8, + 24, 20, 8, 44, 45, 44, 45, 13, 20, 8, 23, 31, 22, 30, 21, 23, 25, 20, 2, 52, 75, + 132, 69, 74, 65, 74, 132, 81, 79, 20, 8, 24, 20, 8, 44, 45, 45, 80, 20, 8, 23, 31, + 22, 30, 21, 23, 25, 20, 2, 14, 30, 20, 8, 23, 20, 8, 50, 30, 18, 15, 2, 14, 30, + 20, 8, 23, 20, 8, 50, 30, 20, 15, 2, 42, 65, 79, 80, 64, 75, 79, 66, 66, 18, 8, + 51, 61, 82, 72, 18, 8, 50, 62, 65, 79, 78, 75, 132, 81, 19, 36, 132, 132, 69, 132, 81, + 65, 74, 81, 18, 2, 42, 65, 79, 80, 64, 75, 79, 66, 66, 18, 8, 51, 61, 82, 72, 18, + 8, 50, 62, 65, 79, 78, 75, 132, 81, 19, 36, 132, 132, 69, 132, 81, 65, 74, 81, 18, 2, + 40, 75, 132, 61, 74, 64, 65, 79, 132, 81, 79, 20, 8, 31, 79, 8, 45, 44, 44, 13, 35, + 124, 20, 8, 23, 31, 23, 22, 21, 23, 27, 20, 2, 40, 75, 132, 61, 74, 64, 65, 79, 132, + 81, 79, 20, 8, 31, 20, 8, 44, 44, 45, 35, 124, 20, 8, 23, 31, 23, 22, 21, 23, 27, + 20, 2, 14, 27, 20, 8, 23, 20, 8, 23, 22, 20, 15, 2, 14, 27, 20, 8, 23, 20, 8, + 23, 22, 20, 15, 2, 42, 79, 65, 64, 86, 18, 8, 41, 79, 61, 74, 87, 18, 8, 14, 44, + 74, 67, 65, 74, 69, 65, 82, 79, 15, 18, 8, 38, 61, 79, 73, 65, 79, 3, 2, 132, 81, + 79, 20, 8, 23, 30, 20, 8, 45, 6, 20, 8, 23, 31, 23, 22, 21, 23, 27, 20, 8, 14, + 23, 22, 20, 8, 23, 20, 8, 22, 22, 20, 15, 2, 42, 82, 81, 81, 73, 61, 74, 74, 18, + 8, 36, 72, 62, 79, 65, 63, 68, 81, 18, 8, 52, 65, 74, 81, 69, 65, 79, 18, 8, 37, + 69, 80, 3, 2, 73, 61, 79, 71, 132, 81, 79, 20, 8, 23, 22, 20, 8, 44, 24, 20, 8, + 23, 31, 23, 24, 21, 23, 29, 20, 8, 14, 24, 20, 8, 23, 24, 20, 2, 22, 30, 20, 15, + 2, 43, 61, 61, 63, 71, 18, 8, 51, 61, 82, 72, 18, 8, 46, 61, 82, 66, 73, 61, 74, + 74, 18, 8, 42, 79, 75, 72, 73, 61, 74, 3, 2, 132, 81, 79, 20, 8, 23, 30, 20, 8, + 44, 45, 44, 29, 20, 8, 23, 31, 22, 30, 21, 23, 25, 20, 8, 14, 23, 25, 20, 8, 28, + 20, 8, 22, 28, 20, 15, 2, 43, 61, 79, 74, 69, 132, 63, 68, 18, 8, 50, 81, 81, 75, + 18, 8, 36, 79, 63, 68, 69, 81, 65, 71, 81, 18, 8, 37, 72, 65, 69, 62, 81, 79, 65, + 82, 3, 2, 132, 81, 79, 20, 8, 23, 27, 21, 23, 28, 20, 8, 44, 72, 20, 8, 23, 31, + 22, 30, 21, 23, 25, 20, 8, 14, 30, 20, 8, 23, 20, 8, 50, 30, 20, 15, 2, 43, 69, + 79, 132, 63, 68, 18, 8, 51, 61, 82, 72, 18, 8, 53, 63, 68, 79, 69, 66, 81, 132, 81, + 65, 72, 72, 65, 79, 18, 8, 57, 61, 72, 72, 3, 2, 132, 81, 79, 20, 8, 27, 24, 20, + 8, 45, 44, 44, 45, 53, 20, 8, 23, 31, 23, 24, 21, 23, 29, 20, 8, 14, 23, 22, 20, + 8, 23, 20, 8, 22, 22, 20, 15, 2, 39, 79, 20, 8, 43, 82, 62, 61, 81, 132, 63, 68, + 18, 8, 50, 80, 71, 61, 79, 18, 8, 42, 86, 73, 74, 61, 132, 69, 61, 72, 19, 39, 69, + 79, 65, 71, 3, 2, 81, 75, 79, 18, 8, 53, 63, 68, 69, 72, 72, 65, 79, 132, 81, 79, + 20, 8, 24, 28, 20, 8, 44, 81, 20, 8, 23, 31, 23, 24, 21, 23, 29, 20, 2, 14, 42, + 28, 8, 23, 2, 45, 61, 63, 68, 73, 61, 74, 74, 18, 8, 43, 61, 74, 80, 18, 8, 39, + 69, 79, 65, 71, 81, 75, 79, 18, 8, 53, 61, 83, 69, 67, 74, 86, 3, 2, 51, 72, 61, + 81, 87, 8, 23, 20, 8, 44, 124, 20, 8, 23, 31, 23, 22, 21, 23, 27, 20, 8, 14, 23, + 25, 20, 8, 28, 20, 8, 22, 28, 20, 15, 2, 45, 61, 63, 75, 62, 69, 18, 8, 53, 69, + 65, 67, 73, 82, 74, 64, 18, 8, 52, 65, 74, 81, 69, 65, 79, 18, 8, 47, 69, 65, 81, + 87, 65, 74, 3, 2, 62, 82, 79, 67, 65, 79, 8, 53, 81, 79, 20, 8, 24, 27, 20, 8, + 57, 20, 8, 23, 27, 20, 8, 44, 45, 9, 20, 8, 23, 31, 22, 30, 21, 23, 25, 20, 2, + 14, 30, 20, 8, 23, 20, 8, 22, 30, 20, 15, 2, 53, 81, 103, 64, 81, 69, 132, 63, 68, + 65, 8, 47, 61, 67, 65, 79, 78, 72, 61, 81, 87, 83, 65, 79, 84, 61, 72, 81, 82, 74, + 67, 8, 82, 74, 64, 2, 48, 61, 132, 63, 68, 69, 74, 65, 74, 81, 65, 63, 68, 74, 69, + 132, 63, 68, 65, 8, 36, 62, 81, 65, 69, 72, 82, 74, 67, 8, 64, 65, 79, 2, 54, 69, + 65, 66, 62, 61, 82, 83, 65, 79, 84, 61, 72, 81, 82, 74, 67, 20, 2, 57, 69, 72, 68, + 65, 72, 73, 78, 72, 61, 81, 87, 8, 23, 61, 18, 8, 40, 69, 74, 67, 61, 74, 67, 8, + 47, 110, 81, 87, 75, 84, 65, 79, 2, 53, 81, 79, 61, 102, 65, 20, 2, 53, 81, 61, 64, + 81, 62, 61, 82, 69, 74, 67, 65, 74, 69, 65, 82, 79, 32, 8, 37, 69, 81, 81, 74, 65, + 79, 18, 8, 46, 109, 74, 69, 67, 80, 3, 2, 84, 65, 67, 8, 27, 27, 20, 2, 47, 61, + 67, 65, 79, 78, 72, 61, 81, 87, 19, 56, 75, 79, 132, 81, 65, 68, 65, 79, 32, 8, 46, + 61, 73, 62, 61, 63, 68, 18, 8, 53, 61, 72, 87, 3, 2, 82, 66, 65, 79, 8, 24, 23, + 20, 2, 47, 61, 67, 65, 79, 78, 72, 61, 81, 87, 19, 36, 132, 132, 69, 132, 81, 65, 74, + 81, 32, 8, 37, 65, 102, 65, 79, 18, 8, 57, 65, 132, 81, 65, 74, 64, 18, 2, 55, 72, + 73, 65, 74, 19, 36, 72, 72, 65, 65, 8, 26, 30, 20, 2, 47, 61, 67, 65, 79, 78, 72, + 61, 81, 87, 19, 42, 65, 68, 69, 72, 66, 65, 32, 8, 42, 79, 61, 73, 73, 18, 8, 53, + 63, 68, 61, 79, 79, 65, 74, 3, 2, 132, 81, 79, 61, 102, 65, 8, 24, 20, 2, 53, 81, + 65, 66, 66, 65, 74, 18, 8, 54, 61, 82, 79, 75, 67, 67, 65, 74, 65, 79, 8, 53, 81, + 79, 20, 8, 31, 20, 2, 47, 61, 67, 65, 79, 78, 72, 61, 81, 87, 8, 48, 75, 72, 72, + 84, 69, 81, 87, 132, 81, 79, 61, 102, 65, 20, 2, 47, 61, 67, 65, 79, 78, 72, 61, 81, + 87, 19, 36, 82, 66, 132, 65, 68, 65, 79, 32, 8, 53, 63, 68, 82, 72, 87, 65, 18, 8, + 53, 75, 78, 68, 69, 65, 3, 2, 38, 68, 61, 79, 72, 75, 81, 81, 65, 74, 19, 53, 81, + 79, 20, 8, 23, 23, 26, 20, 2, 14, 47, 61, 67, 65, 79, 78, 72, 61, 81, 87, 8, 49, + 75, 74, 74, 65, 74, 64, 61, 73, 73, 8, 45, 20, 15, 2, 41, 65, 79, 74, 79, 82, 66, + 32, 8, 36, 73, 81, 8, 38, 68, 20, 8, 28, 22, 30, 26, 20, 2, 47, 61, 67, 65, 79, + 78, 72, 61, 81, 87, 19, 36, 82, 66, 132, 65, 68, 65, 79, 32, 8, 37, 61, 72, 71, 65, + 18, 8, 46, 61, 69, 132, 65, 79, 69, 74, 3, 2, 36, 82, 67, 82, 132, 81, 61, 4, 19, + 36, 72, 72, 65, 65, 8, 27, 31, 20, 2, 47, 61, 67, 65, 79, 78, 72, 61, 81, 87, 8, + 49, 75, 74, 74, 65, 74, 64, 61, 73, 73, 8, 44, 45, 20, 2, 47, 61, 67, 65, 79, 78, + 72, 61, 81, 87, 19, 36, 82, 66, 132, 65, 68, 65, 79, 32, 8, 46, 82, 74, 87, 65, 18, + 8, 46, 74, 75, 62, 65, 72, 80, 3, 2, 64, 75, 79, 66, 66, 132, 81, 79, 20, 8, 26, + 23, 20, 2, 53, 81, 103, 64, 81, 69, 132, 63, 68, 65, 8, 51, 75, 72, 69, 87, 65, 69, + 19, 56, 65, 79, 84, 61, 72, 81, 82, 74, 67, 20, 2, 14, 3, 8, 36, 62, 81, 65, 69, + 72, 82, 74, 67, 8, 44, 45, 8, 3, 15, 2, 56, 65, 79, 73, 65, 132, 132, 82, 74, 67, + 80, 61, 73, 81, 20, 2, 39, 61, 80, 8, 56, 65, 79, 73, 65, 132, 132, 82, 74, 67, 80, + 61, 73, 81, 8, 132, 81, 65, 68, 81, 8, 87, 82, 79, 2, 56, 65, 79, 66, 110, 67, 82, + 74, 67, 8, 14, 70, 65, 64, 65, 79, 8, 65, 69, 74, 87, 65, 72, 74, 65, 74, 8, 53, + 75, 74, 64, 65, 79, 3, 2, 83, 65, 79, 84, 61, 72, 81, 82, 74, 67, 15, 18, 8, 84, + 65, 72, 63, 68, 65, 8, 64, 65, 73, 132, 65, 72, 62, 65, 74, 8, 82, 74, 3, 2, 73, + 69, 81, 81, 65, 72, 62, 61, 79, 8, 36, 82, 66, 81, 79, 103, 67, 65, 8, 87, 82, 8, + 65, 79, 81, 65, 69, 72, 65, 74, 8, 82, 74, 64, 8, 73, 69, 81, 2, 69, 68, 73, 8, + 73, 110, 74, 64, 72, 69, 63, 68, 8, 75, 64, 65, 79, 8, 132, 63, 68, 79, 69, 66, 81, + 72, 69, 63, 68, 8, 87, 82, 8, 83, 65, 79, 3, 2, 71, 65, 68, 79, 65, 74, 8, 87, + 82, 132, 81, 103, 74, 64, 69, 67, 8, 69, 132, 81, 20, 2, 52, 61, 81, 68, 20, 18, 8, + 44, 44, 45, 20, 8, 50, 62, 65, 79, 67, 65, 132, 63, 68, 20, 18, 8, 60, 69, 73, 73, + 65, 79, 8, 25, 23, 25, 2, 62, 69, 80, 8, 25, 23, 27, 20, 2, 56, 65, 79, 73, 65, + 132, 132, 82, 74, 67, 80, 69, 74, 132, 78, 65, 71, 81, 75, 79, 32, 8, 53, 81, 82, 73, + 78, 66, 18, 8, 57, 69, 72, 4, 2, 73, 65, 79, 80, 64, 75, 79, 66, 18, 8, 49, 61, + 132, 132, 61, 82, 69, 132, 63, 68, 65, 132, 81, 79, 20, 8, 25, 29, 20, 2, 54, 65, 63, + 68, 74, 69, 132, 63, 68, 65, 8, 37, 65, 61, 73, 81, 65, 20, 2, 36, 62, 81, 65, 69, + 72, 82, 74, 67, 8, 45, 20, 2, 47, 61, 74, 64, 73, 65, 132, 132, 65, 79, 32, 8, 37, + 65, 79, 74, 68, 61, 79, 64, 81, 18, 8, 48, 65, 65, 79, 132, 63, 68, 65, 69, 64, 3, + 2, 132, 81, 79, 61, 102, 65, 8, 40, 63, 71, 65, 8, 41, 79, 65, 64, 65, 79, 69, 63, + 69, 61, 132, 81, 79, 61, 102, 65, 20, 2, 60, 65, 69, 63, 68, 74, 65, 79, 32, 8, 37, + 72, 61, 74, 71, 18, 8, 42, 79, 110, 74, 132, 81, 79, 20, 8, 24, 23, 20, 2, 53, 63, + 68, 73, 69, 64, 81, 18, 8, 43, 75, 79, 132, 81, 84, 65, 67, 8, 30, 21, 31, 20, 2, + 36, 62, 81, 65, 69, 72, 82, 74, 67, 8, 44, 45, 20, 2, 50, 62, 65, 79, 72, 61, 74, + 64, 73, 65, 132, 132, 65, 79, 32, 8, 52, 65, 78, 71, 65, 84, 69, 81, 87, 18, 8, 46, + 61, 69, 132, 65, 79, 3, 2, 41, 79, 69, 65, 64, 79, 69, 63, 68, 19, 53, 81, 79, 20, + 8, 60, 61, 20, 2, 56, 65, 79, 73, 65, 132, 132, 82, 74, 67, 80, 19, 36, 132, 132, 69, + 132, 81, 65, 74, 81, 32, 8, 43, 65, 79, 67, 65, 132, 65, 72, 72, 18, 2, 52, 110, 63, + 71, 65, 79, 81, 132, 81, 79, 20, 8, 30, 20, 2, 60, 65, 69, 63, 68, 74, 65, 79, 32, + 8, 49, 65, 82, 65, 74, 64, 75, 79, 66, 66, 18, 8, 53, 109, 73, 73, 65, 79, 69, 74, + 67, 3, 2, 132, 81, 79, 61, 102, 65, 8, 24, 26, 20, 2, 53, 81, 65, 72, 72, 65, 8, + 58, 32, 8, 43, 75, 63, 68, 62, 61, 82, 20, 2, 36, 74, 67, 65, 72, 65, 67, 65, 74, + 68, 65, 69, 81, 65, 74, 8, 64, 65, 79, 8, 43, 75, 63, 68, 62, 61, 82, 3, 2, 39, + 65, 78, 82, 81, 61, 81, 69, 75, 74, 8, 82, 74, 64, 8, 64, 65, 79, 8, 39, 65, 78, + 82, 81, 61, 81, 69, 75, 74, 8, 66, 110, 79, 2, 64, 69, 65, 8, 40, 79, 79, 69, 63, + 68, 81, 82, 74, 67, 8, 65, 69, 74, 65, 79, 8, 42, 79, 75, 102, 73, 61, 79, 71, 81, + 68, 61, 72, 72, 65, 20, 2, 41, 65, 79, 74, 132, 78, 79, 65, 63, 68, 132, 61, 63, 68, + 65, 74, 20, 8, 41, 65, 82, 65, 79, 83, 65, 79, 132, 69, 63, 68, 65, 79, 82, 74, 67, + 2, 64, 65, 79, 8, 132, 81, 103, 64, 81, 69, 132, 63, 68, 65, 74, 8, 42, 65, 62, 103, + 82, 64, 65, 18, 8, 36, 74, 72, 61, 67, 65, 74, 8, 82, 74, 64, 2, 48, 75, 62, 69, + 72, 69, 65, 74, 8, 132, 75, 84, 69, 65, 8, 64, 65, 79, 8, 57, 61, 72, 64, 82, 74, + 67, 65, 74, 20, 2, 56, 65, 79, 84, 61, 72, 81, 65, 79, 32, 8, 83, 75, 74, 8, 43, + 61, 86, 74, 18, 8, 48, 61, 67, 61, 87, 69, 74, 132, 81, 79, 20, 8, 25, 20, 2, 48, + 61, 132, 63, 68, 69, 74, 65, 74, 73, 65, 69, 132, 81, 65, 79, 32, 8, 60, 69, 73, 73, + 65, 79, 73, 61, 74, 74, 18, 8, 69, 74, 8, 64, 65, 79, 2, 36, 74, 132, 81, 61, 72, + 81, 20, 2, 39, 69, 65, 8, 49, 61, 73, 65, 74, 8, 82, 74, 64, 8, 57, 75, 68, 74, + 82, 74, 67, 65, 74, 8, 64, 65, 80, 2, 110, 62, 79, 69, 67, 65, 74, 8, 51, 65, 79, + 132, 75, 74, 61, 72, 80, 8, 132, 69, 74, 64, 8, 69, 74, 8, 64, 65, 79, 8, 36, 74, + 132, 81, 61, 72, 81, 2, 87, 82, 8, 65, 79, 66, 79, 61, 67, 65, 74, 20, 2, 63, 15, + 8, 39, 61, 80, 8, 41, 72, 65, 69, 132, 63, 68, 132, 63, 68, 61, 82, 61, 73, 81, 20, + 2, 53, 78, 79, 65, 65, 132, 81, 79, 20, 8, 24, 29, 21, 25, 22, 20, 2, 39, 69, 65, + 8, 56, 69, 65, 68, 4, 19, 8, 82, 74, 64, 8, 41, 72, 65, 69, 132, 63, 68, 132, 63, + 68, 61, 82, 8, 14, 72, 69, 132, 81, 8, 64, 82, 79, 63, 68, 2, 51, 75, 72, 69, 87, + 65, 69, 19, 56, 65, 79, 75, 79, 64, 74, 82, 74, 67, 8, 83, 75, 73, 8, 24, 30, 20, + 8, 45, 82, 74, 69, 8, 23, 30, 31, 29, 15, 2, 65, 69, 74, 67, 65, 66, 110, 68, 79, + 81, 20, 8, 39, 69, 65, 8, 54, 79, 69, 63, 68, 69, 74, 65, 74, 132, 63, 68, 61, 82, + 8, 69, 132, 81, 8, 64, 82, 79, 63, 68, 2, 36, 62, 81, 75, 73, 73, 65, 74, 8, 83, + 75, 73, 8, 25, 23, 20, 8, 36, 82, 67, 82, 80, 81, 8, 21, 8, 24, 25, 20, 8, 53, + 65, 78, 81, 65, 73, 62, 65, 79, 8, 23, 30, 31, 29, 8, 61, 73, 2, 23, 27, 20, 8, + 50, 71, 81, 75, 62, 65, 79, 8, 23, 30, 31, 29, 8, 83, 75, 74, 8, 64, 65, 79, 8, + 68, 69, 65, 132, 69, 67, 65, 74, 2, 46, 67, 72, 20, 8, 51, 75, 72, 69, 87, 65, 69, + 19, 39, 69, 79, 65, 71, 81, 69, 75, 74, 8, 110, 62, 65, 79, 74, 75, 73, 73, 65, 74, + 2, 84, 75, 79, 64, 65, 74, 20, 2, 39, 61, 80, 8, 36, 73, 81, 8, 69, 132, 81, 8, + 67, 65, 109, 66, 66, 74, 65, 81, 32, 2, 61, 15, 8, 66, 110, 79, 8, 64, 69, 65, 8, + 41, 72, 65, 69, 132, 63, 68, 132, 63, 68, 61, 82, 20, 2, 61, 15, 8, 69, 73, 8, 53, + 75, 73, 73, 65, 79, 68, 61, 72, 62, 70, 61, 68, 79, 32, 8, 56, 75, 79, 73, 69, 81, + 81, 61, 67, 80, 2, 83, 75, 74, 8, 28, 97, 8, 62, 69, 80, 8, 31, 97, 8, 55, 68, + 79, 33, 8, 61, 82, 102, 65, 79, 64, 65, 73, 2, 53, 75, 74, 74, 61, 62, 65, 74, 64, + 80, 8, 83, 75, 74, 8, 25, 97, 8, 62, 69, 80, 8, 26, 97, 8, 55, 68, 79, 2, 49, + 61, 63, 68, 73, 69, 81, 81, 61, 67, 80, 20, 2, 62, 15, 8, 69, 73, 8, 57, 69, 74, + 81, 65, 79, 68, 61, 72, 62, 70, 61, 68, 79, 32, 8, 56, 75, 79, 73, 69, 81, 81, 61, + 67, 80, 8, 83, 75, 74, 2, 29, 8, 62, 69, 80, 8, 31, 97, 8, 55, 68, 79, 33, 8, + 61, 82, 102, 65, 79, 64, 65, 73, 8, 53, 75, 74, 74, 3, 2, 61, 62, 65, 74, 64, 80, + 8, 83, 75, 74, 8, 25, 97, 8, 62, 69, 80, 8, 26, 97, 8, 55, 68, 79, 8, 49, 61, + 63, 68, 3, 2, 73, 69, 81, 81, 61, 67, 80, 20, 2, 62, 15, 8, 66, 110, 79, 8, 64, + 69, 65, 8, 14, 54, 79, 69, 63, 68, 69, 74, 65, 74, 132, 63, 68, 61, 82, 15, 20, 2, + 61, 15, 8, 69, 73, 8, 53, 75, 73, 73, 65, 79, 62, 61, 72, 62, 132, 61, 68, 79, 32, + 8, 56, 75, 79, 73, 69, 81, 81, 61, 67, 80, 2, 83, 75, 74, 8, 28, 97, 8, 62, 69, + 80, 8, 31, 97, 8, 55, 68, 79, 20, 8, 49, 61, 63, 68, 73, 69, 81, 81, 61, 67, 80, + 2, 83, 75, 74, 8, 27, 8, 62, 69, 80, 8, 29, 8, 72, 69, 65, 79, 8, 73, 69, 81, + 8, 36, 82, 80, 74, 61, 68, 73, 65, 2, 64, 65, 80, 8, 53, 75, 74, 74, 61, 62, 65, + 74, 64, 80, 33, 8, 61, 74, 8, 64, 69, 65, 132, 65, 73, 8, 83, 75, 74, 2, 25, 97, + 8, 62, 69, 80, 8, 26, 97, 8, 55, 68, 79, 20, 2, 62, 15, 8, 69, 73, 8, 57, 69, + 74, 81, 65, 79, 68, 61, 72, 62, 70, 61, 68, 79, 32, 8, 56, 75, 79, 73, 69, 81, 81, + 61, 67, 80, 8, 83, 75, 74, 2, 29, 8, 62, 69, 80, 8, 31, 97, 8, 55, 68, 79, 20, + 8, 49, 61, 63, 68, 73, 69, 81, 81, 61, 67, 80, 8, 83, 75, 74, 2, 27, 8, 62, 69, + 80, 8, 29, 8, 55, 68, 79, 18, 8, 73, 69, 81, 8, 36, 82, 80, 74, 61, 68, 73, 65, + 8, 64, 65, 80, 2, 53, 75, 74, 74, 61, 62, 65, 74, 64, 80, 33, 8, 61, 74, 8, 64, + 69, 65, 132, 65, 73, 8, 83, 75, 74, 8, 25, 97, 2, 62, 69, 80, 8, 26, 97, 8, 55, + 68, 79, 20, 2, 41, 79, 61, 74, 8, 43, 75, 78, 78, 65, 18, 8, 51, 65, 132, 81, 61, + 72, 75, 87, 87, 69, 132, 81, 79, 20, 8, 29, 28, 18, 2, 44, 45, 20, 8, 36, 82, 66, + 67, 61, 74, 67, 20, 2, 64, 15, 8, 39, 69, 65, 8, 39, 65, 80, 69, 74, 66, 65, 71, + 81, 69, 75, 74, 80, 61, 74, 132, 81, 61, 72, 81, 20, 2, 48, 75, 72, 72, 84, 69, 81, + 87, 132, 81, 79, 20, 8, 74, 65, 62, 65, 74, 8, 64, 65, 73, 8, 43, 61, 82, 78, 81, + 78, 82, 73, 78, 3, 2, 84, 65, 79, 71, 18, 8, 53, 75, 78, 68, 69, 65, 8, 38, 68, + 61, 79, 72, 75, 81, 81, 65, 74, 132, 81, 79, 20, 8, 23, 23, 26, 20, 2, 14, 54, 65, + 72, 20, 8, 38, 68, 20, 8, 26, 24, 29, 20, 15, 2, 39, 69, 65, 8, 54, 68, 103, 81, + 69, 67, 71, 65, 69, 81, 8, 64, 65, 79, 8, 36, 74, 132, 81, 61, 72, 81, 8, 65, 79, + 132, 81, 79, 65, 63, 71, 81, 2, 132, 69, 63, 68, 8, 74, 69, 63, 68, 81, 8, 74, 82, + 79, 8, 61, 82, 66, 8, 64, 69, 65, 8, 39, 65, 80, 69, 74, 66, 65, 71, 81, 69, 75, + 74, 2, 64, 65, 79, 70, 65, 74, 69, 67, 65, 74, 8, 42, 65, 67, 65, 74, 132, 81, 103, + 74, 64, 65, 18, 8, 84, 65, 72, 63, 68, 65, 8, 74, 61, 63, 68, 8, 64, 65, 79, 2, + 51, 75, 72, 69, 87, 65, 69, 4, 56, 65, 79, 75, 79, 64, 74, 82, 74, 67, 8, 14, 83, + 75, 73, 8, 23, 22, 20, 8, 45, 82, 74, 69, 8, 23, 30, 31, 25, 15, 2, 65, 69, 74, + 65, 79, 8, 39, 65, 80, 69, 74, 132, 65, 71, 81, 69, 75, 74, 8, 82, 74, 81, 65, 79, + 87, 75, 67, 65, 74, 8, 84, 65, 79, 64, 65, 74, 2, 73, 110, 132, 132, 65, 74, 20, 8, + 40, 80, 8, 84, 65, 79, 64, 65, 74, 8, 83, 69, 65, 72, 73, 65, 68, 79, 8, 61, 82, + 63, 68, 8, 132, 75, 72, 63, 68, 65, 2, 46, 72, 65, 69, 64, 82, 74, 67, 80, 132, 81, + 110, 63, 71, 65, 18, 8, 37, 65, 81, 81, 65, 74, 8, 82, 20, 8, 132, 20, 8, 84, 20, + 8, 87, 82, 79, 2, 39, 65, 80, 69, 74, 66, 65, 71, 81, 69, 75, 74, 8, 110, 62, 65, + 79, 74, 75, 73, 73, 65, 74, 18, 8, 66, 110, 79, 8, 84, 65, 72, 63, 68, 65, 2, 65, + 69, 74, 65, 8, 56, 65, 79, 78, 66, 72, 69, 63, 68, 81, 82, 74, 67, 8, 87, 82, 79, + 8, 52, 65, 69, 74, 69, 67, 82, 74, 67, 8, 74, 69, 63, 68, 81, 2, 62, 65, 132, 81, + 65, 68, 81, 20, 8, 39, 69, 65, 8, 36, 74, 81, 79, 103, 67, 65, 8, 132, 69, 74, 64, + 8, 61, 74, 8, 64, 69, 65, 8, 36, 74, 132, 81, 61, 72, 81, 2, 87, 82, 8, 79, 69, + 63, 68, 81, 65, 74, 20, 2, 39, 69, 65, 8, 39, 65, 80, 69, 74, 66, 65, 71, 81, 69, + 75, 74, 80, 61, 74, 132, 81, 61, 72, 81, 8, 69, 132, 81, 8, 67, 65, 109, 66, 66, 74, + 65, 81, 32, 2, 61, 15, 8, 61, 74, 8, 64, 65, 74, 8, 57, 75, 63, 68, 65, 74, 81, + 61, 67, 65, 74, 32, 8, 83, 75, 74, 8, 29, 8, 55, 68, 79, 2, 56, 75, 79, 73, 69, + 81, 81, 61, 67, 80, 8, 62, 69, 80, 8, 28, 8, 55, 68, 79, 8, 49, 61, 63, 68, 3, + 4, 2, 73, 69, 81, 81, 61, 67, 80, 20, 2, 62, 15, 8, 61, 82, 8, 64, 65, 74, 8, + 53, 75, 74, 74, 81, 61, 67, 65, 74, 32, 8, 83, 75, 74, 8, 31, 8, 55, 68, 79, 2, + 56, 75, 79, 73, 69, 81, 81, 61, 67, 80, 8, 62, 69, 80, 8, 26, 8, 55, 68, 79, 8, + 49, 61, 63, 68, 3, 4, 2, 73, 69, 81, 81, 61, 67, 80, 20, 2, 14, 56, 65, 79, 84, + 61, 72, 81, 65, 79, 32, 8, 42, 79, 65, 82, 72, 69, 63, 68, 18, 8, 53, 75, 78, 68, + 69, 65, 8, 38, 68, 61, 79, 72, 75, 81, 81, 65, 74, 3, 2, 132, 79, 61, 102, 65, 8, + 24, 23, 20, 15, 2, 63, 15, 8, 38, 68, 61, 79, 72, 75, 81, 81, 65, 74, 62, 82, 79, + 67, 65, 79, 8, 82, 74, 64, 2, 47, 110, 102, 75, 84, 65, 79, 8, 36, 63, 71, 65, 79, + 67, 65, 73, 65, 69, 74, 132, 63, 68, 61, 66, 81, 65, 74, 20, 2, 39, 69, 65, 8, 36, + 74, 67, 65, 72, 65, 67, 65, 74, 68, 65, 69, 81, 65, 74, 8, 64, 69, 65, 132, 65, 79, + 8, 36, 63, 71, 65, 79, 3, 2, 67, 65, 73, 65, 69, 74, 132, 63, 68, 61, 66, 81, 65, + 74, 8, 84, 65, 79, 64, 65, 74, 8, 83, 75, 73, 8, 48, 61, 67, 69, 132, 81, 79, 61, + 81, 2, 83, 65, 79, 84, 61, 72, 81, 65, 81, 20, 8, 60, 82, 79, 8, 37, 65, 61, 82, + 66, 132, 69, 63, 68, 81, 69, 67, 82, 74, 67, 8, 64, 65, 79, 2, 70, 65, 74, 132, 65, + 69, 81, 80, 8, 64, 65, 79, 8, 53, 78, 79, 65, 65, 8, 67, 65, 72, 65, 67, 65, 74, + 65, 74, 8, 41, 65, 72, 64, 73, 61, 79, 71, 2, 84, 69, 79, 64, 8, 84, 103, 68, 79, + 65, 74, 64, 8, 64, 65, 79, 8, 53, 75, 73, 73, 65, 79, 73, 75, 74, 61, 81, 65, 8, + 65, 69, 74, 2, 41, 65, 72, 64, 68, 110, 81, 65, 79, 8, 62, 65, 132, 81, 65, 72, 72, + 81, 8, 14, 83, 65, 79, 67, 72, 20, 8, 53, 75, 74, 64, 65, 79, 19, 40, 81, 61, 81, + 8, 28, 2, 36, 62, 132, 63, 68, 74, 69, 81, 81, 8, 24, 25, 8, 82, 74, 64, 8, 24, + 26, 15, 20, 2, 46, 75, 73, 73, 69, 132, 132, 61, 79, 8, 64, 65, 80, 8, 48, 61, 67, + 69, 102, 81, 79, 61, 81, 80, 32, 2, 53, 63, 68, 75, 72, 87, 18, 8, 53, 81, 20, 8, + 56, 20, 8, 52, 61, 81, 8, 82, 74, 64, 8, 69, 74, 8, 64, 65, 74, 8, 53, 81, 103, + 64, 81, 65, 81, 61, 67, 65, 74, 18, 8, 14, 46, 109, 74, 69, 67, 3, 2, 72, 69, 63, + 68, 65, 8, 43, 61, 82, 80, 132, 61, 63, 68, 65, 74, 15, 18, 8, 40, 68, 79, 82, 74, + 67, 65, 74, 18, 8, 41, 65, 69, 65, 79, 72, 69, 63, 68, 3, 2, 71, 65, 69, 81, 65, + 74, 18, 8, 39, 65, 74, 71, 84, 110, 79, 64, 69, 67, 71, 65, 69, 81, 65, 74, 18, 8, + 39, 65, 74, 71, 73, 103, 72, 65, 79, 20, 2, 51, 65, 79, 132, 75, 74, 61, 72, 69, 65, + 74, 8, 64, 65, 79, 8, 48, 61, 67, 69, 132, 81, 79, 61, 81, 80, 73, 69, 81, 67, 72, + 69, 65, 64, 65, 79, 18, 2, 64, 65, 80, 67, 72, 20, 8, 64, 65, 79, 8, 37, 65, 61, + 73, 81, 65, 74, 8, 69, 74, 8, 62, 65, 87, 82, 67, 8, 61, 82, 66, 2, 51, 79, 110, + 66, 82, 74, 67, 18, 8, 36, 74, 132, 81, 65, 72, 72, 82, 74, 67, 18, 8, 37, 65, 132, + 75, 72, 64, 82, 74, 67, 18, 8, 37, 65, 3, 2, 82, 79, 72, 61, 82, 62, 82, 74, 67, + 18, 8, 60, 82, 79, 79, 82, 68, 65, 132, 65, 81, 87, 82, 74, 67, 8, 82, 132, 84, 20, + 18, 8, 132, 75, 84, 69, 65, 2, 64, 65, 79, 8, 61, 82, 66, 8, 51, 79, 69, 83, 61, + 81, 64, 69, 65, 74, 132, 81, 19, 8, 75, 64, 65, 79, 8, 36, 79, 62, 65, 69, 81, 80, + 3, 2, 83, 65, 79, 81, 79, 61, 67, 8, 61, 74, 67, 65, 74, 75, 73, 73, 65, 74, 65, + 74, 8, 51, 65, 79, 132, 75, 74, 65, 74, 8, 64, 65, 79, 2, 61, 72, 72, 67, 65, 73, + 65, 69, 74, 65, 74, 8, 56, 65, 79, 84, 61, 72, 81, 82, 74, 67, 20, 8, 49, 75, 79, + 73, 61, 72, 62, 65, 3, 2, 132, 75, 72, 64, 82, 74, 67, 80, 65, 81, 61, 81, 20, 8, + 14, 52, 65, 69, 132, 65, 71, 75, 132, 81, 65, 74, 8, 82, 74, 64, 8, 54, 61, 67, 65, + 3, 2, 67, 65, 72, 64, 65, 79, 15, 20, 8, 36, 74, 67, 65, 72, 65, 67, 65, 74, 68, + 65, 69, 81, 65, 74, 8, 64, 65, 79, 8, 57, 69, 81, 84, 65, 74, 3, 2, 82, 74, 64, + 8, 57, 61, 69, 132, 65, 74, 83, 65, 79, 132, 75, 79, 67, 82, 74, 67, 8, 66, 110, 79, + 8, 64, 69, 65, 8, 37, 65, 61, 73, 81, 65, 74, 20, 2, 53, 103, 63, 68, 72, 69, 63, + 68, 65, 8, 42, 65, 132, 63, 68, 103, 66, 81, 80, 62, 65, 64, 110, 79, 66, 74, 69, 132, + 132, 65, 20, 8, 41, 110, 68, 79, 82, 74, 67, 2, 64, 65, 80, 8, 37, 65, 132, 63, 68, + 72, 82, 102, 62, 82, 63, 68, 65, 80, 8, 66, 110, 79, 8, 64, 69, 65, 8, 48, 61, 67, + 69, 132, 81, 79, 61, 81, 80, 4, 3, 2, 132, 69, 81, 87, 82, 74, 67, 65, 74, 18, 8, + 36, 74, 67, 65, 72, 65, 67, 65, 74, 68, 65, 69, 81, 65, 74, 8, 64, 65, 79, 8, 41, + 79, 69, 81, 132, 63, 68, 65, 3, 18, 2, 45, 65, 74, 132, 65, 74, 19, 18, 8, 48, 110, + 74, 63, 68, 68, 75, 66, 66, 19, 18, 8, 57, 69, 72, 68, 65, 72, 73, 19, 36, 82, 67, + 82, 132, 81, 61, 19, 18, 2, 57, 69, 72, 68, 65, 72, 73, 69, 74, 65, 8, 41, 79, 61, + 74, 63, 71, 65, 19, 8, 82, 74, 64, 8, 37, 65, 74, 74, 75, 8, 82, 74, 64, 2, 43, + 65, 72, 65, 74, 65, 8, 45, 61, 66, 66, 104, 19, 53, 81, 69, 66, 81, 82, 74, 67, 18, + 8, 64, 65, 80, 8, 49, 61, 81, 69, 75, 74, 61, 72, 3, 2, 64, 61, 74, 71, 80, 8, + 66, 110, 79, 8, 56, 65, 81, 65, 79, 61, 74, 65, 74, 18, 8, 64, 65, 79, 8, 42, 65, + 62, 61, 82, 65, 79, 3, 2, 53, 81, 69, 66, 81, 82, 74, 67, 18, 8, 64, 65, 80, 8, + 57, 65, 69, 68, 65, 79, 13, 132, 63, 68, 65, 74, 8, 82, 74, 64, 8, 64, 65, 80, 2, + 41, 65, 68, 72, 75, 84, 13, 132, 63, 68, 65, 74, 8, 56, 65, 79, 73, 103, 63, 68, 81, + 74, 69, 132, 132, 65, 80, 18, 8, 64, 65, 80, 8, 45, 75, 79, 61, 64, 9, 2, 132, 63, + 68, 65, 74, 8, 47, 65, 69, 62, 79, 65, 74, 81, 65, 74, 66, 75, 74, 64, 80, 8, 82, + 74, 64, 8, 64, 65, 80, 8, 39, 69, 80, 78, 75, 3, 2, 132, 69, 81, 69, 75, 74, 80, + 66, 75, 74, 64, 80, 8, 64, 65, 80, 8, 48, 61, 67, 69, 132, 81, 79, 61, 81, 80, 20, + 8, 53, 81, 103, 64, 81, 69, 132, 63, 68, 65, 2, 56, 65, 79, 84, 61, 72, 81, 82, 74, + 67, 80, 67, 65, 62, 103, 82, 64, 65, 20, 8, 41, 65, 82, 65, 79, 83, 65, 79, 132, 69, + 63, 68, 65, 79, 82, 74, 67, 2, 64, 65, 79, 8, 132, 81, 103, 64, 81, 69, 132, 63, 68, + 65, 74, 8, 42, 65, 62, 103, 82, 64, 65, 18, 8, 36, 74, 72, 61, 67, 65, 74, 8, 82, + 74, 64, 2, 48, 75, 62, 69, 72, 69, 65, 74, 8, 132, 75, 84, 69, 65, 8, 64, 65, 79, + 8, 57, 61, 72, 64, 82, 74, 67, 65, 74, 20, 8, 14, 56, 65, 79, 3, 2, 84, 61, 72, + 81, 82, 74, 67, 8, 64, 65, 80, 8, 52, 61, 81, 68, 61, 82, 132, 65, 80, 20, 15, 8, + 42, 65, 79, 69, 63, 68, 81, 80, 3, 2, 83, 65, 79, 84, 61, 72, 81, 82, 74, 67, 20, + 8, 39, 69, 65, 8, 37, 65, 81, 65, 69, 72, 69, 67, 82, 74, 67, 8, 64, 65, 80, 2, + 50, 62, 65, 79, 62, 110, 79, 67, 65, 79, 73, 65, 69, 132, 81, 65, 79, 80, 8, 61, 74, + 8, 84, 75, 68, 72, 81, 103, 81, 69, 67, 65, 74, 8, 82, 74, 64, 2, 67, 65, 73, 65, + 69, 74, 74, 110, 81, 87, 69, 67, 65, 74, 8, 55, 74, 81, 65, 79, 74, 65, 68, 73, 82, + 74, 67, 65, 74, 8, 82, 132, 84, 20, 2, 14, 37, 75, 81, 65, 15, 32, 8, 39, 103, 68, + 74, 65, 18, 8, 46, 109, 74, 69, 67, 69, 74, 8, 40, 72, 69, 132, 61, 62, 65, 81, 68, + 132, 81, 79, 20, 8, 27, 24, 20, 2, 46, 72, 109, 81, 65, 79, 18, 8, 46, 61, 69, 132, + 65, 79, 8, 41, 79, 69, 65, 64, 79, 69, 63, 68, 132, 81, 79, 20, 8, 24, 28, 20, 2, + 39, 65, 79, 8, 36, 82, 80, 132, 63, 68, 82, 102, 8, 87, 82, 79, 8, 40, 74, 81, 132, + 63, 68, 65, 69, 64, 82, 74, 67, 8, 110, 62, 65, 79, 2, 64, 69, 65, 8, 56, 65, 79, + 67, 65, 62, 82, 74, 67, 8, 82, 74, 64, 8, 37, 65, 74, 82, 81, 87, 82, 74, 67, 8, + 64, 65, 79, 2, 52, 61, 81, 68, 61, 82, 80, 4, 41, 65, 132, 81, 132, 103, 72, 65, 32, + 2, 53, 63, 68, 82, 132, 81, 65, 68, 79, 82, 80, 18, 8, 50, 62, 65, 79, 62, 110, 79, + 67, 65, 79, 73, 65, 69, 132, 81, 65, 79, 18, 2, 48, 61, 81, 81, 69, 74, 67, 18, 8, + 37, 110, 79, 67, 65, 79, 73, 65, 69, 132, 81, 65, 79, 18, 2, 52, 75, 132, 65, 74, 62, + 65, 79, 67, 18, 8, 14, 53, 81, 61, 64, 81, 83, 20, 8, 56, 75, 79, 132, 81, 65, 68, + 65, 79, 15, 18, 2, 46, 61, 82, 66, 73, 61, 74, 74, 18, 8, 14, 53, 81, 61, 64, 81, + 83, 20, 8, 56, 75, 79, 132, 81, 20, 4, 53, 81, 65, 72, 72, 83, 20, 15, 2, 46, 61, + 74, 87, 72, 65, 69, 20, 2, 52, 61, 81, 68, 61, 82, 80, 18, 8, 45, 20, 8, 50, 62, + 65, 79, 67, 65, 132, 63, 68, 75, 102, 18, 2, 14, 60, 69, 73, 73, 65, 79, 8, 23, 24, + 28, 21, 23, 25, 22, 20, 15, 2, 47, 65, 69, 81, 65, 79, 32, 8, 53, 65, 71, 79, 65, + 81, 20, 8, 36, 72, 65, 85, 61, 74, 64, 65, 79, 18, 8, 50, 80, 74, 61, 62, 79, 110, + 63, 71, 65, 79, 3, 2, 132, 40, 40, 2, 37, 82, 79, 20, 4, 42, 65, 68, 20, 32, 8, + 53, 63, 68, 73, 82, 64, 65, 18, 8, 53, 63, 68, 72, 110, 81, 65, 79, 132, 81, 79, 20, + 8, 31, 61, 20, 2, 57, 82, 74, 63, 71, 18, 8, 53, 78, 79, 65, 65, 132, 81, 79, 20, + 8, 23, 26, 20, 2, 51, 65, 74, 74, 65, 63, 71, 65, 8, 53, 78, 61, 74, 64, 61, 82, + 65, 79, 8, 37, 65, 79, 67, 8, 23, 30, 20, 2, 51, 61, 65, 81, 87, 18, 8, 53, 78, + 69, 65, 72, 68, 61, 67, 65, 74, 132, 81, 79, 20, 8, 25, 20, 2, 14, 39, 69, 65, 8, + 49, 61, 73, 65, 74, 8, 82, 74, 64, 8, 57, 75, 68, 74, 82, 74, 67, 65, 74, 8, 64, + 65, 79, 2, 46, 61, 74, 87, 72, 69, 132, 81, 65, 74, 8, 132, 75, 84, 69, 65, 8, 64, + 65, 80, 8, 53, 63, 68, 79, 65, 69, 62, 73, 61, 132, 63, 68, 69, 74, 65, 74, 3, 2, + 78, 65, 79, 132, 75, 74, 61, 72, 80, 8, 132, 69, 74, 64, 8, 69, 74, 8, 64, 65, 79, + 8, 46, 61, 74, 87, 72, 65, 69, 8, 87, 82, 8, 65, 79, 66, 79, 61, 67, 65, 74, 20, + 15, 2, 37, 75, 81, 65, 74, 73, 65, 69, 132, 81, 65, 79, 65, 69, 20, 2, 52, 61, 81, + 68, 61, 82, 80, 18, 8, 45, 20, 8, 50, 62, 65, 79, 67, 65, 132, 63, 68, 75, 102, 18, + 2, 60, 69, 73, 73, 65, 79, 8, 23, 23, 29, 21, 23, 23, 30, 20, 2, 25, 20, 8, 64, + 65, 79, 8, 53, 63, 68, 82, 72, 64, 65, 78, 82, 81, 61, 81, 69, 75, 74, 32, 2, 91, + 8, 24, 23, 20, 2, 14, 132, 75, 84, 69, 65, 8, 30, 8, 26, 18, 8, 91, 53, 8, 23, + 23, 8, 61, 18, 8, 53, 8, 24, 24, 8, 62, 69, 80, 8, 24, 26, 18, 8, 91, 8, 30, + 8, 62, 8, 82, 74, 64, 8, 65, 15, 2, 64, 69, 65, 8, 37, 65, 82, 79, 72, 61, 82, + 62, 82, 74, 67, 8, 83, 75, 74, 8, 47, 65, 68, 79, 78, 65, 79, 132, 75, 74, 65, 74, + 8, 62, 69, 80, 8, 87, 82, 2, 65, 69, 74, 65, 73, 8, 68, 61, 72, 62, 65, 74, 8, + 45, 61, 68, 79, 65, 20, 2, 44, 74, 8, 61, 72, 72, 65, 74, 8, 41, 103, 72, 72, 65, + 74, 8, 64, 69, 65, 132, 65, 80, 8, 51, 61, 79, 61, 67, 79, 61, 78, 68, 65, 74, 18, + 8, 132, 75, 84, 69, 65, 8, 62, 65, 69, 2, 132, 81, 103, 64, 81, 69, 132, 63, 68, 65, + 79, 132, 65, 69, 81, 80, 8, 67, 65, 132, 81, 65, 72, 72, 81, 65, 74, 8, 36, 74, 81, + 79, 103, 67, 65, 74, 8, 69, 74, 8, 36, 74, 67, 65, 72, 65, 67, 65, 74, 68, 65, 69, + 81, 65, 74, 18, 2, 84, 65, 72, 63, 68, 65, 8, 74, 61, 63, 68, 8, 91, 27, 8, 64, + 65, 79, 8, 46, 109, 74, 69, 67, 72, 69, 63, 68, 65, 74, 8, 52, 65, 67, 69, 65, 79, + 82, 74, 67, 8, 83, 75, 79, 62, 65, 68, 61, 72, 81, 65, 74, 2, 66, 69, 74, 64, 18, + 8, 68, 75, 72, 81, 8, 64, 65, 79, 8, 48, 61, 67, 69, 132, 81, 79, 61, 81, 8, 64, + 61, 80, 8, 42, 82, 81, 61, 63, 68, 81, 65, 74, 8, 82, 74, 64, 8, 64, 69, 65, 8, + 56, 75, 79, 3, 2, 132, 63, 68, 72, 103, 67, 65, 8, 64, 65, 79, 8, 53, 63, 68, 82, + 72, 64, 65, 78, 82, 81, 61, 81, 69, 75, 74, 8, 65, 69, 74, 18, 8, 61, 82, 63, 68, + 8, 81, 65, 69, 72, 81, 8, 65, 79, 8, 69, 68, 79, 8, 64, 69, 65, 2, 65, 79, 67, + 65, 68, 65, 74, 64, 65, 74, 8, 40, 74, 81, 132, 63, 68, 65, 69, 64, 82, 74, 67, 65, + 74, 8, 73, 69, 81, 20, 2, 44, 74, 8, 64, 65, 74, 8, 41, 103, 72, 72, 65, 74, 8, + 87, 82, 8, 23, 61, 8, 82, 74, 64, 8, 65, 18, 8, 132, 75, 84, 69, 65, 8, 25, 8, + 64, 69, 65, 132, 65, 80, 2, 51, 61, 79, 61, 67, 79, 61, 78, 68, 65, 74, 8, 132, 65, + 81, 87, 81, 8, 132, 69, 63, 68, 8, 64, 69, 65, 8, 53, 63, 68, 82, 72, 64, 65, 78, + 82, 81, 61, 81, 69, 75, 74, 8, 73, 69, 81, 8, 64, 65, 73, 2, 87, 82, 132, 81, 103, + 74, 64, 69, 67, 65, 74, 8, 46, 79, 65, 69, 80, 132, 63, 68, 82, 72, 69, 74, 132, 78, + 65, 71, 81, 75, 79, 8, 83, 75, 79, 8, 64, 65, 79, 8, 37, 65, 132, 63, 68, 72, 82, + 102, 66, 61, 132, 132, 82, 74, 67, 2, 69, 74, 8, 56, 65, 79, 62, 69, 74, 64, 82, 74, + 67, 20, 2, 91, 8, 29, 20, 8, 7, 39, 69, 65, 8, 53, 63, 68, 82, 72, 64, 65, 78, + 82, 81, 61, 81, 69, 75, 74, 8, 62, 65, 64, 61, 79, 66, 8, 87, 82, 79, 8, 36, 82, + 80, 66, 110, 68, 79, 82, 74, 67, 2, 69, 68, 79, 65, 79, 8, 37, 65, 132, 63, 68, 72, + 110, 132, 132, 65, 8, 64, 65, 79, 8, 42, 65, 74, 65, 68, 73, 69, 67, 82, 74, 67, 8, + 64, 65, 80, 8, 48, 61, 67, 69, 132, 81, 79, 61, 81, 80, 8, 69, 74, 2, 61, 72, 72, + 65, 74, 8, 41, 103, 72, 72, 65, 74, 18, 8, 84, 75, 8, 65, 80, 8, 132, 69, 63, 68, + 8, 82, 73, 8, 74, 65, 82, 65, 8, 40, 69, 74, 79, 69, 63, 68, 81, 82, 74, 67, 65, + 74, 8, 69, 74, 2, 64, 65, 79, 8, 53, 63, 68, 82, 72, 83, 65, 79, 66, 61, 132, 132, + 82, 74, 67, 8, 75, 64, 65, 79, 8, 82, 73, 8, 36, 82, 66, 62, 79, 69, 74, 67, 82, + 74, 67, 8, 83, 75, 74, 8, 42, 65, 72, 64, 3, 2, 73, 69, 81, 81, 65, 72, 74, 8, + 68, 61, 74, 64, 65, 72, 81, 18, 8, 84, 65, 72, 63, 68, 65, 8, 69, 74, 8, 64, 65, + 73, 8, 132, 81, 103, 64, 81, 69, 132, 63, 68, 65, 74, 8, 43, 61, 82, 80, 68, 61, 72, + 81, 80, 3, 2, 78, 72, 61, 74, 65, 8, 74, 69, 63, 68, 81, 8, 83, 75, 79, 67, 65, + 132, 65, 68, 65, 74, 8, 132, 69, 74, 64, 20, 6, 8, 14, 91, 53, 8, 29, 18, 8, 91, + 8, 30, 18, 8, 91, 8, 31, 8, 62, 69, 80, 8, 91, 8, 23, 24, 15, 2, 91, 8, 30, + 20, 8, 14, 49, 65, 84, 8, 56, 75, 79, 71, 18, 8, 56, 65, 73, 65, 74, 18, 8, 56, + 65, 79, 65, 84, 61, 74, 18, 8, 45, 61, 66, 66, 104, 18, 8, 47, 69, 74, 74, 104, 18, + 8, 47, 61, 82, 79, 104, 80, 15, 2, 39, 69, 65, 8, 53, 63, 68, 82, 72, 64, 65, 78, + 82, 81, 61, 81, 69, 75, 74, 8, 84, 69, 79, 71, 81, 8, 82, 74, 81, 65, 79, 8, 64, + 65, 79, 8, 36, 82, 66, 132, 69, 63, 68, 81, 2, 64, 65, 79, 8, 46, 109, 74, 69, 67, + 72, 69, 63, 68, 65, 74, 8, 52, 65, 67, 69, 65, 79, 82, 74, 67, 8, 87, 82, 8, 51, + 75, 81, 80, 64, 61, 73, 8, 61, 72, 80, 8, 132, 81, 61, 61, 81, 72, 69, 63, 68, 65, + 80, 2, 53, 63, 68, 82, 72, 61, 82, 66, 132, 69, 63, 68, 81, 80, 75, 79, 67, 61, 74, + 8, 82, 74, 64, 8, 71, 79, 61, 66, 81, 8, 132, 81, 61, 61, 81, 72, 69, 63, 68, 65, + 74, 8, 36, 82, 66, 81, 79, 61, 67, 65, 80, 2, 74, 65, 62, 65, 74, 8, 64, 65, 74, + 8, 46, 79, 65, 69, 80, 132, 63, 68, 82, 72, 69, 74, 132, 78, 65, 71, 81, 75, 79, 65, + 74, 20, 8, 44, 68, 79, 65, 8, 37, 65, 79, 69, 63, 68, 81, 65, 8, 82, 74, 64, 2, + 56, 65, 79, 66, 110, 67, 82, 74, 67, 65, 74, 18, 8, 132, 75, 84, 69, 65, 8, 64, 69, + 65, 8, 61, 74, 8, 132, 69, 65, 8, 67, 65, 79, 69, 63, 68, 81, 65, 81, 65, 74, 8, + 56, 65, 79, 66, 110, 67, 82, 74, 67, 65, 74, 2, 82, 74, 64, 8, 37, 65, 132, 63, 68, + 65, 69, 64, 65, 8, 65, 79, 67, 65, 68, 65, 74, 8, 82, 74, 81, 65, 79, 8, 64, 65, + 79, 8, 41, 69, 79, 73, 61, 8, 7, 53, 63, 68, 82, 72, 3, 2, 64, 65, 78, 82, 81, + 61, 81, 69, 75, 74, 8, 38, 68, 61, 79, 72, 75, 81, 81, 65, 74, 62, 82, 79, 67, 6, + 20, 2, 14, 44, 74, 8, 64, 65, 74, 8, 103, 82, 102, 65, 79, 65, 74, 8, 53, 63, 68, + 82, 72, 61, 74, 67, 65, 72, 65, 67, 65, 74, 68, 65, 69, 81, 65, 74, 15, 8, 132, 81, + 65, 68, 81, 8, 64, 69, 65, 2, 53, 63, 68, 82, 72, 64, 65, 78, 82, 81, 61, 81, 69, + 75, 74, 8, 87, 82, 73, 8, 48, 61, 67, 69, 132, 81, 79, 61, 81, 8, 69, 74, 8, 64, + 65, 73, 8, 56, 65, 79, 68, 103, 72, 81, 74, 69, 80, 2, 65, 69, 74, 65, 79, 8, 56, + 65, 79, 84, 61, 72, 81, 82, 74, 67, 80, 19, 39, 65, 78, 82, 81, 61, 81, 69, 75, 74, + 8, 69, 73, 8, 53, 69, 74, 74, 65, 8, 64, 65, 80, 8, 91, 8, 27, 31, 2, 64, 65, + 79, 8, 53, 81, 103, 64, 81, 65, 19, 50, 79, 64, 74, 82, 74, 67, 8, 83, 75, 73, 8, + 25, 22, 20, 8, 48, 61, 69, 8, 23, 30, 27, 25, 8, 14, 42, 20, 8, 53, 20, 2, 53, + 20, 8, 24, 28, 23, 8, 66, 66, 20, 15, 8, 41, 110, 79, 8, 69, 68, 79, 65, 8, 42, + 65, 132, 63, 68, 103, 66, 81, 80, 83, 65, 79, 84, 61, 72, 81, 82, 74, 67, 8, 62, 72, + 65, 69, 62, 65, 74, 8, 69, 74, 2, 64, 69, 65, 132, 65, 79, 8, 37, 65, 87, 69, 65, + 68, 82, 74, 67, 8, 64, 69, 65, 8, 37, 65, 132, 81, 69, 73, 73, 82, 74, 67, 65, 74, + 8, 64, 65, 79, 8, 48, 69, 74, 69, 132, 81, 65, 79, 69, 61, 72, 3, 2, 44, 74, 132, + 81, 79, 82, 71, 81, 69, 75, 74, 8, 66, 110, 79, 8, 64, 69, 65, 8, 53, 81, 61, 64, + 81, 73, 61, 67, 69, 132, 81, 79, 61, 81, 65, 8, 83, 75, 73, 8, 24, 27, 20, 8, 48, + 61, 69, 8, 23, 30, 25, 27, 2, 73, 61, 102, 67, 65, 62, 65, 74, 64, 20, 2, 91, 8, + 31, 8, 82, 74, 64, 8, 91, 8, 23, 24, 20, 2, 14, 132, 75, 84, 69, 65, 8, 91, 8, + 23, 25, 18, 8, 23, 26, 18, 8, 25, 23, 8, 61, 8, 62, 69, 80, 8, 66, 18, 8, 91, + 8, 24, 18, 8, 91, 8, 25, 18, 8, 91, 53, 8, 26, 26, 18, 8, 91, 8, 27, 24, 15, + 2, 39, 69, 65, 8, 53, 69, 81, 87, 82, 74, 67, 65, 74, 18, 8, 87, 82, 8, 84, 65, + 72, 63, 68, 65, 74, 8, 64, 65, 79, 8, 56, 75, 79, 132, 69, 81, 87, 65, 74, 64, 65, + 8, 64, 65, 79, 2, 39, 65, 78, 82, 81, 61, 81, 69, 75, 74, 8, 64, 69, 65, 8, 48, + 69, 72, 67, 72, 69, 65, 64, 65, 79, 8, 82, 74, 64, 8, 64, 69, 65, 8, 46, 79, 65, + 69, 80, 132, 63, 68, 82, 72, 3, 2, 69, 74, 132, 78, 65, 71, 81, 75, 79, 65, 74, 8, + 73, 69, 81, 8, 36, 74, 67, 61, 62, 65, 8, 64, 65, 79, 8, 54, 61, 67, 65, 80, 75, + 79, 64, 74, 82, 74, 67, 8, 65, 69, 74, 72, 61, 64, 65, 81, 18, 2, 84, 65, 79, 64, + 65, 74, 8, 74, 61, 63, 68, 8, 14, 37, 65, 64, 110, 79, 66, 74, 69, 80, 8, 67, 65, + 68, 61, 72, 81, 65, 74, 15, 18, 8, 64, 75, 63, 68, 8, 69, 132, 81, 8, 64, 65, 79, + 8, 56, 75, 79, 3, 2, 132, 69, 81, 87, 65, 74, 64, 65, 8, 83, 65, 79, 78, 66, 72, + 69, 63, 68, 81, 65, 81, 18, 8, 61, 82, 66, 8, 36, 74, 81, 79, 61, 67, 8, 64, 79, + 65, 69, 65, 79, 8, 48, 69, 81, 67, 72, 69, 65, 64, 65, 79, 2, 75, 64, 65, 79, 8, + 65, 69, 74, 65, 80, 8, 64, 65, 79, 8, 62, 65, 69, 64, 65, 74, 8, 46, 79, 65, 69, + 80, 132, 63, 68, 82, 72, 69, 74, 132, 78, 65, 71, 81, 75, 79, 65, 74, 8, 65, 69, 74, + 65, 2, 53, 69, 81, 87, 82, 74, 67, 8, 69, 74, 74, 65, 79, 68, 61, 72, 62, 8, 23, + 22, 8, 54, 61, 67, 65, 74, 8, 61, 74, 87, 82, 62, 65, 79, 61, 82, 73, 65, 74, 20, + 8, 14, 39, 69, 65, 2, 84, 69, 64, 65, 79, 132, 78, 79, 65, 63, 68, 65, 74, 8, 70, + 65, 64, 75, 63, 68, 8, 69, 74, 8, 65, 69, 74, 65, 73, 8, 132, 75, 72, 63, 68, 65, + 74, 15, 2, 41, 61, 72, 72, 65, 8, 87, 84, 65, 69, 8, 64, 65, 79, 8, 61, 74, 84, + 65, 132, 65, 74, 64, 65, 74, 8, 48, 69, 81, 67, 72, 69, 65, 64, 65, 79, 8, 75, 64, + 65, 79, 8, 64, 65, 79, 8, 56, 75, 79, 3, 2, 132, 69, 81, 87, 65, 74, 64, 65, 8, + 64, 65, 79, 8, 37, 65, 132, 63, 68, 72, 82, 102, 66, 61, 132, 132, 82, 74, 67, 18, 8, + 132, 75, 8, 73, 82, 102, 8, 64, 69, 65, 8, 40, 79, 72, 65, 64, 69, 67, 82, 74, 67, + 2, 64, 65, 79, 8, 69, 74, 8, 41, 79, 61, 67, 65, 8, 132, 81, 65, 68, 65, 74, 64, + 65, 74, 8, 53, 61, 63, 68, 65, 74, 8, 62, 69, 80, 8, 87, 82, 79, 8, 74, 103, 63, + 68, 132, 81, 65, 74, 2, 14, 75, 79, 64, 65, 74, 81, 72, 69, 63, 68, 65, 74, 8, 75, + 64, 65, 79, 8, 61, 82, 102, 65, 79, 75, 79, 64, 65, 74, 81, 72, 69, 63, 68, 65, 74, + 15, 8, 39, 65, 78, 82, 81, 61, 81, 69, 75, 74, 80, 3, 2, 132, 69, 81, 87, 82, 74, + 67, 8, 61, 82, 80, 67, 65, 132, 65, 81, 87, 81, 8, 84, 65, 79, 64, 65, 74, 20, 8, + 44, 74, 8, 64, 69, 65, 132, 65, 79, 8, 74, 103, 63, 68, 132, 81, 65, 74, 8, 53, 69, + 81, 87, 82, 74, 67, 2, 69, 132, 81, 8, 61, 72, 80, 64, 61, 74, 74, 8, 64, 69, 65, + 8, 39, 65, 78, 82, 81, 61, 81, 69, 75, 74, 8, 75, 68, 74, 65, 8, 52, 110, 63, 71, + 132, 69, 63, 68, 81, 8, 61, 82, 66, 8, 64, 69, 65, 2, 60, 61, 68, 72, 8, 64, 65, + 79, 8, 36, 74, 84, 65, 132, 65, 74, 64, 65, 74, 8, 14, 61, 72, 132, 75, 8, 61, 82, + 63, 68, 8, 69, 74, 8, 64, 65, 73, 8, 41, 61, 72, 72, 65, 18, 8, 64, 61, 102, 2, + 84, 65, 74, 69, 67, 65, 79, 8, 61, 72, 80, 8, 65, 69, 74, 8, 39, 79, 69, 81, 81, + 65, 72, 8, 64, 65, 79, 8, 48, 69, 81, 67, 72, 69, 65, 64, 65, 79, 8, 61, 74, 84, + 65, 132, 65, 74, 64, 8, 69, 132, 81, 15, 2, 62, 65, 132, 63, 68, 72, 82, 102, 66, 103, + 68, 69, 67, 8, 69, 74, 8, 64, 65, 74, 70, 65, 74, 69, 67, 65, 74, 8, 53, 61, 63, + 68, 65, 74, 18, 8, 69, 74, 8, 64, 65, 74, 65, 74, 8, 64, 69, 65, 8, 37, 65, 3, + 2, 132, 63, 68, 72, 82, 102, 66, 61, 132, 132, 82, 74, 67, 8, 69, 74, 8, 64, 65, 79, + 8, 72, 65, 81, 87, 81, 65, 74, 8, 53, 69, 81, 87, 82, 74, 67, 8, 61, 82, 80, 67, + 65, 132, 65, 81, 87, 81, 8, 84, 61, 79, 20, 2, 14, 40, 69, 74, 8, 84, 65, 69, 81, + 65, 79, 65, 79, 8, 40, 69, 74, 84, 61, 74, 64, 8, 67, 65, 67, 65, 74, 8, 64, 69, + 65, 8, 37, 65, 132, 63, 68, 72, 82, 102, 66, 103, 66, 66, 82, 74, 67, 8, 69, 74, 2, + 64, 65, 74, 8, 69, 74, 8, 41, 79, 61, 67, 65, 8, 132, 81, 65, 68, 65, 74, 64, 65, + 74, 8, 36, 74, 67, 65, 72, 65, 67, 65, 74, 68, 65, 69, 81, 65, 74, 8, 69, 132, 81, + 8, 61, 72, 80, 64, 61, 74, 74, 2, 82, 74, 87, 82, 72, 103, 132, 132, 69, 67, 20, 15, + 8, 37, 65, 69, 8, 64, 65, 79, 8, 40, 69, 74, 72, 61, 64, 82, 74, 67, 8, 87, 82, + 8, 64, 69, 65, 132, 65, 79, 8, 87, 84, 65, 69, 81, 65, 74, 2, 53, 69, 81, 87, 82, + 74, 67, 8, 68, 61, 81, 8, 64, 65, 79, 8, 56, 75, 79, 132, 69, 81, 87, 65, 74, 64, + 65, 8, 61, 82, 66, 8, 64, 69, 65, 132, 65, 8, 37, 65, 132, 81, 69, 73, 73, 82, 74, + 67, 2, 70, 65, 64, 65, 80, 73, 61, 72, 8, 62, 65, 132, 75, 74, 64, 65, 79, 80, 8, + 68, 69, 74, 87, 82, 84, 65, 69, 132, 65, 74, 20, 2, 91, 8, 23, 23, 20, 2, 110, 62, + 65, 79, 8, 64, 69, 65, 8, 37, 65, 132, 63, 68, 72, 110, 132, 132, 65, 8, 64, 65, 79, + 8, 39, 65, 78, 82, 81, 61, 81, 69, 75, 74, 8, 69, 132, 81, 8, 65, 69, 74, 2, 51, + 79, 75, 81, 75, 71, 75, 72, 72, 8, 87, 82, 8, 66, 110, 68, 79, 65, 74, 18, 8, 64, + 61, 80, 8, 74, 61, 63, 68, 8, 56, 75, 79, 72, 65, 132, 82, 74, 67, 8, 83, 75, 73, + 8, 56, 75, 79, 3, 2, 69, 69, 81, 87, 65, 74, 64, 65, 74, 8, 82, 74, 64, 8, 73, + 69, 74, 64, 65, 132, 81, 65, 74, 80, 8, 87, 84, 65, 69, 8, 48, 69, 81, 67, 72, 69, + 65, 64, 65, 79, 74, 8, 87, 82, 8, 82, 74, 81, 65, 79, 3, 2, 132, 63, 68, 79, 65, + 69, 62, 65, 74, 8, 69, 132, 81, 20, 2, 91, 8, 23, 24, 20, 2, 36, 74, 64, 65, 79, + 82, 74, 67, 65, 74, 8, 64, 69, 65, 132, 65, 79, 8, 42, 65, 132, 63, 68, 103, 66, 81, + 80, 61, 74, 84, 65, 69, 132, 82, 74, 67, 8, 62, 65, 64, 110, 79, 66, 65, 74, 2, 64, + 65, 79, 8, 42, 65, 74, 65, 68, 73, 69, 67, 82, 74, 67, 8, 64, 65, 79, 8, 46, 109, + 74, 69, 67, 72, 69, 63, 68, 65, 74, 8, 52, 65, 67, 69, 65, 79, 82, 74, 67, 18, 8, + 36, 62, 3, 2, 81, 65, 69, 72, 82, 74, 67, 8, 66, 110, 79, 8, 46, 69, 79, 63, 68, + 65, 74, 19, 8, 82, 74, 64, 8, 53, 63, 68, 82, 72, 84, 65, 132, 65, 74, 8, 69, 74, + 8, 51, 75, 81, 80, 64, 61, 73, 20, 2, 14, 39, 65, 79, 8, 48, 61, 67, 69, 132, 81, + 79, 61, 81, 20, 15, 2, 24, 28, 20, 8, 45, 82, 74, 69, 8, 23, 30, 23, 23, 8, 82, + 74, 64, 8, 23, 23, 20, 8, 48, 103, 79, 87, 8, 23, 30, 31, 30, 2, 62, 65, 84, 65, + 79, 71, 132, 81, 65, 72, 72, 69, 67, 81, 8, 84, 65, 79, 64, 65, 74, 18, 8, 132, 75, + 64, 61, 102, 8, 62, 69, 80, 8, 64, 61, 68, 69, 74, 8, 61, 82, 63, 68, 8, 74, 75, + 63, 68, 8, 64, 69, 65, 2, 52, 65, 69, 74, 69, 67, 82, 74, 67, 8, 78, 78, 20, 8, + 64, 65, 80, 8, 43, 69, 74, 81, 65, 79, 67, 65, 62, 103, 82, 64, 65, 80, 8, 64, 65, + 80, 8, 52, 61, 81, 68, 68, 61, 82, 132, 65, 80, 2, 65, 79, 66, 75, 79, 64, 65, 79, + 72, 69, 63, 68, 8, 84, 61, 79, 20, 8, 14, 48, 69, 81, 81, 65, 72, 8, 84, 61, 79, + 65, 74, 8, 66, 110, 79, 8, 64, 69, 65, 132, 65, 74, 8, 60, 84, 65, 63, 71, 2, 69, + 73, 8, 40, 81, 61, 81, 8, 74, 69, 63, 68, 81, 8, 73, 65, 68, 79, 8, 83, 75, 79, + 67, 65, 132, 65, 68, 65, 74, 8, 82, 74, 64, 8, 68, 61, 62, 65, 74, 8, 84, 69, 79, + 8, 64, 65, 80, 3, 2, 68, 61, 72, 62, 8, 64, 65, 73, 8, 43, 61, 82, 80, 84, 61, + 79, 81, 8, 57, 65, 79, 64, 65, 79, 73, 61, 74, 74, 8, 75, 62, 69, 67, 65, 79, 8, + 51, 75, 132, 69, 81, 69, 75, 74, 2, 66, 110, 79, 8, 64, 69, 65, 8, 48, 75, 74, 61, + 81, 65, 8, 36, 78, 79, 69, 72, 8, 21, 8, 45, 82, 74, 69, 8, 64, 69, 65, 8, 62, + 69, 80, 68, 65, 79, 8, 62, 65, 87, 75, 67, 65, 74, 65, 2, 56, 65, 79, 67, 110, 81, + 82, 74, 67, 8, 83, 75, 74, 8, 73, 75, 74, 61, 81, 72, 69, 63, 68, 8, 29, 27, 8, + 11, 8, 24, 24, 27, 8, 48, 61, 79, 71, 2, 82, 74, 81, 65, 79, 73, 8, 25, 22, 20, + 8, 48, 103, 79, 87, 8, 64, 20, 8, 44, 80, 20, 8, 62, 65, 84, 69, 72, 72, 69, 67, + 81, 20, 15, 2, 53, 65, 69, 81, 8, 64, 65, 73, 8, 23, 20, 8, 45, 82, 74, 69, 8, + 64, 20, 8, 44, 80, 20, 8, 83, 65, 79, 132, 69, 65, 68, 81, 8, 64, 65, 79, 8, 65, + 68, 65, 73, 20, 2, 51, 66, 65, 79, 64, 65, 62, 61, 68, 74, 19, 53, 63, 68, 61, 66, + 66, 74, 65, 79, 8, 53, 63, 68, 109, 74, 65, 62, 65, 79, 67, 8, 64, 69, 65, 8, 42, + 65, 132, 63, 68, 103, 66, 81, 65, 8, 64, 65, 80, 2, 43, 61, 82, 80, 84, 61, 79, 81, + 80, 8, 14, 66, 110, 79, 8, 46, 69, 79, 63, 68, 132, 81, 79, 61, 102, 65, 8, 23, 22, + 15, 8, 82, 74, 64, 8, 62, 65, 87, 69, 65, 68, 81, 8, 65, 79, 8, 61, 82, 63, 68, + 2, 14, 83, 75, 74, 8, 64, 69, 65, 132, 65, 73, 8, 54, 61, 67, 65, 8, 61, 62, 8, + 62, 69, 80, 8, 87, 82, 73, 8, 25, 23, 20, 8, 48, 103, 79, 87, 8, 23, 31, 22, 22, + 15, 8, 64, 69, 65, 2, 52, 65, 73, 82, 74, 65, 79, 61, 81, 69, 75, 74, 8, 14, 83, + 75, 74, 8, 23, 22, 26, 18, 23, 28, 8, 22, 8, 73, 75, 74, 61, 81, 72, 69, 63, 68, + 15, 2, 23, 22, 26, 23, 18, 28, 29, 8, 7, 20, 8, 53, 63, 68, 109, 74, 65, 62, 65, + 79, 67, 8, 68, 61, 81, 8, 83, 75, 73, 8, 50, 71, 81, 75, 62, 65, 79, 8, 61, 62, + 8, 69, 73, 2, 56, 65, 79, 67, 72, 65, 69, 63, 68, 8, 87, 82, 8, 64, 65, 74, 8, + 61, 74, 64, 65, 79, 65, 74, 8, 43, 61, 82, 80, 84, 61, 79, 81, 65, 74, 8, 14, 132, + 69, 65, 68, 65, 8, 60, 82, 132, 61, 73, 73, 65, 74, 3, 2, 132, 81, 65, 72, 72, 82, + 74, 67, 8, 61, 82, 66, 8, 37, 72, 61, 81, 81, 8, 23, 26, 8, 52, 15, 8, 64, 69, + 65, 8, 67, 79, 109, 102, 81, 65, 8, 41, 72, 103, 63, 68, 65, 8, 3, 2, 23, 22, 26, + 23, 18, 30, 31, 8, 77, 73, 8, 3, 8, 82, 74, 64, 8, 64, 69, 65, 8, 67, 79, 109, + 102, 81, 65, 8, 36, 74, 87, 61, 68, 72, 8, 60, 69, 73, 73, 65, 79, 8, 78, 78, 20, + 8, 24, 27, 22, 2, 87, 82, 8, 79, 65, 69, 74, 69, 67, 65, 74, 8, 14, 82, 74, 64, + 8, 87, 82, 8, 68, 65, 69, 87, 65, 74, 15, 18, 8, 84, 61, 80, 8, 65, 79, 8, 75, + 68, 74, 65, 8, 66, 79, 65, 73, 64, 65, 2, 43, 110, 72, 66, 65, 8, 74, 69, 63, 68, + 81, 8, 62, 65, 84, 103, 72, 81, 69, 67, 65, 74, 8, 71, 61, 74, 74, 20, 8, 57, 69, + 79, 8, 68, 61, 62, 65, 74, 8, 64, 65, 80, 68, 61, 72, 62, 2, 64, 69, 65, 8, 40, + 79, 68, 109, 68, 82, 74, 67, 8, 132, 65, 69, 74, 65, 79, 8, 52, 65, 73, 82, 74, 65, + 79, 61, 81, 69, 75, 74, 8, 83, 75, 73, 8, 23, 20, 8, 50, 71, 81, 75, 62, 65, 79, + 2, 64, 20, 8, 44, 80, 20, 8, 61, 62, 8, 83, 75, 74, 8, 23, 24, 27, 22, 8, 7, + 8, 61, 82, 66, 8, 23, 27, 22, 22, 8, 0, 16, 8, 70, 103, 68, 79, 72, 69, 63, 68, + 8, 62, 65, 3, 2, 132, 63, 68, 72, 75, 132, 132, 65, 74, 20, 8, 39, 69, 65, 80, 8, + 73, 61, 63, 68, 81, 8, 66, 110, 79, 8, 64, 69, 65, 8, 60, 65, 69, 81, 8, 83, 75, + 73, 8, 23, 20, 8, 50, 71, 81, 75, 62, 65, 79, 2, 23, 30, 31, 31, 8, 62, 69, 80, + 8, 87, 82, 73, 8, 25, 23, 20, 8, 48, 103, 79, 87, 8, 23, 31, 22, 22, 8, 23, 24, + 27, 8, 48, 61, 79, 71, 20, 2, 39, 61, 8, 68, 69, 65, 79, 74, 61, 63, 68, 8, 24, + 24, 27, 8, 23, 22, 26, 23, 18, 28, 29, 8, 23, 24, 27, 8, 48, 61, 79, 71, 2, 23, + 25, 31, 23, 18, 28, 29, 8, 7, 28, 67, 65, 62, 79, 61, 82, 63, 68, 81, 8, 84, 65, + 79, 64, 65, 74, 8, 14, 69, 73, 8, 40, 81, 61, 81, 8, 61, 62, 65, 79, 8, 74, 82, + 79, 8, 23, 24, 27, 22, 18, 22, 22, 8, 11, 2, 83, 75, 79, 67, 65, 132, 65, 68, 65, + 74, 8, 132, 69, 74, 64, 15, 18, 8, 132, 75, 8, 69, 132, 81, 8, 65, 69, 74, 65, 8, + 56, 65, 79, 132, 81, 103, 79, 71, 82, 74, 67, 8, 64, 65, 79, 8, 51, 75, 132, 69, 81, + 69, 75, 74, 2, 50, 20, 8, 44, 3, 23, 23, 19, 28, 8, 82, 73, 8, 20, 8, 20, 8, + 20, 8, 23, 26, 23, 18, 28, 29, 8, 7, 8, 74, 75, 81, 68, 84, 65, 74, 64, 69, 67, + 20, 2, 41, 110, 79, 8, 51, 79, 110, 66, 82, 74, 67, 8, 64, 65, 79, 8, 51, 79, 75, + 70, 65, 71, 81, 65, 18, 8, 55, 65, 62, 65, 79, 84, 61, 63, 68, 82, 74, 67, 8, 82, + 74, 64, 2, 36, 62, 74, 61, 68, 73, 65, 8, 64, 65, 79, 8, 44, 74, 132, 81, 61, 72, + 72, 61, 81, 69, 75, 74, 8, 69, 132, 81, 8, 65, 69, 74, 65, 8, 65, 69, 74, 73, 61, + 72, 69, 67, 65, 8, 56, 65, 79, 3, 2, 67, 110, 81, 82, 74, 67, 8, 83, 75, 74, 8, + 26, 22, 22, 8, 64, 65, 79, 8, 36, 74, 72, 61, 67, 65, 71, 75, 132, 81, 65, 74, 18, + 8, 70, 65, 64, 75, 63, 68, 8, 74, 69, 63, 68, 81, 8, 73, 65, 68, 79, 2, 61, 72, + 80, 8, 25, 22, 22, 8, 7, 8, 66, 110, 79, 8, 70, 65, 64, 65, 8, 44, 74, 132, 81, + 61, 72, 72, 61, 81, 69, 75, 74, 18, 8, 87, 82, 8, 87, 61, 68, 72, 65, 74, 18, 8, + 84, 75, 62, 65, 69, 2, 64, 69, 65, 8, 46, 75, 132, 81, 65, 74, 8, 66, 110, 79, 8, + 37, 65, 72, 65, 82, 63, 68, 81, 82, 74, 67, 80, 71, 109, 79, 78, 65, 79, 18, 8, 47, + 61, 73, 78, 65, 74, 8, 82, 74, 64, 2, 48, 75, 81, 75, 79, 65, 74, 8, 74, 69, 63, + 68, 81, 8, 73, 69, 81, 8, 87, 82, 8, 62, 65, 79, 65, 63, 68, 74, 65, 74, 8, 132, + 69, 74, 64, 20, 8, 27, 22, 22, 8, 62, 69, 80, 8, 28, 27, 24, 8, 11, 8, 39, 69, + 65, 8, 46, 75, 132, 81, 65, 74, 2, 64, 65, 80, 8, 53, 81, 79, 75, 73, 83, 65, 79, + 62, 79, 61, 82, 63, 68, 80, 8, 14, 84, 103, 68, 79, 65, 74, 64, 8, 64, 65, 79, 8, + 36, 62, 74, 61, 68, 73, 65, 78, 79, 110, 66, 82, 74, 67, 15, 2, 64, 65, 79, 8, 44, + 74, 132, 81, 61, 72, 72, 61, 81, 69, 75, 74, 8, 84, 65, 79, 64, 65, 74, 8, 64, 65, + 73, 8, 36, 62, 74, 65, 68, 73, 65, 79, 8, 74, 61, 63, 68, 8, 48, 61, 102, 3, 2, + 67, 61, 62, 65, 8, 64, 65, 80, 8, 69, 73, 8, 91, 8, 28, 8, 65, 74, 81, 68, 61, + 72, 81, 65, 74, 65, 74, 8, 54, 61, 79, 69, 66, 80, 8, 69, 74, 8, 52, 65, 63, 68, + 74, 82, 74, 67, 8, 67, 65, 132, 81, 65, 72, 72, 81, 20, 2, 39, 69, 65, 8, 48, 69, + 65, 81, 68, 65, 8, 84, 69, 79, 64, 8, 83, 75, 74, 8, 65, 79, 66, 75, 72, 67, 81, + 65, 79, 8, 44, 74, 62, 65, 81, 79, 69, 65, 62, 132, 65, 81, 87, 82, 74, 67, 2, 64, + 65, 80, 8, 48, 65, 132, 132, 65, 79, 80, 8, 61, 74, 8, 66, 110, 79, 8, 64, 65, 74, + 8, 83, 75, 72, 72, 65, 74, 8, 46, 61, 72, 65, 74, 64, 65, 79, 73, 75, 74, 61, 81, + 8, 62, 65, 79, 65, 63, 68, 74, 65, 81, 18, 2, 82, 74, 64, 8, 87, 84, 61, 79, 8, + 61, 82, 63, 68, 8, 64, 61, 74, 74, 8, 84, 65, 74, 74, 8, 64, 65, 79, 8, 91, 8, + 25, 18, 8, 91, 8, 23, 23, 18, 8, 91, 8, 23, 30, 8, 82, 74, 64, 8, 91, 8, 23, + 29, 8, 42, 65, 72, 81, 82, 74, 67, 2, 62, 65, 68, 61, 72, 81, 65, 74, 8, 14, 69, + 73, 8, 41, 61, 72, 72, 65, 8, 65, 69, 74, 65, 80, 8, 56, 65, 79, 73, 69, 74, 64, + 65, 79, 82, 74, 67, 8, 82, 73, 8, 27, 11, 8, 62, 69, 80, 8, 29, 22, 11, 8, 75, + 64, 65, 79, 8, 83, 75, 74, 2, 23, 23, 8, 29, 11, 8, 62, 69, 80, 8, 23, 24, 22, + 11, 15, 20, 2, 48, 61, 102, 74, 61, 68, 73, 65, 74, 8, 56, 75, 79, 132, 63, 68, 82, + 62, 8, 67, 65, 72, 65, 69, 132, 81, 65, 81, 8, 84, 65, 79, 64, 65, 74, 18, 8, 64, + 69, 65, 8, 64, 61, 68, 69, 74, 2, 61, 62, 87, 69, 65, 72, 65, 74, 18, 8, 64, 61, + 102, 8, 64, 69, 65, 8, 47, 65, 82, 81, 65, 8, 61, 82, 80, 8, 64, 65, 74, 8, 67, + 79, 75, 102, 65, 74, 8, 53, 81, 103, 64, 81, 65, 74, 2, 74, 61, 63, 68, 8, 64, 65, + 73, 8, 78, 72, 61, 81, 81, 65, 74, 8, 47, 61, 74, 64, 65, 8, 61, 62, 87, 69, 65, + 68, 65, 74, 20, 2, 91, 27, 20, 2, 7, 40, 80, 8, 69, 132, 81, 8, 61, 72, 132, 75, + 8, 14, 73, 65, 69, 74, 65, 79, 8, 55, 65, 62, 65, 79, 87, 65, 82, 67, 82, 74, 67, + 8, 74, 61, 63, 68, 15, 8, 67, 61, 74, 87, 8, 82, 74, 3, 2, 84, 61, 68, 79, 132, + 63, 68, 65, 69, 74, 72, 69, 63, 68, 18, 8, 64, 61, 102, 8, 87, 61, 68, 72, 79, 65, + 69, 63, 68, 65, 8, 47, 65, 82, 81, 65, 8, 83, 75, 73, 8, 47, 61, 74, 64, 65, 8, + 74, 61, 63, 68, 2, 64, 65, 79, 8, 67, 79, 75, 102, 65, 74, 8, 53, 81, 61, 64, 81, + 8, 87, 69, 65, 68, 65, 74, 8, 84, 65, 79, 64, 65, 74, 33, 8, 83, 69, 65, 72, 73, + 65, 68, 79, 8, 84, 69, 79, 64, 8, 65, 80, 2, 82, 73, 67, 65, 71, 65, 68, 79, 81, + 8, 132, 65, 69, 74, 18, 8, 64, 61, 102, 8, 65, 69, 74, 65, 8, 36, 62, 84, 61, 74, + 64, 65, 79, 82, 74, 67, 8, 61, 82, 80, 8, 64, 65, 79, 2, 67, 79, 75, 102, 65, 74, + 8, 53, 81, 61, 64, 81, 8, 74, 61, 63, 68, 8, 64, 65, 73, 8, 47, 61, 74, 64, 65, + 8, 82, 74, 64, 8, 64, 65, 74, 8, 71, 72, 65, 69, 74, 65, 74, 2, 53, 81, 103, 64, + 81, 65, 74, 8, 65, 79, 66, 75, 72, 67, 65, 74, 8, 84, 69, 79, 64, 20, 8, 14, 39, + 65, 80, 68, 61, 72, 62, 8, 67, 72, 61, 82, 62, 65, 8, 69, 63, 68, 8, 74, 69, 63, + 68, 81, 18, 2, 64, 61, 102, 8, 68, 69, 65, 79, 8, 65, 69, 74, 65, 8, 57, 75, 68, + 74, 82, 74, 67, 80, 74, 75, 81, 8, 65, 69, 74, 81, 79, 65, 81, 65, 74, 8, 84, 69, + 79, 64, 20, 15, 6, 2, 53, 63, 68, 75, 74, 8, 62, 65, 69, 8, 64, 65, 79, 8, 83, + 75, 79, 69, 67, 65, 74, 8, 37, 65, 79, 61, 81, 82, 74, 67, 8, 69, 132, 81, 8, 64, + 61, 79, 61, 82, 66, 8, 68, 69, 74, 0, 129, 3, 2, 67, 65, 84, 69, 65, 132, 65, 74, + 8, 84, 75, 79, 64, 65, 74, 18, 8, 64, 61, 102, 8, 64, 69, 65, 8, 36, 74, 132, 69, + 63, 68, 81, 65, 74, 8, 68, 69, 65, 79, 110, 62, 65, 79, 8, 132, 65, 68, 79, 2, 84, + 65, 69, 81, 8, 61, 82, 80, 65, 69, 74, 61, 74, 64, 65, 79, 67, 65, 68, 65, 74, 18, + 8, 14, 82, 74, 64, 8, 69, 74, 8, 64, 65, 74, 8, 46, 79, 65, 69, 132, 65, 74, 18, + 8, 73, 69, 81, 2, 64, 65, 74, 65, 74, 8, 69, 63, 68, 8, 41, 110, 68, 72, 82, 74, + 67, 8, 68, 61, 62, 65, 15, 18, 8, 69, 132, 81, 8, 64, 82, 79, 63, 68, 61, 82, 80, + 8, 64, 69, 65, 8, 36, 82, 66, 66, 61, 132, 132, 82, 74, 67, 2, 83, 65, 79, 62, 79, + 65, 69, 81, 65, 81, 18, 8, 64, 61, 102, 8, 65, 68, 65, 79, 8, 65, 69, 74, 8, 57, + 75, 68, 74, 82, 74, 67, 80, 110, 62, 65, 79, 132, 63, 68, 82, 102, 8, 61, 72, 80, 8, + 65, 69, 74, 2, 57, 75, 68, 74, 82, 74, 67, 80, 73, 61, 74, 67, 65, 72, 8, 65, 69, + 74, 81, 79, 65, 81, 65, 74, 8, 84, 69, 79, 64, 20, 2, 7, 44, 63, 68, 8, 73, 109, + 63, 68, 81, 65, 8, 74, 75, 63, 68, 8, 64, 61, 79, 61, 74, 8, 65, 79, 69, 74, 74, + 65, 79, 74, 18, 8, 64, 61, 102, 8, 64, 65, 79, 8, 43, 65, 79, 79, 2, 53, 81, 61, + 64, 81, 132, 86, 74, 64, 69, 71, 82, 80, 8, 14, 64, 61, 80, 8, 83, 75, 79, 69, 67, + 65, 8, 48, 61, 72, 15, 8, 61, 82, 80, 67, 65, 66, 110, 68, 79, 81, 8, 68, 61, 81, + 18, 8, 83, 75, 79, 2, 64, 65, 73, 8, 46, 79, 69, 65, 67, 65, 8, 68, 103, 81, 81, + 65, 74, 8, 69, 74, 8, 64, 65, 73, 8, 65, 69, 67, 65, 74, 81, 72, 69, 63, 68, 65, + 74, 8, 37, 65, 79, 72, 69, 74, 8, 24, 30, 8, 22, 22, 22, 2, 57, 75, 68, 74, 82, + 74, 67, 65, 74, 8, 83, 75, 74, 8, 23, 8, 62, 69, 80, 8, 24, 8, 60, 69, 73, 73, + 65, 79, 74, 8, 72, 65, 65, 79, 8, 67, 65, 132, 81, 61, 74, 64, 65, 74, 20, 2, 44, + 74, 87, 84, 69, 132, 63, 68, 65, 74, 8, 84, 65, 79, 64, 65, 74, 8, 64, 69, 65, 132, + 65, 8, 24, 30, 8, 22, 22, 22, 8, 57, 75, 68, 74, 82, 74, 67, 65, 74, 8, 84, 75, + 68, 72, 8, 61, 82, 63, 68, 2, 74, 69, 63, 68, 81, 8, 62, 65, 87, 75, 67, 65, 74, + 8, 84, 75, 79, 64, 65, 74, 8, 132, 65, 69, 74, 18, 8, 82, 74, 64, 8, 84, 65, 74, + 74, 8, 73, 61, 74, 8, 74, 82, 74, 8, 61, 74, 3, 2, 74, 69, 73, 73, 81, 18, 8, + 64, 61, 102, 8, 69, 74, 8, 70, 65, 64, 65, 8, 64, 69, 65, 132, 65, 79, 8, 57, 75, + 68, 74, 82, 74, 67, 65, 74, 8, 65, 81, 84, 61, 8, 27, 8, 51, 65, 79, 3, 2, 132, + 75, 74, 65, 74, 8, 82, 74, 81, 65, 79, 67, 65, 62, 79, 61, 63, 68, 81, 8, 84, 65, + 79, 64, 65, 74, 8, 71, 109, 74, 74, 65, 74, 18, 8, 64, 61, 74, 74, 8, 71, 109, 74, + 74, 65, 74, 18, 2, 84, 65, 74, 74, 8, 61, 72, 132, 75, 8, 74, 82, 79, 8, 64, 69, + 65, 132, 65, 8, 24, 30, 8, 22, 22, 22, 8, 57, 75, 68, 74, 82, 74, 67, 65, 74, 8, + 62, 65, 87, 75, 67, 65, 74, 2, 84, 65, 79, 64, 65, 74, 18, 8, 14, 69, 74, 8, 37, + 65, 79, 72, 69, 74, 15, 8, 61, 72, 72, 65, 69, 74, 8, 65, 81, 84, 61, 8, 23, 26, + 22, 8, 22, 22, 22, 8, 48, 65, 74, 132, 63, 68, 65, 74, 2, 65, 69, 74, 65, 8, 57, + 75, 68, 74, 82, 74, 67, 80, 67, 65, 72, 65, 67, 65, 74, 68, 65, 69, 81, 8, 66, 69, + 74, 64, 65, 74, 20, 6, 2, 7, 44, 63, 68, 8, 67, 72, 61, 82, 62, 65, 18, 8, 84, + 69, 79, 8, 71, 109, 74, 74, 65, 74, 8, 61, 72, 132, 75, 8, 67, 61, 74, 87, 8, 67, + 65, 81, 79, 75, 132, 81, 8, 64, 65, 79, 8, 14, 60, 82, 3, 2, 71, 82, 74, 66, 81, + 8, 65, 74, 81, 67, 65, 67, 65, 74, 132, 65, 68, 65, 74, 15, 8, 82, 74, 64, 8, 62, + 79, 61, 82, 63, 68, 65, 74, 8, 74, 69, 63, 68, 81, 8, 87, 82, 8, 62, 65, 66, 110, + 79, 63, 68, 81, 65, 74, 18, 2, 64, 61, 102, 8, 65, 69, 74, 65, 8, 46, 72, 65, 69, + 74, 84, 75, 68, 74, 82, 74, 67, 80, 74, 75, 81, 8, 65, 69, 74, 81, 79, 65, 81, 65, + 74, 8, 84, 69, 79, 64, 20, 6, 2, 7, 49, 61, 63, 68, 8, 82, 74, 132, 65, 79, 65, + 79, 8, 36, 82, 66, 66, 61, 132, 132, 82, 74, 67, 8, 69, 132, 81, 8, 65, 80, 8, 74, + 69, 63, 68, 81, 8, 74, 109, 81, 69, 67, 18, 8, 64, 61, 102, 8, 65, 81, 84, 61, 80, + 8, 37, 65, 3, 2, 132, 75, 74, 64, 65, 79, 65, 80, 8, 69, 74, 8, 64, 69, 65, 132, + 65, 79, 8, 37, 65, 87, 69, 65, 68, 82, 74, 67, 8, 67, 65, 132, 63, 68, 69, 65, 68, + 81, 8, 82, 74, 64, 8, 74, 65, 82, 65, 2, 48, 61, 102, 74, 61, 68, 73, 65, 74, 8, + 61, 82, 66, 8, 64, 69, 65, 132, 65, 73, 8, 42, 65, 62, 69, 65, 81, 65, 8, 67, 65, + 81, 79, 75, 66, 66, 65, 74, 8, 84, 65, 79, 64, 65, 74, 20, 6, 2, 14, 53, 81, 61, + 64, 81, 132, 86, 74, 64, 69, 71, 82, 80, 8, 53, 65, 73, 62, 79, 69, 81, 87, 71, 69, + 15, 32, 8, 7, 48, 65, 69, 74, 65, 8, 43, 65, 79, 79, 65, 74, 18, 2, 65, 80, 8, + 84, 61, 79, 8, 64, 61, 68, 65, 79, 8, 64, 69, 65, 8, 65, 79, 132, 81, 65, 8, 51, + 66, 72, 69, 63, 68, 81, 18, 2, 82, 73, 8, 110, 62, 65, 79, 68, 61, 82, 78, 81, 8, + 65, 69, 74, 65, 74, 8, 81, 79, 61, 67, 66, 103, 68, 69, 67, 65, 74, 8, 37, 75, 64, + 65, 74, 8, 66, 110, 79, 8, 84, 65, 69, 81, 65, 79, 65, 2, 40, 79, 84, 103, 67, 82, + 74, 67, 65, 74, 8, 87, 82, 8, 66, 69, 74, 64, 65, 74, 18, 8, 64, 69, 65, 8, 56, + 65, 79, 68, 103, 72, 81, 74, 69, 132, 132, 65, 18, 8, 84, 69, 65, 8, 132, 69, 65, 8, + 62, 65, 69, 2, 82, 55, 74, 80, 18, 8, 14, 64, 20, 8, 68, 20, 8, 74, 69, 63, 68, + 81, 8, 74, 82, 79, 8, 69, 74, 8, 38, 68, 61, 79, 72, 75, 81, 81, 65, 74, 62, 82, + 79, 67, 18, 8, 132, 75, 74, 64, 65, 79, 74, 8, 69, 74, 2, 42, 79, 75, 102, 19, 37, + 65, 79, 72, 69, 74, 18, 8, 67, 65, 72, 61, 67, 65, 79, 81, 8, 132, 69, 74, 64, 15, + 18, 8, 66, 65, 132, 81, 87, 82, 132, 81, 65, 72, 72, 65, 74, 20, 8, 60, 82, 8, 64, + 69, 65, 132, 65, 73, 2, 60, 84, 65, 63, 71, 65, 8, 69, 132, 81, 18, 8, 84, 69, 65, + 8, 69, 74, 8, 64, 65, 79, 8, 48, 69, 81, 81, 65, 69, 72, 82, 74, 67, 18, 8, 64, + 69, 65, 8, 44, 68, 74, 65, 74, 8, 67, 65, 3, 2, 64, 79, 82, 63, 71, 81, 8, 87, + 82, 67, 65, 67, 61, 74, 67, 65, 74, 8, 69, 132, 81, 18, 8, 64, 61, 79, 67, 65, 72, + 65, 67, 81, 8, 69, 132, 81, 18, 8, 65, 69, 74, 65, 8, 60, 103, 68, 72, 82, 74, 67, + 2, 64, 65, 79, 8, 72, 65, 65, 79, 8, 132, 81, 65, 68, 65, 74, 64, 65, 74, 8, 57, + 75, 68, 74, 82, 74, 67, 65, 74, 8, 69, 74, 8, 42, 79, 75, 102, 19, 37, 65, 79, 72, + 69, 74, 8, 3, 2, 14, 64, 69, 65, 8, 65, 79, 132, 81, 65, 8, 64, 65, 79, 61, 79, + 81, 69, 67, 65, 8, 60, 103, 68, 72, 82, 74, 67, 8, 74, 61, 63, 68, 8, 65, 69, 74, + 65, 73, 8, 65, 69, 74, 68, 65, 69, 81, 72, 69, 63, 68, 65, 74, 2, 53, 63, 68, 65, + 73, 61, 15, 8, 3, 8, 83, 75, 79, 67, 65, 74, 75, 73, 73, 65, 74, 8, 84, 75, 79, + 64, 65, 74, 20, 6, 2, 7, 44, 63, 68, 8, 73, 109, 63, 68, 81, 65, 8, 69, 74, 8, + 51, 61, 79, 65, 74, 81, 68, 65, 132, 65, 8, 87, 82, 8, 64, 65, 79, 8, 36, 82, 66, + 66, 61, 132, 132, 82, 74, 67, 18, 8, 64, 65, 79, 8, 48, 61, 67, 69, 132, 81, 79, 61, + 81, 8, 68, 103, 81, 81, 65, 2, 69, 74, 8, 64, 69, 65, 132, 65, 79, 8, 53, 61, 63, + 68, 65, 8, 74, 69, 63, 68, 81, 80, 8, 67, 65, 81, 61, 74, 18, 8, 62, 65, 73, 65, + 79, 71, 65, 74, 18, 8, 64, 61, 102, 8, 64, 69, 65, 132, 65, 2, 132, 81, 61, 81, 69, + 132, 81, 69, 132, 63, 68, 65, 8, 36, 82, 66, 74, 61, 68, 73, 65, 8, 14, 67, 65, 67, + 65, 74, 8, 74, 69, 63, 68, 81, 8, 82, 74, 65, 79, 68, 65, 62, 72, 69, 63, 68, 65, + 8, 57, 69, 64, 65, 79, 3, 2, 132, 81, 103, 74, 64, 65, 8, 69, 74, 8, 42, 79, 75, + 102, 19, 37, 65, 79, 72, 69, 74, 15, 8, 61, 82, 66, 8, 44, 74, 69, 81, 69, 61, 81, + 69, 83, 65, 74, 8, 64, 65, 80, 8, 37, 65, 79, 72, 69, 74, 65, 79, 2, 56, 65, 79, + 65, 69, 74, 80, 8, 66, 110, 79, 8, 46, 72, 65, 69, 74, 84, 75, 68, 74, 82, 74, 67, + 80, 84, 65, 132, 65, 74, 8, 87, 82, 132, 81, 61, 74, 64, 65, 8, 67, 65, 71, 75, 73, + 3, 2, 73, 65, 74, 8, 82, 74, 64, 8, 69, 74, 8, 64, 69, 65, 132, 65, 73, 8, 56, + 65, 79, 65, 69, 74, 8, 64, 65, 79, 8, 68, 69, 65, 73, 82, 66, 8, 62, 65, 87, 110, + 67, 4, 3, 2, 72, 69, 63, 68, 65, 8, 36, 74, 81, 79, 61, 67, 8, 83, 75, 74, 8, + 64, 65, 73, 8, 56, 65, 79, 81, 79, 65, 81, 65, 79, 8, 64, 65, 79, 8, 53, 81, 61, + 64, 81, 8, 38, 68, 61, 79, 72, 75, 81, 3, 2, 81, 65, 74, 62, 82, 79, 67, 8, 61, + 82, 80, 67, 65, 67, 61, 74, 67, 65, 74, 8, 69, 132, 81, 20, 6, 2, 14, 7, 68, 65, + 66, 81, 69, 67, 65, 79, 8, 36, 78, 78, 72, 61, 82, 80, 6, 15, 20, 8, 91, 53, 8, + 23, 26, 18, 8, 23, 27, 8, 82, 74, 64, 8, 23, 29, 8, 61, 8, 62, 69, 80, 8, 66, + 20, 8, 91, 8, 24, 24, 20, 8, 14, 53, 8, 26, 8, 62, 69, 80, 8, 28, 15, 2, 39, + 61, 80, 8, 132, 63, 68, 65, 69, 74, 81, 8, 73, 69, 79, 8, 71, 65, 69, 74, 65, 8, + 14, 7, 7, 67, 61, 74, 87, 8, 82, 74, 84, 69, 63, 68, 81, 69, 67, 65, 8, 48, 61, + 102, 79, 65, 67, 65, 72, 6, 15, 8, 69, 74, 8, 64, 69, 65, 132, 65, 79, 8, 53, 61, + 63, 68, 65, 8, 67, 65, 84, 65, 132, 65, 74, 2, 87, 82, 8, 132, 65, 69, 74, 8, 14, + 64, 69, 65, 8, 41, 65, 132, 81, 132, 81, 65, 72, 72, 82, 74, 67, 8, 64, 65, 79, 8, + 40, 79, 67, 65, 62, 74, 69, 132, 132, 65, 8, 64, 69, 65, 132, 65, 79, 8, 53, 81, 61, + 3, 2, 81, 69, 132, 81, 69, 71, 8, 69, 132, 81, 8, 75, 72, 72, 65, 79, 64, 69, 74, + 67, 80, 8, 62, 65, 69, 8, 64, 65, 79, 8, 67, 65, 67, 65, 74, 84, 103, 79, 81, 69, + 67, 65, 74, 8, 42, 65, 132, 63, 68, 103, 66, 81, 80, 3, 2, 72, 61, 67, 65, 8, 64, + 65, 79, 8, 37, 65, 68, 109, 79, 64, 65, 74, 8, 69, 74, 8, 42, 79, 75, 102, 19, 37, + 65, 79, 72, 69, 74, 8, 73, 69, 81, 8, 52, 110, 63, 71, 132, 69, 63, 68, 81, 8, 61, + 82, 66, 2, 64, 65, 74, 8, 55, 73, 132, 81, 61, 74, 64, 18, 8, 64, 61, 102, 8, 65, + 69, 74, 65, 8, 36, 74, 87, 61, 68, 72, 8, 83, 75, 74, 8, 42, 79, 75, 102, 19, 37, + 65, 79, 72, 69, 74, 65, 79, 2, 64, 65, 74, 8, 55, 73, 132, 81, 61, 74, 64, 18, 8, + 64, 61, 102, 8, 65, 69, 74, 65, 8, 36, 74, 87, 61, 68, 72, 8, 83, 75, 74, 8, 42, + 79, 75, 102, 19, 37, 65, 79, 72, 69, 74, 65, 79, 2, 42, 65, 73, 65, 69, 74, 64, 65, + 74, 8, 71, 65, 69, 74, 65, 8, 132, 81, 61, 81, 69, 132, 81, 69, 132, 63, 68, 65, 74, + 8, 36, 65, 73, 81, 65, 79, 8, 75, 64, 65, 79, 8, 64, 65, 79, 67, 72, 65, 69, 63, + 68, 65, 74, 2, 42, 65, 73, 65, 69, 74, 64, 65, 74, 8, 71, 65, 69, 74, 65, 8, 132, + 81, 61, 81, 69, 132, 81, 69, 132, 63, 68, 65, 74, 8, 36, 65, 73, 81, 65, 79, 8, 75, + 64, 65, 79, 8, 64, 65, 79, 67, 72, 65, 69, 63, 68, 65, 74, 2, 40, 69, 74, 79, 69, + 63, 68, 81, 82, 74, 67, 65, 74, 8, 68, 61, 62, 65, 74, 18, 8, 74, 69, 63, 68, 81, + 8, 67, 61, 74, 87, 8, 72, 65, 69, 63, 68, 81, 8, 67, 65, 84, 65, 132, 65, 74, 15, + 18, 2, 40, 69, 74, 79, 69, 63, 68, 81, 82, 74, 67, 65, 74, 8, 68, 61, 62, 65, 74, + 18, 8, 74, 69, 63, 68, 81, 8, 67, 61, 74, 87, 8, 72, 65, 69, 63, 68, 81, 8, 67, + 65, 84, 65, 132, 65, 74, 15, 20, 2, 7, 54, 61, 81, 132, 103, 63, 68, 72, 69, 63, 68, + 8, 69, 132, 81, 8, 64, 65, 73, 8, 48, 61, 67, 69, 132, 81, 79, 61, 81, 8, 64, 61, + 80, 8, 61, 73, 81, 72, 69, 63, 68, 65, 8, 40, 79, 67, 65, 62, 74, 69, 80, 8, 64, + 65, 79, 2, 7, 54, 61, 81, 132, 103, 63, 68, 72, 69, 63, 68, 8, 69, 132, 81, 8, 64, + 65, 73, 8, 48, 61, 67, 69, 132, 81, 79, 61, 81, 8, 64, 61, 80, 8, 61, 73, 81, 72, + 69, 63, 68, 65, 8, 40, 79, 67, 65, 62, 74, 69, 80, 8, 64, 65, 79, 2, 42, 79, 75, + 102, 8, 37, 65, 79, 72, 69, 74, 65, 79, 8, 53, 81, 61, 81, 69, 132, 81, 69, 71, 8, + 61, 82, 63, 68, 8, 65, 79, 132, 81, 8, 69, 74, 8, 64, 65, 74, 8, 72, 65, 81, 87, + 81, 65, 74, 8, 54, 61, 67, 65, 74, 2, 42, 79, 75, 102, 8, 37, 65, 79, 72, 69, 74, + 65, 79, 8, 53, 81, 61, 81, 69, 132, 81, 69, 71, 8, 61, 82, 63, 68, 8, 65, 79, 132, + 81, 8, 69, 74, 8, 64, 65, 74, 8, 72, 65, 81, 87, 81, 65, 74, 8, 54, 61, 67, 65, + 74, 2, 64, 65, 80, 8, 48, 75, 74, 61, 81, 80, 8, 39, 65, 87, 65, 73, 62, 65, 79, + 8, 87, 82, 67, 65, 67, 61, 74, 67, 65, 74, 20, 6, 2, 64, 65, 80, 8, 48, 75, 74, + 61, 81, 80, 8, 39, 65, 87, 65, 73, 62, 65, 79, 8, 87, 82, 67, 65, 67, 61, 74, 67, + 65, 74, 20, 6, 2, 68, 65, 82, 81, 65, 8, 64, 69, 65, 8, 53, 81, 61, 64, 81, 83, + 65, 79, 84, 61, 72, 81, 82, 74, 67, 8, 83, 75, 74, 18, 8, 38, 68, 61, 79, 72, 75, + 81, 81, 65, 74, 62, 82, 79, 67, 2, 68, 65, 82, 81, 65, 8, 64, 69, 65, 8, 53, 81, + 61, 64, 81, 83, 65, 79, 84, 61, 72, 81, 82, 74, 67, 8, 83, 75, 74, 18, 8, 38, 68, + 61, 79, 72, 75, 81, 81, 65, 74, 62, 82, 79, 67, 2, 69, 74, 8, 53, 81, 61, 74, 64, + 8, 132, 65, 81, 87, 65, 74, 18, 8, 64, 69, 65, 132, 65, 79, 8, 61, 71, 82, 81, 65, + 74, 8, 57, 75, 68, 74, 82, 74, 67, 80, 74, 75, 81, 8, 65, 74, 81, 4, 2, 69, 74, + 8, 53, 81, 61, 74, 64, 8, 132, 65, 81, 87, 65, 74, 18, 8, 64, 69, 65, 132, 65, 79, + 8, 61, 71, 82, 81, 65, 74, 8, 57, 75, 68, 74, 82, 74, 67, 80, 74, 75, 81, 8, 65, + 74, 81, 3, 2, 67, 65, 67, 65, 74, 87, 82, 81, 79, 65, 81, 65, 74, 20, 8, 39, 61, + 80, 8, 69, 132, 81, 8, 64, 69, 65, 8, 41, 79, 61, 67, 65, 18, 8, 82, 73, 8, 64, + 69, 65, 8, 65, 80, 8, 132, 69, 63, 68, 8, 14, 91, 8, 64, 15, 2, 67, 65, 67, 65, + 74, 87, 82, 81, 79, 65, 81, 65, 74, 20, 8, 39, 61, 80, 8, 69, 132, 81, 8, 64, 69, + 65, 8, 41, 79, 61, 67, 65, 18, 8, 82, 73, 8, 64, 69, 65, 8, 65, 80, 8, 132, 69, + 63, 68, 8, 14, 91, 53, 8, 30, 15, 2, 79, 65, 68, 81, 20, 8, 40, 69, 74, 87, 69, + 67, 8, 82, 74, 64, 8, 61, 72, 72, 65, 69, 74, 8, 83, 75, 74, 8, 64, 69, 65, 132, + 65, 73, 8, 53, 81, 61, 74, 64, 78, 82, 74, 71, 81, 2, 64, 79, 65, 68, 81, 20, 8, + 40, 69, 74, 87, 69, 67, 8, 82, 74, 64, 8, 61, 72, 72, 65, 69, 74, 8, 83, 75, 74, + 8, 64, 69, 65, 132, 65, 73, 8, 53, 81, 61, 74, 64, 78, 82, 74, 71, 81, 2, 61, 82, + 80, 8, 68, 61, 62, 65, 74, 8, 84, 69, 79, 8, 69, 73, 8, 83, 75, 79, 69, 67, 65, + 74, 8, 45, 61, 68, 79, 65, 8, 64, 65, 74, 8, 36, 74, 81, 79, 61, 67, 8, 67, 65, + 3, 2, 61, 82, 80, 8, 68, 61, 62, 65, 74, 8, 84, 69, 79, 8, 69, 73, 8, 83, 75, + 79, 69, 67, 65, 74, 8, 45, 61, 68, 79, 65, 8, 64, 65, 74, 8, 36, 74, 81, 79, 61, + 67, 8, 67, 65, 3, 2, 132, 81, 65, 72, 72, 81, 20, 8, 57, 69, 79, 8, 68, 61, 62, + 65, 74, 8, 61, 82, 63, 68, 8, 74, 69, 63, 68, 81, 8, 74, 109, 81, 69, 61, 18, 8, + 61, 82, 66, 8, 64, 69, 65, 8, 110, 62, 79, 69, 67, 65, 74, 2, 132, 81, 65, 72, 72, + 81, 20, 8, 57, 69, 79, 8, 68, 61, 62, 65, 74, 8, 61, 82, 63, 68, 8, 74, 69, 63, + 68, 81, 8, 74, 109, 81, 69, 61, 18, 8, 61, 82, 66, 8, 64, 69, 65, 8, 110, 62, 79, + 69, 67, 65, 74, 2, 42, 79, 75, 102, 8, 56, 65, 79, 72, 69, 74, 65, 79, 8, 42, 65, + 73, 65, 69, 74, 64, 65, 74, 8, 52, 110, 63, 71, 132, 68, 81, 8, 87, 82, 8, 69, 65, + 2, 42, 79, 75, 102, 8, 56, 65, 79, 72, 69, 74, 65, 79, 8, 42, 65, 73, 65, 69, 74, + 64, 65, 74, 8, 52, 110, 63, 71, 132, 68, 81, 8, 87, 82, 8, 69, 65, 2, 132, 75, 74, + 64, 65, 79, 74, 8, 84, 69, 79, 8, 68, 61, 62, 65, 74, 8, 82, 74, 132, 65, 79, 65, + 8, 65, 69, 67, 65, 74, 65, 74, 8, 56, 65, 79, 68, 103, 72, 81, 74, 69, 132, 132, 65, + 8, 87, 82, 2, 132, 75, 74, 64, 65, 79, 74, 8, 84, 69, 79, 8, 68, 61, 62, 65, 74, + 8, 82, 74, 132, 65, 79, 65, 8, 65, 69, 67, 65, 74, 65, 74, 8, 56, 65, 79, 68, 103, + 72, 81, 74, 69, 132, 132, 65, 8, 87, 82, 2, 78, 79, 110, 66, 65, 74, 18, 8, 82, 74, + 64, 8, 84, 65, 74, 74, 8, 84, 69, 79, 8, 74, 61, 63, 68, 8, 67, 65, 84, 69, 132, + 132, 65, 74, 68, 61, 66, 81, 65, 79, 8, 51, 79, 110, 66, 82, 74, 67, 2, 78, 79, 110, + 66, 65, 74, 18, 8, 82, 74, 64, 8, 84, 65, 74, 74, 8, 84, 69, 79, 8, 74, 61, 63, + 68, 8, 67, 65, 84, 69, 132, 132, 65, 74, 68, 61, 66, 81, 65, 79, 8, 51, 79, 110, 66, + 82, 74, 67, 2, 87, 82, 8, 64, 65, 79, 8, 36, 74, 132, 63, 68, 61, 82, 82, 74, 67, + 8, 71, 75, 73, 73, 65, 74, 18, 8, 64, 61, 102, 8, 65, 69, 74, 65, 8, 57, 61, 68, + 74, 82, 74, 67, 80, 4, 2, 87, 82, 8, 64, 65, 79, 8, 36, 74, 132, 63, 68, 61, 82, + 82, 74, 67, 8, 71, 75, 73, 73, 65, 74, 18, 8, 64, 61, 102, 8, 65, 69, 74, 65, 8, + 57, 75, 68, 74, 82, 74, 67, 80, 3, 2, 74, 75, 81, 8, 87, 82, 8, 65, 79, 84, 61, + 79, 81, 65, 74, 8, 69, 132, 81, 18, 8, 64, 61, 74, 74, 8, 69, 132, 81, 8, 65, 80, + 8, 82, 74, 132, 65, 79, 65, 8, 83, 65, 79, 64, 61, 73, 73, 81, 65, 2, 74, 75, 81, + 8, 87, 82, 8, 65, 79, 84, 61, 79, 81, 65, 74, 8, 69, 132, 81, 18, 8, 64, 61, 74, + 74, 8, 69, 132, 81, 8, 65, 80, 8, 82, 74, 132, 65, 79, 65, 8, 83, 65, 79, 64, 61, + 73, 73, 81, 65, 2, 51, 66, 72, 69, 63, 68, 81, 8, 82, 74, 64, 8, 53, 63, 68, 82, + 72, 64, 69, 67, 71, 65, 69, 81, 18, 8, 62, 65, 69, 8, 60, 65, 69, 81, 65, 74, 8, + 65, 69, 74, 87, 82, 67, 79, 65, 69, 66, 65, 74, 20, 2, 51, 66, 72, 69, 63, 68, 81, + 8, 82, 74, 64, 8, 53, 63, 68, 82, 72, 64, 69, 67, 71, 65, 69, 81, 18, 8, 62, 65, + 69, 8, 60, 65, 69, 81, 65, 74, 8, 65, 69, 74, 87, 82, 67, 79, 65, 69, 66, 65, 74, + 20, 2, 40, 80, 8, 84, 69, 79, 64, 8, 70, 61, 8, 64, 65, 74, 8, 43, 65, 79, 79, + 65, 74, 18, 8, 64, 69, 65, 8, 72, 103, 74, 67, 65, 79, 8, 69, 74, 8, 64, 65, 79, + 8, 53, 81, 61, 64, 81, 3, 2, 40, 80, 8, 84, 69, 79, 64, 8, 70, 61, 8, 64, 65, + 74, 8, 43, 65, 79, 79, 65, 74, 18, 8, 64, 69, 65, 8, 72, 103, 74, 67, 65, 79, 8, + 69, 74, 8, 64, 65, 79, 8, 53, 81, 61, 64, 81, 3, 2, 83, 65, 79, 75, 79, 64, 74, + 65, 81, 65, 74, 83, 65, 79, 132, 61, 73, 73, 72, 82, 74, 67, 8, 132, 69, 81, 87, 65, + 74, 18, 8, 62, 65, 71, 61, 74, 74, 81, 8, 132, 65, 69, 74, 18, 8, 64, 61, 102, 8, + 84, 69, 79, 2, 83, 65, 79, 75, 79, 64, 74, 65, 81, 65, 74, 83, 65, 79, 132, 61, 73, + 73, 72, 82, 74, 67, 8, 132, 69, 81, 87, 65, 74, 18, 8, 62, 65, 71, 61, 74, 74, 81, + 8, 132, 65, 69, 74, 18, 8, 64, 61, 102, 8, 84, 69, 79, 2, 82, 74, 80, 8, 132, 63, + 68, 75, 74, 8, 65, 69, 74, 73, 61, 72, 18, 8, 83, 75, 79, 8, 73, 65, 68, 79, 8, + 61, 72, 80, 8, 65, 69, 74, 65, 73, 8, 45, 61, 68, 79, 87, 65, 68, 74, 81, 18, 2, + 82, 74, 80, 8, 132, 63, 68, 75, 74, 8, 65, 69, 74, 73, 61, 72, 18, 8, 83, 75, 79, + 8, 73, 65, 68, 79, 8, 61, 72, 80, 8, 65, 69, 74, 65, 73, 8, 45, 61, 68, 79, 87, + 65, 68, 74, 81, 18, 2, 73, 69, 81, 8, 64, 65, 79, 8, 41, 79, 61, 67, 65, 8, 62, + 65, 132, 63, 68, 103, 66, 81, 69, 67, 81, 8, 68, 61, 62, 65, 74, 18, 8, 82, 74, 64, + 8, 69, 63, 68, 8, 73, 82, 102, 8, 132, 61, 67, 65, 74, 18, 2, 73, 69, 81, 8, 64, + 65, 79, 8, 41, 79, 61, 67, 65, 8, 62, 65, 132, 63, 68, 103, 66, 81, 69, 67, 81, 8, + 68, 61, 62, 65, 74, 18, 8, 82, 74, 64, 8, 69, 63, 68, 8, 73, 82, 102, 8, 132, 61, + 67, 65, 74, 18, 2, 64, 61, 102, 8, 73, 69, 63, 68, 8, 64, 69, 65, 8, 36, 79, 81, + 8, 82, 74, 64, 8, 57, 65, 69, 132, 65, 18, 8, 84, 69, 65, 8, 64, 69, 65, 80, 73, + 61, 72, 8, 64, 69, 65, 8, 53, 61, 63, 68, 65, 2, 64, 61, 102, 8, 73, 69, 63, 68, + 8, 64, 69, 65, 8, 36, 79, 81, 8, 82, 74, 64, 8, 57, 65, 69, 132, 65, 18, 8, 84, + 69, 65, 8, 64, 69, 65, 80, 73, 61, 72, 8, 64, 69, 65, 8, 53, 61, 63, 68, 65, 2, + 83, 65, 68, 61, 74, 64, 65, 72, 81, 8, 84, 69, 79, 64, 18, 8, 72, 65, 62, 68, 61, + 66, 81, 8, 61, 74, 8, 64, 69, 65, 8, 64, 61, 73, 61, 72, 69, 67, 65, 8, 37, 65, + 68, 61, 74, 64, 72, 82, 74, 67, 2, 62, 65, 68, 61, 74, 64, 65, 72, 81, 8, 84, 69, + 79, 64, 18, 8, 72, 65, 62, 68, 61, 66, 81, 8, 61, 74, 8, 64, 69, 65, 8, 64, 61, + 73, 61, 72, 69, 67, 65, 8, 37, 65, 68, 61, 74, 64, 72, 82, 74, 67, 2, 65, 79, 69, + 74, 74, 65, 79, 81, 20, 8, 14, 23, 30, 30, 31, 8, 62, 65, 132, 81, 61, 74, 64, 8, + 68, 69, 65, 79, 8, 69, 74, 8, 38, 68, 61, 79, 72, 75, 81, 81, 65, 74, 62, 82, 79, + 67, 2, 65, 79, 69, 74, 74, 65, 79, 81, 20, 8, 14, 23, 30, 30, 31, 8, 62, 65, 132, + 81, 61, 74, 64, 8, 68, 69, 65, 79, 8, 69, 74, 8, 38, 68, 61, 79, 72, 75, 81, 81, + 65, 74, 62, 82, 79, 67, 2, 65, 69, 74, 65, 8, 67, 61, 74, 87, 8, 71, 75, 72, 75, + 132, 132, 61, 72, 65, 8, 57, 75, 68, 74, 82, 74, 67, 80, 74, 75, 81, 20, 15, 8, 40, + 80, 8, 84, 61, 79, 8, 132, 75, 84, 65, 69, 81, 2, 65, 69, 74, 65, 8, 67, 61, 74, + 87, 8, 71, 75, 72, 75, 132, 132, 61, 72, 65, 8, 57, 75, 68, 74, 82, 74, 67, 80, 74, + 75, 81, 20, 15, 8, 40, 80, 8, 84, 61, 79, 8, 132, 75, 84, 65, 69, 81, 2, 65, 71, + 75, 73, 73, 65, 74, 18, 8, 64, 61, 102, 8, 47, 65, 82, 81, 65, 18, 8, 64, 69, 65, + 8, 65, 69, 74, 65, 8, 67, 79, 75, 102, 65, 8, 36, 74, 87, 61, 68, 72, 8, 83, 75, + 74, 2, 67, 65, 71, 75, 73, 73, 65, 74, 18, 8, 64, 61, 102, 8, 47, 65, 82, 81, 65, + 18, 8, 64, 69, 65, 8, 65, 69, 74, 65, 8, 67, 79, 75, 102, 65, 8, 36, 74, 87, 61, + 68, 72, 8, 83, 75, 74, 2, 46, 69, 74, 64, 65, 79, 74, 8, 68, 61, 81, 81, 65, 74, + 18, 8, 110, 62, 65, 79, 68, 61, 82, 78, 81, 8, 71, 65, 69, 74, 65, 8, 57, 75, 68, + 74, 82, 74, 67, 65, 74, 8, 73, 65, 68, 79, 2, 46, 69, 74, 64, 65, 79, 74, 8, 68, + 61, 81, 81, 65, 74, 18, 8, 110, 62, 65, 79, 68, 61, 82, 78, 81, 8, 71, 65, 69, 74, + 65, 8, 57, 75, 68, 74, 82, 74, 67, 65, 74, 8, 73, 65, 68, 79, 2, 62, 65, 71, 61, + 73, 65, 74, 20, 8, 14, 23, 31, 22, 23, 15, 8, 57, 69, 79, 8, 84, 61, 79, 65, 74, + 8, 67, 65, 87, 84, 82, 74, 67, 65, 74, 18, 8, 37, 61, 79, 61, 63, 71, 65, 74, 8, + 61, 82, 66, 87, 82, 3, 2, 62, 65, 71, 61, 73, 65, 74, 20, 8, 14, 23, 31, 22, 23, + 15, 8, 57, 69, 79, 8, 84, 61, 79, 65, 74, 8, 67, 65, 87, 84, 82, 74, 67, 65, 74, + 18, 8, 37, 61, 79, 61, 63, 71, 65, 74, 8, 61, 82, 66, 87, 82, 3, 2, 132, 81, 65, + 72, 72, 65, 74, 18, 8, 69, 74, 8, 64, 65, 74, 65, 74, 8, 84, 69, 79, 8, 47, 65, + 82, 81, 65, 8, 82, 74, 81, 65, 79, 62, 79, 61, 63, 68, 81, 65, 74, 18, 8, 64, 69, + 65, 8, 23, 22, 2, 132, 81, 65, 72, 72, 65, 74, 18, 8, 69, 74, 8, 64, 65, 74, 65, + 74, 8, 84, 69, 79, 8, 47, 65, 82, 81, 65, 8, 82, 74, 81, 65, 79, 62, 79, 61, 63, + 68, 81, 65, 74, 18, 8, 64, 69, 65, 8, 23, 22, 2, 45, 61, 68, 79, 65, 8, 82, 74, + 64, 8, 72, 103, 74, 67, 65, 79, 8, 69, 74, 8, 65, 69, 74, 65, 73, 8, 43, 61, 82, + 132, 65, 8, 67, 65, 84, 75, 68, 74, 81, 8, 82, 74, 64, 2, 45, 61, 68, 79, 65, 8, + 82, 74, 64, 8, 72, 103, 74, 67, 65, 79, 8, 69, 74, 8, 65, 69, 74, 65, 73, 8, 43, + 61, 82, 132, 65, 8, 67, 65, 84, 75, 68, 74, 81, 8, 82, 74, 64, 2, 78, 110, 74, 71, + 81, 72, 69, 63, 68, 8, 69, 68, 79, 65, 8, 48, 69, 65, 81, 65, 8, 67, 65, 87, 61, + 68, 72, 81, 8, 68, 61, 81, 81, 65, 74, 8, 14, 61, 62, 65, 79, 8, 84, 65, 67, 65, + 74, 8, 69, 68, 79, 65, 79, 2, 78, 110, 74, 71, 81, 72, 69, 63, 68, 8, 69, 68, 79, + 65, 8, 48, 69, 65, 81, 65, 8, 67, 65, 87, 61, 68, 72, 81, 8, 68, 61, 81, 81, 65, + 74, 8, 14, 61, 62, 65, 79, 8, 84, 65, 67, 65, 74, 8, 69, 68, 79, 65, 79, 2, 68, + 75, 68, 65, 74, 8, 46, 69, 74, 64, 65, 79, 87, 61, 68, 72, 8, 67, 65, 71, 110, 74, + 64, 69, 67, 81, 8, 82, 74, 64, 8, 61, 82, 66, 8, 64, 69, 65, 8, 53, 81, 79, 82, + 102, 65, 8, 67, 65, 3, 2, 68, 75, 68, 65, 74, 8, 46, 69, 74, 64, 65, 79, 87, 61, + 68, 72, 8, 67, 65, 71, 110, 74, 64, 69, 67, 81, 8, 82, 74, 64, 8, 61, 82, 66, 8, + 64, 69, 65, 8, 53, 81, 79, 61, 102, 65, 8, 67, 65, 3, 2, 84, 75, 79, 66, 65, 74, + 8, 84, 61, 79, 65, 74, 15, 20, 8, 40, 80, 8, 71, 61, 73, 8, 64, 61, 74, 74, 8, + 61, 82, 63, 68, 8, 65, 69, 74, 8, 36, 74, 81, 79, 61, 67, 18, 8, 64, 65, 79, 2, + 84, 75, 79, 66, 65, 74, 8, 84, 61, 79, 65, 74, 15, 20, 8, 40, 80, 8, 71, 61, 73, + 8, 64, 61, 74, 74, 8, 61, 82, 63, 68, 8, 65, 69, 74, 8, 36, 74, 81, 79, 61, 67, + 18, 8, 64, 65, 79, 2, 25, 18, 23, 24, 8, 11, 8, 69, 74, 8, 64, 65, 79, 8, 53, + 81, 61, 64, 81, 83, 65, 79, 75, 79, 64, 74, 65, 81, 65, 74, 83, 65, 79, 132, 61, 73, + 73, 72, 82, 74, 67, 8, 62, 65, 132, 63, 68, 103, 66, 81, 69, 67, 81, 2, 25, 18, 23, + 24, 8, 11, 8, 69, 74, 8, 64, 65, 79, 8, 53, 81, 61, 64, 81, 83, 65, 79, 75, 79, + 64, 74, 65, 81, 65, 74, 83, 65, 79, 132, 61, 73, 73, 72, 82, 74, 67, 8, 62, 65, 132, + 63, 68, 103, 66, 81, 69, 67, 81, 2, 65, 69, 74, 65, 8, 39, 65, 78, 82, 81, 61, 81, + 69, 75, 74, 8, 64, 61, 73, 69, 81, 8, 62, 65, 66, 61, 102, 81, 20, 8, 91, 8, 23, + 24, 20, 8, 39, 69, 65, 8, 53, 61, 63, 68, 65, 8, 68, 61, 81, 2, 65, 69, 74, 65, + 8, 39, 65, 78, 82, 81, 61, 81, 69, 75, 74, 8, 64, 61, 73, 69, 81, 8, 62, 65, 66, + 61, 102, 81, 20, 8, 91, 8, 23, 24, 20, 8, 39, 69, 65, 8, 53, 61, 63, 68, 65, 8, + 68, 61, 81, 2, 63, 68, 8, 68, 69, 74, 67, 65, 87, 75, 67, 65, 74, 8, 82, 74, 64, + 8, 69, 132, 81, 8, 132, 63, 68, 72, 69, 65, 102, 72, 69, 63, 68, 8, 83, 109, 72, 72, + 69, 67, 8, 61, 82, 80, 8, 64, 65, 73, 2, 132, 69, 63, 68, 8, 68, 69, 74, 67, 65, + 87, 75, 67, 65, 74, 8, 82, 74, 64, 8, 69, 132, 81, 8, 132, 63, 68, 72, 69, 65, 102, + 72, 69, 63, 68, 8, 83, 109, 72, 72, 69, 67, 8, 61, 82, 80, 8, 64, 65, 73, 2, 40, + 80, 8, 84, 82, 79, 64, 65, 8, 65, 69, 74, 8, 36, 82, 80, 132, 63, 68, 82, 102, 8, + 65, 69, 74, 67, 65, 132, 65, 81, 87, 81, 8, 82, 74, 64, 8, 91, 8, 24, 23, 28, 8, + 36, 62, 80, 61, 81, 87, 8, 25, 2, 40, 80, 8, 84, 82, 79, 64, 65, 8, 65, 69, 74, + 8, 36, 82, 80, 132, 63, 68, 82, 102, 8, 65, 69, 74, 67, 65, 132, 65, 81, 87, 81, 8, + 82, 74, 64, 8, 91, 53, 8, 24, 23, 28, 8, 36, 62, 80, 61, 81, 87, 8, 25, 2, 132, + 69, 63, 68, 8, 46, 72, 65, 69, 74, 84, 75, 68, 74, 82, 74, 67, 65, 74, 8, 132, 75, + 8, 67, 65, 84, 61, 72, 81, 69, 67, 8, 69, 132, 81, 8, 84, 69, 65, 8, 132, 65, 72, + 81, 65, 74, 8, 83, 75, 79, 68, 65, 79, 20, 2, 132, 69, 63, 68, 8, 46, 72, 65, 69, + 74, 84, 75, 68, 74, 82, 74, 67, 65, 74, 8, 132, 75, 8, 67, 65, 84, 61, 72, 81, 69, + 67, 8, 69, 132, 81, 8, 84, 69, 65, 8, 132, 65, 72, 81, 65, 74, 8, 83, 75, 79, 68, + 65, 79, 20, 2, 49, 82, 74, 8, 69, 132, 81, 8, 64, 69, 65, 8, 41, 79, 61, 67, 65, + 8, 64, 69, 65, 18, 8, 7, 75, 62, 8, 84, 69, 79, 8, 74, 61, 63, 68, 8, 64, 65, + 73, 8, 46, 79, 69, 65, 67, 65, 2, 49, 82, 74, 8, 69, 132, 81, 8, 64, 69, 65, 8, + 41, 79, 61, 67, 65, 8, 64, 69, 65, 18, 8, 7, 75, 62, 8, 84, 69, 79, 8, 74, 61, + 63, 68, 8, 64, 65, 73, 8, 46, 79, 69, 65, 67, 65, 2, 83, 75, 79, 61, 82, 80, 132, + 69, 63, 68, 81, 72, 69, 63, 68, 8, 73, 69, 81, 8, 65, 69, 74, 65, 79, 8, 57, 75, + 68, 74, 82, 74, 67, 80, 71, 75, 81, 8, 87, 82, 8, 79, 65, 63, 68, 74, 65, 74, 2, + 83, 75, 79, 61, 82, 80, 132, 69, 63, 68, 81, 72, 69, 63, 68, 8, 73, 69, 81, 8, 65, + 69, 74, 65, 79, 8, 57, 75, 68, 74, 82, 74, 67, 80, 71, 75, 81, 8, 87, 82, 8, 79, + 65, 63, 68, 74, 65, 74, 2, 61, 62, 65, 74, 20, 6, 8, 91, 8, 25, 25, 20, 8, 42, + 65, 84, 69, 102, 8, 67, 65, 68, 65, 74, 8, 64, 69, 65, 8, 36, 74, 132, 69, 63, 68, + 81, 65, 74, 8, 64, 61, 79, 110, 62, 65, 79, 8, 61, 82, 80, 65, 69, 74, 3, 2, 68, + 61, 62, 65, 74, 20, 6, 8, 91, 8, 25, 25, 20, 8, 42, 65, 84, 69, 102, 8, 67, 65, + 68, 65, 74, 8, 64, 69, 65, 8, 36, 74, 132, 69, 63, 68, 81, 65, 74, 8, 64, 61, 79, + 110, 62, 65, 79, 8, 61, 82, 80, 65, 69, 74, 3, 2, 61, 74, 64, 65, 79, 33, 8, 84, + 69, 79, 8, 68, 61, 62, 65, 74, 8, 70, 61, 8, 67, 65, 68, 109, 79, 81, 18, 8, 64, + 61, 102, 8, 43, 65, 79, 79, 8, 53, 81, 61, 62, 81, 83, 20, 8, 39, 79, 20, 2, 61, + 74, 64, 65, 79, 33, 8, 84, 69, 79, 8, 68, 61, 62, 65, 74, 8, 70, 61, 8, 67, 65, + 68, 109, 79, 81, 18, 8, 64, 61, 102, 8, 43, 65, 79, 79, 8, 53, 81, 61, 64, 81, 83, + 20, 8, 39, 79, 20, 2, 37, 86, 71, 8, 132, 69, 63, 68, 8, 132, 75, 67, 61, 79, 8, + 61, 82, 66, 8, 64, 65, 74, 8, 53, 81, 61, 74, 64, 78, 82, 74, 69, 81, 8, 132, 81, + 65, 72, 72, 81, 18, 8, 64, 61, 102, 8, 84, 69, 79, 2, 37, 86, 71, 8, 132, 69, 63, + 68, 8, 132, 75, 67, 61, 79, 8, 61, 82, 66, 8, 64, 65, 74, 8, 53, 81, 61, 74, 64, + 78, 82, 74, 71, 81, 8, 132, 81, 65, 72, 72, 81, 18, 8, 64, 61, 102, 8, 84, 69, 79, + 2, 79, 65, 74, 8, 55, 65, 62, 65, 79, 66, 72, 82, 102, 8, 61, 74, 8, 57, 75, 68, + 74, 82, 74, 67, 65, 74, 8, 68, 61, 62, 65, 74, 20, 8, 7, 36, 62, 65, 4, 2, 65, + 69, 74, 65, 74, 8, 55, 65, 62, 65, 79, 66, 72, 82, 102, 8, 61, 74, 8, 57, 75, 68, + 74, 82, 74, 67, 65, 74, 8, 68, 61, 62, 65, 74, 20, 8, 7, 36, 62, 65, 79, 18, 2, + 65, 79, 79, 8, 46, 75, 72, 72, 65, 74, 67, 65, 8, 39, 79, 20, 8, 37, 86, 71, 18, + 8, 73, 61, 74, 8, 64, 61, 79, 66, 8, 64, 69, 65, 132, 65, 8, 41, 79, 61, 67, 65, + 8, 74, 69, 63, 68, 81, 2, 43, 65, 79, 79, 8, 46, 75, 72, 72, 65, 74, 67, 65, 8, + 39, 79, 20, 8, 37, 86, 71, 18, 8, 73, 61, 74, 8, 64, 61, 79, 66, 8, 64, 69, 65, + 132, 65, 8, 41, 79, 61, 67, 65, 8, 74, 69, 63, 68, 81, 2, 83, 75, 74, 8, 64, 65, + 73, 8, 65, 69, 74, 132, 65, 69, 81, 69, 67, 65, 74, 8, 44, 74, 81, 65, 79, 65, 132, + 132, 65, 74, 132, 81, 61, 74, 64, 78, 82, 74, 71, 81, 8, 64, 65, 80, 8, 43, 61, 82, + 80, 4, 2, 83, 75, 74, 8, 64, 65, 73, 8, 65, 69, 74, 132, 65, 69, 81, 69, 67, 65, + 74, 8, 44, 74, 81, 65, 79, 65, 132, 132, 65, 74, 132, 81, 61, 74, 64, 78, 82, 74, 71, + 81, 8, 64, 65, 80, 8, 43, 61, 82, 80, 3, 2, 62, 65, 132, 69, 81, 87, 65, 79, 80, + 8, 61, 82, 80, 8, 62, 65, 81, 79, 61, 63, 68, 81, 65, 74, 20, 6, 2, 62, 65, 132, + 69, 81, 87, 65, 79, 80, 8, 61, 82, 80, 8, 62, 65, 81, 79, 61, 63, 68, 81, 65, 74, + 20, 6, 2, 14, 60, 82, 79, 82, 81, 8, 64, 65, 80, 8, 53, 81, 61, 64, 81, 83, 20, + 8, 39, 79, 8, 37, 86, 81, 20, 15, 2, 14, 60, 82, 79, 82, 66, 8, 64, 65, 80, 8, + 53, 81, 61, 64, 81, 83, 20, 8, 39, 79, 8, 37, 86, 81, 20, 15, 2, 7, 39, 61, 80, + 18, 8, 68, 61, 62, 65, 74, 8, 53, 69, 65, 8, 74, 69, 63, 68, 81, 8, 67, 65, 81, + 61, 74, 35, 6, 8, 14, 36, 62, 65, 79, 8, 69, 63, 68, 8, 68, 61, 81, 81, 65, 8, + 64, 65, 74, 2, 7, 39, 61, 80, 18, 8, 68, 61, 62, 65, 74, 8, 53, 69, 65, 8, 74, + 69, 63, 68, 81, 8, 67, 65, 81, 61, 74, 35, 6, 8, 14, 36, 62, 65, 79, 8, 69, 63, + 68, 8, 68, 61, 81, 81, 65, 8, 64, 65, 74, 2, 40, 69, 74, 64, 79, 82, 63, 71, 20, + 15, 8, 48, 61, 74, 8, 64, 61, 79, 66, 8, 64, 69, 65, 132, 65, 8, 41, 79, 61, 67, + 65, 8, 74, 69, 63, 68, 81, 8, 83, 75, 73, 8, 65, 69, 74, 132, 65, 69, 81, 69, 4, + 2, 40, 69, 74, 64, 79, 82, 63, 71, 20, 15, 8, 48, 61, 74, 8, 64, 61, 79, 66, 8, + 64, 69, 65, 132, 65, 8, 41, 79, 61, 67, 65, 8, 74, 69, 63, 68, 81, 8, 83, 75, 73, + 8, 65, 69, 74, 132, 65, 69, 81, 69, 3, 2, 67, 65, 74, 8, 44, 74, 81, 65, 79, 65, + 132, 132, 65, 74, 132, 81, 61, 74, 64, 78, 82, 74, 71, 81, 8, 64, 65, 80, 8, 43, 61, + 82, 80, 62, 65, 132, 69, 81, 87, 65, 79, 80, 8, 61, 82, 80, 2, 67, 65, 74, 8, 44, + 74, 81, 65, 79, 65, 132, 132, 65, 74, 132, 81, 61, 74, 64, 78, 82, 74, 71, 81, 8, 64, + 65, 80, 8, 43, 61, 82, 80, 62, 65, 132, 69, 81, 87, 65, 79, 80, 8, 61, 82, 80, 2, + 62, 65, 81, 79, 61, 63, 68, 81, 65, 74, 18, 8, 132, 75, 74, 64, 65, 79, 74, 8, 73, + 61, 74, 8, 73, 82, 102, 8, 132, 69, 65, 8, 67, 61, 74, 87, 8, 75, 62, 70, 65, 71, + 81, 69, 83, 8, 78, 79, 110, 3, 2, 62, 65, 81, 79, 61, 63, 68, 81, 65, 74, 18, 8, + 132, 75, 74, 64, 65, 79, 74, 8, 73, 61, 74, 8, 73, 82, 102, 8, 132, 69, 65, 8, 67, + 61, 74, 87, 8, 75, 62, 70, 65, 71, 81, 69, 83, 8, 78, 79, 110, 3, 2, 66, 65, 74, + 18, 8, 82, 74, 64, 8, 64, 61, 8, 84, 65, 79, 64, 65, 74, 8, 53, 69, 65, 8, 73, + 69, 79, 8, 87, 82, 67, 65, 62, 65, 74, 18, 8, 64, 61, 102, 8, 66, 61, 132, 81, 8, + 61, 72, 72, 65, 2, 66, 65, 74, 18, 8, 82, 74, 64, 8, 64, 61, 8, 84, 65, 79, 64, + 65, 74, 8, 53, 69, 65, 8, 73, 69, 79, 8, 87, 82, 67, 65, 62, 65, 74, 18, 8, 64, + 61, 102, 8, 66, 61, 132, 81, 8, 61, 72, 72, 65, 2, 46, 65, 74, 74, 65, 79, 8, 64, + 65, 79, 8, 56, 65, 79, 68, 103, 72, 81, 74, 69, 132, 132, 65, 8, 64, 65, 79, 8, 36, + 74, 132, 69, 63, 68, 81, 8, 132, 69, 74, 64, 18, 8, 64, 61, 102, 8, 65, 69, 74, 65, + 2, 46, 65, 74, 74, 65, 79, 8, 64, 65, 79, 8, 56, 65, 79, 68, 103, 72, 81, 74, 69, + 132, 132, 65, 8, 64, 65, 79, 8, 36, 74, 132, 69, 63, 68, 81, 8, 132, 69, 74, 64, 18, + 8, 64, 61, 102, 8, 65, 69, 74, 65, 2, 7, 57, 75, 68, 74, 82, 74, 67, 80, 74, 75, + 81, 13, 8, 62, 65, 83, 75, 79, 132, 81, 65, 68, 81, 20, 8, 43, 65, 79, 79, 8, 46, + 75, 72, 72, 65, 67, 65, 8, 57, 109, 72, 72, 73, 65, 79, 2, 7, 57, 75, 68, 74, 82, + 74, 67, 80, 74, 75, 81, 6, 8, 62, 65, 83, 75, 79, 132, 81, 65, 68, 81, 20, 8, 43, + 65, 79, 79, 8, 46, 75, 72, 72, 65, 67, 65, 8, 57, 109, 72, 72, 73, 65, 79, 2, 73, + 65, 69, 74, 81, 8, 61, 72, 72, 65, 79, 64, 69, 74, 67, 80, 8, 3, 8, 64, 61, 80, + 8, 68, 75, 62, 65, 8, 69, 63, 68, 18, 8, 75, 66, 66, 65, 74, 8, 67, 65, 132, 61, + 67, 81, 20, 8, 74, 69, 63, 68, 81, 2, 73, 65, 69, 74, 81, 8, 61, 72, 72, 65, 79, + 64, 69, 74, 67, 80, 8, 14, 3, 8, 64, 61, 80, 8, 68, 61, 62, 65, 8, 69, 63, 68, + 18, 8, 75, 66, 66, 65, 74, 8, 67, 65, 132, 61, 67, 81, 18, 8, 74, 69, 63, 68, 81, + 2, 79, 65, 63, 68, 81, 8, 83, 65, 79, 132, 81, 61, 74, 64, 65, 74, 8, 3, 15, 18, + 8, 83, 75, 74, 8, 65, 69, 74, 65, 79, 8, 57, 75, 68, 74, 82, 74, 61, 80, 74, 75, + 81, 8, 132, 65, 69, 8, 70, 65, 81, 87, 81, 2, 79, 65, 63, 68, 81, 8, 83, 65, 79, + 132, 81, 61, 74, 64, 65, 74, 8, 3, 15, 18, 8, 83, 75, 74, 8, 65, 69, 74, 65, 79, + 8, 57, 75, 68, 74, 82, 74, 61, 80, 74, 75, 81, 8, 132, 65, 69, 8, 70, 65, 81, 87, + 81, 2, 74, 69, 63, 68, 81, 8, 64, 69, 65, 8, 52, 65, 64, 65, 18, 8, 132, 75, 74, + 64, 65, 79, 74, 8, 74, 82, 79, 8, 83, 75, 74, 8, 65, 69, 74, 65, 73, 8, 67, 65, + 84, 69, 132, 132, 65, 74, 2, 74, 69, 63, 68, 81, 8, 64, 69, 65, 8, 52, 65, 64, 65, + 18, 8, 132, 75, 74, 64, 65, 79, 74, 8, 74, 82, 79, 8, 83, 75, 74, 8, 65, 69, 74, + 65, 73, 8, 67, 65, 84, 69, 132, 132, 65, 74, 2, 48, 61, 74, 67, 65, 72, 8, 61, 74, + 8, 57, 75, 68, 74, 82, 74, 67, 65, 74, 33, 8, 64, 65, 79, 8, 132, 65, 69, 8, 61, + 62, 65, 79, 8, 69, 73, 73, 65, 79, 8, 83, 75, 79, 3, 2, 48, 61, 74, 67, 65, 72, + 8, 61, 74, 8, 57, 75, 68, 74, 82, 74, 67, 65, 74, 102, 33, 8, 64, 65, 79, 8, 132, + 65, 69, 8, 61, 62, 65, 79, 8, 69, 73, 73, 65, 79, 8, 83, 75, 79, 3, 2, 68, 61, + 74, 64, 65, 74, 20, 8, 7, 45, 61, 18, 8, 43, 65, 79, 79, 8, 46, 75, 72, 72, 69, + 65, 67, 65, 8, 57, 109, 72, 72, 73, 65, 79, 18, 8, 84, 69, 79, 8, 84, 69, 132, 132, + 65, 74, 8, 70, 61, 18, 2, 68, 61, 74, 64, 65, 74, 20, 8, 7, 45, 61, 18, 8, 43, + 65, 79, 79, 8, 46, 75, 72, 72, 65, 67, 65, 8, 57, 109, 72, 72, 73, 65, 79, 18, 8, + 84, 69, 79, 8, 84, 69, 132, 132, 65, 74, 8, 70, 61, 18, 2, 64, 61, 102, 8, 69, 73, + 73, 65, 79, 8, 65, 69, 74, 8, 67, 65, 84, 69, 132, 132, 65, 79, 8, 48, 61, 74, 67, + 65, 72, 8, 61, 74, 8, 46, 72, 65, 69, 74, 84, 75, 68, 74, 82, 74, 67, 65, 74, 2, + 64, 61, 102, 8, 69, 73, 73, 65, 79, 8, 65, 69, 74, 8, 67, 65, 84, 69, 132, 132, 65, + 79, 8, 48, 61, 74, 67, 65, 72, 8, 61, 74, 8, 46, 72, 65, 69, 74, 84, 75, 68, 74, + 82, 74, 67, 65, 74, 2, 83, 75, 79, 68, 61, 74, 64, 65, 74, 8, 69, 132, 81, 6, 20, + 8, 36, 62, 65, 79, 8, 61, 82, 80, 8, 64, 65, 79, 8, 53, 81, 61, 81, 69, 132, 81, + 69, 71, 8, 83, 75, 74, 20, 8, 23, 30, 30, 29, 8, 65, 79, 132, 65, 68, 65, 74, 8, + 53, 69, 65, 2, 83, 75, 79, 68, 61, 74, 64, 65, 74, 8, 69, 132, 81, 6, 20, 8, 36, + 62, 65, 79, 8, 61, 82, 80, 8, 64, 65, 79, 8, 53, 81, 61, 81, 69, 132, 81, 69, 71, + 8, 83, 75, 74, 8, 23, 30, 30, 29, 8, 65, 79, 132, 65, 68, 65, 74, 8, 53, 69, 65, + 2, 110, 102, 8, 23, 30, 30, 31, 18, 8, 23, 30, 31, 22, 18, 8, 23, 30, 31, 23, 8, + 67, 65, 79, 61, 64, 65, 8, 66, 110, 79, 8, 38, 68, 61, 79, 72, 75, 81, 81, 65, 74, + 62, 82, 79, 67, 8, 83, 62, 65, 79, 8, 48, 61, 74, 67, 65, 72, 8, 61, 74, 2, 64, + 61, 102, 8, 23, 30, 30, 31, 18, 8, 23, 30, 31, 22, 18, 8, 23, 30, 31, 23, 8, 67, + 65, 79, 61, 64, 65, 8, 66, 110, 79, 8, 38, 68, 61, 79, 72, 75, 81, 81, 65, 74, 62, + 82, 79, 67, 8, 64, 65, 79, 8, 48, 61, 74, 67, 65, 72, 8, 61, 74, 2, 57, 75, 68, + 74, 82, 74, 67, 65, 74, 8, 65, 69, 74, 67, 65, 81, 79, 65, 81, 65, 74, 8, 66, 69, + 74, 64, 18, 8, 84, 65, 74, 74, 8, 25, 29, 18, 27, 8, 57, 75, 68, 74, 82, 74, 67, + 65, 74, 2, 57, 75, 68, 74, 82, 74, 67, 65, 74, 8, 65, 69, 74, 67, 65, 81, 79, 65, + 81, 65, 74, 8, 66, 69, 74, 64, 18, 8, 84, 65, 74, 74, 8, 25, 29, 18, 27, 8, 57, + 75, 68, 74, 82, 74, 67, 65, 74, 2, 83, 75, 74, 8, 132, 65, 72, 62, 132, 81, 8, 62, + 65, 68, 75, 62, 65, 74, 8, 68, 61, 81, 81, 65, 20, 8, 27, 25, 11, 8, 61, 72, 72, + 65, 79, 8, 57, 75, 68, 74, 82, 74, 67, 65, 74, 8, 14, 23, 31, 22, 23, 8, 82, 74, + 64, 2, 83, 75, 74, 8, 132, 65, 72, 62, 132, 81, 8, 62, 65, 68, 75, 62, 65, 74, 8, + 68, 61, 81, 81, 65, 20, 8, 27, 25, 11, 8, 61, 72, 72, 65, 79, 8, 57, 75, 68, 74, + 82, 74, 67, 65, 74, 8, 14, 23, 31, 22, 23, 8, 82, 74, 64, 2, 23, 31, 22, 24, 15, + 18, 8, 132, 78, 103, 81, 65, 79, 8, 23, 24, 18, 22, 24, 18, 8, 67, 61, 79, 8, 24, + 26, 18, 27, 24, 22, 11, 8, 75, 64, 65, 79, 8, 23, 30, 22, 11, 8, 64, 65, 79, 8, + 57, 75, 68, 74, 82, 74, 67, 65, 74, 2, 23, 31, 22, 24, 15, 18, 8, 132, 78, 103, 81, + 65, 79, 8, 23, 24, 18, 22, 24, 22, 22, 18, 8, 67, 61, 79, 8, 24, 26, 18, 27, 24, + 22, 11, 8, 75, 64, 65, 79, 8, 23, 30, 22, 8, 64, 65, 79, 8, 57, 75, 68, 74, 82, + 74, 67, 65, 74, 2, 68, 61, 62, 65, 74, 8, 69, 74, 8, 64, 65, 74, 8, 45, 61, 68, + 79, 65, 74, 8, 23, 30, 29, 30, 18, 8, 23, 30, 29, 31, 18, 8, 23, 30, 30, 22, 18, + 8, 23, 30, 30, 23, 18, 8, 23, 30, 30, 24, 18, 8, 23, 30, 30, 25, 2, 68, 61, 62, + 65, 74, 8, 69, 74, 8, 64, 65, 74, 8, 45, 61, 68, 79, 65, 74, 8, 23, 30, 29, 30, + 18, 8, 23, 30, 29, 31, 18, 8, 23, 30, 30, 22, 18, 8, 23, 30, 30, 23, 18, 8, 23, + 30, 30, 24, 18, 8, 23, 30, 30, 25, 2, 23, 30, 30, 26, 18, 8, 23, 30, 30, 27, 8, + 82, 74, 64, 8, 132, 75, 67, 61, 79, 8, 23, 30, 31, 22, 18, 8, 23, 30, 31, 23, 18, + 8, 23, 30, 31, 24, 18, 8, 23, 30, 31, 25, 18, 8, 23, 30, 31, 26, 18, 8, 132, 65, + 72, 62, 80, 81, 2, 23, 30, 30, 26, 18, 8, 23, 30, 30, 27, 8, 82, 74, 64, 8, 132, + 75, 67, 61, 79, 8, 23, 30, 31, 22, 18, 8, 23, 30, 31, 23, 18, 8, 23, 30, 31, 24, + 18, 8, 23, 30, 31, 25, 18, 8, 23, 30, 31, 26, 18, 8, 132, 65, 72, 62, 80, 81, 2, + 23, 30, 31, 27, 8, 62, 69, 80, 8, 23, 31, 22, 23, 18, 8, 23, 31, 22, 24, 18, 8, + 23, 31, 22, 25, 18, 8, 23, 31, 22, 26, 18, 8, 23, 31, 22, 27, 18, 8, 23, 31, 22, + 28, 18, 8, 23, 31, 22, 29, 18, 8, 23, 31, 22, 30, 2, 23, 30, 31, 27, 8, 62, 69, + 80, 8, 23, 31, 22, 23, 18, 8, 23, 31, 22, 24, 18, 8, 23, 31, 22, 25, 18, 8, 23, + 31, 22, 26, 18, 8, 23, 31, 22, 27, 18, 8, 23, 31, 22, 28, 18, 8, 23, 31, 22, 29, + 18, 8, 23, 31, 22, 30, 2, 14, 53, 65, 68, 79, 8, 67, 82, 81, 20, 15, 8, 53, 91, + 8, 26, 20, 2, 14, 53, 65, 68, 79, 8, 67, 82, 81, 20, 15, 8, 91, 8, 26, 20, 2, + 48, 65, 69, 74, 65, 8, 43, 65, 79, 79, 65, 74, 18, 8, 69, 63, 68, 8, 132, 78, 79, + 65, 63, 68, 65, 8, 64, 61, 80, 8, 61, 82, 80, 8, 73, 69, 81, 8, 61, 72, 72, 65, + 79, 2, 48, 65, 69, 74, 65, 8, 43, 65, 79, 79, 65, 74, 18, 8, 69, 63, 68, 8, 132, + 78, 79, 65, 63, 68, 65, 8, 64, 61, 80, 8, 61, 82, 80, 8, 73, 69, 81, 8, 61, 72, + 72, 65, 79, 2, 52, 65, 132, 65, 79, 83, 65, 20, 8, 40, 80, 8, 69, 132, 81, 8, 71, + 65, 69, 74, 65, 79, 8, 82, 74, 81, 65, 79, 8, 82, 74, 80, 18, 8, 64, 65, 79, 8, + 73, 69, 81, 8, 7, 78, 75, 132, 69, 81, 69, 83, 65, 79, 2, 52, 65, 132, 65, 79, 83, + 65, 20, 8, 40, 80, 8, 69, 132, 81, 8, 71, 65, 69, 74, 65, 79, 8, 82, 74, 81, 65, + 79, 8, 82, 74, 80, 18, 8, 64, 65, 79, 8, 73, 69, 81, 8, 7, 78, 75, 132, 69, 81, + 69, 83, 65, 79, 2, 53, 69, 63, 68, 65, 79, 68, 65, 69, 81, 6, 8, 132, 61, 67, 65, + 74, 8, 84, 69, 79, 64, 32, 8, 74, 82, 79, 8, 64, 69, 65, 132, 65, 80, 8, 75, 64, + 65, 79, 8, 70, 65, 74, 65, 80, 8, 69, 132, 81, 8, 64, 61, 80, 2, 53, 69, 63, 68, + 65, 79, 68, 65, 69, 81, 6, 8, 132, 61, 67, 65, 74, 8, 84, 69, 79, 64, 32, 8, 74, + 82, 79, 8, 64, 69, 65, 132, 65, 80, 8, 75, 64, 65, 79, 8, 70, 65, 74, 65, 80, 8, + 69, 132, 81, 8, 64, 61, 80, 2, 52, 69, 63, 68, 81, 69, 67, 65, 20, 8, 36, 62, 65, + 79, 8, 65, 69, 74, 65, 8, 79, 65, 69, 66, 72, 69, 63, 68, 65, 8, 51, 79, 110, 66, + 82, 74, 67, 8, 65, 79, 132, 63, 68, 65, 69, 74, 81, 8, 73, 69, 79, 2, 52, 69, 63, + 68, 81, 69, 67, 65, 20, 8, 36, 62, 65, 79, 8, 65, 69, 74, 65, 8, 79, 65, 69, 66, + 72, 69, 63, 68, 65, 8, 51, 79, 110, 66, 82, 74, 67, 8, 65, 79, 132, 63, 68, 65, 69, + 74, 81, 8, 73, 69, 79, 2, 74, 8, 70, 65, 64, 65, 79, 8, 57, 65, 69, 132, 65, 8, + 61, 82, 80, 132, 69, 63, 68, 81, 80, 83, 75, 72, 72, 33, 8, 65, 80, 8, 69, 132, 81, + 8, 132, 65, 68, 79, 8, 72, 65, 69, 63, 68, 81, 8, 73, 109, 67, 72, 69, 63, 68, 2, + 69, 74, 8, 70, 65, 64, 65, 79, 8, 57, 65, 69, 132, 65, 8, 61, 82, 80, 132, 69, 63, + 68, 81, 80, 83, 75, 72, 72, 33, 8, 65, 80, 8, 69, 132, 81, 8, 132, 65, 68, 79, 8, + 72, 65, 69, 63, 68, 81, 8, 73, 109, 67, 72, 69, 63, 68, 18, 2, 64, 61, 102, 8, 64, + 65, 79, 8, 40, 81, 61, 81, 8, 69, 74, 8, 61, 74, 64, 65, 79, 65, 79, 8, 42, 65, + 132, 81, 61, 72, 81, 8, 39, 65, 74, 8, 36, 82, 80, 132, 63, 68, 82, 102, 8, 83, 65, + 79, 3, 2, 64, 61, 102, 8, 64, 65, 79, 8, 40, 81, 61, 81, 8, 69, 74, 8, 61, 74, + 64, 65, 79, 65, 79, 8, 42, 65, 132, 81, 61, 72, 81, 8, 39, 65, 74, 8, 36, 82, 80, + 132, 63, 68, 82, 102, 8, 83, 65, 79, 3, 2, 72, 103, 102, 81, 18, 8, 61, 72, 80, 8, + 65, 79, 8, 69, 74, 8, 69, 68, 74, 8, 68, 69, 74, 65, 69, 74, 67, 65, 67, 61, 74, + 67, 65, 74, 8, 69, 132, 81, 20, 8, 44, 63, 68, 8, 132, 78, 79, 65, 63, 68, 65, 2, + 72, 103, 102, 81, 18, 8, 61, 72, 80, 8, 65, 79, 8, 69, 74, 8, 69, 68, 74, 8, 68, + 69, 74, 65, 69, 74, 67, 65, 67, 61, 74, 67, 65, 74, 8, 69, 132, 81, 20, 8, 44, 63, + 68, 8, 132, 78, 79, 65, 63, 68, 65, 2, 64, 61, 80, 8, 61, 82, 80, 8, 73, 69, 81, + 8, 83, 75, 72, 72, 65, 73, 8, 37, 65, 84, 82, 102, 81, 132, 65, 69, 74, 8, 64, 65, + 79, 8, 39, 69, 74, 67, 65, 20, 8, 64, 69, 65, 8, 69, 74, 2, 64, 61, 80, 8, 61, + 82, 80, 8, 73, 69, 81, 8, 83, 75, 72, 72, 65, 73, 8, 37, 65, 84, 82, 102, 81, 132, + 65, 69, 74, 8, 64, 65, 79, 8, 39, 69, 74, 67, 65, 18, 8, 64, 69, 65, 8, 69, 74, + 2, 82, 74, 132, 65, 79, 73, 8, 43, 61, 82, 80, 68, 61, 72, 81, 8, 73, 61, 74, 67, + 65, 72, 68, 61, 66, 81, 8, 82, 74, 64, 8, 64, 82, 74, 71, 65, 72, 8, 132, 69, 74, + 64, 20, 8, 14, 40, 80, 2, 82, 74, 132, 65, 79, 73, 8, 43, 61, 82, 80, 68, 61, 72, + 81, 8, 73, 61, 74, 67, 65, 72, 68, 61, 66, 81, 8, 82, 74, 64, 8, 64, 82, 74, 71, + 65, 72, 8, 132, 69, 74, 64, 20, 8, 14, 40, 80, 2, 67, 65, 74, 110, 67, 81, 8, 74, + 103, 73, 72, 69, 63, 68, 18, 8, 84, 65, 74, 74, 8, 84, 69, 79, 8, 64, 69, 65, 8, + 23, 22, 22, 8, 7, 8, 64, 65, 80, 8, 40, 69, 74, 4, 2, 67, 65, 74, 110, 67, 81, + 8, 74, 103, 73, 72, 69, 63, 68, 18, 8, 84, 65, 74, 74, 8, 84, 69, 79, 8, 64, 69, + 65, 8, 23, 22, 22, 8, 7, 8, 64, 65, 80, 8, 40, 69, 74, 3, 2, 71, 75, 73, 73, + 65, 74, 132, 81, 65, 82, 65, 79, 132, 75, 72, 72, 80, 8, 82, 73, 8, 23, 97, 8, 48, + 69, 72, 72, 69, 75, 74, 8, 65, 79, 68, 109, 68, 65, 74, 18, 8, 82, 73, 8, 64, 65, + 74, 2, 71, 75, 73, 73, 65, 74, 132, 81, 65, 82, 65, 79, 132, 75, 72, 72, 80, 8, 82, + 73, 8, 23, 97, 8, 48, 69, 72, 72, 69, 75, 74, 8, 65, 79, 68, 109, 68, 65, 74, 18, + 8, 82, 73, 8, 64, 65, 74, 2, 65, 81, 84, 61, 69, 67, 65, 74, 8, 36, 82, 80, 66, + 61, 72, 72, 8, 64, 65, 79, 8, 23, 22, 29, 29, 36, 82, 66, 132, 63, 68, 72, 61, 67, + 8, 87, 82, 8, 64, 65, 63, 71, 65, 74, 20, 15, 2, 65, 81, 84, 61, 69, 67, 65, 74, + 8, 36, 82, 80, 66, 61, 72, 72, 8, 64, 65, 79, 8, 23, 22, 29, 29, 8, 36, 82, 66, + 132, 63, 68, 72, 61, 67, 8, 87, 82, 8, 64, 65, 63, 71, 65, 74, 20, 15, 2, 57, 69, + 79, 8, 64, 110, 79, 66, 65, 74, 8, 82, 74, 80, 8, 70, 61, 8, 73, 69, 81, 8, 64, + 65, 73, 8, 42, 65, 66, 110, 68, 72, 18, 8, 64, 61, 102, 8, 84, 69, 79, 8, 64, 65, + 74, 2, 57, 69, 79, 8, 64, 110, 79, 66, 65, 74, 8, 82, 74, 80, 8, 70, 61, 8, 73, + 69, 81, 8, 64, 65, 73, 8, 42, 65, 66, 110, 68, 72, 18, 8, 64, 61, 102, 8, 84, 69, + 79, 8, 64, 65, 74, 2, 53, 81, 65, 82, 65, 79, 132, 61, 81, 87, 8, 74, 69, 63, 68, + 81, 8, 65, 79, 68, 109, 68, 65, 74, 18, 8, 74, 69, 63, 68, 81, 8, 62, 65, 79, 82, + 68, 69, 67, 65, 74, 20, 8, 39, 61, 80, 2, 53, 81, 65, 82, 65, 79, 132, 61, 81, 87, + 8, 74, 69, 63, 68, 81, 8, 65, 79, 68, 109, 68, 65, 74, 18, 8, 74, 69, 63, 68, 81, + 8, 62, 65, 79, 82, 68, 69, 67, 65, 74, 20, 8, 39, 61, 80, 2, 71, 109, 74, 74, 81, + 65, 74, 8, 84, 69, 79, 8, 81, 82, 74, 18, 8, 84, 65, 74, 74, 8, 84, 69, 79, 8, + 23, 22, 22, 8, 0, 12, 0, 16, 8, 75, 64, 65, 79, 8, 83, 69, 65, 72, 72, 65, 69, + 63, 68, 81, 8, 23, 23, 22, 11, 2, 71, 109, 74, 74, 81, 65, 74, 8, 84, 69, 79, 8, + 81, 82, 74, 18, 8, 84, 65, 74, 74, 8, 84, 69, 79, 8, 23, 22, 22, 8, 75, 64, 65, + 79, 8, 83, 69, 65, 72, 72, 65, 69, 63, 68, 81, 8, 23, 23, 22, 11, 2, 74, 65, 68, + 73, 65, 74, 20, 8, 49, 61, 63, 68, 64, 65, 73, 8, 84, 69, 79, 8, 70, 65, 64, 75, + 63, 68, 8, 65, 69, 74, 73, 61, 72, 8, 61, 82, 66, 8, 23, 29, 22, 8, 21, 18, 8, + 23, 30, 22, 8, 83, 21, 8, 75, 64, 65, 79, 8, 67, 61, 79, 2, 74, 65, 68, 73, 65, + 74, 20, 8, 49, 61, 63, 68, 64, 65, 73, 8, 84, 69, 79, 8, 70, 65, 64, 75, 63, 68, + 8, 65, 69, 74, 73, 61, 72, 8, 61, 82, 66, 8, 23, 29, 22, 8, 11, 18, 8, 23, 30, + 22, 8, 75, 64, 65, 79, 8, 67, 61, 79, 2, 24, 23, 22, 8, 29, 8, 67, 65, 67, 61, + 74, 67, 65, 74, 8, 132, 69, 74, 64, 18, 8, 73, 110, 132, 132, 65, 74, 8, 84, 69, 79, + 8, 82, 74, 80, 8, 64, 69, 65, 8, 41, 79, 61, 67, 65, 8, 65, 79, 74, 132, 81, 72, + 69, 63, 68, 2, 24, 23, 22, 8, 29, 39, 8, 67, 65, 67, 61, 74, 67, 65, 74, 8, 132, + 69, 74, 64, 18, 8, 73, 110, 132, 132, 65, 74, 8, 84, 69, 79, 8, 82, 74, 80, 8, 64, + 69, 65, 8, 41, 79, 61, 67, 65, 8, 65, 79, 74, 132, 81, 72, 69, 63, 68, 2, 83, 75, + 79, 72, 65, 67, 65, 74, 32, 8, 68, 61, 62, 65, 74, 8, 84, 69, 79, 8, 65, 80, 8, + 74, 69, 63, 68, 81, 8, 83, 69, 65, 72, 72, 65, 69, 63, 68, 81, 8, 69, 74, 8, 67, + 82, 81, 65, 79, 8, 36, 62, 3, 2, 83, 75, 79, 72, 65, 67, 65, 74, 32, 8, 68, 61, + 62, 65, 74, 8, 84, 69, 79, 8, 65, 80, 8, 74, 69, 63, 68, 81, 8, 83, 69, 65, 72, + 72, 65, 69, 63, 68, 81, 8, 69, 74, 8, 67, 82, 81, 65, 79, 8, 36, 62, 3, 2, 132, + 69, 63, 68, 81, 8, 82, 74, 64, 8, 67, 65, 132, 81, 110, 81, 87, 81, 8, 61, 82, 66, + 8, 64, 69, 65, 8, 64, 61, 73, 61, 72, 80, 8, 83, 75, 79, 72, 69, 65, 67, 65, 74, + 64, 65, 74, 8, 60, 65, 68, 72, 65, 74, 8, 83, 75, 74, 2, 132, 69, 63, 68, 81, 8, + 82, 74, 64, 8, 67, 65, 132, 81, 110, 81, 87, 81, 8, 61, 82, 66, 8, 64, 69, 65, 8, + 64, 61, 73, 61, 72, 80, 8, 83, 75, 79, 72, 69, 65, 67, 65, 74, 64, 65, 74, 8, 60, + 61, 68, 72, 65, 74, 8, 83, 75, 74, 2, 23, 30, 31, 23, 18, 8, 23, 29, 30, 22, 18, + 8, 23, 31, 24, 22, 18, 8, 23, 31, 23, 30, 18, 8, 23, 31, 23, 27, 18, 8, 23, 31, + 22, 24, 18, 8, 23, 31, 22, 23, 18, 8, 23, 31, 22, 22, 18, 8, 23, 31, 22, 24, 2, + 23, 30, 31, 23, 18, 8, 23, 29, 30, 22, 18, 8, 23, 31, 24, 22, 18, 8, 23, 31, 23, + 30, 18, 8, 23, 31, 23, 27, 18, 8, 23, 31, 22, 24, 18, 8, 23, 31, 22, 23, 18, 8, + 23, 31, 22, 22, 18, 8, 23, 31, 22, 24, 2, 65, 81, 84, 61, 80, 8, 87, 82, 8, 84, + 65, 69, 81, 8, 67, 65, 81, 79, 69, 65, 62, 65, 74, 35, 8, 44, 63, 68, 8, 132, 78, + 79, 65, 63, 68, 65, 8, 64, 69, 65, 80, 8, 61, 82, 80, 18, 8, 75, 62, 3, 2, 65, + 81, 84, 61, 80, 8, 87, 82, 8, 84, 65, 69, 81, 8, 67, 65, 81, 79, 69, 65, 62, 65, + 74, 35, 8, 44, 63, 68, 8, 132, 78, 79, 65, 63, 68, 65, 8, 64, 69, 65, 80, 8, 61, + 82, 80, 18, 8, 75, 62, 3, 2, 67, 72, 65, 69, 63, 68, 8, 69, 63, 68, 18, 8, 14, + 84, 69, 65, 8, 64, 65, 79, 8, 43, 65, 79, 79, 8, 46, 103, 73, 73, 65, 79, 65, 79, + 15, 18, 8, 67, 61, 74, 87, 8, 67, 65, 74, 61, 82, 8, 84, 65, 69, 102, 18, 2, 67, + 72, 65, 69, 63, 68, 8, 69, 63, 68, 18, 8, 14, 84, 69, 65, 8, 64, 65, 79, 8, 43, + 65, 79, 79, 8, 46, 103, 73, 73, 65, 79, 65, 79, 15, 18, 8, 67, 61, 74, 87, 8, 67, + 65, 74, 61, 82, 8, 84, 65, 69, 102, 18, 2, 14, 36, 78, 78, 72, 61, 82, 80, 8, 64, + 65, 79, 8, 68, 69, 74, 81, 65, 79, 65, 74, 8, 52, 65, 69, 68, 65, 74, 20, 8, 60, + 82, 79, 82, 66, 32, 8, 7, 57, 61, 68, 79, 72, 69, 63, 68, 9, 6, 15, 2, 14, 36, + 78, 78, 72, 61, 82, 80, 8, 64, 65, 79, 8, 68, 69, 74, 81, 65, 79, 65, 74, 8, 52, + 65, 69, 68, 65, 74, 20, 8, 60, 82, 79, 82, 66, 32, 8, 7, 57, 61, 68, 79, 72, 69, + 63, 68, 9, 6, 15, 2, 64, 61, 102, 8, 69, 74, 8, 82, 74, 132, 65, 79, 73, 8, 40, + 81, 61, 79, 8, 51, 75, 132, 81, 65, 74, 8, 132, 69, 74, 64, 18, 8, 64, 69, 65, 8, + 83, 69, 65, 72, 72, 65, 69, 63, 68, 81, 8, 74, 69, 63, 68, 81, 2, 64, 61, 102, 8, + 69, 74, 8, 82, 74, 132, 65, 79, 73, 8, 40, 81, 61, 81, 8, 51, 75, 132, 81, 65, 74, + 8, 132, 69, 74, 64, 18, 8, 64, 69, 65, 8, 83, 69, 65, 72, 72, 65, 69, 63, 68, 81, + 8, 74, 69, 63, 68, 81, 2, 65, 69, 74, 67, 65, 68, 65, 74, 8, 84, 65, 79, 64, 65, + 74, 18, 8, 87, 20, 8, 37, 20, 8, 64, 69, 65, 8, 54, 68, 65, 61, 81, 65, 79, 78, + 75, 132, 81, 65, 74, 8, 83, 75, 74, 8, 23, 30, 31, 31, 18, 8, 75, 62, 84, 75, 68, + 72, 8, 82, 74, 80, 2, 65, 69, 74, 67, 65, 68, 65, 74, 8, 84, 65, 79, 64, 65, 74, + 18, 8, 87, 20, 8, 37, 20, 8, 64, 69, 65, 8, 54, 68, 65, 61, 81, 65, 79, 78, 75, + 132, 81, 65, 74, 8, 83, 75, 74, 8, 23, 30, 31, 31, 18, 8, 75, 62, 84, 75, 68, 72, + 8, 82, 74, 80, 2, 64, 65, 79, 8, 83, 75, 79, 68, 69, 74, 8, 67, 65, 66, 61, 102, + 81, 65, 8, 37, 65, 132, 63, 68, 72, 82, 102, 8, 69, 74, 8, 64, 69, 65, 132, 65, 79, + 8, 37, 65, 87, 69, 65, 68, 82, 74, 67, 8, 65, 69, 74, 65, 2, 64, 65, 79, 8, 83, + 75, 79, 68, 69, 74, 8, 67, 65, 66, 61, 102, 81, 65, 8, 37, 65, 132, 63, 68, 72, 82, + 102, 8, 69, 74, 8, 64, 69, 65, 132, 65, 79, 8, 37, 65, 87, 69, 65, 68, 82, 74, 67, + 8, 65, 69, 74, 65, 2, 37, 65, 132, 132, 65, 79, 82, 74, 67, 8, 62, 79, 69, 74, 67, + 65, 74, 8, 84, 69, 79, 64, 20, 8, 44, 63, 68, 8, 132, 78, 79, 65, 63, 68, 65, 8, + 64, 69, 65, 80, 8, 61, 82, 80, 18, 8, 75, 62, 3, 2, 37, 65, 132, 132, 65, 79, 82, + 74, 67, 8, 62, 79, 69, 74, 67, 65, 74, 8, 84, 69, 79, 64, 20, 8, 44, 63, 68, 8, + 132, 78, 79, 65, 63, 68, 65, 8, 64, 69, 65, 80, 8, 61, 82, 80, 18, 8, 75, 62, 3, + 2, 67, 72, 65, 69, 63, 68, 8, 69, 63, 68, 8, 73, 69, 79, 8, 83, 75, 72, 72, 71, + 75, 73, 73, 65, 74, 8, 64, 61, 79, 110, 62, 65, 79, 8, 71, 72, 61, 79, 8, 62, 69, + 74, 18, 8, 64, 61, 102, 8, 61, 74, 2, 67, 72, 65, 69, 63, 68, 8, 69, 63, 68, 8, + 73, 69, 79, 8, 83, 75, 72, 72, 71, 75, 73, 73, 65, 74, 8, 64, 61, 79, 110, 62, 65, + 79, 8, 71, 72, 61, 79, 8, 62, 69, 74, 18, 8, 64, 61, 102, 8, 61, 74, 2, 64, 65, + 74, 8, 36, 82, 80, 67, 61, 62, 65, 74, 8, 84, 65, 132, 65, 74, 81, 72, 69, 63, 68, + 65, 8, 36, 62, 132, 81, 79, 69, 63, 68, 65, 8, 74, 69, 63, 68, 81, 8, 67, 65, 73, + 61, 63, 68, 81, 2, 64, 65, 74, 8, 36, 82, 80, 67, 61, 62, 65, 74, 8, 84, 65, 132, + 65, 74, 81, 72, 69, 63, 68, 65, 8, 36, 62, 132, 81, 79, 69, 63, 68, 65, 8, 74, 69, + 63, 68, 81, 8, 67, 65, 73, 61, 63, 68, 81, 2, 84, 65, 79, 64, 65, 74, 8, 71, 109, + 74, 74, 65, 74, 18, 8, 65, 80, 8, 132, 65, 69, 8, 64, 65, 74, 74, 8, 62, 65, 69, + 73, 8, 39, 69, 80, 78, 75, 132, 69, 81, 69, 75, 74, 80, 66, 75, 74, 64, 80, 20, 2, + 84, 65, 79, 64, 65, 74, 8, 71, 109, 74, 74, 65, 74, 18, 8, 65, 80, 8, 132, 65, 69, + 8, 64, 65, 74, 74, 8, 62, 65, 69, 73, 8, 39, 69, 80, 78, 75, 132, 69, 81, 69, 75, + 74, 80, 66, 75, 74, 64, 80, 20, 2, 55, 74, 64, 8, 69, 63, 68, 8, 132, 78, 79, 65, + 63, 68, 65, 8, 64, 69, 65, 80, 8, 61, 82, 80, 18, 8, 75, 62, 67, 72, 65, 69, 63, + 68, 8, 69, 63, 68, 8, 84, 65, 69, 102, 18, 8, 64, 61, 102, 8, 84, 69, 79, 2, 55, + 74, 64, 8, 69, 63, 68, 8, 132, 78, 79, 65, 63, 68, 65, 8, 64, 69, 65, 80, 8, 61, + 82, 80, 18, 8, 75, 62, 67, 72, 65, 69, 63, 68, 8, 69, 63, 68, 8, 84, 65, 69, 102, + 18, 8, 64, 61, 102, 8, 84, 69, 79, 2, 64, 69, 65, 8, 54, 69, 72, 67, 82, 74, 67, + 80, 79, 61, 81, 65, 8, 83, 75, 79, 72, 103, 82, 66, 69, 67, 8, 74, 75, 63, 68, 8, + 74, 69, 63, 68, 81, 8, 62, 65, 84, 69, 72, 72, 69, 67, 81, 8, 62, 65, 3, 2, 64, + 69, 65, 8, 54, 69, 72, 67, 82, 74, 67, 80, 79, 61, 81, 65, 8, 83, 75, 79, 72, 103, + 82, 66, 69, 67, 8, 74, 75, 63, 68, 8, 74, 69, 63, 68, 81, 8, 62, 65, 84, 69, 72, + 72, 69, 67, 81, 8, 62, 65, 3, 2, 71, 75, 73, 73, 65, 74, 8, 68, 61, 62, 65, 74, + 20, 8, 36, 62, 65, 79, 8, 69, 63, 68, 8, 132, 75, 72, 72, 81, 65, 8, 67, 72, 61, + 82, 62, 65, 74, 18, 8, 64, 61, 102, 8, 65, 69, 74, 2, 71, 75, 73, 73, 65, 74, 8, + 68, 61, 62, 65, 74, 20, 8, 36, 62, 65, 79, 8, 69, 63, 68, 8, 132, 75, 72, 72, 81, + 65, 8, 67, 72, 61, 82, 62, 65, 74, 18, 8, 64, 61, 102, 8, 65, 69, 74, 2, 40, 81, + 61, 81, 18, 8, 64, 65, 79, 8, 73, 69, 81, 8, 23, 28, 22, 8, 31, 18, 8, 23, 29, + 22, 8, 31, 22, 18, 8, 67, 61, 79, 8, 25, 22, 22, 8, 31, 8, 64, 69, 65, 8, 37, + 65, 64, 110, 79, 66, 74, 69, 132, 132, 65, 8, 64, 65, 63, 71, 81, 18, 2, 40, 81, 61, + 81, 18, 8, 64, 65, 79, 8, 73, 69, 81, 8, 23, 28, 22, 8, 31, 22, 18, 8, 23, 29, + 22, 8, 18, 8, 67, 61, 79, 8, 25, 22, 22, 8, 22, 8, 64, 69, 65, 8, 37, 65, 64, + 110, 79, 66, 74, 69, 132, 132, 65, 8, 64, 65, 65, 71, 81, 18, 2, 83, 75, 74, 8, 64, + 65, 79, 8, 52, 65, 67, 69, 65, 79, 82, 74, 67, 8, 64, 75, 63, 68, 8, 61, 82, 63, + 68, 8, 61, 72, 80, 8, 65, 69, 74, 8, 64, 82, 79, 63, 68, 61, 82, 80, 8, 132, 75, + 72, 69, 64, 65, 79, 8, 82, 74, 64, 8, 83, 75, 79, 3, 2, 83, 75, 74, 8, 64, 65, + 79, 8, 52, 65, 67, 69, 65, 79, 82, 74, 67, 8, 64, 75, 63, 68, 8, 61, 82, 63, 68, + 8, 61, 72, 80, 8, 65, 69, 74, 8, 64, 82, 79, 63, 68, 61, 82, 80, 8, 132, 75, 72, + 69, 64, 65, 79, 8, 82, 74, 64, 8, 83, 75, 79, 3, 2, 132, 69, 63, 68, 81, 69, 67, + 65, 79, 8, 61, 74, 67, 65, 132, 65, 68, 65, 74, 8, 84, 65, 79, 64, 65, 74, 8, 84, + 69, 79, 64, 20, 8, 44, 63, 68, 8, 73, 82, 102, 8, 70, 65, 64, 75, 63, 68, 8, 61, + 82, 63, 68, 8, 74, 75, 63, 68, 18, 8, 65, 62, 65, 74, 132, 75, 2, 132, 69, 63, 68, + 81, 69, 67, 65, 79, 8, 61, 74, 67, 65, 132, 65, 68, 65, 74, 8, 84, 65, 79, 64, 65, + 74, 8, 84, 69, 79, 64, 20, 8, 44, 63, 68, 8, 73, 82, 102, 8, 70, 65, 64, 75, 63, + 68, 8, 61, 82, 63, 68, 8, 74, 75, 63, 68, 18, 8, 65, 62, 65, 74, 132, 75, 2, 84, + 69, 65, 8, 64, 65, 79, 8, 46, 103, 73, 73, 65, 79, 65, 79, 18, 8, 73, 69, 81, 8, + 65, 69, 74, 69, 67, 65, 74, 8, 57, 75, 79, 81, 65, 74, 8, 61, 82, 66, 8, 64, 65, + 74, 2, 84, 69, 65, 8, 64, 65, 79, 8, 46, 103, 73, 73, 65, 79, 65, 79, 18, 8, 73, + 69, 81, 8, 65, 69, 74, 69, 67, 65, 74, 8, 57, 75, 79, 81, 65, 74, 8, 61, 82, 66, + 8, 64, 65, 74, 2, 37, 65, 132, 63, 68, 72, 82, 102, 8, 84, 65, 67, 65, 74, 8, 64, + 65, 80, 8, 36, 82, 80, 67, 72, 65, 69, 63, 68, 80, 66, 75, 74, 64, 80, 8, 87, 82, + 79, 110, 63, 71, 71, 75, 73, 73, 65, 74, 18, 2, 37, 65, 132, 63, 68, 72, 82, 102, 8, + 84, 65, 67, 65, 74, 8, 64, 65, 80, 8, 36, 82, 80, 67, 72, 65, 69, 63, 68, 80, 66, + 75, 74, 64, 80, 8, 87, 82, 79, 110, 63, 71, 71, 75, 73, 73, 65, 74, 18, 2, 55, 65, + 62, 65, 79, 132, 63, 68, 110, 132, 132, 65, 18, 8, 64, 69, 65, 8, 87, 84, 69, 132, 63, + 68, 65, 74, 8, 24, 22, 22, 8, 22, 22, 22, 8, 82, 74, 64, 8, 24, 8, 48, 69, 72, + 72, 69, 75, 74, 65, 74, 2, 55, 65, 62, 65, 79, 132, 63, 68, 110, 132, 132, 65, 18, 8, + 64, 69, 65, 8, 87, 84, 69, 132, 63, 68, 65, 74, 8, 24, 22, 22, 8, 22, 22, 22, 8, + 11, 8, 82, 74, 64, 8, 24, 8, 48, 69, 72, 72, 69, 75, 74, 65, 74, 2, 132, 63, 68, + 84, 61, 74, 71, 81, 65, 74, 18, 8, 65, 69, 74, 67, 65, 132, 81, 65, 72, 72, 81, 8, + 82, 74, 64, 8, 65, 80, 8, 66, 82, 79, 63, 68, 81, 62, 61, 79, 2, 132, 63, 68, 84, + 61, 74, 71, 81, 65, 74, 18, 8, 65, 69, 74, 67, 65, 132, 81, 65, 72, 72, 81, 8, 82, + 74, 64, 8, 65, 80, 8, 66, 82, 79, 63, 68, 81, 62, 61, 79, 2, 132, 63, 68, 84, 65, + 79, 8, 67, 65, 73, 61, 63, 68, 81, 18, 8, 64, 65, 74, 8, 40, 81, 61, 81, 8, 62, + 65, 69, 8, 67, 72, 65, 69, 63, 68, 65, 74, 8, 53, 81, 65, 82, 65, 79, 132, 103, 3, + 2, 132, 63, 68, 84, 65, 79, 8, 67, 65, 73, 61, 63, 68, 81, 18, 8, 64, 65, 74, 8, + 40, 81, 61, 81, 8, 62, 65, 69, 8, 67, 72, 65, 69, 63, 68, 65, 74, 8, 53, 81, 65, + 82, 65, 79, 132, 103, 3, 2, 87, 82, 8, 62, 69, 72, 61, 74, 87, 69, 65, 79, 65, 74, + 20, 8, 53, 78, 103, 81, 65, 79, 18, 8, 61, 72, 80, 8, 73, 61, 74, 8, 74, 103, 73, + 72, 69, 63, 68, 8, 61, 74, 74, 61, 68, 73, 18, 2, 87, 82, 8, 62, 69, 72, 61, 74, + 87, 69, 65, 79, 65, 74, 20, 8, 53, 78, 103, 81, 65, 79, 18, 8, 61, 72, 80, 8, 73, + 61, 74, 8, 74, 103, 73, 72, 69, 63, 68, 8, 61, 74, 74, 61, 68, 73, 18, 2, 64, 61, + 102, 8, 64, 69, 65, 8, 53, 103, 81, 87, 65, 8, 69, 73, 73, 65, 79, 8, 87, 84, 69, + 132, 63, 68, 65, 74, 8, 23, 8, 82, 74, 64, 8, 24, 8, 48, 69, 72, 72, 69, 75, 74, + 65, 74, 2, 64, 61, 102, 8, 64, 69, 65, 8, 53, 103, 81, 87, 65, 8, 69, 73, 73, 65, + 79, 8, 87, 84, 69, 132, 63, 68, 65, 74, 8, 23, 8, 82, 74, 64, 8, 24, 8, 48, 69, + 72, 72, 69, 75, 74, 65, 74, 2, 132, 63, 68, 84, 61, 74, 71, 65, 74, 8, 84, 110, 79, + 64, 65, 74, 18, 8, 68, 61, 81, 8, 73, 61, 74, 8, 65, 69, 74, 65, 74, 8, 48, 69, + 81, 81, 65, 72, 84, 65, 67, 8, 65, 69, 74, 67, 65, 3, 4, 2, 132, 63, 68, 84, 61, + 74, 71, 65, 74, 8, 84, 110, 79, 64, 65, 74, 18, 8, 68, 61, 81, 8, 73, 61, 74, 8, + 65, 69, 74, 65, 74, 8, 48, 69, 81, 81, 65, 72, 84, 65, 67, 8, 65, 69, 74, 67, 65, + 3, 2, 132, 63, 68, 72, 61, 67, 65, 74, 8, 82, 74, 64, 8, 67, 65, 132, 61, 67, 81, + 32, 8, 74, 82, 79, 8, 23, 8, 48, 69, 72, 72, 69, 75, 74, 8, 0, 12, 8, 69, 74, + 8, 64, 65, 74, 8, 74, 65, 82, 65, 74, 2, 132, 63, 68, 72, 61, 67, 65, 74, 8, 82, + 74, 64, 8, 67, 65, 132, 61, 67, 81, 32, 8, 74, 82, 79, 8, 23, 8, 48, 69, 72, 72, + 69, 75, 74, 8, 7, 8, 69, 74, 8, 64, 65, 74, 8, 74, 65, 82, 65, 74, 2, 40, 81, + 61, 81, 8, 82, 74, 64, 8, 74, 82, 79, 8, 64, 65, 74, 8, 55, 65, 62, 65, 79, 132, + 63, 68, 82, 102, 8, 110, 62, 65, 79, 8, 23, 8, 48, 69, 72, 72, 69, 75, 74, 8, 69, + 74, 2, 40, 81, 61, 81, 8, 82, 74, 64, 8, 74, 82, 79, 8, 64, 65, 74, 8, 55, 65, + 62, 65, 79, 132, 63, 68, 82, 102, 8, 110, 62, 65, 79, 8, 23, 8, 48, 69, 72, 72, 69, + 75, 74, 8, 69, 74, 2, 64, 65, 74, 8, 36, 82, 80, 67, 72, 65, 69, 63, 68, 80, 66, + 75, 74, 64, 80, 9, 8, 56, 69, 65, 72, 72, 65, 69, 63, 68, 81, 8, 69, 132, 81, 8, + 73, 61, 74, 8, 73, 69, 81, 8, 64, 69, 65, 132, 65, 74, 2, 64, 65, 74, 8, 36, 82, + 80, 67, 72, 65, 69, 63, 68, 80, 66, 75, 74, 64, 80, 9, 8, 56, 69, 65, 72, 72, 65, + 69, 63, 68, 81, 8, 69, 132, 81, 8, 73, 61, 74, 8, 73, 69, 81, 8, 64, 69, 65, 132, + 65, 74, 2, 60, 61, 68, 72, 65, 74, 8, 74, 69, 63, 68, 81, 8, 79, 69, 63, 68, 81, + 69, 67, 8, 83, 75, 79, 67, 65, 67, 61, 74, 67, 65, 74, 18, 8, 68, 103, 81, 81, 65, + 8, 83, 69, 65, 72, 72, 65, 69, 63, 68, 81, 2, 60, 61, 68, 72, 65, 74, 8, 74, 69, + 63, 68, 81, 8, 79, 69, 63, 68, 81, 69, 67, 8, 83, 75, 79, 67, 65, 67, 61, 74, 67, + 65, 74, 18, 8, 68, 103, 81, 81, 65, 8, 83, 69, 65, 72, 72, 65, 69, 63, 68, 81, 2, + 65, 69, 74, 65, 8, 61, 74, 64, 65, 79, 65, 8, 60, 61, 68, 72, 8, 84, 103, 68, 72, + 65, 74, 8, 82, 74, 64, 8, 61, 82, 66, 8, 64, 69, 65, 132, 65, 8, 57, 65, 69, 132, + 65, 8, 65, 80, 2, 65, 69, 74, 65, 8, 61, 74, 64, 65, 79, 65, 8, 60, 61, 68, 72, + 8, 84, 103, 68, 72, 65, 74, 8, 82, 74, 64, 8, 61, 82, 66, 8, 64, 69, 65, 132, 65, + 8, 57, 65, 69, 132, 65, 8, 65, 80, 2, 83, 65, 79, 68, 110, 81, 65, 74, 8, 132, 75, + 72, 72, 65, 74, 18, 8, 64, 61, 102, 8, 69, 73, 73, 65, 79, 8, 65, 69, 74, 8, 45, + 61, 68, 79, 8, 83, 75, 74, 8, 64, 65, 73, 8, 61, 74, 4, 2, 83, 65, 79, 68, 110, + 81, 65, 74, 8, 132, 75, 72, 72, 65, 74, 18, 8, 64, 61, 102, 8, 69, 73, 73, 65, 79, + 8, 65, 69, 74, 8, 45, 61, 68, 79, 8, 83, 75, 74, 8, 64, 65, 73, 8, 61, 74, 3, + 2, 64, 65, 79, 74, 8, 69, 74, 8, 132, 75, 72, 63, 68, 65, 79, 8, 66, 69, 74, 61, + 74, 87, 69, 65, 72, 72, 65, 74, 8, 36, 62, 68, 103, 74, 67, 69, 67, 71, 65, 69, 81, + 8, 132, 81, 61, 74, 64, 20, 2, 64, 65, 79, 74, 8, 69, 74, 8, 132, 75, 72, 63, 68, + 65, 79, 8, 66, 69, 74, 61, 74, 87, 69, 65, 72, 72, 65, 74, 8, 36, 62, 68, 103, 74, + 67, 69, 67, 71, 65, 69, 81, 8, 132, 81, 61, 74, 64, 20, 2, 39, 65, 74, 74, 8, 64, + 61, 79, 110, 62, 65, 79, 8, 73, 110, 132, 132, 65, 74, 8, 84, 69, 79, 8, 82, 74, 80, + 8, 64, 75, 63, 68, 8, 71, 72, 61, 79, 8, 132, 65, 69, 74, 18, 8, 64, 61, 2, 39, + 65, 74, 74, 8, 64, 61, 79, 110, 62, 65, 79, 8, 73, 110, 132, 132, 65, 74, 8, 84, 69, + 79, 8, 82, 74, 80, 8, 64, 75, 63, 68, 8, 71, 72, 61, 79, 8, 132, 65, 69, 74, 18, + 8, 64, 61, 2, 62, 65, 69, 8, 64, 65, 73, 8, 66, 79, 110, 68, 65, 79, 65, 74, 8, + 53, 86, 132, 81, 65, 73, 8, 65, 69, 74, 8, 132, 63, 68, 65, 69, 74, 62, 61, 79, 65, + 79, 8, 55, 65, 62, 65, 79, 132, 63, 68, 82, 81, 2, 62, 65, 69, 8, 64, 65, 73, 8, + 66, 79, 110, 68, 65, 79, 65, 74, 8, 53, 86, 132, 81, 65, 73, 8, 65, 69, 74, 8, 132, + 63, 68, 65, 69, 74, 62, 61, 79, 65, 79, 8, 55, 65, 62, 65, 79, 132, 63, 68, 82, 81, + 2, 83, 75, 74, 8, 30, 22, 22, 8, 22, 22, 22, 8, 0, 12, 8, 87, 20, 8, 37, 20, + 8, 65, 69, 67, 65, 74, 81, 72, 69, 63, 68, 8, 65, 69, 74, 8, 39, 65, 66, 69, 87, + 69, 81, 8, 83, 75, 74, 2, 83, 75, 74, 8, 30, 22, 22, 8, 22, 22, 22, 8, 7, 8, + 87, 20, 8, 37, 20, 8, 65, 69, 67, 65, 74, 81, 72, 69, 63, 68, 8, 65, 69, 74, 8, + 39, 65, 66, 69, 87, 69, 81, 8, 83, 75, 74, 2, 24, 22, 22, 8, 22, 22, 22, 8, 7, + 8, 62, 65, 64, 65, 82, 81, 65, 81, 65, 20, 8, 39, 61, 80, 8, 132, 75, 72, 72, 8, + 74, 82, 74, 8, 61, 62, 67, 65, 103, 74, 64, 65, 79, 81, 2, 84, 65, 79, 64, 65, 74, + 18, 8, 82, 74, 64, 8, 69, 74, 132, 75, 84, 65, 69, 81, 8, 132, 81, 69, 73, 73, 65, + 74, 8, 84, 69, 79, 8, 73, 69, 81, 8, 64, 65, 73, 8, 48, 61, 67, 69, 4, 2, 132, + 81, 79, 61, 81, 8, 83, 75, 72, 72, 71, 75, 73, 73, 65, 74, 8, 110, 62, 65, 79, 65, + 69, 74, 20, 8, 57, 69, 79, 8, 67, 72, 61, 82, 62, 65, 74, 8, 61, 62, 65, 79, 18, + 8, 64, 61, 102, 2, 64, 69, 65, 8, 56, 75, 79, 72, 61, 67, 65, 8, 69, 74, 8, 64, + 69, 65, 132, 65, 79, 8, 41, 75, 79, 73, 18, 8, 69, 74, 8, 69, 68, 79, 65, 79, 8, + 46, 110, 79, 87, 65, 8, 74, 69, 63, 68, 81, 2, 61, 74, 67, 65, 74, 75, 73, 73, 65, + 74, 8, 84, 65, 79, 64, 65, 74, 8, 71, 61, 74, 74, 20, 8, 57, 69, 79, 8, 73, 110, + 132, 132, 65, 74, 8, 84, 69, 132, 132, 65, 74, 18, 2, 84, 61, 80, 8, 65, 69, 67, 65, + 74, 81, 72, 69, 63, 68, 8, 64, 69, 65, 132, 65, 79, 8, 41, 75, 74, 64, 80, 8, 132, + 65, 69, 74, 8, 82, 74, 64, 8, 84, 69, 65, 8, 65, 79, 8, 61, 74, 3, 2, 67, 65, + 84, 65, 74, 64, 65, 81, 8, 84, 65, 79, 64, 65, 74, 8, 132, 75, 72, 72, 20, 8, 57, + 65, 74, 74, 8, 64, 65, 79, 8, 43, 65, 79, 79, 8, 46, 103, 73, 73, 65, 79, 65, 79, + 2, 67, 72, 61, 82, 62, 81, 18, 8, 64, 61, 102, 8, 132, 63, 68, 75, 74, 8, 61, 82, + 80, 8, 64, 65, 79, 8, 39, 65, 66, 69, 74, 69, 81, 69, 75, 74, 18, 8, 61, 82, 80, + 8, 64, 65, 73, 2, 57, 75, 79, 81, 65, 8, 36, 82, 80, 67, 72, 65, 69, 63, 68, 80, + 66, 75, 74, 64, 80, 8, 132, 65, 69, 74, 65, 8, 37, 65, 132, 81, 69, 73, 73, 82, 74, + 67, 8, 68, 65, 79, 83, 75, 79, 3, 2, 67, 65, 68, 81, 18, 8, 132, 75, 8, 73, 61, + 67, 8, 64, 61, 80, 8, 132, 65, 69, 74, 20, 8, 14, 40, 80, 8, 71, 61, 74, 74, 8, + 61, 62, 65, 79, 8, 61, 82, 63, 68, 8, 65, 69, 74, 73, 61, 72, 2, 65, 69, 74, 8, + 61, 74, 64, 65, 79, 65, 79, 8, 46, 103, 73, 73, 65, 79, 65, 79, 8, 82, 74, 64, 8, + 65, 69, 74, 8, 61, 74, 64, 65, 79, 65, 79, 8, 48, 61, 67, 69, 132, 81, 79, 61, 81, + 2, 71, 75, 73, 73, 65, 74, 15, 18, 8, 64, 65, 79, 8, 74, 69, 63, 68, 81, 80, 8, + 83, 75, 74, 8, 45, 61, 71, 75, 62, 8, 82, 74, 64, 8, 132, 65, 69, 74, 65, 74, 8, + 37, 79, 110, 64, 65, 79, 74, 2, 84, 82, 102, 81, 65, 18, 8, 82, 74, 64, 8, 64, 69, + 65, 8, 71, 109, 74, 74, 65, 74, 8, 74, 61, 63, 68, 68, 65, 79, 8, 83, 69, 65, 72, + 72, 65, 69, 63, 68, 81, 8, 67, 72, 61, 82, 62, 65, 74, 18, 2, 64, 61, 102, 8, 64, + 69, 65, 132, 65, 79, 8, 41, 75, 74, 64, 80, 8, 74, 69, 63, 68, 81, 80, 8, 84, 65, + 69, 81, 65, 79, 8, 132, 65, 69, 74, 8, 132, 75, 72, 72, 8, 61, 72, 80, 8, 65, 69, + 74, 2, 53, 78, 61, 79, 66, 75, 74, 64, 80, 18, 8, 69, 74, 8, 64, 65, 74, 8, 69, + 73, 73, 65, 79, 8, 65, 81, 84, 61, 80, 8, 68, 69, 74, 65, 69, 74, 67, 65, 81, 61, + 74, 8, 82, 74, 64, 2, 74, 69, 63, 68, 81, 80, 8, 68, 65, 79, 61, 82, 80, 67, 65, + 74, 75, 73, 73, 65, 74, 8, 84, 69, 79, 64, 20, 8, 57, 69, 79, 8, 73, 109, 63, 68, + 81, 65, 74, 8, 64, 75, 63, 68, 2, 61, 82, 63, 68, 8, 71, 110, 74, 66, 81, 69, 67, + 65, 8, 39, 69, 80, 71, 82, 132, 132, 69, 75, 74, 65, 74, 8, 68, 69, 65, 79, 110, 62, + 65, 79, 8, 83, 65, 79, 73, 65, 69, 64, 65, 74, 20, 8, 49, 82, 74, 2, 68, 65, 69, + 102, 81, 8, 65, 80, 32, 8, 65, 79, 8, 132, 75, 72, 72, 8, 74, 82, 79, 8, 69, 74, + 8, 36, 82, 80, 74, 61, 68, 73, 65, 66, 103, 72, 72, 65, 74, 8, 83, 65, 79, 84, 61, + 74, 64, 81, 8, 56, 75, 79, 71, 8, 82, 74, 64, 8, 56, 43, 65, 73, 65, 74, 2, 84, + 65, 79, 64, 65, 74, 18, 8, 56, 69, 74, 67, 8, 84, 65, 74, 74, 8, 62, 65, 69, 8, + 64, 65, 79, 8, 36, 82, 66, 132, 81, 65, 72, 72, 82, 74, 67, 8, 64, 65, 80, 8, 43, + 61, 82, 80, 68, 61, 72, 81, 80, 3, 2, 78, 72, 61, 74, 65, 80, 8, 64, 65, 79, 8, + 36, 82, 80, 67, 72, 65, 69, 63, 68, 8, 69, 74, 66, 75, 72, 67, 65, 8, 83, 75, 79, + 110, 62, 65, 79, 67, 65, 68, 65, 74, 64, 65, 79, 2, 53, 63, 68, 84, 61, 74, 71, 82, + 74, 67, 8, 64, 65, 79, 8, 41, 69, 74, 61, 74, 87, 72, 61, 67, 65, 8, 53, 63, 68, + 84, 69, 65, 79, 69, 67, 71, 65, 69, 81, 65, 74, 8, 62, 65, 79, 65, 69, 81, 65, 81, + 20, 2, 48, 65, 69, 74, 65, 8, 43, 65, 79, 79, 65, 74, 18, 8, 64, 61, 79, 110, 62, + 65, 79, 8, 71, 61, 74, 74, 8, 73, 61, 74, 8, 132, 65, 68, 79, 8, 83, 65, 79, 132, + 63, 68, 69, 65, 64, 65, 74, 65, 79, 2, 48, 65, 69, 74, 82, 74, 67, 8, 132, 65, 69, + 74, 18, 8, 84, 61, 80, 8, 53, 63, 68, 84, 69, 65, 79, 69, 67, 71, 65, 69, 81, 65, + 74, 8, 132, 69, 74, 64, 8, 82, 74, 64, 8, 84, 61, 80, 2, 83, 75, 79, 110, 62, 65, + 79, 67, 65, 68, 65, 74, 64, 65, 8, 56, 65, 79, 68, 103, 72, 81, 74, 69, 132, 132, 65, + 8, 132, 69, 74, 64, 20, 8, 14, 43, 61, 63, 68, 81, 20, 15, 8, 40, 69, 67, 65, 74, + 81, 72, 69, 63, 68, 8, 71, 61, 74, 74, 2, 64, 65, 79, 8, 36, 82, 80, 67, 72, 65, + 69, 63, 68, 8, 74, 69, 65, 73, 61, 72, 80, 8, 53, 63, 68, 84, 69, 65, 79, 69, 67, + 71, 65, 69, 81, 65, 74, 8, 62, 65, 79, 65, 69, 81, 65, 74, 18, 8, 132, 75, 3, 2, + 72, 61, 74, 67, 65, 8, 84, 69, 79, 8, 73, 69, 81, 8, 64, 65, 74, 8, 53, 81, 65, + 82, 65, 79, 74, 8, 132, 75, 8, 68, 75, 63, 68, 8, 67, 65, 68, 65, 74, 8, 71, 109, + 74, 74, 65, 74, 18, 2, 84, 69, 65, 8, 84, 69, 79, 8, 84, 75, 72, 72, 65, 74, 18, + 8, 83, 69, 65, 72, 72, 65, 69, 63, 68, 81, 8, 82, 74, 132, 65, 79, 73, 8, 42, 65, + 84, 69, 132, 132, 65, 74, 18, 8, 61, 62, 65, 79, 2, 74, 69, 63, 68, 81, 8, 64, 65, + 74, 8, 37, 65, 132, 63, 68, 72, 110, 132, 132, 65, 74, 8, 82, 74, 64, 8, 74, 69, 63, + 68, 81, 8, 64, 65, 73, 8, 51, 61, 78, 69, 65, 79, 20, 8, 23, 31, 24, 24, 2, 14, + 91, 53, 8, 23, 20, 15, 8, 49, 103, 63, 68, 132, 81, 8, 64, 65, 74, 8, 36, 132, 132, + 69, 132, 81, 65, 74, 81, 65, 74, 8, 69, 132, 81, 8, 87, 82, 8, 74, 65, 74, 74, 65, + 74, 8, 64, 69, 65, 8, 42, 79, 82, 78, 78, 65, 2, 64, 65, 79, 8, 56, 75, 72, 72, + 87, 69, 65, 68, 65, 79, 18, 8, 46, 61, 132, 132, 65, 74, 62, 75, 81, 65, 74, 8, 82, + 74, 64, 8, 37, 82, 79, 65, 61, 82, 67, 65, 68, 69, 72, 66, 65, 74, 18, 8, 64, 69, + 65, 2, 62, 65, 69, 8, 82, 74, 80, 8, 65, 69, 74, 8, 42, 65, 68, 61, 72, 81, 8, + 83, 75, 74, 8, 24, 22, 27, 22, 8, 62, 69, 80, 8, 25, 23, 22, 22, 8, 18, 8, 24, + 22, 22, 22, 2, 62, 69, 80, 8, 25, 22, 27, 22, 8, 7, 18, 8, 23, 30, 27, 22, 8, + 62, 69, 80, 8, 24, 31, 22, 22, 8, 7, 8, 124, 63, 20, 18, 8, 69, 74, 8, 37, 65, + 79, 72, 69, 74, 8, 64, 61, 67, 65, 67, 65, 74, 2, 83, 75, 74, 8, 23, 30, 22, 22, + 8, 62, 69, 80, 8, 25, 22, 22, 22, 8, 7, 18, 8, 64, 69, 65, 8, 37, 82, 79, 65, + 61, 82, 67, 65, 68, 69, 72, 66, 65, 74, 8, 132, 75, 67, 61, 79, 2, 74, 82, 79, 8, + 83, 75, 74, 8, 23, 24, 22, 22, 8, 62, 69, 80, 8, 25, 22, 22, 22, 8, 7, 8, 62, + 65, 87, 69, 65, 68, 65, 74, 20, 8, 39, 69, 65, 8, 37, 75, 81, 65, 74, 2, 65, 79, + 68, 61, 72, 81, 65, 74, 8, 62, 65, 69, 8, 82, 74, 80, 8, 23, 29, 22, 22, 8, 62, + 69, 80, 8, 24, 26, 22, 22, 8, 0, 107, 18, 18, 8, 69, 74, 8, 37, 65, 79, 72, 69, + 74, 8, 23, 28, 22, 22, 2, 62, 69, 80, 8, 24, 25, 22, 22, 8, 20, 8, 39, 61, 74, + 74, 18, 8, 82, 73, 8, 71, 72, 65, 69, 74, 65, 79, 65, 8, 42, 79, 82, 78, 78, 65, + 74, 8, 87, 82, 8, 110, 62, 65, 79, 3, 2, 67, 65, 68, 65, 74, 8, 71, 61, 73, 65, + 74, 8, 61, 82, 80, 8, 56, 65, 73, 65, 74, 8, 82, 74, 64, 8, 49, 65, 84, 8, 56, + 75, 79, 71, 18, 8, 62, 65, 87, 69, 65, 68, 82, 74, 67, 80, 84, 65, 69, 80, 65, 2, + 61, 82, 80, 8, 64, 65, 73, 8, 37, 61, 79, 61, 69, 74, 8, 82, 74, 64, 8, 37, 61, + 74, 67, 72, 61, 64, 65, 132, 63, 68, 8, 23, 24, 22, 22, 18, 8, 24, 22, 22, 22, 18, + 8, 26, 27, 31, 30, 23, 23, 18, 2, 30, 29, 18, 8, 29, 30, 30, 23, 18, 8, 30, 22, + 22, 18, 8, 31, 27, 30, 29, 26, 23, 24, 25, 28, 8, 7, 8, 68, 69, 74, 87, 82, 20, + 8, 23, 24, 18, 8, 25, 26, 18, 8, 28, 27, 18, 8, 31, 30, 29, 23, 18, 2, 29, 30, + 31, 31, 27, 18, 8, 23, 24, 27, 18, 8, 25, 27, 26, 29, 18, 8, 26, 22, 22, 22, 22, + 18, 8, 24, 23, 22, 22, 18, 8, 26, 30, 22, 22, 18, 8, 30, 22, 22, 22, 18, 8, 31, + 30, 22, 22, 8, 18, 2, 23, 24, 22, 22, 8, 7, 18, 8, 26, 27, 22, 8, 7, 18, 8, + 23, 22, 22, 8, 7, 18, 8, 31, 30, 8, 18, 8, 29, 30, 28, 27, 26, 18, 26, 22, 8, + 7, 18, 8, 24, 23, 18, 31, 31, 8, 7, 2, 48, 69, 81, 81, 65, 72, 80, 8, 62, 65, + 69, 72, 103, 82, 66, 69, 67, 65, 79, 8, 55, 74, 81, 65, 79, 80, 81, 110, 81, 87, 82, + 74, 67, 8, 65, 79, 68, 103, 72, 81, 8, 65, 69, 74, 8, 36, 79, 62, 65, 69, 81, 65, + 79, 2, 73, 69, 74, 64, 65, 132, 81, 65, 74, 80, 8, 25, 27, 22, 8, 18, 8, 67, 65, + 79, 69, 74, 67, 65, 79, 8, 69, 73, 8, 56, 75, 72, 72, 87, 82, 67, 18, 8, 83, 69, + 65, 72, 72, 65, 69, 63, 68, 81, 8, 61, 82, 63, 68, 2, 69, 73, 8, 56, 65, 79, 67, + 72, 65, 69, 63, 68, 8, 73, 69, 81, 8, 53, 81, 61, 61, 81, 80, 61, 74, 72, 65, 69, + 68, 65, 74, 8, 78, 20, 61, 20, 8, 83, 75, 74, 8, 31, 27, 22, 8, 11, 62, 69, 80, + 8, 87, 82, 2, 23, 20, 27, 29, 27, 8, 7, 18, 8, 23, 24, 20, 22, 22, 22, 8, 7, + 18, 8, 26, 27, 22, 22, 8, 7, 18, 8, 28, 30, 29, 26, 27, 31, 8, 7, 18, 8, 28, + 27, 20, 27, 24, 23, 26, 8, 124, 63, 20, 2, 61, 72, 80, 8, 61, 82, 63, 68, 8, 56, + 65, 79, 65, 69, 74, 132, 73, 69, 81, 67, 72, 69, 65, 64, 20, 8, 14, 23, 30, 31, 31, + 18, 8, 23, 29, 27, 23, 18, 8, 23, 29, 28, 27, 18, 8, 23, 25, 26, 29, 18, 8, 23, + 24, 31, 30, 18, 8, 23, 30, 27, 28, 18, 8, 23, 30, 27, 23, 2, 23, 30, 27, 25, 18, + 8, 23, 30, 27, 26, 18, 8, 23, 30, 26, 27, 18, 8, 23, 30, 26, 24, 18, 8, 23, 30, + 26, 25, 18, 8, 23, 30, 31, 24, 18, 8, 23, 30, 26, 22, 18, 8, 23, 30, 22, 24, 18, + 8, 23, 30, 22, 29, 18, 8, 23, 30, 22, 31, 18, 8, 23, 30, 23, 22, 15, 2, 40, 80, + 8, 68, 61, 74, 64, 65, 72, 81, 8, 132, 69, 63, 68, 8, 64, 61, 8, 79, 65, 67, 65, + 72, 73, 103, 102, 69, 67, 18, 8, 73, 65, 69, 74, 65, 8, 7, 43, 65, 79, 79, 65, 74, + 6, 18, 8, 82, 73, 8, 84, 69, 79, 81, 132, 63, 68, 61, 66, 81, 72, 69, 63, 68, 65, + 2, 49, 75, 81, 132, 81, 103, 74, 64, 65, 18, 8, 64, 69, 65, 8, 69, 74, 8, 68, 75, + 68, 65, 8, 43, 82, 74, 64, 65, 79, 81, 65, 8, 68, 69, 74, 65, 69, 74, 67, 65, 68, + 65, 74, 18, 8, 25, 22, 22, 18, 8, 26, 22, 22, 18, 8, 27, 22, 22, 8, 18, 8, 83, + 75, 74, 2, 64, 65, 74, 65, 74, 8, 84, 69, 79, 8, 64, 61, 74, 74, 8, 23, 27, 22, + 18, 8, 24, 22, 22, 18, 8, 24, 27, 22, 18, 8, 25, 22, 22, 18, 8, 25, 27, 22, 18, + 8, 26, 22, 22, 18, 8, 26, 27, 22, 18, 8, 27, 22, 22, 18, 8, 27, 27, 22, 18, 8, + 28, 22, 22, 2, 28, 27, 22, 18, 8, 29, 22, 22, 18, 8, 29, 27, 22, 18, 8, 30, 22, + 22, 18, 8, 30, 27, 22, 18, 8, 31, 22, 22, 18, 8, 31, 27, 22, 18, 8, 23, 22, 22, + 22, 8, 0, 12, 8, 65, 79, 132, 81, 61, 81, 81, 65, 74, 8, 73, 110, 132, 132, 65, 74, + 20, 2, 56, 65, 79, 67, 72, 65, 69, 63, 68, 132, 84, 65, 69, 132, 65, 8, 65, 69, 74, + 66, 61, 63, 68, 8, 65, 79, 132, 63, 68, 65, 69, 74, 81, 8, 68, 69, 65, 79, 8, 83, + 75, 74, 8, 37, 65, 79, 67, 71, 61, 73, 73, 65, 79, 132, 8, 56, 75, 79, 132, 63, 68, + 72, 61, 67, 2, 132, 69, 63, 68, 8, 83, 75, 74, 8, 42, 79, 82, 74, 64, 61, 82, 66, + 8, 87, 82, 8, 83, 65, 79, 61, 62, 132, 63, 68, 69, 65, 64, 65, 74, 20, 8, 39, 69, + 65, 8, 45, 61, 68, 79, 65, 8, 23, 30, 24, 27, 18, 8, 23, 30, 24, 28, 18, 8, 23, + 30, 24, 29, 18, 8, 23, 30, 24, 30, 8, 82, 74, 64, 2, 23, 30, 24, 31, 8, 84, 61, + 79, 65, 74, 8, 68, 61, 79, 81, 20, 8, 24, 22, 18, 27, 8, 7, 22, 8, 82, 74, 132, + 65, 79, 65, 79, 8, 37, 110, 79, 67, 65, 79, 132, 63, 68, 61, 66, 81, 8, 81, 79, 69, + 66, 66, 81, 32, 8, 64, 65, 74, 74, 8, 28, 27, 8, 98, 8, 73, 61, 63, 68, 65, 74, + 2, 61, 72, 72, 65, 69, 74, 8, 64, 69, 65, 8, 40, 69, 74, 71, 75, 73, 73, 65, 74, + 8, 62, 69, 80, 8, 23, 27, 22, 22, 8, 61, 82, 80, 18, 8, 64, 61, 79, 110, 62, 65, + 79, 8, 29, 30, 8, 22, 22, 8, 82, 74, 64, 8, 65, 69, 74, 8, 40, 69, 74, 71, 75, + 73, 73, 65, 74, 8, 83, 75, 74, 8, 74, 69, 63, 68, 81, 8, 73, 65, 68, 79, 2, 61, + 72, 80, 8, 25, 22, 22, 22, 8, 7, 14, 20, 8, 7, 48, 65, 69, 74, 65, 8, 43, 65, + 79, 79, 65, 74, 6, 18, 8, 68, 61, 62, 65, 74, 8, 82, 74, 67, 65, 66, 103, 68, 79, + 8, 31, 22, 8, 22, 11, 8, 75, 64, 65, 79, 8, 31, 30, 8, 22, 8, 82, 74, 132, 65, + 79, 65, 79, 8, 37, 110, 79, 67, 65, 79, 132, 63, 68, 61, 66, 81, 20, 2, 31, 22, 8, + 29, 11, 22, 18, 8, 30, 27, 8, 22, 8, 75, 64, 65, 79, 8, 31, 31, 8, 22, 8, 82, + 74, 132, 65, 79, 65, 79, 8, 37, 110, 79, 67, 65, 79, 80, 63, 68, 61, 66, 81, 9, 8, + 14, 7, 53, 81, 109, 68, 74, 65, 74, 9, 8, 36, 78, 78, 72, 61, 82, 132, 9, 6, 15, + 2, 65, 79, 66, 75, 79, 64, 65, 79, 81, 18, 8, 64, 61, 8, 84, 69, 79, 8, 23, 28, + 22, 8, 22, 22, 22, 8, 7, 8, 66, 110, 79, 8, 65, 72, 65, 71, 81, 79, 69, 132, 63, + 68, 65, 8, 37, 65, 72, 65, 82, 63, 68, 3, 2, 81, 82, 74, 67, 18, 8, 84, 65, 72, + 63, 68, 65, 8, 70, 61, 8, 65, 81, 84, 61, 80, 8, 81, 65, 82, 79, 65, 79, 8, 69, + 132, 81, 18, 8, 73, 65, 68, 79, 8, 61, 82, 66, 84, 65, 74, 64, 65, 74, 2, 73, 110, + 132, 132, 65, 74, 20, 8, 14, 37, 65, 69, 8, 64, 69, 65, 132, 65, 73, 8, 46, 61, 78, + 69, 81, 65, 72, 15, 8, 65, 79, 132, 63, 68, 65, 69, 74, 81, 8, 87, 82, 73, 8, 65, + 79, 132, 81, 65, 74, 73, 75, 72, 8, 64, 61, 80, 2, 53, 63, 68, 69, 72, 72, 65, 79, + 19, 54, 68, 65, 61, 81, 65, 79, 8, 73, 69, 81, 8, 65, 69, 74, 65, 73, 8, 40, 69, + 74, 74, 61, 68, 73, 65, 62, 65, 81, 79, 61, 67, 65, 8, 83, 75, 74, 8, 110, 62, 65, + 79, 2, 30, 27, 8, 22, 22, 22, 8, 7, 33, 8, 65, 80, 8, 65, 79, 132, 63, 68, 65, + 69, 74, 81, 8, 69, 74, 8, 64, 69, 65, 132, 65, 73, 8, 46, 61, 78, 69, 81, 65, 72, + 8, 75, 68, 74, 65, 8, 84, 65, 132, 65, 74, 81, 3, 2, 72, 69, 63, 68, 65, 8, 36, + 82, 80, 67, 61, 62, 65, 20, 8, 39, 61, 80, 8, 69, 132, 81, 8, 74, 61, 81, 82, 79, + 67, 65, 73, 103, 102, 18, 8, 84, 65, 69, 72, 8, 64, 69, 65, 8, 56, 65, 79, 3, 2, + 87, 69, 74, 132, 82, 74, 67, 8, 64, 65, 79, 8, 53, 82, 73, 73, 65, 8, 70, 61, 8, + 61, 82, 80, 8, 36, 74, 72, 65, 69, 68, 65, 73, 69, 81, 81, 65, 72, 74, 8, 67, 65, + 74, 75, 73, 73, 65, 74, 2, 84, 75, 79, 64, 65, 74, 8, 69, 132, 81, 8, 82, 74, 64, + 8, 64, 69, 65, 8, 56, 65, 79, 87, 69, 74, 132, 82, 74, 67, 8, 132, 69, 63, 68, 8, + 62, 65, 69, 73, 8, 46, 61, 78, 69, 81, 65, 72, 8, 58, 44, 44, 45, 18, 2, 64, 65, + 73, 8, 53, 63, 68, 82, 72, 64, 65, 74, 64, 69, 65, 74, 132, 81, 8, 62, 65, 66, 69, + 74, 64, 65, 81, 20, 8, 7, 57, 65, 74, 74, 8, 53, 69, 65, 8, 74, 82, 74, 18, 8, + 73, 20, 8, 43, 20, 18, 2, 65, 69, 74, 65, 74, 8, 37, 72, 69, 63, 71, 8, 69, 74, + 8, 64, 65, 74, 8, 40, 79, 72, 103, 82, 81, 65, 79, 82, 74, 67, 80, 62, 65, 79, 69, + 63, 68, 81, 8, 81, 82, 74, 8, 84, 75, 72, 72, 65, 74, 6, 18, 8, 132, 75, 2, 68, + 61, 62, 65, 74, 8, 84, 69, 79, 8, 44, 68, 74, 65, 74, 8, 64, 75, 79, 81, 8, 61, + 74, 8, 64, 69, 65, 132, 65, 79, 8, 53, 81, 65, 72, 72, 65, 8, 61, 82, 80, 67, 65, + 79, 65, 63, 68, 74, 65, 81, 18, 2, 64, 61, 102, 8, 64, 61, 80, 8, 53, 63, 68, 69, + 72, 72, 65, 79, 19, 54, 68, 65, 61, 81, 65, 79, 8, 69, 74, 8, 64, 69, 65, 132, 65, + 73, 8, 45, 61, 68, 79, 65, 8, 71, 65, 69, 74, 65, 74, 8, 65, 79, 3, 2, 68, 65, + 62, 72, 69, 63, 68, 65, 74, 8, 60, 82, 132, 63, 68, 82, 102, 8, 65, 79, 66, 75, 79, + 64, 65, 79, 81, 8, 3, 8, 65, 80, 8, 84, 65, 79, 64, 65, 74, 8, 74, 82, 79, 8, + 67, 65, 67, 65, 74, 2, 23, 27, 22, 22, 8, 7, 8, 132, 65, 69, 74, 8, 14, 23, 30, + 18, 27, 29, 8, 31, 18, 8, 83, 69, 65, 72, 72, 65, 69, 63, 68, 81, 8, 61, 82, 63, + 68, 8, 74, 82, 79, 8, 23, 27, 18, 27, 27, 8, 7, 64, 15, 18, 2, 64, 61, 102, 8, + 61, 62, 65, 79, 8, 61, 82, 63, 68, 8, 69, 73, 8, 74, 103, 63, 68, 132, 81, 65, 74, + 8, 45, 61, 68, 79, 65, 8, 23, 30, 31, 29, 18, 8, 23, 30, 31, 30, 18, 8, 23, 30, + 31, 31, 2, 82, 74, 64, 8, 61, 82, 63, 68, 8, 66, 110, 79, 8, 64, 69, 65, 8, 87, + 82, 71, 110, 74, 66, 72, 69, 67, 65, 74, 8, 45, 61, 68, 79, 65, 8, 65, 69, 74, 8, + 65, 79, 68, 109, 68, 81, 65, 79, 8, 60, 82, 3, 2, 132, 63, 68, 82, 102, 8, 65, 79, + 66, 75, 79, 64, 65, 79, 72, 69, 63, 68, 8, 132, 65, 69, 74, 8, 84, 69, 79, 64, 18, + 8, 84, 65, 69, 72, 8, 83, 75, 73, 8, 74, 103, 63, 68, 132, 81, 65, 74, 8, 45, 61, + 68, 79, 65, 2, 61, 62, 8, 87, 82, 73, 8, 65, 79, 132, 81, 65, 74, 73, 61, 72, 8, + 64, 69, 65, 8, 36, 73, 75, 79, 81, 69, 132, 61, 81, 69, 75, 74, 8, 64, 65, 79, 8, + 53, 82, 73, 73, 65, 8, 66, 110, 79, 2, 64, 61, 80, 8, 53, 63, 68, 69, 72, 72, 65, + 79, 19, 54, 68, 65, 61, 81, 65, 79, 8, 61, 82, 66, 87, 82, 84, 65, 74, 64, 65, 74, + 8, 69, 132, 81, 20, 8, 57, 65, 74, 74, 8, 53, 69, 65, 8, 64, 69, 65, 132, 65, 2, + 36, 73, 75, 79, 81, 69, 132, 61, 81, 69, 75, 74, 18, 8, 64, 69, 65, 8, 24, 18, 23, + 22, 18, 8, 62, 65, 81, 79, 103, 67, 81, 18, 8, 62, 65, 79, 110, 63, 71, 132, 69, 63, + 68, 81, 69, 67, 65, 74, 18, 8, 132, 75, 2, 84, 69, 79, 64, 8, 64, 69, 65, 8, 53, + 81, 61, 64, 81, 67, 65, 73, 65, 69, 74, 64, 65, 8, 14, 66, 110, 79, 8, 64, 61, 80, + 8, 53, 63, 68, 69, 72, 72, 65, 79, 19, 54, 68, 65, 61, 81, 65, 79, 15, 8, 69, 73, + 2, 74, 103, 63, 68, 132, 81, 65, 74, 8, 82, 74, 64, 8, 69, 74, 8, 64, 65, 74, 8, + 66, 75, 72, 67, 65, 74, 64, 65, 74, 8, 45, 61, 68, 79, 65, 74, 8, 82, 74, 67, 65, + 66, 103, 68, 79, 2, 25, 27, 22, 22, 22, 8, 7, 8, 87, 82, 87, 82, 132, 63, 68, 69, + 65, 102, 65, 74, 8, 68, 61, 62, 65, 74, 20, 2, 39, 69, 65, 8, 61, 72, 72, 67, 65, + 73, 65, 69, 74, 65, 8, 56, 65, 79, 84, 61, 72, 81, 82, 74, 67, 8, 84, 103, 63, 68, + 132, 81, 8, 69, 74, 8, 64, 65, 73, 132, 65, 72, 62, 65, 74, 2, 48, 61, 102, 65, 18, + 8, 53, 69, 65, 8, 68, 61, 62, 65, 74, 8, 64, 61, 8, 65, 79, 68, 65, 62, 72, 69, + 63, 68, 65, 8, 36, 74, 66, 75, 79, 64, 65, 79, 82, 74, 67, 65, 74, 8, 61, 82, 66, + 2, 64, 65, 73, 8, 42, 65, 62, 69, 65, 81, 65, 8, 64, 65, 79, 8, 37, 65, 132, 75, + 72, 64, 82, 74, 67, 65, 74, 18, 8, 61, 82, 66, 8, 64, 65, 73, 8, 42, 65, 62, 69, + 65, 81, 65, 8, 64, 65, 79, 2, 47, 109, 68, 74, 65, 18, 8, 61, 82, 66, 8, 70, 65, + 67, 72, 69, 63, 68, 65, 74, 8, 61, 74, 64, 65, 79, 65, 74, 8, 42, 65, 62, 69, 65, + 81, 65, 74, 18, 8, 64, 69, 65, 8, 110, 62, 65, 79, 68, 61, 82, 78, 81, 2, 73, 69, + 81, 8, 64, 65, 79, 8, 42, 65, 132, 63, 68, 103, 66, 81, 80, 65, 79, 72, 65, 64, 69, + 67, 82, 74, 67, 8, 69, 74, 8, 64, 65, 79, 8, 61, 72, 72, 67, 65, 73, 65, 69, 74, + 65, 74, 8, 56, 65, 79, 3, 2, 84, 61, 72, 81, 82, 74, 67, 8, 87, 82, 132, 61, 73, + 73, 65, 74, 68, 103, 74, 67, 65, 74, 20, 8, 53, 69, 65, 8, 66, 69, 74, 64, 65, 74, + 8, 61, 82, 63, 68, 8, 69, 74, 8, 64, 69, 65, 132, 65, 73, 2, 45, 61, 68, 79, 65, + 8, 65, 69, 74, 65, 74, 8, 37, 65, 81, 79, 61, 67, 8, 83, 75, 74, 8, 24, 22, 22, + 22, 22, 8, 11, 8, 84, 69, 65, 64, 65, 79, 82, 73, 8, 65, 69, 74, 3, 2, 67, 65, + 132, 81, 65, 72, 72, 81, 8, 66, 110, 79, 8, 64, 69, 65, 8, 71, 110, 74, 132, 81, 72, + 65, 79, 69, 132, 63, 68, 65, 8, 36, 82, 80, 67, 65, 132, 81, 61, 72, 81, 82, 74, 67, + 8, 64, 65, 80, 8, 52, 61, 81, 3, 2, 68, 61, 82, 132, 65, 80, 8, 39, 69, 65, 132, + 65, 79, 8, 37, 65, 81, 79, 61, 67, 8, 68, 61, 81, 81, 65, 8, 69, 73, 8, 83, 75, + 79, 69, 67, 65, 74, 8, 82, 74, 64, 18, 8, 84, 65, 74, 74, 2, 69, 63, 68, 8, 74, + 69, 63, 68, 81, 8, 69, 79, 79, 65, 18, 8, 61, 82, 63, 68, 8, 69, 73, 8, 83, 75, + 79, 83, 75, 79, 69, 67, 65, 74, 8, 45, 61, 68, 79, 65, 8, 84, 65, 132, 65, 74, 81, + 72, 69, 63, 68, 2, 67, 65, 132, 81, 79, 69, 63, 68, 65, 74, 8, 84, 65, 79, 64, 65, + 74, 8, 73, 110, 132, 132, 65, 74, 20, 8, 57, 69, 79, 8, 68, 61, 62, 65, 74, 8, 69, + 74, 8, 64, 69, 65, 132, 65, 73, 8, 45, 61, 68, 79, 65, 2, 84, 69, 65, 64, 65, 79, + 8, 64, 69, 65, 8, 83, 75, 72, 72, 65, 8, 53, 82, 73, 73, 65, 8, 83, 75, 74, 8, + 24, 22, 22, 22, 22, 8, 7, 8, 83, 75, 79, 67, 65, 132, 65, 68, 65, 74, 20, 2, 37, + 65, 69, 8, 64, 69, 65, 132, 65, 73, 8, 46, 61, 78, 69, 81, 65, 72, 8, 69, 132, 81, + 8, 64, 65, 79, 8, 39, 69, 80, 78, 75, 132, 69, 81, 69, 75, 74, 80, 66, 75, 74, 64, + 80, 8, 70, 61, 2, 69, 73, 73, 65, 79, 8, 65, 69, 74, 8, 82, 73, 132, 81, 79, 69, + 81, 81, 65, 74, 65, 79, 8, 51, 82, 74, 71, 81, 20, 8, 40, 79, 8, 69, 132, 81, 8, + 61, 82, 66, 8, 27, 27, 22, 8, 22, 22, 22, 8, 0, 135, 18, 2, 62, 82, 79, 67, 65, + 79, 8, 37, 79, 82, 63, 71, 65, 18, 8, 61, 74, 8, 64, 61, 80, 8, 57, 75, 68, 74, + 68, 61, 82, 80, 8, 57, 75, 79, 74, 73, 132, 65, 79, 132, 81, 79, 61, 102, 65, 8, 23, + 23, 18, 2, 75, 74, 8, 64, 61, 80, 8, 53, 63, 68, 69, 72, 72, 65, 79, 19, 54, 68, + 65, 61, 81, 65, 79, 18, 8, 61, 74, 8, 64, 69, 65, 8, 40, 79, 84, 65, 69, 81, 65, + 79, 82, 74, 67, 80, 62, 61, 82, 81, 65, 74, 2, 61, 73, 8, 46, 79, 61, 74, 71, 65, + 74, 68, 61, 82, 80, 18, 8, 61, 74, 8, 64, 69, 65, 8, 46, 61, 69, 132, 65, 79, 64, + 61, 73, 73, 62, 79, 110, 63, 71, 65, 20, 2, 40, 69, 74, 8, 51, 82, 74, 71, 81, 8, + 69, 132, 81, 8, 62, 65, 69, 73, 8, 46, 61, 78, 69, 81, 65, 72, 8, 7, 53, 63, 68, + 82, 72, 64, 65, 74, 83, 65, 79, 84, 61, 72, 81, 82, 74, 67, 6, 2, 87, 82, 8, 65, + 79, 84, 103, 68, 74, 65, 74, 18, 8, 64, 65, 79, 8, 66, 110, 79, 8, 53, 69, 65, 8, + 64, 65, 74, 8, 53, 63, 68, 72, 82, 102, 8, 87, 82, 72, 103, 102, 81, 18, 8, 64, 61, + 102, 2, 84, 69, 79, 8, 82, 74, 80, 8, 83, 75, 74, 8, 74, 65, 82, 65, 73, 8, 14, + 73, 69, 81, 8, 36, 74, 72, 65, 69, 68, 65, 67, 65, 64, 61, 74, 71, 65, 74, 15, 8, + 81, 79, 61, 67, 65, 74, 2, 73, 110, 132, 132, 65, 74, 20, 8, 53, 69, 65, 8, 66, 69, + 74, 64, 65, 74, 8, 74, 103, 73, 72, 69, 63, 68, 8, 30, 22, 8, 22, 22, 22, 8, 66, + 110, 79, 8, 64, 65, 74, 8, 39, 79, 82, 63, 71, 2, 82, 74, 64, 8, 64, 65, 74, 8, + 53, 81, 65, 73, 78, 65, 72, 8, 65, 69, 74, 65, 79, 8, 74, 65, 82, 65, 74, 8, 36, + 74, 72, 65, 69, 68, 65, 18, 8, 64, 69, 65, 8, 132, 69, 63, 68, 8, 69, 74, 2, 37, + 65, 61, 79, 62, 65, 69, 81, 82, 74, 67, 8, 62, 65, 132, 69, 74, 64, 65, 81, 18, 8, + 65, 69, 74, 67, 65, 132, 81, 65, 72, 72, 81, 8, 82, 74, 64, 8, 84, 75, 79, 110, 62, + 65, 79, 8, 53, 69, 65, 2, 65, 69, 74, 65, 8, 62, 65, 132, 75, 74, 64, 65, 79, 65, + 8, 56, 75, 79, 72, 61, 67, 65, 8, 69, 74, 8, 74, 103, 63, 68, 132, 81, 65, 79, 8, + 75, 64, 65, 79, 8, 69, 74, 8, 66, 65, 79, 74, 65, 79, 65, 79, 2, 60, 65, 69, 81, + 8, 65, 79, 68, 61, 72, 81, 65, 74, 8, 84, 65, 79, 64, 65, 74, 20, 2, 26, 23, 22, + 22, 8, 7, 18, 8, 27, 30, 28, 24, 8, 18, 8, 27, 25, 24, 27, 8, 18, 8, 29, 30, + 8, 20, 8, 31, 30, 27, 18, 8, 27, 24, 24, 18, 8, 23, 24, 22, 22, 8, 20, 2, 23, + 30, 22, 23, 18, 8, 23, 25, 22, 24, 18, 8, 23, 30, 22, 25, 18, 8, 14, 23, 30, 22, + 26, 18, 8, 23, 30, 22, 27, 18, 8, 23, 22, 22, 28, 15, 18, 8, 23, 27, 22, 29, 18, + 8, 23, 30, 22, 30, 18, 8, 23, 30, 22, 31, 18, 8, 23, 30, 23, 22, 18, 2, 23, 30, + 23, 23, 18, 8, 23, 27, 23, 24, 18, 8, 23, 30, 23, 25, 18, 8, 23, 30, 23, 26, 18, + 8, 23, 30, 23, 27, 18, 8, 23, 30, 23, 28, 18, 8, 23, 27, 23, 29, 18, 8, 23, 30, + 23, 30, 18, 8, 23, 26, 23, 31, 18, 8, 23, 28, 24, 22, 18, 2, 23, 26, 24, 23, 18, + 8, 23, 30, 24, 24, 18, 8, 23, 27, 24, 25, 8, 7, 18, 8, 23, 24, 24, 26, 18, 8, + 23, 30, 24, 27, 18, 8, 23, 30, 24, 28, 18, 8, 23, 26, 24, 29, 18, 8, 23, 26, 24, + 30, 18, 8, 23, 30, 24, 31, 8, 18, 8, 23, 28, 25, 22, 18, 2, 23, 26, 25, 23, 18, + 8, 23, 25, 25, 24, 18, 8, 23, 30, 25, 25, 18, 8, 23, 30, 25, 26, 18, 8, 23, 30, + 25, 27, 18, 8, 23, 30, 25, 28, 18, 8, 23, 30, 25, 29, 18, 8, 23, 28, 25, 30, 8, + 7, 18, 8, 23, 28, 25, 31, 18, 8, 23, 30, 26, 22, 18, 2, 23, 30, 26, 23, 18, 8, + 23, 25, 26, 24, 18, 8, 23, 30, 26, 25, 18, 8, 23, 22, 30, 26, 26, 18, 8, 23, 28, + 26, 27, 8, 18, 8, 23, 30, 26, 28, 18, 8, 23, 30, 26, 29, 18, 8, 23, 30, 26, 30, + 18, 8, 23, 30, 26, 31, 18, 8, 23, 30, 27, 22, 18, 2, 23, 30, 27, 23, 18, 8, 23, + 22, 27, 24, 18, 8, 23, 25, 27, 25, 18, 8, 23, 22, 27, 26, 18, 8, 23, 30, 27, 27, + 18, 8, 23, 30, 27, 28, 18, 8, 23, 30, 27, 29, 18, 8, 23, 30, 27, 30, 18, 8, 23, + 30, 27, 31, 18, 8, 23, 29, 28, 22, 18, 2, 23, 26, 28, 23, 18, 8, 23, 22, 28, 24, + 18, 8, 23, 30, 28, 25, 18, 8, 23, 30, 28, 26, 18, 8, 23, 30, 28, 27, 18, 8, 23, + 30, 28, 28, 18, 8, 23, 28, 28, 29, 18, 8, 23, 29, 28, 30, 18, 8, 23, 29, 28, 31, + 18, 8, 23, 30, 29, 22, 18, 2, 23, 22, 29, 23, 8, 18, 8, 23, 30, 29, 24, 8, 18, + 8, 23, 30, 29, 25, 18, 8, 23, 30, 29, 26, 18, 8, 23, 29, 29, 27, 18, 8, 23, 22, + 29, 28, 18, 8, 23, 30, 29, 29, 18, 8, 23, 30, 29, 30, 18, 8, 23, 30, 29, 31, 18, + 8, 23, 30, 30, 22, 18, 2, 23, 25, 30, 23, 18, 8, 23, 24, 30, 24, 18, 8, 23, 30, + 30, 25, 18, 8, 23, 30, 30, 26, 18, 8, 23, 30, 30, 27, 18, 8, 23, 22, 22, 28, 18, + 8, 23, 25, 30, 29, 18, 8, 23, 30, 30, 30, 18, 8, 23, 30, 30, 31, 18, 8, 23, 30, + 31, 22, 18, 2, 23, 30, 31, 23, 18, 8, 23, 24, 31, 24, 18, 8, 23, 26, 31, 25, 18, + 8, 23, 30, 31, 26, 8, 7, 18, 8, 23, 22, 31, 27, 18, 8, 23, 30, 31, 28, 18, 8, + 23, 30, 31, 29, 18, 8, 23, 30, 31, 30, 18, 8, 23, 30, 31, 31, 18, 8, 23, 31, 22, + 22, 8, 18, 2, 54, 65, 82, 65, 79, 82, 74, 67, 80, 87, 82, 72, 61, 67, 65, 8, 61, + 62, 67, 65, 72, 65, 68, 74, 81, 8, 68, 61, 81, 20, 8, 14, 40, 69, 74, 73, 61, 72, + 8, 65, 79, 71, 72, 103, 79, 81, 8, 64, 65, 79, 2, 48, 61, 67, 69, 132, 81, 79, 61, + 81, 18, 8, 64, 61, 102, 8, 64, 69, 65, 8, 54, 65, 82, 65, 79, 82, 74, 67, 8, 70, + 61, 8, 64, 75, 63, 68, 8, 62, 61, 72, 64, 8, 83, 75, 79, 110, 62, 65, 79, 3, 2, + 67, 65, 68, 65, 74, 8, 84, 69, 79, 64, 33, 15, 8, 64, 61, 74, 74, 8, 84, 69, 65, + 64, 65, 79, 8, 71, 61, 74, 74, 8, 65, 79, 8, 132, 69, 63, 68, 8, 64, 65, 79, 8, + 48, 65, 69, 74, 82, 74, 67, 2, 44, 63, 68, 8, 68, 103, 81, 81, 65, 8, 64, 61, 74, + 74, 8, 74, 75, 63, 68, 8, 71, 82, 79, 87, 8, 64, 61, 80, 8, 46, 61, 78, 69, 81, + 65, 72, 8, 64, 65, 79, 8, 83, 65, 79, 3, 2, 132, 63, 68, 69, 65, 64, 65, 74, 65, + 74, 8, 40, 69, 74, 74, 61, 68, 73, 65, 74, 8, 82, 74, 64, 8, 36, 82, 80, 67, 61, + 62, 65, 74, 8, 87, 82, 8, 65, 79, 84, 103, 68, 74, 65, 74, 2, 82, 74, 64, 8, 73, + 109, 63, 68, 81, 65, 8, 64, 61, 8, 74, 82, 79, 8, 65, 69, 74, 65, 74, 8, 51, 82, + 74, 71, 81, 8, 68, 65, 79, 61, 82, 80, 67, 79, 65, 69, 66, 65, 74, 32, 8, 64, 61, + 80, 2, 132, 69, 74, 64, 8, 64, 69, 65, 8, 25, 24, 22, 22, 8, 7, 20, 8, 39, 69, + 65, 8, 51, 79, 75, 83, 69, 74, 87, 69, 61, 72, 61, 62, 67, 61, 62, 65, 74, 2, 132, + 69, 74, 64, 8, 69, 74, 8, 64, 69, 65, 132, 65, 73, 8, 45, 61, 68, 79, 65, 8, 132, + 65, 68, 79, 8, 65, 79, 68, 65, 62, 72, 69, 63, 68, 8, 67, 65, 132, 81, 69, 65, 67, + 65, 74, 20, 8, 57, 69, 79, 2, 68, 61, 62, 65, 74, 8, 23, 23, 22, 8, 22, 22, 22, + 8, 67, 65, 67, 65, 74, 8, 64, 61, 80, 8, 56, 75, 79, 132, 61, 68, 79, 8, 73, 65, + 68, 79, 8, 65, 69, 74, 67, 65, 3, 2, 132, 81, 65, 72, 72, 81, 20, 8, 36, 62, 65, + 79, 18, 8, 73, 65, 69, 74, 65, 8, 43, 65, 79, 79, 65, 74, 18, 8, 84, 69, 79, 8, + 84, 65, 79, 64, 65, 74, 8, 73, 69, 81, 8, 64, 69, 65, 132, 65, 73, 2, 37, 65, 81, + 79, 61, 67, 65, 8, 74, 69, 63, 68, 81, 8, 79, 65, 69, 63, 68, 65, 74, 18, 8, 84, + 69, 79, 8, 84, 65, 79, 64, 65, 74, 8, 74, 75, 63, 68, 8, 28, 27, 8, 22, 22, 22, + 8, 11, 2, 87, 82, 72, 65, 67, 65, 74, 8, 73, 110, 132, 132, 65, 74, 20, 8, 40, 80, + 8, 69, 132, 81, 8, 64, 61, 80, 8, 65, 69, 74, 73, 61, 72, 8, 64, 61, 64, 82, 79, + 63, 68, 8, 68, 65, 79, 62, 65, 69, 3, 2, 67, 65, 66, 110, 68, 79, 81, 8, 84, 75, + 79, 64, 65, 74, 18, 8, 64, 61, 102, 8, 65, 69, 74, 65, 8, 74, 65, 82, 65, 8, 37, + 65, 79, 65, 63, 68, 74, 82, 74, 67, 8, 65, 69, 74, 67, 65, 81, 79, 65, 81, 65, 74, + 2, 69, 132, 81, 18, 8, 82, 74, 64, 8, 87, 84, 65, 69, 81, 65, 74, 80, 8, 64, 61, + 64, 82, 79, 63, 68, 18, 8, 64, 61, 102, 8, 64, 69, 65, 8, 38, 68, 61, 82, 132, 132, + 65, 65, 62, 61, 82, 78, 79, 103, 73, 69, 65, 74, 2, 69, 74, 8, 64, 65, 79, 8, 51, + 79, 75, 83, 69, 74, 87, 8, 84, 65, 132, 65, 74, 81, 72, 69, 63, 68, 8, 65, 79, 68, + 109, 68, 81, 8, 84, 75, 79, 64, 65, 74, 8, 132, 69, 74, 64, 8, 82, 74, 64, 2, 84, + 69, 79, 8, 69, 74, 66, 75, 72, 67, 65, 64, 65, 132, 132, 65, 74, 8, 65, 79, 68, 109, + 68, 81, 65, 8, 37, 65, 69, 81, 79, 103, 67, 65, 8, 87, 82, 8, 87, 61, 68, 72, 65, + 74, 8, 68, 61, 62, 65, 74, 20, 2, 39, 69, 65, 8, 36, 74, 64, 65, 79, 82, 74, 67, + 8, 64, 65, 79, 8, 37, 65, 79, 65, 63, 68, 74, 82, 74, 67, 8, 69, 132, 81, 8, 64, + 61, 68, 69, 74, 8, 65, 79, 66, 75, 72, 67, 81, 18, 8, 64, 61, 102, 18, 2, 84, 103, + 68, 79, 65, 74, 64, 8, 69, 74, 8, 66, 79, 110, 68, 65, 79, 65, 74, 8, 45, 61, 68, + 79, 65, 74, 8, 64, 61, 80, 8, 53, 81, 65, 82, 65, 79, 132, 75, 72, 72, 8, 14, 64, + 65, 80, 8, 61, 62, 3, 2, 67, 65, 72, 61, 82, 66, 65, 74, 65, 74, 8, 52, 65, 63, + 68, 74, 82, 74, 67, 80, 70, 61, 68, 79, 65, 80, 15, 8, 64, 65, 66, 69, 74, 69, 81, + 69, 83, 8, 61, 72, 80, 8, 42, 79, 82, 74, 64, 72, 61, 67, 65, 2, 132, 65, 74, 75, + 73, 73, 65, 74, 8, 84, 82, 79, 64, 65, 18, 8, 70, 65, 81, 87, 81, 8, 64, 61, 80, + 8, 53, 81, 65, 82, 65, 79, 132, 75, 72, 72, 18, 8, 64, 61, 80, 8, 61, 73, 8, 23, + 20, 2, 73, 82, 61, 79, 8, 83, 75, 79, 8, 64, 65, 73, 8, 40, 81, 61, 81, 80, 70, + 61, 68, 79, 65, 8, 83, 75, 79, 68, 61, 74, 64, 65, 74, 8, 69, 132, 81, 18, 8, 87, + 82, 8, 42, 79, 82, 74, 64, 65, 2, 67, 65, 72, 65, 67, 81, 8, 84, 69, 79, 64, 20, + 8, 57, 103, 68, 79, 65, 74, 64, 8, 61, 72, 132, 75, 8, 66, 110, 79, 8, 64, 61, 80, + 8, 45, 61, 68, 79, 8, 23, 31, 22, 29, 8, 62, 65, 69, 2, 64, 65, 73, 8, 61, 72, + 81, 65, 74, 8, 48, 75, 64, 82, 80, 8, 74, 61, 63, 68, 8, 64, 65, 73, 8, 53, 81, + 65, 82, 65, 79, 132, 75, 72, 72, 8, 14, 83, 75, 73, 8, 25, 23, 20, 8, 48, 103, 79, + 87, 2, 23, 31, 22, 30, 15, 8, 67, 65, 79, 65, 63, 68, 74, 65, 81, 8, 84, 75, 79, + 64, 65, 74, 8, 84, 103, 79, 65, 18, 8, 84, 69, 79, 64, 8, 69, 74, 8, 64, 69, 65, + 132, 65, 73, 8, 45, 61, 68, 79, 65, 2, 74, 61, 63, 68, 8, 64, 65, 73, 8, 53, 81, + 61, 81, 82, 80, 8, 83, 75, 73, 8, 23, 20, 8, 45, 61, 74, 82, 61, 79, 8, 23, 31, + 22, 29, 8, 67, 65, 79, 65, 63, 68, 74, 65, 81, 20, 2, 39, 61, 64, 82, 79, 63, 68, + 8, 84, 69, 79, 64, 8, 132, 63, 68, 75, 74, 8, 14, 75, 68, 74, 65, 8, 84, 65, 69, + 81, 65, 79, 65, 80, 15, 8, 65, 69, 74, 65, 8, 40, 79, 68, 109, 68, 82, 74, 67, 8, + 64, 65, 80, 2, 51, 79, 75, 87, 65, 74, 81, 132, 61, 81, 87, 65, 80, 8, 62, 65, 64, + 69, 74, 67, 81, 18, 8, 84, 65, 69, 72, 8, 70, 61, 8, 69, 73, 8, 74, 103, 63, 68, + 132, 81, 65, 74, 8, 45, 61, 68, 79, 65, 8, 75, 68, 74, 65, 2, 46, 8, 64, 61, 80, + 8, 53, 81, 65, 82, 65, 79, 132, 75, 72, 72, 8, 27, 26, 8, 22, 22, 8, 74, 75, 63, + 68, 8, 67, 65, 84, 61, 63, 68, 132, 65, 74, 8, 132, 65, 69, 74, 8, 84, 110, 79, 64, + 65, 20, 2, 72, 82, 102, 65, 79, 64, 65, 73, 8, 132, 69, 74, 64, 8, 61, 62, 65, 79, + 8, 74, 75, 63, 68, 8, 64, 69, 65, 8, 51, 79, 75, 83, 69, 74, 87, 69, 61, 72, 61, + 62, 67, 61, 62, 65, 74, 8, 65, 79, 3, 2, 36, 82, 63, 68, 8, 64, 69, 65, 80, 8, + 65, 73, 78, 66, 69, 65, 68, 72, 81, 8, 132, 69, 63, 68, 8, 67, 61, 74, 87, 8, 83, + 75, 74, 8, 132, 65, 72, 81, 132, 81, 20, 8, 37, 65, 69, 2, 64, 65, 74, 8, 68, 65, + 82, 81, 69, 67, 65, 74, 8, 67, 65, 132, 81, 65, 69, 67, 65, 79, 81, 65, 74, 8, 36, + 74, 132, 78, 79, 110, 63, 68, 65, 74, 8, 61, 74, 8, 70, 82, 74, 67, 65, 8, 47, 65, + 82, 81, 65, 18, 2, 64, 69, 65, 8, 69, 73, 8, 37, 82, 63, 68, 68, 61, 72, 81, 65, + 79, 132, 81, 61, 74, 64, 65, 18, 8, 110, 62, 65, 79, 68, 61, 82, 78, 81, 8, 69, 73, + 8, 46, 61, 82, 66, 73, 61, 74, 74, 80, 66, 61, 63, 68, 2, 66, 75, 79, 81, 71, 75, + 73, 73, 65, 74, 8, 84, 75, 72, 72, 65, 74, 18, 8, 84, 69, 79, 64, 8, 64, 69, 65, + 8, 46, 65, 74, 74, 81, 74, 69, 80, 8, 64, 65, 79, 8, 53, 63, 68, 79, 65, 69, 62, + 3, 2, 73, 61, 132, 63, 68, 69, 74, 65, 74, 81, 65, 63, 68, 74, 69, 71, 8, 75, 68, + 74, 65, 8, 84, 65, 69, 81, 65, 79, 65, 80, 8, 83, 65, 79, 72, 61, 74, 67, 81, 20, + 8, 44, 63, 68, 8, 62, 69, 81, 81, 65, 2, 53, 69, 65, 18, 8, 61, 82, 63, 68, 8, + 64, 69, 65, 132, 65, 73, 8, 36, 74, 81, 79, 61, 67, 65, 8, 87, 82, 87, 82, 132, 81, + 69, 73, 73, 65, 74, 20, 2, 44, 73, 8, 40, 81, 61, 81, 80, 61, 82, 80, 132, 63, 68, + 82, 102, 8, 84, 82, 79, 64, 65, 8, 66, 65, 79, 74, 65, 79, 8, 61, 74, 67, 65, 79, + 65, 67, 81, 18, 8, 69, 74, 2, 64, 65, 79, 8, 37, 65, 132, 75, 72, 64, 82, 74, 67, + 80, 64, 69, 65, 74, 132, 81, 75, 79, 64, 74, 82, 74, 67, 8, 14, 69, 74, 8, 46, 61, + 78, 69, 81, 65, 72, 8, 44, 44, 45, 18, 8, 91, 8, 31, 15, 8, 64, 69, 65, 2, 53, + 65, 71, 79, 65, 81, 61, 79, 69, 61, 81, 80, 132, 81, 65, 72, 72, 65, 8, 66, 110, 79, + 8, 64, 69, 65, 8, 46, 82, 74, 132, 81, 67, 65, 84, 65, 79, 62, 65, 18, 8, 82, 74, + 64, 8, 43, 61, 74, 64, 3, 2, 84, 65, 79, 71, 65, 79, 132, 63, 68, 82, 72, 65, 8, + 73, 69, 81, 8, 64, 65, 74, 8, 53, 81, 65, 72, 72, 65, 74, 8, 64, 65, 79, 8, 67, + 65, 78, 79, 110, 66, 81, 65, 74, 8, 53, 65, 71, 79, 65, 81, 103, 79, 65, 2, 67, 72, + 65, 69, 63, 68, 87, 82, 66, 81, 65, 72, 72, 65, 74, 20, 8, 39, 69, 65, 132, 65, 79, + 8, 36, 74, 81, 79, 61, 67, 8, 25, 25, 18, 25, 8, 29, 8, 69, 132, 81, 8, 61, 62, + 65, 79, 8, 83, 75, 73, 8, 40, 81, 61, 81, 80, 3, 2, 61, 82, 80, 132, 63, 68, 82, + 102, 8, 61, 62, 67, 65, 72, 65, 68, 74, 81, 8, 84, 75, 79, 64, 65, 74, 20, 2, 14, + 53, 81, 61, 64, 81, 132, 63, 68, 82, 72, 79, 61, 81, 8, 39, 79, 20, 8, 49, 65, 82, + 66, 65, 79, 81, 15, 32, 8, 7, 39, 65, 79, 8, 42, 65, 64, 61, 74, 71, 65, 18, 8, + 69, 74, 2, 64, 65, 79, 8, 41, 75, 79, 81, 62, 69, 72, 64, 82, 74, 67, 80, 132, 63, + 68, 82, 72, 65, 6, 18, 8, 82, 74, 64, 8, 87, 84, 61, 79, 8, 69, 74, 8, 64, 65, + 79, 8, 41, 75, 79, 81, 3, 2, 62, 69, 72, 64, 82, 74, 67, 80, 132, 63, 68, 82, 72, + 65, 8, 66, 110, 79, 8, 73, 103, 74, 74, 72, 69, 63, 68, 65, 8, 51, 65, 79, 132, 75, + 74, 65, 74, 18, 8, 37, 110, 79, 67, 65, 79, 71, 82, 74, 64, 65, 2, 82, 74, 64, 8, + 47, 65, 62, 65, 74, 80, 71, 82, 74, 64, 65, 8, 65, 69, 74, 87, 82, 66, 110, 68, 79, + 65, 74, 18, 8, 65, 74, 81, 132, 78, 79, 69, 63, 68, 81, 8, 64, 82, 79, 63, 68, 61, + 82, 80, 2, 64, 65, 74, 8, 40, 79, 84, 103, 67, 82, 74, 67, 65, 74, 18, 8, 64, 69, + 65, 8, 69, 73, 8, 7, 48, 61, 67, 69, 132, 81, 79, 61, 81, 6, 8, 132, 63, 68, 75, + 74, 8, 132, 65, 69, 81, 8, 72, 61, 74, 67, 65, 79, 2, 60, 65, 69, 81, 8, 67, 65, + 78, 66, 72, 75, 67, 65, 74, 8, 84, 75, 79, 64, 65, 74, 8, 132, 69, 74, 64, 20, 8, + 40, 80, 8, 132, 69, 74, 64, 8, 62, 69, 80, 68, 65, 79, 8, 132, 63, 68, 75, 74, 2, + 46, 61, 78, 69, 81, 65, 72, 8, 61, 82, 80, 8, 64, 69, 65, 132, 65, 74, 8, 42, 65, + 62, 69, 65, 81, 65, 74, 8, 69, 73, 8, 64, 65, 82, 81, 132, 63, 68, 65, 74, 8, 55, + 74, 81, 65, 79, 79, 69, 63, 68, 81, 2, 73, 69, 81, 8, 64, 82, 79, 63, 68, 67, 65, + 74, 75, 73, 73, 65, 74, 8, 84, 75, 79, 64, 65, 74, 18, 8, 82, 74, 64, 8, 83, 75, + 79, 8, 65, 69, 74, 65, 73, 8, 45, 61, 68, 79, 65, 2, 81, 79, 61, 81, 65, 74, 8, + 84, 69, 79, 8, 64, 65, 73, 8, 42, 65, 64, 61, 74, 71, 65, 74, 8, 74, 61, 68, 65, + 18, 8, 65, 69, 74, 65, 74, 8, 84, 61, 68, 72, 66, 79, 65, 69, 65, 74, 2, 46, 82, + 79, 132, 82, 80, 8, 61, 82, 80, 132, 63, 68, 72, 69, 65, 102, 72, 69, 63, 68, 8, 66, + 110, 79, 8, 37, 110, 79, 67, 65, 79, 19, 8, 82, 74, 64, 8, 47, 65, 62, 65, 74, 80, + 71, 82, 74, 64, 65, 2, 65, 69, 74, 87, 82, 66, 110, 68, 79, 65, 74, 20, 8, 40, 80, + 8, 69, 132, 81, 8, 64, 61, 73, 61, 72, 80, 8, 64, 65, 79, 8, 7, 39, 69, 79, 65, + 71, 81, 75, 79, 8, 64, 65, 79, 8, 36, 74, 132, 81, 61, 72, 81, 6, 2, 62, 65, 61, + 82, 66, 81, 79, 61, 67, 81, 8, 84, 75, 79, 64, 65, 74, 18, 8, 65, 69, 74, 65, 74, + 8, 47, 65, 68, 79, 78, 72, 61, 74, 8, 64, 61, 66, 110, 79, 8, 61, 82, 80, 87, 82, + 61, 79, 62, 65, 69, 81, 65, 74, 20, 2, 39, 65, 79, 132, 65, 72, 62, 65, 8, 72, 69, + 65, 67, 81, 8, 132, 65, 69, 81, 8, 71, 82, 79, 87, 65, 73, 8, 69, 74, 8, 64, 65, + 74, 8, 84, 65, 132, 65, 74, 81, 72, 69, 63, 68, 65, 74, 8, 40, 72, 65, 73, 65, 74, + 81, 65, 74, 2, 83, 75, 79, 18, 8, 82, 74, 64, 8, 69, 63, 68, 8, 67, 72, 61, 82, + 62, 65, 8, 74, 69, 63, 68, 81, 18, 8, 64, 61, 102, 8, 83, 75, 74, 8, 132, 65, 69, + 81, 65, 74, 8, 64, 65, 80, 8, 23, 24, 27, 22, 11, 11, 2, 29, 25, 28, 18, 25, 30, + 8, 18, 8, 28, 22, 22, 8, 7, 18, 8, 29, 25, 28, 31, 8, 82, 74, 64, 8, 84, 65, + 69, 81, 65, 79, 8, 30, 22, 22, 8, 18, 8, 24, 27, 22, 22, 18, 25, 22, 8, 36, 20, + 2, 14, 91, 53, 8, 23, 8, 62, 69, 80, 8, 23, 23, 18, 8, 53, 8, 23, 24, 8, 82, + 74, 64, 8, 91, 8, 30, 31, 15, 2, 48, 61, 67, 69, 132, 81, 79, 61, 81, 80, 8, 65, + 81, 84, 61, 80, 8, 64, 61, 67, 65, 67, 65, 74, 8, 65, 69, 74, 67, 65, 84, 65, 74, + 64, 65, 81, 8, 84, 65, 79, 64, 65, 74, 8, 71, 109, 74, 74, 81, 65, 20, 8, 30, 31, + 31, 2, 36, 82, 63, 68, 8, 69, 74, 8, 64, 65, 79, 8, 41, 75, 79, 81, 62, 69, 72, + 64, 82, 74, 67, 80, 132, 63, 68, 82, 72, 65, 8, 66, 110, 79, 8, 48, 103, 64, 63, 68, + 65, 74, 8, 68, 61, 62, 65, 74, 2, 84, 69, 79, 8, 62, 65, 79, 65, 69, 81, 80, 8, + 55, 74, 81, 65, 79, 79, 69, 63, 68, 81, 8, 69, 74, 8, 7, 37, 110, 79, 67, 65, 79, + 19, 8, 82, 74, 64, 8, 47, 65, 62, 65, 74, 80, 71, 82, 74, 64, 65, 6, 2, 65, 69, + 74, 67, 65, 66, 110, 68, 79, 81, 20, 8, 43, 69, 65, 79, 8, 68, 61, 62, 65, 74, 8, + 84, 69, 79, 8, 132, 69, 65, 8, 75, 79, 67, 61, 74, 69, 132, 63, 68, 8, 73, 69, 81, + 8, 64, 65, 73, 2, 64, 65, 82, 81, 132, 63, 68, 65, 74, 8, 55, 74, 81, 65, 79, 79, + 69, 63, 68, 81, 8, 87, 82, 8, 83, 65, 79, 62, 69, 74, 64, 65, 74, 8, 67, 65, 132, + 82, 63, 68, 81, 18, 8, 64, 65, 79, 8, 69, 74, 2, 65, 81, 84, 61, 80, 8, 61, 74, + 64, 65, 79, 65, 79, 8, 57, 65, 69, 132, 65, 18, 8, 84, 65, 74, 69, 67, 65, 79, 8, + 61, 62, 68, 103, 74, 67, 69, 67, 8, 83, 75, 74, 8, 64, 65, 79, 2, 57, 65, 74, 74, + 8, 61, 72, 132, 75, 8, 55, 74, 81, 65, 79, 79, 69, 63, 68, 81, 8, 69, 73, 8, 7, + 48, 61, 132, 63, 68, 69, 74, 65, 74, 3, 2, 132, 63, 68, 79, 65, 69, 62, 65, 74, 6, + 8, 65, 79, 81, 65, 69, 72, 81, 8, 84, 65, 79, 64, 65, 74, 8, 132, 75, 72, 72, 18, + 8, 64, 61, 74, 74, 8, 73, 82, 102, 8, 61, 82, 63, 68, 2, 42, 65, 72, 65, 67, 65, + 74, 68, 65, 69, 81, 8, 83, 75, 79, 68, 61, 74, 64, 65, 74, 8, 132, 65, 69, 74, 18, + 8, 67, 72, 65, 69, 63, 68, 87, 65, 69, 81, 69, 67, 8, 73, 69, 74, 64, 65, 132, 81, + 65, 74, 80, 2, 23, 22, 8, 29, 8, 64, 65, 79, 8, 53, 63, 68, 110, 72, 65, 79, 8, + 87, 82, 8, 62, 65, 132, 63, 68, 103, 66, 81, 69, 67, 65, 74, 33, 8, 132, 75, 74, 132, + 81, 8, 69, 132, 81, 8, 65, 80, 8, 87, 82, 8, 81, 65, 82, 65, 79, 20, 2, 57, 65, + 74, 74, 8, 53, 69, 65, 8, 61, 62, 65, 79, 8, 73, 65, 69, 74, 65, 74, 8, 132, 75, + 72, 72, 81, 65, 74, 18, 8, 64, 61, 102, 8, 64, 69, 65, 132, 65, 2, 48, 61, 132, 63, + 68, 69, 74, 65, 74, 8, 74, 82, 79, 8, 61, 82, 66, 67, 65, 132, 81, 65, 72, 72, 81, + 8, 84, 65, 79, 64, 65, 74, 8, 132, 75, 72, 72, 65, 74, 18, 8, 64, 61, 73, 69, 81, + 2, 68, 69, 65, 79, 8, 82, 74, 64, 8, 64, 61, 8, 73, 61, 72, 8, 70, 65, 73, 61, + 74, 64, 8, 64, 61, 79, 61, 82, 66, 8, 110, 62, 81, 18, 8, 132, 75, 8, 68, 61, 62, + 65, 8, 69, 63, 68, 2, 61, 82, 63, 68, 8, 64, 61, 67, 65, 67, 65, 74, 8, 72, 65, + 62, 68, 61, 66, 81, 65, 8, 37, 65, 64, 65, 74, 71, 65, 74, 20, 8, 40, 69, 74, 8, + 132, 75, 72, 63, 68, 65, 80, 8, 101, 62, 65, 74, 2, 61, 82, 66, 8, 65, 69, 74, 65, + 79, 8, 71, 75, 132, 81, 132, 78, 69, 65, 72, 69, 67, 65, 74, 8, 48, 61, 132, 63, 68, + 69, 74, 65, 8, 14, 66, 110, 79, 8, 23, 27, 8, 62, 69, 80, 8, 23, 28, 70, 103, 68, + 79, 69, 67, 65, 15, 2, 79, 82, 74, 64, 8, 25, 22, 8, 31, 22, 18, 8, 73, 61, 74, + 63, 68, 73, 61, 72, 8, 61, 62, 65, 79, 8, 61, 82, 63, 68, 8, 74, 69, 63, 68, 81, + 8, 84, 65, 74, 69, 67, 65, 79, 8, 61, 72, 132, 8, 24, 26, 18, 25, 25, 21, 27, 2, + 83, 75, 74, 8, 25, 31, 31, 31, 8, 7, 18, 8, 65, 79, 67, 65, 62, 65, 74, 8, 26, + 22, 22, 22, 8, 20, 8, 39, 61, 79, 110, 62, 65, 79, 8, 80, 69, 74, 64, 8, 87, 82, + 8, 74, 65, 74, 74, 65, 74, 8, 27, 22, 11, 21, 2, 64, 65, 79, 8, 48, 103, 64, 63, + 68, 65, 74, 18, 8, 24, 25, 8, 31, 21, 8, 64, 65, 79, 8, 45, 110, 74, 67, 72, 69, + 74, 67, 65, 18, 8, 67, 61, 79, 8, 29, 27, 18, 27, 8, 126, 8, 64, 65, 79, 8, 36, + 64, 72, 69, 67, 65, 74, 2, 82, 74, 64, 8, 73, 65, 68, 79, 8, 79, 82, 74, 64, 8, + 30, 30, 18, 27, 26, 8, 22, 8, 64, 65, 79, 8, 51, 66, 65, 79, 64, 65, 20, 8, 36, + 82, 63, 68, 8, 23, 24, 8, 31, 15, 22, 8, 64, 65, 79, 8, 56, 61, 63, 68, 81, 65, + 74, 8, 69, 73, 8, 56, 65, 73, 65, 74, 18, 2, 29, 28, 8, 29, 8, 64, 65, 79, 8, + 49, 65, 84, 8, 56, 75, 79, 71, 65, 79, 18, 8, 23, 23, 22, 11, 8, 83, 75, 73, 8, + 56, 65, 79, 65, 84, 61, 74, 18, 8, 61, 82, 63, 68, 8, 61, 82, 80, 8, 57, 78, 65, + 79, 74, 8, 71, 61, 73, 65, 74, 2, 26, 26, 8, 29, 11, 18, 8, 84, 75, 62, 65, 69, + 8, 26, 22, 18, 29, 27, 8, 22, 22, 8, 64, 65, 79, 8, 48, 69, 81, 67, 72, 69, 65, + 64, 65, 79, 8, 83, 65, 79, 84, 61, 74, 64, 81, 8, 62, 72, 69, 65, 62, 65, 74, 18, + 8, 64, 75, 63, 68, 8, 25, 25, 18, 31, 30, 8, 21, 18, 2, 62, 69, 80, 8, 87, 82, + 8, 26, 29, 18, 30, 30, 8, 22, 8, 68, 61, 81, 81, 65, 74, 8, 42, 65, 64, 82, 72, + 64, 20, 8, 48, 65, 68, 79, 8, 59, 56, 65, 74, 8, 61, 72, 80, 8, 56, 69, 74, 67, + 8, 82, 74, 64, 8, 59, 61, 74, 67, 20, 8, 39, 61, 80, 2, 26, 22, 22, 22, 8, 7, + 11, 8, 84, 65, 74, 69, 67, 65, 79, 8, 84, 103, 79, 65, 74, 8, 61, 72, 80, 8, 25, + 22, 22, 22, 8, 75, 64, 65, 79, 8, 24, 22, 22, 22, 8, 7, 8, 65, 79, 67, 61, 62, + 8, 71, 65, 69, 74, 65, 79, 72, 65, 69, 8, 53, 69, 74, 74, 20, 2, 56, 75, 74, 8, + 79, 82, 74, 64, 8, 26, 24, 25, 8, 7, 18, 8, 30, 31, 31, 22, 8, 7, 18, 8, 23, + 24, 8, 22, 22, 22, 8, 7, 18, 8, 24, 22, 8, 22, 22, 22, 8, 7, 20, 8, 40, 79, + 67, 69, 62, 81, 8, 23, 24, 8, 18, 2, 23, 26, 8, 29, 11, 8, 75, 64, 65, 79, 8, + 23, 28, 8, 31, 18, 20, 8, 47, 65, 64, 69, 67, 72, 69, 63, 68, 8, 29, 28, 8, 31, + 22, 8, 68, 61, 81, 81, 65, 74, 8, 51, 79, 75, 62, 72, 65, 73, 65, 18, 8, 64, 61, + 79, 82, 74, 81, 65, 79, 8, 24, 25, 8, 31, 21, 22, 8, 41, 79, 61, 82, 65, 74, 18, + 2, 26, 26, 8, 29, 11, 8, 48, 103, 74, 74, 65, 79, 18, 8, 23, 24, 18, 26, 27, 8, + 22, 21, 8, 45, 82, 74, 67, 65, 74, 8, 82, 74, 64, 8, 23, 30, 18, 29, 8, 7, 8, + 48, 103, 64, 63, 68, 65, 74, 8, 82, 74, 64, 8, 30, 18, 26, 11, 8, 43, 82, 74, 64, + 65, 20, 2, 45, 110, 74, 67, 72, 69, 74, 67, 65, 8, 68, 61, 72, 81, 65, 8, 69, 63, + 68, 8, 66, 110, 79, 8, 65, 69, 74, 8, 61, 82, 102, 65, 79, 75, 79, 64, 65, 74, 81, + 72, 69, 63, 68, 8, 67, 65, 84, 61, 67, 81, 65, 80, 2, 82, 74, 64, 8, 67, 65, 66, + 103, 68, 79, 72, 69, 63, 68, 65, 80, 8, 40, 85, 78, 65, 79, 69, 73, 65, 74, 81, 20, + 8, 48, 65, 69, 74, 65, 8, 43, 65, 79, 79, 65, 74, 18, 8, 53, 69, 65, 2, 71, 109, + 74, 74, 65, 74, 8, 110, 62, 65, 79, 87, 65, 82, 67, 81, 8, 132, 65, 69, 74, 18, 8, + 64, 61, 102, 8, 61, 72, 80, 64, 61, 74, 74, 8, 83, 75, 74, 8, 64, 65, 74, 2, 26, + 8, 48, 61, 132, 63, 68, 69, 74, 65, 74, 8, 73, 61, 74, 63, 68, 73, 61, 72, 8, 74, + 82, 79, 8, 64, 69, 65, 8, 43, 103, 72, 66, 72, 65, 8, 69, 73, 8, 42, 61, 74, 67, + 65, 2, 64, 69, 65, 8, 61, 74, 64, 65, 79, 65, 8, 79, 65, 78, 61, 79, 61, 81, 82, + 79, 62, 65, 64, 110, 79, 66, 81, 69, 67, 8, 132, 65, 69, 74, 8, 84, 110, 79, 64, 65, + 74, 20, 8, 40, 80, 8, 67, 65, 68, 109, 79, 81, 2, 82, 74, 81, 65, 79, 8, 61, 72, + 72, 65, 74, 8, 55, 73, 132, 81, 103, 74, 64, 65, 74, 8, 61, 82, 63, 68, 8, 65, 69, + 74, 65, 8, 36, 82, 66, 132, 69, 63, 68, 81, 8, 64, 61, 87, 82, 18, 8, 82, 74, 64, + 2, 64, 61, 80, 8, 73, 61, 63, 68, 81, 8, 64, 69, 65, 8, 53, 61, 63, 68, 65, 8, + 71, 75, 132, 81, 132, 78, 69, 65, 72, 69, 67, 20, 2, 55, 74, 81, 65, 79, 8, 64, 69, + 65, 132, 65, 73, 8, 7, 56, 75, 79, 62, 65, 68, 61, 72, 81, 6, 8, 84, 69, 72, 72, + 8, 69, 63, 68, 8, 61, 62, 65, 79, 8, 64, 69, 65, 8, 46, 79, 69, 81, 69, 71, 65, + 74, 2, 82, 74, 64, 8, 36, 82, 80, 132, 81, 65, 72, 72, 82, 74, 67, 65, 74, 8, 64, + 65, 80, 8, 43, 65, 79, 79, 74, 8, 53, 81, 61, 64, 81, 83, 20, 8, 39, 79, 20, 8, + 51, 65, 74, 87, 69, 67, 2, 67, 65, 79, 74, 8, 61, 74, 65, 79, 71, 65, 74, 74, 65, + 74, 8, 14, 82, 74, 64, 8, 87, 82, 132, 69, 63, 68, 65, 79, 74, 18, 8, 84, 65, 74, + 74, 8, 53, 69, 65, 8, 82, 74, 80, 8, 64, 69, 65, 2, 109, 81, 69, 67, 65, 8, 60, + 65, 69, 81, 8, 72, 61, 132, 132, 65, 74, 15, 18, 8, 64, 65, 73, 8, 36, 82, 80, 132, + 63, 68, 82, 102, 8, 48, 61, 81, 65, 79, 69, 61, 72, 8, 83, 75, 79, 87, 82, 3, 2, + 65, 67, 65, 74, 18, 8, 132, 75, 84, 65, 69, 81, 8, 69, 63, 68, 8, 69, 74, 8, 64, + 65, 79, 8, 47, 61, 67, 65, 8, 62, 69, 74, 18, 8, 65, 80, 8, 87, 82, 8, 62, 65, + 132, 63, 68, 61, 66, 66, 65, 74, 20, 2, 14, 39, 69, 65, 8, 37, 65, 79, 61, 81, 82, + 74, 67, 8, 84, 69, 79, 64, 8, 67, 65, 132, 63, 68, 72, 75, 132, 132, 65, 74, 20, 8, + 39, 69, 65, 8, 56, 65, 79, 132, 61, 73, 73, 3, 2, 82, 74, 67, 8, 62, 65, 132, 63, + 68, 72, 69, 65, 102, 81, 8, 64, 69, 65, 8, 40, 69, 74, 132, 65, 81, 87, 82, 74, 67, + 8, 65, 69, 74, 65, 80, 8, 36, 82, 80, 132, 63, 68, 82, 132, 132, 65, 80, 8, 83, 75, + 74, 2, 48, 69, 81, 67, 72, 69, 65, 64, 65, 79, 74, 8, 82, 74, 64, 8, 84, 103, 68, + 72, 81, 8, 87, 82, 8, 36, 82, 80, 132, 63, 68, 82, 102, 73, 69, 81, 67, 72, 69, 65, + 64, 65, 79, 74, 2, 69, 65, 8, 53, 81, 61, 64, 81, 83, 20, 8, 37, 61, 79, 74, 65, + 84, 69, 81, 87, 18, 8, 37, 72, 61, 74, 63, 71, 18, 8, 39, 79, 20, 8, 37, 75, 79, + 63, 68, 61, 79, 64, 81, 18, 8, 46, 61, 82, 66, 3, 32, 2, 83, 20, 8, 47, 69, 80, + 87, 81, 18, 8, 39, 79, 20, 8, 51, 65, 74, 87, 69, 67, 18, 8, 53, 61, 63, 68, 80, + 18, 8, 54, 68, 69, 65, 73, 65, 8, 82, 74, 64, 2, 56, 75, 67, 65, 72, 20, 15, 8, + 56, 75, 79, 132, 81, 65, 68, 65, 79, 8, 52, 75, 132, 65, 74, 62, 65, 79, 67, 32, 8, + 51, 82, 74, 71, 81, 8, 23, 29, 8, 64, 65, 79, 8, 54, 61, 67, 65, 80, 75, 79, 64, + 74, 82, 74, 67, 32, 2, 23, 24, 8, 29, 11, 8, 64, 65, 79, 8, 56, 75, 79, 72, 61, + 67, 65, 8, 62, 65, 81, 79, 20, 8, 49, 65, 82, 62, 61, 82, 8, 65, 69, 74, 65, 79, + 8, 42, 65, 73, 65, 69, 74, 64, 65, 64, 75, 78, 78, 65, 72, 3, 2, 132, 63, 68, 82, + 72, 65, 8, 69, 74, 8, 64, 65, 79, 8, 53, 86, 62, 65, 72, 132, 81, 79, 61, 102, 65, + 20, 8, 3, 8, 39, 79, 82, 63, 71, 132, 61, 63, 68, 65, 8, 91, 8, 27, 23, 20, 2, + 37, 65, 79, 69, 63, 68, 81, 65, 79, 132, 81, 61, 81, 81, 65, 79, 8, 53, 81, 61, 64, + 81, 83, 20, 8, 57, 75, 72, 66, 66, 65, 74, 132, 81, 65, 69, 74, 32, 2, 43, 65, 79, + 79, 65, 74, 18, 8, 64, 69, 65, 8, 56, 75, 79, 72, 61, 67, 65, 8, 62, 65, 66, 61, + 102, 81, 8, 132, 69, 63, 68, 8, 73, 69, 81, 8, 65, 69, 74, 65, 73, 8, 53, 63, 68, + 82, 72, 62, 61, 82, 2, 61, 82, 66, 8, 65, 69, 74, 65, 73, 8, 42, 79, 82, 74, 64, + 132, 81, 110, 63, 71, 18, 8, 69, 74, 8, 64, 65, 79, 8, 53, 86, 62, 65, 72, 132, 81, + 79, 61, 102, 65, 8, 67, 65, 72, 65, 67, 65, 74, 18, 2, 84, 65, 72, 63, 68, 65, 80, + 8, 64, 69, 65, 8, 53, 81, 61, 64, 81, 8, 69, 73, 8, 45, 61, 68, 79, 65, 8, 23, + 31, 22, 24, 8, 14, 79, 82, 74, 64, 8, 24, 29, 20, 27, 28, 8, 7, 98, 15, 8, 65, + 79, 84, 75, 79, 62, 65, 74, 8, 68, 61, 81, 20, 2, 40, 69, 74, 65, 8, 53, 81, 79, + 61, 102, 65, 18, 8, 64, 69, 65, 8, 52, 75, 132, 63, 68, 65, 79, 132, 81, 79, 61, 102, + 65, 18, 8, 66, 110, 68, 79, 81, 8, 64, 69, 79, 65, 71, 81, 8, 83, 75, 73, 8, 46, + 82, 79, 3, 2, 66, 110, 79, 132, 81, 65, 74, 64, 61, 73, 73, 8, 61, 82, 66, 8, 64, + 61, 80, 8, 42, 79, 82, 74, 64, 132, 81, 110, 63, 71, 8, 87, 82, 20, 8, 39, 61, 80, + 8, 51, 79, 75, 70, 65, 71, 81, 2, 84, 65, 69, 132, 81, 8, 25, 31, 8, 53, 63, 68, + 82, 72, 71, 72, 61, 132, 132, 65, 74, 8, 61, 82, 66, 18, 8, 83, 75, 74, 8, 64, 65, + 74, 65, 74, 8, 24, 22, 8, 61, 82, 66, 8, 46, 74, 61, 62, 65, 74, 18, 2, 61, 82, + 63, 68, 18, 8, 64, 61, 102, 8, 64, 61, 80, 8, 56, 65, 132, 81, 69, 62, 82, 110, 72, + 8, 66, 82, 79, 8, 64, 69, 65, 8, 46, 74, 61, 62, 65, 74, 61, 62, 81, 65, 69, 72, + 81, 82, 74, 67, 8, 132, 65, 68, 79, 20, 2, 57, 61, 80, 8, 64, 69, 65, 8, 46, 75, + 132, 81, 65, 74, 66, 79, 61, 67, 65, 8, 61, 74, 62, 65, 81, 79, 69, 66, 66, 81, 18, + 8, 132, 75, 8, 69, 132, 81, 8, 64, 65, 79, 8, 46, 82, 62, 69, 71, 3, 2, 73, 65, + 81, 65, 79, 8, 82, 73, 62, 61, 82, 81, 65, 79, 8, 52, 61, 82, 73, 8, 73, 69, 81, + 8, 24, 23, 11, 21, 8, 61, 74, 67, 65, 132, 65, 81, 87, 81, 20, 8, 39, 61, 80, 2, + 69, 132, 81, 8, 65, 69, 74, 8, 51, 79, 65, 69, 80, 18, 8, 64, 65, 79, 8, 73, 69, + 79, 8, 64, 82, 79, 63, 68, 61, 82, 80, 8, 61, 74, 67, 65, 73, 65, 132, 132, 65, 74, + 8, 87, 82, 8, 132, 65, 69, 74, 2, 132, 63, 68, 65, 69, 74, 81, 20, 8, 39, 69, 65, + 8, 42, 65, 132, 61, 73, 81, 132, 82, 73, 73, 65, 8, 64, 65, 79, 8, 46, 75, 132, 81, + 65, 74, 8, 62, 65, 72, 103, 82, 66, 81, 8, 132, 69, 63, 68, 8, 61, 82, 66, 2, 30, + 23, 27, 8, 22, 22, 22, 8, 7, 14, 20, 8, 57, 65, 74, 74, 8, 69, 63, 68, 8, 61, + 82, 66, 8, 64, 69, 65, 8, 36, 79, 63, 68, 69, 81, 65, 71, 81, 82, 79, 8, 74, 75, + 63, 68, 8, 87, 82, 8, 132, 78, 79, 65, 63, 68, 65, 74, 2, 71, 75, 73, 73, 65, 18, + 8, 132, 75, 8, 73, 109, 63, 68, 81, 65, 8, 69, 63, 68, 8, 132, 61, 67, 65, 74, 18, + 8, 64, 61, 102, 8, 132, 69, 65, 8, 69, 73, 8, 52, 61, 68, 73, 65, 74, 8, 64, 65, + 80, 2, 42, 79, 82, 74, 64, 79, 69, 132, 132, 65, 80, 8, 61, 74, 67, 65, 73, 65, 132, + 132, 65, 74, 8, 82, 74, 64, 8, 69, 74, 8, 67, 82, 81, 65, 79, 8, 42, 79, 82, 78, + 78, 69, 65, 79, 82, 74, 67, 2, 73, 61, 72, 65, 79, 69, 132, 63, 68, 8, 61, 82, 66, + 67, 65, 72, 109, 132, 81, 8, 69, 132, 81, 20, 8, 40, 80, 8, 69, 132, 81, 8, 65, 69, + 74, 8, 37, 61, 63, 71, 132, 81, 65, 69, 74, 62, 61, 82, 8, 73, 69, 81, 2, 46, 72, + 75, 132, 81, 65, 79, 66, 75, 79, 73, 61, 81, 8, 78, 79, 75, 70, 65, 71, 81, 69, 65, + 79, 81, 18, 8, 65, 69, 74, 87, 65, 72, 74, 65, 8, 54, 65, 69, 72, 65, 8, 69, 74, + 8, 53, 61, 74, 64, 132, 81, 65, 69, 74, 18, 2, 65, 69, 74, 87, 65, 72, 74, 65, 8, + 41, 72, 103, 63, 68, 65, 74, 8, 14, 79, 82, 74, 64, 8, 25, 27, 18, 23, 24, 8, 31, + 21, 22, 15, 8, 69, 74, 8, 51, 82, 81, 87, 20, 8, 36, 62, 65, 79, 8, 64, 82, 79, + 63, 68, 8, 65, 69, 74, 65, 8, 56, 65, 79, 3, 2, 103, 74, 64, 65, 79, 82, 74, 67, + 8, 64, 65, 80, 8, 42, 79, 82, 74, 64, 79, 69, 132, 132, 65, 80, 8, 87, 82, 8, 28, + 28, 8, 29, 8, 64, 65, 80, 8, 56, 75, 79, 64, 65, 79, 68, 61, 82, 132, 65, 80, 18, + 8, 64, 69, 65, 8, 69, 63, 68, 2, 66, 110, 79, 8, 84, 110, 74, 132, 63, 68, 65, 74, + 80, 84, 65, 79, 81, 8, 68, 61, 72, 81, 65, 18, 8, 84, 69, 79, 64, 8, 132, 69, 63, + 68, 8, 65, 62, 65, 74, 8, 61, 82, 63, 68, 8, 65, 69, 74, 65, 2, 61, 74, 64, 65, + 79, 65, 8, 41, 61, 132, 132, 61, 64, 65, 8, 73, 69, 81, 8, 79, 82, 74, 64, 8, 23, + 24, 8, 18, 11, 8, 65, 79, 67, 65, 62, 65, 74, 20, 8, 36, 82, 80, 8, 64, 65, 74, + 8, 42, 79, 110, 74, 64, 65, 74, 18, 8, 64, 69, 65, 8, 69, 63, 68, 8, 61, 74, 67, + 65, 66, 110, 68, 79, 81, 8, 68, 61, 62, 65, 18, 8, 132, 69, 74, 64, 2, 73, 65, 69, + 74, 65, 8, 41, 79, 61, 71, 81, 69, 75, 74, 80, 67, 65, 74, 75, 132, 132, 65, 74, 8, + 14, 69, 74, 8, 54, 65, 69, 72, 65, 74, 8, 83, 75, 74, 8, 24, 27, 8, 18, 8, 26, + 27, 8, 31, 22, 18, 8, 27, 22, 8, 7, 98, 15, 8, 64, 61, 66, 110, 79, 18, 8, 64, + 69, 65, 8, 56, 75, 79, 72, 61, 67, 65, 8, 65, 69, 74, 65, 73, 2, 36, 82, 80, 132, + 63, 68, 82, 132, 132, 65, 8, 83, 75, 74, 8, 31, 8, 48, 69, 81, 67, 72, 69, 65, 64, + 65, 79, 74, 8, 87, 82, 8, 110, 62, 65, 79, 84, 65, 69, 132, 65, 74, 20, 2, 14, 39, + 69, 65, 8, 37, 65, 79, 61, 81, 82, 74, 67, 8, 84, 69, 79, 64, 8, 67, 65, 132, 63, + 68, 72, 75, 132, 132, 65, 74, 20, 8, 39, 69, 65, 8, 56, 65, 79, 132, 61, 73, 73, 72, + 82, 74, 67, 2, 62, 65, 132, 63, 68, 72, 69, 65, 102, 81, 8, 64, 69, 65, 8, 40, 69, + 74, 132, 65, 81, 87, 82, 74, 67, 8, 65, 69, 74, 65, 80, 8, 36, 82, 80, 132, 63, 68, + 82, 132, 132, 65, 80, 8, 83, 75, 74, 8, 31, 8, 31, 18, 2, 91, 8, 23, 24, 20, 8, + 48, 69, 81, 67, 72, 69, 65, 64, 65, 79, 74, 8, 82, 74, 64, 8, 84, 103, 68, 72, 81, + 8, 87, 82, 8, 36, 82, 80, 132, 63, 68, 82, 102, 73, 69, 81, 67, 72, 69, 65, 64, 65, + 79, 74, 8, 64, 69, 65, 2, 53, 81, 61, 64, 81, 83, 20, 8, 39, 79, 20, 8, 37, 61, + 82, 65, 79, 18, 8, 39, 79, 20, 8, 37, 75, 79, 63, 68, 61, 79, 64, 81, 18, 8, 42, + 79, 65, 64, 86, 18, 8, 46, 72, 69, 63, 71, 18, 2, 47, 69, 74, 67, 74, 65, 79, 18, + 8, 48, 69, 81, 81, 61, 67, 18, 8, 53, 63, 68, 73, 69, 64, 81, 18, 8, 39, 79, 20, + 8, 53, 81, 61, 64, 81, 68, 61, 67, 65, 74, 8, 82, 74, 64, 2, 57, 75, 72, 66, 66, + 65, 74, 132, 81, 65, 69, 74, 20, 15, 8, 56, 75, 79, 132, 81, 65, 68, 65, 79, 8, 52, + 75, 132, 65, 74, 62, 65, 79, 67, 32, 8, 7, 39, 61, 80, 8, 51, 79, 75, 81, 75, 71, + 75, 72, 72, 8, 83, 75, 72, 72, 87, 69, 65, 68, 65, 74, 2, 68, 65, 82, 81, 65, 8, + 64, 69, 65, 8, 43, 65, 79, 79, 65, 74, 8, 53, 81, 61, 64, 81, 83, 20, 8, 56, 75, + 67, 65, 72, 18, 8, 57, 65, 74, 69, 67, 8, 82, 74, 64, 8, 57, 75, 72, 66, 66, 65, + 74, 132, 81, 65, 69, 74, 20, 6, 2, 51, 82, 74, 71, 81, 8, 23, 30, 8, 64, 65, 79, + 8, 54, 61, 67, 65, 80, 75, 79, 64, 74, 82, 74, 67, 18, 8, 91, 8, 24, 24, 18, 8, + 91, 8, 31, 27, 18, 8, 91, 27, 8, 62, 69, 80, 8, 91, 29, 18, 8, 91, 8, 23, 22, + 24, 32, 2, 43, 65, 79, 79, 8, 48, 61, 132, 63, 68, 69, 74, 65, 79, 69, 65, 64, 69, + 79, 65, 71, 81, 75, 79, 8, 47, 61, 82, 81, 65, 74, 132, 63, 68, 72, 103, 67, 65, 79, + 18, 8, 84, 65, 72, 63, 68, 65, 79, 2, 65, 79, 71, 72, 103, 79, 81, 8, 68, 61, 81, + 18, 8, 66, 110, 79, 8, 132, 65, 69, 74, 65, 8, 51, 65, 79, 132, 75, 74, 8, 61, 82, + 66, 8, 69, 79, 67, 65, 74, 64, 84, 65, 72, 63, 68, 65, 74, 8, 56, 65, 79, 3, 2, + 64, 69, 65, 74, 132, 81, 8, 87, 82, 8, 83, 65, 79, 87, 69, 63, 68, 81, 65, 74, 18, + 8, 84, 65, 74, 74, 8, 69, 73, 8, 53, 63, 68, 69, 72, 72, 65, 79, 81, 68, 65, 61, + 81, 65, 79, 8, 65, 69, 74, 65, 2, 39, 79, 65, 68, 62, 110, 68, 74, 65, 8, 132, 65, + 69, 74, 65, 79, 8, 46, 75, 74, 132, 81, 79, 82, 71, 81, 69, 75, 74, 8, 65, 69, 74, + 67, 65, 79, 69, 63, 68, 81, 65, 81, 8, 84, 65, 79, 64, 65, 74, 2, 132, 75, 72, 72, + 81, 65, 18, 8, 62, 65, 79, 65, 63, 68, 74, 65, 81, 8, 64, 69, 65, 8, 46, 75, 132, + 81, 65, 74, 8, 65, 69, 74, 65, 79, 8, 132, 75, 72, 63, 68, 65, 74, 8, 46, 75, 74, + 132, 81, 79, 82, 71, 81, 69, 75, 74, 8, 14, 53, 91, 8, 30, 20, 15, 2, 61, 82, 66, + 8, 63, 61, 20, 8, 26, 29, 22, 22, 22, 18, 22, 22, 8, 7, 8, 75, 64, 65, 79, 8, + 30, 29, 25, 25, 8, 7, 8, 83, 69, 65, 72, 73, 65, 68, 79, 8, 30, 29, 25, 25, 18, + 31, 31, 11, 104, 82, 74, 64, 8, 65, 79, 8, 68, 61, 81, 8, 82, 74, 80, 8, 81, 61, + 81, 132, 103, 63, 68, 72, 69, 63, 68, 8, 64, 65, 74, 2, 49, 61, 63, 68, 84, 65, 69, + 80, 8, 67, 65, 72, 69, 65, 66, 65, 79, 81, 18, 8, 64, 61, 102, 8, 73, 69, 81, 8, + 65, 69, 74, 65, 79, 8, 74, 69, 65, 64, 79, 69, 67, 65, 79, 65, 74, 8, 53, 82, 73, + 73, 65, 2, 65, 69, 74, 65, 8, 84, 69, 79, 71, 72, 69, 63, 68, 8, 83, 75, 72, 72, + 71, 75, 73, 73, 65, 74, 65, 8, 46, 75, 74, 132, 81, 79, 82, 71, 81, 69, 75, 74, 8, + 74, 69, 63, 68, 81, 8, 68, 65, 79, 87, 82, 3, 2, 132, 81, 65, 72, 72, 65, 74, 8, + 69, 132, 81, 20, 8, 40, 69, 74, 65, 8, 39, 79, 65, 68, 62, 110, 68, 74, 65, 8, 62, + 69, 65, 81, 65, 81, 8, 87, 84, 65, 69, 66, 65, 72, 72, 75, 80, 8, 61, 82, 102, 65, + 79, 75, 79, 64, 65, 74, 81, 3, 2, 74, 65, 68, 73, 18, 8, 84, 65, 74, 74, 8, 73, + 61, 74, 8, 132, 69, 65, 8, 65, 69, 74, 65, 79, 8, 82, 74, 64, 8, 132, 69, 65, 8, + 69, 132, 81, 8, 69, 73, 8, 68, 109, 63, 68, 132, 81, 65, 74, 8, 42, 79, 61, 64, 65, + 8, 61, 74, 67, 65, 3, 2, 66, 65, 79, 81, 69, 67, 65, 74, 8, 37, 110, 68, 74, 65, + 74, 19, 40, 69, 74, 79, 69, 63, 68, 81, 82, 74, 67, 8, 74, 75, 63, 68, 8, 61, 74, + 67, 72, 69, 65, 64, 65, 79, 74, 8, 71, 61, 74, 74, 20, 8, 37, 65, 83, 75, 79, 8, + 132, 69, 65, 8, 70, 65, 64, 75, 63, 68, 2, 61, 74, 67, 65, 132, 63, 68, 61, 66, 66, + 81, 8, 84, 69, 79, 64, 18, 8, 132, 75, 72, 72, 81, 65, 74, 8, 64, 69, 65, 132, 65, + 8, 39, 69, 74, 67, 65, 8, 83, 75, 79, 3, 2, 68, 61, 74, 64, 65, 74, 8, 132, 65, + 69, 74, 8, 82, 74, 64, 8, 64, 61, 8, 84, 69, 79, 8, 64, 65, 79, 8, 66, 65, 132, + 81, 65, 74, 8, 55, 62, 65, 79, 87, 65, 82, 67, 82, 74, 67, 8, 132, 69, 74, 64, 18, + 2, 84, 69, 65, 8, 64, 61, 80, 8, 64, 69, 65, 8, 37, 65, 69, 132, 78, 69, 65, 72, + 65, 8, 65, 69, 74, 65, 79, 8, 67, 61, 74, 87, 65, 74, 8, 52, 65, 69, 68, 65, 2, + 82, 74, 132, 65, 79, 65, 79, 8, 67, 79, 109, 102, 65, 79, 65, 74, 8, 37, 110, 68, 74, + 65, 74, 8, 87, 65, 69, 67, 81, 18, 8, 84, 75, 68, 72, 8, 64, 69, 65, 8, 39, 79, + 65, 68, 62, 110, 68, 74, 65, 18, 2, 74, 69, 63, 68, 81, 8, 61, 62, 65, 79, 8, 61, + 74, 64, 65, 79, 65, 8, 40, 69, 74, 79, 69, 63, 68, 81, 82, 74, 67, 65, 74, 8, 65, + 74, 81, 62, 65, 68, 79, 65, 74, 8, 71, 61, 74, 74, 18, 8, 132, 75, 2, 65, 73, 78, + 66, 65, 68, 72, 65, 74, 8, 84, 69, 79, 18, 8, 64, 69, 65, 8, 132, 65, 69, 81, 65, + 74, 80, 8, 64, 65, 79, 8, 132, 81, 103, 64, 81, 69, 132, 63, 68, 65, 74, 8, 46, 75, + 72, 72, 65, 67, 69, 65, 74, 2, 73, 69, 81, 8, 64, 65, 79, 8, 36, 62, 132, 69, 63, + 68, 81, 8, 65, 69, 74, 65, 79, 8, 56, 65, 79, 62, 65, 132, 132, 65, 79, 82, 74, 67, + 8, 82, 74, 132, 65, 79, 65, 79, 8, 37, 110, 68, 74, 65, 74, 3, 2, 61, 74, 72, 61, + 67, 65, 8, 67, 65, 74, 65, 68, 73, 69, 67, 81, 65, 74, 8, 25, 22, 8, 22, 22, 22, + 8, 7, 8, 69, 74, 8, 61, 74, 64, 65, 79, 65, 79, 8, 57, 65, 69, 132, 65, 2, 87, + 82, 8, 83, 65, 79, 84, 65, 74, 64, 65, 74, 8, 82, 74, 64, 8, 87, 84, 61, 79, 8, + 132, 63, 68, 72, 61, 67, 65, 74, 8, 84, 69, 79, 8, 83, 75, 79, 18, 8, 87, 82, 8, + 64, 65, 79, 2, 68, 65, 82, 81, 69, 67, 65, 74, 8, 37, 110, 68, 74, 65, 74, 65, 69, + 74, 79, 69, 63, 68, 81, 82, 74, 67, 8, 74, 75, 63, 68, 8, 41, 75, 72, 67, 65, 74, + 64, 65, 80, 8, 61, 72, 80, 8, 40, 79, 3, 2, 67, 103, 74, 87, 82, 74, 67, 8, 68, + 69, 74, 87, 82, 87, 82, 66, 110, 67, 65, 74, 8, 14, 72, 61, 82, 81, 8, 91, 8, 23, + 28, 18, 8, 36, 62, 20, 8, 61, 15, 32, 2, 14, 23, 8, 53, 63, 68, 69, 65, 62, 65, + 87, 82, 67, 15, 2, 23, 8, 46, 61, 132, 132, 65, 81, 81, 65, 74, 71, 72, 61, 78, 78, + 65, 2, 14, 27, 8, 41, 79, 65, 69, 66, 61, 68, 79, 81, 83, 65, 79, 132, 63, 68, 72, + 110, 132, 132, 65, 15, 2, 31, 8, 46, 82, 72, 69, 132, 132, 65, 74, 84, 103, 67, 65, 74, + 2, 23, 8, 57, 61, 74, 64, 65, 72, 78, 61, 74, 75, 79, 61, 73, 61, 2, 39, 65, 79, + 8, 55, 73, 67, 65, 73, 65, 69, 74, 64, 82, 74, 67, 8, 64, 65, 79, 8, 87, 82, 73, + 8, 42, 82, 81, 80, 62, 65, 87, 69, 79, 71, 8, 54, 65, 67, 65, 72, 65, 79, 2, 41, + 75, 79, 132, 81, 8, 67, 65, 68, 109, 79, 69, 67, 65, 74, 8, 51, 61, 79, 87, 65, 72, + 72, 65, 74, 8, 25, 23, 30, 21, 23, 8, 124, 63, 20, 18, 8, 25, 23, 31, 21, 27, 8, + 124, 63, 20, 18, 2, 25, 24, 22, 21, 27, 18, 8, 25, 24, 23, 21, 24, 26, 8, 124, 63, + 20, 18, 8, 25, 24, 24, 21, 24, 26, 18, 8, 25, 24, 25, 21, 24, 24, 8, 45, 44, 45, + 20, 8, 25, 24, 26, 21, 24, 24, 8, 24, 63, 20, 18, 2, 44, 56, 18, 8, 25, 24, 27, + 21, 24, 24, 8, 56, 124, 63, 20, 18, 8, 25, 24, 28, 21, 24, 24, 8, 44, 45, 20, 18, + 8, 25, 24, 29, 21, 24, 24, 8, 45, 18, 8, 25, 25, 24, 21, 24, 25, 2, 44, 45, 20, + 18, 8, 25, 25, 25, 21, 24, 26, 8, 24, 63, 20, 18, 8, 25, 25, 26, 21, 26, 30, 8, + 24, 63, 20, 18, 8, 25, 24, 30, 21, 24, 30, 8, 45, 44, 45, 18, 8, 25, 24, 31, 21, + 24, 25, 8, 124, 63, 20, 18, 2, 25, 25, 22, 21, 23, 25, 8, 82, 74, 64, 8, 25, 25, + 23, 21, 23, 25, 8, 124, 63, 20, 18, 8, 46, 61, 79, 81, 65, 74, 62, 72, 61, 81, 81, + 8, 23, 18, 8, 132, 75, 84, 69, 65, 8, 64, 65, 79, 2, 61, 74, 67, 79, 65, 74, 87, + 65, 74, 64, 65, 74, 8, 54, 65, 69, 72, 65, 8, 64, 65, 79, 8, 53, 61, 61, 81, 84, + 69, 74, 71, 65, 72, 65, 79, 8, 38, 68, 61, 82, 132, 132, 65, 65, 18, 2, 132, 75, 8, + 84, 69, 65, 8, 132, 69, 65, 8, 61, 82, 66, 8, 64, 65, 73, 8, 62, 65, 69, 8, 64, + 65, 74, 8, 36, 71, 81, 65, 74, 8, 7, 56, 65, 79, 68, 61, 74, 64, 3, 2, 72, 82, + 74, 67, 65, 74, 8, 62, 65, 81, 79, 20, 8, 64, 69, 65, 8, 45, 82, 74, 67, 66, 65, + 79, 74, 68, 65, 69, 64, 65, 6, 8, 14, 43, 65, 66, 81, 8, 49, 79, 20, 8, 25, 30, + 26, 15, 2, 62, 65, 66, 69, 74, 64, 72, 69, 63, 68, 65, 74, 8, 47, 61, 67, 65, 78, + 72, 61, 74, 8, 14, 37, 72, 61, 81, 81, 8, 24, 26, 25, 15, 8, 73, 69, 81, 8, 64, + 65, 74, 2, 37, 82, 63, 68, 132, 81, 61, 62, 65, 74, 8, 65, 64, 79, 77, 132, 65, 8, + 82, 74, 64, 68, 69, 81, 82, 72, 75, 83, 80, 68, 2, 62, 65, 87, 65, 69, 63, 68, 74, + 65, 81, 8, 132, 69, 74, 64, 18, 8, 74, 61, 63, 68, 8, 64, 65, 79, 8, 53, 81, 61, + 64, 81, 67, 65, 73, 65, 69, 74, 64, 65, 8, 38, 68, 61, 79, 3, 2, 72, 75, 81, 81, + 65, 74, 62, 82, 79, 67, 18, 8, 84, 69, 79, 64, 8, 87, 82, 67, 65, 132, 81, 69, 73, + 73, 81, 20, 2, 36, 82, 66, 8, 42, 79, 82, 74, 64, 8, 64, 65, 79, 8, 42, 65, 73, + 65, 69, 74, 64, 65, 62, 65, 132, 63, 68, 72, 110, 132, 132, 65, 8, 83, 75, 73, 8, 24, + 20, 21, 23, 27, 20, 8, 45, 82, 74, 69, 2, 82, 74, 64, 8, 23, 27, 20, 21, 24, 23, + 20, 8, 39, 65, 87, 65, 73, 62, 65, 79, 8, 83, 20, 8, 45, 80, 20, 8, 14, 39, 79, + 82, 63, 71, 132, 61, 63, 68, 65, 8, 49, 79, 20, 8, 24, 30, 26, 2, 82, 74, 64, 8, + 27, 22, 24, 15, 8, 68, 61, 81, 8, 64, 69, 65, 8, 53, 81, 61, 64, 81, 67, 65, 73, + 65, 69, 74, 64, 65, 8, 87, 82, 79, 8, 36, 74, 72, 65, 67, 82, 74, 67, 8, 65, 69, + 74, 65, 80, 2, 56, 75, 72, 71, 80, 78, 61, 79, 71, 65, 80, 8, 54, 65, 69, 72, 65, + 8, 64, 65, 79, 8, 45, 82, 74, 67, 66, 65, 79, 74, 68, 65, 69, 64, 65, 8, 83, 75, + 73, 8, 41, 75, 79, 132, 81, 66, 69, 80, 71, 82, 80, 2, 65, 79, 84, 75, 79, 62, 65, + 74, 20, 8, 44, 74, 8, 64, 65, 74, 8, 62, 65, 87, 110, 67, 72, 69, 63, 68, 65, 74, + 8, 46, 61, 82, 66, 83, 65, 79, 81, 79, 103, 67, 65, 74, 8, 14, 83, 75, 73, 2, 23, + 30, 20, 8, 45, 82, 74, 69, 8, 82, 74, 64, 8, 23, 20, 8, 45, 82, 72, 69, 8, 83, + 20, 8, 44, 80, 20, 8, 3, 8, 49, 79, 20, 8, 26, 31, 31, 8, 82, 74, 64, 8, 27, + 22, 28, 15, 2, 64, 65, 80, 8, 55, 79, 71, 82, 74, 64, 65, 74, 83, 65, 79, 87, 65, + 69, 63, 68, 74, 69, 132, 132, 65, 80, 8, 64, 65, 79, 8, 53, 81, 61, 64, 81, 8, 38, + 68, 61, 79, 72, 75, 81, 81, 65, 74, 62, 82, 79, 67, 8, 3, 2, 69, 132, 81, 8, 64, + 69, 65, 8, 55, 73, 67, 65, 73, 65, 69, 74, 64, 82, 74, 67, 8, 64, 65, 79, 8, 65, + 79, 84, 75, 79, 62, 65, 74, 65, 74, 8, 41, 72, 103, 63, 68, 65, 74, 8, 82, 74, 64, + 2, 64, 65, 79, 8, 61, 74, 8, 132, 69, 65, 8, 61, 74, 132, 81, 75, 102, 65, 74, 64, + 65, 74, 8, 54, 65, 69, 72, 65, 8, 64, 65, 79, 8, 53, 61, 61, 81, 84, 69, 74, 71, + 65, 72, 65, 79, 2, 38, 68, 61, 82, 132, 132, 65, 65, 8, 83, 75, 79, 67, 65, 132, 65, + 68, 65, 74, 20, 8, 39, 69, 65, 8, 82, 73, 87, 82, 67, 65, 73, 65, 69, 74, 64, 65, + 74, 64, 65, 74, 8, 51, 61, 79, 3, 2, 87, 65, 72, 72, 65, 74, 8, 64, 65, 79, 8, + 45, 82, 74, 67, 66, 65, 79, 74, 68, 65, 69, 64, 65, 8, 68, 61, 62, 65, 74, 8, 69, + 74, 66, 75, 72, 67, 65, 8, 71, 61, 81, 61, 132, 81, 65, 79, 73, 103, 102, 69, 67, 65, + 79, 2, 41, 75, 79, 81, 132, 63, 68, 79, 65, 69, 62, 82, 74, 67, 8, 64, 69, 65, 8, + 69, 73, 8, 54, 65, 74, 75, 79, 8, 61, 82, 66, 67, 65, 66, 110, 68, 79, 81, 65, 74, + 8, 74, 65, 82, 65, 74, 2, 49, 82, 73, 73, 65, 79, 74, 8, 65, 79, 68, 61, 72, 81, + 65, 74, 20, 8, 44, 68, 79, 65, 8, 42, 65, 132, 61, 73, 81, 67, 79, 109, 102, 65, 8, + 3, 8, 23, 30, 26, 8, 68, 61, 2, 23, 30, 8, 61, 8, 22, 28, 8, 77, 73, 8, 3, + 8, 84, 65, 69, 63, 68, 81, 8, 83, 75, 74, 8, 64, 65, 79, 8, 69, 73, 8, 56, 65, + 79, 81, 79, 61, 67, 65, 8, 83, 75, 73, 2, 23, 30, 20, 8, 45, 82, 74, 69, 8, 83, + 20, 8, 45, 80, 20, 8, 83, 75, 79, 67, 65, 132, 65, 68, 65, 74, 65, 74, 8, 82, 73, + 8, 65, 69, 74, 8, 67, 65, 79, 69, 74, 67, 65, 80, 8, 61, 62, 20, 2, 39, 61, 8, + 83, 75, 79, 61, 82, 80, 132, 69, 63, 68, 81, 72, 69, 63, 68, 8, 64, 65, 79, 8, 37, + 65, 87, 69, 79, 71, 80, 61, 82, 80, 132, 63, 68, 82, 102, 8, 69, 74, 8, 51, 75, 81, + 80, 3, 2, 132, 81, 103, 79, 71, 82, 74, 67, 8, 83, 75, 74, 8, 40, 81, 61, 81, 80, + 74, 82, 73, 73, 65, 79, 74, 8, 14, 64, 65, 80, 8, 50, 79, 64, 20, 8, 46, 61, 78, + 69, 81, 65, 72, 8, 56, 44, 8, 66, 110, 79, 8, 23, 31, 22, 27, 15, 20, 2, 55, 79, + 132, 63, 68, 79, 69, 66, 81, 72, 69, 63, 68, 8, 73, 69, 81, 8, 65, 69, 74, 65, 73, + 8, 43, 65, 66, 81, 8, 61, 74, 8, 64, 69, 65, 8, 53, 81, 61, 64, 81, 83, 65, 79, + 75, 79, 64, 74, 65, 81, 65, 74, 19, 56, 65, 79, 132, 61, 73, 73, 72, 82, 74, 67, 2, + 73, 69, 81, 8, 64, 65, 73, 8, 36, 74, 81, 79, 61, 67, 65, 18, 8, 87, 82, 8, 62, + 65, 132, 63, 68, 72, 69, 65, 102, 65, 74, 32, 8, 60, 82, 79, 8, 56, 65, 79, 132, 81, + 103, 79, 71, 82, 74, 67, 8, 66, 75, 72, 67, 65, 74, 64, 65, 79, 8, 40, 81, 61, 81, + 80, 74, 82, 73, 73, 65, 79, 74, 2, 64, 65, 80, 8, 50, 79, 64, 69, 74, 61, 79, 69, + 82, 73, 80, 8, 46, 61, 78, 69, 81, 65, 72, 8, 56, 44, 44, 8, 66, 110, 79, 8, 23, + 31, 22, 27, 8, 84, 65, 79, 64, 65, 74, 2, 61, 82, 80, 8, 72, 61, 82, 66, 65, 74, + 64, 65, 74, 8, 48, 69, 81, 81, 65, 72, 74, 8, 74, 61, 63, 68, 62, 65, 84, 69, 72, + 72, 69, 67, 81, 32, 2, 61, 15, 8, 36, 62, 132, 63, 68, 74, 69, 81, 81, 8, 24, 8, + 49, 79, 20, 8, 23, 61, 8, 14, 37, 61, 82, 72, 69, 63, 68, 65, 8, 55, 74, 81, 65, + 79, 68, 61, 72, 81, 82, 74, 67, 2, 64, 65, 80, 8, 52, 61, 81, 68, 61, 82, 132, 65, + 80, 15, 8, 25, 27, 22, 22, 8, 7, 18, 8, 23, 24, 8, 27, 22, 22, 8, 7, 18, 8, + 24, 26, 26, 26, 26, 8, 75, 64, 65, 79, 8, 25, 28, 25, 8, 28, 27, 28, 8, 0, 134, + 2, 62, 15, 8, 36, 62, 132, 63, 68, 74, 69, 81, 81, 8, 25, 8, 14, 49, 79, 20, 8, + 23, 25, 15, 8, 14, 55, 74, 81, 65, 79, 68, 61, 72, 81, 82, 74, 67, 8, 64, 65, 79, + 8, 48, 109, 62, 65, 72, 2, 82, 74, 64, 8, 42, 65, 79, 103, 81, 65, 8, 69, 73, 8, + 52, 61, 81, 68, 61, 82, 132, 65, 15, 8, 27, 28, 22, 22, 8, 7, 18, 8, 25, 22, 22, + 22, 8, 18, 8, 25, 24, 27, 22, 8, 20, 2, 60, 82, 8, 61, 20, 8, 39, 69, 65, 8, + 62, 61, 82, 72, 69, 63, 68, 65, 8, 55, 74, 81, 65, 79, 68, 61, 72, 81, 82, 74, 67, + 8, 64, 65, 80, 8, 52, 61, 81, 68, 61, 82, 132, 65, 80, 2, 68, 61, 81, 8, 69, 73, + 8, 72, 61, 82, 66, 65, 74, 64, 65, 74, 8, 52, 65, 63, 68, 74, 82, 74, 67, 80, 70, + 61, 68, 79, 65, 8, 65, 79, 68, 65, 62, 72, 69, 63, 68, 8, 67, 79, 109, 102, 65, 79, + 65, 2, 36, 82, 66, 84, 65, 74, 64, 82, 74, 67, 65, 74, 8, 83, 65, 79, 82, 79, 132, + 61, 63, 68, 81, 18, 8, 61, 72, 80, 8, 62, 65, 69, 8, 64, 65, 79, 8, 40, 81, 61, + 81, 80, 61, 82, 66, 132, 81, 65, 72, 72, 82, 74, 67, 2, 61, 74, 67, 65, 74, 75, 73, + 73, 65, 74, 8, 84, 82, 79, 64, 65, 18, 8, 132, 75, 8, 64, 61, 102, 8, 64, 65, 79, + 8, 69, 73, 8, 40, 72, 61, 81, 8, 83, 75, 79, 67, 65, 132, 65, 68, 65, 74, 65, 2, + 37, 65, 81, 79, 61, 67, 8, 83, 75, 74, 8, 29, 22, 22, 22, 8, 7, 8, 62, 65, 79, + 65, 69, 81, 80, 8, 110, 62, 65, 79, 132, 63, 68, 79, 69, 81, 81, 65, 74, 8, 69, 132, + 81, 20, 8, 44, 74, 8, 64, 65, 79, 2, 43, 61, 82, 78, 81, 132, 61, 63, 68, 65, 8, + 132, 69, 74, 64, 8, 64, 69, 65, 8, 48, 65, 68, 79, 61, 82, 80, 67, 61, 62, 65, 74, + 8, 64, 82, 79, 63, 68, 8, 64, 69, 65, 8, 56, 65, 79, 3, 2, 103, 74, 64, 65, 79, + 82, 74, 67, 65, 74, 8, 64, 65, 79, 8, 40, 69, 74, 66, 61, 68, 79, 81, 65, 74, 8, + 69, 73, 8, 67, 79, 75, 102, 65, 74, 8, 43, 75, 66, 65, 18, 8, 64, 82, 79, 63, 68, + 2, 56, 65, 79, 103, 74, 64, 65, 79, 82, 74, 67, 8, 82, 74, 64, 8, 56, 65, 79, 132, + 81, 103, 79, 71, 82, 74, 67, 8, 64, 65, 79, 8, 37, 65, 72, 65, 82, 63, 68, 81, 82, + 74, 67, 8, 69, 74, 2, 64, 65, 74, 8, 46, 75, 79, 79, 69, 64, 75, 79, 65, 74, 8, + 82, 74, 64, 8, 60, 69, 73, 73, 65, 69, 74, 18, 8, 64, 82, 79, 63, 68, 8, 65, 79, + 68, 65, 62, 72, 69, 63, 68, 65, 8, 56, 65, 79, 3, 2, 103, 74, 64, 65, 79, 82, 74, + 67, 65, 74, 8, 82, 74, 64, 8, 56, 65, 79, 62, 65, 132, 132, 65, 79, 82, 74, 67, 65, + 74, 8, 69, 74, 8, 65, 69, 74, 65, 73, 8, 39, 65, 87, 65, 79, 74, 65, 74, 81, 65, + 74, 3, 2, 87, 69, 73, 73, 65, 79, 8, 132, 75, 84, 69, 65, 8, 64, 82, 79, 63, 68, + 8, 71, 110, 74, 132, 81, 72, 69, 63, 68, 65, 8, 40, 74, 81, 132, 81, 103, 82, 62, 82, + 74, 67, 8, 64, 65, 80, 2, 48, 61, 67, 69, 132, 81, 79, 61, 81, 80, 19, 8, 82, 74, + 64, 8, 53, 81, 61, 64, 81, 83, 65, 79, 75, 79, 64, 74, 65, 81, 65, 74, 19, 53, 69, + 81, 87, 82, 74, 67, 80, 132, 61, 61, 72, 65, 80, 8, 84, 69, 65, 2, 64, 65, 79, 8, + 60, 69, 73, 73, 65, 79, 8, 64, 65, 79, 8, 62, 65, 69, 64, 65, 74, 8, 37, 110, 79, + 67, 65, 79, 73, 65, 69, 132, 81, 65, 79, 8, 68, 65, 79, 83, 75, 79, 67, 65, 79, 82, + 66, 65, 74, 2, 84, 75, 79, 64, 65, 74, 20, 8, 39, 69, 65, 8, 110, 62, 65, 79, 132, + 63, 68, 79, 65, 69, 81, 82, 74, 67, 8, 84, 69, 79, 64, 8, 83, 75, 79, 61, 82, 80, + 132, 69, 63, 68, 81, 72, 69, 63, 68, 2, 25, 27, 22, 22, 8, 7, 8, 62, 65, 81, 79, + 61, 67, 65, 74, 18, 8, 64, 69, 65, 8, 64, 82, 79, 63, 68, 8, 40, 79, 132, 78, 61, + 79, 74, 69, 132, 132, 65, 8, 62, 65, 69, 8, 64, 65, 74, 2, 82, 74, 81, 65, 79, 8, + 64, 65, 79, 132, 65, 72, 62, 65, 74, 8, 40, 81, 61, 81, 80, 74, 82, 73, 73, 65, 79, + 8, 62, 65, 79, 65, 69, 81, 132, 81, 65, 68, 65, 74, 64, 65, 74, 8, 48, 69, 81, 81, + 65, 72, 74, 2, 66, 110, 79, 8, 64, 69, 65, 8, 110, 62, 79, 69, 67, 65, 74, 8, 56, + 65, 79, 84, 61, 72, 81, 82, 74, 67, 80, 67, 65, 62, 103, 82, 64, 65, 8, 74, 69, 63, + 68, 81, 8, 67, 65, 64, 65, 63, 71, 81, 2, 84, 65, 79, 64, 65, 74, 8, 71, 61, 74, + 74, 20, 8, 60, 82, 132, 62, 15, 8, 39, 65, 79, 8, 66, 110, 79, 8, 64, 69, 65, 8, + 55, 74, 81, 65, 79, 68, 61, 72, 81, 82, 74, 67, 8, 64, 65, 79, 8, 48, 109, 62, 65, + 72, 2, 82, 74, 64, 8, 42, 65, 79, 103, 81, 65, 8, 69, 73, 8, 40, 72, 61, 81, 8, + 83, 75, 79, 67, 65, 132, 65, 68, 65, 74, 65, 8, 37, 65, 81, 79, 61, 67, 8, 83, 75, + 74, 8, 28, 25, 22, 22, 8, 20, 2, 68, 61, 81, 8, 132, 69, 63, 68, 8, 65, 62, 65, + 74, 66, 61, 72, 72, 80, 8, 61, 72, 80, 8, 82, 74, 87, 82, 79, 65, 69, 63, 68, 65, + 74, 64, 8, 65, 79, 84, 69, 65, 132, 65, 74, 20, 8, 44, 74, 66, 75, 72, 67, 65, 2, + 64, 65, 79, 8, 61, 73, 8, 23, 20, 8, 36, 78, 79, 69, 72, 8, 82, 74, 64, 8, 23, + 20, 8, 50, 71, 81, 75, 62, 65, 79, 8, 23, 31, 22, 27, 8, 65, 69, 74, 67, 65, 81, + 79, 65, 81, 65, 74, 65, 74, 2, 37, 65, 61, 73, 81, 65, 74, 83, 65, 79, 73, 65, 68, + 79, 82, 74, 67, 65, 74, 8, 82, 74, 64, 8, 64, 65, 79, 8, 64, 61, 73, 69, 81, 81, + 8, 83, 65, 79, 62, 82, 74, 64, 65, 74, 65, 74, 2, 7, 42, 65, 68, 65, 69, 73, 65, + 74, 8, 52, 65, 67, 69, 65, 79, 82, 74, 67, 80, 79, 61, 81, 80, 6, 8, 46, 74, 61, + 63, 71, 18, 8, 66, 110, 67, 65, 8, 69, 63, 68, 8, 69, 74, 2, 36, 62, 132, 63, 68, + 79, 69, 66, 81, 8, 62, 65, 69, 18, 8, 64, 65, 80, 67, 72, 65, 69, 63, 68, 65, 74, + 8, 64, 65, 74, 8, 132, 81, 65, 74, 75, 67, 79, 61, 78, 68, 69, 132, 63, 68, 65, 74, + 8, 37, 65, 79, 69, 63, 68, 81, 2, 110, 62, 65, 79, 8, 64, 69, 65, 8, 53, 69, 81, + 87, 82, 74, 67, 8, 64, 65, 80, 8, 52, 65, 69, 63, 68, 80, 81, 61, 67, 80, 8, 83, + 75, 73, 8, 24, 31, 20, 8, 49, 75, 83, 65, 73, 3, 2, 62, 65, 79, 8, 23, 31, 22, + 27, 20, 8, 36, 82, 80, 8, 72, 65, 81, 87, 81, 65, 79, 65, 73, 8, 69, 132, 81, 8, + 87, 82, 8, 65, 79, 132, 65, 68, 65, 74, 18, 8, 64, 61, 102, 8, 62, 65, 69, 8, 64, + 65, 79, 2, 69, 74, 8, 70, 65, 74, 65, 79, 8, 53, 69, 81, 87, 82, 74, 67, 8, 132, + 81, 61, 81, 81, 67, 65, 66, 82, 74, 64, 65, 74, 65, 74, 8, 51, 79, 103, 132, 69, 64, + 65, 74, 81, 65, 74, 84, 61, 68, 72, 8, 69, 74, 2, 64, 65, 79, 8, 64, 65, 74, 8, + 51, 79, 103, 132, 69, 64, 65, 74, 81, 65, 74, 8, 82, 74, 64, 8, 64, 65, 74, 8, 87, + 84, 65, 69, 81, 65, 74, 8, 56, 69, 87, 65, 19, 51, 79, 103, 66, 69, 64, 65, 74, 81, + 65, 74, 2, 62, 65, 81, 79, 65, 66, 66, 65, 74, 64, 65, 74, 8, 57, 61, 68, 72, 67, + 103, 74, 67, 65, 74, 8, 29, 24, 8, 62, 87, 84, 20, 8, 28, 26, 8, 82, 74, 62, 65, + 132, 63, 68, 79, 69, 65, 62, 65, 74, 65, 2, 53, 81, 69, 73, 73, 87, 65, 81, 81, 65, + 72, 8, 61, 72, 80, 8, 82, 74, 67, 110, 72, 81, 69, 67, 8, 83, 75, 74, 8, 64, 65, + 79, 8, 42, 65, 132, 61, 73, 81, 68, 65, 69, 81, 8, 64, 65, 79, 2, 61, 62, 67, 65, + 67, 65, 62, 65, 74, 65, 74, 8, 53, 72, 69, 73, 73, 87, 65, 81, 81, 65, 72, 8, 61, + 62, 67, 65, 87, 75, 67, 65, 74, 8, 84, 75, 79, 64, 65, 74, 8, 132, 69, 74, 64, 20, + 2, 44, 74, 8, 64, 65, 73, 8, 83, 75, 73, 8, 37, 82, 74, 64, 65, 80, 79, 61, 81, + 8, 87, 82, 79, 8, 36, 82, 80, 66, 110, 68, 79, 82, 74, 67, 8, 64, 65, 80, 2, 57, + 61, 68, 72, 67, 65, 132, 65, 81, 87, 65, 80, 8, 66, 110, 79, 8, 64, 65, 74, 8, 52, + 65, 69, 63, 68, 80, 81, 61, 67, 8, 65, 79, 72, 61, 132, 132, 65, 74, 65, 74, 8, 57, + 61, 68, 72, 3, 2, 79, 65, 67, 72, 65, 73, 65, 74, 81, 8, 83, 75, 73, 8, 24, 30, + 20, 8, 48, 61, 69, 8, 23, 30, 29, 22, 8, 69, 132, 81, 8, 61, 82, 80, 64, 79, 110, + 63, 71, 72, 69, 63, 68, 8, 62, 65, 3, 2, 132, 81, 69, 73, 73, 81, 18, 8, 64, 61, + 102, 8, 53, 81, 69, 73, 73, 87, 65, 81, 81, 65, 72, 18, 8, 84, 65, 72, 63, 68, 65, + 8, 71, 65, 69, 74, 65, 74, 8, 49, 61, 73, 65, 74, 8, 68, 61, 62, 65, 74, 18, 2, + 82, 74, 67, 110, 72, 81, 69, 67, 8, 132, 69, 74, 64, 8, 14, 91, 8, 23, 31, 8, 49, + 79, 20, 8, 24, 15, 18, 8, 64, 61, 102, 8, 64, 69, 65, 8, 82, 74, 67, 110, 72, 81, + 69, 67, 65, 74, 2, 53, 81, 69, 73, 73, 65, 74, 8, 14, 61, 72, 132, 75, 8, 61, 82, + 63, 68, 8, 82, 74, 62, 65, 132, 63, 68, 79, 69, 65, 62, 65, 74, 65, 8, 53, 81, 69, + 73, 73, 87, 65, 81, 81, 65, 72, 18, 8, 62, 65, 69, 2, 41, 65, 132, 81, 132, 81, 65, + 72, 72, 82, 74, 67, 8, 64, 65, 80, 8, 57, 61, 68, 72, 79, 65, 132, 82, 72, 81, 61, + 81, 80, 15, 8, 74, 69, 63, 68, 81, 8, 69, 74, 8, 36, 74, 79, 65, 63, 68, 74, 82, + 74, 67, 2, 71, 75, 73, 73, 65, 74, 8, 14, 91, 8, 24, 22, 8, 36, 62, 132, 20, 8, + 24, 15, 8, 82, 74, 64, 8, 64, 61, 102, 8, 64, 65, 79, 70, 65, 74, 69, 67, 65, 8, + 46, 61, 74, 64, 69, 64, 61, 81, 2, 61, 72, 80, 8, 67, 65, 84, 103, 68, 72, 81, 8, + 67, 69, 72, 81, 18, 8, 61, 82, 66, 8, 84, 65, 72, 63, 68, 65, 74, 8, 132, 69, 63, + 68, 8, 64, 69, 65, 8, 61, 62, 132, 75, 72, 82, 81, 65, 8, 48, 65, 68, 79, 68, 65, + 69, 81, 2, 64, 65, 79, 8, 61, 62, 67, 65, 67, 65, 62, 65, 74, 65, 74, 8, 67, 110, + 72, 81, 69, 67, 65, 74, 8, 53, 81, 69, 73, 73, 65, 74, 8, 14, 61, 72, 132, 75, 8, + 61, 62, 87, 110, 67, 72, 69, 63, 68, 8, 64, 65, 79, 2, 82, 74, 62, 65, 132, 63, 68, + 79, 69, 65, 62, 65, 74, 65, 74, 8, 53, 72, 69, 73, 73, 87, 65, 81, 81, 65, 81, 15, + 8, 83, 65, 79, 65, 69, 74, 69, 67, 81, 8, 14, 91, 8, 24, 30, 8, 36, 62, 132, 20, + 8, 45, 15, 20, 2, 40, 62, 65, 74, 132, 75, 8, 68, 65, 69, 102, 81, 8, 65, 80, 8, + 69, 74, 8, 64, 65, 73, 8, 64, 65, 79, 8, 51, 79, 75, 83, 69, 74, 87, 69, 61, 72, + 75, 79, 64, 74, 82, 74, 67, 8, 14, 83, 75, 73, 8, 24, 31, 20, 8, 45, 82, 74, 69, + 8, 23, 30, 29, 27, 15, 2, 62, 65, 69, 67, 65, 66, 110, 67, 81, 65, 74, 8, 7, 57, + 61, 68, 72, 79, 65, 67, 72, 65, 73, 65, 74, 81, 6, 18, 8, 64, 61, 102, 8, 7, 53, + 81, 69, 73, 73, 87, 65, 81, 81, 65, 72, 8, 75, 68, 74, 65, 8, 49, 61, 73, 65, 74, + 6, 8, 82, 74, 67, 110, 72, 81, 69, 67, 2, 14, 91, 53, 8, 28, 15, 8, 82, 74, 64, + 8, 64, 61, 68, 65, 79, 8, 61, 72, 80, 8, 74, 69, 63, 68, 81, 8, 61, 62, 67, 65, + 67, 65, 62, 65, 74, 8, 87, 82, 8, 62, 65, 81, 79, 61, 63, 68, 81, 65, 74, 2, 132, + 69, 74, 64, 8, 14, 91, 53, 8, 29, 15, 8, 82, 74, 64, 8, 14, 91, 8, 25, 24, 15, + 8, 64, 65, 79, 8, 51, 79, 75, 83, 69, 74, 87, 69, 61, 72, 75, 79, 64, 74, 82, 74, + 67, 8, 132, 63, 68, 79, 65, 69, 62, 81, 2, 83, 75, 79, 18, 8, 64, 61, 102, 8, 64, + 65, 79, 8, 56, 75, 79, 132, 69, 81, 87, 65, 74, 64, 65, 8, 64, 65, 80, 8, 51, 79, + 75, 83, 69, 74, 87, 69, 61, 72, 72, 61, 74, 64, 81, 61, 67, 65, 80, 18, 2, 64, 65, + 132, 132, 65, 74, 8, 53, 81, 65, 72, 72, 82, 74, 67, 8, 87, 82, 8, 64, 69, 65, 132, + 65, 79, 8, 46, 109, 79, 78, 65, 79, 132, 63, 68, 61, 66, 81, 8, 67, 65, 74, 61, 82, + 8, 64, 65, 79, 3, 2, 70, 65, 74, 69, 67, 65, 74, 8, 64, 65, 80, 8, 53, 81, 61, + 64, 83, 65, 79, 75, 79, 64, 74, 65, 81, 65, 74, 83, 75, 79, 132, 81, 65, 68, 65, 79, + 80, 8, 87, 82, 79, 8, 53, 81, 61, 64, 81, 3, 2, 83, 65, 79, 75, 79, 64, 74, 65, + 81, 65, 74, 83, 65, 79, 132, 61, 73, 73, 72, 82, 74, 67, 8, 65, 74, 81, 132, 78, 79, + 69, 63, 68, 81, 18, 8, 74, 61, 63, 68, 8, 64, 65, 74, 8, 56, 75, 79, 3, 2, 132, + 63, 68, 79, 69, 66, 81, 65, 74, 8, 64, 65, 80, 8, 64, 65, 79, 8, 51, 79, 75, 83, + 69, 74, 87, 69, 61, 72, 75, 79, 64, 74, 82, 74, 67, 8, 61, 74, 67, 65, 66, 110, 67, + 81, 65, 74, 8, 57, 61, 68, 72, 3, 2, 79, 65, 67, 72, 65, 73, 65, 74, 81, 80, 8, + 87, 82, 8, 84, 103, 68, 72, 65, 74, 8, 69, 132, 81, 20, 8, 39, 61, 80, 8, 52, 65, + 69, 63, 68, 80, 67, 65, 79, 69, 63, 68, 81, 8, 68, 61, 81, 8, 64, 69, 65, 8, 41, + 79, 61, 67, 65, 2, 69, 74, 8, 65, 69, 74, 65, 73, 8, 14, 69, 74, 8, 37, 64, 20, + 8, 24, 22, 18, 8, 53, 20, 8, 23, 26, 22, 8, 66, 66, 20, 15, 8, 61, 62, 67, 65, + 64, 79, 82, 63, 71, 81, 65, 74, 8, 67, 79, 82, 74, 64, 72, 65, 67, 65, 74, 64, 65, + 74, 8, 82, 74, 64, 8, 110, 62, 65, 79, 3, 2, 87, 65, 82, 67, 65, 74, 64, 65, 74, + 8, 40, 79, 71, 65, 74, 74, 81, 74, 69, 80, 8, 83, 75, 73, 8, 31, 20, 8, 48, 103, + 79, 87, 8, 23, 30, 30, 30, 8, 83, 65, 79, 74, 65, 69, 74, 81, 20, 2, 61, 15, 8, + 40, 80, 8, 66, 110, 68, 79, 81, 8, 61, 82, 80, 18, 8, 64, 61, 102, 8, 62, 65, 69, + 8, 40, 79, 73, 69, 81, 81, 65, 72, 82, 74, 67, 8, 64, 65, 80, 8, 53, 69, 74, 74, + 65, 80, 8, 64, 65, 79, 2, 69, 68, 79, 65, 79, 8, 36, 82, 80, 72, 65, 67, 82, 74, + 67, 8, 74, 61, 63, 68, 8, 132, 81, 79, 65, 69, 81, 69, 67, 65, 74, 8, 14, 42, 65, + 132, 65, 81, 87, 8, 62, 65, 87, 84, 20, 8, 53, 81, 61, 81, 82, 81, 3, 2, 62, 65, + 132, 81, 69, 73, 73, 82, 74, 67, 15, 8, 61, 82, 66, 8, 69, 68, 79, 65, 74, 8, 60, + 84, 65, 63, 71, 8, 82, 74, 64, 8, 64, 69, 65, 8, 49, 61, 81, 82, 79, 8, 64, 65, + 79, 2, 53, 61, 63, 68, 65, 8, 64, 61, 80, 8, 65, 74, 81, 132, 63, 68, 65, 69, 64, + 65, 74, 64, 65, 8, 42, 65, 84, 69, 63, 68, 81, 8, 67, 65, 72, 65, 67, 81, 8, 84, + 65, 79, 64, 65, 74, 8, 73, 110, 132, 132, 65, 18, 2, 64, 61, 102, 8, 62, 65, 69, 8, + 41, 65, 132, 81, 132, 81, 65, 72, 72, 82, 74, 67, 8, 65, 69, 74, 65, 79, 8, 64, 82, + 79, 63, 68, 8, 36, 62, 132, 81, 69, 73, 73, 82, 74, 67, 8, 87, 82, 8, 65, 79, 3, + 2, 87, 69, 65, 72, 65, 74, 64, 65, 74, 8, 48, 65, 68, 79, 68, 65, 69, 81, 8, 69, + 73, 8, 61, 72, 72, 67, 65, 73, 65, 69, 74, 65, 74, 8, 82, 74, 64, 8, 73, 61, 74, + 67, 65, 72, 80, 2, 61, 62, 84, 65, 69, 63, 68, 65, 74, 64, 65, 79, 8, 61, 82, 80, + 64, 79, 110, 63, 71, 72, 69, 63, 68, 65, 79, 8, 7, 56, 75, 79, 132, 63, 68, 79, 69, + 66, 81, 8, 64, 65, 80, 8, 42, 65, 132, 65, 72, 72, 132, 63, 68, 61, 66, 81, 80, 3, + 2, 83, 65, 81, 81, 79, 61, 67, 65, 80, 8, 74, 82, 79, 8, 64, 69, 65, 8, 53, 81, + 69, 73, 73, 65, 74, 8, 64, 65, 79, 70, 65, 74, 69, 67, 65, 74, 18, 8, 84, 65, 72, + 63, 68, 65, 8, 132, 69, 63, 68, 2, 83, 65, 79, 81, 79, 61, 67, 65, 80, 6, 8, 74, + 82, 79, 8, 64, 69, 65, 8, 53, 81, 69, 73, 73, 65, 74, 8, 64, 65, 79, 70, 65, 74, + 69, 67, 65, 74, 18, 8, 84, 65, 72, 63, 68, 65, 8, 132, 69, 63, 68, 2, 61, 74, 8, + 64, 65, 79, 8, 36, 62, 132, 81, 69, 73, 73, 82, 74, 67, 8, 62, 65, 81, 65, 69, 72, + 69, 67, 81, 8, 68, 61, 62, 65, 74, 18, 8, 69, 74, 8, 56, 65, 81, 79, 61, 63, 68, + 81, 8, 67, 65, 32, 4, 2, 61, 74, 8, 64, 65, 79, 8, 36, 62, 132, 81, 69, 73, 73, + 82, 74, 67, 8, 62, 65, 81, 65, 69, 72, 69, 67, 81, 8, 68, 61, 62, 65, 74, 18, 8, + 69, 74, 8, 37, 65, 81, 79, 61, 63, 68, 81, 8, 67, 65, 3, 2, 87, 109, 67, 65, 74, + 8, 84, 65, 79, 64, 65, 74, 8, 71, 109, 74, 74, 81, 65, 74, 18, 8, 84, 65, 69, 72, + 8, 64, 65, 79, 70, 65, 74, 69, 67, 65, 18, 8, 84, 65, 72, 63, 68, 65, 79, 8, 132, + 69, 63, 68, 2, 87, 75, 67, 65, 74, 8, 84, 65, 79, 64, 65, 74, 8, 71, 109, 74, 74, + 81, 65, 74, 18, 8, 84, 65, 69, 72, 8, 64, 65, 79, 70, 65, 74, 69, 67, 65, 18, 8, + 84, 65, 72, 63, 68, 65, 79, 8, 132, 69, 63, 68, 2, 81, 79, 75, 81, 87, 8, 132, 65, + 69, 74, 65, 79, 8, 36, 74, 84, 65, 132, 65, 74, 68, 65, 69, 81, 8, 64, 65, 79, 8, + 36, 62, 132, 81, 69, 73, 73, 82, 74, 67, 8, 65, 74, 81, 68, 103, 72, 81, 18, 2, 81, + 79, 75, 81, 87, 8, 132, 65, 69, 74, 65, 79, 8, 36, 74, 84, 65, 132, 65, 74, 68, 65, + 69, 81, 8, 64, 65, 79, 8, 36, 62, 132, 81, 69, 73, 73, 82, 74, 67, 8, 65, 74, 81, + 68, 103, 72, 81, 18, 2, 74, 69, 63, 68, 81, 8, 74, 82, 79, 8, 61, 82, 66, 8, 66, + 65, 69, 74, 8, 53, 81, 69, 73, 73, 79, 65, 63, 68, 81, 8, 83, 65, 79, 87, 69, 63, + 68, 81, 65, 18, 8, 132, 75, 74, 64, 65, 79, 74, 8, 64, 61, 4, 19, 2, 74, 69, 63, + 68, 81, 8, 74, 82, 79, 8, 61, 82, 66, 8, 132, 65, 69, 74, 8, 53, 81, 69, 73, 73, + 79, 65, 63, 68, 81, 8, 83, 65, 79, 87, 69, 63, 68, 81, 65, 18, 8, 132, 75, 74, 64, + 65, 79, 74, 8, 64, 61, 3, 2, 73, 69, 81, 8, 61, 82, 63, 68, 8, 64, 69, 65, 8, + 40, 74, 81, 132, 63, 68, 65, 69, 64, 82, 74, 67, 8, 36, 62, 65, 79, 8, 64, 65, 74, + 8, 87, 82, 79, 8, 36, 62, 132, 81, 69, 73, 73, 82, 74, 67, 2, 73, 69, 81, 8, 61, + 82, 63, 68, 8, 64, 69, 65, 8, 40, 74, 81, 132, 63, 68, 65, 69, 64, 82, 74, 67, 8, + 110, 62, 65, 79, 8, 64, 65, 74, 8, 87, 82, 79, 8, 36, 62, 132, 81, 69, 73, 73, 82, + 74, 67, 2, 132, 81, 65, 68, 65, 74, 64, 65, 74, 8, 36, 74, 81, 79, 61, 67, 8, 64, + 65, 74, 8, 110, 62, 124, 69, 67, 65, 74, 8, 37, 65, 81, 65, 69, 72, 69, 67, 81, 65, + 74, 8, 61, 74, 68, 65, 69, 73, 8, 67, 65, 62, 65, 18, 2, 132, 81, 65, 68, 65, 74, + 64, 65, 74, 8, 36, 74, 81, 79, 61, 67, 8, 64, 65, 74, 8, 110, 62, 79, 69, 67, 65, + 74, 8, 37, 65, 81, 65, 69, 72, 69, 67, 81, 65, 74, 8, 61, 74, 68, 65, 69, 73, 8, + 67, 65, 62, 65, 18, 2, 65, 79, 8, 73, 69, 81, 68, 69, 74, 8, 71, 65, 69, 74, 65, + 8, 61, 74, 64, 65, 79, 65, 8, 7, 53, 81, 65, 72, 72, 82, 74, 67, 8, 87, 82, 110, + 79, 8, 53, 61, 63, 68, 65, 6, 8, 65, 69, 74, 74, 65, 68, 73, 65, 18, 2, 65, 79, + 8, 73, 69, 81, 68, 69, 74, 8, 71, 65, 69, 74, 65, 8, 61, 74, 64, 65, 79, 65, 8, + 7, 53, 81, 65, 72, 72, 82, 74, 67, 8, 87, 82, 79, 8, 53, 61, 63, 68, 65, 6, 8, + 65, 69, 74, 74, 65, 68, 73, 65, 18, 2, 84, 69, 65, 8, 64, 69, 65, 70, 65, 74, 69, + 67, 65, 74, 8, 53, 81, 110, 72, 73, 62, 65, 79, 65, 63, 68, 81, 69, 67, 81, 65, 74, + 18, 8, 64, 69, 65, 8, 132, 69, 63, 68, 8, 65, 74, 81, 84, 65, 64, 65, 79, 2, 84, + 69, 65, 8, 64, 69, 65, 70, 65, 74, 69, 67, 65, 74, 8, 53, 81, 69, 73, 73, 62, 65, + 79, 65, 63, 68, 81, 69, 67, 81, 65, 74, 18, 8, 64, 69, 65, 8, 132, 69, 63, 68, 8, + 65, 74, 81, 84, 65, 64, 65, 79, 2, 110, 63, 62, 65, 79, 68, 61, 82, 102, 81, 8, 74, + 69, 63, 68, 81, 8, 65, 69, 74, 67, 65, 66, 82, 74, 64, 65, 74, 18, 8, 75, 64, 65, + 79, 8, 83, 75, 79, 8, 64, 65, 79, 8, 36, 62, 132, 81, 69, 73, 73, 82, 73, 67, 87, + 2, 110, 62, 65, 79, 68, 61, 82, 78, 81, 8, 74, 69, 63, 68, 81, 8, 65, 69, 74, 67, + 65, 66, 82, 74, 64, 65, 74, 18, 8, 75, 64, 65, 79, 8, 83, 75, 79, 8, 64, 65, 79, + 8, 36, 62, 132, 81, 69, 73, 73, 82, 74, 67, 2, 61, 82, 80, 8, 64, 65, 79, 8, 56, + 65, 79, 132, 61, 69, 74, 73, 72, 82, 74, 67, 8, 65, 74, 81, 132, 65, 79, 74, 81, 8, + 68, 61, 62, 65, 74, 18, 8, 82, 74, 64, 8, 132, 65, 69, 74, 65, 79, 2, 61, 82, 80, + 8, 64, 65, 79, 8, 56, 65, 79, 132, 61, 73, 73, 72, 82, 74, 67, 8, 65, 74, 81, 66, + 65, 79, 74, 81, 8, 68, 61, 62, 65, 74, 18, 8, 82, 74, 64, 8, 132, 65, 69, 74, 65, + 79, 2, 36, 62, 132, 69, 63, 68, 81, 8, 64, 65, 73, 67, 65, 73, 103, 102, 8, 74, 82, + 79, 8, 64, 61, 64, 82, 79, 63, 68, 13, 65, 74, 81, 132, 78, 79, 75, 63, 68, 65, 74, + 8, 84, 65, 79, 64, 65, 18, 2, 36, 62, 132, 69, 63, 68, 81, 8, 64, 65, 73, 67, 65, + 73, 103, 102, 8, 74, 82, 79, 8, 64, 61, 64, 82, 79, 63, 68, 8, 65, 74, 81, 132, 78, + 79, 75, 63, 68, 65, 74, 8, 84, 65, 79, 64, 65, 18, 2, 64, 61, 132, 102, 68, 65, 79, + 8, 68, 69, 74, 132, 69, 63, 68, 81, 72, 69, 63, 68, 8, 64, 65, 79, 8, 48, 65, 68, + 79, 68, 65, 69, 81, 80, 62, 65, 79, 65, 63, 68, 74, 82, 74, 67, 8, 65, 62, 65, 74, + 132, 75, 4, 2, 64, 61, 102, 8, 65, 79, 8, 68, 69, 74, 132, 69, 63, 68, 81, 72, 69, + 63, 68, 8, 64, 65, 79, 8, 48, 65, 68, 79, 68, 65, 69, 81, 80, 62, 65, 79, 65, 63, + 68, 74, 82, 74, 67, 8, 65, 62, 65, 74, 132, 75, 2, 83, 69, 65, 8, 64, 69, 65, 8, + 61, 62, 84, 65, 132, 65, 74, 64, 65, 74, 8, 53, 81, 69, 73, 73, 62, 65, 79, 65, 63, + 68, 81, 69, 67, 81, 65, 74, 8, 62, 65, 68, 61, 74, 64, 65, 72, 81, 2, 84, 69, 65, + 8, 64, 69, 65, 8, 61, 62, 84, 65, 132, 65, 74, 64, 65, 74, 8, 53, 81, 69, 73, 73, + 62, 65, 79, 65, 63, 68, 81, 69, 67, 81, 65, 74, 8, 62, 65, 68, 61, 74, 64, 65, 72, + 81, 2, 37, 65, 81, 65, 69, 72, 69, 67, 81, 65, 74, 8, 51, 65, 79, 132, 75, 74, 65, + 74, 8, 67, 65, 67, 65, 74, 84, 103, 79, 81, 69, 67, 8, 14, 64, 61, 73, 61, 72, 69, + 67, 8, 25, 26, 8, 11, 18, 8, 23, 24, 18, 27, 28, 8, 22, 22, 2, 37, 65, 81, 65, + 69, 72, 69, 67, 81, 65, 74, 8, 51, 65, 79, 132, 75, 74, 65, 74, 8, 67, 65, 67, 65, + 74, 84, 103, 79, 81, 69, 67, 8, 14, 64, 61, 73, 61, 72, 69, 67, 8, 25, 26, 8, 18, + 21, 30, 18, 8, 23, 24, 18, 27, 28, 8, 18, 2, 67, 61, 79, 8, 23, 25, 18, 31, 27, + 8, 22, 8, 75, 64, 65, 79, 8, 24, 26, 8, 22, 11, 20, 8, 39, 61, 83, 75, 74, 8, + 61, 62, 67, 65, 68, 65, 74, 64, 8, 23, 22, 22, 8, 0, 12, 8, 62, 69, 80, 8, 24, + 27, 22, 8, 11, 2, 67, 61, 79, 8, 23, 25, 18, 31, 27, 8, 11, 8, 75, 64, 65, 79, + 8, 24, 26, 8, 31, 22, 15, 20, 8, 39, 61, 83, 75, 74, 8, 61, 62, 67, 65, 68, 65, + 74, 64, 8, 23, 22, 22, 8, 62, 69, 80, 8, 24, 27, 22, 2, 73, 61, 82, 86, 63, 68, + 73, 61, 72, 8, 67, 61, 79, 8, 62, 69, 80, 8, 30, 22, 22, 8, 0, 12, 8, 75, 64, + 65, 79, 8, 31, 22, 22, 8, 11, 18, 8, 73, 75, 0, 129, 74, 61, 81, 72, 69, 63, 68, + 8, 61, 62, 87, 69, 65, 68, 65, 74, 64, 2, 73, 61, 74, 63, 68, 73, 61, 72, 8, 67, + 61, 79, 8, 62, 69, 80, 8, 30, 22, 22, 8, 7, 8, 75, 64, 65, 79, 8, 31, 22, 22, + 8, 0, 133, 8, 73, 75, 74, 61, 81, 72, 69, 63, 68, 8, 61, 62, 87, 69, 65, 68, 65, + 74, 64, 2, 83, 75, 73, 8, 42, 65, 84, 69, 74, 74, 8, 69, 74, 8, 51, 79, 75, 87, + 65, 74, 81, 33, 8, 23, 22, 8, 21, 18, 8, 23, 22, 18, 27, 8, 22, 18, 8, 23, 24, + 18, 29, 27, 8, 22, 11, 18, 8, 24, 22, 8, 11, 2, 83, 75, 73, 8, 42, 65, 84, 69, + 74, 74, 8, 69, 74, 8, 51, 79, 75, 87, 65, 74, 81, 32, 8, 23, 22, 8, 18, 8, 23, + 22, 18, 27, 8, 31, 22, 18, 8, 23, 24, 18, 29, 27, 8, 7, 18, 8, 24, 22, 2, 40, + 74, 80, 81, 65, 68, 65, 74, 64, 8, 64, 65, 79, 8, 36, 82, 66, 84, 61, 74, 64, 8, + 83, 75, 74, 8, 23, 24, 8, 27, 22, 22, 8, 0, 12, 8, 62, 69, 80, 8, 23, 26, 8, + 22, 22, 22, 2, 40, 74, 80, 81, 65, 68, 65, 74, 64, 8, 64, 65, 79, 8, 36, 82, 66, + 84, 61, 74, 64, 8, 83, 75, 74, 8, 23, 24, 8, 27, 22, 22, 8, 7, 8, 62, 69, 80, + 8, 23, 26, 8, 22, 22, 22, 2, 71, 72, 65, 69, 74, 65, 79, 65, 8, 54, 65, 69, 72, + 104, 8, 65, 79, 67, 65, 62, 65, 74, 8, 29, 28, 22, 8, 11, 18, 8, 30, 22, 22, 8, + 46, 18, 8, 30, 24, 22, 8, 18, 18, 8, 23, 22, 22, 22, 8, 73, 75, 74, 61, 81, 72, + 69, 63, 68, 2, 71, 72, 65, 69, 74, 65, 79, 65, 8, 54, 65, 69, 72, 65, 8, 65, 79, + 67, 65, 62, 65, 74, 8, 29, 28, 22, 8, 18, 8, 30, 22, 22, 8, 18, 8, 30, 24, 22, + 8, 48, 18, 8, 23, 22, 22, 22, 8, 7, 8, 73, 75, 74, 61, 81, 72, 69, 63, 68, 2, + 75, 64, 65, 79, 8, 24, 22, 22, 22, 8, 7, 8, 62, 69, 80, 8, 24, 28, 22, 22, 8, + 73, 75, 74, 61, 81, 72, 69, 63, 68, 18, 8, 73, 61, 85, 69, 73, 61, 72, 8, 26, 22, + 22, 22, 8, 0, 111, 18, 26, 29, 22, 22, 2, 75, 64, 65, 79, 8, 24, 22, 22, 22, 8, + 7, 8, 62, 69, 80, 8, 24, 28, 22, 22, 8, 11, 8, 73, 75, 74, 61, 81, 72, 69, 63, + 68, 18, 8, 73, 61, 85, 69, 73, 61, 72, 8, 26, 22, 22, 22, 8, 18, 8, 26, 29, 22, + 22, 2, 26, 30, 22, 22, 8, 26, 31, 22, 22, 8, 18, 8, 27, 22, 22, 22, 8, 18, 8, + 27, 24, 22, 22, 8, 18, 8, 27, 25, 22, 22, 8, 18, 8, 28, 22, 22, 22, 18, 20, 2, + 26, 30, 22, 22, 8, 7, 18, 8, 26, 31, 22, 22, 8, 7, 18, 8, 27, 22, 22, 22, 8, + 18, 8, 27, 24, 22, 22, 8, 7, 18, 8, 27, 25, 22, 22, 8, 7, 18, 8, 28, 22, 22, + 22, 8, 0, 12, 20, 2, 84, 65, 79, 64, 65, 20, 8, 14, 60, 82, 8, 62, 15, 8, 36, + 82, 80, 8, 64, 69, 65, 132, 65, 74, 8, 42, 79, 110, 74, 64, 65, 74, 8, 71, 75, 73, + 73, 81, 8, 64, 61, 80, 8, 52, 65, 69, 63, 68, 80, 67, 65, 79, 69, 63, 68, 81, 8, + 25, 26, 30, 25, 30, 8, 20, 2, 84, 65, 79, 64, 65, 20, 8, 14, 60, 82, 132, 110, 62, + 15, 8, 36, 82, 80, 8, 64, 69, 65, 132, 65, 74, 8, 42, 79, 110, 74, 64, 65, 74, 8, + 71, 75, 73, 73, 81, 8, 64, 61, 80, 8, 52, 65, 69, 63, 68, 80, 67, 65, 79, 69, 63, + 68, 81, 8, 25, 26, 30, 25, 30, 8, 20, 2, 87, 82, 8, 64, 65, 73, 8, 53, 63, 68, + 72, 82, 102, 8, 64, 61, 102, 8, 61, 72, 80, 83, 65, 79, 81, 66, 65, 81, 65, 74, 8, + 69, 74, 8, 64, 65, 79, 8, 42, 65, 74, 65, 79, 61, 72, 19, 4, 2, 87, 82, 8, 64, + 65, 73, 8, 53, 63, 68, 72, 82, 102, 18, 8, 64, 61, 102, 8, 61, 72, 80, 8, 7, 83, + 65, 79, 81, 79, 65, 81, 65, 74, 8, 69, 74, 8, 64, 65, 79, 8, 42, 65, 74, 65, 79, + 61, 72, 3, 2, 83, 65, 79, 132, 61, 73, 73, 72, 82, 74, 67, 6, 8, 82, 82, 79, 8, + 64, 65, 79, 8, 36, 71, 81, 69, 65, 74, 62, 65, 132, 69, 81, 87, 8, 64, 65, 79, 70, + 65, 74, 69, 67, 65, 74, 8, 36, 71, 81, 69, 75, 4, 2, 83, 65, 79, 132, 61, 73, 73, + 72, 82, 74, 67, 6, 8, 74, 82, 79, 8, 64, 65, 79, 8, 36, 71, 81, 69, 65, 74, 62, + 65, 132, 69, 81, 87, 8, 64, 65, 79, 70, 65, 74, 69, 67, 65, 74, 8, 36, 71, 81, 69, + 75, 3, 2, 68, 61, 103, 0, 129, 79, 65, 8, 61, 74, 67, 65, 132, 65, 68, 65, 74, 8, + 84, 65, 79, 64, 65, 74, 8, 71, 109, 74, 74, 74, 65, 18, 8, 84, 65, 72, 63, 68, 65, + 8, 62, 65, 69, 8, 64, 65, 79, 8, 36, 62, 4, 2, 74, 103, 79, 65, 8, 61, 74, 67, + 65, 132, 65, 68, 65, 74, 8, 84, 65, 79, 64, 65, 74, 8, 71, 109, 74, 74, 65, 18, 8, + 84, 65, 72, 63, 68, 65, 8, 62, 65, 69, 8, 64, 65, 79, 8, 36, 62, 3, 2, 132, 81, + 69, 73, 73, 74, 82, 74, 67, 8, 69, 68, 79, 65, 8, 53, 81, 69, 73, 73, 65, 8, 61, + 62, 67, 65, 67, 65, 62, 65, 74, 8, 68, 61, 62, 65, 74, 13, 8, 82, 74, 64, 8, 64, + 61, 102, 2, 132, 81, 69, 73, 73, 82, 74, 67, 8, 69, 68, 79, 65, 8, 53, 81, 69, 73, + 73, 65, 8, 61, 62, 67, 65, 67, 65, 62, 65, 74, 8, 68, 61, 62, 65, 74, 8, 82, 74, + 64, 8, 64, 61, 102, 2, 65, 80, 8, 72, 65, 69, 74, 65, 74, 8, 55, 74, 81, 65, 79, + 132, 63, 68, 69, 65, 64, 8, 73, 61, 63, 68, 65, 18, 8, 75, 62, 8, 64, 82, 79, 63, + 68, 8, 64, 69, 65, 8, 36, 62, 132, 81, 69, 74, 4, 2, 65, 80, 8, 72, 65, 69, 74, + 65, 74, 8, 55, 74, 81, 65, 79, 132, 63, 68, 69, 65, 64, 8, 73, 61, 63, 68, 65, 18, + 8, 75, 62, 8, 64, 82, 79, 63, 68, 8, 64, 69, 65, 8, 36, 62, 132, 81, 69, 73, 3, + 2, 73, 82, 74, 67, 8, 65, 69, 74, 65, 8, 79, 65, 72, 61, 81, 69, 83, 65, 18, 8, + 61, 62, 132, 75, 72, 82, 81, 65, 8, 75, 64, 65, 79, 8, 77, 82, 61, 72, 69, 66, 69, + 87, 69, 65, 79, 81, 71, 8, 48, 65, 68, 79, 4, 2, 73, 82, 74, 67, 8, 65, 69, 74, + 65, 8, 79, 65, 72, 61, 81, 69, 83, 65, 18, 8, 61, 62, 132, 75, 72, 82, 81, 65, 8, + 75, 64, 65, 79, 8, 77, 82, 61, 72, 69, 66, 69, 87, 69, 65, 79, 81, 65, 8, 48, 65, + 68, 79, 3, 2, 68, 65, 69, 81, 18, 8, 23, 24, 23, 30, 21, 23, 8, 124, 63, 18, 8, + 25, 21, 27, 8, 124, 65, 20, 18, 8, 54, 65, 69, 72, 73, 65, 74, 67, 65, 8, 83, 75, + 74, 8, 26, 22, 22, 22, 8, 0, 12, 20, 2, 68, 65, 69, 81, 18, 8, 23, 24, 23, 30, + 21, 23, 8, 24, 63, 20, 18, 8, 25, 21, 27, 8, 124, 63, 20, 18, 8, 54, 65, 69, 72, + 73, 65, 74, 67, 65, 8, 83, 75, 74, 8, 26, 22, 22, 22, 8, 20, 2, 27, 24, 25, 27, + 21, 27, 18, 8, 25, 24, 23, 21, 24, 26, 8, 124, 63, 20, 18, 8, 25, 24, 24, 21, 24, + 26, 18, 8, 27, 31, 25, 27, 21, 24, 24, 8, 45, 44, 45, 18, 8, 25, 24, 26, 21, 24, + 24, 8, 29, 63, 20, 18, 8, 24, 28, 8, 11, 2, 27, 24, 25, 27, 21, 27, 18, 8, 25, + 24, 23, 21, 24, 26, 8, 124, 63, 20, 18, 8, 25, 24, 24, 21, 24, 26, 18, 8, 27, 24, + 25, 27, 21, 24, 24, 8, 45, 44, 45, 18, 8, 25, 24, 26, 21, 24, 24, 8, 124, 63, 20, + 18, 8, 24, 28, 8, 11, 22, 2, 45, 56, 20, 8, 24, 25, 27, 21, 24, 24, 8, 56, 69, + 20, 18, 8, 24, 25, 26, 25, 26, 21, 23, 8, 45, 45, 20, 8, 30, 28, 27, 26, 21, 24, + 24, 8, 45, 124, 65, 20, 18, 8, 25, 26, 25, 26, 21, 24, 25, 18, 8, 23, 24, 8, 11, + 2, 44, 56, 18, 8, 24, 25, 27, 21, 24, 24, 8, 56, 124, 63, 20, 18, 8, 24, 25, 26, + 25, 26, 21, 23, 8, 44, 45, 20, 18, 8, 30, 28, 27, 26, 21, 24, 24, 8, 44, 8, 124, + 63, 20, 18, 8, 25, 26, 25, 26, 21, 24, 25, 18, 8, 23, 24, 8, 20, 2, 44, 8, 25, + 25, 25, 25, 25, 21, 24, 26, 8, 83, 20, 18, 8, 23, 24, 24, 23, 24, 21, 26, 30, 8, + 124, 63, 20, 18, 8, 25, 24, 30, 29, 24, 30, 8, 45, 45, 20, 8, 25, 24, 31, 21, 24, + 25, 8, 45, 65, 20, 18, 8, 30, 30, 8, 22, 20, 2, 44, 45, 20, 18, 8, 25, 25, 25, + 25, 25, 21, 24, 26, 8, 124, 63, 20, 18, 8, 23, 24, 24, 23, 24, 21, 26, 30, 8, 124, + 63, 20, 18, 8, 25, 24, 30, 21, 24, 30, 8, 44, 45, 18, 8, 25, 24, 31, 21, 24, 25, + 8, 124, 63, 20, 18, 8, 30, 30, 8, 22, 20, 2, 23, 23, 25, 21, 23, 25, 8, 110, 74, + 64, 8, 25, 25, 23, 21, 23, 30, 8, 124, 63, 20, 8, 82, 73, 8, 73, 65, 68, 79, 65, + 79, 65, 8, 23, 22, 22, 8, 7, 18, 20, 2, 23, 23, 25, 21, 23, 25, 8, 82, 74, 64, + 8, 25, 25, 23, 21, 23, 25, 8, 124, 63, 20, 8, 82, 73, 8, 73, 65, 68, 79, 65, 79, + 65, 8, 23, 22, 22, 8, 21, 20, 2, 25, 26, 30, 25, 30, 8, 46, 18, 8, 24, 30, 30, + 30, 8, 0, 12, 8, 82, 74, 64, 8, 23, 22, 22, 22, 8, 7, 48, 61, 110, 63, 68, 73, + 61, 72, 8, 30, 30, 30, 8, 75, 64, 65, 79, 8, 23, 24, 8, 22, 22, 22, 8, 20, 2, + 25, 26, 30, 25, 30, 8, 97, 18, 8, 24, 30, 30, 30, 8, 11, 8, 82, 74, 64, 8, 23, + 22, 22, 22, 8, 20, 8, 48, 61, 74, 63, 68, 73, 61, 72, 8, 30, 30, 30, 8, 75, 64, + 65, 79, 8, 23, 24, 8, 22, 22, 22, 8, 7, 20, 2, 43, 65, 79, 71, 109, 73, 73, 72, + 69, 63, 68, 8, 82, 74, 81, 65, 79, 8, 26, 22, 22, 8, 11, 18, 8, 57, 65, 79, 71, + 81, 61, 67, 65, 8, 27, 22, 22, 8, 32, 8, 36, 74, 8, 41, 65, 81, 65, 79, 81, 61, + 67, 65, 74, 2, 43, 65, 79, 71, 109, 73, 73, 72, 69, 63, 68, 8, 82, 74, 81, 65, 79, + 8, 26, 22, 22, 8, 0, 107, 18, 8, 57, 65, 79, 71, 81, 61, 67, 65, 8, 27, 22, 22, + 8, 43, 18, 20, 8, 36, 74, 8, 41, 65, 69, 65, 79, 81, 61, 67, 65, 74, 2, 23, 24, + 22, 22, 20, 8, 28, 22, 8, 20, 8, 56, 75, 74, 8, 29, 22, 8, 62, 69, 80, 8, 87, + 82, 8, 24, 25, 22, 8, 46, 20, 8, 39, 61, 79, 82, 74, 81, 65, 79, 8, 27, 22, 8, + 26, 2, 23, 24, 22, 22, 8, 20, 8, 28, 22, 8, 20, 8, 56, 75, 74, 8, 29, 22, 8, + 62, 69, 80, 8, 87, 82, 8, 24, 25, 22, 8, 20, 8, 39, 61, 79, 82, 74, 81, 65, 79, + 8, 27, 22, 11, 2, 40, 69, 74, 62, 65, 68, 61, 72, 81, 8, 75, 64, 65, 79, 8, 73, + 65, 68, 79, 20, 8, 54, 65, 69, 72, 65, 8, 87, 65, 69, 67, 65, 74, 8, 61, 82, 66, + 8, 25, 22, 8, 11, 21, 8, 14, 25, 22, 22, 7, 15, 18, 8, 25, 24, 8, 22, 11, 8, + 14, 26, 22, 22, 2, 40, 69, 74, 62, 65, 68, 61, 72, 81, 8, 75, 64, 65, 79, 8, 73, + 65, 68, 79, 20, 8, 54, 65, 69, 72, 65, 8, 87, 65, 69, 67, 65, 74, 8, 61, 82, 66, + 8, 25, 22, 11, 8, 14, 25, 22, 22, 8, 11, 10, 3, 15, 18, 3, 8, 25, 24, 8, 11, + 8, 14, 26, 22, 22, 2, 26, 11, 8, 26, 22, 8, 22, 8, 14, 27, 22, 22, 8, 46, 15, + 18, 8, 26, 27, 8, 11, 8, 14, 29, 22, 22, 8, 14, 15, 20, 8, 36, 72, 69, 63, 68, + 8, 110, 62, 65, 79, 8, 28, 22, 8, 11, 14, 23, 23, 26, 26, 8, 11, 0, 106, 61, 32, + 2, 15, 18, 8, 26, 22, 8, 11, 8, 27, 22, 22, 8, 7, 14, 15, 18, 8, 26, 27, 8, + 11, 8, 14, 29, 22, 22, 8, 7, 15, 20, 8, 36, 82, 63, 68, 8, 110, 62, 65, 79, 8, + 28, 22, 11, 8, 14, 23, 23, 26, 26, 8, 18, 15, 18, 2, 28, 27, 8, 7, 11, 8, 14, + 23, 27, 22, 22, 8, 15, 18, 8, 28, 29, 8, 22, 22, 8, 14, 23, 27, 27, 27, 8, 7, + 15, 20, 8, 23, 23, 22, 30, 24, 8, 7, 8, 51, 86, 71, 8, 61, 82, 80, 132, 69, 63, + 68, 81, 80, 83, 75, 72, 72, 33, 8, 23, 31, 23, 30, 2, 28, 27, 8, 6, 8, 14, 23, + 27, 22, 22, 8, 7, 15, 18, 8, 28, 29, 8, 11, 8, 14, 23, 27, 27, 27, 8, 7, 15, + 20, 8, 23, 23, 22, 30, 24, 8, 37, 86, 71, 8, 61, 82, 80, 132, 69, 63, 68, 81, 80, + 83, 75, 72, 72, 33, 8, 23, 31, 23, 30, 2, 62, 83, 61, 65, 68, 65, 74, 8, 53, 61, + 73, 73, 65, 72, 68, 65, 69, 87, 82, 74, 67, 8, 48, 65, 81, 68, 75, 64, 65, 8, 124, + 65, 20, 8, 26, 27, 22, 27, 8, 7, 8, 64, 69, 65, 8, 52, 65, 61, 72, 132, 63, 68, + 82, 72, 65, 8, 39, 69, 80, 78, 75, 132, 69, 81, 69, 75, 74, 80, 66, 75, 74, 64, 80, + 80, 19, 2, 67, 65, 68, 65, 74, 8, 53, 61, 73, 73, 65, 72, 68, 65, 69, 87, 82, 74, + 67, 8, 48, 65, 81, 68, 75, 64, 65, 8, 124, 63, 20, 8, 26, 27, 22, 27, 8, 64, 69, + 65, 8, 52, 65, 61, 72, 132, 63, 68, 82, 72, 65, 8, 39, 69, 80, 78, 75, 132, 69, 81, + 69, 75, 74, 80, 66, 75, 74, 64, 80, 2, 37, 65, 132, 81, 69, 73, 73, 82, 74, 67, 65, + 74, 8, 52, 61, 81, 68, 61, 82, 132, 65, 80, 20, 8, 29, 28, 22, 28, 8, 0, 12, 8, + 66, 110, 85, 8, 41, 65, 72, 64, 81, 79, 82, 78, 78, 65, 74, 8, 30, 25, 8, 68, 21, + 8, 65, 69, 74, 65, 8, 64, 75, 63, 68, 8, 29, 29, 24, 8, 11, 11, 2, 37, 65, 132, + 81, 69, 73, 73, 82, 74, 67, 65, 74, 8, 52, 61, 81, 68, 61, 82, 132, 65, 80, 20, 8, + 29, 28, 22, 28, 8, 66, 110, 79, 8, 41, 65, 72, 64, 81, 79, 82, 78, 78, 65, 74, 8, + 30, 25, 8, 7, 22, 8, 65, 69, 74, 65, 8, 64, 75, 63, 68, 8, 29, 29, 24, 27, 8, + 0, 106, 2, 37, 65, 69, 8, 62, 65, 87, 65, 69, 63, 68, 74, 65, 81, 8, 47, 61, 67, + 65, 78, 72, 61, 74, 20, 8, 29, 29, 27, 30, 8, 23, 30, 26, 30, 8, 14, 43, 65, 66, + 81, 8, 30, 15, 8, 23, 29, 8, 27, 11, 18, 8, 24, 31, 8, 15, 18, 8, 25, 26, 8, + 11, 21, 8, 82, 74, 64, 8, 27, 31, 8, 11, 20, 2, 37, 65, 69, 8, 62, 65, 87, 65, + 69, 63, 68, 74, 65, 81, 8, 47, 61, 67, 65, 78, 72, 61, 74, 20, 8, 29, 29, 27, 30, + 8, 7, 8, 23, 30, 26, 30, 8, 14, 43, 65, 66, 81, 8, 30, 15, 8, 23, 29, 8, 7, + 11, 18, 8, 24, 31, 8, 21, 18, 8, 25, 26, 8, 11, 8, 82, 74, 64, 8, 27, 31, 8, + 7, 21, 20, 2, 23, 28, 8, 11, 18, 8, 23, 23, 31, 22, 18, 8, 23, 24, 11, 18, 8, + 26, 26, 8, 37, 69, 80, 8, 87, 82, 32, 8, 31, 31, 22, 11, 8, 75, 64, 65, 79, 8, + 31, 31, 18, 31, 27, 75, 18, 8, 39, 61, 79, 110, 62, 65, 79, 8, 67, 65, 68, 65, 74, + 64, 2, 23, 28, 8, 11, 18, 8, 23, 23, 22, 18, 8, 23, 24, 22, 22, 18, 8, 26, 26, + 22, 22, 20, 8, 37, 69, 80, 8, 87, 82, 8, 31, 31, 22, 22, 8, 75, 64, 65, 79, 8, + 31, 31, 18, 31, 27, 21, 20, 8, 39, 61, 79, 110, 62, 65, 79, 8, 67, 65, 68, 65, 74, + 64, 2, 25, 24, 25, 25, 27, 8, 22, 11, 8, 36, 82, 63, 68, 8, 25, 24, 18, 25, 24, + 8, 22, 22, 20, 8, 48, 61, 74, 63, 68, 73, 61, 72, 8, 73, 65, 68, 79, 8, 61, 72, + 80, 8, 27, 27, 20, 20, 8, 23, 24, 8, 11, 8, 24, 25, 2, 24, 25, 18, 25, 27, 8, + 7, 20, 8, 36, 82, 63, 68, 8, 25, 24, 18, 25, 24, 8, 31, 15, 22, 20, 8, 48, 61, + 74, 63, 68, 73, 61, 72, 8, 73, 65, 68, 79, 8, 61, 72, 80, 8, 27, 27, 8, 132, 20, + 8, 23, 24, 8, 11, 8, 24, 25, 2, 24, 25, 22, 8, 11, 8, 71, 109, 74, 74, 65, 74, + 8, 55, 72, 73, 65, 74, 4, 36, 72, 72, 65, 65, 8, 62, 61, 82, 72, 69, 63, 68, 65, + 8, 83, 75, 74, 8, 37, 61, 79, 82, 74, 81, 65, 79, 8, 29, 22, 8, 15, 8, 64, 65, + 79, 8, 23, 20, 21, 8, 65, 69, 74, 65, 8, 87, 82, 8, 73, 103, 74, 74, 72, 69, 63, + 68, 65, 74, 20, 23, 31, 23, 29, 18, 2, 25, 22, 8, 29, 8, 71, 109, 74, 74, 65, 74, + 8, 55, 72, 73, 65, 74, 36, 72, 72, 65, 65, 8, 62, 61, 82, 72, 69, 63, 68, 65, 8, + 83, 75, 74, 8, 39, 61, 79, 82, 74, 81, 65, 79, 8, 29, 22, 8, 31, 22, 22, 8, 64, + 65, 79, 8, 23, 8, 11, 8, 65, 69, 74, 65, 8, 87, 82, 8, 73, 103, 74, 74, 72, 69, + 63, 68, 65, 74, 8, 23, 31, 23, 29, 2, 65, 69, 74, 65, 79, 8, 55, 74, 81, 65, 79, + 80, 81, 110, 81, 87, 82, 74, 67, 19, 31, 27, 8, 31, 15, 18, 8, 28, 26, 8, 64, 65, + 79, 8, 47, 65, 68, 79, 78, 72, 103, 74, 65, 20, 8, 27, 29, 8, 18, 8, 84, 69, 64, + 65, 79, 132, 78, 79, 65, 63, 68, 65, 74, 20, 2, 65, 69, 74, 65, 79, 8, 55, 74, 81, + 65, 79, 80, 81, 110, 81, 87, 82, 74, 67, 8, 31, 27, 8, 31, 22, 18, 8, 28, 26, 8, + 29, 8, 64, 65, 79, 8, 47, 65, 68, 79, 78, 72, 103, 74, 65, 20, 8, 27, 29, 8, 7, + 8, 84, 69, 64, 65, 79, 132, 78, 79, 65, 63, 68, 65, 74, 20, 2, 23, 31, 8, 11, 8, + 53, 61, 73, 73, 65, 8, 26, 29, 8, 68, 8, 46, 69, 79, 63, 68, 132, 81, 79, 61, 102, + 65, 8, 132, 75, 74, 64, 65, 79, 74, 8, 61, 74, 8, 23, 30, 31, 29, 8, 36, 62, 74, + 61, 68, 73, 65, 2, 23, 31, 8, 29, 8, 53, 82, 73, 73, 65, 20, 8, 26, 29, 8, 22, + 21, 8, 46, 69, 79, 63, 68, 132, 81, 79, 61, 102, 65, 8, 132, 75, 74, 64, 65, 79, 74, + 8, 61, 74, 8, 23, 30, 31, 29, 8, 36, 62, 74, 61, 68, 73, 65, 2, 27, 21, 8, 46, + 79, 65, 69, 80, 132, 63, 68, 82, 72, 69, 74, 132, 78, 65, 71, 81, 75, 79, 65, 74, 18, + 8, 28, 27, 8, 31, 22, 8, 42, 65, 73, 65, 69, 74, 64, 65, 74, 18, 8, 27, 25, 18, + 25, 27, 8, 22, 8, 47, 61, 74, 64, 71, 79, 65, 69, 132, 65, 18, 2, 27, 29, 8, 29, + 8, 46, 79, 65, 69, 80, 132, 63, 68, 82, 72, 69, 74, 132, 78, 65, 71, 81, 75, 79, 65, + 74, 18, 8, 28, 27, 8, 22, 22, 8, 42, 65, 73, 65, 69, 74, 64, 65, 74, 18, 8, 27, + 25, 18, 25, 27, 8, 21, 8, 47, 61, 74, 64, 71, 79, 65, 69, 132, 65, 18, 2, 26, 29, + 18, 24, 27, 20, 11, 11, 8, 53, 81, 103, 64, 81, 65, 8, 82, 74, 64, 8, 66, 79, 65, + 69, 65, 8, 53, 81, 103, 64, 81, 65, 18, 8, 24, 23, 20, 21, 20, 8, 55, 74, 81, 65, + 79, 72, 61, 74, 64, 71, 79, 65, 69, 132, 65, 18, 8, 29, 8, 22, 8, 57, 61, 72, 64, + 67, 65, 62, 69, 65, 81, 80, 4, 2, 26, 29, 18, 24, 27, 8, 29, 8, 53, 81, 103, 64, + 81, 65, 8, 82, 74, 64, 8, 66, 79, 65, 69, 65, 8, 53, 81, 103, 64, 81, 65, 18, 8, + 24, 23, 8, 55, 74, 81, 65, 79, 72, 61, 74, 64, 71, 79, 65, 69, 132, 65, 18, 8, 29, + 8, 22, 11, 8, 57, 61, 72, 64, 67, 65, 62, 69, 65, 81, 80, 3, 2, 83, 65, 79, 71, + 65, 69, 72, 82, 74, 67, 65, 74, 18, 8, 64, 61, 71, 82, 74, 71, 65, 79, 8, 25, 24, + 22, 8, 7, 43, 75, 63, 68, 84, 103, 72, 64, 65, 79, 6, 18, 8, 24, 24, 8, 7, 22, + 8, 48, 69, 80, 63, 68, 84, 103, 72, 64, 65, 79, 18, 8, 25, 29, 18, 24, 8, 22, 8, + 49, 61, 64, 65, 72, 4, 2, 83, 65, 79, 81, 65, 69, 72, 82, 74, 67, 65, 74, 18, 8, + 64, 61, 79, 82, 74, 81, 65, 79, 8, 25, 24, 8, 22, 11, 8, 7, 43, 75, 63, 68, 84, + 103, 72, 64, 65, 79, 6, 18, 8, 24, 24, 8, 7, 8, 48, 69, 80, 63, 68, 84, 103, 72, + 64, 65, 79, 18, 8, 25, 29, 18, 24, 8, 11, 8, 49, 61, 64, 65, 72, 3, 2, 84, 103, + 72, 64, 8, 65, 79, 18, 8, 23, 23, 31, 22, 27, 8, 47, 61, 82, 62, 84, 103, 72, 64, + 65, 79, 18, 8, 23, 22, 20, 11, 8, 53, 110, 73, 78, 66, 65, 18, 8, 27, 18, 30, 29, + 8, 21, 8, 53, 81, 65, 78, 78, 65, 74, 18, 8, 24, 24, 25, 8, 22, 11, 8, 36, 72, + 73, 65, 74, 8, 103, 2, 84, 103, 72, 64, 65, 79, 18, 8, 23, 23, 8, 22, 22, 8, 47, + 61, 82, 62, 84, 103, 72, 64, 65, 79, 18, 8, 23, 22, 8, 22, 8, 53, 110, 73, 78, 66, + 65, 18, 8, 27, 18, 30, 29, 8, 75, 8, 53, 81, 65, 78, 78, 65, 74, 18, 8, 24, 18, + 24, 25, 8, 7, 29, 8, 36, 72, 73, 65, 74, 2, 64, 61, 67, 65, 67, 65, 74, 8, 49, + 65, 84, 8, 56, 75, 79, 71, 18, 8, 56, 65, 79, 84, 61, 74, 8, 82, 74, 64, 8, 56, + 65, 73, 65, 74, 20, 8, 56, 61, 63, 68, 81, 83, 65, 79, 71, 103, 82, 66, 65, 8, 87, + 82, 8, 23, 27, 8, 11, 11, 8, 67, 65, 132, 81, 69, 65, 67, 65, 74, 20, 2, 64, 61, + 67, 65, 67, 65, 74, 8, 49, 65, 84, 8, 56, 75, 79, 71, 18, 8, 56, 65, 79, 84, 61, + 74, 8, 82, 74, 64, 8, 56, 65, 73, 65, 74, 20, 8, 56, 61, 63, 68, 81, 83, 65, 79, + 71, 103, 82, 66, 65, 8, 87, 82, 8, 23, 27, 8, 18, 8, 67, 65, 132, 81, 69, 65, 67, + 65, 74, 20, 2, 43, 61, 63, 68, 81, 61, 62, 67, 103, 74, 67, 65, 8, 87, 82, 8, 24, + 27, 8, 22, 8, 67, 65, 66, 61, 72, 72, 65, 74, 20, 8, 7, 39, 65, 79, 8, 56, 65, + 74, 8, 66, 69, 65, 72, 8, 82, 73, 8, 23, 23, 8, 21, 11, 8, 67, 65, 67, 65, 74, + 110, 62, 65, 79, 8, 24, 27, 8, 11, 8, 64, 65, 80, 2, 56, 61, 63, 68, 81, 61, 62, + 67, 103, 74, 67, 65, 8, 87, 82, 8, 24, 27, 8, 22, 8, 67, 65, 66, 61, 72, 72, 65, + 74, 20, 8, 7, 39, 65, 79, 8, 56, 65, 74, 8, 66, 69, 65, 72, 8, 82, 73, 8, 23, + 23, 8, 18, 8, 67, 65, 67, 65, 74, 110, 62, 65, 79, 8, 24, 27, 8, 7, 8, 64, 65, + 80, 2, 51, 39, 51, 56, 75, 79, 70, 132, 61, 68, 79, 65, 80, 20, 6, 8, 14, 24, 26, + 8, 22, 8, 67, 65, 67, 65, 74, 110, 62, 65, 79, 8, 26, 24, 8, 28, 20, 8, 39, 61, + 74, 65, 62, 65, 74, 8, 61, 82, 63, 68, 8, 36, 74, 132, 81, 69, 65, 67, 65, 8, 82, + 73, 8, 23, 23, 8, 21, 8, 24, 24, 8, 11, 8, 25, 26, 20, 8, 11, 2, 56, 75, 79, + 70, 61, 68, 79, 65, 80, 20, 6, 8, 14, 24, 26, 8, 11, 8, 67, 65, 67, 65, 74, 110, + 62, 65, 79, 8, 26, 24, 8, 7, 20, 8, 39, 61, 74, 65, 62, 65, 74, 8, 61, 82, 63, + 68, 8, 36, 74, 132, 81, 69, 65, 67, 65, 8, 82, 73, 8, 23, 23, 8, 75, 18, 8, 24, + 24, 8, 98, 18, 8, 25, 26, 8, 11, 18, 2, 27, 26, 8, 22, 8, 28, 31, 8, 22, 18, + 8, 7, 29, 28, 8, 22, 22, 18, 8, 30, 29, 8, 22, 18, 8, 31, 30, 8, 22, 22, 8, + 82, 74, 64, 8, 31, 31, 11, 11, 18, 15, 2, 27, 26, 8, 11, 18, 8, 28, 31, 8, 7, + 18, 18, 8, 29, 28, 8, 18, 8, 30, 29, 8, 21, 18, 8, 31, 30, 8, 22, 11, 8, 82, + 74, 64, 8, 31, 31, 21, 18, 20, 15, 2, 39, 69, 65, 8, 52, 110, 63, 71, 132, 69, 63, + 68, 81, 8, 7, 36, 82, 80, 67, 65, 132, 81, 61, 72, 81, 82, 74, 67, 6, 8, 75, 64, + 65, 79, 8, 37, 75, 81, 65, 74, 73, 65, 69, 132, 81, 65, 79, 65, 69, 20, 8, 55, 74, + 132, 65, 79, 73, 7, 40, 69, 74, 87, 65, 72, 66, 61, 72, 72, 65, 6, 8, 64, 65, 80, + 68, 61, 72, 62, 2, 39, 69, 65, 8, 52, 110, 63, 71, 132, 69, 63, 68, 81, 8, 7, 36, + 82, 80, 67, 65, 132, 81, 61, 72, 81, 82, 74, 67, 6, 8, 75, 64, 65, 79, 8, 37, 75, + 81, 65, 74, 73, 65, 69, 132, 81, 65, 79, 65, 69, 20, 8, 55, 74, 132, 65, 79, 73, 8, + 7, 40, 69, 74, 87, 65, 72, 66, 61, 72, 72, 65, 6, 8, 64, 65, 80, 68, 61, 72, 62, + 2, 7, 47, 61, 82, 62, 84, 103, 72, 64, 65, 79, 6, 18, 8, 64, 61, 79, 82, 73, 8, + 40, 62, 65, 79, 8, 82, 74, 132, 65, 79, 73, 8, 7, 36, 79, 62, 65, 69, 81, 65, 79, + 6, 18, 8, 61, 62, 65, 79, 8, 7, 62, 65, 132, 75, 74, 64, 65, 79, 80, 6, 8, 64, + 65, 80, 68, 61, 72, 62, 8, 62, 65, 132, 63, 68, 61, 66, 66, 65, 74, 21, 2, 7, 47, + 61, 82, 62, 84, 103, 72, 64, 65, 79, 6, 18, 8, 64, 61, 79, 82, 73, 8, 61, 62, 65, + 79, 8, 82, 74, 132, 65, 79, 73, 8, 7, 36, 79, 62, 65, 69, 81, 65, 79, 6, 18, 8, + 61, 62, 65, 79, 8, 7, 62, 65, 132, 75, 74, 64, 65, 79, 80, 6, 8, 64, 65, 80, 68, + 61, 72, 62, 8, 62, 65, 132, 63, 68, 61, 66, 66, 65, 74, 20, 2, 39, 69, 65, 8, 53, + 81, 69, 73, 73, 65, 74, 8, 7, 42, 65, 73, 65, 69, 74, 64, 65, 62, 65, 132, 63, 68, + 72, 82, 102, 6, 8, 64, 61, 79, 82, 86, 69, 8, 132, 65, 72, 72, 81, 65, 18, 8, 64, + 65, 80, 68, 61, 72, 62, 8, 64, 65, 79, 8, 37, 65, 132, 63, 68, 72, 82, 102, 8, 7, + 63, 68, 84, 61, 74, 71, 81, 65, 74, 6, 8, 13, 2, 39, 69, 65, 8, 53, 81, 69, 73, + 73, 65, 74, 8, 7, 42, 65, 73, 65, 69, 74, 64, 65, 62, 65, 132, 63, 68, 72, 82, 102, + 6, 8, 64, 61, 79, 82, 73, 8, 132, 75, 72, 72, 81, 65, 18, 8, 64, 65, 80, 68, 61, + 72, 62, 8, 64, 65, 79, 8, 37, 65, 132, 63, 68, 72, 82, 102, 8, 7, 132, 63, 68, 84, + 61, 74, 71, 81, 65, 74, 6, 2, 47, 65, 68, 79, 78, 72, 103, 74, 65, 20, 8, 57, 65, + 69, 72, 8, 7, 67, 65, 81, 79, 75, 132, 81, 6, 8, 37, 82, 79, 4, 42, 65, 68, 20, + 33, 8, 64, 61, 79, 110, 73, 8, 7, 53, 63, 68, 82, 72, 65, 74, 6, 8, 65, 69, 74, + 73, 61, 72, 8, 67, 65, 72, 81, 79, 74, 74, 20, 2, 47, 65, 68, 79, 78, 72, 103, 74, + 65, 20, 8, 57, 65, 69, 72, 8, 7, 67, 65, 81, 79, 75, 132, 81, 6, 8, 37, 82, 79, + 20, 19, 42, 65, 68, 20, 32, 8, 64, 61, 79, 82, 73, 8, 7, 53, 63, 68, 82, 72, 65, + 74, 6, 8, 65, 69, 74, 73, 61, 72, 8, 67, 65, 72, 81, 65, 74, 20, 2, 42, 65, 79, + 103, 81, 65, 8, 7, 42, 82, 132, 81, 61, 83, 6, 8, 75, 64, 65, 79, 8, 83, 75, 72, + 72, 71, 75, 73, 73, 65, 74, 18, 8, 64, 65, 80, 68, 61, 72, 62, 8, 7, 132, 63, 68, + 65, 69, 74, 81, 6, 8, 91, 8, 25, 24, 28, 21, 24, 24, 8, 64, 61, 79, 82, 73, 56, + 65, 79, 73, 103, 63, 68, 81, 74, 69, 132, 132, 65, 80, 6, 8, 132, 75, 72, 72, 81, 65, + 74, 18, 2, 42, 65, 79, 103, 81, 65, 8, 7, 42, 82, 132, 81, 61, 83, 6, 8, 75, 64, + 65, 79, 8, 83, 75, 72, 72, 71, 75, 73, 73, 65, 74, 18, 8, 64, 65, 80, 68, 61, 72, + 62, 8, 7, 132, 63, 68, 65, 69, 74, 81, 6, 8, 91, 8, 25, 24, 28, 21, 24, 24, 8, + 64, 61, 79, 82, 73, 8, 7, 56, 65, 79, 73, 103, 63, 68, 81, 74, 69, 132, 132, 65, 80, + 6, 8, 132, 75, 72, 72, 81, 65, 74, 18, 2, 67, 65, 71, 75, 73, 73, 65, 74, 8, 7, + 67, 65, 132, 81, 110, 81, 87, 81, 6, 8, 75, 62, 65, 79, 8, 54, 65, 69, 72, 62, 65, + 81, 79, 85, 61, 67, 8, 82, 74, 64, 8, 7, 84, 65, 79, 64, 65, 74, 6, 8, 36, 74, + 132, 63, 68, 61, 82, 82, 74, 67, 8, 61, 62, 65, 79, 8, 7, 53, 63, 68, 82, 72, 64, + 65, 74, 83, 65, 79, 84, 61, 72, 81, 82, 74, 67, 2, 67, 65, 71, 75, 73, 73, 65, 74, + 8, 7, 67, 65, 132, 81, 110, 81, 87, 81, 6, 8, 61, 62, 65, 79, 8, 54, 65, 69, 72, + 62, 65, 81, 79, 61, 67, 8, 82, 74, 64, 8, 7, 84, 65, 79, 64, 65, 74, 6, 8, 36, + 74, 132, 63, 68, 61, 82, 82, 74, 67, 8, 61, 62, 65, 79, 8, 7, 53, 63, 68, 82, 72, + 64, 65, 74, 83, 65, 79, 84, 61, 72, 81, 82, 74, 67, 6, 2, 73, 69, 81, 81, 65, 72, + 62, 61, 79, 8, 7, 46, 72, 65, 69, 74, 84, 75, 68, 70, 69, 70, 82, 74, 67, 65, 74, + 6, 8, 61, 62, 65, 79, 8, 47, 65, 68, 79, 78, 72, 103, 74, 65, 74, 8, 64, 61, 79, + 82, 73, 8, 7, 83, 75, 79, 67, 65, 67, 61, 74, 67, 65, 74, 6, 8, 51, 65, 74, 87, + 69, 67, 8, 55, 2, 73, 69, 81, 81, 65, 72, 62, 61, 79, 8, 7, 46, 72, 65, 69, 74, + 84, 75, 68, 74, 82, 74, 67, 65, 74, 6, 8, 61, 62, 65, 79, 8, 47, 65, 68, 79, 78, + 72, 103, 74, 65, 74, 8, 64, 61, 79, 82, 73, 8, 7, 83, 75, 79, 67, 65, 67, 61, 74, + 67, 65, 74, 6, 8, 51, 65, 74, 87, 69, 67, 2, 64, 61, 79, 82, 73, 8, 7, 69, 74, + 80, 62, 65, 132, 75, 74, 64, 65, 79, 65, 6, 8, 40, 69, 74, 67, 61, 74, 67, 33, 8, + 45, 61, 68, 79, 65, 74, 8, 7, 83, 75, 79, 61, 82, 80, 66, 69, 63, 68, 81, 72, 69, + 63, 68, 6, 8, 84, 65, 80, 68, 61, 72, 62, 8, 39, 65, 87, 65, 73, 62, 65, 79, 2, + 64, 61, 79, 82, 73, 8, 7, 69, 74, 80, 62, 65, 132, 75, 74, 64, 65, 79, 65, 6, 8, + 40, 69, 74, 67, 61, 74, 67, 33, 8, 45, 61, 68, 79, 65, 74, 8, 7, 83, 75, 79, 61, + 82, 80, 132, 69, 63, 68, 81, 72, 69, 63, 68, 6, 8, 84, 65, 80, 68, 61, 72, 62, 8, + 39, 65, 87, 65, 73, 62, 65, 79, 2, 82, 74, 64, 8, 7, 53, 82, 73, 73, 65, 20, 6, + 8, 56, 65, 79, 84, 61, 72, 81, 65, 79, 32, 8, 82, 74, 64, 8, 7, 83, 75, 79, 68, + 69, 74, 6, 8, 81, 79, 61, 81, 65, 74, 8, 61, 74, 64, 65, 79, 65, 8, 78, 110, 74, + 71, 81, 72, 69, 63, 68, 18, 8, 84, 65, 79, 64, 65, 74, 74, 2, 82, 74, 64, 8, 7, + 53, 82, 73, 73, 65, 20, 6, 8, 56, 65, 79, 84, 61, 72, 81, 65, 79, 32, 8, 82, 74, + 64, 8, 7, 83, 75, 79, 68, 69, 74, 6, 8, 81, 79, 61, 81, 65, 74, 8, 61, 74, 64, + 65, 79, 65, 8, 78, 110, 74, 71, 81, 72, 69, 63, 68, 8, 7, 84, 65, 79, 64, 65, 74, + 6, 2, 82, 74, 64, 4, 46, 72, 65, 69, 74, 84, 75, 68, 74, 82, 74, 67, 65, 74, 8, + 82, 74, 64, 8, 14, 7, 55, 74, 79, 82, 68, 65, 6, 15, 8, 60, 69, 74, 74, 74, 65, + 79, 8, 61, 62, 65, 79, 8, 7, 61, 74, 64, 65, 79, 71, 79, 80, 65, 69, 71, 80, 6, + 8, 71, 75, 73, 73, 65, 74, 2, 82, 74, 64, 8, 46, 72, 65, 69, 74, 84, 75, 68, 74, + 82, 74, 67, 65, 74, 8, 82, 74, 64, 8, 14, 7, 55, 74, 79, 82, 68, 65, 6, 15, 8, + 60, 69, 73, 73, 65, 79, 8, 61, 62, 65, 79, 8, 7, 61, 74, 64, 65, 79, 65, 79, 80, + 65, 69, 81, 80, 6, 8, 71, 75, 73, 73, 65, 74, 2, 82, 73, 132, 81, 79, 69, 81, 81, + 65, 74, 65, 79, 8, 7, 42, 65, 132, 63, 68, 103, 66, 81, 80, 83, 65, 79, 84, 61, 72, + 81, 82, 74, 67, 6, 8, 75, 64, 65, 79, 8, 60, 65, 69, 81, 65, 74, 8, 64, 65, 80, + 68, 61, 72, 62, 8, 14, 7, 36, 78, 78, 72, 61, 82, 132, 6, 15, 2, 82, 73, 132, 81, + 79, 69, 81, 81, 65, 74, 65, 79, 8, 7, 42, 65, 132, 63, 68, 103, 66, 81, 80, 83, 65, + 79, 84, 61, 72, 81, 82, 74, 67, 6, 8, 75, 64, 65, 79, 8, 60, 65, 69, 81, 65, 74, + 8, 64, 65, 80, 68, 61, 72, 62, 8, 14, 7, 36, 78, 78, 72, 61, 82, 132, 6, 15, 2, + 132, 78, 79, 65, 63, 68, 65, 8, 64, 65, 80, 68, 61, 72, 62, 7, 64, 69, 65, 132, 65, + 79, 6, 8, 14, 7, 57, 61, 68, 79, 72, 69, 63, 68, 9, 6, 15, 20, 8, 52, 65, 69, + 68, 65, 74, 20, 8, 7, 53, 81, 61, 81, 69, 132, 81, 69, 71, 6, 8, 53, 63, 68, 84, + 69, 65, 79, 69, 67, 71, 65, 69, 81, 65, 74, 2, 132, 78, 79, 65, 63, 68, 65, 8, 64, + 65, 80, 68, 61, 72, 62, 8, 7, 64, 69, 65, 132, 65, 79, 6, 8, 14, 7, 57, 61, 68, + 79, 72, 69, 63, 68, 9, 6, 15, 20, 8, 52, 65, 69, 68, 65, 74, 20, 8, 7, 53, 81, + 61, 81, 69, 132, 81, 69, 71, 6, 8, 53, 63, 68, 84, 69, 65, 79, 69, 67, 71, 65, 69, + 81, 65, 74, 2, 84, 65, 80, 68, 61, 72, 62, 8, 22, 18, 40, 69, 74, 84, 61, 74, 64, + 6, 15, 8, 7, 48, 61, 132, 63, 68, 69, 74, 65, 74, 4, 64, 61, 79, 82, 73, 8, 7, + 52, 110, 63, 71, 67, 61, 74, 67, 6, 8, 62, 65, 69, 64, 65, 74, 8, 42, 65, 64, 61, + 74, 71, 65, 8, 7, 36, 62, 132, 81, 69, 73, 73, 82, 74, 67, 6, 18, 2, 84, 65, 80, + 68, 61, 72, 62, 8, 14, 7, 40, 69, 74, 84, 61, 74, 64, 6, 15, 8, 7, 48, 61, 132, + 63, 68, 69, 74, 65, 74, 19, 64, 61, 79, 82, 73, 8, 7, 52, 110, 63, 71, 67, 61, 74, + 67, 6, 8, 62, 65, 69, 64, 65, 74, 8, 42, 65, 64, 61, 74, 71, 65, 8, 7, 36, 62, + 132, 81, 69, 73, 73, 82, 74, 67, 6, 2, 64, 65, 80, 68, 61, 72, 62, 8, 37, 72, 82, + 73, 65, 74, 132, 81, 79, 61, 102, 65, 8, 14, 7, 36, 78, 78, 72, 61, 82, 132, 6, 15, + 18, 8, 30, 8, 29, 30, 8, 65, 79, 67, 65, 62, 65, 74, 8, 64, 65, 80, 68, 61, 72, + 62, 8, 7, 37, 82, 79, 20, 4, 42, 65, 68, 4, 8, 110, 62, 79, 69, 67, 65, 74, 8, + 30, 8, 24, 31, 2, 91, 26, 8, 64, 65, 80, 68, 61, 72, 62, 8, 37, 72, 82, 73, 65, + 74, 132, 81, 79, 61, 102, 65, 8, 14, 7, 36, 78, 78, 72, 61, 82, 132, 6, 15, 20, 8, + 53, 8, 29, 30, 8, 65, 79, 67, 65, 62, 65, 74, 8, 64, 65, 80, 68, 61, 72, 62, 8, + 7, 37, 82, 79, 20, 19, 42, 65, 68, 20, 32, 6, 8, 110, 62, 79, 69, 67, 65, 74, 8, + 91, 8, 24, 31, 2, 48, 69, 63, 68, 61, 65, 72, 69, 80, 8, 7, 64, 65, 73, 132, 65, + 72, 62, 65, 74, 6, 8, 91, 8, 27, 20, 84, 65, 80, 68, 61, 72, 62, 8, 41, 103, 72, + 72, 65, 74, 8, 75, 64, 65, 79, 8, 14, 7, 55, 74, 79, 82, 68, 65, 81, 15, 8, 91, + 8, 26, 8, 62, 65, 69, 72, 103, 82, 66, 69, 67, 65, 79, 18, 2, 48, 69, 63, 68, 61, + 65, 72, 69, 80, 8, 7, 64, 65, 73, 132, 65, 72, 62, 65, 74, 6, 8, 91, 8, 27, 8, + 84, 65, 80, 68, 61, 72, 62, 8, 41, 103, 72, 72, 65, 74, 8, 75, 64, 65, 79, 8, 14, + 7, 55, 74, 79, 82, 68, 65, 6, 15, 8, 91, 8, 26, 8, 62, 65, 69, 72, 103, 82, 66, + 69, 67, 65, 79, 18, 2, 75, 62, 65, 79, 8, 7, 68, 65, 82, 81, 69, 67, 65, 74, 8, + 39, 61, 81, 82, 73, 80, 6, 8, 64, 65, 79, 69, 65, 74, 69, 67, 65, 8, 91, 8, 23, + 25, 8, 14, 53, 81, 61, 64, 81, 132, 63, 68, 82, 72, 79, 61, 81, 15, 8, 7, 68, 69, + 74, 81, 65, 79, 65, 74, 6, 8, 91, 8, 23, 23, 20, 2, 61, 62, 65, 79, 8, 7, 68, + 65, 82, 81, 69, 67, 65, 74, 8, 39, 61, 81, 82, 73, 80, 6, 8, 64, 65, 79, 70, 65, + 74, 69, 67, 65, 8, 91, 8, 23, 25, 8, 14, 53, 81, 61, 64, 81, 132, 63, 68, 82, 72, + 79, 61, 81, 15, 8, 7, 68, 69, 74, 81, 65, 79, 65, 74, 6, 8, 91, 8, 23, 23, 2, + 74, 64, 8, 84, 65, 69, 81, 65, 79, 65, 80, 8, 82, 74, 64, 8, 36, 78, 78, 72, 61, + 82, 132, 6, 15, 8, 91, 8, 27, 27, 8, 84, 65, 69, 72, 8, 7, 56, 65, 79, 73, 69, + 74, 64, 65, 79, 82, 74, 67, 6, 8, 42, 65, 64, 82, 72, 64, 20, 8, 53, 8, 24, 23, + 2, 82, 74, 64, 8, 84, 65, 69, 81, 65, 79, 65, 80, 8, 82, 74, 64, 8, 14, 7, 36, + 78, 78, 72, 61, 82, 132, 6, 15, 8, 91, 8, 27, 27, 18, 8, 84, 65, 69, 72, 8, 7, + 56, 65, 79, 73, 69, 74, 64, 65, 79, 82, 74, 67, 6, 8, 42, 65, 64, 82, 72, 64, 20, + 8, 91, 8, 24, 23, 2, 40, 79, 68, 109, 68, 82, 74, 67, 8, 7, 36, 74, 81, 79, 61, + 67, 6, 8, 91, 8, 30, 26, 8, 61, 62, 65, 79, 8, 65, 69, 74, 67, 65, 66, 82, 74, + 64, 65, 74, 8, 84, 65, 80, 68, 61, 72, 62, 20, 14, 7, 60, 84, 69, 132, 63, 68, 65, + 74, 79, 82, 66, 6, 15, 8, 91, 8, 26, 26, 8, 57, 61, 74, 64, 65, 79, 82, 74, 67, + 2, 40, 79, 68, 109, 68, 82, 74, 67, 8, 7, 36, 74, 81, 79, 61, 67, 6, 8, 91, 8, + 30, 26, 8, 61, 62, 65, 79, 8, 65, 69, 74, 67, 65, 66, 82, 74, 64, 65, 74, 8, 84, + 65, 80, 68, 61, 72, 62, 8, 14, 7, 60, 84, 69, 132, 63, 68, 65, 74, 79, 82, 66, 6, + 15, 8, 91, 8, 26, 26, 8, 57, 61, 74, 64, 65, 79, 82, 74, 67, 2, 64, 61, 79, 82, + 73, 8, 7, 83, 69, 65, 72, 72, 65, 69, 63, 68, 81, 6, 8, 7, 83, 65, 79, 81, 79, + 65, 81, 65, 74, 8, 69, 74, 8, 91, 8, 23, 26, 18, 18, 8, 64, 61, 73, 69, 81, 8, + 7, 36, 82, 80, 67, 65, 132, 81, 61, 72, 81, 82, 74, 67, 6, 8, 53, 8, 27, 28, 8, + 82, 74, 64, 8, 51, 79, 110, 66, 82, 74, 67, 2, 64, 61, 79, 82, 73, 8, 7, 83, 69, + 65, 72, 72, 65, 69, 63, 68, 81, 6, 8, 7, 83, 65, 79, 81, 79, 65, 81, 65, 74, 8, + 69, 74, 8, 91, 8, 23, 26, 6, 18, 8, 64, 61, 73, 69, 81, 8, 7, 36, 82, 80, 67, + 65, 132, 81, 61, 72, 81, 82, 74, 67, 6, 8, 91, 8, 27, 28, 8, 82, 74, 64, 8, 51, + 79, 110, 66, 82, 74, 67, 2, 64, 65, 79, 8, 14, 7, 55, 74, 85, 82, 68, 65, 6, 15, + 8, 91, 8, 30, 30, 8, 55, 74, 81, 65, 79, 72, 61, 74, 64, 71, 79, 65, 69, 132, 65, + 8, 14, 84, 65, 69, 72, 7, 48, 65, 68, 79, 61, 82, 80, 67, 61, 62, 65, 74, 6, 8, + 57, 61, 68, 72, 79, 65, 132, 82, 72, 81, 61, 81, 80, 6, 2, 64, 65, 79, 8, 14, 7, + 55, 74, 79, 82, 68, 65, 6, 15, 8, 91, 8, 30, 30, 8, 55, 74, 81, 65, 79, 72, 61, + 74, 64, 71, 79, 65, 69, 132, 65, 8, 14, 84, 65, 69, 72, 8, 7, 48, 65, 68, 79, 61, + 82, 80, 67, 61, 62, 65, 74, 6, 8, 57, 61, 68, 72, 79, 65, 132, 82, 72, 81, 61, 81, + 80, 15, 2, 91, 8, 29, 22, 8, 50, 62, 65, 79, 72, 61, 74, 64, 73, 65, 132, 132, 65, + 79, 32, 8, 7, 61, 74, 64, 65, 79, 65, 79, 6, 8, 91, 8, 27, 27, 8, 64, 65, 80, + 68, 61, 72, 62, 8, 56, 75, 79, 73, 69, 81, 81, 61, 67, 80, 8, 84, 65, 80, 68, 61, + 72, 62, 8, 14, 7, 40, 69, 74, 84, 61, 74, 64, 6, 15, 2, 91, 53, 8, 29, 22, 8, + 50, 62, 65, 79, 72, 61, 74, 64, 73, 65, 132, 132, 65, 79, 32, 8, 7, 61, 74, 64, 65, + 79, 65, 79, 6, 8, 91, 8, 27, 27, 8, 64, 65, 80, 68, 61, 72, 62, 8, 56, 75, 79, + 73, 69, 81, 81, 61, 67, 80, 8, 84, 65, 80, 68, 61, 72, 62, 8, 14, 7, 40, 69, 74, + 84, 61, 74, 64, 6, 15, 2, 84, 65, 69, 81, 65, 79, 65, 8, 7, 84, 75, 79, 64, 65, + 74, 9, 4, 53, 8, 28, 29, 8, 82, 79, 72, 61, 82, 62, 82, 74, 67, 8, 84, 65, 69, + 72, 8, 7, 37, 82, 63, 68, 68, 61, 72, 81, 65, 79, 132, 81, 61, 74, 64, 65, 6, 2, + 84, 65, 69, 81, 65, 79, 65, 8, 7, 84, 75, 79, 64, 65, 74, 6, 8, 91, 8, 28, 29, + 8, 82, 79, 72, 61, 82, 62, 82, 74, 67, 8, 84, 65, 69, 72, 8, 7, 37, 82, 63, 68, + 68, 61, 72, 81, 65, 79, 132, 81, 61, 74, 64, 65, 6, 2, 84, 65, 80, 68, 61, 72, 62, + 8, 53, 81, 79, 65, 69, 81, 69, 67, 71, 65, 69, 81, 65, 74, 8, 91, 8, 30, 26, 8, + 82, 74, 64, 20, 14, 18, 60, 84, 69, 132, 63, 68, 65, 74, 79, 82, 66, 104, 15, 8, 91, + 8, 23, 25, 8, 40, 79, 68, 109, 68, 82, 74, 67, 20, 2, 84, 65, 80, 68, 61, 72, 62, + 8, 53, 81, 79, 65, 69, 81, 69, 67, 71, 65, 69, 81, 65, 74, 8, 91, 53, 8, 30, 26, + 8, 82, 74, 64, 8, 14, 7, 60, 84, 69, 132, 63, 68, 65, 74, 79, 82, 66, 6, 15, 8, + 91, 53, 8, 23, 25, 8, 40, 79, 68, 109, 68, 82, 74, 67, 2, 75, 64, 65, 79, 18, 7, + 36, 62, 132, 81, 69, 73, 73, 82, 74, 67, 6, 8, 79, 65, 67, 65, 72, 73, 103, 102, 69, + 67, 8, 91, 8, 27, 24, 8, 53, 63, 68, 84, 69, 65, 79, 69, 67, 71, 65, 69, 81, 65, + 74, 20, 8, 41, 61, 74, 64, 65, 79, 65, 6, 8, 84, 65, 80, 68, 61, 72, 62, 8, 64, + 69, 79, 65, 71, 81, 8, 91, 43, 28, 30, 8, 82, 74, 64, 2, 75, 64, 65, 79, 8, 7, + 36, 62, 132, 81, 69, 73, 73, 82, 74, 67, 6, 8, 79, 65, 67, 65, 72, 73, 103, 102, 69, + 67, 8, 91, 8, 27, 24, 8, 53, 63, 68, 84, 69, 65, 79, 69, 67, 71, 65, 69, 81, 65, + 74, 8, 7, 61, 74, 64, 65, 79, 65, 6, 8, 84, 65, 80, 68, 61, 72, 62, 8, 64, 69, + 79, 65, 71, 81, 8, 91, 8, 28, 30, 8, 82, 74, 64, 2, 38, 55, 74, 79, 82, 68, 65, + 6, 15, 8, 53, 8, 23, 26, 8, 62, 65, 79, 65, 63, 68, 74, 65, 81, 8, 75, 64, 65, + 79, 8, 7, 64, 65, 80, 67, 72, 20, 6, 8, 53, 81, 65, 72, 72, 65, 8, 91, 8, 25, + 29, 2, 14, 7, 55, 74, 79, 82, 68, 65, 6, 15, 8, 91, 8, 23, 26, 8, 62, 65, 79, + 65, 63, 68, 74, 65, 81, 8, 75, 64, 65, 79, 8, 7, 64, 65, 80, 67, 72, 20, 6, 8, + 53, 81, 65, 72, 72, 65, 8, 91, 8, 25, 29, 2, 3, 8, 60, 41, 65, 69, 65, 79, 72, + 69, 63, 68, 8, 7, 84, 65, 79, 64, 65, 74, 6, 8, 91, 8, 31, 8, 64, 65, 80, 68, + 61, 72, 62, 8, 64, 65, 79, 8, 56, 65, 79, 84, 61, 72, 81, 82, 74, 67, 20, 8, 36, + 82, 63, 68, 8, 14, 7, 60, 84, 69, 132, 63, 68, 65, 74, 79, 82, 66, 13, 15, 8, 91, + 8, 30, 22, 8, 65, 74, 81, 68, 103, 72, 81, 2, 41, 65, 69, 65, 79, 72, 69, 63, 68, + 8, 7, 84, 65, 79, 64, 65, 74, 6, 8, 91, 8, 31, 8, 64, 65, 80, 68, 61, 72, 62, + 8, 64, 65, 79, 8, 56, 65, 79, 84, 61, 72, 81, 82, 74, 67, 20, 8, 36, 82, 63, 68, + 8, 14, 7, 60, 84, 69, 132, 63, 68, 65, 74, 79, 82, 66, 6, 15, 8, 91, 8, 30, 22, + 8, 65, 74, 81, 68, 103, 72, 81, 2, 75, 64, 65, 79, 8, 7, 23, 31, 23, 24, 21, 23, + 29, 6, 18, 8, 132, 75, 74, 64, 65, 79, 74, 8, 30, 8, 31, 30, 33, 8, 14, 68, 65, + 66, 81, 69, 67, 65, 71, 8, 7, 53, 81, 69, 73, 73, 87, 65, 81, 81, 65, 72, 6, 8, + 91, 8, 26, 15, 8, 61, 62, 65, 79, 4, 62, 65, 64, 69, 82, 67, 81, 18, 2, 75, 64, + 65, 79, 8, 7, 23, 31, 23, 24, 21, 23, 29, 6, 18, 8, 132, 75, 74, 64, 65, 79, 74, + 8, 91, 8, 31, 30, 33, 8, 14, 68, 65, 66, 81, 69, 67, 65, 79, 8, 7, 53, 81, 69, + 73, 73, 87, 65, 81, 81, 65, 72, 6, 8, 91, 8, 45, 15, 18, 8, 61, 62, 65, 79, 8, + 62, 65, 64, 69, 74, 67, 81, 18, 2, 78, 39, 63, 65, 80, 68, 61, 72, 62, 8, 14, 18, + 36, 78, 78, 71, 61, 82, 132, 6, 15, 8, 0, 12, 8, 27, 22, 8, 53, 63, 68, 84, 61, + 74, 71, 82, 74, 67, 8, 82, 74, 64, 8, 7, 87, 84, 69, 132, 63, 68, 65, 74, 6, 8, + 36, 74, 81, 79, 61, 67, 8, 30, 26, 22, 2, 64, 65, 80, 68, 61, 72, 62, 8, 14, 7, + 36, 78, 78, 72, 61, 82, 132, 6, 15, 8, 91, 8, 27, 31, 8, 53, 63, 68, 84, 61, 74, + 71, 82, 74, 67, 8, 82, 74, 64, 8, 7, 87, 84, 69, 132, 63, 68, 65, 74, 6, 8, 36, + 74, 81, 79, 61, 67, 8, 91, 8, 26, 22, 2, 7, 83, 75, 79, 67, 65, 67, 61, 74, 67, + 65, 74, 6, 8, 7, 53, 81, 65, 72, 72, 65, 13, 8, 91, 8, 25, 30, 8, 82, 74, 64, + 8, 67, 65, 81, 79, 69, 65, 62, 65, 74, 35, 8, 57, 65, 69, 72, 8, 14, 7, 64, 69, + 65, 132, 65, 73, 6, 15, 8, 91, 8, 25, 24, 8, 82, 74, 64, 8, 7, 71, 109, 74, 74, + 81, 65, 74, 8, 74, 103, 63, 68, 132, 81, 65, 74, 20, 2, 7, 83, 75, 79, 67, 65, 67, + 61, 74, 67, 65, 74, 6, 8, 7, 53, 81, 65, 72, 72, 65, 6, 8, 91, 8, 25, 30, 8, + 82, 74, 64, 8, 67, 65, 81, 79, 69, 65, 62, 65, 74, 35, 8, 57, 65, 69, 72, 8, 14, + 7, 64, 69, 65, 132, 65, 73, 6, 15, 8, 91, 8, 25, 24, 8, 82, 74, 64, 8, 7, 71, + 109, 74, 74, 81, 65, 74, 6, 8, 74, 103, 63, 68, 132, 81, 65, 74, 2, 47, 109, 29, 8, + 48, 65, 69, 74, 82, 74, 67, 8, 7, 66, 75, 72, 67, 65, 74, 64, 65, 79, 6, 8, 82, + 74, 64, 8, 60, 61, 68, 72, 65, 74, 8, 82, 74, 64, 8, 91, 8, 23, 27, 8, 43, 7, + 64, 61, 73, 61, 72, 69, 67, 6, 15, 18, 8, 84, 65, 69, 72, 8, 7, 65, 79, 132, 63, + 68, 65, 69, 74, 81, 6, 8, 84, 69, 64, 65, 79, 132, 78, 79, 65, 63, 68, 65, 74, 19, + 2, 91, 8, 25, 29, 8, 48, 65, 69, 74, 82, 74, 67, 8, 7, 66, 75, 72, 67, 65, 74, + 64, 65, 79, 6, 8, 82, 74, 64, 8, 60, 61, 68, 72, 65, 74, 8, 82, 74, 64, 8, 91, + 8, 23, 27, 8, 14, 7, 64, 61, 73, 61, 72, 69, 67, 15, 18, 8, 84, 65, 69, 72, 8, + 7, 65, 79, 132, 63, 68, 65, 69, 74, 81, 6, 8, 84, 69, 64, 65, 79, 132, 78, 79, 65, + 63, 68, 65, 74, 2, 30, 8, 26, 27, 18, 53, 91, 8, 30, 24, 18, 8, 91, 8, 31, 28, + 18, 8, 91, 8, 31, 18, 8, 91, 8, 27, 29, 8, 82, 74, 64, 8, 61, 82, 63, 68, 8, + 91, 8, 28, 31, 18, 8, 91, 8, 27, 25, 18, 8, 91, 8, 25, 30, 20, 2, 53, 91, 8, + 26, 28, 18, 8, 91, 8, 30, 24, 18, 8, 91, 8, 31, 28, 18, 8, 91, 8, 31, 18, 8, + 91, 8, 27, 29, 8, 82, 74, 64, 8, 61, 82, 63, 68, 8, 91, 8, 28, 31, 18, 8, 91, + 8, 27, 25, 18, 8, 91, 8, 25, 30, 20, 2, 41, 79, 61, 82, 65, 74, 8, 7, 84, 65, + 80, 68, 61, 72, 62, 6, 8, 82, 74, 64, 8, 56, 65, 79, 62, 69, 74, 64, 82, 74, 67, + 8, 64, 65, 79, 8, 14, 7, 52, 65, 61, 72, 132, 63, 68, 82, 72, 65, 15, 8, 91, 8, + 30, 22, 8, 75, 64, 65, 79, 8, 7, 42, 79, 82, 62, 65, 74, 72, 61, 73, 78, 65, 74, + 6, 8, 84, 65, 79, 64, 65, 74, 2, 41, 79, 61, 82, 65, 74, 8, 7, 84, 65, 80, 68, + 61, 72, 62, 6, 8, 82, 74, 64, 8, 56, 65, 79, 62, 69, 74, 64, 82, 74, 67, 8, 64, + 65, 79, 8, 14, 7, 52, 65, 61, 72, 132, 63, 68, 82, 72, 65, 6, 15, 8, 91, 53, 8, + 30, 22, 8, 75, 64, 65, 79, 8, 7, 42, 79, 82, 62, 65, 74, 72, 61, 73, 78, 65, 74, + 6, 8, 84, 65, 79, 64, 65, 74, 2, 47, 61, 67, 65, 79, 78, 72, 61, 81, 87, 4, 36, + 82, 66, 132, 65, 68, 65, 79, 4, 7, 62, 65, 79, 65, 69, 81, 65, 81, 6, 8, 91, 8, + 67, 8, 82, 62, 65, 79, 8, 7, 46, 72, 65, 69, 74, 84, 75, 68, 74, 82, 74, 67, 65, + 74, 6, 8, 84, 65, 80, 68, 61, 72, 62, 8, 14, 69, 74, 74, 65, 79, 68, 61, 72, 62, + 6, 15, 8, 91, 8, 30, 31, 47, 8, 61, 62, 65, 79, 2, 47, 61, 67, 65, 79, 78, 72, + 61, 81, 87, 19, 36, 82, 66, 132, 65, 68, 65, 79, 32, 8, 7, 62, 65, 79, 65, 69, 81, + 65, 81, 6, 8, 91, 53, 8, 31, 8, 61, 62, 65, 79, 8, 7, 46, 72, 65, 69, 74, 84, + 75, 68, 74, 82, 74, 67, 65, 74, 6, 8, 84, 65, 80, 68, 61, 72, 62, 8, 14, 7, 69, + 74, 74, 65, 79, 68, 61, 72, 62, 6, 15, 8, 91, 8, 30, 31, 8, 61, 62, 65, 79, 2, + 43, 75, 74, 64, 65, 79, 65, 79, 6, 8, 40, 79, 67, 65, 62, 74, 69, 80, 8, 73, 69, + 81, 81, 72, 65, 79, 65, 79, 8, 7, 56, 65, 73, 65, 74, 6, 18, 8, 14, 91, 8, 23, + 29, 15, 8, 36, 72, 72, 65, 79, 64, 69, 74, 67, 80, 8, 69, 74, 8, 37, 65, 79, 72, + 69, 74, 8, 75, 64, 65, 79, 8, 42, 82, 74, 132, 65, 79, 73, 6, 0, 131, 2, 7, 61, + 74, 64, 65, 79, 65, 79, 6, 8, 40, 79, 67, 65, 62, 74, 69, 80, 8, 73, 69, 81, 81, + 72, 65, 79, 65, 79, 8, 7, 56, 65, 73, 65, 74, 6, 20, 8, 14, 91, 8, 23, 29, 15, + 8, 36, 72, 72, 65, 79, 64, 69, 74, 67, 80, 8, 69, 74, 8, 37, 65, 79, 72, 69, 74, + 8, 75, 64, 65, 79, 8, 14, 7, 82, 74, 132, 65, 79, 73, 6, 15, 2, 30, 72, 75, 64, + 65, 79, 62, 65, 81, 79, 61, 63, 68, 81, 65, 74, 6, 8, 61, 82, 80, 67, 65, 66, 110, + 68, 79, 81, 33, 8, 64, 65, 74, 74, 8, 91, 8, 25, 23, 0, 100, 14, 64, 61, 68, 65, + 79, 8, 64, 69, 65, 8, 52, 82, 66, 65, 15, 33, 8, 7, 64, 61, 79, 110, 62, 65, 79, + 6, 8, 79, 69, 63, 68, 81, 65, 81, 8, 53, 30, 28, 8, 61, 82, 63, 68, 8, 69, 74, + 2, 91, 8, 23, 8, 75, 64, 65, 79, 8, 7, 62, 65, 81, 79, 61, 63, 68, 81, 65, 74, + 6, 8, 61, 82, 80, 67, 65, 66, 110, 68, 79, 81, 33, 8, 64, 65, 74, 74, 8, 91, 8, + 25, 23, 8, 14, 64, 61, 68, 65, 79, 8, 64, 69, 65, 8, 52, 82, 66, 65, 15, 33, 8, + 7, 64, 61, 79, 110, 62, 65, 79, 6, 8, 79, 69, 63, 68, 81, 65, 81, 8, 91, 8, 30, + 28, 8, 61, 82, 63, 68, 8, 69, 74, 2, 51, 79, 75, 83, 69, 74, 87, 69, 61, 72, 72, + 61, 132, 64, 81, 61, 67, 65, 80, 8, 64, 61, 79, 82, 74, 8, 14, 7, 48, 61, 132, 63, + 68, 69, 74, 65, 74, 6, 15, 8, 45, 8, 29, 28, 8, 64, 65, 80, 68, 61, 72, 62, 8, + 7, 132, 75, 72, 72, 81, 65, 6, 8, 54, 65, 69, 72, 62, 65, 81, 71, 61, 67, 8, 69, + 74, 8, 91, 8, 28, 24, 2, 51, 79, 75, 83, 69, 74, 87, 69, 61, 72, 72, 61, 74, 64, + 81, 61, 67, 65, 80, 8, 64, 61, 79, 82, 73, 8, 14, 7, 48, 61, 132, 63, 68, 69, 74, + 65, 74, 6, 15, 8, 91, 8, 29, 28, 8, 64, 65, 80, 68, 61, 72, 62, 8, 7, 132, 75, + 72, 72, 81, 65, 6, 8, 54, 65, 69, 72, 62, 65, 81, 79, 61, 67, 8, 69, 74, 8, 91, + 8, 28, 24, 2, 37, 65, 109, 65, 64, 65, 74, 71, 65, 74, 20, 8, 7, 53, 81, 68, 84, + 61, 74, 71, 65, 74, 6, 8, 91, 8, 25, 22, 8, 64, 61, 79, 82, 73, 8, 48, 69, 65, + 71, 65, 69, 74, 69, 67, 82, 74, 67, 80, 103, 73, 81, 65, 79, 8, 84, 65, 80, 68, 61, + 72, 62, 8, 14, 18, 48, 69, 81, 81, 61, 67, 80, 82, 74, 81, 65, 79, 79, 69, 63, 68, + 81, 6, 15, 8, 64, 65, 79, 2, 37, 65, 64, 65, 74, 71, 65, 74, 20, 8, 7, 53, 63, + 68, 84, 61, 74, 71, 65, 74, 6, 8, 91, 8, 25, 22, 8, 64, 61, 79, 82, 73, 8, 48, + 69, 65, 81, 65, 69, 74, 69, 67, 82, 74, 67, 80, 103, 73, 81, 65, 79, 8, 84, 65, 80, + 68, 61, 72, 62, 8, 14, 7, 48, 69, 81, 81, 61, 67, 80, 82, 74, 81, 65, 79, 79, 69, + 63, 68, 81, 6, 15, 8, 64, 65, 79, 2, 82, 74, 81, 65, 79, 65, 8, 91, 8, 29, 28, + 8, 64, 65, 80, 68, 61, 72, 62, 8, 7, 87, 84, 69, 132, 63, 68, 65, 74, 35, 8, 73, + 69, 81, 81, 65, 72, 62, 61, 79, 8, 91, 24, 27, 8, 82, 74, 64, 8, 67, 65, 132, 63, + 68, 72, 75, 66, 66, 65, 74, 65, 74, 8, 46, 109, 74, 69, 67, 72, 69, 63, 68, 65, 74, + 6, 2, 82, 74, 81, 65, 79, 65, 8, 91, 8, 29, 28, 8, 64, 65, 80, 68, 61, 72, 62, + 8, 7, 87, 84, 69, 132, 63, 68, 65, 74, 6, 8, 73, 69, 81, 81, 65, 72, 62, 61, 79, + 8, 91, 8, 24, 27, 8, 82, 74, 64, 8, 67, 65, 132, 63, 68, 72, 75, 132, 132, 65, 74, + 65, 74, 8, 7, 46, 109, 74, 69, 67, 72, 69, 63, 68, 65, 74, 6, 2, 64, 61, 79, 82, + 73, 8, 87, 65, 82, 67, 65, 74, 64, 65, 74, 8, 64, 65, 80, 68, 61, 72, 62, 20, 8, + 0, 108, 8, 31, 28, 8, 42, 71, 109, 74, 74, 65, 74, 15, 8, 91, 8, 29, 25, 8, 84, + 65, 80, 68, 61, 72, 62, 8, 7, 64, 69, 65, 132, 65, 80, 6, 8, 14, 7, 55, 74, 79, + 82, 68, 65, 6, 15, 8, 91, 30, 8, 51, 79, 75, 70, 65, 71, 81, 65, 8, 65, 2, 64, + 61, 79, 82, 73, 8, 87, 65, 82, 67, 65, 74, 64, 65, 74, 8, 64, 65, 80, 68, 61, 72, + 62, 8, 91, 8, 31, 28, 8, 14, 7, 7, 71, 109, 74, 74, 65, 74, 6, 15, 8, 91, 53, + 8, 29, 25, 8, 84, 65, 80, 68, 61, 72, 62, 8, 7, 64, 69, 65, 132, 65, 80, 6, 8, + 14, 7, 55, 74, 79, 82, 68, 65, 6, 15, 8, 91, 8, 30, 8, 51, 79, 75, 70, 65, 71, + 81, 65, 2, 7, 48, 61, 102, 71, 61, 68, 73, 65, 81, 6, 8, 91, 8, 26, 26, 8, 61, + 62, 65, 79, 8, 45, 65, 74, 132, 65, 74, 62, 79, 110, 63, 71, 72, 65, 18, 8, 84, 61, + 79, 82, 73, 8, 14, 55, 74, 79, 82, 68, 65, 20, 15, 8, 69, 73, 8, 91, 8, 25, 26, + 8, 82, 74, 64, 8, 7, 36, 78, 78, 72, 61, 82, 80, 6, 8, 64, 65, 80, 8, 60, 82, + 84, 61, 63, 68, 80, 20, 2, 7, 48, 61, 102, 74, 61, 68, 73, 65, 74, 6, 8, 91, 8, + 26, 26, 8, 61, 62, 65, 79, 8, 45, 65, 74, 132, 65, 74, 62, 79, 110, 63, 71, 72, 65, + 18, 8, 84, 61, 79, 82, 73, 8, 14, 7, 55, 74, 79, 82, 68, 65, 6, 15, 8, 69, 73, + 8, 91, 8, 25, 26, 8, 82, 74, 64, 8, 7, 36, 78, 78, 72, 61, 82, 80, 6, 8, 64, + 65, 80, 8, 60, 82, 84, 61, 63, 68, 80, 2, 68, 82, 72, 64, 65, 78, 82, 81, 61, 81, + 69, 75, 74, 32, 8, 7, 46, 103, 73, 73, 65, 79, 65, 79, 6, 8, 91, 8, 27, 25, 8, + 84, 65, 80, 68, 82, 72, 64, 8, 132, 63, 68, 79, 65, 69, 62, 65, 74, 8, 82, 74, 64, + 8, 14, 7, 36, 82, 80, 72, 65, 67, 82, 74, 67, 6, 15, 8, 53, 8, 28, 24, 8, 64, + 65, 80, 68, 61, 72, 62, 2, 53, 63, 68, 82, 72, 64, 65, 78, 82, 81, 61, 81, 69, 75, + 74, 32, 8, 7, 46, 103, 73, 73, 65, 79, 65, 79, 6, 8, 91, 8, 27, 25, 8, 84, 65, + 80, 68, 61, 72, 62, 8, 132, 63, 68, 79, 65, 69, 62, 65, 74, 8, 82, 74, 64, 8, 14, + 7, 36, 82, 80, 72, 65, 67, 82, 74, 67, 6, 15, 8, 91, 8, 28, 24, 8, 64, 65, 80, + 68, 61, 72, 62, 2, 7, 7, 71, 110, 74, 66, 81, 69, 65, 6, 8, 87, 61, 68, 72, 79, + 65, 69, 63, 68, 65, 8, 7, 53, 110, 73, 78, 66, 65, 6, 8, 91, 8, 30, 25, 18, 8, + 61, 62, 65, 79, 8, 7, 43, 75, 63, 68, 84, 103, 72, 64, 65, 79, 9, 8, 82, 74, 64, + 8, 14, 7, 55, 65, 62, 65, 81, 132, 63, 68, 82, 102, 6, 15, 8, 64, 65, 80, 8, 91, + 8, 31, 8, 82, 74, 64, 2, 7, 71, 110, 74, 66, 81, 69, 67, 65, 6, 8, 87, 61, 68, + 72, 79, 65, 69, 63, 68, 65, 8, 7, 53, 110, 73, 78, 66, 65, 6, 8, 91, 8, 30, 25, + 18, 8, 61, 62, 65, 79, 8, 7, 43, 75, 63, 68, 84, 103, 72, 64, 65, 79, 6, 8, 82, + 74, 64, 8, 14, 7, 55, 65, 62, 65, 79, 132, 63, 68, 82, 102, 6, 15, 8, 64, 65, 80, + 8, 91, 8, 67, 8, 82, 74, 64, 2, 53, 51, 68, 61, 79, 87, 65, 72, 72, 65, 74, 6, + 8, 84, 65, 74, 69, 67, 65, 79, 8, 69, 73, 8, 91, 8, 27, 30, 18, 8, 64, 69, 65, + 132, 65, 73, 8, 7, 72, 69, 65, 68, 65, 74, 6, 8, 14, 91, 8, 31, 30, 15, 8, 82, + 74, 64, 8, 48, 61, 67, 69, 132, 81, 79, 61, 81, 19, 75, 64, 65, 79, 8, 14, 62, 65, + 66, 69, 74, 64, 72, 72, 69, 63, 68, 65, 74, 6, 15, 2, 7, 51, 61, 79, 87, 65, 72, + 72, 65, 74, 6, 8, 84, 65, 74, 69, 67, 65, 79, 8, 69, 73, 8, 91, 8, 27, 30, 18, + 8, 64, 69, 65, 132, 65, 73, 8, 7, 72, 69, 65, 68, 65, 74, 6, 8, 14, 91, 53, 8, + 31, 30, 15, 8, 82, 74, 64, 8, 48, 61, 67, 69, 132, 81, 79, 61, 81, 8, 75, 64, 65, + 79, 8, 14, 7, 62, 65, 66, 69, 74, 64, 72, 69, 63, 68, 65, 74, 6, 15, 2, 53, 8, + 27, 25, 8, 64, 61, 79, 82, 73, 8, 7, 64, 65, 80, 67, 72, 65, 69, 63, 68, 65, 74, + 6, 8, 53, 63, 68, 79, 69, 66, 81, 132, 81, 65, 72, 72, 65, 79, 8, 73, 69, 81, 8, + 37, 65, 73, 65, 79, 71, 82, 74, 67, 65, 74, 8, 14, 7, 36, 78, 78, 72, 61, 82, 80, + 6, 15, 20, 18, 8, 91, 43, 8, 25, 22, 8, 64, 65, 80, 68, 61, 72, 62, 2, 91, 8, + 31, 25, 8, 64, 61, 79, 82, 73, 8, 7, 64, 65, 80, 67, 72, 65, 69, 63, 68, 65, 74, + 6, 8, 53, 63, 68, 79, 69, 66, 81, 132, 81, 65, 72, 72, 65, 79, 8, 73, 69, 81, 8, + 37, 65, 73, 65, 79, 71, 82, 74, 67, 65, 74, 8, 14, 7, 36, 78, 78, 72, 61, 82, 80, + 6, 15, 20, 8, 91, 53, 8, 25, 22, 8, 64, 65, 80, 68, 61, 72, 62, 2, 71, 109, 74, + 74, 65, 74, 8, 75, 64, 65, 79, 8, 14, 7, 53, 63, 68, 82, 81, 87, 65, 6, 15, 8, + 91, 8, 28, 67, 8, 82, 74, 64, 8, 7, 82, 74, 67, 65, 66, 103, 68, 79, 6, 8, 79, + 65, 72, 61, 81, 69, 78, 65, 8, 91, 8, 28, 26, 8, 7, 43, 75, 63, 68, 84, 103, 72, + 64, 65, 79, 6, 8, 14, 7, 60, 84, 69, 132, 63, 68, 65, 74, 79, 82, 66, 6, 15, 8, + 63, 8, 30, 30, 2, 71, 109, 74, 74, 65, 74, 8, 75, 64, 65, 79, 8, 14, 7, 53, 63, + 68, 82, 81, 87, 65, 6, 15, 8, 91, 8, 28, 31, 8, 82, 74, 64, 8, 7, 82, 74, 67, + 65, 66, 103, 68, 79, 6, 8, 79, 65, 72, 61, 81, 69, 83, 65, 8, 91, 8, 28, 26, 8, + 7, 43, 75, 63, 68, 84, 103, 72, 64, 65, 79, 6, 8, 14, 7, 60, 84, 69, 132, 63, 68, + 65, 74, 79, 82, 66, 6, 15, 8, 91, 8, 30, 25, 2, 64, 65, 80, 68, 61, 72, 62, 8, + 83, 69, 65, 72, 72, 65, 69, 63, 68, 81, 8, 84, 65, 80, 68, 61, 72, 62, 8, 14, 7, + 37, 65, 79, 61, 81, 82, 74, 67, 6, 15, 8, 91, 8, 23, 30, 8, 75, 64, 65, 79, 8, + 7, 51, 75, 81, 80, 64, 61, 73, 6, 8, 68, 61, 82, 132, 65, 80, 2, 64, 65, 80, 68, + 61, 72, 62, 8, 83, 69, 65, 72, 72, 65, 69, 63, 68, 81, 8, 84, 65, 80, 68, 61, 72, + 62, 8, 14, 7, 37, 65, 79, 61, 81, 82, 74, 67, 6, 15, 8, 91, 8, 23, 30, 8, 75, + 64, 65, 79, 8, 7, 51, 75, 81, 80, 64, 61, 73, 6, 20, 18, 8, 68, 61, 82, 132, 65, + 80, 2, 37, 65, 79, 67, 73, 61, 74, 73, 74, 18, 8, 48, 61, 74, 67, 65, 72, 6, 8, + 91, 8, 25, 24, 8, 75, 64, 65, 79, 8, 70, 65, 64, 75, 63, 68, 8, 103, 62, 65, 79, + 8, 14, 7, 42, 79, 82, 78, 78, 65, 74, 6, 15, 8, 91, 8, 31, 22, 8, 64, 65, 80, + 68, 61, 72, 62, 8, 7, 39, 65, 80, 69, 74, 132, 65, 71, 81, 69, 75, 74, 6, 8, 37, + 65, 81, 81, 65, 74, 2, 37, 65, 79, 67, 73, 61, 74, 74, 8, 7, 48, 61, 74, 67, 65, + 72, 6, 8, 91, 8, 25, 24, 8, 75, 64, 65, 79, 8, 70, 65, 64, 75, 63, 68, 8, 61, + 62, 65, 79, 8, 14, 7, 42, 79, 82, 78, 78, 65, 74, 6, 15, 8, 91, 8, 31, 22, 8, + 64, 65, 80, 68, 61, 72, 62, 8, 7, 39, 65, 80, 69, 74, 132, 65, 71, 81, 69, 75, 74, + 6, 8, 37, 65, 81, 81, 65, 74, 2, 52, 49, 72, 65, 61, 72, 132, 63, 68, 82, 72, 65, + 8, 7, 36, 82, 80, 132, 63, 68, 82, 102, 6, 8, 91, 8, 28, 31, 18, 8, 91, 8, 29, + 30, 8, 64, 61, 85, 82, 73, 8, 68, 69, 74, 87, 82, 84, 65, 69, 132, 65, 74, 20, 8, + 50, 64, 65, 79, 8, 14, 7, 73, 110, 132, 132, 65, 74, 6, 15, 8, 91, 8, 27, 30, 8, + 82, 74, 64, 8, 91, 8, 30, 23, 8, 61, 62, 65, 79, 8, 7, 83, 75, 72, 72, 71, 75, + 73, 73, 65, 74, 6, 2, 52, 65, 61, 72, 132, 63, 68, 82, 72, 65, 8, 7, 36, 82, 80, + 132, 63, 68, 82, 102, 6, 8, 91, 8, 28, 31, 18, 8, 91, 8, 29, 30, 8, 64, 61, 79, + 82, 73, 8, 68, 69, 74, 87, 82, 84, 65, 69, 132, 65, 74, 20, 8, 50, 64, 65, 79, 8, + 14, 7, 73, 110, 132, 132, 65, 74, 6, 15, 8, 91, 8, 27, 30, 8, 82, 74, 64, 8, 91, + 8, 30, 23, 8, 61, 62, 65, 79, 8, 7, 83, 75, 72, 72, 71, 75, 73, 73, 65, 74, 6, + 2, 65, 74, 81, 68, 103, 72, 81, 8, 53, 61, 63, 68, 65, 74, 8, 7, 42, 82, 132, 81, + 61, 83, 6, 8, 27, 8, 39, 18, 8, 91, 8, 28, 22, 20, 8, 91, 8, 23, 23, 18, 8, + 61, 62, 65, 79, 8, 53, 63, 68, 61, 74, 71, 84, 69, 79, 81, 8, 84, 65, 80, 68, 61, + 72, 62, 8, 14, 7, 53, 63, 68, 82, 72, 61, 82, 132, 132, 69, 63, 68, 81, 80, 75, 79, + 67, 61, 74, 6, 15, 2, 65, 74, 81, 68, 103, 72, 81, 8, 53, 61, 63, 68, 65, 74, 8, + 7, 42, 82, 132, 81, 61, 83, 6, 8, 91, 8, 31, 18, 8, 91, 8, 28, 22, 18, 8, 91, + 53, 8, 23, 23, 18, 8, 61, 62, 65, 79, 8, 53, 63, 68, 61, 74, 71, 84, 69, 79, 81, + 8, 84, 65, 80, 68, 61, 72, 62, 8, 14, 7, 53, 63, 68, 82, 72, 61, 82, 66, 132, 69, + 63, 68, 81, 80, 75, 79, 67, 61, 74, 6, 15, 2, 7, 61, 62, 65, 79, 8, 7, 62, 65, + 87, 75, 67, 65, 74, 6, 8, 57, 61, 74, 64, 65, 72, 78, 61, 74, 75, 79, 61, 73, 61, + 8, 30, 91, 8, 25, 26, 8, 36, 82, 66, 132, 69, 63, 68, 81, 18, 64, 65, 80, 67, 72, + 65, 69, 63, 68, 65, 74, 6, 8, 75, 64, 65, 79, 8, 61, 62, 84, 65, 69, 63, 68, 65, + 74, 64, 65, 79, 2, 61, 62, 65, 79, 8, 7, 62, 65, 87, 75, 67, 65, 74, 6, 8, 57, + 61, 74, 64, 65, 72, 78, 61, 74, 75, 79, 61, 73, 61, 8, 91, 8, 25, 26, 8, 36, 82, + 66, 132, 69, 63, 68, 81, 8, 7, 64, 65, 80, 67, 72, 65, 69, 63, 68, 65, 74, 6, 8, + 75, 64, 65, 79, 8, 61, 62, 84, 65, 69, 63, 68, 65, 74, 64, 65, 79, 18, 2, 65, 80, + 68, 61, 72, 62, 8, 14, 7, 132, 63, 68, 65, 69, 74, 81, 80, 6, 15, 8, 53, 8, 29, + 25, 8, 75, 64, 65, 79, 8, 7, 67, 65, 72, 65, 67, 81, 6, 8, 46, 82, 64, 61, 73, + 73, 69, 132, 81, 79, 61, 102, 65, 8, 91, 8, 27, 26, 8, 62, 65, 84, 65, 79, 71, 132, + 81, 65, 72, 72, 69, 67, 81, 18, 2, 84, 65, 80, 68, 61, 72, 62, 8, 14, 7, 132, 63, + 68, 65, 69, 74, 81, 80, 6, 15, 8, 91, 8, 29, 25, 8, 75, 64, 65, 79, 8, 7, 67, + 65, 72, 65, 67, 81, 6, 8, 46, 82, 64, 61, 73, 73, 132, 81, 79, 61, 102, 65, 8, 91, + 8, 27, 8, 62, 65, 84, 65, 79, 71, 132, 81, 65, 72, 72, 69, 67, 81, 33, 2, 51, 70, + 68, 69, 74, 132, 69, 63, 68, 81, 72, 69, 63, 68, 32, 8, 91, 8, 25, 27, 8, 75, 64, + 65, 79, 8, 110, 62, 65, 79, 74, 75, 73, 73, 65, 74, 8, 75, 64, 65, 79, 8, 14, 7, + 48, 103, 74, 74, 65, 79, 6, 15, 8, 91, 8, 23, 26, 18, 8, 84, 65, 69, 72, 8, 7, + 83, 65, 72, 63, 68, 65, 74, 9, 6, 2, 7, 68, 69, 74, 132, 69, 63, 68, 81, 72, 69, + 63, 68, 6, 8, 91, 53, 8, 25, 27, 8, 75, 64, 65, 79, 8, 110, 62, 65, 79, 74, 75, + 73, 73, 65, 74, 8, 75, 64, 65, 79, 8, 14, 7, 48, 103, 74, 74, 65, 79, 6, 15, 8, + 91, 8, 23, 23, 18, 8, 84, 65, 69, 72, 8, 7, 84, 65, 72, 63, 68, 65, 79, 6, 2, + 37, 65, 81, 81, 65, 74, 8, 91, 8, 27, 24, 8, 45, 65, 74, 132, 65, 74, 78, 72, 61, + 81, 87, 7, 64, 65, 66, 69, 74, 69, 81, 69, 83, 6, 8, 91, 8, 23, 26, 18, 8, 61, + 62, 65, 79, 8, 14, 61, 74, 64, 65, 79, 65, 6, 15, 8, 91, 8, 25, 26, 18, 8, 84, + 65, 69, 72, 7, 83, 69, 65, 72, 72, 65, 69, 63, 68, 8, 3, 2, 37, 65, 81, 81, 65, + 74, 8, 91, 8, 27, 24, 8, 45, 65, 74, 132, 65, 74, 78, 72, 61, 81, 87, 8, 7, 64, + 65, 66, 69, 74, 69, 81, 69, 83, 6, 8, 91, 53, 8, 23, 26, 18, 8, 61, 62, 65, 79, + 8, 7, 61, 74, 64, 65, 79, 65, 6, 15, 8, 91, 8, 25, 26, 18, 8, 84, 65, 69, 72, + 8, 7, 83, 69, 65, 72, 72, 65, 69, 63, 68, 81, 6, 2, 64, 79, 65, 69, 80, 132, 63, + 68, 82, 72, 69, 74, 132, 78, 65, 71, 81, 75, 79, 8, 30, 8, 24, 29, 8, 62, 65, 72, + 65, 67, 81, 8, 7, 67, 65, 81, 79, 75, 66, 66, 65, 74, 65, 6, 8, 53, 8, 28, 23, + 8, 84, 65, 80, 68, 61, 72, 62, 8, 65, 79, 66, 75, 72, 67, 65, 74, 2, 46, 79, 65, + 69, 80, 132, 63, 68, 82, 72, 69, 74, 132, 78, 65, 71, 81, 75, 79, 8, 91, 8, 24, 29, + 8, 62, 65, 72, 65, 67, 81, 8, 7, 67, 65, 81, 79, 75, 66, 66, 65, 74, 65, 6, 8, + 91, 8, 28, 23, 8, 84, 65, 80, 68, 61, 72, 62, 8, 65, 79, 66, 75, 72, 67, 65, 74, + 2, 55, 74, 64, 8, 14, 18, 8, 51, 79, 75, 70, 65, 71, 81, 65, 8, 15, 8, 91, 8, + 30, 26, 8, 84, 65, 80, 68, 61, 72, 62, 8, 7, 84, 65, 80, 68, 61, 72, 62, 6, 8, + 39, 69, 80, 78, 75, 4, 71, 86, 65, 64, 69, 81, 65, 8, 69, 74, 8, 30, 27, 27, 2, + 82, 74, 64, 8, 14, 7, 51, 79, 75, 70, 65, 71, 81, 65, 6, 15, 8, 91, 53, 8, 30, + 26, 8, 84, 65, 80, 68, 61, 72, 62, 8, 7, 84, 65, 80, 68, 61, 72, 62, 6, 8, 39, + 69, 80, 78, 75, 19, 85, 79, 65, 64, 69, 81, 65, 8, 69, 74, 8, 91, 8, 27, 28, 2, + 65, 79, 84, 75, 79, 62, 65, 74, 18, 8, 57, 61, 74, 64, 65, 79, 82, 74, 67, 8, 97, + 8, 49, 65, 82, 62, 61, 82, 8, 23, 30, 31, 31, 8, 23, 31, 22, 24, 20, 8, 7, 21, + 8, 61, 74, 67, 65, 73, 65, 132, 132, 65, 74, 8, 82, 74, 64, 8, 82, 74, 64, 8, 26, + 31, 8, 83, 75, 74, 8, 25, 23, 22, 8, 84, 69, 79, 64, 20, 2, 65, 79, 84, 75, 79, + 62, 65, 74, 20, 8, 57, 61, 74, 64, 65, 79, 82, 74, 67, 8, 7, 97, 8, 49, 65, 82, + 62, 61, 82, 8, 23, 30, 31, 31, 8, 23, 31, 22, 24, 20, 8, 97, 8, 61, 74, 67, 65, + 73, 65, 132, 132, 65, 74, 8, 82, 74, 64, 8, 82, 74, 64, 8, 26, 31, 8, 18, 96, 98, + 8, 83, 75, 74, 8, 25, 23, 22, 22, 8, 84, 69, 79, 64, 20, 2, 87, 84, 65, 69, 20, + 4, 8, 49, 65, 8, 71, 7, 65, 69, 74, 65, 73, 8, 64, 65, 79, 8, 23, 30, 29, 25, + 20, 8, 23, 30, 26, 8, 42, 65, 62, 69, 65, 81, 65, 74, 8, 7, 64, 61, 79, 110, 62, + 65, 79, 6, 8, 27, 30, 8, 61, 82, 80, 8, 24, 22, 22, 8, 66, 82, 79, 63, 68, 81, + 62, 61, 79, 2, 87, 84, 65, 69, 8, 64, 69, 65, 8, 97, 8, 65, 69, 74, 65, 73, 8, + 64, 65, 79, 8, 23, 30, 29, 25, 11, 8, 97, 8, 23, 30, 26, 8, 42, 65, 62, 69, 65, + 81, 65, 74, 8, 7, 64, 61, 79, 110, 62, 65, 79, 6, 8, 27, 30, 8, 97, 8, 61, 82, + 80, 8, 24, 22, 8, 66, 82, 79, 63, 68, 81, 62, 61, 79, 2, 23, 25, 29, 31, 22, 31, + 8, 64, 61, 79, 110, 62, 65, 79, 8, 29, 8, 36, 82, 80, 67, 61, 62, 65, 74, 8, 52, + 65, 82, 62, 61, 82, 8, 28, 27, 22, 8, 7, 21, 8, 39, 69, 74, 67, 65, 8, 36, 74, + 79, 82, 66, 65, 74, 8, 75, 64, 65, 79, 8, 23, 23, 8, 96, 8, 23, 30, 29, 29, 8, + 24, 22, 22, 22, 8, 11, 8, 46, 109, 74, 69, 67, 69, 74, 2, 23, 30, 31, 31, 8, 64, + 61, 79, 110, 62, 65, 79, 8, 36, 82, 80, 67, 61, 62, 65, 74, 8, 49, 65, 82, 62, 61, + 82, 8, 28, 27, 22, 22, 8, 29, 21, 8, 39, 69, 74, 67, 65, 8, 36, 74, 79, 82, 66, + 65, 74, 8, 75, 64, 65, 79, 8, 23, 23, 8, 97, 8, 23, 30, 29, 29, 8, 24, 22, 22, + 22, 8, 11, 8, 46, 109, 74, 69, 67, 69, 74, 2, 64, 61, 67, 65, 67, 65, 74, 8, 132, + 75, 74, 64, 65, 79, 74, 20, 8, 64, 69, 65, 8, 70, 65, 64, 75, 63, 68, 8, 23, 30, + 27, 27, 20, 8, 24, 15, 8, 56, 65, 79, 67, 72, 65, 69, 63, 68, 4, 53, 69, 81, 87, + 82, 74, 67, 8, 64, 65, 79, 8, 28, 31, 8, 97, 8, 132, 81, 69, 65, 67, 8, 23, 31, + 22, 27, 11, 8, 64, 61, 80, 2, 64, 61, 67, 65, 67, 65, 74, 8, 132, 75, 74, 64, 65, + 79, 74, 8, 97, 8, 64, 69, 65, 8, 70, 65, 64, 75, 63, 68, 8, 23, 30, 27, 27, 20, + 8, 24, 18, 8, 56, 65, 79, 67, 72, 65, 69, 63, 68, 8, 53, 69, 81, 87, 82, 74, 67, + 8, 64, 65, 79, 8, 28, 31, 8, 97, 8, 132, 81, 69, 65, 67, 8, 23, 31, 22, 27, 22, + 11, 8, 64, 61, 80, 2, 70, 78, 78, 79, 65, 63, 68, 65, 8, 69, 63, 68, 8, 7, 8, + 25, 30, 26, 8, 27, 30, 28, 24, 8, 25, 27, 8, 11, 8, 29, 25, 28, 8, 75, 64, 65, + 79, 8, 53, 61, 73, 73, 65, 72, 68, 65, 69, 87, 82, 74, 67, 8, 24, 23, 8, 27, 18, + 8, 83, 65, 79, 62, 69, 74, 64, 65, 74, 8, 23, 30, 31, 25, 22, 11, 8, 74, 75, 63, + 68, 2, 132, 78, 79, 65, 63, 68, 65, 8, 69, 63, 68, 8, 18, 8, 25, 30, 26, 8, 27, + 30, 28, 24, 8, 25, 27, 8, 7, 29, 8, 24, 8, 29, 25, 28, 8, 75, 64, 65, 79, 8, + 53, 61, 73, 73, 65, 72, 68, 65, 69, 87, 82, 74, 67, 8, 24, 23, 8, 27, 8, 83, 65, + 79, 62, 69, 74, 64, 65, 74, 8, 23, 30, 31, 25, 22, 8, 74, 75, 63, 68, 2, 22, 30, + 20, 8, 62, 65, 84, 69, 72, 72, 69, 67, 81, 8, 7, 8, 26, 28, 27, 31, 8, 65, 69, + 74, 81, 79, 65, 81, 65, 74, 8, 23, 22, 27, 26, 11, 8, 7, 97, 8, 68, 61, 62, 65, + 74, 8, 64, 65, 79, 8, 27, 27, 20, 8, 23, 28, 26, 27, 8, 97, 18, 18, 8, 39, 65, + 79, 8, 25, 22, 31, 22, 8, 36, 74, 132, 78, 79, 110, 63, 68, 65, 74, 2, 22, 30, 20, + 8, 62, 65, 84, 69, 72, 72, 69, 67, 81, 8, 11, 8, 23, 28, 25, 31, 8, 65, 69, 74, + 81, 79, 65, 81, 65, 74, 8, 23, 22, 27, 26, 22, 11, 8, 97, 8, 68, 61, 62, 65, 74, + 8, 64, 65, 79, 8, 27, 27, 20, 8, 23, 28, 26, 27, 8, 18, 8, 7, 39, 65, 79, 8, + 25, 22, 22, 21, 8, 36, 74, 132, 78, 79, 110, 63, 68, 65, 74, 2, 56, 65, 79, 4, 8, + 42, 79, 82, 74, 64, 61, 82, 66, 8, 27, 8, 67, 65, 68, 65, 74, 64, 8, 64, 65, 73, + 8, 23, 24, 20, 11, 8, 11, 8, 57, 75, 68, 74, 82, 74, 67, 65, 74, 8, 25, 25, 26, + 8, 67, 65, 78, 79, 110, 66, 81, 65, 74, 8, 23, 31, 23, 27, 20, 21, 8, 23, 31, 24, + 22, 8, 23, 31, 23, 28, 8, 11, 8, 23, 27, 22, 2, 56, 65, 79, 19, 4, 42, 79, 82, + 74, 64, 61, 82, 66, 8, 7, 8, 67, 65, 68, 65, 74, 64, 8, 64, 65, 73, 8, 23, 24, + 11, 8, 57, 75, 68, 74, 82, 74, 67, 65, 74, 8, 25, 25, 26, 8, 67, 65, 78, 79, 110, + 66, 81, 65, 74, 8, 23, 31, 23, 27, 8, 97, 8, 23, 31, 24, 22, 8, 23, 31, 23, 28, + 8, 23, 27, 22, 2, 53, 69, 65, 8, 62, 65, 4, 18, 8, 36, 74, 67, 65, 72, 65, 67, + 65, 74, 68, 65, 69, 81, 65, 74, 8, 42, 79, 75, 72, 73, 61, 74, 4, 85, 23, 31, 24, + 22, 20, 8, 21, 8, 83, 20, 8, 23, 22, 22, 8, 64, 65, 79, 8, 28, 29, 8, 22, 8, + 66, 110, 79, 8, 23, 25, 22, 24, 8, 11, 8, 64, 69, 65, 2, 53, 69, 65, 8, 62, 65, + 19, 97, 8, 36, 74, 67, 65, 72, 65, 67, 65, 74, 68, 65, 69, 81, 65, 74, 8, 42, 79, + 75, 72, 73, 61, 74, 19, 23, 31, 24, 22, 20, 8, 97, 8, 124, 63, 20, 8, 23, 22, 22, + 8, 64, 65, 79, 8, 28, 29, 8, 97, 8, 66, 110, 79, 8, 23, 25, 22, 24, 8, 29, 8, + 64, 69, 65, 2, 69, 74, 87, 65, 72, 74, 65, 74, 8, 65, 69, 74, 132, 65, 69, 81, 69, + 4, 8, 7, 8, 83, 65, 79, 81, 79, 61, 67, 8, 64, 65, 79, 8, 30, 26, 20, 8, 27, + 28, 8, 67, 82, 81, 65, 79, 20, 8, 73, 69, 81, 8, 69, 132, 81, 8, 23, 30, 24, 28, + 8, 11, 8, 23, 30, 26, 25, 8, 23, 26, 8, 11, 8, 0, 105, 79, 67, 65, 62, 65, 74, + 20, 18, 2, 65, 69, 74, 87, 65, 72, 74, 65, 74, 8, 65, 69, 74, 132, 65, 69, 81, 69, + 19, 8, 83, 65, 79, 81, 79, 61, 67, 8, 64, 65, 79, 8, 30, 26, 20, 8, 31, 8, 67, + 82, 81, 65, 79, 8, 73, 69, 81, 8, 69, 132, 81, 8, 23, 30, 24, 28, 8, 97, 8, 23, + 30, 26, 25, 8, 23, 26, 8, 29, 8, 65, 79, 67, 65, 62, 65, 74, 20, 2, 70, 104, 64, + 75, 63, 68, 8, 87, 82, 79, 8, 7, 8, 51, 79, 110, 66, 82, 74, 67, 8, 46, 79, 69, + 65, 67, 65, 8, 23, 24, 24, 26, 18, 8, 97, 8, 84, 69, 65, 8, 83, 75, 73, 8, 64, + 65, 79, 8, 23, 23, 20, 21, 8, 67, 65, 81, 79, 75, 66, 66, 65, 74, 65, 8, 31, 31, + 8, 84, 65, 69, 72, 2, 70, 65, 64, 75, 63, 68, 8, 87, 82, 79, 8, 7, 97, 8, 51, + 79, 110, 66, 82, 74, 67, 8, 46, 79, 69, 65, 67, 65, 8, 23, 24, 24, 26, 20, 8, 97, + 8, 84, 69, 65, 8, 83, 75, 73, 8, 64, 65, 79, 8, 23, 23, 8, 24, 15, 8, 67, 65, + 81, 79, 75, 66, 66, 65, 74, 65, 8, 31, 22, 8, 84, 65, 69, 72, 2, 64, 61, 102, 8, + 36, 73, 75, 79, 81, 69, 132, 61, 81, 69, 75, 74, 8, 99, 22, 8, 48, 61, 102, 65, 8, + 64, 65, 79, 8, 23, 29, 22, 29, 11, 8, 23, 26, 27, 8, 57, 65, 69, 132, 65, 8, 83, + 75, 74, 8, 27, 29, 8, 11, 8, 41, 103, 72, 72, 65, 74, 8, 29, 22, 22, 22, 8, 82, + 74, 64, 2, 64, 61, 102, 8, 36, 73, 75, 79, 81, 69, 132, 61, 81, 69, 75, 74, 8, 97, + 8, 48, 61, 102, 65, 8, 64, 65, 79, 8, 23, 29, 22, 33, 8, 21, 8, 23, 26, 27, 8, + 57, 65, 69, 132, 65, 8, 83, 75, 74, 8, 27, 29, 8, 41, 103, 72, 72, 65, 74, 8, 29, + 22, 22, 22, 22, 11, 8, 82, 74, 64, 2, 73, 68, 81, 8, 64, 65, 79, 8, 27, 18, 8, + 52, 61, 81, 68, 61, 82, 132, 65, 80, 8, 64, 69, 65, 8, 24, 18, 8, 6, 8, 64, 65, + 79, 8, 61, 72, 66, 8, 87, 82, 79, 8, 28, 27, 22, 8, 97, 8, 84, 65, 79, 64, 65, + 74, 8, 23, 29, 28, 27, 22, 11, 8, 61, 82, 66, 79, 65, 63, 68, 81, 2, 87, 82, 73, + 8, 64, 65, 79, 8, 24, 8, 52, 61, 81, 68, 61, 82, 132, 65, 80, 8, 64, 69, 65, 8, + 24, 18, 8, 64, 65, 79, 8, 61, 82, 66, 8, 87, 82, 79, 8, 28, 27, 22, 8, 97, 8, + 84, 65, 79, 64, 65, 74, 8, 23, 29, 28, 27, 22, 8, 61, 82, 66, 79, 65, 63, 68, 81, + 2, 7, 37, 65, 87, 69, 65, 68, 82, 74, 67, 8, 82, 74, 132, 65, 79, 73, 8, 7, 69, + 68, 79, 65, 79, 8, 82, 74, 64, 8, 23, 31, 20, 8, 64, 65, 79, 8, 82, 74, 64, 8, + 83, 65, 79, 87, 69, 63, 68, 81, 65, 74, 8, 30, 22, 8, 65, 74, 81, 132, 78, 79, 69, + 63, 68, 81, 8, 31, 31, 11, 8, 82, 74, 80, 2, 37, 65, 87, 69, 65, 68, 82, 74, 67, + 8, 82, 74, 132, 65, 79, 73, 8, 69, 68, 79, 65, 79, 8, 82, 74, 64, 8, 23, 31, 20, + 8, 97, 8, 64, 65, 79, 8, 82, 74, 64, 8, 83, 65, 79, 87, 69, 63, 68, 81, 65, 74, + 8, 30, 22, 8, 29, 8, 65, 74, 81, 132, 78, 79, 69, 63, 68, 81, 8, 31, 31, 22, 11, + 8, 82, 74, 80, 2, 82, 74, 80, 8, 87, 82, 79, 8, 27, 31, 28, 8, 67, 65, 74, 8, + 53, 63, 68, 110, 72, 65, 79, 8, 23, 28, 22, 22, 8, 18, 8, 132, 75, 84, 69, 65, 8, + 64, 61, 79, 82, 73, 8, 52, 65, 61, 72, 132, 63, 68, 82, 72, 65, 8, 23, 31, 24, 22, + 8, 21, 8, 64, 82, 79, 63, 68, 8, 24, 22, 22, 22, 8, 11, 8, 61, 74, 64, 65, 79, + 65, 2, 82, 74, 80, 8, 87, 82, 79, 8, 0, 5, 8, 67, 65, 74, 8, 53, 63, 68, 110, + 72, 65, 79, 8, 23, 28, 22, 22, 11, 8, 97, 8, 132, 75, 84, 69, 65, 8, 64, 61, 79, + 82, 73, 8, 52, 65, 61, 72, 132, 63, 68, 82, 72, 65, 8, 23, 31, 24, 22, 8, 97, 8, + 64, 82, 79, 63, 68, 8, 24, 22, 22, 22, 8, 6, 8, 61, 74, 64, 65, 79, 65, 2, 64, + 61, 102, 8, 64, 65, 79, 67, 72, 65, 69, 63, 68, 65, 74, 8, 7, 8, 132, 69, 63, 68, + 8, 62, 69, 80, 8, 24, 28, 22, 22, 18, 8, 18, 18, 8, 45, 82, 74, 69, 8, 79, 82, + 74, 64, 8, 62, 65, 69, 73, 8, 31, 8, 97, 8, 57, 75, 68, 74, 82, 74, 67, 65, 74, + 8, 23, 22, 22, 8, 83, 75, 74, 2, 62, 72, 69, 65, 62, 8, 74, 61, 63, 68, 8, 79, + 82, 74, 64, 8, 71, 109, 74, 74, 65, 74, 8, 26, 20, 8, 97, 8, 64, 65, 73, 8, 48, + 61, 79, 71, 8, 84, 65, 69, 72, 8, 30, 8, 97, 60, 8, 66, 110, 79, 8, 23, 30, 31, + 23, 8, 29, 8, 132, 79, 61, 102, 65, 2, 72, 65, 81, 87, 81, 65, 74, 8, 36, 74, 81, + 79, 61, 67, 8, 98, 97, 8, 64, 61, 79, 82, 73, 8, 23, 31, 23, 22, 21, 23, 27, 20, + 8, 28, 29, 22, 11, 8, 97, 8, 64, 69, 65, 132, 65, 73, 8, 45, 61, 68, 79, 8, 69, + 63, 68, 8, 25, 29, 8, 97, 8, 65, 69, 74, 65, 74, 8, 24, 22, 27, 22, 22, 11, 8, + 53, 81, 65, 82, 65, 79, 74, 2, 47, 65, 68, 79, 78, 72, 103, 74, 65, 74, 8, 23, 27, + 23, 24, 8, 97, 8, 36, 79, 87, 81, 8, 64, 65, 79, 8, 23, 30, 22, 31, 20, 8, 97, + 8, 124, 63, 20, 8, 51, 65, 79, 132, 75, 74, 8, 65, 74, 81, 132, 63, 68, 65, 69, 64, + 65, 74, 64, 65, 8, 26, 22, 8, 97, 8, 74, 61, 63, 68, 8, 23, 30, 31, 31, 31, 8, + 64, 65, 79, 2, 36, 62, 84, 61, 74, 64, 65, 79, 82, 74, 67, 8, 56, 75, 79, 64, 65, + 79, 68, 61, 82, 132, 65, 80, 8, 7, 97, 8, 64, 65, 73, 8, 70, 65, 64, 65, 79, 8, + 23, 31, 22, 23, 20, 8, 22, 22, 20, 8, 64, 69, 65, 8, 64, 69, 65, 8, 30, 24, 8, + 97, 8, 74, 61, 63, 68, 8, 26, 22, 22, 8, 7, 48, 103, 74, 74, 65, 79, 6, 2, 64, + 65, 73, 8, 84, 69, 79, 8, 27, 28, 8, 83, 75, 74, 8, 84, 65, 72, 63, 68, 65, 8, + 23, 31, 20, 8, 24, 15, 8, 64, 69, 65, 8, 74, 61, 63, 68, 8, 87, 69, 74, 132, 82, + 74, 67, 8, 25, 22, 8, 29, 8, 61, 62, 65, 79, 8, 23, 30, 27, 22, 22, 11, 11, 8, + 83, 75, 74, 2, 82, 74, 64, 8, 45, 82, 74, 67, 65, 74, 8, 7, 8, 64, 65, 73, 8, + 62, 69, 80, 8, 23, 22, 22, 18, 8, 98, 8, 36, 74, 132, 78, 79, 82, 63, 68, 8, 64, + 65, 79, 8, 64, 61, 79, 82, 73, 8, 23, 22, 8, 61, 82, 63, 68, 8, 25, 8, 53, 63, + 68, 69, 72, 72, 65, 79, 19, 54, 68, 65, 61, 81, 65, 79, 2, 81, 65, 74, 62, 82, 79, + 67, 8, 23, 24, 22, 11, 8, 97, 8, 23, 24, 8, 7, 29, 8, 7, 57, 61, 68, 79, 72, + 69, 63, 68, 9, 6, 8, 30, 30, 20, 8, 24, 18, 8, 62, 69, 80, 8, 73, 109, 63, 68, + 81, 65, 8, 73, 61, 74, 63, 68, 73, 61, 72, 8, 23, 30, 8, 97, 8, 87, 82, 72, 65, + 67, 65, 74, 8, 25, 26, 22, 11, 8, 73, 61, 63, 68, 65, 2, 64, 65, 74, 8, 24, 27, + 22, 22, 8, 97, 8, 82, 74, 64, 8, 65, 79, 66, 79, 61, 67, 65, 74, 20, 8, 23, 22, + 20, 8, 31, 21, 8, 69, 68, 79, 65, 8, 132, 75, 84, 69, 65, 8, 64, 69, 65, 8, 23, + 30, 29, 31, 8, 97, 8, 65, 79, 132, 81, 65, 74, 73, 75, 72, 8, 23, 30, 31, 30, 31, + 21, 8, 23, 30, 23, 23, 2, 83, 75, 73, 8, 51, 79, 75, 70, 65, 71, 81, 8, 27, 18, + 8, 69, 132, 81, 8, 84, 110, 74, 132, 63, 68, 65, 74, 80, 84, 65, 79, 81, 8, 23, 24, + 18, 8, 7, 8, 75, 64, 65, 79, 8, 132, 69, 74, 64, 20, 8, 24, 30, 26, 8, 23, 31, + 23, 30, 8, 24, 18, 8, 52, 65, 69, 68, 65, 8, 24, 22, 11, 22, 8, 48, 65, 69, 74, + 82, 74, 67, 2, 68, 61, 62, 65, 74, 8, 67, 65, 68, 65, 74, 8, 64, 65, 79, 8, 44, + 72, 20, 8, 29, 25, 11, 8, 24, 26, 26, 26, 26, 8, 84, 103, 72, 64, 65, 79, 8, 39, + 69, 65, 8, 23, 30, 27, 23, 8, 83, 75, 73, 8, 24, 29, 22, 8, 37, 65, 81, 65, 69, + 72, 69, 67, 81, 65, 74, 2, 24, 27, 22, 22, 8, 30, 22, 22, 22, 8, 7, 8, 65, 69, + 74, 67, 65, 66, 110, 68, 79, 81, 20, 8, 54, 65, 74, 75, 79, 8, 24, 27, 20, 8, 7, + 8, 65, 69, 74, 8, 87, 82, 132, 81, 103, 74, 64, 69, 67, 65, 74, 8, 64, 61, 79, 82, + 73, 8, 23, 31, 22, 23, 8, 97, 8, 64, 65, 73, 8, 23, 31, 22, 26, 31, 18, 8, 39, + 65, 74, 2, 65, 69, 74, 65, 73, 8, 64, 65, 79, 8, 97, 8, 23, 31, 22, 24, 8, 65, + 69, 74, 65, 8, 23, 30, 26, 22, 18, 8, 25, 28, 8, 39, 69, 65, 8, 37, 65, 132, 63, + 68, 72, 82, 102, 8, 84, 61, 79, 65, 74, 8, 23, 24, 8, 24, 18, 8, 64, 65, 79, 8, + 27, 22, 22, 11, 8, 64, 69, 65, 2, 84, 82, 79, 64, 65, 8, 65, 69, 74, 65, 80, 8, + 27, 28, 8, 62, 82, 79, 67, 65, 79, 8, 64, 69, 65, 8, 28, 27, 22, 22, 11, 8, 11, + 8, 25, 22, 22, 8, 74, 69, 73, 73, 81, 8, 64, 65, 80, 68, 61, 72, 62, 8, 25, 22, + 22, 8, 24, 97, 8, 23, 22, 27, 24, 8, 24, 22, 8, 29, 8, 53, 63, 68, 82, 72, 65, + 74, 2, 83, 75, 73, 8, 64, 65, 79, 8, 7, 8, 64, 65, 79, 8, 84, 69, 79, 64, 20, + 6, 8, 30, 30, 20, 8, 27, 26, 31, 8, 23, 31, 23, 30, 8, 23, 27, 24, 25, 8, 23, + 8, 27, 28, 8, 44, 74, 81, 65, 79, 65, 132, 132, 65, 74, 132, 81, 61, 74, 64, 78, 82, + 74, 71, 81, 8, 25, 22, 22, 8, 67, 65, 68, 65, 74, 2, 64, 69, 65, 8, 84, 61, 79, + 8, 44, 74, 132, 81, 61, 72, 72, 61, 81, 69, 75, 74, 8, 64, 65, 79, 8, 23, 25, 25, + 24, 97, 8, 64, 69, 65, 8, 27, 22, 22, 22, 8, 64, 65, 80, 8, 30, 30, 30, 8, 97, + 8, 73, 69, 81, 8, 23, 26, 22, 22, 8, 61, 62, 65, 79, 2, 72, 65, 81, 87, 81, 65, + 79, 65, 73, 8, 51, 65, 74, 87, 69, 67, 8, 18, 97, 8, 64, 65, 79, 8, 65, 79, 68, + 109, 68, 65, 74, 8, 23, 27, 11, 8, 24, 8, 132, 65, 69, 8, 84, 65, 72, 63, 68, 65, + 8, 65, 69, 74, 65, 80, 8, 26, 24, 8, 97, 8, 39, 61, 80, 8, 27, 22, 22, 22, 8, + 39, 61, 80, 2, 74, 69, 63, 68, 81, 8, 62, 69, 80, 8, 75, 8, 73, 69, 81, 8, 59, + 78, 80, 69, 72, 75, 74, 8, 29, 24, 20, 8, 97, 8, 53, 81, 103, 64, 81, 65, 8, 132, + 65, 81, 87, 65, 74, 8, 31, 30, 27, 8, 24, 22, 8, 97, 8, 64, 82, 79, 63, 68, 8, + 29, 30, 29, 8, 53, 63, 68, 82, 72, 64, 65, 78, 82, 81, 61, 81, 69, 75, 74, 2, 64, + 65, 74, 8, 23, 24, 27, 22, 8, 97, 8, 73, 65, 68, 79, 8, 48, 75, 73, 73, 132, 65, + 74, 19, 25, 22, 22, 22, 18, 8, 27, 28, 8, 56, 75, 79, 132, 81, 65, 68, 65, 79, 8, + 49, 75, 74, 74, 65, 74, 64, 61, 73, 73, 8, 45, 80, 20, 8, 23, 31, 23, 29, 8, 97, + 8, 64, 61, 79, 61, 74, 8, 27, 26, 22, 22, 8, 75, 62, 3, 2, 52, 65, 69, 68, 65, + 74, 20, 8, 62, 65, 71, 61, 73, 65, 74, 20, 8, 24, 15, 8, 68, 65, 79, 87, 82, 19, + 8, 73, 69, 81, 8, 23, 30, 31, 23, 22, 11, 22, 8, 7, 8, 61, 82, 66, 8, 61, 82, + 80, 8, 64, 61, 79, 82, 73, 8, 23, 30, 31, 22, 8, 27, 21, 28, 8, 64, 75, 63, 68, + 8, 23, 30, 24, 27, 29, 8, 45, 81, 124, 20, 2, 82, 74, 64, 8, 25, 24, 31, 21, 24, + 25, 8, 97, 8, 61, 72, 80, 8, 37, 65, 81, 81, 65, 74, 8, 23, 30, 30, 25, 18, 8, + 97, 8, 55, 74, 81, 65, 79, 62, 61, 82, 8, 64, 61, 80, 8, 67, 65, 132, 81, 61, 72, + 81, 65, 81, 65, 8, 30, 25, 8, 50, 79, 64, 20, 8, 23, 31, 23, 28, 8, 64, 65, 80, + 2, 64, 65, 79, 8, 37, 65, 81, 79, 61, 67, 8, 27, 28, 8, 43, 110, 72, 66, 65, 8, + 61, 82, 80, 8, 23, 31, 22, 30, 18, 8, 18, 8, 64, 65, 79, 8, 56, 65, 79, 84, 61, + 72, 81, 82, 74, 67, 20, 8, 64, 65, 80, 8, 23, 30, 31, 23, 8, 18, 97, 8, 61, 82, + 66, 87, 82, 84, 65, 74, 64, 65, 74, 8, 23, 23, 22, 8, 51, 82, 81, 87, 20, 2, 64, + 65, 80, 8, 23, 30, 28, 27, 8, 24, 18, 8, 83, 75, 74, 8, 46, 79, 65, 69, 80, 132, + 63, 68, 82, 72, 19, 24, 24, 33, 8, 97, 8, 82, 74, 64, 8, 7, 132, 63, 68, 65, 69, + 74, 81, 6, 8, 64, 69, 65, 8, 23, 27, 8, 97, 8, 83, 75, 79, 19, 24, 27, 22, 8, + 7, 8, 73, 65, 68, 79, 2, 132, 63, 68, 72, 69, 65, 102, 72, 69, 63, 68, 8, 64, 69, + 65, 8, 7, 18, 8, 64, 69, 65, 132, 65, 8, 132, 69, 65, 8, 23, 27, 11, 8, 97, 21, + 8, 84, 65, 72, 63, 68, 65, 8, 68, 61, 74, 64, 65, 72, 81, 8, 61, 62, 65, 79, 8, + 25, 25, 8, 97, 8, 23, 30, 28, 27, 8, 29, 8, 37, 65, 3, 2, 82, 74, 64, 8, 61, + 72, 80, 8, 24, 8, 132, 81, 103, 64, 81, 69, 132, 63, 68, 65, 79, 132, 65, 69, 81, 80, + 8, 132, 81, 79, 20, 8, 24, 11, 8, 18, 97, 8, 73, 69, 81, 8, 132, 69, 63, 68, 8, + 65, 69, 74, 65, 8, 23, 30, 27, 27, 8, 27, 21, 8, 48, 69, 72, 72, 69, 75, 74, 65, + 74, 8, 27, 31, 22, 22, 8, 64, 65, 79, 2, 64, 61, 79, 82, 73, 8, 39, 79, 65, 68, + 62, 110, 68, 74, 65, 8, 62, 65, 69, 8, 68, 61, 62, 65, 74, 8, 23, 30, 27, 23, 18, + 8, 7, 8, 40, 69, 74, 87, 69, 67, 8, 61, 82, 63, 68, 8, 82, 74, 80, 8, 30, 30, + 8, 18, 97, 8, 82, 74, 64, 8, 24, 31, 22, 8, 42, 79, 75, 72, 73, 61, 74, 3, 2, + 64, 65, 73, 8, 36, 82, 66, 132, 69, 63, 68, 81, 8, 7, 8, 70, 65, 74, 65, 80, 8, + 47, 61, 67, 65, 79, 78, 72, 61, 81, 87, 8, 24, 24, 33, 8, 97, 8, 64, 65, 73, 8, + 31, 27, 22, 22, 20, 8, 83, 75, 73, 8, 23, 31, 24, 22, 8, 97, 8, 83, 65, 79, 19, + 23, 23, 31, 22, 22, 8, 73, 65, 68, 79, 2, 67, 65, 74, 61, 82, 8, 73, 61, 74, 63, + 68, 73, 61, 72, 8, 97, 96, 22, 8, 84, 69, 79, 8, 61, 82, 80, 8, 31, 20, 8, 7, + 87, 84, 69, 132, 63, 68, 65, 74, 6, 8, 62, 69, 80, 8, 71, 61, 74, 74, 20, 8, 29, + 27, 8, 24, 8, 64, 69, 65, 8, 25, 24, 27, 22, 22, 11, 8, 60, 69, 73, 73, 65, 79, + 2, 22, 22, 22, 8, 64, 65, 73, 8, 97, 8, 56, 65, 79, 62, 69, 74, 64, 82, 74, 67, + 8, 43, 82, 67, 75, 8, 23, 24, 22, 22, 20, 8, 97, 8, 64, 69, 65, 8, 75, 64, 65, + 79, 8, 36, 74, 79, 82, 66, 65, 74, 8, 30, 24, 8, 64, 69, 65, 8, 24, 23, 22, 22, + 22, 8, 83, 69, 65, 72, 72, 65, 69, 63, 68, 81, 2, 64, 65, 79, 74, 8, 132, 75, 84, + 65, 69, 81, 8, 97, 8, 65, 79, 132, 81, 65, 74, 8, 124, 63, 20, 8, 25, 28, 25, 20, + 8, 18, 97, 8, 65, 69, 74, 65, 8, 23, 30, 22, 31, 8, 56, 65, 79, 132, 81, 103, 79, + 71, 82, 74, 67, 8, 23, 30, 31, 31, 8, 24, 18, 8, 23, 24, 20, 8, 31, 8, 29, 8, + 83, 75, 74, 2, 7, 51, 61, 79, 87, 65, 72, 72, 65, 74, 6, 8, 69, 132, 81, 8, 28, + 8, 67, 61, 74, 87, 8, 64, 61, 80, 8, 23, 30, 26, 22, 20, 8, 84, 82, 79, 64, 65, + 8, 37, 86, 71, 8, 82, 74, 64, 8, 28, 29, 8, 27, 28, 8, 42, 79, 82, 74, 64, 8, + 24, 29, 22, 22, 8, 23, 31, 22, 30, 21, 23, 25, 20, 2, 68, 65, 66, 81, 8, 83, 75, + 74, 8, 97, 8, 73, 69, 79, 8, 47, 61, 73, 78, 65, 74, 8, 23, 30, 29, 30, 11, 8, + 97, 8, 22, 22, 22, 8, 41, 65, 79, 74, 65, 79, 8, 23, 29, 26, 8, 25, 22, 8, 27, + 28, 8, 64, 61, 102, 8, 25, 23, 8, 29, 8, 132, 75, 72, 72, 2, 42, 79, 75, 102, 19, + 37, 65, 79, 72, 69, 74, 8, 65, 69, 74, 73, 61, 72, 8, 7, 97, 8, 44, 45, 44, 45, + 8, 82, 74, 64, 8, 23, 22, 20, 8, 97, 8, 46, 109, 74, 69, 67, 19, 44, 45, 44, 45, + 8, 61, 82, 66, 8, 23, 26, 22, 8, 97, 8, 25, 22, 20, 8, 24, 27, 22, 8, 29, 8, + 64, 69, 65, 2, 48, 103, 64, 63, 68, 65, 74, 8, 84, 65, 79, 64, 65, 74, 8, 97, 8, + 64, 69, 65, 8, 43, 61, 82, 80, 84, 61, 79, 81, 80, 8, 23, 24, 20, 8, 27, 21, 8, + 83, 75, 79, 19, 23, 30, 29, 31, 8, 39, 61, 79, 110, 62, 65, 79, 8, 23, 30, 31, 28, + 8, 18, 8, 83, 75, 74, 8, 25, 28, 25, 22, 22, 8, 65, 79, 67, 61, 62, 2, 45, 61, + 66, 66, 104, 8, 31, 24, 8, 11, 8, 24, 21, 8, 61, 82, 63, 68, 8, 61, 82, 66, 8, + 30, 29, 20, 8, 27, 21, 8, 75, 64, 65, 79, 8, 36, 74, 72, 65, 69, 68, 65, 8, 65, + 81, 84, 61, 69, 67, 65, 74, 8, 30, 22, 22, 8, 29, 18, 8, 84, 65, 69, 72, 8, 25, + 26, 30, 25, 30, 22, 21, 8, 64, 65, 79, 2, 53, 65, 69, 81, 8, 61, 74, 64, 65, 79, + 65, 79, 8, 7, 8, 83, 75, 73, 8, 72, 69, 65, 68, 65, 74, 8, 24, 18, 8, 97, 75, + 22, 8, 64, 61, 80, 8, 23, 23, 25, 21, 23, 25, 8, 24, 22, 22, 22, 8, 23, 26, 23, + 8, 97, 8, 64, 69, 65, 8, 23, 24, 22, 22, 8, 64, 65, 79, 70, 65, 74, 69, 67, 65, + 74, 2, 64, 61, 80, 8, 64, 61, 102, 8, 7, 97, 8, 48, 61, 74, 67, 65, 72, 8, 84, + 69, 79, 64, 8, 23, 29, 28, 31, 11, 8, 7, 8, 23, 22, 22, 8, 81, 79, 75, 81, 87, + 8, 64, 65, 74, 8, 28, 23, 8, 97, 8, 83, 65, 79, 62, 69, 74, 64, 65, 74, 8, 23, + 30, 25, 27, 22, 8, 61, 72, 132, 75, 2, 74, 69, 63, 68, 81, 8, 84, 65, 72, 63, 68, + 65, 8, 64, 61, 80, 8, 84, 65, 69, 63, 68, 81, 8, 23, 24, 27, 33, 8, 97, 8, 64, + 65, 79, 8, 23, 30, 31, 25, 8, 132, 63, 68, 79, 69, 66, 81, 65, 74, 8, 23, 23, 8, + 98, 8, 24, 30, 20, 8, 24, 30, 26, 8, 29, 8, 68, 69, 65, 132, 69, 67, 65, 74, 2, + 60, 82, 132, 63, 68, 82, 102, 8, 64, 65, 73, 67, 65, 73, 103, 102, 8, 24, 21, 8, 64, + 65, 80, 8, 65, 69, 74, 65, 79, 8, 31, 20, 8, 27, 18, 8, 83, 75, 79, 68, 61, 74, + 64, 65, 74, 8, 67, 65, 72, 65, 67, 65, 74, 8, 83, 75, 72, 72, 65, 74, 8, 27, 8, + 29, 7, 8, 39, 61, 80, 8, 27, 28, 22, 21, 8, 65, 79, 68, 61, 72, 81, 65, 74, 2, + 23, 28, 8, 29, 8, 55, 68, 79, 8, 29, 8, 28, 28, 8, 29, 8, 82, 74, 64, 8, 27, + 18, 8, 56, 8, 84, 69, 79, 8, 65, 69, 74, 8, 68, 69, 74, 87, 82, 87, 82, 66, 110, + 67, 65, 74, 8, 23, 25, 30, 23, 8, 29, 18, 28, 8, 82, 74, 64, 8, 24, 22, 22, 8, + 64, 61, 80, 2, 47, 61, 67, 65, 79, 78, 72, 61, 81, 87, 19, 56, 75, 79, 132, 81, 65, + 68, 65, 79, 32, 8, 75, 64, 65, 79, 8, 29, 8, 132, 69, 74, 64, 8, 64, 65, 79, 8, + 28, 31, 20, 8, 18, 8, 110, 62, 65, 79, 8, 64, 69, 65, 8, 84, 103, 79, 65, 74, 8, + 26, 22, 8, 22, 22, 22, 8, 23, 22, 22, 8, 132, 69, 63, 68, 2, 132, 69, 74, 64, 8, + 40, 79, 72, 61, 102, 8, 97, 8, 61, 82, 66, 8, 82, 74, 64, 8, 23, 30, 29, 22, 20, + 8, 7, 8, 64, 65, 79, 8, 82, 74, 64, 8, 109, 81, 69, 67, 65, 8, 23, 30, 28, 28, + 8, 64, 69, 65, 8, 25, 22, 22, 22, 22, 11, 8, 87, 75, 67, 65, 74, 2, 65, 79, 109, + 66, 66, 74, 65, 81, 20, 8, 53, 81, 61, 74, 64, 78, 82, 74, 71, 81, 8, 18, 8, 22, + 22, 22, 8, 48, 61, 69, 8, 23, 30, 30, 30, 20, 8, 97, 8, 75, 68, 74, 65, 8, 132, + 69, 63, 68, 8, 64, 61, 102, 8, 25, 27, 8, 27, 28, 8, 84, 65, 79, 64, 65, 74, 20, + 8, 23, 30, 31, 30, 22, 11, 8, 36, 62, 62, 61, 82, 2, 132, 75, 72, 63, 68, 65, 74, + 8, 53, 63, 68, 84, 69, 65, 79, 69, 67, 71, 65, 69, 81, 65, 74, 8, 27, 28, 8, 64, + 69, 65, 132, 65, 74, 8, 57, 75, 68, 74, 82, 74, 67, 65, 74, 8, 31, 20, 8, 27, 8, + 23, 26, 22, 8, 22, 22, 22, 8, 44, 63, 68, 8, 28, 8, 21, 8, 23, 22, 20, 8, 31, + 22, 11, 8, 24, 22, 22, 2, 84, 65, 79, 64, 65, 74, 8, 74, 69, 63, 68, 81, 8, 7, + 8, 54, 65, 69, 72, 62, 65, 81, 79, 61, 67, 8, 42, 65, 64, 61, 74, 71, 65, 74, 8, + 25, 22, 22, 22, 11, 8, 27, 8, 64, 69, 65, 8, 70, 82, 74, 67, 65, 8, 61, 82, 63, + 68, 8, 23, 8, 97, 8, 61, 82, 66, 8, 23, 30, 24, 28, 8, 23, 23, 26, 20, 2, 132, + 65, 69, 74, 65, 8, 22, 30, 20, 8, 97, 8, 64, 69, 65, 8, 82, 74, 64, 8, 28, 27, + 28, 18, 8, 97, 8, 82, 74, 64, 8, 54, 61, 67, 65, 80, 75, 79, 64, 74, 82, 74, 67, + 32, 8, 64, 65, 79, 8, 23, 23, 8, 97, 8, 64, 65, 79, 8, 23, 24, 27, 22, 8, 61, + 72, 132, 75, 2, 64, 65, 80, 68, 61, 72, 62, 8, 67, 61, 74, 87, 8, 7, 8, 84, 69, + 79, 8, 74, 82, 79, 8, 24, 22, 22, 22, 18, 8, 24, 18, 8, 87, 82, 73, 8, 23, 30, + 30, 25, 8, 65, 69, 74, 8, 24, 22, 8, 97, 18, 8, 84, 65, 79, 64, 65, 74, 20, 6, + 8, 23, 22, 22, 8, 37, 61, 74, 67, 72, 61, 64, 65, 132, 63, 68, 2, 53, 69, 65, 8, + 69, 63, 68, 8, 7, 8, 83, 75, 73, 8, 64, 65, 79, 8, 26, 31, 31, 33, 8, 29, 18, + 8, 53, 69, 65, 8, 79, 82, 74, 64, 8, 23, 25, 26, 24, 8, 31, 22, 8, 37, 61, 72, + 71, 65, 8, 27, 22, 22, 22, 22, 8, 82, 74, 81, 65, 79, 79, 69, 63, 68, 81, 65, 81, + 20, 2, 84, 65, 80, 68, 61, 72, 62, 8, 64, 65, 80, 8, 62, 65, 132, 63, 68, 72, 69, + 65, 102, 81, 8, 37, 65, 61, 73, 81, 65, 74, 8, 23, 24, 22, 22, 20, 8, 53, 82, 73, + 73, 65, 20, 8, 23, 30, 24, 24, 8, 84, 65, 132, 65, 74, 81, 19, 23, 24, 22, 22, 8, + 7, 8, 64, 65, 79, 8, 23, 24, 22, 22, 8, 64, 65, 79, 2, 42, 79, 65, 64, 86, 8, + 66, 110, 79, 8, 18, 8, 23, 30, 27, 27, 8, 84, 103, 63, 68, 132, 81, 8, 23, 30, 27, + 30, 20, 8, 18, 97, 8, 61, 82, 66, 8, 36, 82, 63, 68, 8, 37, 65, 102, 65, 79, 8, + 23, 31, 24, 22, 8, 46, 61, 73, 62, 61, 63, 68, 8, 29, 22, 22, 8, 39, 69, 65, 2, + 36, 74, 84, 65, 132, 65, 74, 68, 65, 69, 81, 8, 64, 61, 87, 82, 8, 7, 8, 82, 74, + 64, 8, 23, 30, 29, 31, 8, 23, 31, 23, 27, 20, 11, 8, 59, 61, 74, 67, 8, 41, 75, + 74, 64, 80, 8, 82, 74, 64, 8, 25, 22, 22, 22, 8, 24, 11, 8, 49, 61, 73, 65, 74, + 6, 8, 23, 24, 22, 22, 22, 22, 8, 23, 31, 23, 28, 2, 67, 61, 74, 87, 8, 84, 65, + 79, 64, 65, 74, 8, 64, 61, 102, 8, 66, 82, 79, 63, 68, 81, 62, 61, 79, 8, 23, 24, + 22, 22, 20, 8, 29, 18, 8, 82, 74, 64, 8, 24, 24, 20, 8, 64, 65, 73, 8, 23, 25, + 8, 97, 8, 87, 82, 79, 8, 29, 8, 29, 8, 65, 69, 74, 65, 80, 2, 64, 65, 73, 8, + 64, 65, 79, 8, 25, 28, 25, 8, 64, 65, 73, 8, 23, 22, 22, 18, 8, 97, 8, 23, 30, + 20, 8, 30, 22, 22, 22, 8, 52, 61, 81, 68, 68, 61, 82, 132, 65, 80, 8, 23, 22, 29, + 28, 8, 97, 8, 64, 65, 74, 8, 31, 31, 31, 22, 21, 22, 8, 82, 74, 64, 2, 69, 79, + 67, 65, 74, 64, 84, 65, 72, 63, 68, 65, 74, 8, 87, 82, 79, 8, 7, 8, 70, 65, 74, + 69, 67, 65, 74, 8, 53, 81, 79, 20, 8, 24, 22, 20, 8, 97, 8, 62, 65, 81, 79, 103, + 67, 81, 8, 23, 30, 31, 22, 8, 65, 69, 74, 65, 74, 8, 30, 8, 97, 8, 23, 26, 25, + 23, 8, 30, 23, 8, 23, 30, 29, 29, 2, 55, 74, 81, 65, 79, 74, 65, 68, 73, 82, 74, + 67, 65, 74, 8, 42, 79, 110, 74, 132, 81, 79, 20, 8, 97, 8, 23, 27, 20, 8, 64, 65, + 79, 8, 29, 11, 8, 27, 21, 8, 64, 69, 65, 8, 82, 74, 64, 8, 62, 69, 80, 8, 28, + 8, 7, 8, 65, 69, 74, 65, 73, 8, 23, 31, 24, 22, 22, 8, 132, 69, 81, 87, 82, 74, + 67, 65, 74, 2, 82, 74, 64, 8, 67, 65, 84, 61, 72, 81, 69, 67, 8, 65, 69, 74, 87, + 65, 72, 74, 65, 8, 132, 69, 74, 64, 20, 8, 23, 23, 20, 8, 29, 8, 132, 69, 74, 64, + 8, 61, 62, 65, 79, 8, 23, 30, 26, 30, 8, 23, 24, 8, 11, 8, 36, 82, 66, 66, 61, + 132, 132, 82, 74, 67, 8, 30, 22, 8, 67, 61, 74, 87, 2, 65, 79, 84, 75, 79, 62, 65, + 74, 8, 67, 72, 61, 82, 62, 65, 8, 97, 8, 84, 69, 79, 64, 8, 40, 69, 74, 65, 8, + 23, 30, 24, 28, 33, 8, 27, 28, 8, 23, 26, 20, 8, 61, 82, 66, 8, 64, 65, 74, 65, + 74, 8, 23, 24, 8, 97, 8, 53, 81, 65, 72, 72, 65, 74, 8, 27, 30, 31, 22, 18, 8, + 37, 65, 132, 63, 68, 84, 65, 79, 64, 65, 74, 2, 53, 63, 68, 82, 72, 64, 69, 67, 71, + 65, 69, 81, 8, 56, 65, 79, 65, 69, 74, 8, 97, 8, 69, 132, 81, 8, 23, 30, 29, 30, + 8, 30, 25, 8, 97, 8, 64, 65, 74, 8, 64, 69, 65, 132, 65, 73, 8, 61, 82, 63, 68, + 8, 25, 27, 8, 18, 98, 8, 25, 25, 23, 21, 23, 25, 8, 30, 23, 29, 6, 8, 53, 81, + 82, 73, 78, 66, 2, 23, 22, 22, 8, 64, 65, 80, 68, 61, 72, 62, 8, 18, 8, 68, 65, + 79, 87, 82, 19, 39, 82, 79, 63, 68, 8, 27, 22, 20, 8, 27, 18, 8, 83, 65, 79, 82, + 79, 132, 61, 63, 68, 81, 8, 43, 65, 79, 79, 65, 74, 8, 67, 65, 71, 110, 74, 64, 69, + 67, 81, 8, 27, 27, 8, 97, 8, 36, 79, 81, 8, 31, 27, 22, 8, 11, 8, 64, 65, 74, + 2, 36, 82, 66, 132, 63, 68, 72, 61, 67, 8, 124, 63, 20, 8, 15, 8, 87, 82, 79, 8, + 68, 61, 81, 8, 23, 31, 23, 29, 20, 8, 18, 8, 64, 65, 80, 8, 64, 65, 79, 8, 61, + 62, 84, 65, 69, 63, 68, 65, 74, 64, 65, 79, 8, 30, 28, 8, 11, 97, 8, 37, 65, 69, + 8, 31, 31, 22, 11, 8, 7, 39, 61, 80, 2, 61, 72, 132, 75, 8, 73, 69, 81, 8, 97, + 75, 8, 64, 65, 74, 8, 31, 30, 27, 8, 27, 26, 31, 22, 21, 11, 8, 45, 61, 68, 79, + 65, 80, 62, 65, 83, 109, 72, 71, 65, 79, 82, 74, 67, 8, 74, 61, 63, 68, 8, 22, 22, + 22, 8, 23, 31, 24, 22, 8, 24, 18, 8, 69, 132, 81, 8, 30, 30, 8, 7, 11, 29, 8, + 60, 61, 68, 72, 2, 62, 65, 69, 8, 23, 31, 24, 25, 25, 8, 7, 8, 61, 82, 80, 8, + 64, 65, 80, 8, 29, 25, 28, 31, 18, 8, 7, 8, 64, 65, 79, 8, 83, 75, 74, 8, 27, + 30, 28, 24, 8, 25, 26, 8, 21, 8, 23, 27, 24, 25, 8, 23, 29, 22, 11, 8, 36, 74, + 132, 81, 69, 65, 67, 65, 2, 64, 69, 65, 8, 132, 75, 72, 72, 65, 74, 8, 29, 8, 7, + 68, 65, 66, 81, 69, 67, 65, 79, 8, 71, 72, 65, 69, 74, 65, 74, 8, 24, 20, 8, 23, + 23, 26, 20, 8, 64, 65, 74, 8, 44, 45, 45, 8, 24, 22, 22, 8, 53, 69, 65, 8, 28, + 31, 22, 8, 53, 75, 74, 74, 81, 61, 67, 65, 74, 32, 2, 132, 69, 63, 68, 8, 65, 69, + 74, 8, 7, 8, 64, 65, 79, 8, 36, 74, 79, 82, 66, 65, 74, 8, 23, 31, 18, 8, 18, + 8, 84, 69, 79, 8, 61, 72, 132, 75, 8, 87, 82, 79, 8, 29, 28, 8, 79, 65, 63, 68, + 81, 8, 23, 30, 25, 29, 22, 22, 8, 64, 61, 68, 65, 79, 2, 45, 82, 74, 67, 65, 74, + 8, 40, 81, 61, 81, 8, 27, 28, 8, 53, 81, 61, 64, 81, 8, 48, 75, 74, 61, 81, 65, + 74, 8, 25, 24, 22, 22, 18, 8, 7, 97, 8, 64, 61, 102, 8, 64, 65, 73, 8, 132, 63, + 68, 84, 61, 74, 71, 65, 74, 8, 23, 24, 8, 97, 8, 64, 65, 79, 8, 26, 22, 22, 8, + 37, 65, 64, 110, 79, 66, 74, 69, 80, 2, 25, 24, 27, 22, 8, 64, 69, 65, 8, 97, 22, + 8, 60, 82, 79, 8, 64, 69, 65, 132, 65, 79, 8, 31, 30, 27, 11, 8, 97, 8, 45, 61, + 68, 79, 65, 8, 64, 69, 65, 8, 61, 62, 87, 69, 65, 72, 65, 74, 8, 25, 27, 8, 7, + 97, 8, 132, 69, 63, 68, 8, 23, 24, 27, 22, 8, 29, 8, 83, 75, 74, 2, 67, 65, 73, + 103, 102, 8, 132, 75, 72, 63, 68, 65, 80, 8, 28, 8, 62, 69, 80, 8, 57, 69, 79, 8, + 29, 30, 11, 8, 64, 69, 65, 8, 42, 65, 73, 65, 69, 74, 64, 65, 62, 65, 68, 109, 79, + 64, 65, 8, 74, 65, 82, 65, 74, 8, 23, 27, 22, 29, 8, 29, 21, 8, 64, 65, 79, 8, + 23, 24, 22, 11, 8, 64, 61, 102, 2, 57, 65, 74, 69, 67, 8, 71, 61, 74, 74, 8, 7, + 8, 48, 69, 65, 81, 83, 65, 79, 81, 79, 61, 67, 8, 70, 82, 74, 67, 65, 8, 23, 20, + 8, 24, 8, 68, 61, 81, 8, 68, 61, 62, 65, 74, 8, 61, 72, 80, 8, 24, 22, 8, 97, + 8, 24, 25, 27, 21, 24, 24, 8, 29, 30, 8, 7, 8, 64, 65, 79, 2, 36, 74, 64, 65, + 79, 82, 74, 67, 8, 23, 31, 23, 24, 21, 23, 29, 20, 8, 97, 8, 70, 65, 73, 61, 74, + 64, 8, 25, 23, 25, 8, 25, 22, 22, 22, 8, 7, 8, 64, 61, 64, 82, 79, 63, 68, 8, + 64, 61, 102, 8, 82, 74, 64, 8, 23, 30, 29, 22, 8, 18, 8, 65, 69, 74, 65, 8, 23, + 30, 27, 29, 22, 8, 132, 78, 79, 65, 63, 68, 65, 2, 64, 65, 79, 8, 51, 79, 75, 81, + 75, 71, 75, 72, 72, 8, 98, 8, 23, 30, 31, 23, 8, 64, 65, 80, 8, 23, 24, 11, 8, + 24, 15, 8, 132, 75, 84, 69, 65, 8, 64, 61, 102, 8, 61, 72, 80, 8, 31, 8, 18, 97, + 8, 83, 75, 73, 8, 23, 26, 31, 22, 22, 22, 8, 67, 65, 73, 69, 65, 81, 65, 81, 65, + 74, 2, 31, 31, 8, 7, 8, 64, 65, 79, 8, 24, 8, 73, 61, 102, 67, 65, 62, 65, 74, + 64, 20, 8, 23, 30, 23, 23, 8, 29, 28, 33, 8, 64, 69, 65, 8, 23, 24, 20, 8, 23, + 31, 23, 22, 21, 23, 27, 20, 8, 23, 31, 22, 28, 8, 39, 61, 80, 8, 23, 30, 30, 24, + 22, 22, 8, 36, 74, 132, 78, 79, 110, 63, 68, 65, 74, 2, 65, 79, 68, 61, 72, 81, 65, + 74, 20, 8, 132, 69, 81, 87, 65, 74, 64, 65, 8, 15, 8, 71, 72, 61, 79, 8, 68, 61, + 81, 81, 65, 74, 8, 27, 31, 11, 8, 53, 81, 69, 73, 73, 65, 74, 8, 87, 82, 73, 8, + 62, 69, 80, 8, 30, 25, 8, 97, 8, 64, 65, 79, 8, 23, 30, 22, 11, 8, 39, 79, 20, + 2, 55, 74, 81, 65, 79, 79, 69, 63, 68, 81, 8, 64, 65, 79, 8, 97, 8, 47, 65, 62, + 65, 74, 80, 71, 82, 74, 64, 65, 8, 64, 65, 73, 8, 23, 30, 31, 27, 20, 8, 27, 18, + 8, 61, 82, 63, 68, 8, 87, 61, 68, 72, 65, 74, 8, 30, 22, 22, 8, 25, 8, 28, 8, + 83, 75, 74, 8, 23, 31, 22, 22, 8, 83, 75, 74, 2, 67, 65, 132, 81, 65, 69, 67, 65, + 79, 81, 65, 74, 8, 23, 27, 23, 24, 8, 40, 69, 74, 19, 57, 65, 69, 132, 65, 8, 27, + 24, 24, 22, 8, 97, 8, 87, 82, 20, 8, 30, 22, 22, 8, 132, 81, 69, 65, 67, 8, 28, + 27, 8, 97, 8, 132, 63, 68, 82, 72, 65, 8, 24, 25, 22, 11, 8, 42, 79, 82, 78, 78, + 65, 74, 2, 87, 65, 69, 67, 65, 74, 8, 62, 65, 81, 79, 61, 67, 65, 74, 8, 62, 69, + 80, 8, 23, 30, 24, 31, 8, 23, 30, 30, 22, 11, 8, 21, 8, 36, 82, 80, 132, 81, 61, + 81, 81, 82, 74, 67, 8, 71, 65, 69, 74, 65, 74, 8, 124, 63, 20, 8, 23, 27, 23, 29, + 8, 97, 8, 64, 61, 102, 8, 23, 23, 8, 29, 8, 42, 65, 62, 65, 79, 81, 2, 23, 26, + 23, 8, 36, 82, 63, 68, 8, 97, 8, 45, 80, 20, 8, 64, 69, 65, 132, 65, 79, 8, 24, + 29, 11, 8, 97, 8, 64, 65, 80, 8, 36, 82, 80, 67, 72, 65, 69, 63, 68, 8, 83, 75, + 74, 8, 23, 31, 23, 27, 8, 7, 98, 8, 23, 31, 23, 31, 8, 24, 8, 29, 8, 65, 69, + 74, 132, 65, 69, 81, 69, 4, 2, 75, 64, 65, 79, 8, 44, 63, 68, 8, 24, 18, 8, 73, + 69, 81, 8, 43, 65, 79, 71, 109, 73, 73, 72, 69, 63, 68, 8, 23, 27, 20, 8, 97, 8, + 83, 75, 74, 8, 40, 81, 61, 81, 8, 64, 61, 74, 74, 8, 23, 31, 22, 27, 8, 36, 62, + 19, 25, 27, 22, 22, 21, 8, 41, 65, 68, 72, 75, 84, 13, 132, 63, 68, 65, 74, 2, 47, + 109, 68, 74, 65, 8, 64, 61, 102, 8, 24, 8, 45, 82, 72, 69, 8, 47, 65, 69, 81, 65, + 79, 32, 8, 26, 27, 20, 8, 97, 8, 7, 48, 65, 69, 74, 65, 8, 64, 65, 80, 8, 67, + 65, 79, 69, 63, 68, 81, 65, 81, 65, 74, 8, 23, 23, 8, 97, 97, 8, 71, 109, 74, 74, + 65, 74, 8, 23, 31, 31, 21, 22, 8, 23, 27, 22, 2, 62, 69, 80, 8, 65, 69, 74, 8, + 48, 61, 69, 8, 72, 65, 69, 63, 68, 81, 8, 23, 30, 31, 22, 20, 8, 27, 18, 8, 67, + 65, 81, 79, 69, 65, 62, 65, 74, 35, 8, 57, 69, 72, 68, 65, 72, 73, 8, 68, 61, 62, + 65, 74, 8, 25, 22, 22, 8, 29, 18, 8, 61, 72, 80, 8, 24, 23, 31, 22, 8, 83, 75, + 79, 2, 69, 68, 79, 65, 79, 8, 31, 27, 31, 22, 22, 20, 8, 74, 69, 63, 68, 81, 8, + 36, 74, 132, 81, 61, 72, 81, 8, 23, 28, 26, 27, 20, 8, 24, 27, 18, 8, 61, 82, 63, + 68, 8, 84, 69, 79, 64, 8, 51, 75, 132, 69, 81, 69, 75, 74, 8, 27, 8, 11, 97, 8, + 23, 24, 8, 11, 8, 23, 30, 22, 30, 11, 8, 64, 65, 79, 2, 41, 79, 61, 67, 65, 8, + 65, 69, 74, 65, 8, 7, 8, 68, 61, 81, 81, 65, 20, 8, 27, 24, 20, 8, 25, 22, 20, + 8, 18, 97, 18, 8, 124, 63, 20, 8, 57, 75, 68, 74, 82, 74, 67, 65, 74, 8, 84, 75, + 79, 64, 65, 74, 20, 8, 23, 31, 24, 22, 8, 25, 8, 83, 75, 73, 8, 23, 22, 22, 8, + 62, 65, 69, 64, 65, 74, 2, 84, 65, 69, 102, 8, 43, 61, 82, 80, 68, 61, 72, 81, 80, + 19, 97, 8, 84, 82, 79, 64, 65, 8, 60, 82, 67, 65, 87, 75, 67, 65, 74, 65, 74, 8, + 23, 24, 20, 8, 6, 8, 66, 110, 79, 8, 66, 110, 79, 8, 44, 45, 45, 8, 23, 26, 8, + 97, 8, 22, 22, 22, 8, 23, 31, 23, 30, 29, 8, 61, 62, 132, 75, 72, 82, 81, 65, 2, + 84, 69, 79, 64, 8, 61, 82, 66, 79, 65, 63, 68, 81, 8, 7, 8, 84, 69, 79, 8, 132, + 81, 69, 65, 67, 8, 23, 31, 23, 28, 33, 8, 97, 8, 64, 65, 74, 8, 60, 84, 65, 63, + 71, 65, 8, 68, 61, 62, 65, 8, 23, 8, 18, 97, 8, 42, 65, 62, 103, 82, 64, 65, 8, + 27, 22, 22, 8, 74, 61, 63, 68, 2, 23, 23, 20, 8, 132, 69, 65, 8, 97, 18, 8, 64, + 69, 65, 8, 53, 81, 61, 64, 81, 8, 25, 30, 20, 8, 27, 28, 8, 53, 63, 68, 82, 72, + 64, 65, 78, 82, 81, 61, 81, 69, 75, 74, 8, 64, 65, 74, 8, 66, 110, 79, 8, 28, 31, + 8, 43, 65, 79, 79, 8, 23, 23, 22, 22, 8, 64, 65, 80, 68, 61, 72, 62, 2, 64, 65, + 79, 8, 36, 78, 79, 69, 72, 8, 21, 8, 45, 82, 74, 69, 8, 24, 18, 8, 36, 62, 81, + 65, 69, 72, 82, 74, 67, 8, 62, 65, 69, 64, 65, 74, 8, 27, 24, 20, 8, 97, 75, 8, + 65, 69, 74, 65, 73, 8, 61, 82, 63, 68, 8, 64, 69, 65, 132, 65, 79, 8, 25, 27, 22, + 8, 24, 97, 8, 124, 63, 20, 8, 26, 29, 8, 7, 8, 84, 69, 65, 64, 65, 79, 2, 41, + 65, 79, 74, 79, 82, 66, 32, 8, 39, 61, 80, 8, 97, 8, 23, 31, 23, 29, 8, 48, 61, + 67, 69, 132, 81, 79, 61, 81, 8, 27, 29, 11, 8, 24, 18, 8, 39, 69, 65, 8, 64, 65, + 80, 8, 36, 62, 81, 75, 73, 73, 65, 74, 8, 30, 26, 8, 97, 8, 82, 132, 84, 20, 8, + 29, 26, 8, 11, 8, 67, 65, 73, 103, 102, 2, 74, 75, 63, 68, 8, 79, 82, 74, 64, 8, + 97, 8, 82, 74, 64, 8, 83, 65, 79, 132, 63, 68, 69, 65, 64, 65, 74, 65, 79, 8, 23, + 11, 8, 62, 65, 79, 8, 23, 22, 20, 8, 64, 61, 102, 8, 29, 27, 22, 8, 28, 8, 51, + 66, 72, 69, 63, 68, 81, 8, 23, 22, 22, 8, 62, 65, 87, 75, 67, 65, 74, 2, 64, 69, + 65, 8, 49, 65, 82, 66, 65, 79, 81, 8, 97, 8, 29, 22, 22, 22, 8, 45, 124, 20, 8, + 23, 22, 30, 26, 26, 33, 8, 7, 8, 23, 23, 22, 8, 132, 63, 68, 72, 61, 67, 65, 74, + 8, 71, 72, 65, 69, 74, 65, 74, 8, 24, 29, 8, 97, 8, 43, 65, 65, 79, 65, 80, 64, + 69, 65, 74, 132, 81, 65, 8, 25, 24, 11, 8, 64, 65, 79, 2, 65, 79, 68, 65, 62, 72, + 69, 63, 68, 8, 73, 65, 69, 74, 65, 74, 8, 83, 75, 74, 8, 39, 69, 65, 8, 26, 31, + 20, 8, 23, 30, 26, 28, 8, 82, 74, 64, 8, 37, 79, 75, 64, 65, 8, 28, 27, 24, 8, + 97, 8, 23, 31, 23, 29, 8, 31, 30, 8, 29, 8, 69, 132, 81, 2, 73, 109, 63, 68, 81, + 65, 8, 23, 24, 27, 22, 8, 97, 8, 84, 65, 72, 63, 68, 65, 74, 8, 64, 69, 65, 8, + 24, 22, 22, 18, 8, 97, 8, 64, 61, 80, 8, 56, 78, 80, 69, 72, 75, 74, 8, 73, 109, + 63, 68, 81, 65, 8, 30, 8, 29, 18, 8, 82, 74, 64, 8, 28, 30, 31, 21, 8, 87, 69, + 65, 72, 65, 74, 64, 65, 74, 2, 61, 62, 65, 79, 8, 64, 75, 63, 68, 8, 68, 69, 74, + 65, 69, 74, 67, 65, 81, 61, 74, 8, 43, 75, 66, 65, 8, 24, 22, 22, 22, 33, 8, 97, + 8, 7, 37, 65, 79, 61, 81, 82, 74, 67, 6, 8, 84, 65, 79, 64, 65, 74, 8, 124, 63, + 20, 8, 28, 27, 28, 8, 97, 8, 73, 61, 74, 8, 23, 29, 22, 11, 8, 65, 81, 84, 61, + 80, 2, 52, 75, 132, 63, 68, 65, 79, 132, 81, 79, 61, 102, 65, 8, 69, 132, 81, 8, 29, + 97, 8, 84, 65, 69, 81, 65, 79, 65, 80, 8, 64, 65, 79, 8, 30, 27, 22, 22, 11, 8, + 11, 8, 83, 75, 74, 8, 51, 65, 74, 87, 69, 67, 8, 64, 65, 79, 8, 23, 30, 31, 22, + 8, 11, 8, 40, 69, 74, 65, 8, 24, 29, 22, 8, 72, 65, 81, 87, 81, 65, 74, 2, 64, + 65, 80, 8, 132, 81, 79, 61, 102, 65, 8, 45, 9, 20, 8, 23, 22, 24, 32, 8, 24, 31, + 18, 8, 97, 8, 68, 61, 62, 65, 8, 83, 65, 79, 84, 61, 72, 81, 65, 81, 20, 8, 51, + 79, 110, 66, 82, 74, 67, 8, 23, 28, 25, 30, 8, 97, 8, 23, 22, 26, 8, 27, 30, 28, + 24, 8, 7, 11, 8, 68, 65, 82, 81, 65, 2, 61, 62, 65, 79, 8, 74, 82, 79, 8, 97, + 8, 49, 79, 20, 8, 73, 65, 81, 65, 79, 8, 25, 23, 18, 8, 7, 8, 83, 75, 74, 8, + 132, 69, 63, 68, 8, 61, 62, 65, 74, 64, 80, 8, 31, 8, 18, 98, 8, 69, 63, 68, 8, + 23, 24, 8, 6, 8, 53, 81, 65, 73, 78, 65, 72, 2, 68, 109, 68, 65, 79, 65, 8, 66, + 75, 72, 67, 65, 74, 64, 65, 32, 8, 7, 8, 64, 65, 79, 8, 82, 74, 64, 8, 23, 31, + 22, 23, 20, 8, 24, 18, 8, 65, 69, 74, 65, 8, 45, 61, 68, 79, 8, 61, 72, 72, 65, + 79, 8, 23, 23, 8, 27, 8, 84, 69, 79, 64, 8, 23, 22, 28, 24, 8, 29, 8, 64, 65, + 80, 2, 53, 81, 79, 20, 8, 132, 69, 63, 68, 8, 60, 65, 69, 63, 68, 74, 65, 79, 32, + 8, 62, 65, 69, 8, 29, 11, 8, 7, 8, 62, 65, 81, 79, 61, 63, 68, 81, 65, 74, 20, + 6, 8, 64, 69, 65, 8, 50, 22, 30, 20, 8, 30, 30, 8, 56, 61, 63, 68, 81, 8, 23, + 27, 8, 29, 8, 64, 69, 65, 2, 42, 65, 84, 69, 102, 8, 37, 65, 69, 8, 18, 8, 23, + 31, 23, 22, 21, 23, 27, 20, 8, 26, 24, 25, 8, 23, 22, 22, 18, 8, 97, 22, 8, 64, + 61, 102, 8, 132, 81, 79, 20, 8, 64, 65, 79, 8, 23, 22, 29, 29, 8, 24, 18, 8, 66, + 110, 79, 8, 30, 31, 31, 8, 7, 8, 61, 72, 72, 67, 65, 73, 65, 69, 74, 65, 2, 65, + 79, 84, 75, 79, 62, 65, 74, 20, 8, 57, 61, 74, 64, 65, 79, 82, 74, 67, 8, 7, 97, + 8, 49, 65, 82, 62, 61, 82, 8, 23, 30, 31, 31, 8, 23, 31, 22, 24, 20, 8, 97, 8, + 61, 74, 67, 65, 73, 65, 132, 132, 65, 74, 8, 82, 74, 64, 8, 82, 74, 64, 8, 26, 31, + 8, 18, 96, 98, 8, 83, 75, 74, 8, 25, 23, 22, 22, 8, 84, 69, 79, 64, 20, 2, 87, + 84, 65, 69, 8, 64, 69, 65, 8, 97, 8, 65, 69, 74, 65, 73, 8, 64, 65, 79, 8, 23, + 30, 29, 25, 11, 8, 97, 8, 23, 30, 26, 8, 42, 65, 62, 69, 65, 81, 65, 74, 8, 7, + 64, 61, 79, 110, 62, 65, 79, 6, 8, 27, 30, 8, 97, 8, 61, 82, 80, 8, 24, 22, 8, + 66, 82, 79, 63, 68, 81, 62, 61, 79, 2, 23, 30, 31, 31, 8, 64, 61, 79, 110, 62, 65, + 79, 8, 36, 82, 80, 67, 61, 62, 65, 74, 8, 49, 65, 82, 62, 61, 82, 8, 28, 27, 22, + 22, 8, 29, 21, 8, 39, 69, 74, 67, 65, 8, 36, 74, 79, 82, 66, 65, 74, 8, 75, 64, + 65, 79, 8, 23, 23, 8, 97, 8, 23, 30, 29, 29, 8, 24, 22, 22, 22, 8, 11, 8, 46, + 109, 74, 69, 67, 69, 74, 2, 64, 61, 67, 65, 67, 65, 74, 8, 132, 75, 74, 64, 65, 79, + 74, 8, 97, 8, 64, 69, 65, 8, 70, 65, 64, 75, 63, 68, 8, 23, 30, 27, 27, 20, 8, + 24, 18, 8, 56, 65, 79, 67, 72, 65, 69, 63, 68, 8, 53, 69, 81, 87, 82, 74, 67, 8, + 64, 65, 79, 8, 28, 31, 8, 97, 8, 132, 81, 69, 65, 67, 8, 23, 31, 22, 27, 22, 11, + 8, 64, 61, 80, 2, 132, 78, 79, 65, 63, 68, 65, 8, 69, 63, 68, 8, 18, 8, 25, 30, + 26, 8, 27, 30, 28, 24, 8, 25, 27, 8, 7, 29, 8, 24, 8, 29, 25, 28, 8, 75, 64, + 65, 79, 8, 53, 61, 73, 73, 65, 72, 68, 65, 69, 87, 82, 74, 67, 8, 24, 23, 8, 27, + 8, 83, 65, 79, 62, 69, 74, 64, 65, 74, 8, 23, 30, 31, 25, 22, 8, 74, 75, 63, 68, + 2, 22, 30, 20, 8, 62, 65, 84, 69, 72, 72, 69, 67, 81, 8, 11, 8, 23, 28, 25, 31, + 8, 65, 69, 74, 81, 79, 65, 81, 65, 74, 8, 23, 22, 27, 26, 22, 11, 8, 97, 8, 68, + 61, 62, 65, 74, 8, 64, 65, 79, 8, 27, 27, 20, 8, 23, 28, 26, 27, 8, 18, 8, 7, + 39, 65, 79, 8, 25, 22, 22, 21, 8, 36, 74, 132, 78, 79, 110, 63, 68, 65, 74, 2, 56, + 65, 79, 19, 4, 42, 79, 82, 74, 64, 61, 82, 66, 8, 7, 8, 67, 65, 68, 65, 74, 64, + 8, 64, 65, 73, 8, 23, 24, 11, 8, 57, 75, 68, 74, 82, 74, 67, 65, 74, 8, 25, 25, + 26, 8, 67, 65, 78, 79, 110, 66, 81, 65, 74, 8, 23, 31, 23, 27, 8, 97, 8, 23, 31, + 24, 22, 8, 23, 31, 23, 28, 8, 23, 27, 22, 2, 53, 69, 65, 8, 62, 65, 19, 97, 8, + 36, 74, 67, 65, 72, 65, 67, 65, 74, 68, 65, 69, 81, 65, 74, 8, 42, 79, 75, 72, 73, + 61, 74, 19, 23, 31, 24, 22, 20, 8, 97, 8, 124, 63, 20, 8, 23, 22, 22, 8, 64, 65, + 79, 8, 28, 29, 8, 97, 8, 66, 110, 79, 8, 23, 25, 22, 24, 8, 29, 8, 64, 69, 65, + 2, 65, 69, 74, 87, 65, 72, 74, 65, 74, 8, 65, 69, 74, 132, 65, 69, 81, 69, 19, 8, + 83, 65, 79, 81, 79, 61, 67, 8, 64, 65, 79, 8, 30, 26, 20, 8, 31, 8, 67, 82, 81, + 65, 79, 8, 73, 69, 81, 8, 69, 132, 81, 8, 23, 30, 24, 28, 8, 97, 8, 23, 30, 26, + 25, 8, 23, 26, 8, 29, 8, 65, 79, 67, 65, 62, 65, 74, 20, 2, 70, 65, 64, 75, 63, + 68, 8, 87, 82, 79, 8, 7, 97, 8, 51, 79, 110, 66, 82, 74, 67, 8, 46, 79, 69, 65, + 67, 65, 8, 23, 24, 24, 26, 20, 8, 97, 8, 84, 69, 65, 8, 83, 75, 73, 8, 64, 65, + 79, 8, 23, 23, 8, 24, 15, 8, 67, 65, 81, 79, 75, 66, 66, 65, 74, 65, 8, 31, 22, + 8, 84, 65, 69, 72, 2, 64, 61, 102, 8, 36, 73, 75, 79, 81, 69, 132, 61, 81, 69, 75, + 74, 8, 97, 8, 48, 61, 102, 65, 8, 64, 65, 79, 8, 23, 29, 22, 33, 8, 21, 8, 23, + 26, 27, 8, 57, 65, 69, 132, 65, 8, 83, 75, 74, 8, 27, 29, 8, 41, 103, 72, 72, 65, + 74, 8, 29, 22, 22, 22, 22, 11, 8, 82, 74, 64, 2, 87, 82, 73, 8, 64, 65, 79, 8, + 24, 8, 52, 61, 81, 68, 61, 82, 132, 65, 80, 8, 64, 69, 65, 8, 24, 18, 8, 64, 65, + 79, 8, 61, 82, 66, 8, 87, 82, 79, 8, 28, 27, 22, 8, 97, 8, 84, 65, 79, 64, 65, + 74, 8, 23, 29, 28, 27, 22, 8, 61, 82, 66, 79, 65, 63, 68, 81, 2, 37, 65, 87, 69, + 65, 68, 82, 74, 67, 8, 82, 74, 132, 65, 79, 73, 8, 69, 68, 79, 65, 79, 8, 82, 74, + 64, 8, 23, 31, 20, 8, 97, 8, 64, 65, 79, 8, 82, 74, 64, 8, 83, 65, 79, 87, 69, + 63, 68, 81, 65, 74, 8, 30, 22, 8, 29, 8, 65, 74, 81, 132, 78, 79, 69, 63, 68, 81, + 8, 31, 31, 22, 11, 8, 82, 74, 80, 2, 82, 74, 80, 8, 87, 82, 79, 8, 0, 5, 8, + 67, 65, 74, 8, 53, 63, 68, 110, 72, 65, 79, 8, 23, 28, 22, 22, 11, 8, 97, 8, 132, + 75, 84, 69, 65, 8, 64, 61, 79, 82, 73, 8, 52, 65, 61, 72, 132, 63, 68, 82, 72, 65, + 8, 23, 31, 24, 22, 8, 97, 8, 64, 82, 79, 63, 68, 8, 24, 22, 22, 22, 8, 6, 8, + 61, 74, 64, 65, 79, 65, 2, 64, 61, 102, 8, 64, 65, 79, 67, 72, 65, 69, 63, 68, 65, + 74, 8, 7, 8, 132, 69, 63, 68, 8, 62, 69, 80, 8, 24, 28, 22, 22, 18, 8, 18, 18, + 8, 45, 82, 74, 69, 8, 79, 82, 74, 64, 8, 62, 65, 69, 73, 8, 31, 8, 97, 8, 57, + 75, 68, 74, 82, 74, 67, 65, 74, 8, 23, 22, 22, 8, 83, 75, 74, 2, 62, 72, 69, 65, + 62, 8, 74, 61, 63, 68, 8, 79, 82, 74, 64, 8, 71, 109, 74, 74, 65, 74, 8, 26, 20, + 8, 97, 8, 64, 65, 73, 8, 48, 61, 79, 71, 8, 84, 65, 69, 72, 8, 30, 8, 97, 60, + 8, 66, 110, 79, 8, 23, 30, 31, 23, 8, 29, 8, 132, 79, 61, 102, 65, 2, 72, 65, 81, + 87, 81, 65, 74, 8, 36, 74, 81, 79, 61, 67, 8, 98, 97, 8, 64, 61, 79, 82, 73, 8, + 23, 31, 23, 22, 21, 23, 27, 20, 8, 28, 29, 22, 11, 8, 97, 8, 64, 69, 65, 132, 65, + 73, 8, 45, 61, 68, 79, 8, 69, 63, 68, 8, 25, 29, 8, 97, 8, 65, 69, 74, 65, 74, + 8, 24, 22, 27, 22, 22, 11, 8, 53, 81, 65, 82, 65, 79, 74, 2, 47, 65, 68, 79, 78, + 72, 103, 74, 65, 74, 8, 23, 27, 23, 24, 8, 97, 8, 36, 79, 87, 81, 8, 64, 65, 79, + 8, 23, 30, 22, 31, 20, 8, 97, 8, 124, 63, 20, 8, 51, 65, 79, 132, 75, 74, 8, 65, + 74, 81, 132, 63, 68, 65, 69, 64, 65, 74, 64, 65, 8, 26, 22, 8, 97, 8, 74, 61, 63, + 68, 8, 23, 30, 31, 31, 31, 8, 64, 65, 79, 2, 36, 62, 84, 61, 74, 64, 65, 79, 82, + 74, 67, 8, 56, 75, 79, 64, 65, 79, 68, 61, 82, 132, 65, 80, 8, 7, 97, 8, 64, 65, + 73, 8, 70, 65, 64, 65, 79, 8, 23, 31, 22, 23, 20, 8, 22, 22, 20, 8, 64, 69, 65, + 8, 64, 69, 65, 8, 30, 24, 8, 97, 8, 74, 61, 63, 68, 8, 26, 22, 22, 8, 7, 48, + 103, 74, 74, 65, 79, 6, 2, 64, 65, 73, 8, 84, 69, 79, 8, 27, 28, 8, 83, 75, 74, + 8, 84, 65, 72, 63, 68, 65, 8, 23, 31, 20, 8, 24, 15, 8, 64, 69, 65, 8, 74, 61, + 63, 68, 8, 87, 69, 74, 132, 82, 74, 67, 8, 25, 22, 8, 29, 8, 61, 62, 65, 79, 8, + 23, 30, 27, 22, 22, 11, 11, 8, 83, 75, 74, 2, 82, 74, 64, 8, 45, 82, 74, 67, 65, + 74, 8, 7, 8, 64, 65, 73, 8, 62, 69, 80, 8, 23, 22, 22, 18, 8, 98, 8, 36, 74, + 132, 78, 79, 82, 63, 68, 8, 64, 65, 79, 8, 64, 61, 79, 82, 73, 8, 23, 22, 8, 61, + 82, 63, 68, 8, 25, 8, 53, 63, 68, 69, 72, 72, 65, 79, 19, 54, 68, 65, 61, 81, 65, + 79, 2, 81, 65, 74, 62, 82, 79, 67, 8, 23, 24, 22, 11, 8, 97, 8, 23, 24, 8, 7, + 29, 8, 7, 57, 61, 68, 79, 72, 69, 63, 68, 9, 6, 8, 30, 30, 20, 8, 24, 18, 8, + 62, 69, 80, 8, 73, 109, 63, 68, 81, 65, 8, 73, 61, 74, 63, 68, 73, 61, 72, 8, 23, + 30, 8, 97, 8, 87, 82, 72, 65, 67, 65, 74, 8, 25, 26, 22, 11, 8, 73, 61, 63, 68, + 65, 2, 64, 65, 74, 8, 24, 27, 22, 22, 8, 97, 8, 82, 74, 64, 8, 65, 79, 66, 79, + 61, 67, 65, 74, 20, 8, 23, 22, 20, 8, 31, 21, 8, 69, 68, 79, 65, 8, 132, 75, 84, + 69, 65, 8, 64, 69, 65, 8, 23, 30, 29, 31, 8, 97, 8, 65, 79, 132, 81, 65, 74, 73, + 75, 72, 8, 23, 30, 31, 30, 31, 21, 8, 23, 30, 23, 23, 2, 83, 75, 73, 8, 51, 79, + 75, 70, 65, 71, 81, 8, 27, 18, 8, 69, 132, 81, 8, 84, 110, 74, 132, 63, 68, 65, 74, + 80, 84, 65, 79, 81, 8, 23, 24, 18, 8, 7, 8, 75, 64, 65, 79, 8, 132, 69, 74, 64, + 20, 8, 24, 30, 26, 8, 23, 31, 23, 30, 8, 24, 18, 8, 52, 65, 69, 68, 65, 8, 24, + 22, 11, 22, 8, 48, 65, 69, 74, 82, 74, 67, 2, 68, 61, 62, 65, 74, 8, 67, 65, 68, + 65, 74, 8, 64, 65, 79, 8, 44, 72, 20, 8, 29, 25, 11, 8, 24, 26, 26, 26, 26, 8, + 84, 103, 72, 64, 65, 79, 8, 39, 69, 65, 8, 23, 30, 27, 23, 8, 83, 75, 73, 8, 24, + 29, 22, 8, 37, 65, 81, 65, 69, 72, 69, 67, 81, 65, 74, 2, 24, 27, 22, 22, 8, 30, + 22, 22, 22, 8, 7, 8, 65, 69, 74, 67, 65, 66, 110, 68, 79, 81, 20, 8, 54, 65, 74, + 75, 79, 8, 24, 27, 20, 8, 7, 8, 65, 69, 74, 8, 87, 82, 132, 81, 103, 74, 64, 69, + 67, 65, 74, 8, 64, 61, 79, 82, 73, 8, 23, 31, 22, 23, 8, 97, 8, 64, 65, 73, 8, + 23, 31, 22, 26, 31, 18, 8, 39, 65, 74, 2, 65, 69, 74, 65, 73, 8, 64, 65, 79, 8, + 97, 8, 23, 31, 22, 24, 8, 65, 69, 74, 65, 8, 23, 30, 26, 22, 18, 8, 25, 28, 8, + 39, 69, 65, 8, 37, 65, 132, 63, 68, 72, 82, 102, 8, 84, 61, 79, 65, 74, 8, 23, 24, + 8, 24, 18, 8, 64, 65, 79, 8, 27, 22, 22, 11, 8, 64, 69, 65, 2, 84, 82, 79, 64, + 65, 8, 65, 69, 74, 65, 80, 8, 27, 28, 8, 62, 82, 79, 67, 65, 79, 8, 64, 69, 65, + 8, 28, 27, 22, 22, 11, 8, 11, 8, 25, 22, 22, 8, 74, 69, 73, 73, 81, 8, 64, 65, + 80, 68, 61, 72, 62, 8, 25, 22, 22, 8, 24, 97, 8, 23, 22, 27, 24, 8, 24, 22, 8, + 29, 8, 53, 63, 68, 82, 72, 65, 74, 2, 83, 75, 73, 8, 64, 65, 79, 8, 7, 8, 64, + 65, 79, 8, 84, 69, 79, 64, 20, 6, 8, 30, 30, 20, 8, 27, 26, 31, 8, 23, 31, 23, + 30, 8, 23, 27, 24, 25, 8, 23, 8, 27, 28, 8, 44, 74, 81, 65, 79, 65, 132, 132, 65, + 74, 132, 81, 61, 74, 64, 78, 82, 74, 71, 81, 8, 25, 22, 22, 8, 67, 65, 68, 65, 74, + 2, 64, 69, 65, 8, 84, 61, 79, 8, 44, 74, 132, 81, 61, 72, 72, 61, 81, 69, 75, 74, + 8, 64, 65, 79, 8, 23, 25, 25, 24, 97, 8, 64, 69, 65, 8, 27, 22, 22, 22, 8, 64, + 65, 80, 8, 30, 30, 30, 8, 97, 8, 73, 69, 81, 8, 23, 26, 22, 22, 8, 61, 62, 65, + 79, 2, 72, 65, 81, 87, 81, 65, 79, 65, 73, 8, 51, 65, 74, 87, 69, 67, 8, 18, 97, + 8, 64, 65, 79, 8, 65, 79, 68, 109, 68, 65, 74, 8, 23, 27, 11, 8, 24, 8, 132, 65, + 69, 8, 84, 65, 72, 63, 68, 65, 8, 65, 69, 74, 65, 80, 8, 26, 24, 8, 97, 8, 39, + 61, 80, 8, 27, 22, 22, 22, 8, 39, 61, 80, 2, 74, 69, 63, 68, 81, 8, 62, 69, 80, + 8, 75, 8, 73, 69, 81, 8, 59, 78, 80, 69, 72, 75, 74, 8, 29, 24, 20, 8, 97, 8, + 53, 81, 103, 64, 81, 65, 8, 132, 65, 81, 87, 65, 74, 8, 31, 30, 27, 8, 24, 22, 8, + 97, 8, 64, 82, 79, 63, 68, 8, 29, 30, 29, 8, 53, 63, 68, 82, 72, 64, 65, 78, 82, + 81, 61, 81, 69, 75, 74, 2, 64, 65, 74, 8, 23, 24, 27, 22, 8, 97, 8, 73, 65, 68, + 79, 8, 48, 75, 73, 73, 132, 65, 74, 19, 25, 22, 22, 22, 18, 8, 27, 28, 8, 56, 75, + 79, 132, 81, 65, 68, 65, 79, 8, 49, 75, 74, 74, 65, 74, 64, 61, 73, 73, 8, 45, 80, + 20, 8, 23, 31, 23, 29, 8, 97, 8, 64, 61, 79, 61, 74, 8, 27, 26, 22, 22, 8, 75, + 62, 3, 2, 52, 65, 69, 68, 65, 74, 20, 8, 62, 65, 71, 61, 73, 65, 74, 20, 8, 24, + 15, 8, 68, 65, 79, 87, 82, 19, 8, 73, 69, 81, 8, 23, 30, 31, 23, 22, 11, 22, 8, + 7, 8, 61, 82, 66, 8, 61, 82, 80, 8, 64, 61, 79, 82, 73, 8, 23, 30, 31, 22, 8, + 27, 21, 28, 8, 64, 75, 63, 68, 8, 23, 30, 24, 27, 29, 8, 45, 81, 124, 20, 2, 82, + 74, 64, 8, 25, 24, 31, 21, 24, 25, 8, 97, 8, 61, 72, 80, 8, 37, 65, 81, 81, 65, + 74, 8, 23, 30, 30, 25, 18, 8, 97, 8, 55, 74, 81, 65, 79, 62, 61, 82, 8, 64, 61, + 80, 8, 67, 65, 132, 81, 61, 72, 81, 65, 81, 65, 8, 30, 25, 8, 50, 79, 64, 20, 8, + 23, 31, 23, 28, 8, 64, 65, 80, 2, 64, 65, 79, 8, 37, 65, 81, 79, 61, 67, 8, 27, + 28, 8, 43, 110, 72, 66, 65, 8, 61, 82, 80, 8, 23, 31, 22, 30, 18, 8, 18, 8, 64, + 65, 79, 8, 56, 65, 79, 84, 61, 72, 81, 82, 74, 67, 20, 8, 64, 65, 80, 8, 23, 30, + 31, 23, 8, 18, 97, 8, 61, 82, 66, 87, 82, 84, 65, 74, 64, 65, 74, 8, 23, 23, 22, + 8, 51, 82, 81, 87, 20, 2, 64, 65, 80, 8, 23, 30, 28, 27, 8, 24, 18, 8, 83, 75, + 74, 8, 46, 79, 65, 69, 80, 132, 63, 68, 82, 72, 19, 24, 24, 33, 8, 97, 8, 82, 74, + 64, 8, 7, 132, 63, 68, 65, 69, 74, 81, 6, 8, 64, 69, 65, 8, 23, 27, 8, 97, 8, + 83, 75, 79, 19, 24, 27, 22, 8, 7, 8, 73, 65, 68, 79, 2, 132, 63, 68, 72, 69, 65, + 102, 72, 69, 63, 68, 8, 64, 69, 65, 8, 7, 18, 8, 64, 69, 65, 132, 65, 8, 132, 69, + 65, 8, 23, 27, 11, 8, 97, 21, 8, 84, 65, 72, 63, 68, 65, 8, 68, 61, 74, 64, 65, + 72, 81, 8, 61, 62, 65, 79, 8, 25, 25, 8, 97, 8, 23, 30, 28, 27, 8, 29, 8, 37, + 65, 3, 2, 82, 74, 64, 8, 61, 72, 80, 8, 24, 8, 132, 81, 103, 64, 81, 69, 132, 63, + 68, 65, 79, 132, 65, 69, 81, 80, 8, 132, 81, 79, 20, 8, 24, 11, 8, 18, 97, 8, 73, + 69, 81, 8, 132, 69, 63, 68, 8, 65, 69, 74, 65, 8, 23, 30, 27, 27, 8, 27, 21, 8, + 48, 69, 72, 72, 69, 75, 74, 65, 74, 8, 27, 31, 22, 22, 8, 64, 65, 79, 2, 64, 61, + 79, 82, 73, 8, 39, 79, 65, 68, 62, 110, 68, 74, 65, 8, 62, 65, 69, 8, 68, 61, 62, + 65, 74, 8, 23, 30, 27, 23, 18, 8, 7, 8, 40, 69, 74, 87, 69, 67, 8, 61, 82, 63, + 68, 8, 82, 74, 80, 8, 30, 30, 8, 18, 97, 8, 82, 74, 64, 8, 24, 31, 22, 8, 42, + 79, 75, 72, 73, 61, 74, 3, 2, 64, 65, 73, 8, 36, 82, 66, 132, 69, 63, 68, 81, 8, + 7, 8, 70, 65, 74, 65, 80, 8, 47, 61, 67, 65, 79, 78, 72, 61, 81, 87, 8, 24, 24, + 33, 8, 97, 8, 64, 65, 73, 8, 31, 27, 22, 22, 20, 8, 83, 75, 73, 8, 23, 31, 24, + 22, 8, 97, 8, 83, 65, 79, 19, 23, 23, 31, 22, 22, 8, 73, 65, 68, 79, 2, 67, 65, + 74, 61, 82, 8, 73, 61, 74, 63, 68, 73, 61, 72, 8, 97, 96, 22, 8, 84, 69, 79, 8, + 61, 82, 80, 8, 31, 20, 8, 7, 87, 84, 69, 132, 63, 68, 65, 74, 6, 8, 62, 69, 80, + 8, 71, 61, 74, 74, 20, 8, 29, 27, 8, 24, 8, 64, 69, 65, 8, 25, 24, 27, 22, 22, + 11, 8, 60, 69, 73, 73, 65, 79, 2, 22, 22, 22, 8, 64, 65, 73, 8, 97, 8, 56, 65, + 79, 62, 69, 74, 64, 82, 74, 67, 8, 43, 82, 67, 75, 8, 23, 24, 22, 22, 20, 8, 97, + 8, 64, 69, 65, 8, 75, 64, 65, 79, 8, 36, 74, 79, 82, 66, 65, 74, 8, 30, 24, 8, + 64, 69, 65, 8, 24, 23, 22, 22, 22, 8, 83, 69, 65, 72, 72, 65, 69, 63, 68, 81, 2, + 64, 65, 79, 74, 8, 132, 75, 84, 65, 69, 81, 8, 97, 8, 65, 79, 132, 81, 65, 74, 8, + 124, 63, 20, 8, 25, 28, 25, 20, 8, 18, 97, 8, 65, 69, 74, 65, 8, 23, 30, 22, 31, + 8, 56, 65, 79, 132, 81, 103, 79, 71, 82, 74, 67, 8, 23, 30, 31, 31, 8, 24, 18, 8, + 23, 24, 20, 8, 31, 8, 29, 8, 83, 75, 74, 2, 7, 51, 61, 79, 87, 65, 72, 72, 65, + 74, 6, 8, 69, 132, 81, 8, 28, 8, 67, 61, 74, 87, 8, 64, 61, 80, 8, 23, 30, 26, + 22, 20, 8, 84, 82, 79, 64, 65, 8, 37, 86, 71, 8, 82, 74, 64, 8, 28, 29, 8, 27, + 28, 8, 42, 79, 82, 74, 64, 8, 24, 29, 22, 22, 8, 23, 31, 22, 30, 21, 23, 25, 20, + 2, 68, 65, 66, 81, 8, 83, 75, 74, 8, 97, 8, 73, 69, 79, 8, 47, 61, 73, 78, 65, + 74, 8, 23, 30, 29, 30, 11, 8, 97, 8, 22, 22, 22, 8, 41, 65, 79, 74, 65, 79, 8, + 23, 29, 26, 8, 25, 22, 8, 27, 28, 8, 64, 61, 102, 8, 25, 23, 8, 29, 8, 132, 75, + 72, 72, 2, 42, 79, 75, 102, 19, 37, 65, 79, 72, 69, 74, 8, 65, 69, 74, 73, 61, 72, + 8, 7, 97, 8, 44, 45, 44, 45, 8, 82, 74, 64, 8, 23, 22, 20, 8, 97, 8, 46, 109, + 74, 69, 67, 19, 44, 45, 44, 45, 8, 61, 82, 66, 8, 23, 26, 22, 8, 97, 8, 25, 22, + 20, 8, 24, 27, 22, 8, 29, 8, 64, 69, 65, 2, 48, 103, 64, 63, 68, 65, 74, 8, 84, + 65, 79, 64, 65, 74, 8, 97, 8, 64, 69, 65, 8, 43, 61, 82, 80, 84, 61, 79, 81, 80, + 8, 23, 24, 20, 8, 27, 21, 8, 83, 75, 79, 19, 23, 30, 29, 31, 8, 39, 61, 79, 110, + 62, 65, 79, 8, 23, 30, 31, 28, 8, 18, 8, 83, 75, 74, 8, 25, 28, 25, 22, 22, 8, + 65, 79, 67, 61, 62, 2, 45, 61, 66, 66, 104, 8, 31, 24, 8, 11, 8, 24, 21, 8, 61, + 82, 63, 68, 8, 61, 82, 66, 8, 30, 29, 20, 8, 27, 21, 8, 75, 64, 65, 79, 8, 36, + 74, 72, 65, 69, 68, 65, 8, 65, 81, 84, 61, 69, 67, 65, 74, 8, 30, 22, 22, 8, 29, + 18, 8, 84, 65, 69, 72, 8, 25, 26, 30, 25, 30, 22, 21, 8, 64, 65, 79, 2, 53, 65, + 69, 81, 8, 61, 74, 64, 65, 79, 65, 79, 8, 7, 8, 83, 75, 73, 8, 72, 69, 65, 68, + 65, 74, 8, 24, 18, 8, 97, 75, 22, 8, 64, 61, 80, 8, 23, 23, 25, 21, 23, 25, 8, + 24, 22, 22, 22, 8, 23, 26, 23, 8, 97, 8, 64, 69, 65, 8, 23, 24, 22, 22, 8, 64, + 65, 79, 70, 65, 74, 69, 67, 65, 74, 2, 64, 61, 80, 8, 64, 61, 102, 8, 7, 97, 8, + 48, 61, 74, 67, 65, 72, 8, 84, 69, 79, 64, 8, 23, 29, 28, 31, 11, 8, 7, 8, 23, + 22, 22, 8, 81, 79, 75, 81, 87, 8, 64, 65, 74, 8, 28, 23, 8, 97, 8, 83, 65, 79, + 62, 69, 74, 64, 65, 74, 8, 23, 30, 25, 27, 22, 8, 61, 72, 132, 75, 2, 74, 69, 63, + 68, 81, 8, 84, 65, 72, 63, 68, 65, 8, 64, 61, 80, 8, 84, 65, 69, 63, 68, 81, 8, + 23, 24, 27, 33, 8, 97, 8, 64, 65, 79, 8, 23, 30, 31, 25, 8, 132, 63, 68, 79, 69, + 66, 81, 65, 74, 8, 23, 23, 8, 98, 8, 24, 30, 20, 8, 24, 30, 26, 8, 29, 8, 68, + 69, 65, 132, 69, 67, 65, 74, 2, 60, 82, 132, 63, 68, 82, 102, 8, 64, 65, 73, 67, 65, + 73, 103, 102, 8, 24, 21, 8, 64, 65, 80, 8, 65, 69, 74, 65, 79, 8, 31, 20, 8, 27, + 18, 8, 83, 75, 79, 68, 61, 74, 64, 65, 74, 8, 67, 65, 72, 65, 67, 65, 74, 8, 83, + 75, 72, 72, 65, 74, 8, 27, 8, 29, 7, 8, 39, 61, 80, 8, 27, 28, 22, 21, 8, 65, + 79, 68, 61, 72, 81, 65, 74, 2, 23, 28, 8, 29, 8, 55, 68, 79, 8, 29, 8, 28, 28, + 8, 29, 8, 82, 74, 64, 8, 27, 18, 8, 56, 8, 84, 69, 79, 8, 65, 69, 74, 8, 68, + 69, 74, 87, 82, 87, 82, 66, 110, 67, 65, 74, 8, 23, 25, 30, 23, 8, 29, 18, 28, 8, + 82, 74, 64, 8, 24, 22, 22, 8, 64, 61, 80, 2, 47, 61, 67, 65, 79, 78, 72, 61, 81, + 87, 19, 56, 75, 79, 132, 81, 65, 68, 65, 79, 32, 8, 75, 64, 65, 79, 8, 29, 8, 132, + 69, 74, 64, 8, 64, 65, 79, 8, 28, 31, 20, 8, 18, 8, 110, 62, 65, 79, 8, 64, 69, + 65, 8, 84, 103, 79, 65, 74, 8, 26, 22, 8, 22, 22, 22, 8, 23, 22, 22, 8, 132, 69, + 63, 68, 2, 132, 69, 74, 64, 8, 40, 79, 72, 61, 102, 8, 97, 8, 61, 82, 66, 8, 82, + 74, 64, 8, 23, 30, 29, 22, 20, 8, 7, 8, 64, 65, 79, 8, 82, 74, 64, 8, 109, 81, + 69, 67, 65, 8, 23, 30, 28, 28, 8, 64, 69, 65, 8, 25, 22, 22, 22, 22, 11, 8, 87, + 75, 67, 65, 74, 2, 65, 79, 109, 66, 66, 74, 65, 81, 20, 8, 53, 81, 61, 74, 64, 78, + 82, 74, 71, 81, 8, 18, 8, 22, 22, 22, 8, 48, 61, 69, 8, 23, 30, 30, 30, 20, 8, + 97, 8, 75, 68, 74, 65, 8, 132, 69, 63, 68, 8, 64, 61, 102, 8, 25, 27, 8, 27, 28, + 8, 84, 65, 79, 64, 65, 74, 20, 8, 23, 30, 31, 30, 22, 11, 8, 36, 62, 62, 61, 82, + 2, 132, 75, 72, 63, 68, 65, 74, 8, 53, 63, 68, 84, 69, 65, 79, 69, 67, 71, 65, 69, + 81, 65, 74, 8, 27, 28, 8, 64, 69, 65, 132, 65, 74, 8, 57, 75, 68, 74, 82, 74, 67, + 65, 74, 8, 31, 20, 8, 27, 8, 23, 26, 22, 8, 22, 22, 22, 8, 44, 63, 68, 8, 28, + 8, 21, 8, 23, 22, 20, 8, 31, 22, 11, 8, 24, 22, 22, 2, 84, 65, 79, 64, 65, 74, + 8, 74, 69, 63, 68, 81, 8, 7, 8, 54, 65, 69, 72, 62, 65, 81, 79, 61, 67, 8, 42, + 65, 64, 61, 74, 71, 65, 74, 8, 25, 22, 22, 22, 11, 8, 27, 8, 64, 69, 65, 8, 70, + 82, 74, 67, 65, 8, 61, 82, 63, 68, 8, 23, 8, 97, 8, 61, 82, 66, 8, 23, 30, 24, + 28, 8, 23, 23, 26, 20, 2, 132, 65, 69, 74, 65, 8, 22, 30, 20, 8, 97, 8, 64, 69, + 65, 8, 82, 74, 64, 8, 28, 27, 28, 18, 8, 97, 8, 82, 74, 64, 8, 54, 61, 67, 65, + 80, 75, 79, 64, 74, 82, 74, 67, 32, 8, 64, 65, 79, 8, 23, 23, 8, 97, 8, 64, 65, + 79, 8, 23, 24, 27, 22, 8, 61, 72, 132, 75, 2, 64, 65, 80, 68, 61, 72, 62, 8, 67, + 61, 74, 87, 8, 7, 8, 84, 69, 79, 8, 74, 82, 79, 8, 24, 22, 22, 22, 18, 8, 24, + 18, 8, 87, 82, 73, 8, 23, 30, 30, 25, 8, 65, 69, 74, 8, 24, 22, 8, 97, 18, 8, + 84, 65, 79, 64, 65, 74, 20, 6, 8, 23, 22, 22, 8, 37, 61, 74, 67, 72, 61, 64, 65, + 132, 63, 68, 2, 53, 69, 65, 8, 69, 63, 68, 8, 7, 8, 83, 75, 73, 8, 64, 65, 79, + 8, 26, 31, 31, 33, 8, 29, 18, 8, 53, 69, 65, 8, 79, 82, 74, 64, 8, 23, 25, 26, + 24, 8, 31, 22, 8, 37, 61, 72, 71, 65, 8, 27, 22, 22, 22, 22, 8, 82, 74, 81, 65, + 79, 79, 69, 63, 68, 81, 65, 81, 20, 2, 84, 65, 80, 68, 61, 72, 62, 8, 64, 65, 80, + 8, 62, 65, 132, 63, 68, 72, 69, 65, 102, 81, 8, 37, 65, 61, 73, 81, 65, 74, 8, 23, + 24, 22, 22, 20, 8, 53, 82, 73, 73, 65, 20, 8, 23, 30, 24, 24, 8, 84, 65, 132, 65, + 74, 81, 19, 23, 24, 22, 22, 8, 7, 8, 64, 65, 79, 8, 23, 24, 22, 22, 8, 64, 65, + 79, 2, 42, 79, 65, 64, 86, 8, 66, 110, 79, 8, 18, 8, 23, 30, 27, 27, 8, 84, 103, + 63, 68, 132, 81, 8, 23, 30, 27, 30, 20, 8, 18, 97, 8, 61, 82, 66, 8, 36, 82, 63, + 68, 8, 37, 65, 102, 65, 79, 8, 23, 31, 24, 22, 8, 46, 61, 73, 62, 61, 63, 68, 8, + 29, 22, 22, 8, 39, 69, 65, 2, 36, 74, 84, 65, 132, 65, 74, 68, 65, 69, 81, 8, 64, + 61, 87, 82, 8, 7, 8, 82, 74, 64, 8, 23, 30, 29, 31, 8, 23, 31, 23, 27, 20, 11, + 8, 59, 61, 74, 67, 8, 41, 75, 74, 64, 80, 8, 82, 74, 64, 8, 25, 22, 22, 22, 8, + 24, 11, 8, 49, 61, 73, 65, 74, 6, 8, 23, 24, 22, 22, 22, 22, 8, 23, 31, 23, 28, + 2, 67, 61, 74, 87, 8, 84, 65, 79, 64, 65, 74, 8, 64, 61, 102, 8, 66, 82, 79, 63, + 68, 81, 62, 61, 79, 8, 23, 24, 22, 22, 20, 8, 29, 18, 8, 82, 74, 64, 8, 24, 24, + 20, 8, 64, 65, 73, 8, 23, 25, 8, 97, 8, 87, 82, 79, 8, 29, 8, 29, 8, 65, 69, + 74, 65, 80, 2, 64, 65, 73, 8, 64, 65, 79, 8, 25, 28, 25, 8, 64, 65, 73, 8, 23, + 22, 22, 18, 8, 97, 8, 23, 30, 20, 8, 30, 22, 22, 22, 8, 52, 61, 81, 68, 68, 61, + 82, 132, 65, 80, 8, 23, 22, 29, 28, 8, 97, 8, 64, 65, 74, 8, 31, 31, 31, 22, 21, + 22, 8, 82, 74, 64, 2, 69, 79, 67, 65, 74, 64, 84, 65, 72, 63, 68, 65, 74, 8, 87, + 82, 79, 8, 7, 8, 70, 65, 74, 69, 67, 65, 74, 8, 53, 81, 79, 20, 8, 24, 22, 20, + 8, 97, 8, 62, 65, 81, 79, 103, 67, 81, 8, 23, 30, 31, 22, 8, 65, 69, 74, 65, 74, + 8, 30, 8, 97, 8, 23, 26, 25, 23, 8, 30, 23, 8, 23, 30, 29, 29, 2, 55, 74, 81, + 65, 79, 74, 65, 68, 73, 82, 74, 67, 65, 74, 8, 42, 79, 110, 74, 132, 81, 79, 20, 8, + 97, 8, 23, 27, 20, 8, 64, 65, 79, 8, 29, 11, 8, 27, 21, 8, 64, 69, 65, 8, 82, + 74, 64, 8, 62, 69, 80, 8, 28, 8, 7, 8, 65, 69, 74, 65, 73, 8, 23, 31, 24, 22, + 22, 8, 132, 69, 81, 87, 82, 74, 67, 65, 74, 2, 82, 74, 64, 8, 67, 65, 84, 61, 72, + 81, 69, 67, 8, 65, 69, 74, 87, 65, 72, 74, 65, 8, 132, 69, 74, 64, 20, 8, 23, 23, + 20, 8, 29, 8, 132, 69, 74, 64, 8, 61, 62, 65, 79, 8, 23, 30, 26, 30, 8, 23, 24, + 8, 11, 8, 36, 82, 66, 66, 61, 132, 132, 82, 74, 67, 8, 30, 22, 8, 67, 61, 74, 87, + 2, 65, 79, 84, 75, 79, 62, 65, 74, 8, 67, 72, 61, 82, 62, 65, 8, 97, 8, 84, 69, + 79, 64, 8, 40, 69, 74, 65, 8, 23, 30, 24, 28, 33, 8, 27, 28, 8, 23, 26, 20, 8, + 61, 82, 66, 8, 64, 65, 74, 65, 74, 8, 23, 24, 8, 97, 8, 53, 81, 65, 72, 72, 65, + 74, 8, 27, 30, 31, 22, 18, 8, 37, 65, 132, 63, 68, 84, 65, 79, 64, 65, 74, 2, 53, + 63, 68, 82, 72, 64, 69, 67, 71, 65, 69, 81, 8, 56, 65, 79, 65, 69, 74, 8, 97, 8, + 69, 132, 81, 8, 23, 30, 29, 30, 8, 30, 25, 8, 97, 8, 64, 65, 74, 8, 64, 69, 65, + 132, 65, 73, 8, 61, 82, 63, 68, 8, 25, 27, 8, 18, 98, 8, 25, 25, 23, 21, 23, 25, + 8, 30, 23, 29, 6, 8, 53, 81, 82, 73, 78, 66, 2, 23, 22, 22, 8, 64, 65, 80, 68, + 61, 72, 62, 8, 18, 8, 68, 65, 79, 87, 82, 19, 39, 82, 79, 63, 68, 8, 27, 22, 20, + 8, 27, 18, 8, 83, 65, 79, 82, 79, 132, 61, 63, 68, 81, 8, 43, 65, 79, 79, 65, 74, + 8, 67, 65, 71, 110, 74, 64, 69, 67, 81, 8, 27, 27, 8, 97, 8, 36, 79, 81, 8, 31, + 27, 22, 8, 11, 8, 64, 65, 74, 2, 36, 82, 66, 132, 63, 68, 72, 61, 67, 8, 124, 63, + 20, 8, 15, 8, 87, 82, 79, 8, 68, 61, 81, 8, 23, 31, 23, 29, 20, 8, 18, 8, 64, + 65, 80, 8, 64, 65, 79, 8, 61, 62, 84, 65, 69, 63, 68, 65, 74, 64, 65, 79, 8, 30, + 28, 8, 11, 97, 8, 37, 65, 69, 8, 31, 31, 22, 11, 8, 7, 39, 61, 80, 2, 61, 72, + 132, 75, 8, 73, 69, 81, 8, 97, 75, 8, 64, 65, 74, 8, 31, 30, 27, 8, 27, 26, 31, + 22, 21, 11, 8, 45, 61, 68, 79, 65, 80, 62, 65, 83, 109, 72, 71, 65, 79, 82, 74, 67, + 8, 74, 61, 63, 68, 8, 22, 22, 22, 8, 23, 31, 24, 22, 8, 24, 18, 8, 69, 132, 81, + 8, 30, 30, 8, 7, 11, 29, 8, 60, 61, 68, 72, 2, 62, 65, 69, 8, 23, 31, 24, 25, + 25, 8, 7, 8, 61, 82, 80, 8, 64, 65, 80, 8, 29, 25, 28, 31, 18, 8, 7, 8, 64, + 65, 79, 8, 83, 75, 74, 8, 27, 30, 28, 24, 8, 25, 26, 8, 21, 8, 23, 27, 24, 25, + 8, 23, 29, 22, 11, 8, 36, 74, 132, 81, 69, 65, 67, 65, 2, 64, 69, 65, 8, 132, 75, + 72, 72, 65, 74, 8, 29, 8, 7, 68, 65, 66, 81, 69, 67, 65, 79, 8, 71, 72, 65, 69, + 74, 65, 74, 8, 24, 20, 8, 23, 23, 26, 20, 8, 64, 65, 74, 8, 44, 45, 45, 8, 24, + 22, 22, 8, 53, 69, 65, 8, 28, 31, 22, 8, 53, 75, 74, 74, 81, 61, 67, 65, 74, 32, + 2, 132, 69, 63, 68, 8, 65, 69, 74, 8, 7, 8, 64, 65, 79, 8, 36, 74, 79, 82, 66, + 65, 74, 8, 23, 31, 18, 8, 18, 8, 84, 69, 79, 8, 61, 72, 132, 75, 8, 87, 82, 79, + 8, 29, 28, 8, 79, 65, 63, 68, 81, 8, 23, 30, 25, 29, 22, 22, 8, 64, 61, 68, 65, + 79, 2, 45, 82, 74, 67, 65, 74, 8, 40, 81, 61, 81, 8, 27, 28, 8, 53, 81, 61, 64, + 81, 8, 48, 75, 74, 61, 81, 65, 74, 8, 25, 24, 22, 22, 18, 8, 7, 97, 8, 64, 61, + 102, 8, 64, 65, 73, 8, 132, 63, 68, 84, 61, 74, 71, 65, 74, 8, 23, 24, 8, 97, 8, + 64, 65, 79, 8, 26, 22, 22, 8, 37, 65, 64, 110, 79, 66, 74, 69, 80, 2, 25, 24, 27, + 22, 8, 64, 69, 65, 8, 97, 22, 8, 60, 82, 79, 8, 64, 69, 65, 132, 65, 79, 8, 31, + 30, 27, 11, 8, 97, 8, 45, 61, 68, 79, 65, 8, 64, 69, 65, 8, 61, 62, 87, 69, 65, + 72, 65, 74, 8, 25, 27, 8, 7, 97, 8, 132, 69, 63, 68, 8, 23, 24, 27, 22, 8, 29, + 8, 83, 75, 74, 2, 67, 65, 73, 103, 102, 8, 132, 75, 72, 63, 68, 65, 80, 8, 28, 8, + 62, 69, 80, 8, 57, 69, 79, 8, 29, 30, 11, 8, 64, 69, 65, 8, 42, 65, 73, 65, 69, + 74, 64, 65, 62, 65, 68, 109, 79, 64, 65, 8, 74, 65, 82, 65, 74, 8, 23, 27, 22, 29, + 8, 29, 21, 8, 64, 65, 79, 8, 23, 24, 22, 11, 8, 64, 61, 102, 2, 57, 65, 74, 69, + 67, 8, 71, 61, 74, 74, 8, 7, 8, 48, 69, 65, 81, 83, 65, 79, 81, 79, 61, 67, 8, + 70, 82, 74, 67, 65, 8, 23, 20, 8, 24, 8, 68, 61, 81, 8, 68, 61, 62, 65, 74, 8, + 61, 72, 80, 8, 24, 22, 8, 97, 8, 24, 25, 27, 21, 24, 24, 8, 29, 30, 8, 7, 8, + 64, 65, 79, 2, 36, 74, 64, 65, 79, 82, 74, 67, 8, 23, 31, 23, 24, 21, 23, 29, 20, + 8, 97, 8, 70, 65, 73, 61, 74, 64, 8, 25, 23, 25, 8, 25, 22, 22, 22, 8, 7, 8, + 64, 61, 64, 82, 79, 63, 68, 8, 64, 61, 102, 8, 82, 74, 64, 8, 23, 30, 29, 22, 8, + 18, 8, 65, 69, 74, 65, 8, 23, 30, 27, 29, 22, 8, 132, 78, 79, 65, 63, 68, 65, 2, + 64, 65, 79, 8, 51, 79, 75, 81, 75, 71, 75, 72, 72, 8, 98, 8, 23, 30, 31, 23, 8, + 64, 65, 80, 8, 23, 24, 11, 8, 24, 15, 8, 132, 75, 84, 69, 65, 8, 64, 61, 102, 8, + 61, 72, 80, 8, 31, 8, 18, 97, 8, 83, 75, 73, 8, 23, 26, 31, 22, 22, 22, 8, 67, + 65, 73, 69, 65, 81, 65, 81, 65, 74, 2, 31, 31, 8, 7, 8, 64, 65, 79, 8, 24, 8, + 73, 61, 102, 67, 65, 62, 65, 74, 64, 20, 8, 23, 30, 23, 23, 8, 29, 28, 33, 8, 64, + 69, 65, 8, 23, 24, 20, 8, 23, 31, 23, 22, 21, 23, 27, 20, 8, 23, 31, 22, 28, 8, + 39, 61, 80, 8, 23, 30, 30, 24, 22, 22, 8, 36, 74, 132, 78, 79, 110, 63, 68, 65, 74, + 2, 65, 79, 68, 61, 72, 81, 65, 74, 20, 8, 132, 69, 81, 87, 65, 74, 64, 65, 8, 15, + 8, 71, 72, 61, 79, 8, 68, 61, 81, 81, 65, 74, 8, 27, 31, 11, 8, 53, 81, 69, 73, + 73, 65, 74, 8, 87, 82, 73, 8, 62, 69, 80, 8, 30, 25, 8, 97, 8, 64, 65, 79, 8, + 23, 30, 22, 11, 8, 39, 79, 20, 2, 55, 74, 81, 65, 79, 79, 69, 63, 68, 81, 8, 64, + 65, 79, 8, 97, 8, 47, 65, 62, 65, 74, 80, 71, 82, 74, 64, 65, 8, 64, 65, 73, 8, + 23, 30, 31, 27, 20, 8, 27, 18, 8, 61, 82, 63, 68, 8, 87, 61, 68, 72, 65, 74, 8, + 30, 22, 22, 8, 25, 8, 28, 8, 83, 75, 74, 8, 23, 31, 22, 22, 8, 83, 75, 74, 2, + 67, 65, 132, 81, 65, 69, 67, 65, 79, 81, 65, 74, 8, 23, 27, 23, 24, 8, 40, 69, 74, + 19, 57, 65, 69, 132, 65, 8, 27, 24, 24, 22, 8, 97, 8, 87, 82, 20, 8, 30, 22, 22, + 8, 132, 81, 69, 65, 67, 8, 28, 27, 8, 97, 8, 132, 63, 68, 82, 72, 65, 8, 24, 25, + 22, 11, 8, 42, 79, 82, 78, 78, 65, 74, 2, 87, 65, 69, 67, 65, 74, 8, 62, 65, 81, + 79, 61, 67, 65, 74, 8, 62, 69, 80, 8, 23, 30, 24, 31, 8, 23, 30, 30, 22, 11, 8, + 21, 8, 36, 82, 80, 132, 81, 61, 81, 81, 82, 74, 67, 8, 71, 65, 69, 74, 65, 74, 8, + 124, 63, 20, 8, 23, 27, 23, 29, 8, 97, 8, 64, 61, 102, 8, 23, 23, 8, 29, 8, 42, + 65, 62, 65, 79, 81, 2, 23, 26, 23, 8, 36, 82, 63, 68, 8, 97, 8, 45, 80, 20, 8, + 64, 69, 65, 132, 65, 79, 8, 24, 29, 11, 8, 97, 8, 64, 65, 80, 8, 36, 82, 80, 67, + 72, 65, 69, 63, 68, 8, 83, 75, 74, 8, 23, 31, 23, 27, 8, 7, 98, 8, 23, 31, 23, + 31, 8, 24, 8, 29, 8, 65, 69, 74, 132, 65, 69, 81, 69, 4, 2, 75, 64, 65, 79, 8, + 44, 63, 68, 8, 24, 18, 8, 73, 69, 81, 8, 43, 65, 79, 71, 109, 73, 73, 72, 69, 63, + 68, 8, 23, 27, 20, 8, 97, 8, 83, 75, 74, 8, 40, 81, 61, 81, 8, 64, 61, 74, 74, + 8, 23, 31, 22, 27, 8, 36, 62, 19, 25, 27, 22, 22, 21, 8, 41, 65, 68, 72, 75, 84, + 13, 132, 63, 68, 65, 74, 2, 47, 109, 68, 74, 65, 8, 64, 61, 102, 8, 24, 8, 45, 82, + 72, 69, 8, 47, 65, 69, 81, 65, 79, 32, 8, 26, 27, 20, 8, 97, 8, 7, 48, 65, 69, + 74, 65, 8, 64, 65, 80, 8, 67, 65, 79, 69, 63, 68, 81, 65, 81, 65, 74, 8, 23, 23, + 8, 97, 97, 8, 71, 109, 74, 74, 65, 74, 8, 23, 31, 31, 21, 22, 8, 23, 27, 22, 2, + 62, 69, 80, 8, 65, 69, 74, 8, 48, 61, 69, 8, 72, 65, 69, 63, 68, 81, 8, 23, 30, + 31, 22, 20, 8, 27, 18, 8, 67, 65, 81, 79, 69, 65, 62, 65, 74, 35, 8, 57, 69, 72, + 68, 65, 72, 73, 8, 68, 61, 62, 65, 74, 8, 25, 22, 22, 8, 29, 18, 8, 61, 72, 80, + 8, 24, 23, 31, 22, 8, 83, 75, 79, 2, 69, 68, 79, 65, 79, 8, 31, 27, 31, 22, 22, + 20, 8, 74, 69, 63, 68, 81, 8, 36, 74, 132, 81, 61, 72, 81, 8, 23, 28, 26, 27, 20, + 8, 24, 27, 18, 8, 61, 82, 63, 68, 8, 84, 69, 79, 64, 8, 51, 75, 132, 69, 81, 69, + 75, 74, 8, 27, 8, 11, 97, 8, 23, 24, 8, 11, 8, 23, 30, 22, 30, 11, 8, 64, 65, + 79, 2, 41, 79, 61, 67, 65, 8, 65, 69, 74, 65, 8, 7, 8, 68, 61, 81, 81, 65, 20, + 8, 27, 24, 20, 8, 25, 22, 20, 8, 18, 97, 18, 8, 124, 63, 20, 8, 57, 75, 68, 74, + 82, 74, 67, 65, 74, 8, 84, 75, 79, 64, 65, 74, 20, 8, 23, 31, 24, 22, 8, 25, 8, + 83, 75, 73, 8, 23, 22, 22, 8, 62, 65, 69, 64, 65, 74, 2, 84, 65, 69, 102, 8, 43, + 61, 82, 80, 68, 61, 72, 81, 80, 19, 97, 8, 84, 82, 79, 64, 65, 8, 60, 82, 67, 65, + 87, 75, 67, 65, 74, 65, 74, 8, 23, 24, 20, 8, 6, 8, 66, 110, 79, 8, 66, 110, 79, + 8, 44, 45, 45, 8, 23, 26, 8, 97, 8, 22, 22, 22, 8, 23, 31, 23, 30, 29, 8, 61, + 62, 132, 75, 72, 82, 81, 65, 2, 84, 69, 79, 64, 8, 61, 82, 66, 79, 65, 63, 68, 81, + 8, 7, 8, 84, 69, 79, 8, 132, 81, 69, 65, 67, 8, 23, 31, 23, 28, 33, 8, 97, 8, + 64, 65, 74, 8, 60, 84, 65, 63, 71, 65, 8, 68, 61, 62, 65, 8, 23, 8, 18, 97, 8, + 42, 65, 62, 103, 82, 64, 65, 8, 27, 22, 22, 8, 74, 61, 63, 68, 2, 23, 23, 20, 8, + 132, 69, 65, 8, 97, 18, 8, 64, 69, 65, 8, 53, 81, 61, 64, 81, 8, 25, 30, 20, 8, + 27, 28, 8, 53, 63, 68, 82, 72, 64, 65, 78, 82, 81, 61, 81, 69, 75, 74, 8, 64, 65, + 74, 8, 66, 110, 79, 8, 28, 31, 8, 43, 65, 79, 79, 8, 23, 23, 22, 22, 8, 64, 65, + 80, 68, 61, 72, 62, 2, 64, 65, 79, 8, 36, 78, 79, 69, 72, 8, 21, 8, 45, 82, 74, + 69, 8, 24, 18, 8, 36, 62, 81, 65, 69, 72, 82, 74, 67, 8, 62, 65, 69, 64, 65, 74, + 8, 27, 24, 20, 8, 97, 75, 8, 65, 69, 74, 65, 73, 8, 61, 82, 63, 68, 8, 64, 69, + 65, 132, 65, 79, 8, 25, 27, 22, 8, 24, 97, 8, 124, 63, 20, 8, 26, 29, 8, 7, 8, + 84, 69, 65, 64, 65, 79, 2, 45, 61, 68, 79, 65, 8, 23, 30, 24, 30, 8, 97, 8, 64, + 65, 79, 8, 64, 65, 79, 8, 30, 30, 20, 8, 97, 8, 46, 109, 74, 69, 67, 72, 69, 63, + 68, 65, 74, 8, 45, 61, 68, 79, 8, 66, 110, 79, 8, 24, 26, 26, 26, 26, 8, 97, 8, + 65, 69, 74, 8, 23, 28, 24, 22, 22, 11, 8, 41, 79, 61, 67, 65, 2, 41, 65, 79, 74, + 79, 82, 66, 32, 8, 39, 61, 80, 8, 97, 8, 23, 31, 23, 29, 8, 48, 61, 67, 69, 132, + 81, 79, 61, 81, 8, 27, 29, 11, 8, 24, 18, 8, 39, 69, 65, 8, 64, 65, 80, 8, 36, + 62, 81, 75, 73, 73, 65, 74, 8, 30, 26, 8, 97, 8, 82, 132, 84, 20, 8, 29, 26, 8, + 11, 8, 67, 65, 73, 103, 102, 2, 74, 75, 63, 68, 8, 79, 82, 74, 64, 8, 97, 8, 82, + 74, 64, 8, 83, 65, 79, 132, 63, 68, 69, 65, 64, 65, 74, 65, 79, 8, 23, 11, 8, 62, + 65, 79, 8, 23, 22, 20, 8, 64, 61, 102, 8, 29, 27, 22, 8, 28, 8, 51, 66, 72, 69, + 63, 68, 81, 8, 23, 22, 22, 8, 62, 65, 87, 75, 67, 65, 74, 2, 64, 69, 65, 8, 49, + 65, 82, 66, 65, 79, 81, 8, 97, 8, 29, 22, 22, 22, 8, 45, 124, 20, 8, 23, 22, 30, + 26, 26, 33, 8, 7, 8, 23, 23, 22, 8, 132, 63, 68, 72, 61, 67, 65, 74, 8, 71, 72, + 65, 69, 74, 65, 74, 8, 24, 29, 8, 97, 8, 43, 65, 65, 79, 65, 80, 64, 69, 65, 74, + 132, 81, 65, 8, 25, 24, 11, 8, 64, 65, 79, 2, 65, 79, 68, 65, 62, 72, 69, 63, 68, + 8, 73, 65, 69, 74, 65, 74, 8, 83, 75, 74, 8, 39, 69, 65, 8, 26, 31, 20, 8, 23, + 30, 26, 28, 8, 82, 74, 64, 8, 37, 79, 75, 64, 65, 8, 28, 27, 24, 8, 97, 8, 23, + 31, 23, 29, 8, 31, 30, 8, 29, 8, 69, 132, 81, 2, 73, 109, 63, 68, 81, 65, 8, 23, + 24, 27, 22, 8, 97, 8, 84, 65, 72, 63, 68, 65, 74, 8, 64, 69, 65, 8, 24, 22, 22, + 18, 8, 97, 8, 64, 61, 80, 8, 56, 78, 80, 69, 72, 75, 74, 8, 73, 109, 63, 68, 81, + 65, 8, 30, 8, 29, 18, 8, 82, 74, 64, 8, 28, 30, 31, 21, 8, 87, 69, 65, 72, 65, + 74, 64, 65, 74, 2, 61, 62, 65, 79, 8, 64, 75, 63, 68, 8, 68, 69, 74, 65, 69, 74, + 67, 65, 81, 61, 74, 8, 43, 75, 66, 65, 8, 24, 22, 22, 22, 33, 8, 97, 8, 7, 37, + 65, 79, 61, 81, 82, 74, 67, 6, 8, 84, 65, 79, 64, 65, 74, 8, 124, 63, 20, 8, 28, + 27, 28, 8, 97, 8, 73, 61, 74, 8, 23, 29, 22, 11, 8, 65, 81, 84, 61, 80, 2, 52, + 75, 132, 63, 68, 65, 79, 132, 81, 79, 61, 102, 65, 8, 69, 132, 81, 8, 29, 97, 8, 84, + 65, 69, 81, 65, 79, 65, 80, 8, 64, 65, 79, 8, 30, 27, 22, 22, 11, 8, 11, 8, 83, + 75, 74, 8, 51, 65, 74, 87, 69, 67, 8, 64, 65, 79, 8, 23, 30, 31, 22, 8, 11, 8, + 40, 69, 74, 65, 8, 24, 29, 22, 8, 72, 65, 81, 87, 81, 65, 74, 2, 64, 65, 80, 8, + 132, 81, 79, 61, 102, 65, 8, 45, 9, 20, 8, 23, 22, 24, 32, 8, 24, 31, 18, 8, 97, + 8, 68, 61, 62, 65, 8, 83, 65, 79, 84, 61, 72, 81, 65, 81, 20, 8, 51, 79, 110, 66, + 82, 74, 67, 8, 23, 28, 25, 30, 8, 97, 8, 23, 22, 26, 8, 27, 30, 28, 24, 8, 7, + 11, 8, 68, 65, 82, 81, 65, 2, 61, 62, 65, 79, 8, 74, 82, 79, 8, 97, 8, 49, 79, + 20, 8, 73, 65, 81, 65, 79, 8, 25, 23, 18, 8, 7, 8, 83, 75, 74, 8, 132, 69, 63, + 68, 8, 61, 62, 65, 74, 64, 80, 8, 31, 8, 18, 98, 8, 69, 63, 68, 8, 23, 24, 8, + 6, 8, 53, 81, 65, 73, 78, 65, 72, 2, 68, 109, 68, 65, 79, 65, 8, 66, 75, 72, 67, + 65, 74, 64, 65, 32, 8, 7, 8, 64, 65, 79, 8, 82, 74, 64, 8, 23, 31, 22, 23, 20, + 8, 24, 18, 8, 65, 69, 74, 65, 8, 45, 61, 68, 79, 8, 61, 72, 72, 65, 79, 8, 23, + 23, 8, 27, 8, 84, 69, 79, 64, 8, 23, 22, 28, 24, 8, 29, 8, 64, 65, 80, 2, 53, + 81, 79, 20, 8, 132, 69, 63, 68, 8, 60, 65, 69, 63, 68, 74, 65, 79, 32, 8, 62, 65, + 69, 8, 29, 11, 8, 7, 8, 62, 65, 81, 79, 61, 63, 68, 81, 65, 74, 20, 6, 8, 64, + 69, 65, 8, 50, 22, 30, 20, 8, 30, 30, 8, 56, 61, 63, 68, 81, 8, 23, 27, 8, 29, + 8, 64, 69, 65, 2, 42, 65, 84, 69, 102, 8, 37, 65, 69, 8, 18, 8, 23, 31, 23, 22, + 21, 23, 27, 20, 8, 26, 24, 25, 8, 23, 22, 22, 18, 8, 97, 22, 8, 64, 61, 102, 8, + 132, 81, 79, 20, 8, 64, 65, 79, 8, 23, 22, 29, 29, 8, 24, 18, 8, 66, 110, 79, 8, + 30, 31, 31, 8, 7, 8, 61, 72, 72, 67, 65, 73, 65, 69, 74, 65, 2, 65, 79, 84, 75, + 79, 62, 65, 74, 20, 8, 57, 61, 74, 64, 65, 79, 82, 74, 67, 8, 7, 97, 8, 49, 65, + 82, 62, 61, 82, 8, 23, 30, 31, 31, 8, 23, 31, 22, 24, 20, 8, 97, 8, 61, 74, 67, + 65, 73, 65, 132, 132, 65, 74, 8, 82, 74, 64, 8, 82, 74, 64, 8, 26, 31, 8, 18, 96, + 98, 8, 83, 75, 74, 8, 25, 23, 22, 22, 8, 84, 69, 79, 64, 20, 2, 87, 84, 65, 69, + 8, 64, 69, 65, 8, 97, 8, 65, 69, 74, 65, 73, 8, 64, 65, 79, 8, 23, 30, 29, 25, + 11, 8, 97, 8, 23, 30, 26, 8, 42, 65, 62, 69, 65, 81, 65, 74, 8, 7, 64, 61, 79, + 110, 62, 65, 79, 6, 8, 27, 30, 8, 97, 8, 61, 82, 80, 8, 24, 22, 8, 66, 82, 79, + 63, 68, 81, 62, 61, 79, 2, 23, 30, 31, 31, 8, 64, 61, 79, 110, 62, 65, 79, 8, 36, + 82, 80, 67, 61, 62, 65, 74, 8, 49, 65, 82, 62, 61, 82, 8, 28, 27, 22, 22, 8, 29, + 21, 8, 39, 69, 74, 67, 65, 8, 36, 74, 79, 82, 66, 65, 74, 8, 75, 64, 65, 79, 8, + 23, 23, 8, 97, 8, 23, 30, 29, 29, 8, 24, 22, 22, 22, 8, 11, 8, 46, 109, 74, 69, + 67, 69, 74, 2, 64, 61, 67, 65, 67, 65, 74, 8, 132, 75, 74, 64, 65, 79, 74, 8, 97, + 8, 64, 69, 65, 8, 70, 65, 64, 75, 63, 68, 8, 23, 30, 27, 27, 20, 8, 24, 18, 8, + 56, 65, 79, 67, 72, 65, 69, 63, 68, 8, 53, 69, 81, 87, 82, 74, 67, 8, 64, 65, 79, + 8, 28, 31, 8, 97, 8, 132, 81, 69, 65, 67, 8, 23, 31, 22, 27, 22, 11, 8, 64, 61, + 80, 2, 132, 78, 79, 65, 63, 68, 65, 8, 69, 63, 68, 8, 18, 8, 25, 30, 26, 8, 27, + 30, 28, 24, 8, 25, 27, 8, 7, 29, 8, 24, 8, 29, 25, 28, 8, 75, 64, 65, 79, 8, + 53, 61, 73, 73, 65, 72, 68, 65, 69, 87, 82, 74, 67, 8, 24, 23, 8, 27, 8, 83, 65, + 79, 62, 69, 74, 64, 65, 74, 8, 23, 30, 31, 25, 22, 8, 74, 75, 63, 68, 2, 22, 30, + 20, 8, 62, 65, 84, 69, 72, 72, 69, 67, 81, 8, 11, 8, 23, 28, 25, 31, 8, 65, 69, + 74, 81, 79, 65, 81, 65, 74, 8, 23, 22, 27, 26, 22, 11, 8, 97, 8, 68, 61, 62, 65, + 74, 8, 64, 65, 79, 8, 27, 27, 20, 8, 23, 28, 26, 27, 8, 18, 8, 7, 39, 65, 79, + 8, 25, 22, 22, 21, 8, 36, 74, 132, 78, 79, 110, 63, 68, 65, 74, 2, 56, 65, 79, 19, + 4, 42, 79, 82, 74, 64, 61, 82, 66, 8, 7, 8, 67, 65, 68, 65, 74, 64, 8, 64, 65, + 73, 8, 23, 24, 11, 8, 57, 75, 68, 74, 82, 74, 67, 65, 74, 8, 25, 25, 26, 8, 67, + 65, 78, 79, 110, 66, 81, 65, 74, 8, 23, 31, 23, 27, 8, 97, 8, 23, 31, 24, 22, 8, + 23, 31, 23, 28, 8, 23, 27, 22, 2, 53, 69, 65, 8, 62, 65, 19, 97, 8, 36, 74, 67, + 65, 72, 65, 67, 65, 74, 68, 65, 69, 81, 65, 74, 8, 42, 79, 75, 72, 73, 61, 74, 19, + 23, 31, 24, 22, 20, 8, 97, 8, 124, 63, 20, 8, 23, 22, 22, 8, 64, 65, 79, 8, 28, + 29, 8, 97, 8, 66, 110, 79, 8, 23, 25, 22, 24, 8, 29, 8, 64, 69, 65, 2, 65, 69, + 74, 87, 65, 72, 74, 65, 74, 8, 65, 69, 74, 132, 65, 69, 81, 69, 19, 8, 83, 65, 79, + 81, 79, 61, 67, 8, 64, 65, 79, 8, 30, 26, 20, 8, 31, 8, 67, 82, 81, 65, 79, 8, + 73, 69, 81, 8, 69, 132, 81, 8, 23, 30, 24, 28, 8, 97, 8, 23, 30, 26, 25, 8, 23, + 26, 8, 29, 8, 65, 79, 67, 65, 62, 65, 74, 20, 2, 70, 65, 64, 75, 63, 68, 8, 87, + 82, 79, 8, 7, 97, 8, 51, 79, 110, 66, 82, 74, 67, 8, 46, 79, 69, 65, 67, 65, 8, + 23, 24, 24, 26, 20, 8, 97, 8, 84, 69, 65, 8, 83, 75, 73, 8, 64, 65, 79, 8, 23, + 23, 8, 24, 15, 8, 67, 65, 81, 79, 75, 66, 66, 65, 74, 65, 8, 31, 22, 8, 84, 65, + 69, 72, 2, 64, 61, 102, 8, 36, 73, 75, 79, 81, 69, 132, 61, 81, 69, 75, 74, 8, 97, + 8, 48, 61, 102, 65, 8, 64, 65, 79, 8, 23, 29, 22, 33, 8, 21, 8, 23, 26, 27, 8, + 57, 65, 69, 132, 65, 8, 83, 75, 74, 8, 27, 29, 8, 41, 103, 72, 72, 65, 74, 8, 29, + 22, 22, 22, 22, 11, 8, 82, 74, 64, 2, 87, 82, 73, 8, 64, 65, 79, 8, 24, 8, 52, + 61, 81, 68, 61, 82, 132, 65, 80, 8, 64, 69, 65, 8, 24, 18, 8, 64, 65, 79, 8, 61, + 82, 66, 8, 87, 82, 79, 8, 28, 27, 22, 8, 97, 8, 84, 65, 79, 64, 65, 74, 8, 23, + 29, 28, 27, 22, 8, 61, 82, 66, 79, 65, 63, 68, 81, 2, 37, 65, 87, 69, 65, 68, 82, + 74, 67, 8, 82, 74, 132, 65, 79, 73, 8, 69, 68, 79, 65, 79, 8, 82, 74, 64, 8, 23, + 31, 20, 8, 97, 8, 64, 65, 79, 8, 82, 74, 64, 8, 83, 65, 79, 87, 69, 63, 68, 81, + 65, 74, 8, 30, 22, 8, 29, 8, 65, 74, 81, 132, 78, 79, 69, 63, 68, 81, 8, 31, 31, + 22, 11, 8, 82, 74, 80, 2, 82, 74, 80, 8, 87, 82, 79, 8, 0, 5, 8, 67, 65, 74, + 8, 53, 63, 68, 110, 72, 65, 79, 8, 23, 28, 22, 22, 11, 8, 97, 8, 132, 75, 84, 69, + 65, 8, 64, 61, 79, 82, 73, 8, 52, 65, 61, 72, 132, 63, 68, 82, 72, 65, 8, 23, 31, + 24, 22, 8, 97, 8, 64, 82, 79, 63, 68, 8, 24, 22, 22, 22, 8, 6, 8, 61, 74, 64, + 65, 79, 65, 2, 64, 61, 102, 8, 64, 65, 79, 67, 72, 65, 69, 63, 68, 65, 74, 8, 7, + 8, 132, 69, 63, 68, 8, 62, 69, 80, 8, 24, 28, 22, 22, 18, 8, 18, 18, 8, 45, 82, + 74, 69, 8, 79, 82, 74, 64, 8, 62, 65, 69, 73, 8, 31, 8, 97, 8, 57, 75, 68, 74, + 82, 74, 67, 65, 74, 8, 23, 22, 22, 8, 83, 75, 74, 2, 62, 72, 69, 65, 62, 8, 74, + 61, 63, 68, 8, 79, 82, 74, 64, 8, 71, 109, 74, 74, 65, 74, 8, 26, 20, 8, 97, 8, + 64, 65, 73, 8, 48, 61, 79, 71, 8, 84, 65, 69, 72, 8, 30, 8, 97, 60, 8, 66, 110, + 79, 8, 23, 30, 31, 23, 8, 29, 8, 132, 79, 61, 102, 65, 2, 72, 65, 81, 87, 81, 65, + 74, 8, 36, 74, 81, 79, 61, 67, 8, 98, 97, 8, 64, 61, 79, 82, 73, 8, 23, 31, 23, + 22, 21, 23, 27, 20, 8, 28, 29, 22, 11, 8, 97, 8, 64, 69, 65, 132, 65, 73, 8, 45, + 61, 68, 79, 8, 69, 63, 68, 8, 25, 29, 8, 97, 8, 65, 69, 74, 65, 74, 8, 24, 22, + 27, 22, 22, 11, 8, 53, 81, 65, 82, 65, 79, 74, 2, 47, 65, 68, 79, 78, 72, 103, 74, + 65, 74, 8, 23, 27, 23, 24, 8, 97, 8, 36, 79, 87, 81, 8, 64, 65, 79, 8, 23, 30, + 22, 31, 20, 8, 97, 8, 124, 63, 20, 8, 51, 65, 79, 132, 75, 74, 8, 65, 74, 81, 132, + 63, 68, 65, 69, 64, 65, 74, 64, 65, 8, 26, 22, 8, 97, 8, 74, 61, 63, 68, 8, 23, + 30, 31, 31, 31, 8, 64, 65, 79, 2, 36, 62, 84, 61, 74, 64, 65, 79, 82, 74, 67, 8, + 56, 75, 79, 64, 65, 79, 68, 61, 82, 132, 65, 80, 8, 7, 97, 8, 64, 65, 73, 8, 70, + 65, 64, 65, 79, 8, 23, 31, 22, 23, 20, 8, 22, 22, 20, 8, 64, 69, 65, 8, 64, 69, + 65, 8, 30, 24, 8, 97, 8, 74, 61, 63, 68, 8, 26, 22, 22, 8, 7, 48, 103, 74, 74, + 65, 79, 6, 2, 64, 65, 73, 8, 84, 69, 79, 8, 27, 28, 8, 83, 75, 74, 8, 84, 65, + 72, 63, 68, 65, 8, 23, 31, 20, 8, 24, 15, 8, 64, 69, 65, 8, 74, 61, 63, 68, 8, + 87, 69, 74, 132, 82, 74, 67, 8, 25, 22, 8, 29, 8, 61, 62, 65, 79, 8, 23, 30, 27, + 22, 22, 11, 11, 8, 83, 75, 74, 2, 82, 74, 64, 8, 45, 82, 74, 67, 65, 74, 8, 7, + 8, 64, 65, 73, 8, 62, 69, 80, 8, 23, 22, 22, 18, 8, 98, 8, 36, 74, 132, 78, 79, + 82, 63, 68, 8, 64, 65, 79, 8, 64, 61, 79, 82, 73, 8, 23, 22, 8, 61, 82, 63, 68, + 8, 25, 8, 53, 63, 68, 69, 72, 72, 65, 79, 19, 54, 68, 65, 61, 81, 65, 79, 2, 81, + 65, 74, 62, 82, 79, 67, 8, 23, 24, 22, 11, 8, 97, 8, 23, 24, 8, 7, 29, 8, 7, + 57, 61, 68, 79, 72, 69, 63, 68, 9, 6, 8, 30, 30, 20, 8, 24, 18, 8, 62, 69, 80, + 8, 73, 109, 63, 68, 81, 65, 8, 73, 61, 74, 63, 68, 73, 61, 72, 8, 23, 30, 8, 97, + 8, 87, 82, 72, 65, 67, 65, 74, 8, 25, 26, 22, 11, 8, 73, 61, 63, 68, 65, 2, 64, + 65, 74, 8, 24, 27, 22, 22, 8, 97, 8, 82, 74, 64, 8, 65, 79, 66, 79, 61, 67, 65, + 74, 20, 8, 23, 22, 20, 8, 31, 21, 8, 69, 68, 79, 65, 8, 132, 75, 84, 69, 65, 8, + 64, 69, 65, 8, 23, 30, 29, 31, 8, 97, 8, 65, 79, 132, 81, 65, 74, 73, 75, 72, 8, + 23, 30, 31, 30, 31, 21, 8, 23, 30, 23, 23, 2, 83, 75, 73, 8, 51, 79, 75, 70, 65, + 71, 81, 8, 27, 18, 8, 69, 132, 81, 8, 84, 110, 74, 132, 63, 68, 65, 74, 80, 84, 65, + 79, 81, 8, 23, 24, 18, 8, 7, 8, 75, 64, 65, 79, 8, 132, 69, 74, 64, 20, 8, 24, + 30, 26, 8, 23, 31, 23, 30, 8, 24, 18, 8, 52, 65, 69, 68, 65, 8, 24, 22, 11, 22, + 8, 48, 65, 69, 74, 82, 74, 67, 2, 68, 61, 62, 65, 74, 8, 67, 65, 68, 65, 74, 8, + 64, 65, 79, 8, 44, 72, 20, 8, 29, 25, 11, 8, 24, 26, 26, 26, 26, 8, 84, 103, 72, + 64, 65, 79, 8, 39, 69, 65, 8, 23, 30, 27, 23, 8, 83, 75, 73, 8, 24, 29, 22, 8, + 37, 65, 81, 65, 69, 72, 69, 67, 81, 65, 74, 2, 24, 27, 22, 22, 8, 30, 22, 22, 22, + 8, 7, 8, 65, 69, 74, 67, 65, 66, 110, 68, 79, 81, 20, 8, 54, 65, 74, 75, 79, 8, + 24, 27, 20, 8, 7, 8, 65, 69, 74, 8, 87, 82, 132, 81, 103, 74, 64, 69, 67, 65, 74, + 8, 64, 61, 79, 82, 73, 8, 23, 31, 22, 23, 8, 97, 8, 64, 65, 73, 8, 23, 31, 22, + 26, 31, 18, 8, 39, 65, 74, 2, 65, 69, 74, 65, 73, 8, 64, 65, 79, 8, 97, 8, 23, + 31, 22, 24, 8, 65, 69, 74, 65, 8, 23, 30, 26, 22, 18, 8, 25, 28, 8, 39, 69, 65, + 8, 37, 65, 132, 63, 68, 72, 82, 102, 8, 84, 61, 79, 65, 74, 8, 23, 24, 8, 24, 18, + 8, 64, 65, 79, 8, 27, 22, 22, 11, 8, 64, 69, 65, 2, 84, 82, 79, 64, 65, 8, 65, + 69, 74, 65, 80, 8, 27, 28, 8, 62, 82, 79, 67, 65, 79, 8, 64, 69, 65, 8, 28, 27, + 22, 22, 11, 8, 11, 8, 25, 22, 22, 8, 74, 69, 73, 73, 81, 8, 64, 65, 80, 68, 61, + 72, 62, 8, 25, 22, 22, 8, 24, 97, 8, 23, 22, 27, 24, 8, 24, 22, 8, 29, 8, 53, + 63, 68, 82, 72, 65, 74, 2, 83, 75, 73, 8, 64, 65, 79, 8, 7, 8, 64, 65, 79, 8, + 84, 69, 79, 64, 20, 6, 8, 30, 30, 20, 8, 27, 26, 31, 8, 23, 31, 23, 30, 8, 23, + 27, 24, 25, 8, 23, 8, 27, 28, 8, 44, 74, 81, 65, 79, 65, 132, 132, 65, 74, 132, 81, + 61, 74, 64, 78, 82, 74, 71, 81, 8, 25, 22, 22, 8, 67, 65, 68, 65, 74, 2, 64, 69, + 65, 8, 84, 61, 79, 8, 44, 74, 132, 81, 61, 72, 72, 61, 81, 69, 75, 74, 8, 64, 65, + 79, 8, 23, 25, 25, 24, 97, 8, 64, 69, 65, 8, 27, 22, 22, 22, 8, 64, 65, 80, 8, + 30, 30, 30, 8, 97, 8, 73, 69, 81, 8, 23, 26, 22, 22, 8, 61, 62, 65, 79, 2, 72, + 65, 81, 87, 81, 65, 79, 65, 73, 8, 51, 65, 74, 87, 69, 67, 8, 18, 97, 8, 64, 65, + 79, 8, 65, 79, 68, 109, 68, 65, 74, 8, 23, 27, 11, 8, 24, 8, 132, 65, 69, 8, 84, + 65, 72, 63, 68, 65, 8, 65, 69, 74, 65, 80, 8, 26, 24, 8, 97, 8, 39, 61, 80, 8, + 27, 22, 22, 22, 8, 39, 61, 80, 2, 74, 69, 63, 68, 81, 8, 62, 69, 80, 8, 75, 8, + 73, 69, 81, 8, 59, 78, 80, 69, 72, 75, 74, 8, 29, 24, 20, 8, 97, 8, 53, 81, 103, + 64, 81, 65, 8, 132, 65, 81, 87, 65, 74, 8, 31, 30, 27, 8, 24, 22, 8, 97, 8, 64, + 82, 79, 63, 68, 8, 29, 30, 29, 8, 53, 63, 68, 82, 72, 64, 65, 78, 82, 81, 61, 81, + 69, 75, 74, 2, 64, 65, 74, 8, 23, 24, 27, 22, 8, 97, 8, 73, 65, 68, 79, 8, 48, + 75, 73, 73, 132, 65, 74, 19, 25, 22, 22, 22, 18, 8, 27, 28, 8, 56, 75, 79, 132, 81, + 65, 68, 65, 79, 8, 49, 75, 74, 74, 65, 74, 64, 61, 73, 73, 8, 45, 80, 20, 8, 23, + 31, 23, 29, 8, 97, 8, 64, 61, 79, 61, 74, 8, 27, 26, 22, 22, 8, 75, 62, 3, 2, + 52, 65, 69, 68, 65, 74, 20, 8, 62, 65, 71, 61, 73, 65, 74, 20, 8, 24, 15, 8, 68, + 65, 79, 87, 82, 19, 8, 73, 69, 81, 8, 23, 30, 31, 23, 22, 11, 22, 8, 7, 8, 61, + 82, 66, 8, 61, 82, 80, 8, 64, 61, 79, 82, 73, 8, 23, 30, 31, 22, 8, 27, 21, 28, + 8, 64, 75, 63, 68, 8, 23, 30, 24, 27, 29, 8, 45, 81, 124, 20, 2, 82, 74, 64, 8, + 25, 24, 31, 21, 24, 25, 8, 97, 8, 61, 72, 80, 8, 37, 65, 81, 81, 65, 74, 8, 23, + 30, 30, 25, 18, 8, 97, 8, 55, 74, 81, 65, 79, 62, 61, 82, 8, 64, 61, 80, 8, 67, + 65, 132, 81, 61, 72, 81, 65, 81, 65, 8, 30, 25, 8, 50, 79, 64, 20, 8, 23, 31, 23, + 28, 8, 64, 65, 80, 2, 64, 65, 79, 8, 37, 65, 81, 79, 61, 67, 8, 27, 28, 8, 43, + 110, 72, 66, 65, 8, 61, 82, 80, 8, 23, 31, 22, 30, 18, 8, 18, 8, 64, 65, 79, 8, + 56, 65, 79, 84, 61, 72, 81, 82, 74, 67, 20, 8, 64, 65, 80, 8, 23, 30, 31, 23, 8, + 18, 97, 8, 61, 82, 66, 87, 82, 84, 65, 74, 64, 65, 74, 8, 23, 23, 22, 8, 51, 82, + 81, 87, 20, 2, 64, 65, 80, 8, 23, 30, 28, 27, 8, 24, 18, 8, 83, 75, 74, 8, 46, + 79, 65, 69, 80, 132, 63, 68, 82, 72, 19, 24, 24, 33, 8, 97, 8, 82, 74, 64, 8, 7, + 132, 63, 68, 65, 69, 74, 81, 6, 8, 64, 69, 65, 8, 23, 27, 8, 97, 8, 83, 75, 79, + 19, 24, 27, 22, 8, 7, 8, 73, 65, 68, 79, 2, 132, 63, 68, 72, 69, 65, 102, 72, 69, + 63, 68, 8, 64, 69, 65, 8, 7, 18, 8, 64, 69, 65, 132, 65, 8, 132, 69, 65, 8, 23, + 27, 11, 8, 97, 21, 8, 84, 65, 72, 63, 68, 65, 8, 68, 61, 74, 64, 65, 72, 81, 8, + 61, 62, 65, 79, 8, 25, 25, 8, 97, 8, 23, 30, 28, 27, 8, 29, 8, 37, 65, 3, 2, + 82, 74, 64, 8, 61, 72, 80, 8, 24, 8, 132, 81, 103, 64, 81, 69, 132, 63, 68, 65, 79, + 132, 65, 69, 81, 80, 8, 132, 81, 79, 20, 8, 24, 11, 8, 18, 97, 8, 73, 69, 81, 8, + 132, 69, 63, 68, 8, 65, 69, 74, 65, 8, 23, 30, 27, 27, 8, 27, 21, 8, 48, 69, 72, + 72, 69, 75, 74, 65, 74, 8, 27, 31, 22, 22, 8, 64, 65, 79, 2, 64, 61, 79, 82, 73, + 8, 39, 79, 65, 68, 62, 110, 68, 74, 65, 8, 62, 65, 69, 8, 68, 61, 62, 65, 74, 8, + 23, 30, 27, 23, 18, 8, 7, 8, 40, 69, 74, 87, 69, 67, 8, 61, 82, 63, 68, 8, 82, + 74, 80, 8, 30, 30, 8, 18, 97, 8, 82, 74, 64, 8, 24, 31, 22, 8, 42, 79, 75, 72, + 73, 61, 74, 3, 2, 64, 65, 73, 8, 36, 82, 66, 132, 69, 63, 68, 81, 8, 7, 8, 70, + 65, 74, 65, 80, 8, 47, 61, 67, 65, 79, 78, 72, 61, 81, 87, 8, 24, 24, 33, 8, 97, + 8, 64, 65, 73, 8, 31, 27, 22, 22, 20, 8, 83, 75, 73, 8, 23, 31, 24, 22, 8, 97, + 8, 83, 65, 79, 19, 23, 23, 31, 22, 22, 8, 73, 65, 68, 79, 2, 67, 65, 74, 61, 82, + 8, 73, 61, 74, 63, 68, 73, 61, 72, 8, 97, 96, 22, 8, 84, 69, 79, 8, 61, 82, 80, + 8, 31, 20, 8, 7, 87, 84, 69, 132, 63, 68, 65, 74, 6, 8, 62, 69, 80, 8, 71, 61, + 74, 74, 20, 8, 29, 27, 8, 24, 8, 64, 69, 65, 8, 25, 24, 27, 22, 22, 11, 8, 60, + 69, 73, 73, 65, 79, 2, 22, 22, 22, 8, 64, 65, 73, 8, 97, 8, 56, 65, 79, 62, 69, + 74, 64, 82, 74, 67, 8, 43, 82, 67, 75, 8, 23, 24, 22, 22, 20, 8, 97, 8, 64, 69, + 65, 8, 75, 64, 65, 79, 8, 36, 74, 79, 82, 66, 65, 74, 8, 30, 24, 8, 64, 69, 65, + 8, 24, 23, 22, 22, 22, 8, 83, 69, 65, 72, 72, 65, 69, 63, 68, 81, 2, 64, 65, 79, + 74, 8, 132, 75, 84, 65, 69, 81, 8, 97, 8, 65, 79, 132, 81, 65, 74, 8, 124, 63, 20, + 8, 25, 28, 25, 20, 8, 18, 97, 8, 65, 69, 74, 65, 8, 23, 30, 22, 31, 8, 56, 65, + 79, 132, 81, 103, 79, 71, 82, 74, 67, 8, 23, 30, 31, 31, 8, 24, 18, 8, 23, 24, 20, + 8, 31, 8, 29, 8, 83, 75, 74, 2, 7, 51, 61, 79, 87, 65, 72, 72, 65, 74, 6, 8, + 69, 132, 81, 8, 28, 8, 67, 61, 74, 87, 8, 64, 61, 80, 8, 23, 30, 26, 22, 20, 8, + 84, 82, 79, 64, 65, 8, 37, 86, 71, 8, 82, 74, 64, 8, 28, 29, 8, 27, 28, 8, 42, + 79, 82, 74, 64, 8, 24, 29, 22, 22, 8, 23, 31, 22, 30, 21, 23, 25, 20, 2, 68, 65, + 66, 81, 8, 83, 75, 74, 8, 97, 8, 73, 69, 79, 8, 47, 61, 73, 78, 65, 74, 8, 23, + 30, 29, 30, 11, 8, 97, 8, 22, 22, 22, 8, 41, 65, 79, 74, 65, 79, 8, 23, 29, 26, + 8, 25, 22, 8, 27, 28, 8, 64, 61, 102, 8, 25, 23, 8, 29, 8, 132, 75, 72, 72, 2, + 42, 79, 75, 102, 19, 37, 65, 79, 72, 69, 74, 8, 65, 69, 74, 73, 61, 72, 8, 7, 97, + 8, 44, 45, 44, 45, 8, 82, 74, 64, 8, 23, 22, 20, 8, 97, 8, 46, 109, 74, 69, 67, + 19, 44, 45, 44, 45, 8, 61, 82, 66, 8, 23, 26, 22, 8, 97, 8, 25, 22, 20, 8, 24, + 27, 22, 8, 29, 8, 64, 69, 65, 2, 48, 103, 64, 63, 68, 65, 74, 8, 84, 65, 79, 64, + 65, 74, 8, 97, 8, 64, 69, 65, 8, 43, 61, 82, 80, 84, 61, 79, 81, 80, 8, 23, 24, + 20, 8, 27, 21, 8, 83, 75, 79, 19, 23, 30, 29, 31, 8, 39, 61, 79, 110, 62, 65, 79, + 8, 23, 30, 31, 28, 8, 18, 8, 83, 75, 74, 8, 25, 28, 25, 22, 22, 8, 65, 79, 67, + 61, 62, 2, 45, 61, 66, 66, 104, 8, 31, 24, 8, 11, 8, 24, 21, 8, 61, 82, 63, 68, + 8, 61, 82, 66, 8, 30, 29, 20, 8, 27, 21, 8, 75, 64, 65, 79, 8, 36, 74, 72, 65, + 69, 68, 65, 8, 65, 81, 84, 61, 69, 67, 65, 74, 8, 30, 22, 22, 8, 29, 18, 8, 84, + 65, 69, 72, 8, 25, 26, 30, 25, 30, 22, 21, 8, 64, 65, 79, 2, 53, 65, 69, 81, 8, + 61, 74, 64, 65, 79, 65, 79, 8, 7, 8, 83, 75, 73, 8, 72, 69, 65, 68, 65, 74, 8, + 24, 18, 8, 97, 75, 22, 8, 64, 61, 80, 8, 23, 23, 25, 21, 23, 25, 8, 24, 22, 22, + 22, 8, 23, 26, 23, 8, 97, 8, 64, 69, 65, 8, 23, 24, 22, 22, 8, 64, 65, 79, 70, + 65, 74, 69, 67, 65, 74, 2, 64, 61, 80, 8, 64, 61, 102, 8, 7, 97, 8, 48, 61, 74, + 67, 65, 72, 8, 84, 69, 79, 64, 8, 23, 29, 28, 31, 11, 8, 7, 8, 23, 22, 22, 8, + 81, 79, 75, 81, 87, 8, 64, 65, 74, 8, 28, 23, 8, 97, 8, 83, 65, 79, 62, 69, 74, + 64, 65, 74, 8, 23, 30, 25, 27, 22, 8, 61, 72, 132, 75, 2, 74, 69, 63, 68, 81, 8, + 84, 65, 72, 63, 68, 65, 8, 64, 61, 80, 8, 84, 65, 69, 63, 68, 81, 8, 23, 24, 27, + 33, 8, 97, 8, 64, 65, 79, 8, 23, 30, 31, 25, 8, 132, 63, 68, 79, 69, 66, 81, 65, + 74, 8, 23, 23, 8, 98, 8, 24, 30, 20, 8, 24, 30, 26, 8, 29, 8, 68, 69, 65, 132, + 69, 67, 65, 74, 2, 60, 82, 132, 63, 68, 82, 102, 8, 64, 65, 73, 67, 65, 73, 103, 102, + 8, 24, 21, 8, 64, 65, 80, 8, 65, 69, 74, 65, 79, 8, 31, 20, 8, 27, 18, 8, 83, + 75, 79, 68, 61, 74, 64, 65, 74, 8, 67, 65, 72, 65, 67, 65, 74, 8, 83, 75, 72, 72, + 65, 74, 8, 27, 8, 29, 7, 8, 39, 61, 80, 8, 27, 28, 22, 21, 8, 65, 79, 68, 61, + 72, 81, 65, 74, 2, 23, 28, 8, 29, 8, 55, 68, 79, 8, 29, 8, 28, 28, 8, 29, 8, + 82, 74, 64, 8, 27, 18, 8, 56, 8, 84, 69, 79, 8, 65, 69, 74, 8, 68, 69, 74, 87, + 82, 87, 82, 66, 110, 67, 65, 74, 8, 23, 25, 30, 23, 8, 29, 18, 28, 8, 82, 74, 64, + 8, 24, 22, 22, 8, 64, 61, 80, 2, 47, 61, 67, 65, 79, 78, 72, 61, 81, 87, 19, 56, + 75, 79, 132, 81, 65, 68, 65, 79, 32, 8, 75, 64, 65, 79, 8, 29, 8, 132, 69, 74, 64, + 8, 64, 65, 79, 8, 28, 31, 20, 8, 18, 8, 110, 62, 65, 79, 8, 64, 69, 65, 8, 84, + 103, 79, 65, 74, 8, 26, 22, 8, 22, 22, 22, 8, 23, 22, 22, 8, 132, 69, 63, 68, 2, + 132, 69, 74, 64, 8, 40, 79, 72, 61, 102, 8, 97, 8, 61, 82, 66, 8, 82, 74, 64, 8, + 23, 30, 29, 22, 20, 8, 7, 8, 64, 65, 79, 8, 82, 74, 64, 8, 109, 81, 69, 67, 65, + 8, 23, 30, 28, 28, 8, 64, 69, 65, 8, 25, 22, 22, 22, 22, 11, 8, 87, 75, 67, 65, + 74, 2, 65, 79, 109, 66, 66, 74, 65, 81, 20, 8, 53, 81, 61, 74, 64, 78, 82, 74, 71, + 81, 8, 18, 8, 22, 22, 22, 8, 48, 61, 69, 8, 23, 30, 30, 30, 20, 8, 97, 8, 75, + 68, 74, 65, 8, 132, 69, 63, 68, 8, 64, 61, 102, 8, 25, 27, 8, 27, 28, 8, 84, 65, + 79, 64, 65, 74, 20, 8, 23, 30, 31, 30, 22, 11, 8, 36, 62, 62, 61, 82, 2, 132, 75, + 72, 63, 68, 65, 74, 8, 53, 63, 68, 84, 69, 65, 79, 69, 67, 71, 65, 69, 81, 65, 74, + 8, 27, 28, 8, 64, 69, 65, 132, 65, 74, 8, 57, 75, 68, 74, 82, 74, 67, 65, 74, 8, + 31, 20, 8, 27, 8, 23, 26, 22, 8, 22, 22, 22, 8, 44, 63, 68, 8, 28, 8, 21, 8, + 23, 22, 20, 8, 31, 22, 11, 8, 24, 22, 22, 2, 84, 65, 79, 64, 65, 74, 8, 74, 69, + 63, 68, 81, 8, 7, 8, 54, 65, 69, 72, 62, 65, 81, 79, 61, 67, 8, 42, 65, 64, 61, + 74, 71, 65, 74, 8, 25, 22, 22, 22, 11, 8, 27, 8, 64, 69, 65, 8, 70, 82, 74, 67, + 65, 8, 61, 82, 63, 68, 8, 23, 8, 97, 8, 61, 82, 66, 8, 23, 30, 24, 28, 8, 23, + 23, 26, 20, 2, 132, 65, 69, 74, 65, 8, 22, 30, 20, 8, 97, 8, 64, 69, 65, 8, 82, + 74, 64, 8, 28, 27, 28, 18, 8, 97, 8, 82, 74, 64, 8, 54, 61, 67, 65, 80, 75, 79, + 64, 74, 82, 74, 67, 32, 8, 64, 65, 79, 8, 23, 23, 8, 97, 8, 64, 65, 79, 8, 23, + 24, 27, 22, 8, 61, 72, 132, 75, 2, 64, 65, 80, 68, 61, 72, 62, 8, 67, 61, 74, 87, + 8, 7, 8, 84, 69, 79, 8, 74, 82, 79, 8, 24, 22, 22, 22, 18, 8, 24, 18, 8, 87, + 82, 73, 8, 23, 30, 30, 25, 8, 65, 69, 74, 8, 24, 22, 8, 97, 18, 8, 84, 65, 79, + 64, 65, 74, 20, 6, 8, 23, 22, 22, 8, 37, 61, 74, 67, 72, 61, 64, 65, 132, 63, 68, + 2, 53, 69, 65, 8, 69, 63, 68, 8, 7, 8, 83, 75, 73, 8, 64, 65, 79, 8, 26, 31, + 31, 33, 8, 29, 18, 8, 53, 69, 65, 8, 79, 82, 74, 64, 8, 23, 25, 26, 24, 8, 31, + 22, 8, 37, 61, 72, 71, 65, 8, 27, 22, 22, 22, 22, 8, 82, 74, 81, 65, 79, 79, 69, + 63, 68, 81, 65, 81, 20, 2, 84, 65, 80, 68, 61, 72, 62, 8, 64, 65, 80, 8, 62, 65, + 132, 63, 68, 72, 69, 65, 102, 81, 8, 37, 65, 61, 73, 81, 65, 74, 8, 23, 24, 22, 22, + 20, 8, 53, 82, 73, 73, 65, 20, 8, 23, 30, 24, 24, 8, 84, 65, 132, 65, 74, 81, 19, + 23, 24, 22, 22, 8, 7, 8, 64, 65, 79, 8, 23, 24, 22, 22, 8, 64, 65, 79, 2, 42, + 79, 65, 64, 86, 8, 66, 110, 79, 8, 18, 8, 23, 30, 27, 27, 8, 84, 103, 63, 68, 132, + 81, 8, 23, 30, 27, 30, 20, 8, 18, 97, 8, 61, 82, 66, 8, 36, 82, 63, 68, 8, 37, + 65, 102, 65, 79, 8, 23, 31, 24, 22, 8, 46, 61, 73, 62, 61, 63, 68, 8, 29, 22, 22, + 8, 39, 69, 65, 2, 36, 74, 84, 65, 132, 65, 74, 68, 65, 69, 81, 8, 64, 61, 87, 82, + 8, 7, 8, 82, 74, 64, 8, 23, 30, 29, 31, 8, 23, 31, 23, 27, 20, 11, 8, 59, 61, + 74, 67, 8, 41, 75, 74, 64, 80, 8, 82, 74, 64, 8, 25, 22, 22, 22, 8, 24, 11, 8, + 49, 61, 73, 65, 74, 6, 8, 23, 24, 22, 22, 22, 22, 8, 23, 31, 23, 28, 2, 67, 61, + 74, 87, 8, 84, 65, 79, 64, 65, 74, 8, 64, 61, 102, 8, 66, 82, 79, 63, 68, 81, 62, + 61, 79, 8, 23, 24, 22, 22, 20, 8, 29, 18, 8, 82, 74, 64, 8, 24, 24, 20, 8, 64, + 65, 73, 8, 23, 25, 8, 97, 8, 87, 82, 79, 8, 29, 8, 29, 8, 65, 69, 74, 65, 80, + 2, 64, 65, 73, 8, 64, 65, 79, 8, 25, 28, 25, 8, 64, 65, 73, 8, 23, 22, 22, 18, + 8, 97, 8, 23, 30, 20, 8, 30, 22, 22, 22, 8, 52, 61, 81, 68, 68, 61, 82, 132, 65, + 80, 8, 23, 22, 29, 28, 8, 97, 8, 64, 65, 74, 8, 31, 31, 31, 22, 21, 22, 8, 82, + 74, 64, 2, 69, 79, 67, 65, 74, 64, 84, 65, 72, 63, 68, 65, 74, 8, 87, 82, 79, 8, + 7, 8, 70, 65, 74, 69, 67, 65, 74, 8, 53, 81, 79, 20, 8, 24, 22, 20, 8, 97, 8, + 62, 65, 81, 79, 103, 67, 81, 8, 23, 30, 31, 22, 8, 65, 69, 74, 65, 74, 8, 30, 8, + 97, 8, 23, 26, 25, 23, 8, 30, 23, 8, 23, 30, 29, 29, 2, 55, 74, 81, 65, 79, 74, + 65, 68, 73, 82, 74, 67, 65, 74, 8, 42, 79, 110, 74, 132, 81, 79, 20, 8, 97, 8, 23, + 27, 20, 8, 64, 65, 79, 8, 29, 11, 8, 27, 21, 8, 64, 69, 65, 8, 82, 74, 64, 8, + 62, 69, 80, 8, 28, 8, 7, 8, 65, 69, 74, 65, 73, 8, 23, 31, 24, 22, 22, 8, 132, + 69, 81, 87, 82, 74, 67, 65, 74, 2, 82, 74, 64, 8, 67, 65, 84, 61, 72, 81, 69, 67, + 8, 65, 69, 74, 87, 65, 72, 74, 65, 8, 132, 69, 74, 64, 20, 8, 23, 23, 20, 8, 29, + 8, 132, 69, 74, 64, 8, 61, 62, 65, 79, 8, 23, 30, 26, 30, 8, 23, 24, 8, 11, 8, + 36, 82, 66, 66, 61, 132, 132, 82, 74, 67, 8, 30, 22, 8, 67, 61, 74, 87, 2, 65, 79, + 84, 75, 79, 62, 65, 74, 8, 67, 72, 61, 82, 62, 65, 8, 97, 8, 84, 69, 79, 64, 8, + 40, 69, 74, 65, 8, 23, 30, 24, 28, 33, 8, 27, 28, 8, 23, 26, 20, 8, 61, 82, 66, + 8, 64, 65, 74, 65, 74, 8, 23, 24, 8, 97, 8, 53, 81, 65, 72, 72, 65, 74, 8, 27, + 30, 31, 22, 18, 8, 37, 65, 132, 63, 68, 84, 65, 79, 64, 65, 74, 2, 53, 63, 68, 82, + 72, 64, 69, 67, 71, 65, 69, 81, 8, 56, 65, 79, 65, 69, 74, 8, 97, 8, 69, 132, 81, + 8, 23, 30, 29, 30, 8, 30, 25, 8, 97, 8, 64, 65, 74, 8, 64, 69, 65, 132, 65, 73, + 8, 61, 82, 63, 68, 8, 25, 27, 8, 18, 98, 8, 25, 25, 23, 21, 23, 25, 8, 30, 23, + 29, 6, 8, 53, 81, 82, 73, 78, 66, 2, 23, 22, 22, 8, 64, 65, 80, 68, 61, 72, 62, + 8, 18, 8, 68, 65, 79, 87, 82, 19, 39, 82, 79, 63, 68, 8, 27, 22, 20, 8, 27, 18, + 8, 83, 65, 79, 82, 79, 132, 61, 63, 68, 81, 8, 43, 65, 79, 79, 65, 74, 8, 67, 65, + 71, 110, 74, 64, 69, 67, 81, 8, 27, 27, 8, 97, 8, 36, 79, 81, 8, 31, 27, 22, 8, + 11, 8, 64, 65, 74, 2, 36, 82, 66, 132, 63, 68, 72, 61, 67, 8, 124, 63, 20, 8, 15, + 8, 87, 82, 79, 8, 68, 61, 81, 8, 23, 31, 23, 29, 20, 8, 18, 8, 64, 65, 80, 8, + 64, 65, 79, 8, 61, 62, 84, 65, 69, 63, 68, 65, 74, 64, 65, 79, 8, 30, 28, 8, 11, + 97, 8, 37, 65, 69, 8, 31, 31, 22, 11, 8, 7, 39, 61, 80, 2, 61, 72, 132, 75, 8, + 73, 69, 81, 8, 97, 75, 8, 64, 65, 74, 8, 31, 30, 27, 8, 27, 26, 31, 22, 21, 11, + 8, 45, 61, 68, 79, 65, 80, 62, 65, 83, 109, 72, 71, 65, 79, 82, 74, 67, 8, 74, 61, + 63, 68, 8, 22, 22, 22, 8, 23, 31, 24, 22, 8, 24, 18, 8, 69, 132, 81, 8, 30, 30, + 8, 7, 11, 29, 8, 60, 61, 68, 72, 2, 62, 65, 69, 8, 23, 31, 24, 25, 25, 8, 7, + 8, 61, 82, 80, 8, 64, 65, 80, 8, 29, 25, 28, 31, 18, 8, 7, 8, 64, 65, 79, 8, + 83, 75, 74, 8, 27, 30, 28, 24, 8, 25, 26, 8, 21, 8, 23, 27, 24, 25, 8, 23, 29, + 22, 11, 8, 36, 74, 132, 81, 69, 65, 67, 65, 2, 64, 69, 65, 8, 132, 75, 72, 72, 65, + 74, 8, 29, 8, 7, 68, 65, 66, 81, 69, 67, 65, 79, 8, 71, 72, 65, 69, 74, 65, 74, + 8, 24, 20, 8, 23, 23, 26, 20, 8, 64, 65, 74, 8, 44, 45, 45, 8, 24, 22, 22, 8, + 53, 69, 65, 8, 28, 31, 22, 8, 53, 75, 74, 74, 81, 61, 67, 65, 74, 32, 2, 132, 69, + 63, 68, 8, 65, 69, 74, 8, 7, 8, 64, 65, 79, 8, 36, 74, 79, 82, 66, 65, 74, 8, + 23, 31, 18, 8, 18, 8, 84, 69, 79, 8, 61, 72, 132, 75, 8, 87, 82, 79, 8, 29, 28, + 8, 79, 65, 63, 68, 81, 8, 23, 30, 25, 29, 22, 22, 8, 64, 61, 68, 65, 79, 2, 45, + 82, 74, 67, 65, 74, 8, 40, 81, 61, 81, 8, 27, 28, 8, 53, 81, 61, 64, 81, 8, 48, + 75, 74, 61, 81, 65, 74, 8, 25, 24, 22, 22, 18, 8, 7, 97, 8, 64, 61, 102, 8, 64, + 65, 73, 8, 132, 63, 68, 84, 61, 74, 71, 65, 74, 8, 23, 24, 8, 97, 8, 64, 65, 79, + 8, 26, 22, 22, 8, 37, 65, 64, 110, 79, 66, 74, 69, 80, 2, 25, 24, 27, 22, 8, 64, + 69, 65, 8, 97, 22, 8, 60, 82, 79, 8, 64, 69, 65, 132, 65, 79, 8, 31, 30, 27, 11, + 8, 97, 8, 45, 61, 68, 79, 65, 8, 64, 69, 65, 8, 61, 62, 87, 69, 65, 72, 65, 74, + 8, 25, 27, 8, 7, 97, 8, 132, 69, 63, 68, 8, 23, 24, 27, 22, 8, 29, 8, 83, 75, + 74, 2, 67, 65, 73, 103, 102, 8, 132, 75, 72, 63, 68, 65, 80, 8, 28, 8, 62, 69, 80, + 8, 57, 69, 79, 8, 29, 30, 11, 8, 64, 69, 65, 8, 42, 65, 73, 65, 69, 74, 64, 65, + 62, 65, 68, 109, 79, 64, 65, 8, 74, 65, 82, 65, 74, 8, 23, 27, 22, 29, 8, 29, 21, + 8, 64, 65, 79, 8, 23, 24, 22, 11, 8, 64, 61, 102, 2, 57, 65, 74, 69, 67, 8, 71, + 61, 74, 74, 8, 7, 8, 48, 69, 65, 81, 83, 65, 79, 81, 79, 61, 67, 8, 70, 82, 74, + 67, 65, 8, 23, 20, 8, 24, 8, 68, 61, 81, 8, 68, 61, 62, 65, 74, 8, 61, 72, 80, + 8, 24, 22, 8, 97, 8, 24, 25, 27, 21, 24, 24, 8, 29, 30, 8, 7, 8, 64, 65, 79, + 2, 36, 74, 64, 65, 79, 82, 74, 67, 8, 23, 31, 23, 24, 21, 23, 29, 20, 8, 97, 8, + 70, 65, 73, 61, 74, 64, 8, 25, 23, 25, 8, 25, 22, 22, 22, 8, 7, 8, 64, 61, 64, + 82, 79, 63, 68, 8, 64, 61, 102, 8, 82, 74, 64, 8, 23, 30, 29, 22, 8, 18, 8, 65, + 69, 74, 65, 8, 23, 30, 27, 29, 22, 8, 132, 78, 79, 65, 63, 68, 65, 2, 64, 65, 79, + 8, 51, 79, 75, 81, 75, 71, 75, 72, 72, 8, 98, 8, 23, 30, 31, 23, 8, 64, 65, 80, + 8, 23, 24, 11, 8, 24, 15, 8, 132, 75, 84, 69, 65, 8, 64, 61, 102, 8, 61, 72, 80, + 8, 31, 8, 18, 97, 8, 83, 75, 73, 8, 23, 26, 31, 22, 22, 22, 8, 67, 65, 73, 69, + 65, 81, 65, 81, 65, 74, 2, 31, 31, 8, 7, 8, 64, 65, 79, 8, 24, 8, 73, 61, 102, + 67, 65, 62, 65, 74, 64, 20, 8, 23, 30, 23, 23, 8, 29, 28, 33, 8, 64, 69, 65, 8, + 23, 24, 20, 8, 23, 31, 23, 22, 21, 23, 27, 20, 8, 23, 31, 22, 28, 8, 39, 61, 80, + 8, 23, 30, 30, 24, 22, 22, 8, 36, 74, 132, 78, 79, 110, 63, 68, 65, 74, 2, 65, 79, + 68, 61, 72, 81, 65, 74, 20, 8, 132, 69, 81, 87, 65, 74, 64, 65, 8, 15, 8, 71, 72, + 61, 79, 8, 68, 61, 81, 81, 65, 74, 8, 27, 31, 11, 8, 53, 81, 69, 73, 73, 65, 74, + 8, 87, 82, 73, 8, 62, 69, 80, 8, 30, 25, 8, 97, 8, 64, 65, 79, 8, 23, 30, 22, + 11, 8, 39, 79, 20, 2, 55, 74, 81, 65, 79, 79, 69, 63, 68, 81, 8, 64, 65, 79, 8, + 97, 8, 47, 65, 62, 65, 74, 80, 71, 82, 74, 64, 65, 8, 64, 65, 73, 8, 23, 30, 31, + 27, 20, 8, 27, 18, 8, 61, 82, 63, 68, 8, 87, 61, 68, 72, 65, 74, 8, 30, 22, 22, + 8, 25, 8, 28, 8, 83, 75, 74, 8, 23, 31, 22, 22, 8, 83, 75, 74, 2, 67, 65, 132, + 81, 65, 69, 67, 65, 79, 81, 65, 74, 8, 23, 27, 23, 24, 8, 40, 69, 74, 19, 57, 65, + 69, 132, 65, 8, 27, 24, 24, 22, 8, 97, 8, 87, 82, 20, 8, 30, 22, 22, 8, 132, 81, + 69, 65, 67, 8, 28, 27, 8, 97, 8, 132, 63, 68, 82, 72, 65, 8, 24, 25, 22, 11, 8, + 42, 79, 82, 78, 78, 65, 74, 2, 87, 65, 69, 67, 65, 74, 8, 62, 65, 81, 79, 61, 67, + 65, 74, 8, 62, 69, 80, 8, 23, 30, 24, 31, 8, 23, 30, 30, 22, 11, 8, 21, 8, 36, + 82, 80, 132, 81, 61, 81, 81, 82, 74, 67, 8, 71, 65, 69, 74, 65, 74, 8, 124, 63, 20, + 8, 23, 27, 23, 29, 8, 97, 8, 64, 61, 102, 8, 23, 23, 8, 29, 8, 42, 65, 62, 65, + 79, 81, 2, 23, 26, 23, 8, 36, 82, 63, 68, 8, 97, 8, 45, 80, 20, 8, 64, 69, 65, + 132, 65, 79, 8, 24, 29, 11, 8, 97, 8, 64, 65, 80, 8, 36, 82, 80, 67, 72, 65, 69, + 63, 68, 8, 83, 75, 74, 8, 23, 31, 23, 27, 8, 7, 98, 8, 23, 31, 23, 31, 8, 24, + 8, 29, 8, 65, 69, 74, 132, 65, 69, 81, 69, 4, 2, 75, 64, 65, 79, 8, 44, 63, 68, + 8, 24, 18, 8, 73, 69, 81, 8, 43, 65, 79, 71, 109, 73, 73, 72, 69, 63, 68, 8, 23, + 27, 20, 8, 97, 8, 83, 75, 74, 8, 40, 81, 61, 81, 8, 64, 61, 74, 74, 8, 23, 31, + 22, 27, 8, 36, 62, 19, 25, 27, 22, 22, 21, 8, 41, 65, 68, 72, 75, 84, 13, 132, 63, + 68, 65, 74, 2, 47, 109, 68, 74, 65, 8, 64, 61, 102, 8, 24, 8, 45, 82, 72, 69, 8, + 47, 65, 69, 81, 65, 79, 32, 8, 26, 27, 20, 8, 97, 8, 7, 48, 65, 69, 74, 65, 8, + 64, 65, 80, 8, 67, 65, 79, 69, 63, 68, 81, 65, 81, 65, 74, 8, 23, 23, 8, 97, 97, + 8, 71, 109, 74, 74, 65, 74, 8, 23, 31, 31, 21, 22, 8, 23, 27, 22, 2, 62, 69, 80, + 8, 65, 69, 74, 8, 48, 61, 69, 8, 72, 65, 69, 63, 68, 81, 8, 23, 30, 31, 22, 20, + 8, 27, 18, 8, 67, 65, 81, 79, 69, 65, 62, 65, 74, 35, 8, 57, 69, 72, 68, 65, 72, + 73, 8, 68, 61, 62, 65, 74, 8, 25, 22, 22, 8, 29, 18, 8, 61, 72, 80, 8, 24, 23, + 31, 22, 8, 83, 75, 79, 2, 69, 68, 79, 65, 79, 8, 31, 27, 31, 22, 22, 20, 8, 74, + 69, 63, 68, 81, 8, 36, 74, 132, 81, 61, 72, 81, 8, 23, 28, 26, 27, 20, 8, 24, 27, + 18, 8, 61, 82, 63, 68, 8, 84, 69, 79, 64, 8, 51, 75, 132, 69, 81, 69, 75, 74, 8, + 27, 8, 11, 97, 8, 23, 24, 8, 11, 8, 23, 30, 22, 30, 11, 8, 64, 65, 79, 2, 41, + 79, 61, 67, 65, 8, 65, 69, 74, 65, 8, 7, 8, 68, 61, 81, 81, 65, 20, 8, 27, 24, + 20, 8, 25, 22, 20, 8, 18, 97, 18, 8, 124, 63, 20, 8, 57, 75, 68, 74, 82, 74, 67, + 65, 74, 8, 84, 75, 79, 64, 65, 74, 20, 8, 23, 31, 24, 22, 8, 25, 8, 83, 75, 73, + 8, 23, 22, 22, 8, 62, 65, 69, 64, 65, 74, 2, 84, 65, 69, 102, 8, 43, 61, 82, 80, + 68, 61, 72, 81, 80, 19, 97, 8, 84, 82, 79, 64, 65, 8, 60, 82, 67, 65, 87, 75, 67, + 65, 74, 65, 74, 8, 23, 24, 20, 8, 6, 8, 66, 110, 79, 8, 66, 110, 79, 8, 44, 45, + 45, 8, 23, 26, 8, 97, 8, 22, 22, 22, 8, 23, 31, 23, 30, 29, 8, 61, 62, 132, 75, + 72, 82, 81, 65, 2, 84, 69, 79, 64, 8, 61, 82, 66, 79, 65, 63, 68, 81, 8, 7, 8, + 84, 69, 79, 8, 132, 81, 69, 65, 67, 8, 23, 31, 23, 28, 33, 8, 97, 8, 64, 65, 74, + 8, 60, 84, 65, 63, 71, 65, 8, 68, 61, 62, 65, 8, 23, 8, 18, 97, 8, 42, 65, 62, + 103, 82, 64, 65, 8, 27, 22, 22, 8, 74, 61, 63, 68, 2, 23, 23, 20, 8, 132, 69, 65, + 8, 97, 18, 8, 64, 69, 65, 8, 53, 81, 61, 64, 81, 8, 25, 30, 20, 8, 27, 28, 8, + 53, 63, 68, 82, 72, 64, 65, 78, 82, 81, 61, 81, 69, 75, 74, 8, 64, 65, 74, 8, 66, + 110, 79, 8, 28, 31, 8, 43, 65, 79, 79, 8, 23, 23, 22, 22, 8, 64, 65, 80, 68, 61, + 72, 62, 2, 64, 65, 79, 8, 36, 78, 79, 69, 72, 8, 21, 8, 45, 82, 74, 69, 8, 24, + 18, 8, 36, 62, 81, 65, 69, 72, 82, 74, 67, 8, 62, 65, 69, 64, 65, 74, 8, 27, 24, + 20, 8, 97, 75, 8, 65, 69, 74, 65, 73, 8, 61, 82, 63, 68, 8, 64, 69, 65, 132, 65, + 79, 8, 25, 27, 22, 8, 24, 97, 8, 124, 63, 20, 8, 26, 29, 8, 7, 8, 84, 69, 65, + 64, 65, 79, 2, 45, 61, 68, 79, 65, 8, 23, 30, 24, 30, 8, 97, 8, 64, 65, 79, 8, + 64, 65, 79, 8, 30, 30, 20, 8, 97, 8, 46, 109, 74, 69, 67, 72, 69, 63, 68, 65, 74, + 8, 45, 61, 68, 79, 8, 66, 110, 79, 8, 24, 26, 26, 26, 26, 8, 97, 8, 65, 69, 74, + 8, 23, 28, 24, 22, 22, 11, 8, 41, 79, 61, 67, 65, 2, 41, 65, 79, 74, 79, 82, 66, + 32, 8, 39, 61, 80, 8, 97, 8, 23, 31, 23, 29, 8, 48, 61, 67, 69, 132, 81, 79, 61, + 81, 8, 27, 29, 11, 8, 24, 18, 8, 39, 69, 65, 8, 64, 65, 80, 8, 36, 62, 81, 75, + 73, 73, 65, 74, 8, 30, 26, 8, 97, 8, 82, 132, 84, 20, 8, 29, 26, 8, 11, 8, 67, + 65, 73, 103, 102, 2, 74, 75, 63, 68, 8, 79, 82, 74, 64, 8, 97, 8, 82, 74, 64, 8, + 83, 65, 79, 132, 63, 68, 69, 65, 64, 65, 74, 65, 79, 8, 23, 11, 8, 62, 65, 79, 8, + 23, 22, 20, 8, 64, 61, 102, 8, 29, 27, 22, 8, 28, 8, 51, 66, 72, 69, 63, 68, 81, + 8, 23, 22, 22, 8, 62, 65, 87, 75, 67, 65, 74, 2, 64, 69, 65, 8, 49, 65, 82, 66, + 65, 79, 81, 8, 97, 8, 29, 22, 22, 22, 8, 45, 124, 20, 8, 23, 22, 30, 26, 26, 33, + 8, 7, 8, 23, 23, 22, 8, 132, 63, 68, 72, 61, 67, 65, 74, 8, 71, 72, 65, 69, 74, + 65, 74, 8, 24, 29, 8, 97, 8, 43, 65, 65, 79, 65, 80, 64, 69, 65, 74, 132, 81, 65, + 8, 25, 24, 11, 8, 64, 65, 79, 2, 65, 79, 68, 65, 62, 72, 69, 63, 68, 8, 73, 65, + 69, 74, 65, 74, 8, 83, 75, 74, 8, 39, 69, 65, 8, 26, 31, 20, 8, 23, 30, 26, 28, + 8, 82, 74, 64, 8, 37, 79, 75, 64, 65, 8, 28, 27, 24, 8, 97, 8, 23, 31, 23, 29, + 8, 31, 30, 8, 29, 8, 69, 132, 81, 2, 73, 109, 63, 68, 81, 65, 8, 23, 24, 27, 22, + 8, 97, 8, 84, 65, 72, 63, 68, 65, 74, 8, 64, 69, 65, 8, 24, 22, 22, 18, 8, 97, + 8, 64, 61, 80, 8, 56, 78, 80, 69, 72, 75, 74, 8, 73, 109, 63, 68, 81, 65, 8, 30, + 8, 29, 18, 8, 82, 74, 64, 8, 28, 30, 31, 21, 8, 87, 69, 65, 72, 65, 74, 64, 65, + 74, 2, 61, 62, 65, 79, 8, 64, 75, 63, 68, 8, 68, 69, 74, 65, 69, 74, 67, 65, 81, + 61, 74, 8, 43, 75, 66, 65, 8, 24, 22, 22, 22, 33, 8, 97, 8, 7, 37, 65, 79, 61, + 81, 82, 74, 67, 6, 8, 84, 65, 79, 64, 65, 74, 8, 124, 63, 20, 8, 28, 27, 28, 8, + 97, 8, 73, 61, 74, 8, 23, 29, 22, 11, 8, 65, 81, 84, 61, 80, 2, 52, 75, 132, 63, + 68, 65, 79, 132, 81, 79, 61, 102, 65, 8, 69, 132, 81, 8, 29, 97, 8, 84, 65, 69, 81, + 65, 79, 65, 80, 8, 64, 65, 79, 8, 30, 27, 22, 22, 11, 8, 11, 8, 83, 75, 74, 8, + 51, 65, 74, 87, 69, 67, 8, 64, 65, 79, 8, 23, 30, 31, 22, 8, 11, 8, 40, 69, 74, + 65, 8, 24, 29, 22, 8, 72, 65, 81, 87, 81, 65, 74, 2, 64, 65, 80, 8, 132, 81, 79, + 61, 102, 65, 8, 45, 9, 20, 8, 23, 22, 24, 32, 8, 24, 31, 18, 8, 97, 8, 68, 61, + 62, 65, 8, 83, 65, 79, 84, 61, 72, 81, 65, 81, 20, 8, 51, 79, 110, 66, 82, 74, 67, + 8, 23, 28, 25, 30, 8, 97, 8, 23, 22, 26, 8, 27, 30, 28, 24, 8, 7, 11, 8, 68, + 65, 82, 81, 65, 2, 61, 62, 65, 79, 8, 74, 82, 79, 8, 97, 8, 49, 79, 20, 8, 73, + 65, 81, 65, 79, 8, 25, 23, 18, 8, 7, 8, 83, 75, 74, 8, 132, 69, 63, 68, 8, 61, + 62, 65, 74, 64, 80, 8, 31, 8, 18, 98, 8, 69, 63, 68, 8, 23, 24, 8, 6, 8, 53, + 81, 65, 73, 78, 65, 72, 2, 68, 109, 68, 65, 79, 65, 8, 66, 75, 72, 67, 65, 74, 64, + 65, 32, 8, 7, 8, 64, 65, 79, 8, 82, 74, 64, 8, 23, 31, 22, 23, 20, 8, 24, 18, + 8, 65, 69, 74, 65, 8, 45, 61, 68, 79, 8, 61, 72, 72, 65, 79, 8, 23, 23, 8, 27, + 8, 84, 69, 79, 64, 8, 23, 22, 28, 24, 8, 29, 8, 64, 65, 80, 2, 53, 81, 79, 20, + 8, 132, 69, 63, 68, 8, 60, 65, 69, 63, 68, 74, 65, 79, 32, 8, 62, 65, 69, 8, 29, + 11, 8, 7, 8, 62, 65, 81, 79, 61, 63, 68, 81, 65, 74, 20, 6, 8, 64, 69, 65, 8, + 50, 22, 30, 20, 8, 30, 30, 8, 56, 61, 63, 68, 81, 8, 23, 27, 8, 29, 8, 64, 69, + 65, 2, 42, 65, 84, 69, 102, 8, 37, 65, 69, 8, 18, 8, 23, 31, 23, 22, 21, 23, 27, + 20, 8, 26, 24, 25, 8, 23, 22, 22, 18, 8, 97, 22, 8, 64, 61, 102, 8, 132, 81, 79, + 20, 8, 64, 65, 79, 8, 23, 22, 29, 29, 8, 24, 18, 8, 66, 110, 79, 8, 30, 31, 31, + 8, 7, 8, 61, 72, 72, 67, 65, 73, 65, 69, 74, 65, 2, 65, 79, 84, 75, 79, 62, 65, + 74, 20, 8, 57, 61, 74, 64, 65, 79, 82, 74, 67, 8, 7, 97, 8, 49, 65, 82, 62, 61, + 82, 8, 23, 30, 31, 31, 8, 23, 31, 22, 24, 20, 8, 97, 8, 61, 74, 67, 65, 73, 65, + 132, 132, 65, 74, 8, 82, 74, 64, 8, 82, 74, 64, 8, 26, 31, 8, 18, 96, 98, 8, 83, + 75, 74, 8, 25, 23, 22, 22, 8, 84, 69, 79, 64, 20, 2, 87, 84, 65, 69, 8, 64, 69, + 65, 8, 97, 8, 65, 69, 74, 65, 73, 8, 64, 65, 79, 8, 23, 30, 29, 25, 11, 8, 97, + 8, 23, 30, 26, 8, 42, 65, 62, 69, 65, 81, 65, 74, 8, 7, 64, 61, 79, 110, 62, 65, + 79, 6, 8, 27, 30, 8, 97, 8, 61, 82, 80, 8, 24, 22, 8, 66, 82, 79, 63, 68, 81, + 62, 61, 79, 2, 23, 30, 31, 31, 8, 64, 61, 79, 110, 62, 65, 79, 8, 36, 82, 80, 67, + 61, 62, 65, 74, 8, 49, 65, 82, 62, 61, 82, 8, 28, 27, 22, 22, 8, 29, 21, 8, 39, + 69, 74, 67, 65, 8, 36, 74, 79, 82, 66, 65, 74, 8, 75, 64, 65, 79, 8, 23, 23, 8, + 97, 8, 23, 30, 29, 29, 8, 24, 22, 22, 22, 8, 11, 8, 46, 109, 74, 69, 67, 69, 74, + 2, 64, 61, 67, 65, 67, 65, 74, 8, 132, 75, 74, 64, 65, 79, 74, 8, 97, 8, 64, 69, + 65, 8, 70, 65, 64, 75, 63, 68, 8, 23, 30, 27, 27, 20, 8, 24, 18, 8, 56, 65, 79, + 67, 72, 65, 69, 63, 68, 8, 53, 69, 81, 87, 82, 74, 67, 8, 64, 65, 79, 8, 28, 31, + 8, 97, 8, 132, 81, 69, 65, 67, 8, 23, 31, 22, 27, 22, 11, 8, 64, 61, 80, 2, 132, + 78, 79, 65, 63, 68, 65, 8, 69, 63, 68, 8, 18, 8, 25, 30, 26, 8, 27, 30, 28, 24, + 8, 25, 27, 8, 7, 29, 8, 24, 8, 29, 25, 28, 8, 75, 64, 65, 79, 8, 53, 61, 73, + 73, 65, 72, 68, 65, 69, 87, 82, 74, 67, 8, 24, 23, 8, 27, 8, 83, 65, 79, 62, 69, + 74, 64, 65, 74, 8, 23, 30, 31, 25, 22, 8, 74, 75, 63, 68, 2, 22, 30, 20, 8, 62, + 65, 84, 69, 72, 72, 69, 67, 81, 8, 11, 8, 23, 28, 25, 31, 8, 65, 69, 74, 81, 79, + 65, 81, 65, 74, 8, 23, 22, 27, 26, 22, 11, 8, 97, 8, 68, 61, 62, 65, 74, 8, 64, + 65, 79, 8, 27, 27, 20, 8, 23, 28, 26, 27, 8, 18, 8, 7, 39, 65, 79, 8, 25, 22, + 22, 21, 8, 36, 74, 132, 78, 79, 110, 63, 68, 65, 74, 2, 56, 65, 79, 19, 4, 42, 79, + 82, 74, 64, 61, 82, 66, 8, 7, 8, 67, 65, 68, 65, 74, 64, 8, 64, 65, 73, 8, 23, + 24, 11, 8, 57, 75, 68, 74, 82, 74, 67, 65, 74, 8, 25, 25, 26, 8, 67, 65, 78, 79, + 110, 66, 81, 65, 74, 8, 23, 31, 23, 27, 8, 97, 8, 23, 31, 24, 22, 8, 23, 31, 23, + 28, 8, 23, 27, 22, 2, 53, 69, 65, 8, 62, 65, 19, 97, 8, 36, 74, 67, 65, 72, 65, + 67, 65, 74, 68, 65, 69, 81, 65, 74, 8, 42, 79, 75, 72, 73, 61, 74, 19, 23, 31, 24, + 22, 20, 8, 97, 8, 124, 63, 20, 8, 23, 22, 22, 8, 64, 65, 79, 8, 28, 29, 8, 97, + 8, 66, 110, 79, 8, 23, 25, 22, 24, 8, 29, 8, 64, 69, 65, 2, 65, 69, 74, 87, 65, + 72, 74, 65, 74, 8, 65, 69, 74, 132, 65, 69, 81, 69, 19, 8, 83, 65, 79, 81, 79, 61, + 67, 8, 64, 65, 79, 8, 30, 26, 20, 8, 31, 8, 67, 82, 81, 65, 79, 8, 73, 69, 81, + 8, 69, 132, 81, 8, 23, 30, 24, 28, 8, 97, 8, 23, 30, 26, 25, 8, 23, 26, 8, 29, + 8, 65, 79, 67, 65, 62, 65, 74, 20, 2, 70, 65, 64, 75, 63, 68, 8, 87, 82, 79, 8, + 7, 97, 8, 51, 79, 110, 66, 82, 74, 67, 8, 46, 79, 69, 65, 67, 65, 8, 23, 24, 24, + 26, 20, 8, 97, 8, 84, 69, 65, 8, 83, 75, 73, 8, 64, 65, 79, 8, 23, 23, 8, 24, + 15, 8, 67, 65, 81, 79, 75, 66, 66, 65, 74, 65, 8, 31, 22, 8, 84, 65, 69, 72, 2, + 64, 61, 102, 8, 36, 73, 75, 79, 81, 69, 132, 61, 81, 69, 75, 74, 8, 97, 8, 48, 61, + 102, 65, 8, 64, 65, 79, 8, 23, 29, 22, 33, 8, 21, 8, 23, 26, 27, 8, 57, 65, 69, + 132, 65, 8, 83, 75, 74, 8, 27, 29, 8, 41, 103, 72, 72, 65, 74, 8, 29, 22, 22, 22, + 22, 11, 8, 82, 74, 64, 2, 87, 82, 73, 8, 64, 65, 79, 8, 24, 8, 52, 61, 81, 68, + 61, 82, 132, 65, 80, 8, 64, 69, 65, 8, 24, 18, 8, 64, 65, 79, 8, 61, 82, 66, 8, + 87, 82, 79, 8, 28, 27, 22, 8, 97, 8, 84, 65, 79, 64, 65, 74, 8, 23, 29, 28, 27, + 22, 8, 61, 82, 66, 79, 65, 63, 68, 81, 2, 37, 65, 87, 69, 65, 68, 82, 74, 67, 8, + 82, 74, 132, 65, 79, 73, 8, 69, 68, 79, 65, 79, 8, 82, 74, 64, 8, 23, 31, 20, 8, + 97, 8, 64, 65, 79, 8, 82, 74, 64, 8, 83, 65, 79, 87, 69, 63, 68, 81, 65, 74, 8, + 30, 22, 8, 29, 8, 65, 74, 81, 132, 78, 79, 69, 63, 68, 81, 8, 31, 31, 22, 11, 8, + 82, 74, 80, 2, 82, 74, 80, 8, 87, 82, 79, 8, 0, 5, 8, 67, 65, 74, 8, 53, 63, + 68, 110, 72, 65, 79, 8, 23, 28, 22, 22, 11, 8, 97, 8, 132, 75, 84, 69, 65, 8, 64, + 61, 79, 82, 73, 8, 52, 65, 61, 72, 132, 63, 68, 82, 72, 65, 8, 23, 31, 24, 22, 8, + 97, 8, 64, 82, 79, 63, 68, 8, 24, 22, 22, 22, 8, 6, 8, 61, 74, 64, 65, 79, 65, + 2, 64, 61, 102, 8, 64, 65, 79, 67, 72, 65, 69, 63, 68, 65, 74, 8, 7, 8, 132, 69, + 63, 68, 8, 62, 69, 80, 8, 24, 28, 22, 22, 18, 8, 18, 18, 8, 45, 82, 74, 69, 8, + 79, 82, 74, 64, 8, 62, 65, 69, 73, 8, 31, 8, 97, 8, 57, 75, 68, 74, 82, 74, 67, + 65, 74, 8, 23, 22, 22, 8, 83, 75, 74, 2, 62, 72, 69, 65, 62, 8, 74, 61, 63, 68, + 8, 79, 82, 74, 64, 8, 71, 109, 74, 74, 65, 74, 8, 26, 20, 8, 97, 8, 64, 65, 73, + 8, 48, 61, 79, 71, 8, 84, 65, 69, 72, 8, 30, 8, 97, 60, 8, 66, 110, 79, 8, 23, + 30, 31, 23, 8, 29, 8, 132, 79, 61, 102, 65, 2, 72, 65, 81, 87, 81, 65, 74, 8, 36, + 74, 81, 79, 61, 67, 8, 98, 97, 8, 64, 61, 79, 82, 73, 8, 23, 31, 23, 22, 21, 23, + 27, 20, 8, 28, 29, 22, 11, 8, 97, 8, 64, 69, 65, 132, 65, 73, 8, 45, 61, 68, 79, + 8, 69, 63, 68, 8, 25, 29, 8, 97, 8, 65, 69, 74, 65, 74, 8, 24, 22, 27, 22, 22, + 11, 8, 53, 81, 65, 82, 65, 79, 74, 2, 47, 65, 68, 79, 78, 72, 103, 74, 65, 74, 8, + 23, 27, 23, 24, 8, 97, 8, 36, 79, 87, 81, 8, 64, 65, 79, 8, 23, 30, 22, 31, 20, + 8, 97, 8, 124, 63, 20, 8, 51, 65, 79, 132, 75, 74, 8, 65, 74, 81, 132, 63, 68, 65, + 69, 64, 65, 74, 64, 65, 8, 26, 22, 8, 97, 8, 74, 61, 63, 68, 8, 23, 30, 31, 31, + 31, 8, 64, 65, 79, 2, 36, 62, 84, 61, 74, 64, 65, 79, 82, 74, 67, 8, 56, 75, 79, + 64, 65, 79, 68, 61, 82, 132, 65, 80, 8, 7, 97, 8, 64, 65, 73, 8, 70, 65, 64, 65, + 79, 8, 23, 31, 22, 23, 20, 8, 22, 22, 20, 8, 64, 69, 65, 8, 64, 69, 65, 8, 30, + 24, 8, 97, 8, 74, 61, 63, 68, 8, 26, 22, 22, 8, 7, 48, 103, 74, 74, 65, 79, 6, + 2, 64, 65, 73, 8, 84, 69, 79, 8, 27, 28, 8, 83, 75, 74, 8, 84, 65, 72, 63, 68, + 65, 8, 23, 31, 20, 8, 24, 15, 8, 64, 69, 65, 8, 74, 61, 63, 68, 8, 87, 69, 74, + 132, 82, 74, 67, 8, 25, 22, 8, 29, 8, 61, 62, 65, 79, 8, 23, 30, 27, 22, 22, 11, + 11, 8, 83, 75, 74, 2, 82, 74, 64, 8, 45, 82, 74, 67, 65, 74, 8, 7, 8, 64, 65, + 73, 8, 62, 69, 80, 8, 23, 22, 22, 18, 8, 98, 8, 36, 74, 132, 78, 79, 82, 63, 68, + 8, 64, 65, 79, 8, 64, 61, 79, 82, 73, 8, 23, 22, 8, 61, 82, 63, 68, 8, 25, 8, + 53, 63, 68, 69, 72, 72, 65, 79, 19, 54, 68, 65, 61, 81, 65, 79, 2, 81, 65, 74, 62, + 82, 79, 67, 8, 23, 24, 22, 11, 8, 97, 8, 23, 24, 8, 7, 29, 8, 7, 57, 61, 68, + 79, 72, 69, 63, 68, 9, 6, 8, 30, 30, 20, 8, 24, 18, 8, 62, 69, 80, 8, 73, 109, + 63, 68, 81, 65, 8, 73, 61, 74, 63, 68, 73, 61, 72, 8, 23, 30, 8, 97, 8, 87, 82, + 72, 65, 67, 65, 74, 8, 25, 26, 22, 11, 8, 73, 61, 63, 68, 65, 2, 64, 65, 74, 8, + 24, 27, 22, 22, 8, 97, 8, 82, 74, 64, 8, 65, 79, 66, 79, 61, 67, 65, 74, 20, 8, + 23, 22, 20, 8, 31, 21, 8, 69, 68, 79, 65, 8, 132, 75, 84, 69, 65, 8, 64, 69, 65, + 8, 23, 30, 29, 31, 8, 97, 8, 65, 79, 132, 81, 65, 74, 73, 75, 72, 8, 23, 30, 31, + 30, 31, 21, 8, 23, 30, 23, 23, 2, 83, 75, 73, 8, 51, 79, 75, 70, 65, 71, 81, 8, + 27, 18, 8, 69, 132, 81, 8, 84, 110, 74, 132, 63, 68, 65, 74, 80, 84, 65, 79, 81, 8, + 23, 24, 18, 8, 7, 8, 75, 64, 65, 79, 8, 132, 69, 74, 64, 20, 8, 24, 30, 26, 8, + 23, 31, 23, 30, 8, 24, 18, 8, 52, 65, 69, 68, 65, 8, 24, 22, 11, 22, 8, 48, 65, + 69, 74, 82, 74, 67, 2, 68, 61, 62, 65, 74, 8, 67, 65, 68, 65, 74, 8, 64, 65, 79, + 8, 44, 72, 20, 8, 29, 25, 11, 8, 24, 26, 26, 26, 26, 8, 84, 103, 72, 64, 65, 79, + 8, 39, 69, 65, 8, 23, 30, 27, 23, 8, 83, 75, 73, 8, 24, 29, 22, 8, 37, 65, 81, + 65, 69, 72, 69, 67, 81, 65, 74, 2, 24, 27, 22, 22, 8, 30, 22, 22, 22, 8, 7, 8, + 65, 69, 74, 67, 65, 66, 110, 68, 79, 81, 20, 8, 54, 65, 74, 75, 79, 8, 24, 27, 20, + 8, 7, 8, 65, 69, 74, 8, 87, 82, 132, 81, 103, 74, 64, 69, 67, 65, 74, 8, 64, 61, + 79, 82, 73, 8, 23, 31, 22, 23, 8, 97, 8, 64, 65, 73, 8, 23, 31, 22, 26, 31, 18, + 8, 39, 65, 74, 2, 65, 69, 74, 65, 73, 8, 64, 65, 79, 8, 97, 8, 23, 31, 22, 24, + 8, 65, 69, 74, 65, 8, 23, 30, 26, 22, 18, 8, 25, 28, 8, 39, 69, 65, 8, 37, 65, + 132, 63, 68, 72, 82, 102, 8, 84, 61, 79, 65, 74, 8, 23, 24, 8, 24, 18, 8, 64, 65, + 79, 8, 27, 22, 22, 11, 8, 64, 69, 65, 2, 84, 82, 79, 64, 65, 8, 65, 69, 74, 65, + 80, 8, 27, 28, 8, 62, 82, 79, 67, 65, 79, 8, 64, 69, 65, 8, 28, 27, 22, 22, 11, + 8, 11, 8, 25, 22, 22, 8, 74, 69, 73, 73, 81, 8, 64, 65, 80, 68, 61, 72, 62, 8, + 25, 22, 22, 8, 24, 97, 8, 23, 22, 27, 24, 8, 24, 22, 8, 29, 8, 53, 63, 68, 82, + 72, 65, 74, 2, 83, 75, 73, 8, 64, 65, 79, 8, 7, 8, 64, 65, 79, 8, 84, 69, 79, + 64, 20, 6, 8, 30, 30, 20, 8, 27, 26, 31, 8, 23, 31, 23, 30, 8, 23, 27, 24, 25, + 8, 23, 8, 27, 28, 8, 44, 74, 81, 65, 79, 65, 132, 132, 65, 74, 132, 81, 61, 74, 64, + 78, 82, 74, 71, 81, 8, 25, 22, 22, 8, 67, 65, 68, 65, 74, 2, 64, 69, 65, 8, 84, + 61, 79, 8, 44, 74, 132, 81, 61, 72, 72, 61, 81, 69, 75, 74, 8, 64, 65, 79, 8, 23, + 25, 25, 24, 97, 8, 64, 69, 65, 8, 27, 22, 22, 22, 8, 64, 65, 80, 8, 30, 30, 30, + 8, 97, 8, 73, 69, 81, 8, 23, 26, 22, 22, 8, 61, 62, 65, 79, 2, 72, 65, 81, 87, + 81, 65, 79, 65, 73, 8, 51, 65, 74, 87, 69, 67, 8, 18, 97, 8, 64, 65, 79, 8, 65, + 79, 68, 109, 68, 65, 74, 8, 23, 27, 11, 8, 24, 8, 132, 65, 69, 8, 84, 65, 72, 63, + 68, 65, 8, 65, 69, 74, 65, 80, 8, 26, 24, 8, 97, 8, 39, 61, 80, 8, 27, 22, 22, + 22, 8, 39, 61, 80, 2, 74, 69, 63, 68, 81, 8, 62, 69, 80, 8, 75, 8, 73, 69, 81, + 8, 59, 78, 80, 69, 72, 75, 74, 8, 29, 24, 20, 8, 97, 8, 53, 81, 103, 64, 81, 65, + 8, 132, 65, 81, 87, 65, 74, 8, 31, 30, 27, 8, 24, 22, 8, 97, 8, 64, 82, 79, 63, + 68, 8, 29, 30, 29, 8, 53, 63, 68, 82, 72, 64, 65, 78, 82, 81, 61, 81, 69, 75, 74, + 2, 64, 65, 74, 8, 23, 24, 27, 22, 8, 97, 8, 73, 65, 68, 79, 8, 48, 75, 73, 73, + 132, 65, 74, 19, 25, 22, 22, 22, 18, 8, 27, 28, 8, 56, 75, 79, 132, 81, 65, 68, 65, + 79, 8, 49, 75, 74, 74, 65, 74, 64, 61, 73, 73, 8, 45, 80, 20, 8, 23, 31, 23, 29, + 8, 97, 8, 64, 61, 79, 61, 74, 8, 27, 26, 22, 22, 8, 75, 62, 3, 2, 52, 65, 69, + 68, 65, 74, 20, 8, 62, 65, 71, 61, 73, 65, 74, 20, 8, 24, 15, 8, 68, 65, 79, 87, + 82, 19, 8, 73, 69, 81, 8, 23, 30, 31, 23, 22, 11, 22, 8, 7, 8, 61, 82, 66, 8, + 61, 82, 80, 8, 64, 61, 79, 82, 73, 8, 23, 30, 31, 22, 8, 27, 21, 28, 8, 64, 75, + 63, 68, 8, 23, 30, 24, 27, 29, 8, 45, 81, 124, 20, 2, 82, 74, 64, 8, 25, 24, 31, + 21, 24, 25, 8, 97, 8, 61, 72, 80, 8, 37, 65, 81, 81, 65, 74, 8, 23, 30, 30, 25, + 18, 8, 97, 8, 55, 74, 81, 65, 79, 62, 61, 82, 8, 64, 61, 80, 8, 67, 65, 132, 81, + 61, 72, 81, 65, 81, 65, 8, 30, 25, 8, 50, 79, 64, 20, 8, 23, 31, 23, 28, 8, 64, + 65, 80, 2, 64, 65, 79, 8, 37, 65, 81, 79, 61, 67, 8, 27, 28, 8, 43, 110, 72, 66, + 65, 8, 61, 82, 80, 8, 23, 31, 22, 30, 18, 8, 18, 8, 64, 65, 79, 8, 56, 65, 79, + 84, 61, 72, 81, 82, 74, 67, 20, 8, 64, 65, 80, 8, 23, 30, 31, 23, 8, 18, 97, 8, + 61, 82, 66, 87, 82, 84, 65, 74, 64, 65, 74, 8, 23, 23, 22, 8, 51, 82, 81, 87, 20, + 2, 64, 65, 80, 8, 23, 30, 28, 27, 8, 24, 18, 8, 83, 75, 74, 8, 46, 79, 65, 69, + 80, 132, 63, 68, 82, 72, 19, 24, 24, 33, 8, 97, 8, 82, 74, 64, 8, 7, 132, 63, 68, + 65, 69, 74, 81, 6, 8, 64, 69, 65, 8, 23, 27, 8, 97, 8, 83, 75, 79, 19, 24, 27, + 22, 8, 7, 8, 73, 65, 68, 79, 2, 132, 63, 68, 72, 69, 65, 102, 72, 69, 63, 68, 8, + 64, 69, 65, 8, 7, 18, 8, 64, 69, 65, 132, 65, 8, 132, 69, 65, 8, 23, 27, 11, 8, + 97, 21, 8, 84, 65, 72, 63, 68, 65, 8, 68, 61, 74, 64, 65, 72, 81, 8, 61, 62, 65, + 79, 8, 25, 25, 8, 97, 8, 23, 30, 28, 27, 8, 29, 8, 37, 65, 3, 2, 82, 74, 64, + 8, 61, 72, 80, 8, 24, 8, 132, 81, 103, 64, 81, 69, 132, 63, 68, 65, 79, 132, 65, 69, + 81, 80, 8, 132, 81, 79, 20, 8, 24, 11, 8, 18, 97, 8, 73, 69, 81, 8, 132, 69, 63, + 68, 8, 65, 69, 74, 65, 8, 23, 30, 27, 27, 8, 27, 21, 8, 48, 69, 72, 72, 69, 75, + 74, 65, 74, 8, 27, 31, 22, 22, 8, 64, 65, 79, 2, 64, 61, 79, 82, 73, 8, 39, 79, + 65, 68, 62, 110, 68, 74, 65, 8, 62, 65, 69, 8, 68, 61, 62, 65, 74, 8, 23, 30, 27, + 23, 18, 8, 7, 8, 40, 69, 74, 87, 69, 67, 8, 61, 82, 63, 68, 8, 82, 74, 80, 8, + 30, 30, 8, 18, 97, 8, 82, 74, 64, 8, 24, 31, 22, 8, 42, 79, 75, 72, 73, 61, 74, + 3, 2, 64, 65, 73, 8, 36, 82, 66, 132, 69, 63, 68, 81, 8, 7, 8, 70, 65, 74, 65, + 80, 8, 47, 61, 67, 65, 79, 78, 72, 61, 81, 87, 8, 24, 24, 33, 8, 97, 8, 64, 65, + 73, 8, 31, 27, 22, 22, 20, 8, 83, 75, 73, 8, 23, 31, 24, 22, 8, 97, 8, 83, 65, + 79, 19, 23, 23, 31, 22, 22, 8, 73, 65, 68, 79, 2, 67, 65, 74, 61, 82, 8, 73, 61, + 74, 63, 68, 73, 61, 72, 8, 97, 96, 22, 8, 84, 69, 79, 8, 61, 82, 80, 8, 31, 20, + 8, 7, 87, 84, 69, 132, 63, 68, 65, 74, 6, 8, 62, 69, 80, 8, 71, 61, 74, 74, 20, + 8, 29, 27, 8, 24, 8, 64, 69, 65, 8, 25, 24, 27, 22, 22, 11, 8, 60, 69, 73, 73, + 65, 79, 2, 22, 22, 22, 8, 64, 65, 73, 8, 97, 8, 56, 65, 79, 62, 69, 74, 64, 82, + 74, 67, 8, 43, 82, 67, 75, 8, 23, 24, 22, 22, 20, 8, 97, 8, 64, 69, 65, 8, 75, + 64, 65, 79, 8, 36, 74, 79, 82, 66, 65, 74, 8, 30, 24, 8, 64, 69, 65, 8, 24, 23, + 22, 22, 22, 8, 83, 69, 65, 72, 72, 65, 69, 63, 68, 81, 2, 64, 65, 79, 74, 8, 132, + 75, 84, 65, 69, 81, 8, 97, 8, 65, 79, 132, 81, 65, 74, 8, 124, 63, 20, 8, 25, 28, + 25, 20, 8, 18, 97, 8, 65, 69, 74, 65, 8, 23, 30, 22, 31, 8, 56, 65, 79, 132, 81, + 103, 79, 71, 82, 74, 67, 8, 23, 30, 31, 31, 8, 24, 18, 8, 23, 24, 20, 8, 31, 8, + 29, 8, 83, 75, 74, 2, 7, 51, 61, 79, 87, 65, 72, 72, 65, 74, 6, 8, 69, 132, 81, + 8, 28, 8, 67, 61, 74, 87, 8, 64, 61, 80, 8, 23, 30, 26, 22, 20, 8, 84, 82, 79, + 64, 65, 8, 37, 86, 71, 8, 82, 74, 64, 8, 28, 29, 8, 27, 28, 8, 42, 79, 82, 74, + 64, 8, 24, 29, 22, 22, 8, 23, 31, 22, 30, 21, 23, 25, 20, 2, 68, 65, 66, 81, 8, + 83, 75, 74, 8, 97, 8, 73, 69, 79, 8, 47, 61, 73, 78, 65, 74, 8, 23, 30, 29, 30, + 11, 8, 97, 8, 22, 22, 22, 8, 41, 65, 79, 74, 65, 79, 8, 23, 29, 26, 8, 25, 22, + 8, 27, 28, 8, 64, 61, 102, 8, 25, 23, 8, 29, 8, 132, 75, 72, 72, 2, 42, 79, 75, + 102, 19, 37, 65, 79, 72, 69, 74, 8, 65, 69, 74, 73, 61, 72, 8, 7, 97, 8, 44, 45, + 44, 45, 8, 82, 74, 64, 8, 23, 22, 20, 8, 97, 8, 46, 109, 74, 69, 67, 19, 44, 45, + 44, 45, 8, 61, 82, 66, 8, 23, 26, 22, 8, 97, 8, 25, 22, 20, 8, 24, 27, 22, 8, + 29, 8, 64, 69, 65, 2, 48, 103, 64, 63, 68, 65, 74, 8, 84, 65, 79, 64, 65, 74, 8, + 97, 8, 64, 69, 65, 8, 43, 61, 82, 80, 84, 61, 79, 81, 80, 8, 23, 24, 20, 8, 27, + 21, 8, 83, 75, 79, 19, 23, 30, 29, 31, 8, 39, 61, 79, 110, 62, 65, 79, 8, 23, 30, + 31, 28, 8, 18, 8, 83, 75, 74, 8, 25, 28, 25, 22, 22, 8, 65, 79, 67, 61, 62, 2, + 45, 61, 66, 66, 104, 8, 31, 24, 8, 11, 8, 24, 21, 8, 61, 82, 63, 68, 8, 61, 82, + 66, 8, 30, 29, 20, 8, 27, 21, 8, 75, 64, 65, 79, 8, 36, 74, 72, 65, 69, 68, 65, + 8, 65, 81, 84, 61, 69, 67, 65, 74, 8, 30, 22, 22, 8, 29, 18, 8, 84, 65, 69, 72, + 8, 25, 26, 30, 25, 30, 22, 21, 8, 64, 65, 79, 2, 53, 65, 69, 81, 8, 61, 74, 64, + 65, 79, 65, 79, 8, 7, 8, 83, 75, 73, 8, 72, 69, 65, 68, 65, 74, 8, 24, 18, 8, + 97, 75, 22, 8, 64, 61, 80, 8, 23, 23, 25, 21, 23, 25, 8, 24, 22, 22, 22, 8, 23, + 26, 23, 8, 97, 8, 64, 69, 65, 8, 23, 24, 22, 22, 8, 64, 65, 79, 70, 65, 74, 69, + 67, 65, 74, 2, 64, 61, 80, 8, 64, 61, 102, 8, 7, 97, 8, 48, 61, 74, 67, 65, 72, + 8, 84, 69, 79, 64, 8, 23, 29, 28, 31, 11, 8, 7, 8, 23, 22, 22, 8, 81, 79, 75, + 81, 87, 8, 64, 65, 74, 8, 28, 23, 8, 97, 8, 83, 65, 79, 62, 69, 74, 64, 65, 74, + 8, 23, 30, 25, 27, 22, 8, 61, 72, 132, 75, 2, 74, 69, 63, 68, 81, 8, 84, 65, 72, + 63, 68, 65, 8, 64, 61, 80, 8, 84, 65, 69, 63, 68, 81, 8, 23, 24, 27, 33, 8, 97, + 8, 64, 65, 79, 8, 23, 30, 31, 25, 8, 132, 63, 68, 79, 69, 66, 81, 65, 74, 8, 23, + 23, 8, 98, 8, 24, 30, 20, 8, 24, 30, 26, 8, 29, 8, 68, 69, 65, 132, 69, 67, 65, + 74, 2, 60, 82, 132, 63, 68, 82, 102, 8, 64, 65, 73, 67, 65, 73, 103, 102, 8, 24, 21, + 8, 64, 65, 80, 8, 65, 69, 74, 65, 79, 8, 31, 20, 8, 27, 18, 8, 83, 75, 79, 68, + 61, 74, 64, 65, 74, 8, 67, 65, 72, 65, 67, 65, 74, 8, 83, 75, 72, 72, 65, 74, 8, + 27, 8, 29, 7, 8, 39, 61, 80, 8, 27, 28, 22, 21, 8, 65, 79, 68, 61, 72, 81, 65, + 74, 2}; + +std::vector ocr_example2 = { + 22, 18, 27, 22, 8, 23, 23, 18, 29, 27, 8, 23, 28, 18, 29, 27, 8, 24, 18, 27, 31, + 8, 24, 18, 29, 22, 8, 24, 24, 18, 31, 24, 8, 23, 24, 18, 25, 25, 8, 24, 26, 18, + 30, 24, 8, 23, 26, 18, 25, 30, 11, 2, 22, 18, 27, 22, 8, 23, 23, 18, 29, 27, 8, + 23, 28, 18, 29, 27, 8, 24, 18, 27, 31, 8, 24, 18, 29, 22, 8, 24, 24, 18, 31, 24, + 8, 23, 24, 18, 25, 25, 8, 24, 26, 18, 30, 24, 8, 23, 26, 18, 25, 30, 11, 2, 22, + 18, 28, 27, 8, 23, 23, 18, 24, 29, 8, 23, 30, 18, 24, 22, 11, 8, 25, 18, 24, 24, + 8, 25, 18, 25, 24, 8, 22, 18, 29, 23, 8, 23, 22, 18, 31, 22, 8, 24, 18, 31, 22, + 8, 23, 24, 18, 28, 22, 2, 22, 18, 28, 27, 8, 23, 23, 18, 24, 29, 8, 23, 30, 18, + 24, 22, 11, 8, 25, 18, 24, 24, 8, 25, 18, 25, 24, 8, 22, 18, 29, 23, 8, 23, 22, + 18, 31, 22, 8, 24, 18, 31, 22, 8, 23, 24, 18, 28, 22, 2, 22, 18, 27, 26, 8, 23, + 22, 18, 30, 23, 8, 23, 31, 18, 22, 22, 8, 24, 18, 31, 25, 8, 11, 8, 24, 18, 24, + 22, 8, 24, 22, 18, 25, 25, 8, 23, 22, 18, 22, 24, 8, 24, 24, 18, 30, 28, 8, 23, + 24, 18, 25, 23, 2, 22, 18, 27, 26, 8, 23, 22, 18, 30, 23, 8, 23, 31, 18, 22, 22, + 8, 24, 18, 31, 25, 8, 11, 8, 24, 18, 24, 22, 8, 24, 22, 18, 25, 25, 8, 23, 22, + 18, 22, 24, 8, 24, 24, 18, 30, 28, 8, 23, 24, 18, 25, 23, 2, 22, 18, 27, 23, 8, + 23, 24, 18, 23, 31, 8, 23, 31, 18, 22, 26, 8, 24, 18, 26, 24, 8, 24, 22, 18, 23, + 23, 8, 23, 23, 18, 29, 8, 24, 30, 18, 26, 30, 8, 23, 30, 18, 29, 28, 11, 2, 22, + 18, 27, 23, 8, 23, 24, 18, 23, 31, 8, 23, 31, 18, 22, 26, 8, 24, 18, 26, 24, 8, + 24, 22, 18, 23, 23, 8, 23, 23, 18, 29, 8, 24, 30, 18, 26, 30, 8, 23, 30, 18, 29, + 28, 11, 2, 22, 18, 27, 22, 8, 23, 24, 18, 25, 24, 8, 24, 22, 18, 28, 24, 8, 30, + 25, 18, 24, 27, 8, 23, 18, 31, 22, 8, 23, 27, 18, 27, 27, 8, 30, 18, 22, 8, 24, + 24, 18, 29, 28, 8, 23, 23, 18, 28, 22, 2, 22, 18, 27, 22, 8, 23, 24, 18, 25, 24, + 8, 24, 22, 18, 28, 24, 8, 30, 25, 18, 24, 27, 8, 23, 18, 31, 22, 8, 23, 27, 18, + 27, 27, 8, 30, 18, 22, 8, 24, 24, 18, 29, 28, 8, 23, 23, 18, 28, 22, 2, 22, 18, + 30, 8, 23, 23, 18, 29, 28, 8, 24, 18, 26, 24, 8, 30, 25, 31, 22, 23, 18, 27, 23, + 8, 23, 24, 30, 26, 8, 30, 30, 27, 8, 24, 25, 18, 25, 26, 8, 23, 23, 18, 30, 23, + 2, 22, 18, 30, 8, 23, 23, 18, 29, 28, 8, 24, 18, 26, 24, 8, 30, 25, 31, 22, 23, + 18, 27, 23, 8, 23, 24, 30, 26, 8, 30, 30, 27, 8, 24, 25, 18, 25, 26, 8, 23, 23, + 18, 30, 23, 2, 22, 18, 30, 25, 24, 8, 23, 26, 18, 28, 25, 8, 23, 31, 18, 30, 28, + 8, 24, 18, 31, 29, 8, 23, 18, 27, 30, 8, 23, 22, 18, 29, 23, 8, 23, 22, 18, 25, + 24, 8, 24, 30, 18, 26, 31, 8, 23, 26, 18, 23, 24, 11, 2, 22, 18, 30, 25, 24, 8, + 23, 26, 18, 28, 25, 8, 23, 31, 18, 30, 28, 8, 24, 18, 31, 29, 8, 23, 18, 27, 30, + 8, 23, 22, 18, 29, 23, 8, 23, 22, 18, 25, 24, 8, 24, 30, 18, 26, 31, 8, 23, 26, + 18, 23, 24, 11, 2, 22, 18, 25, 24, 8, 23, 28, 22, 24, 23, 29, 26, 8, 24, 18, 30, + 27, 8, 23, 18, 27, 28, 8, 31, 18, 29, 27, 8, 23, 24, 18, 30, 8, 24, 27, 18, 26, + 24, 11, 8, 23, 26, 31, 22, 2, 22, 18, 25, 24, 8, 23, 28, 22, 24, 23, 29, 26, 8, + 24, 18, 30, 27, 8, 23, 18, 27, 28, 8, 31, 18, 29, 27, 8, 23, 24, 18, 30, 8, 24, + 27, 18, 26, 24, 11, 8, 23, 26, 31, 22, 2, 23, 25, 18, 25, 31, 11, 8, 23, 28, 18, + 23, 25, 8, 24, 20, 30, 28, 23, 18, 27, 29, 8, 23, 23, 18, 30, 25, 8, 23, 18, 28, + 31, 8, 24, 29, 18, 25, 27, 8, 23, 26, 18, 26, 25, 2, 23, 25, 18, 25, 31, 11, 8, + 23, 28, 18, 23, 25, 8, 24, 20, 30, 28, 23, 18, 27, 29, 8, 23, 23, 18, 30, 25, 8, + 23, 18, 28, 31, 8, 24, 29, 18, 25, 27, 8, 23, 26, 18, 26, 25, 2, 24, 29, 18, 28, + 31, 8, 23, 28, 18, 22, 27, 11, 8, 24, 18, 26, 31, 8, 22, 18, 27, 26, 8, 23, 25, + 18, 27, 29, 8, 23, 27, 18, 25, 26, 8, 23, 24, 18, 26, 23, 8, 30, 18, 30, 24, 8, + 24, 24, 18, 22, 30, 8, 23, 22, 18, 29, 29, 11, 2, 24, 29, 18, 28, 31, 8, 23, 28, + 18, 22, 27, 11, 8, 24, 18, 26, 31, 8, 22, 18, 27, 26, 8, 23, 25, 18, 27, 29, 8, + 23, 27, 18, 25, 26, 8, 23, 24, 18, 26, 23, 8, 30, 18, 30, 24, 8, 24, 24, 18, 22, + 30, 8, 23, 22, 18, 29, 29, 11, 2, 39, 69, 65, 8, 60, 61, 68, 72, 8, 64, 65, 79, + 8, 40, 68, 65, 132, 63, 68, 72, 69, 65, 102, 82, 74, 67, 65, 74, 8, 84, 61, 79, 8, + 23, 31, 23, 27, 8, 61, 82, 66, 8, 82, 74, 67, 65, 66, 103, 68, 79, 8, 87, 84, 65, + 69, 8, 39, 79, 69, 81, 81, 65, 72, 8, 64, 65, 79, 8, 69, 74, 2, 39, 69, 65, 8, + 60, 61, 68, 72, 8, 64, 65, 79, 8, 40, 68, 65, 132, 63, 68, 72, 69, 65, 102, 82, 74, + 67, 65, 74, 8, 84, 61, 79, 8, 23, 31, 23, 27, 8, 61, 82, 66, 8, 82, 74, 67, 65, + 66, 103, 68, 79, 8, 87, 84, 65, 69, 8, 39, 79, 69, 81, 81, 65, 72, 8, 64, 65, 79, + 8, 69, 74, 2, 64, 65, 74, 8, 41, 79, 69, 65, 64, 65, 74, 80, 66, 61, 68, 79, 65, + 74, 8, 14, 23, 31, 23, 23, 3, 23, 31, 23, 25, 15, 8, 64, 82, 79, 63, 68, 132, 63, + 68, 74, 69, 81, 81, 72, 69, 63, 68, 8, 67, 65, 132, 63, 68, 72, 75, 132, 132, 65, 74, + 65, 74, 8, 40, 68, 65, 74, 8, 87, 82, 79, 110, 63, 71, 67, 65, 67, 61, 74, 67, 65, + 74, 18, 8, 65, 79, 66, 82, 68, 79, 8, 69, 74, 2, 64, 65, 74, 8, 41, 79, 69, 65, + 64, 65, 74, 80, 66, 61, 68, 79, 65, 74, 8, 14, 23, 31, 23, 23, 3, 23, 31, 23, 25, + 15, 8, 64, 82, 79, 63, 68, 132, 63, 68, 74, 69, 81, 81, 72, 69, 63, 68, 8, 67, 65, + 132, 63, 68, 72, 75, 132, 132, 65, 74, 65, 74, 8, 40, 68, 65, 74, 8, 87, 82, 79, 110, + 63, 71, 67, 65, 67, 61, 74, 67, 65, 74, 18, 8, 65, 79, 66, 82, 68, 79, 8, 69, 74, + 2, 64, 65, 74, 8, 62, 65, 69, 64, 65, 74, 8, 66, 75, 72, 67, 65, 74, 64, 65, 74, + 8, 45, 61, 68, 79, 65, 74, 8, 74, 82, 79, 8, 65, 69, 74, 65, 8, 67, 65, 79, 69, + 74, 67, 65, 8, 60, 82, 74, 61, 68, 73, 65, 8, 82, 74, 64, 8, 132, 81, 69, 65, 67, + 8, 64, 61, 74, 74, 8, 83, 75, 74, 8, 23, 31, 23, 30, 8, 62, 69, 80, 8, 23, 31, + 24, 22, 2, 64, 65, 74, 8, 62, 65, 69, 64, 65, 74, 8, 66, 75, 72, 67, 65, 74, 64, + 65, 74, 8, 45, 61, 68, 79, 65, 74, 8, 74, 82, 79, 8, 65, 69, 74, 65, 8, 67, 65, + 79, 69, 74, 67, 65, 8, 60, 82, 74, 61, 68, 73, 65, 8, 82, 74, 64, 8, 132, 81, 69, + 65, 67, 8, 64, 61, 74, 74, 8, 83, 75, 74, 8, 23, 31, 23, 30, 8, 62, 69, 80, 8, + 23, 31, 24, 22, 2, 84, 69, 65, 64, 65, 79, 8, 65, 79, 68, 65, 62, 72, 69, 63, 68, + 20, 8, 36, 82, 66, 8, 23, 22, 22, 22, 8, 40, 69, 74, 84, 75, 68, 74, 65, 79, 8, + 73, 69, 81, 81, 72, 65, 79, 65, 79, 8, 45, 61, 68, 79, 65, 80, 62, 65, 83, 109, 72, + 71, 65, 79, 82, 74, 67, 8, 62, 65, 87, 75, 67, 65, 74, 18, 8, 132, 81, 65, 72, 72, + 81, 65, 8, 132, 69, 63, 68, 8, 64, 69, 65, 2, 84, 69, 65, 64, 65, 79, 8, 65, 79, + 68, 65, 62, 72, 69, 63, 68, 20, 8, 36, 82, 66, 8, 23, 22, 22, 22, 8, 40, 69, 74, + 84, 75, 68, 74, 65, 79, 8, 73, 69, 81, 81, 72, 65, 79, 65, 79, 8, 45, 61, 68, 79, + 65, 80, 62, 65, 83, 109, 72, 71, 65, 79, 82, 74, 67, 8, 62, 65, 87, 75, 67, 65, 74, + 18, 8, 132, 81, 65, 72, 72, 81, 65, 8, 132, 69, 63, 68, 8, 64, 69, 65, 2, 60, 61, + 68, 72, 8, 64, 65, 79, 8, 40, 68, 65, 132, 63, 68, 72, 69, 65, 102, 65, 74, 64, 65, + 74, 8, 23, 31, 23, 27, 3, 23, 31, 23, 30, 8, 61, 82, 66, 8, 23, 27, 18, 28, 24, + 18, 8, 23, 31, 23, 27, 3, 23, 31, 24, 22, 8, 61, 82, 66, 8, 23, 31, 18, 30, 22, + 8, 67, 65, 67, 65, 74, 8, 23, 31, 18, 24, 22, 8, 69, 74, 8, 64, 65, 74, 2, 60, + 61, 68, 72, 8, 64, 65, 79, 8, 40, 68, 65, 132, 63, 68, 72, 69, 65, 102, 65, 74, 64, + 65, 74, 8, 23, 31, 23, 27, 3, 23, 31, 23, 30, 8, 61, 82, 66, 8, 23, 27, 18, 28, + 24, 18, 8, 23, 31, 23, 27, 3, 23, 31, 24, 22, 8, 61, 82, 66, 8, 23, 31, 18, 30, + 22, 8, 67, 65, 67, 65, 74, 8, 23, 31, 18, 24, 22, 8, 69, 74, 8, 64, 65, 74, 2, + 64, 79, 65, 69, 8, 72, 65, 81, 87, 81, 65, 74, 8, 41, 79, 69, 65, 64, 65, 74, 80, + 70, 61, 68, 79, 65, 74, 20, 8, 39, 82, 79, 63, 68, 8, 79, 69, 63, 68, 81, 65, 79, + 72, 69, 63, 68, 65, 74, 8, 53, 78, 79, 82, 63, 68, 8, 84, 82, 79, 64, 65, 74, 8, + 23, 31, 23, 27, 3, 23, 22, 23, 30, 8, 28, 29, 25, 8, 40, 68, 65, 74, 8, 67, 65, + 4, 2, 64, 79, 65, 69, 8, 72, 65, 81, 87, 81, 65, 74, 8, 41, 79, 69, 65, 64, 65, + 74, 80, 70, 61, 68, 79, 65, 74, 20, 8, 39, 82, 79, 63, 68, 8, 79, 69, 63, 68, 81, + 65, 79, 72, 69, 63, 68, 65, 74, 8, 53, 78, 79, 82, 63, 68, 8, 84, 82, 79, 64, 65, + 74, 8, 23, 31, 23, 27, 3, 23, 22, 23, 30, 8, 28, 29, 25, 8, 40, 68, 65, 74, 8, + 67, 65, 4, 2, 132, 63, 68, 69, 65, 64, 65, 74, 18, 8, 64, 20, 8, 69, 20, 8, 29, + 18, 24, 22, 8, 61, 82, 66, 8, 70, 65, 8, 23, 22, 22, 18, 8, 69, 74, 8, 64, 65, + 79, 8, 67, 72, 65, 69, 63, 68, 65, 74, 8, 60, 65, 69, 81, 8, 74, 65, 82, 8, 65, + 69, 74, 67, 65, 67, 61, 74, 67, 65, 74, 65, 8, 40, 68, 65, 74, 18, 8, 23, 31, 23, + 31, 8, 61, 72, 72, 65, 69, 74, 2, 132, 63, 68, 69, 65, 64, 65, 74, 18, 8, 64, 20, + 8, 69, 20, 8, 29, 18, 24, 22, 8, 61, 82, 66, 8, 70, 65, 8, 23, 22, 22, 18, 8, + 69, 74, 8, 64, 65, 79, 8, 67, 72, 65, 69, 63, 68, 65, 74, 8, 60, 65, 69, 81, 8, + 74, 65, 82, 8, 65, 69, 74, 67, 65, 67, 61, 74, 67, 65, 74, 65, 8, 40, 68, 65, 74, + 18, 8, 23, 31, 23, 31, 8, 61, 72, 72, 65, 69, 74, 2, 25, 27, 31, 8, 34, 8, 30, + 18, 24, 11, 8, 67, 65, 67, 65, 74, 8, 29, 18, 24, 11, 8, 14, 23, 31, 23, 23, 3, + 23, 31, 23, 25, 15, 20, 2, 25, 27, 31, 8, 34, 8, 30, 18, 24, 11, 8, 67, 65, 67, + 65, 74, 8, 29, 18, 24, 11, 8, 14, 23, 31, 23, 23, 3, 23, 31, 23, 25, 15, 20, 2, + 40, 69, 74, 65, 74, 8, 79, 65, 63, 68, 81, 8, 65, 79, 68, 65, 62, 72, 69, 63, 68, + 65, 74, 8, 52, 110, 63, 71, 67, 61, 74, 67, 8, 65, 79, 66, 82, 68, 79, 8, 64, 69, + 65, 8, 60, 61, 68, 72, 8, 64, 65, 79, 8, 42, 65, 62, 75, 79, 65, 74, 65, 74, 20, + 8, 37, 65, 81, 79, 82, 67, 8, 132, 69, 65, 2, 40, 69, 74, 65, 74, 8, 79, 65, 63, + 68, 81, 8, 65, 79, 68, 65, 62, 72, 69, 63, 68, 65, 74, 8, 52, 110, 63, 71, 67, 61, + 74, 67, 8, 65, 79, 66, 82, 68, 79, 8, 64, 69, 65, 8, 60, 61, 68, 72, 8, 64, 65, + 79, 8, 42, 65, 62, 75, 79, 65, 74, 65, 74, 20, 8, 37, 65, 81, 79, 82, 67, 8, 132, + 69, 65, 2, 23, 31, 23, 27, 8, 74, 75, 63, 68, 8, 27, 29, 25, 31, 8, 82, 74, 64, + 8, 62, 72, 69, 65, 62, 8, 132, 69, 65, 8, 132, 75, 73, 69, 81, 8, 74, 82, 79, 8, + 82, 73, 8, 23, 29, 26, 8, 68, 69, 74, 81, 65, 79, 8, 64, 65, 79, 8, 23, 31, 23, + 25, 8, 67, 65, 73, 65, 72, 64, 65, 81, 65, 74, 8, 60, 61, 68, 72, 8, 64, 65, 79, + 8, 42, 65, 4, 2, 23, 31, 23, 27, 8, 74, 75, 63, 68, 8, 27, 29, 25, 31, 8, 82, + 74, 64, 8, 62, 72, 69, 65, 62, 8, 132, 69, 65, 8, 132, 75, 73, 69, 81, 8, 74, 82, + 79, 8, 82, 73, 8, 23, 29, 26, 8, 68, 69, 74, 81, 65, 79, 8, 64, 65, 79, 8, 23, + 31, 23, 25, 8, 67, 65, 73, 65, 72, 64, 65, 81, 65, 74, 8, 60, 61, 68, 72, 8, 64, + 65, 79, 8, 42, 65, 4, 2, 62, 75, 79, 65, 74, 65, 74, 8, 87, 82, 79, 110, 63, 71, + 18, 8, 132, 75, 8, 132, 61, 74, 71, 8, 132, 69, 65, 8, 14, 23, 31, 23, 27, 15, 8, + 61, 82, 66, 8, 26, 28, 29, 27, 18, 8, 14, 23, 31, 23, 28, 15, 8, 61, 82, 66, 8, + 25, 29, 24, 23, 8, 82, 74, 64, 8, 14, 23, 31, 23, 29, 15, 8, 67, 61, 79, 8, 61, + 82, 66, 8, 25, 23, 31, 25, 18, 8, 82, 73, 8, 64, 61, 74, 74, 2, 62, 75, 79, 65, + 74, 65, 74, 8, 87, 82, 79, 110, 63, 71, 18, 8, 132, 75, 8, 132, 61, 74, 71, 8, 132, + 69, 65, 8, 14, 23, 31, 23, 27, 15, 8, 61, 82, 66, 8, 26, 28, 29, 27, 18, 8, 14, + 23, 31, 23, 28, 15, 8, 61, 82, 66, 8, 25, 29, 24, 23, 8, 82, 74, 64, 8, 14, 23, + 31, 23, 29, 15, 8, 67, 61, 79, 8, 61, 82, 66, 8, 25, 23, 31, 25, 18, 8, 82, 73, + 8, 64, 61, 74, 74, 2, 39, 69, 65, 8, 48, 65, 81, 68, 75, 64, 65, 8, 87, 82, 79, + 8, 40, 79, 79, 65, 63, 68, 74, 82, 74, 67, 8, 64, 69, 65, 132, 65, 79, 8, 60, 69, + 66, 66, 65, 79, 8, 82, 74, 64, 8, 64, 69, 65, 8, 60, 69, 132, 132, 65, 79, 74, 8, + 132, 65, 72, 62, 132, 81, 8, 66, 69, 74, 64, 65, 74, 18, 8, 132, 69, 63, 68, 8, 69, + 74, 8, 64, 65, 79, 8, 69, 73, 8, 48, 61, 69, 4, 2, 39, 69, 65, 8, 48, 65, 81, + 68, 75, 64, 65, 8, 87, 82, 79, 8, 40, 79, 79, 65, 63, 68, 74, 82, 74, 67, 8, 64, + 69, 65, 132, 65, 79, 8, 60, 69, 66, 66, 65, 79, 8, 82, 74, 64, 8, 64, 69, 65, 8, + 60, 69, 132, 132, 65, 79, 74, 8, 132, 65, 72, 62, 132, 81, 8, 66, 69, 74, 64, 65, 74, + 18, 8, 132, 69, 63, 68, 8, 69, 74, 8, 64, 65, 79, 8, 69, 73, 8, 48, 61, 69, 4, + 2, 68, 65, 66, 81, 8, 23, 31, 24, 23, 8, 64, 65, 79, 8, 39, 65, 82, 81, 132, 63, + 68, 65, 74, 8, 48, 75, 74, 61, 81, 80, 132, 63, 68, 79, 69, 66, 81, 8, 66, 110, 79, + 8, 109, 66, 66, 65, 74, 81, 72, 69, 63, 68, 65, 8, 42, 65, 132, 82, 74, 64, 68, 65, + 69, 81, 80, 78, 66, 72, 65, 67, 65, 8, 83, 75, 73, 8, 53, 81, 61, 81, 69, 132, 81, + 69, 132, 63, 68, 65, 74, 8, 36, 73, 81, 8, 64, 65, 79, 8, 53, 81, 61, 64, 81, 2, + 68, 65, 66, 81, 8, 23, 31, 24, 23, 8, 64, 65, 79, 8, 39, 65, 82, 81, 132, 63, 68, + 65, 74, 8, 48, 75, 74, 61, 81, 80, 132, 63, 68, 79, 69, 66, 81, 8, 66, 110, 79, 8, + 109, 66, 66, 65, 74, 81, 72, 69, 63, 68, 65, 8, 42, 65, 132, 82, 74, 64, 68, 65, 69, + 81, 80, 78, 66, 72, 65, 67, 65, 8, 83, 75, 73, 8, 53, 81, 61, 81, 69, 132, 81, 69, + 132, 63, 68, 65, 74, 8, 36, 73, 81, 8, 64, 65, 79, 8, 53, 81, 61, 64, 81, 2, 38, + 68, 61, 79, 72, 75, 81, 81, 65, 74, 62, 82, 79, 67, 8, 83, 65, 79, 109, 66, 66, 65, + 74, 81, 72, 69, 63, 68, 81, 65, 74, 8, 36, 79, 62, 65, 69, 81, 32, 8, 7, 39, 69, + 65, 8, 53, 103, 82, 67, 72, 69, 74, 67, 80, 132, 81, 65, 79, 62, 72, 69, 63, 68, 81, + 65, 69, 81, 8, 69, 74, 8, 38, 62, 61, 79, 72, 75, 81, 81, 65, 74, 62, 82, 79, 67, + 2, 38, 68, 61, 79, 72, 75, 81, 81, 65, 74, 62, 82, 79, 67, 8, 83, 65, 79, 109, 66, + 66, 65, 74, 81, 72, 69, 63, 68, 81, 65, 74, 8, 36, 79, 62, 65, 69, 81, 32, 8, 7, + 39, 69, 65, 8, 53, 103, 82, 67, 72, 69, 74, 67, 80, 132, 81, 65, 79, 62, 72, 69, 63, + 68, 81, 65, 69, 81, 8, 69, 74, 8, 38, 62, 61, 79, 72, 75, 81, 81, 65, 74, 62, 82, + 79, 67, 2, 23, 31, 23, 24, 3, 23, 31, 8, 73, 69, 81, 8, 71, 79, 69, 81, 69, 132, + 63, 68, 65, 74, 8, 37, 65, 73, 65, 79, 71, 82, 74, 67, 65, 74, 8, 87, 82, 79, 8, + 48, 65, 81, 68, 75, 64, 65, 8, 64, 65, 79, 8, 48, 65, 80, 80, 82, 74, 67, 8, 64, + 65, 79, 8, 53, 103, 82, 67, 72, 69, 74, 67, 80, 132, 81, 65, 79, 62, 72, 69, 63, 68, + 71, 65, 69, 81, 6, 18, 8, 53, 20, 8, 23, 26, 27, 8, 66, 66, 20, 2, 23, 31, 23, + 24, 3, 23, 31, 8, 73, 69, 81, 8, 71, 79, 69, 81, 69, 132, 63, 68, 65, 74, 8, 37, + 65, 73, 65, 79, 71, 82, 74, 67, 65, 74, 8, 87, 82, 79, 8, 48, 65, 81, 68, 75, 64, + 65, 8, 64, 65, 79, 8, 48, 65, 80, 80, 82, 74, 67, 8, 64, 65, 79, 8, 53, 103, 82, + 67, 72, 69, 74, 67, 80, 132, 81, 65, 79, 62, 72, 69, 63, 68, 71, 65, 69, 81, 6, 18, + 8, 53, 20, 8, 23, 26, 27, 8, 66, 66, 20, 2, 69, 74, 8, 64, 65, 74, 8, 62, 65, + 69, 64, 65, 74, 8, 48, 75, 74, 61, 81, 65, 74, 8, 49, 75, 83, 65, 73, 62, 65, 79, + 8, 82, 74, 64, 8, 39, 65, 87, 65, 73, 62, 65, 79, 8, 84, 61, 74, 64, 65, 79, 81, + 65, 74, 8, 61, 72, 72, 65, 69, 74, 8, 23, 31, 24, 25, 25, 8, 73, 103, 74, 74, 72, + 69, 63, 68, 65, 8, 51, 65, 79, 4, 2, 69, 74, 8, 64, 65, 74, 8, 62, 65, 69, 64, + 65, 74, 8, 48, 75, 74, 61, 81, 65, 74, 8, 49, 75, 83, 65, 73, 62, 65, 79, 8, 82, + 74, 64, 8, 39, 65, 87, 65, 73, 62, 65, 79, 8, 84, 61, 74, 64, 65, 79, 81, 65, 74, + 8, 61, 72, 72, 65, 69, 74, 8, 23, 31, 24, 25, 25, 8, 73, 103, 74, 74, 72, 69, 63, + 68, 65, 8, 51, 65, 79, 4, 2, 132, 75, 74, 65, 74, 8, 69, 74, 66, 75, 72, 67, 65, + 8, 64, 65, 79, 8, 39, 65, 73, 75, 62, 69, 72, 73, 61, 63, 68, 82, 74, 67, 8, 73, + 65, 68, 79, 8, 87, 82, 8, 61, 72, 80, 8, 61, 62, 20, 8, 44, 73, 8, 45, 61, 68, + 79, 65, 8, 23, 31, 23, 31, 8, 65, 79, 67, 61, 62, 8, 132, 69, 63, 68, 8, 65, 69, + 74, 8, 62, 65, 4, 2, 132, 75, 74, 65, 74, 8, 69, 74, 66, 75, 72, 67, 65, 8, 64, + 65, 79, 8, 39, 65, 73, 75, 62, 69, 72, 73, 61, 63, 68, 82, 74, 67, 8, 73, 65, 68, + 79, 8, 87, 82, 8, 61, 72, 80, 8, 61, 62, 20, 8, 44, 73, 8, 45, 61, 68, 79, 65, + 8, 23, 31, 23, 31, 8, 65, 79, 67, 61, 62, 8, 132, 69, 63, 68, 8, 65, 69, 74, 8, + 62, 65, 4, 2, 64, 65, 82, 81, 65, 74, 64, 65, 79, 8, 55, 65, 62, 65, 79, 132, 63, + 68, 82, 102, 8, 14, 64, 65, 79, 8, 60, 82, 67, 65, 87, 75, 67, 65, 74, 65, 74, 15, + 8, 62, 65, 69, 8, 65, 79, 68, 109, 68, 81, 65, 79, 8, 57, 61, 74, 64, 65, 79, 62, + 65, 84, 65, 67, 82, 74, 67, 18, 8, 84, 103, 68, 79, 65, 74, 64, 8, 64, 61, 80, 8, + 45, 61, 68, 79, 8, 23, 31, 24, 22, 2, 64, 65, 82, 81, 65, 74, 64, 65, 79, 8, 55, + 65, 62, 65, 79, 132, 63, 68, 82, 102, 8, 14, 64, 65, 79, 8, 60, 82, 67, 65, 87, 75, + 67, 65, 74, 65, 74, 15, 8, 62, 65, 69, 8, 65, 79, 68, 109, 68, 81, 65, 79, 8, 57, + 61, 74, 64, 65, 79, 62, 65, 84, 65, 67, 82, 74, 67, 18, 8, 84, 103, 68, 79, 65, 74, + 64, 8, 64, 61, 80, 8, 45, 61, 68, 79, 8, 23, 31, 24, 22, 2, 62, 65, 69, 8, 65, + 69, 74, 67, 65, 132, 63, 68, 79, 103, 74, 71, 81, 65, 79, 8, 57, 61, 74, 64, 65, 79, + 82, 74, 67, 8, 65, 69, 74, 65, 74, 8, 61, 74, 132, 65, 68, 74, 72, 69, 63, 68, 65, + 74, 8, 60, 82, 84, 61, 63, 68, 80, 18, 8, 83, 75, 79, 8, 61, 72, 72, 65, 73, 8, + 83, 75, 74, 8, 73, 103, 74, 74, 72, 69, 63, 68, 65, 74, 8, 51, 65, 79, 4, 2, 62, + 65, 69, 8, 65, 69, 74, 67, 65, 132, 63, 68, 79, 103, 74, 71, 81, 65, 79, 8, 57, 61, + 74, 64, 65, 79, 82, 74, 67, 8, 65, 69, 74, 65, 74, 8, 61, 74, 132, 65, 68, 74, 72, + 69, 63, 68, 65, 74, 8, 60, 82, 84, 61, 63, 68, 80, 18, 8, 83, 75, 79, 8, 61, 72, + 72, 65, 73, 8, 83, 75, 74, 8, 73, 103, 74, 74, 72, 69, 63, 68, 65, 74, 8, 51, 65, + 79, 4, 2, 132, 75, 74, 65, 74, 8, 61, 82, 66, 84, 69, 65, 80, 20, 8, 36, 72, 80, + 8, 46, 79, 69, 65, 67, 80, 62, 69, 72, 61, 74, 87, 8, 65, 79, 67, 69, 62, 81, 8, + 132, 69, 63, 68, 8, 66, 110, 79, 8, 64, 69, 65, 8, 14, 38, 68, 61, 79, 72, 75, 81, + 81, 65, 74, 62, 82, 79, 67, 65, 79, 8, 37, 65, 83, 109, 72, 71, 65, 79, 82, 74, 67, + 15, 8, 84, 103, 68, 79, 65, 74, 64, 2, 132, 75, 74, 65, 74, 8, 61, 82, 66, 84, 69, + 65, 80, 20, 8, 36, 72, 80, 8, 46, 79, 69, 65, 67, 80, 62, 69, 72, 61, 74, 87, 8, + 65, 79, 67, 69, 62, 81, 8, 132, 69, 63, 68, 8, 66, 110, 79, 8, 64, 69, 65, 8, 14, + 38, 68, 61, 79, 72, 75, 81, 81, 65, 74, 62, 82, 79, 67, 65, 79, 8, 37, 65, 83, 109, + 72, 71, 65, 79, 82, 74, 67, 15, 8, 84, 103, 68, 79, 65, 74, 64, 2, 64, 65, 79, 8, + 60, 65, 69, 81, 8, 83, 75, 73, 8, 23, 20, 8, 36, 82, 67, 82, 132, 81, 8, 23, 31, + 23, 26, 8, 62, 69, 80, 8, 87, 82, 73, 8, 25, 23, 20, 8, 50, 71, 81, 75, 62, 65, + 79, 8, 23, 31, 23, 30, 8, 65, 69, 74, 65, 8, 36, 62, 74, 61, 68, 73, 65, 8, 64, + 65, 79, 8, 73, 103, 74, 74, 72, 69, 63, 68, 65, 74, 8, 37, 65, 4, 2, 64, 65, 79, + 8, 60, 65, 69, 81, 8, 83, 75, 73, 8, 23, 20, 8, 36, 82, 67, 82, 132, 81, 8, 23, + 31, 23, 26, 8, 62, 69, 80, 8, 87, 82, 73, 8, 25, 23, 20, 8, 50, 71, 81, 75, 62, + 65, 79, 8, 23, 31, 23, 30, 8, 65, 69, 74, 65, 8, 36, 62, 74, 61, 68, 73, 65, 8, + 64, 65, 79, 8, 73, 103, 74, 74, 72, 69, 63, 68, 65, 74, 8, 37, 65, 4, 2, 83, 109, + 72, 71, 65, 79, 82, 74, 67, 8, 82, 73, 8, 79, 82, 74, 64, 8, 24, 29, 8, 28, 22, + 22, 8, 34, 8, 23, 31, 18, 29, 25, 11, 8, 82, 74, 64, 8, 65, 69, 74, 65, 8, 60, + 82, 74, 61, 68, 73, 65, 8, 64, 65, 80, 8, 84, 65, 69, 62, 72, 69, 63, 68, 65, 74, + 8, 37, 65, 83, 109, 72, 71, 65, 79, 82, 74, 67, 80, 4, 2, 83, 109, 72, 71, 65, 79, + 82, 74, 67, 8, 82, 73, 8, 79, 82, 74, 64, 8, 24, 29, 8, 28, 22, 22, 8, 34, 8, + 23, 31, 18, 29, 25, 11, 8, 82, 74, 64, 8, 65, 69, 74, 65, 8, 60, 82, 74, 61, 68, + 73, 65, 8, 64, 65, 80, 8, 84, 65, 69, 62, 72, 69, 63, 68, 65, 74, 8, 37, 65, 83, + 109, 72, 71, 65, 79, 82, 74, 67, 80, 4, 2, 81, 65, 69, 72, 65, 80, 8, 82, 73, 8, + 79, 82, 74, 64, 8, 23, 23, 8, 24, 22, 22, 8, 34, 8, 28, 18, 25, 27, 11, 18, 8, + 61, 72, 132, 75, 8, 65, 69, 74, 65, 8, 36, 62, 74, 61, 68, 73, 65, 8, 64, 65, 79, + 8, 42, 65, 132, 61, 73, 81, 62, 65, 83, 109, 72, 71, 65, 79, 82, 74, 67, 8, 82, 73, + 8, 79, 82, 74, 64, 2, 81, 65, 69, 72, 65, 80, 8, 82, 73, 8, 79, 82, 74, 64, 8, + 23, 23, 8, 24, 22, 22, 8, 34, 8, 28, 18, 25, 27, 11, 18, 8, 61, 72, 132, 75, 8, + 65, 69, 74, 65, 8, 36, 62, 74, 61, 68, 73, 65, 8, 64, 65, 79, 8, 42, 65, 132, 61, + 73, 81, 62, 65, 83, 109, 72, 71, 65, 79, 82, 74, 67, 8, 82, 73, 8, 79, 82, 74, 64, + 2, 23, 28, 26, 22, 22, 8, 34, 8, 27, 18, 23, 29, 11, 20, 8, 39, 69, 65, 8, 49, + 61, 63, 68, 71, 79, 69, 65, 67, 80, 87, 65, 69, 81, 8, 62, 69, 80, 8, 25, 23, 20, + 8, 39, 65, 87, 65, 73, 62, 65, 79, 8, 23, 31, 23, 31, 8, 84, 69, 65, 80, 8, 64, + 61, 74, 74, 8, 65, 69, 74, 65, 8, 60, 82, 74, 61, 68, 73, 65, 2, 23, 28, 26, 22, + 22, 8, 34, 8, 27, 18, 23, 29, 11, 20, 8, 39, 69, 65, 8, 49, 61, 63, 68, 71, 79, + 69, 65, 67, 80, 87, 65, 69, 81, 8, 62, 69, 80, 8, 25, 23, 20, 8, 39, 65, 87, 65, + 73, 62, 65, 79, 8, 23, 31, 23, 31, 8, 84, 69, 65, 80, 8, 64, 61, 74, 74, 8, 65, + 69, 74, 65, 8, 60, 82, 74, 61, 68, 73, 65, 2, 64, 65, 79, 8, 73, 103, 74, 74, 72, + 69, 63, 68, 65, 74, 8, 37, 65, 83, 109, 72, 71, 65, 79, 82, 74, 67, 8, 82, 73, 8, + 25, 23, 8, 23, 22, 22, 8, 34, 8, 24, 29, 18, 28, 28, 11, 8, 82, 74, 64, 8, 65, + 69, 74, 65, 8, 36, 62, 74, 61, 68, 73, 65, 8, 64, 65, 79, 8, 84, 65, 69, 62, 72, + 69, 63, 68, 65, 74, 8, 37, 65, 4, 2, 64, 65, 79, 8, 73, 103, 74, 74, 72, 69, 63, + 68, 65, 74, 8, 37, 65, 83, 109, 72, 71, 65, 79, 82, 74, 67, 8, 82, 73, 8, 25, 23, + 8, 23, 22, 22, 8, 34, 8, 24, 29, 18, 28, 28, 11, 8, 82, 74, 64, 8, 65, 69, 74, + 65, 8, 36, 62, 74, 61, 68, 73, 65, 8, 64, 65, 79, 8, 84, 65, 69, 62, 72, 69, 63, + 68, 65, 74, 8, 37, 65, 4, 2, 83, 109, 72, 71, 65, 79, 82, 74, 67, 8, 82, 73, 8, + 23, 24, 27, 22, 8, 34, 8, 22, 18, 28, 28, 11, 8, 61, 82, 66, 18, 8, 84, 103, 68, + 79, 65, 74, 64, 8, 64, 69, 65, 8, 42, 65, 132, 61, 73, 81, 62, 65, 83, 109, 72, 71, + 65, 79, 82, 74, 67, 8, 82, 73, 8, 24, 31, 8, 30, 27, 22, 8, 34, 8, 31, 18, 31, + 24, 11, 8, 87, 82, 74, 61, 68, 73, 20, 2, 83, 109, 72, 71, 65, 79, 82, 74, 67, 8, + 82, 73, 8, 23, 24, 27, 22, 8, 34, 8, 22, 18, 28, 28, 11, 8, 61, 82, 66, 18, 8, + 84, 103, 68, 79, 65, 74, 64, 8, 64, 69, 65, 8, 42, 65, 132, 61, 73, 81, 62, 65, 83, + 109, 72, 71, 65, 79, 82, 74, 67, 8, 82, 73, 8, 24, 31, 8, 30, 27, 22, 8, 34, 8, + 31, 18, 31, 24, 11, 8, 87, 82, 74, 61, 68, 73, 20, 2, 39, 69, 65, 8, 73, 69, 81, + 81, 72, 65, 79, 65, 8, 40, 69, 74, 84, 75, 68, 74, 65, 79, 87, 61, 68, 72, 8, 38, + 68, 61, 79, 72, 75, 81, 81, 65, 74, 62, 82, 79, 67, 80, 8, 62, 65, 81, 79, 82, 67, + 8, 23, 31, 23, 27, 8, 79, 82, 74, 64, 8, 25, 22, 26, 8, 31, 22, 22, 18, 2, 39, + 69, 65, 8, 73, 69, 81, 81, 72, 65, 79, 65, 8, 40, 69, 74, 84, 75, 68, 74, 65, 79, + 87, 61, 68, 72, 8, 38, 68, 61, 79, 72, 75, 81, 81, 65, 74, 62, 82, 79, 67, 80, 8, + 62, 65, 81, 79, 82, 67, 8, 23, 31, 23, 27, 8, 79, 82, 74, 64, 8, 25, 22, 26, 8, + 31, 22, 22, 18, 2, 66, 69, 65, 72, 8, 23, 31, 23, 28, 8, 61, 82, 66, 8, 25, 22, + 22, 8, 23, 22, 22, 18, 8, 132, 61, 74, 71, 8, 23, 31, 23, 29, 8, 84, 65, 69, 81, + 65, 79, 8, 61, 82, 66, 8, 24, 31, 29, 8, 22, 22, 22, 8, 82, 74, 64, 8, 132, 81, + 69, 65, 67, 8, 64, 61, 74, 74, 8, 84, 69, 65, 64, 65, 79, 8, 23, 31, 23, 30, 8, + 61, 82, 66, 8, 25, 22, 22, 8, 26, 22, 22, 18, 2, 66, 69, 65, 72, 8, 23, 31, 23, + 28, 8, 61, 82, 66, 8, 25, 22, 22, 8, 23, 22, 22, 18, 8, 132, 61, 74, 71, 8, 23, + 31, 23, 29, 8, 84, 65, 69, 81, 65, 79, 8, 61, 82, 66, 8, 24, 31, 29, 8, 22, 22, + 22, 8, 82, 74, 64, 8, 132, 81, 69, 65, 67, 8, 64, 61, 74, 74, 8, 84, 69, 65, 64, + 65, 79, 8, 23, 31, 23, 30, 8, 61, 82, 66, 8, 25, 22, 22, 8, 26, 22, 22, 18, 2, + 23, 31, 23, 31, 8, 61, 82, 66, 8, 25, 24, 23, 8, 26, 22, 22, 8, 82, 74, 64, 8, + 23, 31, 24, 22, 8, 61, 82, 66, 8, 25, 25, 26, 8, 27, 22, 22, 20, 2, 23, 31, 23, + 31, 8, 61, 82, 66, 8, 25, 24, 23, 8, 26, 22, 22, 8, 82, 74, 64, 8, 23, 31, 24, + 22, 8, 61, 82, 66, 8, 25, 25, 26, 8, 27, 22, 22, 20, 2, 39, 69, 65, 8, 74, 61, + 81, 110, 79, 72, 69, 63, 68, 65, 8, 37, 65, 83, 109, 72, 71, 65, 79, 82, 74, 67, 80, + 62, 65, 84, 65, 67, 82, 74, 67, 18, 8, 62, 65, 64, 69, 74, 67, 81, 8, 64, 82, 79, + 63, 68, 8, 64, 69, 65, 8, 40, 68, 65, 132, 63, 68, 72, 69, 65, 4, 2, 39, 69, 65, + 8, 74, 61, 81, 110, 79, 72, 69, 63, 68, 65, 8, 37, 65, 83, 109, 72, 71, 65, 79, 82, + 74, 67, 80, 62, 65, 84, 65, 67, 82, 74, 67, 18, 8, 62, 65, 64, 69, 74, 67, 81, 8, + 64, 82, 79, 63, 68, 8, 64, 69, 65, 8, 40, 68, 65, 132, 63, 68, 72, 69, 65, 4, 2, + 102, 82, 74, 67, 65, 74, 18, 8, 42, 65, 62, 82, 79, 81, 65, 74, 8, 82, 74, 64, 8, + 53, 81, 65, 79, 62, 65, 66, 103, 72, 72, 65, 18, 8, 84, 61, 79, 8, 66, 75, 72, 67, + 65, 74, 64, 65, 32, 2, 102, 82, 74, 67, 65, 74, 18, 8, 42, 65, 62, 82, 79, 81, 65, + 74, 8, 82, 74, 64, 8, 53, 81, 65, 79, 62, 65, 66, 103, 72, 72, 65, 18, 8, 84, 61, + 79, 8, 66, 75, 72, 67, 65, 74, 64, 65, 32, 2, 84, 82, 79, 64, 65, 74, 8, 64, 69, + 65, 8, 37, 65, 132, 81, 69, 73, 73, 82, 74, 67, 65, 74, 8, 66, 110, 79, 8, 64, 61, + 80, 8, 38, 68, 61, 79, 72, 75, 81, 81, 65, 74, 62, 82, 79, 67, 65, 79, 8, 48, 69, + 65, 81, 65, 69, 74, 69, 67, 82, 74, 67, 80, 61, 73, 81, 8, 69, 73, 8, 48, 61, 69, + 8, 23, 31, 23, 27, 2, 84, 82, 79, 64, 65, 74, 8, 64, 69, 65, 8, 37, 65, 132, 81, + 69, 73, 73, 82, 74, 67, 65, 74, 8, 66, 110, 79, 8, 64, 61, 80, 8, 38, 68, 61, 79, + 72, 75, 81, 81, 65, 74, 62, 82, 79, 67, 65, 79, 8, 48, 69, 65, 81, 65, 69, 74, 69, + 67, 82, 74, 67, 80, 61, 73, 81, 8, 69, 73, 8, 48, 61, 69, 8, 23, 31, 23, 27, 2, + 64, 61, 68, 69, 74, 8, 61, 62, 67, 65, 103, 74, 64, 65, 79, 81, 18, 8, 64, 61, 102, + 8, 64, 69, 65, 132, 65, 80, 8, 87, 82, 67, 72, 65, 69, 63, 68, 8, 61, 72, 80, 8, + 43, 86, 78, 75, 81, 68, 65, 71, 65, 74, 65, 69, 74, 69, 67, 82, 74, 67, 80, 61, 73, + 81, 8, 69, 74, 8, 54, 103, 81, 69, 67, 4, 2, 64, 61, 68, 69, 74, 8, 61, 62, 67, + 65, 103, 74, 64, 65, 79, 81, 18, 8, 64, 61, 102, 8, 64, 69, 65, 132, 65, 80, 8, 87, + 82, 67, 72, 65, 69, 63, 68, 8, 61, 72, 80, 8, 43, 86, 78, 75, 81, 68, 65, 71, 65, + 74, 65, 69, 74, 69, 67, 82, 74, 67, 80, 61, 73, 81, 8, 69, 74, 8, 54, 103, 81, 69, + 67, 4, 2, 71, 65, 69, 81, 8, 81, 79, 61, 81, 20, 8, 43, 69, 65, 79, 87, 82, 8, + 84, 82, 79, 64, 65, 74, 8, 64, 65, 73, 8, 36, 73, 81, 8, 61, 82, 63, 68, 8, 64, + 69, 65, 8, 132, 75, 67, 65, 74, 61, 74, 74, 81, 65, 74, 8, 60, 84, 61, 74, 67, 80, + 62, 65, 66, 82, 67, 74, 69, 132, 132, 65, 8, 83, 65, 79, 4, 2, 71, 65, 69, 81, 8, + 81, 79, 61, 81, 20, 8, 43, 69, 65, 79, 87, 82, 8, 84, 82, 79, 64, 65, 74, 8, 64, + 65, 73, 8, 36, 73, 81, 8, 61, 82, 63, 68, 8, 64, 69, 65, 8, 132, 75, 67, 65, 74, + 61, 74, 74, 81, 65, 74, 8, 60, 84, 61, 74, 67, 80, 62, 65, 66, 82, 67, 74, 69, 132, + 132, 65, 8, 83, 65, 79, 4, 2, 72, 69, 65, 68, 65, 74, 18, 8, 14, 84, 75, 74, 61, + 63, 68, 8, 64, 69, 65, 8, 37, 65, 81, 65, 69, 72, 69, 67, 81, 65, 74, 8, 83, 65, + 79, 78, 66, 72, 69, 63, 68, 81, 65, 81, 8, 132, 69, 74, 64, 15, 18, 8, 83, 75, 79, + 8, 64, 65, 73, 8, 36, 73, 81, 8, 87, 82, 8, 65, 79, 132, 63, 68, 65, 69, 74, 65, + 74, 8, 82, 74, 64, 8, 36, 82, 80, 71, 82, 74, 66, 81, 2, 72, 69, 65, 68, 65, 74, + 18, 8, 14, 84, 75, 74, 61, 63, 68, 8, 64, 69, 65, 8, 37, 65, 81, 65, 69, 72, 69, + 67, 81, 65, 74, 8, 83, 65, 79, 78, 66, 72, 69, 63, 68, 81, 65, 81, 8, 132, 69, 74, + 64, 15, 18, 8, 83, 75, 79, 8, 64, 65, 73, 8, 36, 73, 81, 8, 87, 82, 8, 65, 79, + 132, 63, 68, 65, 69, 74, 65, 74, 8, 82, 74, 64, 8, 36, 82, 80, 71, 82, 74, 66, 81, + 2, 87, 82, 8, 65, 79, 81, 65, 69, 72, 65, 74, 20, 8, 39, 82, 79, 63, 68, 8, 64, + 69, 65, 8, 37, 82, 74, 64, 65, 80, 79, 61, 81, 80, 83, 65, 79, 75, 79, 64, 74, 82, + 74, 67, 8, 14, 87, 82, 73, 8, 53, 63, 68, 82, 81, 87, 65, 8, 64, 65, 79, 8, 48, + 69, 65, 81, 65, 79, 15, 8, 83, 75, 73, 8, 24, 28, 20, 8, 45, 82, 72, 69, 8, 23, + 31, 23, 29, 2, 87, 82, 8, 65, 79, 81, 65, 69, 72, 65, 74, 20, 8, 39, 82, 79, 63, + 68, 8, 64, 69, 65, 8, 37, 82, 74, 64, 65, 80, 79, 61, 81, 80, 83, 65, 79, 75, 79, + 64, 74, 82, 74, 67, 8, 14, 87, 82, 73, 8, 53, 63, 68, 82, 81, 87, 65, 8, 64, 65, + 79, 8, 48, 69, 65, 81, 65, 79, 15, 8, 83, 75, 73, 8, 24, 28, 20, 8, 45, 82, 72, + 69, 8, 23, 31, 23, 29, 2, 69, 74, 8, 56, 65, 79, 62, 69, 74, 64, 82, 74, 67, 8, + 73, 69, 81, 8, 64, 65, 73, 8, 40, 79, 72, 61, 102, 8, 64, 65, 80, 8, 48, 69, 74, + 69, 132, 81, 65, 79, 80, 8, 64, 65, 80, 8, 44, 74, 74, 65, 79, 74, 8, 83, 75, 73, + 8, 25, 22, 20, 8, 36, 82, 67, 82, 132, 81, 8, 23, 31, 23, 29, 8, 82, 74, 64, 8, + 64, 65, 79, 8, 48, 69, 65, 81, 65, 79, 4, 2, 69, 74, 8, 56, 65, 79, 62, 69, 74, + 64, 82, 74, 67, 8, 73, 69, 81, 8, 64, 65, 73, 8, 40, 79, 72, 61, 102, 8, 64, 65, + 80, 8, 48, 69, 74, 69, 132, 81, 65, 79, 80, 8, 64, 65, 80, 8, 44, 74, 74, 65, 79, + 74, 8, 83, 75, 73, 8, 25, 22, 20, 8, 36, 82, 67, 82, 132, 81, 8, 23, 31, 23, 29, + 8, 82, 74, 64, 8, 64, 65, 79, 8, 48, 69, 65, 81, 65, 79, 4, 2, 132, 63, 68, 82, + 81, 87, 83, 65, 79, 75, 79, 64, 74, 82, 74, 67, 8, 14, 83, 75, 73, 8, 24, 25, 20, + 8, 53, 65, 78, 81, 65, 73, 62, 65, 79, 8, 23, 31, 23, 30, 15, 8, 84, 82, 79, 64, + 65, 8, 64, 61, 80, 8, 40, 69, 74, 69, 67, 82, 74, 67, 80, 61, 73, 81, 8, 66, 65, + 79, 74, 65, 79, 8, 65, 79, 73, 103, 63, 68, 81, 69, 67, 81, 18, 8, 61, 82, 66, 2, + 132, 63, 68, 82, 81, 87, 83, 65, 79, 75, 79, 64, 74, 82, 74, 67, 8, 14, 83, 75, 73, + 8, 24, 25, 20, 8, 53, 65, 78, 81, 65, 73, 62, 65, 79, 8, 23, 31, 23, 30, 15, 8, + 84, 82, 79, 64, 65, 8, 64, 61, 80, 8, 40, 69, 74, 69, 67, 82, 74, 67, 80, 61, 73, + 81, 8, 66, 65, 79, 74, 65, 79, 8, 65, 79, 73, 103, 63, 68, 81, 69, 67, 81, 18, 8, + 61, 82, 66, 2, 36, 74, 79, 82, 66, 65, 74, 8, 65, 69, 74, 65, 80, 8, 48, 69, 65, + 81, 65, 79, 80, 8, 110, 62, 65, 79, 8, 64, 69, 65, 8, 57, 69, 79, 71, 132, 61, 73, + 71, 65, 69, 81, 8, 65, 69, 74, 65, 79, 8, 46, 110, 74, 64, 69, 67, 82, 74, 67, 8, + 64, 65, 80, 8, 56, 65, 79, 73, 69, 65, 81, 65, 79, 80, 18, 8, 110, 62, 65, 79, 8, + 64, 69, 65, 2, 36, 74, 79, 82, 66, 65, 74, 8, 65, 69, 74, 65, 80, 8, 48, 69, 65, + 81, 65, 79, 80, 8, 110, 62, 65, 79, 8, 64, 69, 65, 8, 57, 69, 79, 71, 132, 61, 73, + 71, 65, 69, 81, 8, 65, 69, 74, 65, 79, 8, 46, 110, 74, 64, 69, 67, 82, 74, 67, 8, + 64, 65, 80, 8, 56, 65, 79, 73, 69, 65, 81, 65, 79, 80, 18, 8, 110, 62, 65, 79, 8, + 64, 69, 65, 2, 41, 75, 79, 81, 132, 65, 81, 87, 82, 74, 67, 8, 64, 65, 80, 8, 48, + 69, 65, 81, 83, 65, 79, 68, 103, 72, 81, 74, 69, 132, 132, 65, 80, 18, 8, 61, 82, 63, + 68, 8, 84, 65, 74, 74, 8, 71, 65, 69, 74, 65, 8, 46, 110, 74, 64, 69, 67, 82, 74, + 67, 8, 83, 75, 79, 72, 69, 65, 67, 81, 18, 8, 62, 69, 80, 8, 87, 82, 79, 8, 39, + 61, 82, 65, 79, 8, 65, 69, 74, 65, 80, 2, 41, 75, 79, 81, 132, 65, 81, 87, 82, 74, + 67, 8, 64, 65, 80, 8, 48, 69, 65, 81, 83, 65, 79, 68, 103, 72, 81, 74, 69, 132, 132, + 65, 80, 18, 8, 61, 82, 63, 68, 8, 84, 65, 74, 74, 8, 71, 65, 69, 74, 65, 8, 46, + 110, 74, 64, 69, 67, 82, 74, 67, 8, 83, 75, 79, 72, 69, 65, 67, 81, 18, 8, 62, 69, + 80, 8, 87, 82, 79, 8, 39, 61, 82, 65, 79, 8, 65, 69, 74, 65, 80, 2, 45, 61, 68, + 79, 65, 80, 8, 132, 75, 84, 69, 65, 8, 110, 62, 65, 79, 8, 65, 69, 74, 65, 8, 40, + 79, 68, 109, 68, 82, 74, 67, 8, 64, 65, 80, 8, 48, 69, 65, 81, 87, 69, 74, 132, 65, + 80, 8, 14, 69, 73, 8, 41, 61, 72, 72, 65, 8, 64, 65, 79, 8, 41, 75, 79, 81, 132, + 65, 81, 87, 82, 74, 67, 15, 8, 87, 82, 8, 62, 65, 80, 81, 69, 73, 73, 65, 74, 18, + 2, 45, 61, 68, 79, 65, 80, 8, 132, 75, 84, 69, 65, 8, 110, 62, 65, 79, 8, 65, 69, + 74, 65, 8, 40, 79, 68, 109, 68, 82, 74, 67, 8, 64, 65, 80, 8, 48, 69, 65, 81, 87, + 69, 74, 132, 65, 80, 8, 14, 69, 73, 8, 41, 61, 72, 72, 65, 8, 64, 65, 79, 8, 41, + 75, 79, 81, 132, 65, 81, 87, 82, 74, 67, 15, 8, 87, 82, 8, 62, 65, 80, 81, 69, 73, + 73, 65, 74, 18, 2, 61, 82, 66, 8, 36, 74, 79, 82, 66, 65, 74, 8, 65, 69, 74, 65, + 80, 8, 56, 65, 79, 73, 69, 65, 81, 65, 79, 80, 8, 65, 69, 74, 65, 74, 8, 73, 69, + 81, 8, 65, 69, 74, 65, 73, 8, 74, 65, 82, 65, 74, 8, 48, 69, 65, 81, 65, 79, 8, + 61, 62, 67, 65, 132, 63, 68, 72, 75, 132, 132, 65, 74, 65, 74, 8, 48, 69, 65, 81, 83, + 65, 79, 81, 79, 61, 67, 18, 2, 61, 82, 66, 8, 36, 74, 79, 82, 66, 65, 74, 8, 65, + 69, 74, 65, 80, 8, 56, 65, 79, 73, 69, 65, 81, 65, 79, 80, 8, 65, 69, 74, 65, 74, + 8, 73, 69, 81, 8, 65, 69, 74, 65, 73, 8, 74, 65, 82, 65, 74, 8, 48, 69, 65, 81, + 65, 79, 8, 61, 62, 67, 65, 132, 63, 68, 72, 75, 132, 132, 65, 74, 65, 74, 8, 48, 69, + 65, 81, 83, 65, 79, 81, 79, 61, 67, 18, 2, 64, 65, 132, 132, 65, 74, 8, 40, 79, 66, + 110, 72, 72, 82, 74, 67, 8, 83, 75, 74, 8, 65, 69, 74, 65, 79, 8, 40, 74, 81, 132, + 63, 68, 65, 69, 64, 82, 74, 67, 8, 110, 62, 65, 79, 8, 64, 69, 65, 8, 64, 79, 65, + 69, 8, 65, 62, 65, 74, 8, 67, 65, 74, 61, 74, 74, 81, 65, 74, 8, 41, 103, 72, 72, + 65, 8, 75, 64, 65, 79, 8, 83, 75, 79, 8, 65, 69, 74, 65, 73, 2, 64, 65, 132, 132, + 65, 74, 8, 40, 79, 66, 110, 72, 72, 82, 74, 67, 8, 83, 75, 74, 8, 65, 69, 74, 65, + 79, 8, 40, 74, 81, 132, 63, 68, 65, 69, 64, 82, 74, 67, 8, 110, 62, 65, 79, 8, 64, + 69, 65, 8, 64, 79, 65, 69, 8, 65, 62, 65, 74, 8, 67, 65, 74, 61, 74, 74, 81, 65, + 74, 8, 41, 103, 72, 72, 65, 8, 75, 64, 65, 79, 8, 83, 75, 79, 8, 65, 69, 74, 65, + 73, 2, 56, 65, 79, 67, 72, 65, 69, 63, 68, 8, 83, 75, 79, 8, 64, 65, 73, 8, 48, + 69, 65, 81, 65, 69, 74, 69, 67, 82, 74, 67, 80, 61, 73, 81, 8, 67, 65, 81, 79, 75, + 66, 66, 65, 74, 8, 84, 69, 79, 64, 18, 8, 73, 69, 81, 8, 79, 110, 63, 71, 84, 69, + 79, 71, 65, 74, 64, 65, 79, 8, 46, 79, 61, 66, 81, 8, 61, 82, 66, 87, 82, 68, 65, + 62, 65, 74, 20, 2, 56, 65, 79, 67, 72, 65, 69, 63, 68, 8, 83, 75, 79, 8, 64, 65, + 73, 8, 48, 69, 65, 81, 65, 69, 74, 69, 67, 82, 74, 67, 80, 61, 73, 81, 8, 67, 65, + 81, 79, 75, 66, 66, 65, 74, 8, 84, 69, 79, 64, 18, 8, 73, 69, 81, 8, 79, 110, 63, + 71, 84, 69, 79, 71, 65, 74, 64, 65, 79, 8, 46, 79, 61, 66, 81, 8, 61, 82, 66, 87, + 82, 68, 65, 62, 65, 74, 20, 2, 41, 65, 79, 74, 65, 79, 8, 84, 82, 79, 64, 65, 8, + 64, 61, 80, 8, 48, 69, 65, 81, 65, 69, 74, 69, 67, 82, 74, 67, 80, 61, 73, 81, 8, + 69, 74, 8, 64, 69, 65, 8, 47, 61, 67, 65, 8, 67, 65, 132, 65, 81, 87, 81, 18, 8, + 64, 69, 65, 8, 40, 79, 72, 61, 82, 62, 74, 69, 80, 8, 64, 65, 80, 8, 56, 65, 79, + 4, 2, 41, 65, 79, 74, 65, 79, 8, 84, 82, 79, 64, 65, 8, 64, 61, 80, 8, 48, 69, + 65, 81, 65, 69, 74, 69, 67, 82, 74, 67, 80, 61, 73, 81, 8, 69, 74, 8, 64, 69, 65, + 8, 47, 61, 67, 65, 8, 67, 65, 132, 65, 81, 87, 81, 18, 8, 64, 69, 65, 8, 40, 79, + 72, 61, 82, 62, 74, 69, 80, 8, 64, 65, 80, 8, 56, 65, 79, 4, 2, 73, 69, 65, 81, + 65, 79, 80, 18, 8, 64, 65, 74, 8, 42, 65, 62, 79, 61, 82, 63, 68, 8, 64, 65, 79, + 8, 67, 65, 73, 69, 65, 81, 65, 81, 65, 74, 8, 53, 61, 63, 68, 65, 8, 65, 69, 74, + 65, 73, 8, 39, 79, 69, 81, 81, 65, 74, 8, 87, 82, 8, 110, 62, 65, 79, 72, 61, 132, + 132, 65, 74, 18, 8, 69, 74, 80, 62, 65, 132, 75, 74, 64, 65, 79, 65, 8, 64, 69, 65, + 2, 73, 69, 65, 81, 65, 79, 80, 18, 8, 64, 65, 74, 8, 42, 65, 62, 79, 61, 82, 63, + 68, 8, 64, 65, 79, 8, 67, 65, 73, 69, 65, 81, 65, 81, 65, 74, 8, 53, 61, 63, 68, + 65, 8, 65, 69, 74, 65, 73, 8, 39, 79, 69, 81, 81, 65, 74, 8, 87, 82, 8, 110, 62, + 65, 79, 72, 61, 132, 132, 65, 74, 18, 8, 69, 74, 80, 62, 65, 132, 75, 74, 64, 65, 79, + 65, 8, 64, 69, 65, 2, 53, 61, 63, 68, 65, 8, 84, 65, 69, 81, 65, 79, 8, 87, 82, + 8, 83, 65, 79, 73, 69, 65, 81, 65, 74, 8, 14, 91, 8, 27, 26, 31, 8, 36, 62, 132, + 20, 8, 23, 8, 64, 65, 80, 8, 37, 42, 37, 20, 15, 18, 8, 87, 82, 8, 65, 79, 132, + 65, 81, 87, 65, 74, 20, 8, 36, 82, 66, 8, 42, 79, 82, 74, 64, 8, 62, 65, 132, 75, + 74, 64, 65, 79, 65, 79, 2, 53, 61, 63, 68, 65, 8, 84, 65, 69, 81, 65, 79, 8, 87, + 82, 8, 83, 65, 79, 73, 69, 65, 81, 65, 74, 8, 14, 91, 8, 27, 26, 31, 8, 36, 62, + 132, 20, 8, 23, 8, 64, 65, 80, 8, 37, 42, 37, 20, 15, 18, 8, 87, 82, 8, 65, 79, + 132, 65, 81, 87, 65, 74, 20, 8, 36, 82, 66, 8, 42, 79, 82, 74, 64, 8, 62, 65, 132, + 75, 74, 64, 65, 79, 65, 79, 2, 39, 82, 79, 63, 68, 8, 64, 61, 80, 8, 42, 65, 132, + 65, 81, 87, 8, 83, 75, 73, 8, 23, 23, 20, 8, 48, 61, 69, 8, 23, 31, 24, 22, 8, + 84, 82, 79, 64, 65, 8, 64, 69, 65, 8, 37, 65, 71, 61, 74, 74, 81, 73, 61, 63, 68, + 82, 74, 67, 8, 87, 82, 73, 8, 53, 63, 68, 82, 81, 87, 65, 8, 64, 65, 79, 2, 39, + 82, 79, 63, 68, 8, 64, 61, 80, 8, 42, 65, 132, 65, 81, 87, 8, 83, 75, 73, 8, 23, + 23, 20, 8, 48, 61, 69, 8, 23, 31, 24, 22, 8, 84, 82, 79, 64, 65, 8, 64, 69, 65, + 8, 37, 65, 71, 61, 74, 74, 81, 73, 61, 63, 68, 82, 74, 67, 8, 87, 82, 73, 8, 53, + 63, 68, 82, 81, 87, 65, 8, 64, 65, 79, 2, 48, 69, 65, 81, 65, 79, 8, 83, 75, 73, + 8, 14, 24, 25, 20, 8, 53, 65, 78, 81, 65, 73, 62, 65, 79, 8, 23, 31, 23, 30, 8, + 21, 8, 24, 24, 20, 8, 45, 82, 74, 69, 8, 23, 31, 23, 31, 15, 8, 110, 62, 65, 79, + 8, 64, 65, 74, 8, 25, 23, 20, 8, 39, 65, 87, 65, 73, 62, 65, 79, 8, 23, 31, 24, + 22, 8, 68, 69, 74, 61, 82, 80, 2, 48, 69, 65, 81, 65, 79, 8, 83, 75, 73, 8, 14, + 24, 25, 20, 8, 53, 65, 78, 81, 65, 73, 62, 65, 79, 8, 23, 31, 23, 30, 8, 21, 8, + 24, 24, 20, 8, 45, 82, 74, 69, 8, 23, 31, 23, 31, 15, 8, 110, 62, 65, 79, 8, 64, + 65, 74, 8, 25, 23, 20, 8, 39, 65, 87, 65, 73, 62, 65, 79, 8, 23, 31, 24, 22, 8, + 68, 69, 74, 61, 82, 80, 2, 62, 69, 80, 8, 61, 82, 66, 8, 84, 65, 69, 81, 65, 79, + 65, 80, 8, 83, 65, 79, 72, 103, 74, 67, 65, 79, 81, 8, 82, 74, 64, 8, 64, 61, 80, + 8, 40, 69, 74, 69, 67, 82, 74, 67, 80, 61, 73, 81, 8, 87, 82, 79, 8, 40, 74, 81, + 132, 63, 68, 65, 69, 64, 82, 74, 67, 8, 110, 62, 65, 79, 8, 37, 65, 132, 63, 68, 84, + 65, 79, 64, 65, 74, 8, 67, 65, 67, 65, 74, 2, 62, 69, 80, 8, 61, 82, 66, 8, 84, + 65, 69, 81, 65, 79, 65, 80, 8, 83, 65, 79, 72, 103, 74, 67, 65, 79, 81, 8, 82, 74, + 64, 8, 64, 61, 80, 8, 40, 69, 74, 69, 67, 82, 74, 67, 80, 61, 73, 81, 8, 87, 82, + 79, 8, 40, 74, 81, 132, 63, 68, 65, 69, 64, 82, 74, 67, 8, 110, 62, 65, 79, 8, 37, + 65, 132, 63, 68, 84, 65, 79, 64, 65, 74, 8, 67, 65, 67, 65, 74, 2, 65, 69, 74, 65, + 8, 83, 75, 74, 8, 64, 65, 79, 8, 42, 65, 73, 65, 69, 74, 64, 65, 62, 65, 68, 109, + 79, 64, 65, 8, 61, 82, 66, 8, 42, 79, 82, 74, 64, 8, 64, 65, 79, 8, 57, 75, 68, + 74, 82, 74, 67, 80, 73, 61, 74, 67, 65, 72, 83, 65, 79, 75, 79, 64, 74, 82, 74, 67, + 8, 69, 73, 8, 40, 69, 74, 87, 65, 72, 66, 61, 72, 72, 65, 2, 65, 69, 74, 65, 8, + 83, 75, 74, 8, 64, 65, 79, 8, 42, 65, 73, 65, 69, 74, 64, 65, 62, 65, 68, 109, 79, + 64, 65, 8, 61, 82, 66, 8, 42, 79, 82, 74, 64, 8, 64, 65, 79, 8, 57, 75, 68, 74, + 82, 74, 67, 80, 73, 61, 74, 67, 65, 72, 83, 65, 79, 75, 79, 64, 74, 82, 74, 67, 8, + 69, 73, 8, 40, 69, 74, 87, 65, 72, 66, 61, 72, 72, 65, 2, 67, 65, 81, 79, 75, 66, + 66, 65, 74, 65, 8, 42, 65, 79, 66, 110, 74, 82, 74, 67, 8, 66, 110, 79, 8, 87, 82, + 132, 81, 103, 74, 64, 69, 67, 8, 65, 79, 71, 72, 103, 79, 81, 20, 2, 67, 65, 81, 79, + 75, 66, 66, 65, 74, 65, 8, 42, 65, 79, 66, 110, 74, 82, 74, 67, 8, 66, 110, 79, 8, + 87, 82, 132, 81, 103, 74, 64, 69, 67, 8, 65, 79, 71, 72, 103, 79, 81, 20, 2, 53, 75, + 64, 61, 74, 74, 8, 132, 69, 74, 64, 8, 64, 69, 65, 8, 40, 69, 74, 69, 67, 82, 74, + 67, 80, 103, 73, 81, 65, 79, 8, 67, 65, 73, 103, 102, 8, 91, 8, 29, 26, 8, 64, 65, + 79, 8, 36, 82, 80, 66, 110, 68, 79, 82, 74, 67, 80, 62, 65, 132, 81, 69, 73, 73, 82, + 74, 67, 65, 74, 8, 87, 82, 73, 2, 53, 75, 64, 61, 74, 74, 8, 132, 69, 74, 64, 8, + 64, 69, 65, 8, 40, 69, 74, 69, 67, 82, 74, 67, 80, 103, 73, 81, 65, 79, 8, 67, 65, + 73, 103, 102, 8, 91, 8, 29, 26, 8, 64, 65, 79, 8, 36, 82, 80, 66, 110, 68, 79, 82, + 74, 67, 80, 62, 65, 132, 81, 69, 73, 73, 82, 74, 67, 65, 74, 8, 87, 82, 73, 2, 46, + 75, 68, 72, 65, 74, 132, 81, 65, 82, 65, 79, 67, 65, 132, 65, 81, 87, 8, 14, 83, 75, + 73, 8, 30, 20, 8, 36, 78, 79, 69, 72, 8, 23, 31, 23, 29, 15, 8, 61, 72, 80, 8, + 53, 63, 68, 69, 65, 64, 80, 132, 81, 65, 72, 72, 65, 8, 65, 79, 79, 69, 63, 68, 81, + 65, 81, 8, 66, 110, 79, 8, 64, 69, 65, 8, 61, 82, 80, 8, 64, 65, 79, 8, 36, 74, + 4, 2, 46, 75, 68, 72, 65, 74, 132, 81, 65, 82, 65, 79, 67, 65, 132, 65, 81, 87, 8, + 14, 83, 75, 73, 8, 30, 20, 8, 36, 78, 79, 69, 72, 8, 23, 31, 23, 29, 15, 8, 61, + 72, 80, 8, 53, 63, 68, 69, 65, 64, 80, 132, 81, 65, 72, 72, 65, 8, 65, 79, 79, 69, + 63, 68, 81, 65, 81, 8, 66, 110, 79, 8, 64, 69, 65, 8, 61, 82, 80, 8, 64, 65, 79, + 8, 36, 74, 4, 2, 84, 65, 74, 64, 82, 74, 67, 8, 64, 65, 80, 8, 91, 8, 25, 29, + 8, 36, 62, 132, 20, 8, 25, 8, 64, 65, 80, 8, 46, 75, 68, 72, 65, 74, 132, 81, 65, + 82, 65, 79, 74, 65, 132, 65, 81, 87, 65, 80, 8, 68, 65, 79, 79, 110, 68, 79, 65, 74, + 64, 65, 74, 8, 53, 81, 79, 65, 69, 81, 69, 67, 71, 65, 69, 81, 65, 74, 8, 87, 84, + 69, 132, 63, 68, 65, 74, 2, 84, 65, 74, 64, 82, 74, 67, 8, 64, 65, 80, 8, 91, 8, + 25, 29, 8, 36, 62, 132, 20, 8, 25, 8, 64, 65, 80, 8, 46, 75, 68, 72, 65, 74, 132, + 81, 65, 82, 65, 79, 74, 65, 132, 65, 81, 87, 65, 80, 8, 68, 65, 79, 79, 110, 68, 79, + 65, 74, 64, 65, 74, 8, 53, 81, 79, 65, 69, 81, 69, 67, 71, 65, 69, 81, 65, 74, 8, + 87, 84, 69, 132, 63, 68, 65, 74, 2, 110, 62, 65, 79, 8, 53, 61, 73, 73, 65, 72, 68, + 65, 69, 87, 82, 74, 67, 8, 82, 74, 64, 8, 57, 61, 79, 73, 84, 61, 132, 132, 65, 79, + 83, 65, 79, 132, 75, 79, 67, 82, 74, 67, 80, 61, 74, 72, 61, 67, 65, 74, 8, 69, 74, + 8, 48, 69, 65, 81, 79, 103, 82, 73, 65, 74, 8, 83, 75, 73, 8, 24, 20, 8, 49, 75, + 83, 65, 73, 62, 65, 79, 2, 110, 62, 65, 79, 8, 53, 61, 73, 73, 65, 72, 68, 65, 69, + 87, 82, 74, 67, 8, 82, 74, 64, 8, 57, 61, 79, 73, 84, 61, 132, 132, 65, 79, 83, 65, + 79, 132, 75, 79, 67, 82, 74, 67, 80, 61, 74, 72, 61, 67, 65, 74, 8, 69, 74, 8, 48, + 69, 65, 81, 79, 103, 82, 73, 65, 74, 8, 83, 75, 73, 8, 24, 20, 8, 49, 75, 83, 65, + 73, 62, 65, 79, 2, 23, 31, 23, 29, 8, 62, 65, 132, 81, 69, 73, 73, 81, 65, 8, 64, + 69, 65, 8, 48, 69, 65, 81, 65, 69, 74, 69, 67, 82, 74, 67, 80, 103, 73, 81, 65, 79, + 8, 61, 72, 80, 8, 53, 63, 68, 69, 65, 64, 80, 132, 81, 65, 72, 72, 65, 74, 8, 66, + 110, 79, 8, 53, 81, 79, 65, 69, 81, 69, 67, 71, 65, 69, 81, 65, 74, 18, 2, 23, 31, + 23, 29, 8, 62, 65, 132, 81, 69, 73, 73, 81, 65, 8, 64, 69, 65, 8, 48, 69, 65, 81, + 65, 69, 74, 69, 67, 82, 74, 67, 80, 103, 73, 81, 65, 79, 8, 61, 72, 80, 8, 53, 63, + 68, 69, 65, 64, 80, 132, 81, 65, 72, 72, 65, 74, 8, 66, 110, 79, 8, 53, 81, 79, 65, + 69, 81, 69, 67, 71, 65, 69, 81, 65, 74, 18, 2, 56, 65, 79, 75, 79, 64, 74, 82, 74, + 67, 8, 110, 62, 65, 79, 8, 48, 69, 65, 81, 65, 79, 132, 63, 68, 82, 81, 87, 8, 82, + 74, 64, 8, 53, 61, 73, 73, 65, 72, 68, 65, 69, 87, 82, 74, 67, 8, 65, 79, 67, 69, + 74, 67, 8, 61, 73, 8, 24, 24, 20, 8, 45, 82, 74, 69, 8, 23, 31, 23, 31, 20, 8, + 3, 8, 3, 2, 56, 65, 79, 75, 79, 64, 74, 82, 74, 67, 8, 110, 62, 65, 79, 8, 48, + 69, 65, 81, 65, 79, 132, 63, 68, 82, 81, 87, 8, 82, 74, 64, 8, 53, 61, 73, 73, 65, + 72, 68, 65, 69, 87, 82, 74, 67, 8, 65, 79, 67, 69, 74, 67, 8, 61, 73, 8, 24, 24, + 20, 8, 45, 82, 74, 69, 8, 23, 31, 23, 31, 20, 8, 3, 8, 3, 2, 46, 75, 68, 72, + 65, 74, 132, 81, 65, 82, 65, 79, 19, 40, 79, 132, 81, 61, 81, 81, 82, 74, 67, 80, 61, + 74, 132, 78, 79, 110, 63, 68, 65, 8, 64, 65, 79, 8, 56, 65, 79, 73, 69, 65, 81, 65, + 79, 8, 61, 72, 80, 8, 61, 82, 63, 68, 8, 48, 69, 74, 64, 65, 79, 82, 74, 67, 80, + 61, 74, 132, 78, 79, 110, 63, 68, 65, 8, 64, 65, 79, 8, 48, 69, 69, 2, 46, 75, 68, + 72, 65, 74, 132, 81, 65, 82, 65, 79, 19, 40, 79, 132, 81, 61, 81, 81, 82, 74, 67, 80, + 61, 74, 132, 78, 79, 110, 63, 68, 65, 8, 64, 65, 79, 8, 56, 65, 79, 73, 69, 65, 81, + 65, 79, 8, 61, 72, 80, 8, 61, 82, 63, 68, 8, 48, 69, 74, 64, 65, 79, 82, 74, 67, + 80, 61, 74, 132, 78, 79, 110, 63, 68, 65, 8, 64, 65, 79, 8, 48, 69, 69, 2, 52, 65, + 61, 72, 132, 63, 68, 82, 72, 65, 8, 44, 44, 44, 8, 84, 82, 79, 64, 65, 8, 48, 69, + 63, 68, 61, 65, 72, 69, 80, 8, 23, 31, 23, 28, 8, 64, 69, 65, 8, 44, 56, 8, 48, + 18, 8, 48, 69, 63, 68, 61, 65, 72, 69, 80, 8, 23, 31, 23, 29, 8, 64, 69, 65, 8, + 55, 8, 44, 44, 44, 8, 48, 8, 82, 74, 64, 2, 52, 65, 61, 72, 132, 63, 68, 82, 72, + 65, 8, 44, 44, 44, 8, 84, 82, 79, 64, 65, 8, 48, 69, 63, 68, 61, 65, 72, 69, 80, + 8, 23, 31, 23, 28, 8, 64, 69, 65, 8, 44, 56, 8, 48, 18, 8, 48, 69, 63, 68, 61, + 65, 72, 69, 80, 8, 23, 31, 23, 29, 8, 64, 69, 65, 8, 55, 8, 44, 44, 44, 8, 48, + 8, 82, 74, 64, 2, 48, 69, 63, 68, 61, 65, 72, 69, 80, 8, 23, 31, 23, 31, 8, 64, + 69, 65, 8, 50, 8, 44, 44, 44, 8, 48, 8, 65, 79, 109, 66, 66, 74, 65, 81, 20, 8, + 41, 65, 79, 74, 65, 79, 8, 84, 82, 79, 64, 65, 74, 8, 50, 132, 81, 65, 79, 74, 8, + 23, 31, 23, 30, 18, 8, 23, 31, 23, 31, 8, 82, 74, 64, 8, 23, 31, 24, 22, 8, 65, + 69, 74, 65, 2, 48, 69, 63, 68, 61, 65, 72, 69, 80, 8, 23, 31, 23, 31, 8, 64, 69, + 65, 8, 50, 8, 44, 44, 44, 8, 48, 8, 65, 79, 109, 66, 66, 74, 65, 81, 20, 8, 41, + 65, 79, 74, 65, 79, 8, 84, 82, 79, 64, 65, 74, 8, 50, 132, 81, 65, 79, 74, 8, 23, + 31, 23, 30, 18, 8, 23, 31, 23, 31, 8, 82, 74, 64, 8, 23, 31, 24, 22, 8, 65, 69, + 74, 65, 2, 56, 44, 8, 50, 18, 8, 56, 50, 8, 82, 74, 64, 8, 44, 56, 8, 50, 8, + 65, 79, 109, 66, 66, 74, 65, 81, 18, 8, 82, 73, 8, 14, 51, 72, 61, 81, 87, 8, 66, + 110, 79, 8, 64, 69, 65, 8, 53, 63, 68, 110, 72, 65, 79, 8, 87, 82, 8, 132, 63, 68, + 61, 66, 66, 65, 74, 15, 18, 8, 64, 69, 65, 8, 84, 65, 67, 65, 74, 8, 51, 72, 61, + 81, 87, 73, 61, 74, 67, 65, 72, 80, 2, 56, 44, 8, 50, 18, 8, 56, 50, 8, 82, 74, + 64, 8, 44, 56, 8, 50, 8, 65, 79, 109, 66, 66, 74, 65, 81, 18, 8, 82, 73, 8, 14, + 51, 72, 61, 81, 87, 8, 66, 110, 79, 8, 64, 69, 65, 8, 53, 63, 68, 110, 72, 65, 79, + 8, 87, 82, 8, 132, 63, 68, 61, 66, 66, 65, 74, 15, 18, 8, 64, 69, 65, 8, 84, 65, + 67, 65, 74, 8, 51, 72, 61, 81, 87, 73, 61, 74, 67, 65, 72, 80, 2, 69, 74, 8, 61, + 74, 64, 65, 79, 65, 8, 68, 109, 68, 65, 79, 65, 8, 53, 63, 68, 82, 72, 65, 74, 8, + 74, 69, 63, 68, 81, 8, 61, 82, 66, 67, 65, 74, 75, 73, 73, 65, 74, 8, 84, 65, 79, + 64, 65, 74, 8, 71, 75, 74, 74, 81, 65, 74, 20, 2, 69, 74, 8, 61, 74, 64, 65, 79, + 65, 8, 68, 109, 68, 65, 79, 65, 8, 53, 63, 68, 82, 72, 65, 74, 8, 74, 69, 63, 68, + 81, 8, 61, 82, 66, 67, 65, 74, 75, 73, 73, 65, 74, 8, 84, 65, 79, 64, 65, 74, 8, + 71, 75, 74, 74, 81, 65, 74, 20, 2, 40, 74, 64, 65, 8, 23, 31, 23, 30, 8, 84, 61, + 79, 65, 74, 8, 61, 72, 132, 75, 8, 66, 75, 72, 67, 65, 74, 64, 65, 8, 132, 81, 103, + 64, 81, 69, 132, 63, 68, 65, 8, 53, 63, 68, 82, 72, 65, 74, 8, 83, 75, 79, 68, 61, + 74, 64, 65, 74, 32, 8, 39, 61, 80, 8, 48, 75, 73, 73, 132, 65, 74, 4, 2, 40, 74, + 64, 65, 8, 23, 31, 23, 30, 8, 84, 61, 79, 65, 74, 8, 61, 72, 132, 75, 8, 66, 75, + 72, 67, 65, 74, 64, 65, 8, 132, 81, 103, 64, 81, 69, 132, 63, 68, 65, 8, 53, 63, 68, + 82, 72, 65, 74, 8, 83, 75, 79, 68, 61, 74, 64, 65, 74, 32, 8, 39, 61, 80, 8, 48, + 75, 73, 73, 132, 65, 74, 4, 2, 67, 86, 73, 74, 61, 132, 69, 82, 73, 8, 73, 69, 81, + 8, 52, 65, 61, 72, 132, 63, 68, 82, 72, 65, 18, 8, 64, 61, 80, 8, 53, 63, 68, 69, + 72, 72, 65, 79, 4, 52, 65, 61, 72, 67, 86, 73, 74, 61, 132, 69, 82, 73, 18, 8, 64, + 69, 65, 8, 53, 69, 65, 73, 65, 74, 80, 4, 2, 67, 86, 73, 74, 61, 132, 69, 82, 73, + 8, 73, 69, 81, 8, 52, 65, 61, 72, 132, 63, 68, 82, 72, 65, 18, 8, 64, 61, 80, 8, + 53, 63, 68, 69, 72, 72, 65, 79, 4, 52, 65, 61, 72, 67, 86, 73, 74, 61, 132, 69, 82, + 73, 18, 8, 64, 69, 65, 8, 53, 69, 65, 73, 65, 74, 80, 4, 2, 82, 74, 64, 8, 64, + 69, 65, 8, 47, 65, 69, 62, 74, 69, 87, 4, 50, 62, 65, 79, 79, 65, 61, 72, 132, 63, + 68, 82, 72, 65, 18, 8, 64, 69, 65, 8, 43, 69, 74, 64, 65, 74, 62, 82, 79, 67, 4, + 52, 65, 61, 72, 132, 63, 68, 82, 72, 65, 18, 8, 64, 69, 65, 2, 82, 74, 64, 8, 64, + 69, 65, 8, 47, 65, 69, 62, 74, 69, 87, 4, 50, 62, 65, 79, 79, 65, 61, 72, 132, 63, + 68, 82, 72, 65, 18, 8, 64, 69, 65, 8, 43, 69, 74, 64, 65, 74, 62, 82, 79, 67, 4, + 52, 65, 61, 72, 132, 63, 68, 82, 72, 65, 18, 8, 64, 69, 65, 2, 52, 65, 61, 72, 132, + 63, 68, 82, 72, 65, 8, 44, 44, 8, 82, 74, 64, 8, 64, 69, 65, 8, 52, 65, 61, 72, + 132, 63, 68, 82, 72, 65, 8, 44, 44, 44, 8, 73, 69, 81, 8, 64, 65, 74, 8, 46, 72, + 61, 132, 132, 65, 74, 8, 56, 44, 8, 62, 69, 80, 8, 50, 8, 44, 44, 44, 33, 8, 64, + 69, 65, 8, 46, 61, 69, 132, 65, 79, 4, 2, 52, 65, 61, 72, 132, 63, 68, 82, 72, 65, + 8, 44, 44, 8, 82, 74, 64, 8, 64, 69, 65, 8, 52, 65, 61, 72, 132, 63, 68, 82, 72, + 65, 8, 44, 44, 44, 8, 73, 69, 81, 8, 64, 65, 74, 8, 46, 72, 61, 132, 132, 65, 74, + 8, 56, 44, 8, 62, 69, 80, 8, 50, 8, 44, 44, 44, 33, 8, 64, 69, 65, 8, 46, 61, + 69, 132, 65, 79, 4, 2, 41, 79, 69, 65, 64, 79, 69, 63, 68, 132, 63, 68, 82, 72, 65, + 8, 14, 42, 86, 73, 74, 61, 132, 69, 82, 73, 8, 82, 74, 64, 8, 52, 65, 61, 72, 132, + 63, 68, 82, 72, 65, 8, 73, 69, 81, 8, 67, 65, 73, 65, 69, 74, 132, 61, 73, 65, 73, + 8, 55, 74, 81, 65, 79, 62, 61, 82, 15, 8, 82, 74, 64, 8, 64, 69, 65, 2, 41, 79, + 69, 65, 64, 79, 69, 63, 68, 132, 63, 68, 82, 72, 65, 8, 14, 42, 86, 73, 74, 61, 132, + 69, 82, 73, 8, 82, 74, 64, 8, 52, 65, 61, 72, 132, 63, 68, 82, 72, 65, 8, 73, 69, + 81, 8, 67, 65, 73, 65, 69, 74, 132, 61, 73, 65, 73, 8, 55, 74, 81, 65, 79, 62, 61, + 82, 15, 8, 82, 74, 64, 8, 64, 69, 65, 2, 43, 65, 79, 64, 65, 79, 132, 63, 68, 82, + 72, 65, 8, 14, 52, 65, 61, 72, 67, 86, 73, 74, 61, 132, 69, 82, 73, 8, 82, 74, 64, + 8, 52, 65, 61, 72, 132, 63, 68, 82, 72, 65, 8, 73, 69, 81, 8, 67, 65, 73, 65, 69, + 74, 132, 61, 73, 65, 73, 8, 55, 74, 81, 65, 79, 62, 61, 82, 15, 20, 8, 44, 74, 8, + 64, 65, 74, 2, 43, 65, 79, 64, 65, 79, 132, 63, 68, 82, 72, 65, 8, 14, 52, 65, 61, + 72, 67, 86, 73, 74, 61, 132, 69, 82, 73, 8, 82, 74, 64, 8, 52, 65, 61, 72, 132, 63, + 68, 82, 72, 65, 8, 73, 69, 81, 8, 67, 65, 73, 65, 69, 74, 132, 61, 73, 65, 73, 8, + 55, 74, 81, 65, 79, 62, 61, 82, 15, 20, 8, 44, 74, 8, 64, 65, 74, 2, 65, 79, 132, + 81, 65, 74, 8, 29, 8, 53, 63, 68, 82, 72, 65, 74, 8, 84, 82, 79, 64, 65, 8, 74, + 61, 63, 68, 8, 64, 65, 74, 8, 61, 72, 72, 67, 65, 73, 65, 69, 74, 65, 74, 8, 47, + 65, 68, 79, 78, 72, 103, 74, 65, 74, 18, 8, 69, 74, 8, 64, 65, 74, 8, 72, 65, 81, + 87, 81, 65, 74, 8, 62, 65, 69, 64, 65, 74, 8, 74, 61, 63, 68, 8, 64, 65, 74, 2, + 65, 79, 132, 81, 65, 74, 8, 29, 8, 53, 63, 68, 82, 72, 65, 74, 8, 84, 82, 79, 64, + 65, 8, 74, 61, 63, 68, 8, 64, 65, 74, 8, 61, 72, 72, 67, 65, 73, 65, 69, 74, 65, + 74, 8, 47, 65, 68, 79, 78, 72, 103, 74, 65, 74, 18, 8, 69, 74, 8, 64, 65, 74, 8, + 72, 65, 81, 87, 81, 65, 74, 8, 62, 65, 69, 64, 65, 74, 8, 74, 61, 63, 68, 8, 64, + 65, 74, 2, 41, 79, 61, 74, 71, 66, 82, 79, 81, 65, 79, 8, 47, 65, 68, 79, 78, 72, + 103, 74, 65, 74, 8, 82, 74, 81, 65, 79, 79, 69, 63, 68, 81, 65, 81, 20, 2, 41, 79, + 61, 74, 71, 66, 82, 79, 81, 65, 79, 8, 47, 65, 68, 79, 78, 72, 103, 74, 65, 74, 8, + 82, 74, 81, 65, 79, 79, 69, 63, 68, 81, 65, 81, 20, 2, 54, 79, 75, 81, 87, 8, 87, + 61, 68, 72, 79, 65, 69, 63, 68, 65, 79, 8, 40, 69, 74, 62, 65, 79, 82, 66, 82, 74, + 67, 65, 74, 8, 83, 75, 74, 8, 47, 65, 68, 79, 71, 79, 103, 66, 81, 65, 74, 8, 87, + 82, 73, 8, 43, 65, 65, 79, 65, 80, 64, 69, 65, 74, 132, 81, 65, 8, 71, 75, 74, 74, + 81, 65, 8, 64, 65, 79, 8, 55, 74, 81, 65, 79, 79, 69, 63, 68, 81, 80, 62, 65, 81, + 79, 69, 65, 62, 2, 54, 79, 75, 81, 87, 8, 87, 61, 68, 72, 79, 65, 69, 63, 68, 65, + 79, 8, 40, 69, 74, 62, 65, 79, 82, 66, 82, 74, 67, 65, 74, 8, 83, 75, 74, 8, 47, + 65, 68, 79, 71, 79, 103, 66, 81, 65, 74, 8, 87, 82, 73, 8, 43, 65, 65, 79, 65, 80, + 64, 69, 65, 74, 132, 81, 65, 8, 71, 75, 74, 74, 81, 65, 8, 64, 65, 79, 8, 55, 74, + 81, 65, 79, 79, 69, 63, 68, 81, 80, 62, 65, 81, 79, 69, 65, 62, 2, 61, 74, 8, 61, + 72, 72, 65, 74, 8, 68, 109, 68, 65, 79, 65, 74, 8, 46, 74, 61, 62, 65, 74, 132, 63, + 68, 82, 72, 65, 74, 8, 61, 82, 66, 79, 65, 63, 68, 81, 8, 65, 79, 68, 61, 72, 81, + 65, 74, 8, 84, 65, 79, 64, 65, 74, 20, 2, 61, 74, 8, 61, 72, 72, 65, 74, 8, 68, + 109, 68, 65, 79, 65, 74, 8, 46, 74, 61, 62, 65, 74, 132, 63, 68, 82, 72, 65, 74, 8, + 61, 82, 66, 79, 65, 63, 68, 81, 8, 65, 79, 68, 61, 72, 81, 65, 74, 8, 84, 65, 79, + 64, 65, 74, 20, 2, 39, 69, 65, 8, 37, 65, 132, 63, 68, 61, 66, 66, 82, 74, 67, 8, + 83, 75, 74, 8, 43, 69, 72, 66, 80, 72, 65, 68, 79, 71, 79, 103, 66, 81, 65, 74, 8, + 67, 65, 132, 81, 61, 72, 81, 65, 81, 65, 8, 132, 69, 63, 68, 2, 39, 69, 65, 8, 37, + 65, 132, 63, 68, 61, 66, 66, 82, 74, 67, 8, 83, 75, 74, 8, 43, 69, 72, 66, 80, 72, + 65, 68, 79, 71, 79, 103, 66, 81, 65, 74, 8, 67, 65, 132, 81, 61, 72, 81, 65, 81, 65, + 8, 132, 69, 63, 68, 2, 61, 62, 65, 79, 8, 132, 65, 68, 79, 8, 132, 63, 68, 84, 69, + 65, 79, 69, 67, 20, 8, 14, 44, 73, 8, 57, 69, 74, 81, 65, 79, 68, 61, 72, 62, 70, + 61, 68, 79, 8, 23, 31, 23, 30, 8, 84, 82, 79, 64, 65, 74, 8, 73, 65, 68, 79, 65, + 79, 65, 8, 53, 63, 68, 82, 72, 65, 74, 2, 61, 62, 65, 79, 8, 132, 65, 68, 79, 8, + 132, 63, 68, 84, 69, 65, 79, 69, 67, 20, 8, 14, 44, 73, 8, 57, 69, 74, 81, 65, 79, + 68, 61, 72, 62, 70, 61, 68, 79, 8, 23, 31, 23, 30, 8, 84, 82, 79, 64, 65, 74, 8, + 73, 65, 68, 79, 65, 79, 65, 8, 53, 63, 68, 82, 72, 65, 74, 2, 87, 82, 79, 8, 55, + 74, 81, 65, 79, 62, 79, 69, 74, 67, 82, 74, 67, 8, 83, 75, 74, 8, 48, 69, 72, 69, + 81, 103, 79, 18, 8, 74, 61, 73, 65, 74, 81, 72, 69, 63, 68, 8, 74, 61, 63, 68, 8, + 64, 65, 73, 8, 40, 69, 74, 81, 79, 65, 66, 66, 65, 74, 8, 64, 65, 79, 8, 41, 65, + 72, 64, 81, 79, 82, 78, 78, 65, 74, 18, 2, 87, 82, 79, 8, 55, 74, 81, 65, 79, 62, + 79, 69, 74, 67, 82, 74, 67, 8, 83, 75, 74, 8, 48, 69, 72, 69, 81, 103, 79, 18, 8, + 74, 61, 73, 65, 74, 81, 72, 69, 63, 68, 8, 74, 61, 63, 68, 8, 64, 65, 73, 8, 40, + 69, 74, 81, 79, 65, 66, 66, 65, 74, 8, 64, 65, 79, 8, 41, 65, 72, 64, 81, 79, 82, + 78, 78, 65, 74, 18, 2, 69, 74, 8, 36, 74, 132, 78, 79, 82, 63, 68, 8, 67, 65, 74, + 75, 73, 73, 65, 74, 20, 15, 8, 44, 74, 66, 75, 72, 67, 65, 64, 65, 132, 132, 65, 74, + 8, 73, 82, 102, 81, 65, 74, 8, 61, 74, 64, 65, 79, 65, 8, 53, 63, 68, 82, 72, 67, + 65, 62, 103, 82, 64, 65, 8, 64, 75, 78, 78, 65, 72, 81, 8, 62, 65, 72, 65, 67, 81, + 18, 2, 69, 74, 8, 36, 74, 132, 78, 79, 82, 63, 68, 8, 67, 65, 74, 75, 73, 73, 65, + 74, 20, 15, 8, 44, 74, 66, 75, 72, 67, 65, 64, 65, 132, 132, 65, 74, 8, 73, 82, 102, + 81, 65, 74, 8, 61, 74, 64, 65, 79, 65, 8, 53, 63, 68, 82, 72, 67, 65, 62, 103, 82, + 64, 65, 8, 64, 75, 78, 78, 65, 72, 81, 8, 62, 65, 72, 65, 67, 81, 18, 2, 64, 65, + 79, 8, 55, 74, 81, 65, 79, 79, 69, 63, 68, 81, 8, 61, 82, 66, 8, 25, 22, 8, 46, + 82, 79, 87, 132, 81, 82, 74, 64, 65, 74, 8, 62, 65, 132, 63, 68, 79, 103, 74, 71, 81, + 8, 82, 74, 64, 8, 69, 74, 8, 56, 75, 79, 73, 69, 81, 81, 61, 67, 80, 4, 8, 82, + 74, 64, 8, 49, 61, 63, 68, 4, 2, 64, 65, 79, 8, 55, 74, 81, 65, 79, 79, 69, 63, + 68, 81, 8, 61, 82, 66, 8, 25, 22, 8, 46, 82, 79, 87, 132, 81, 82, 74, 64, 65, 74, + 8, 62, 65, 132, 63, 68, 79, 103, 74, 71, 81, 8, 82, 74, 64, 8, 69, 74, 8, 56, 75, + 79, 73, 69, 81, 81, 61, 67, 80, 4, 8, 82, 74, 64, 8, 49, 61, 63, 68, 4, 2, 73, + 69, 81, 81, 61, 67, 80, 82, 74, 81, 65, 79, 79, 69, 63, 68, 81, 8, 67, 65, 81, 65, + 69, 72, 81, 8, 84, 65, 79, 64, 65, 74, 20, 2, 73, 69, 81, 81, 61, 67, 80, 82, 74, + 81, 65, 79, 79, 69, 63, 68, 81, 8, 67, 65, 81, 65, 69, 72, 81, 8, 84, 65, 79, 64, + 65, 74, 20, 2, 36, 82, 66, 8, 42, 65, 73, 65, 69, 74, 64, 65, 62, 65, 132, 63, 68, + 72, 82, 102, 8, 83, 75, 73, 8, 26, 20, 21, 23, 24, 20, 8, 24, 20, 8, 23, 31, 24, + 22, 8, 84, 82, 79, 64, 65, 8, 50, 132, 81, 65, 79, 74, 8, 23, 31, 24, 22, 8, 73, + 69, 81, 8, 64, 65, 73, 8, 36, 62, 62, 61, 82, 8, 64, 65, 79, 8, 56, 75, 79, 132, + 63, 68, 82, 72, 65, 74, 2, 36, 82, 66, 8, 42, 65, 73, 65, 69, 74, 64, 65, 62, 65, + 132, 63, 68, 72, 82, 102, 8, 83, 75, 73, 8, 26, 20, 21, 23, 24, 20, 8, 24, 20, 8, + 23, 31, 24, 22, 8, 84, 82, 79, 64, 65, 8, 50, 132, 81, 65, 79, 74, 8, 23, 31, 24, + 22, 8, 73, 69, 81, 8, 64, 65, 73, 8, 36, 62, 62, 61, 82, 8, 64, 65, 79, 8, 56, + 75, 79, 132, 63, 68, 82, 72, 65, 74, 2, 62, 65, 67, 75, 74, 74, 65, 74, 20, 8, 14, + 60, 82, 79, 8, 65, 79, 132, 81, 65, 74, 8, 36, 82, 80, 132, 81, 61, 81, 81, 82, 74, + 67, 8, 73, 69, 81, 8, 55, 74, 81, 65, 79, 79, 69, 63, 68, 81, 80, 73, 69, 81, 81, + 65, 72, 74, 8, 65, 79, 68, 69, 65, 72, 81, 8, 64, 69, 65, 2, 62, 65, 67, 75, 74, + 74, 65, 74, 20, 8, 14, 60, 82, 79, 8, 65, 79, 132, 81, 65, 74, 8, 36, 82, 80, 132, + 81, 61, 81, 81, 82, 74, 67, 8, 73, 69, 81, 8, 55, 74, 81, 65, 79, 79, 69, 63, 68, + 81, 80, 73, 69, 81, 81, 65, 72, 74, 8, 65, 79, 68, 69, 65, 72, 81, 8, 64, 69, 65, + 2, 52, 65, 61, 72, 132, 63, 68, 82, 72, 65, 8, 44, 44, 8, 54, 65, 69, 72, 62, 65, + 81, 79, 103, 67, 65, 15, 8, 83, 75, 74, 8, 25, 22, 22, 22, 8, 1, 112, 18, 8, 26, + 22, 22, 22, 8, 1, 112, 18, 8, 25, 22, 22, 22, 8, 1, 112, 8, 82, 74, 64, 8, 24, + 27, 22, 22, 8, 1, 112, 18, 2, 52, 65, 61, 72, 132, 63, 68, 82, 72, 65, 8, 44, 44, + 8, 54, 65, 69, 72, 62, 65, 81, 79, 103, 67, 65, 15, 8, 83, 75, 74, 8, 25, 22, 22, + 22, 8, 1, 112, 18, 8, 26, 22, 22, 22, 8, 1, 112, 18, 8, 25, 22, 22, 22, 8, 1, + 112, 8, 82, 74, 64, 8, 24, 27, 22, 22, 8, 1, 112, 18, 2, 64, 69, 65, 8, 52, 65, + 61, 72, 132, 63, 68, 82, 72, 65, 8, 44, 44, 44, 8, 64, 65, 74, 8, 65, 79, 132, 81, + 65, 74, 8, 82, 74, 64, 8, 87, 84, 65, 69, 81, 65, 74, 8, 54, 65, 69, 72, 62, 65, + 81, 79, 61, 67, 2, 64, 69, 65, 8, 52, 65, 61, 72, 132, 63, 68, 82, 72, 65, 8, 44, + 44, 44, 8, 64, 65, 74, 8, 65, 79, 132, 81, 65, 74, 8, 82, 74, 64, 8, 87, 84, 65, + 69, 81, 65, 74, 8, 54, 65, 69, 72, 62, 65, 81, 79, 61, 67, 2, 83, 75, 74, 8, 70, + 65, 8, 24, 22, 22, 22, 8, 1, 112, 8, 53, 81, 61, 64, 81, 83, 65, 79, 75, 79, 64, + 74, 65, 81, 65, 74, 4, 56, 65, 79, 132, 61, 73, 73, 72, 82, 74, 67, 20, 8, 14, 91, + 8, 31, 20, 15, 2, 83, 75, 74, 8, 70, 65, 8, 24, 22, 22, 22, 8, 1, 112, 8, 53, + 81, 61, 64, 81, 83, 65, 79, 75, 79, 64, 74, 65, 81, 65, 74, 4, 56, 65, 79, 132, 61, + 73, 73, 72, 82, 74, 67, 20, 8, 14, 91, 8, 31, 20, 15, 2, 37, 61, 64, 65, 18, 8, + 43, 65, 79, 73, 61, 74, 74, 18, 8, 53, 63, 68, 61, 74, 71, 84, 69, 79, 81, 18, 8, + 46, 61, 69, 132, 65, 79, 69, 74, 4, 2, 37, 61, 64, 65, 18, 8, 43, 65, 79, 73, 61, + 74, 74, 18, 8, 53, 63, 68, 61, 74, 71, 84, 69, 79, 81, 18, 8, 46, 61, 69, 132, 65, + 79, 69, 74, 4, 2, 36, 82, 67, 82, 132, 81, 61, 4, 36, 72, 72, 65, 65, 8, 27, 24, + 20, 8, 44, 44, 44, 1, 92, 20, 8, 23, 31, 23, 24, 21, 23, 29, 20, 2, 36, 82, 67, + 82, 132, 81, 61, 4, 36, 72, 72, 65, 65, 8, 27, 24, 20, 8, 44, 44, 44, 1, 92, 20, + 8, 23, 31, 23, 24, 21, 23, 29, 20, 2, 14, 23, 22, 20, 8, 23, 20, 8, 23, 24, 20, + 15, 2, 14, 23, 22, 20, 8, 23, 20, 8, 23, 24, 20, 15, 2, 39, 79, 20, 8, 37, 61, + 82, 65, 79, 18, 8, 43, 82, 67, 75, 18, 8, 53, 61, 74, 69, 81, 103, 81, 80, 79, 61, + 81, 18, 8, 52, 75, 4, 2, 39, 79, 20, 8, 37, 61, 82, 65, 79, 18, 8, 43, 82, 67, + 75, 18, 8, 53, 61, 74, 69, 81, 103, 81, 80, 79, 61, 81, 18, 8, 52, 75, 4, 2, 132, + 69, 74, 65, 74, 132, 81, 79, 20, 8, 23, 20, 8, 44, 44, 1, 94, 18, 8, 23, 31, 23, + 24, 21, 23, 29, 20, 8, 14, 24, 20, 8, 23, 24, 20, 8, 22, 30, 20, 15, 2, 132, 69, + 74, 65, 74, 132, 81, 79, 20, 8, 23, 20, 8, 44, 44, 1, 94, 18, 8, 23, 31, 23, 24, + 21, 23, 29, 20, 8, 14, 24, 20, 8, 23, 24, 20, 8, 22, 30, 20, 15, 2, 37, 61, 82, + 73, 61, 74, 74, 18, 8, 40, 82, 67, 65, 74, 18, 8, 46, 61, 82, 66, 73, 61, 74, 74, + 18, 8, 42, 79, 75, 72, 4, 2, 37, 61, 82, 73, 61, 74, 74, 18, 8, 40, 82, 67, 65, + 74, 18, 8, 46, 61, 82, 66, 73, 61, 74, 74, 18, 8, 42, 79, 75, 72, 4, 2, 73, 61, + 74, 132, 81, 79, 20, 8, 26, 21, 27, 20, 8, 44, 1, 93, 20, 8, 23, 31, 22, 30, 21, + 23, 25, 20, 8, 14, 23, 22, 20, 8, 23, 20, 8, 23, 24, 20, 15, 2, 73, 61, 74, 132, + 81, 79, 20, 8, 26, 21, 27, 20, 8, 44, 1, 93, 20, 8, 23, 31, 22, 30, 21, 23, 25, + 20, 8, 14, 23, 22, 20, 8, 23, 20, 8, 23, 24, 20, 15, 2, 37, 65, 63, 71, 65, 79, + 18, 8, 42, 82, 132, 81, 61, 83, 18, 8, 42, 65, 74, 65, 79, 61, 72, 73, 61, 70, 75, + 79, 8, 87, 20, 8, 39, 20, 18, 2, 37, 65, 63, 71, 65, 79, 18, 8, 42, 82, 132, 81, + 61, 83, 18, 8, 42, 65, 74, 65, 79, 61, 72, 73, 61, 70, 75, 79, 8, 87, 20, 8, 39, + 20, 18, 2, 46, 61, 132, 81, 61, 74, 69, 65, 74, 61, 72, 72, 65, 65, 8, 29, 20, 8, + 44, 44, 1, 94, 20, 8, 23, 31, 22, 30, 21, 23, 25, 20, 2, 46, 61, 132, 81, 61, 74, + 69, 65, 74, 61, 72, 72, 65, 65, 8, 29, 20, 8, 44, 44, 1, 94, 20, 8, 23, 31, 22, + 30, 21, 23, 25, 20, 2, 14, 30, 20, 8, 23, 20, 8, 22, 24, 20, 15, 2, 14, 30, 20, + 8, 23, 20, 8, 22, 24, 20, 15, 2, 37, 65, 79, 67, 73, 61, 74, 74, 18, 8, 48, 61, + 85, 18, 8, 52, 65, 74, 81, 69, 65, 79, 18, 8, 46, 82, 79, 66, 110, 79, 132, 81, 65, + 74, 4, 2, 37, 65, 79, 67, 73, 61, 74, 74, 18, 8, 48, 61, 85, 18, 8, 52, 65, 74, + 81, 69, 65, 79, 18, 8, 46, 82, 79, 66, 110, 79, 132, 81, 65, 74, 4, 2, 64, 61, 73, + 73, 8, 26, 24, 20, 8, 57, 20, 8, 23, 27, 20, 8, 44, 44, 44, 1, 88, 20, 8, 23, + 31, 23, 24, 21, 23, 29, 20, 2, 64, 61, 73, 73, 8, 26, 24, 20, 8, 57, 20, 8, 23, + 27, 20, 8, 44, 44, 44, 1, 88, 20, 8, 23, 31, 23, 24, 21, 23, 29, 20, 2, 14, 24, + 20, 8, 23, 24, 20, 8, 22, 30, 20, 15, 2, 14, 24, 20, 8, 23, 24, 20, 8, 22, 30, + 20, 15, 2, 37, 75, 72, 72, 73, 61, 74, 74, 18, 8, 43, 65, 69, 74, 79, 69, 63, 68, + 18, 8, 37, 82, 79, 65, 61, 82, 64, 69, 79, 65, 71, 81, 75, 79, 18, 2, 37, 75, 72, + 72, 73, 61, 74, 74, 18, 8, 43, 65, 69, 74, 79, 69, 63, 68, 18, 8, 37, 82, 79, 65, + 61, 82, 64, 69, 79, 65, 71, 81, 75, 79, 18, 2, 57, 69, 72, 73, 65, 79, 80, 64, 75, + 79, 66, 65, 79, 8, 53, 81, 79, 20, 8, 27, 24, 20, 8, 44, 44, 44, 1, 93, 20, 8, + 23, 31, 22, 30, 21, 23, 25, 20, 2, 57, 69, 72, 73, 65, 79, 80, 64, 75, 79, 66, 65, + 79, 8, 53, 81, 79, 20, 8, 27, 24, 20, 8, 44, 44, 44, 1, 93, 20, 8, 23, 31, 22, + 30, 21, 23, 25, 20, 2, 14, 23, 22, 20, 8, 23, 20, 8, 22, 28, 20, 15, 2, 14, 23, + 22, 20, 8, 23, 20, 8, 22, 28, 20, 15, 2, 39, 79, 20, 8, 37, 75, 79, 63, 68, 61, + 79, 64, 81, 18, 8, 37, 79, 82, 74, 75, 18, 8, 53, 63, 68, 79, 69, 66, 81, 132, 81, + 65, 72, 72, 65, 79, 18, 2, 39, 79, 20, 8, 37, 75, 79, 63, 68, 61, 79, 64, 81, 18, + 8, 37, 79, 82, 74, 75, 18, 8, 53, 63, 68, 79, 69, 66, 81, 132, 81, 65, 72, 72, 65, + 79, 18, 2, 46, 61, 74, 81, 132, 81, 79, 20, 8, 23, 24, 22, 21, 23, 24, 23, 20, 8, + 44, 44, 44, 1, 93, 20, 8, 23, 31, 23, 24, 21, 23, 29, 20, 2, 46, 61, 74, 81, 132, + 81, 79, 20, 8, 23, 24, 22, 21, 23, 24, 23, 20, 8, 44, 44, 44, 1, 93, 20, 8, 23, + 31, 23, 24, 21, 23, 29, 20, 2, 14, 30, 20, 8, 23, 20, 8, 22, 24, 20, 15, 2, 14, + 30, 20, 8, 23, 20, 8, 22, 24, 20, 15, 2, 37, 79, 61, 82, 74, 65, 18, 8, 38, 61, + 79, 72, 18, 8, 52, 65, 74, 81, 69, 65, 79, 18, 8, 57, 69, 65, 72, 61, 74, 64, 132, + 81, 79, 20, 8, 25, 29, 20, 2, 37, 79, 61, 82, 74, 65, 18, 8, 38, 61, 79, 72, 18, + 8, 52, 65, 74, 81, 69, 65, 79, 18, 8, 57, 69, 65, 72, 61, 74, 64, 132, 81, 79, 20, + 8, 25, 29, 20, 2, 44, 1, 94, 20, 8, 23, 31, 23, 22, 21, 23, 27, 20, 8, 14, 24, + 20, 8, 23, 24, 20, 8, 22, 30, 20, 15, 2, 44, 1, 94, 20, 8, 23, 31, 23, 22, 21, + 23, 27, 20, 8, 14, 24, 20, 8, 23, 24, 20, 8, 22, 30, 20, 15, 2, 37, 79, 75, 64, + 65, 18, 8, 57, 69, 72, 68, 65, 72, 73, 18, 8, 52, 65, 74, 81, 69, 65, 79, 18, 8, + 39, 61, 68, 72, 73, 61, 74, 74, 4, 2, 37, 79, 75, 64, 65, 18, 8, 57, 69, 72, 68, + 65, 72, 73, 18, 8, 52, 65, 74, 81, 69, 65, 79, 18, 8, 39, 61, 68, 72, 73, 61, 74, + 74, 4, 2, 132, 81, 79, 20, 8, 24, 31, 20, 8, 44, 1, 89, 20, 8, 23, 31, 23, 22, + 21, 23, 27, 20, 8, 14, 24, 20, 8, 23, 24, 20, 8, 22, 30, 20, 15, 2, 132, 81, 79, + 20, 8, 24, 31, 20, 8, 44, 1, 89, 20, 8, 23, 31, 23, 22, 21, 23, 27, 20, 8, 14, + 24, 20, 8, 23, 24, 20, 8, 22, 30, 20, 15, 2, 39, 79, 20, 8, 37, 86, 71, 18, 8, + 47, 65, 75, 78, 75, 72, 64, 18, 8, 36, 79, 87, 81, 18, 8, 57, 69, 65, 72, 61, 74, + 64, 4, 2, 39, 79, 20, 8, 37, 86, 71, 18, 8, 47, 65, 75, 78, 75, 72, 64, 18, 8, + 36, 79, 87, 81, 18, 8, 57, 69, 65, 72, 61, 74, 64, 4, 2, 132, 81, 79, 20, 8, 23, + 27, 20, 8, 44, 44, 1, 92, 20, 8, 23, 31, 23, 24, 21, 23, 29, 20, 8, 14, 23, 22, + 20, 8, 23, 20, 8, 23, 24, 20, 15, 2, 132, 81, 79, 20, 8, 23, 27, 20, 8, 44, 44, + 1, 92, 20, 8, 23, 31, 23, 24, 21, 23, 29, 20, 8, 14, 23, 22, 20, 8, 23, 20, 8, + 23, 24, 20, 15, 2, 42, 65, 62, 65, 79, 81, 18, 8, 36, 82, 67, 82, 132, 81, 18, 8, + 42, 65, 84, 65, 79, 71, 132, 63, 68, 61, 66, 81, 80, 62, 65, 61, 73, 81, 65, 79, 18, + 2, 42, 65, 62, 65, 79, 81, 18, 8, 36, 82, 67, 82, 132, 81, 18, 8, 42, 65, 84, 65, + 79, 71, 132, 63, 68, 61, 66, 81, 80, 62, 65, 61, 73, 81, 65, 79, 18, 2, 52, 75, 132, + 69, 74, 65, 74, 132, 81, 79, 20, 8, 24, 20, 8, 44, 44, 44, 1, 93, 20, 8, 23, 31, + 22, 30, 21, 23, 25, 20, 2, 52, 75, 132, 69, 74, 65, 74, 132, 81, 79, 20, 8, 24, 20, + 8, 44, 44, 44, 1, 93, 20, 8, 23, 31, 22, 30, 21, 23, 25, 20, 2, 14, 30, 20, 8, + 23, 20, 8, 50, 30, 20, 15, 2, 14, 30, 20, 8, 23, 20, 8, 50, 30, 20, 15, 2, 42, + 65, 79, 80, 64, 75, 79, 66, 66, 18, 8, 51, 61, 82, 72, 18, 8, 50, 62, 65, 79, 78, + 75, 132, 81, 4, 36, 132, 132, 69, 132, 81, 65, 74, 81, 18, 2, 42, 65, 79, 80, 64, 75, + 79, 66, 66, 18, 8, 51, 61, 82, 72, 18, 8, 50, 62, 65, 79, 78, 75, 132, 81, 4, 36, + 132, 132, 69, 132, 81, 65, 74, 81, 18, 2, 40, 75, 132, 61, 74, 64, 65, 79, 132, 81, 79, + 20, 8, 31, 20, 8, 44, 44, 44, 1, 92, 20, 8, 23, 31, 23, 22, 21, 23, 27, 20, 2, + 40, 75, 132, 61, 74, 64, 65, 79, 132, 81, 79, 20, 8, 31, 20, 8, 44, 44, 44, 1, 92, + 20, 8, 23, 31, 23, 22, 21, 23, 27, 20, 2, 14, 27, 20, 8, 23, 20, 8, 23, 22, 20, + 15, 2, 14, 27, 20, 8, 23, 20, 8, 23, 22, 20, 15, 2, 42, 79, 65, 64, 86, 18, 8, + 41, 79, 61, 74, 87, 18, 8, 14, 44, 74, 67, 65, 74, 69, 65, 82, 79, 15, 18, 8, 38, + 61, 79, 73, 65, 79, 4, 2, 132, 81, 79, 20, 8, 23, 30, 20, 8, 44, 1, 89, 20, 8, + 23, 31, 23, 22, 21, 23, 27, 20, 8, 14, 23, 22, 20, 8, 23, 20, 8, 22, 22, 20, 15, + 2, 42, 82, 81, 81, 73, 61, 74, 74, 18, 8, 36, 72, 62, 79, 65, 63, 68, 81, 18, 8, + 52, 65, 74, 81, 69, 65, 79, 18, 8, 37, 69, 80, 4, 2, 73, 61, 79, 71, 132, 81, 79, + 20, 8, 23, 22, 20, 8, 44, 1, 92, 20, 8, 23, 31, 23, 24, 21, 23, 29, 20, 8, 14, + 24, 20, 8, 23, 24, 20, 2, 22, 30, 20, 15, 2, 43, 61, 61, 63, 71, 18, 8, 51, 61, + 82, 72, 18, 8, 46, 61, 82, 66, 73, 61, 74, 74, 18, 8, 42, 79, 75, 72, 73, 61, 74, + 4, 2, 132, 81, 79, 20, 8, 23, 30, 20, 8, 44, 44, 44, 1, 88, 20, 8, 23, 31, 22, + 30, 21, 23, 25, 20, 8, 14, 23, 25, 20, 8, 28, 20, 8, 22, 28, 20, 15, 2, 43, 61, + 79, 74, 69, 132, 63, 68, 18, 8, 50, 81, 81, 75, 18, 8, 36, 79, 63, 68, 69, 81, 65, + 71, 81, 18, 8, 37, 72, 65, 69, 62, 81, 79, 65, 82, 4, 2, 132, 81, 79, 20, 8, 23, + 27, 21, 23, 28, 20, 8, 44, 1, 94, 20, 8, 23, 31, 22, 30, 21, 23, 25, 20, 8, 14, + 30, 20, 8, 23, 20, 8, 50, 30, 20, 15, 2, 43, 69, 79, 132, 63, 68, 18, 8, 51, 61, + 82, 72, 18, 8, 53, 63, 68, 79, 69, 66, 81, 132, 81, 65, 72, 72, 65, 79, 18, 8, 57, + 61, 72, 72, 4, 2, 132, 81, 79, 20, 8, 27, 24, 20, 8, 44, 44, 44, 1, 90, 20, 8, + 23, 31, 23, 24, 21, 23, 29, 20, 8, 14, 23, 22, 20, 8, 23, 20, 8, 22, 22, 20, 15, + 2, 39, 79, 20, 8, 43, 82, 62, 61, 81, 132, 63, 68, 18, 8, 50, 80, 71, 61, 79, 18, + 8, 42, 86, 73, 74, 61, 132, 69, 61, 72, 4, 39, 69, 79, 65, 71, 4, 2, 81, 75, 79, + 18, 8, 53, 63, 68, 69, 72, 72, 65, 79, 132, 81, 79, 20, 8, 24, 28, 20, 8, 44, 1, + 94, 20, 8, 23, 31, 23, 24, 21, 23, 29, 20, 2, 14, 28, 20, 8, 23, 20, 8, 31, 24, + 20, 15, 2, 45, 61, 63, 68, 73, 61, 74, 74, 18, 8, 43, 61, 74, 80, 18, 8, 39, 69, + 79, 65, 71, 81, 75, 79, 18, 8, 53, 61, 83, 69, 67, 74, 86, 4, 2, 51, 72, 61, 81, + 87, 8, 23, 20, 8, 44, 1, 92, 20, 8, 23, 31, 23, 22, 21, 23, 27, 20, 8, 14, 23, + 25, 20, 8, 28, 20, 8, 22, 28, 20, 15, 2, 45, 61, 63, 75, 62, 69, 18, 8, 53, 69, + 65, 67, 73, 82, 74, 64, 18, 8, 52, 65, 74, 81, 69, 65, 79, 18, 8, 47, 69, 65, 81, + 87, 65, 74, 4, 2, 62, 82, 79, 67, 65, 79, 8, 53, 81, 79, 20, 8, 24, 27, 20, 8, + 57, 20, 8, 23, 27, 20, 8, 44, 44, 1, 89, 20, 8, 23, 31, 22, 30, 21, 23, 25, 20, + 2, 14, 30, 20, 8, 23, 20, 8, 22, 30, 20, 15, 2, 53, 81, 103, 64, 81, 69, 132, 63, + 68, 65, 8, 47, 61, 67, 65, 79, 78, 72, 61, 81, 87, 83, 65, 79, 84, 61, 72, 81, 82, + 74, 67, 8, 82, 74, 64, 2, 48, 61, 132, 63, 68, 69, 74, 65, 74, 81, 65, 63, 68, 74, + 69, 132, 63, 68, 65, 8, 36, 62, 81, 65, 69, 72, 82, 74, 67, 8, 64, 65, 79, 2, 54, + 69, 65, 66, 62, 61, 82, 83, 65, 79, 84, 61, 72, 81, 82, 74, 67, 20, 2, 57, 69, 72, + 68, 65, 72, 73, 78, 72, 61, 81, 87, 8, 23, 61, 18, 8, 40, 69, 74, 67, 61, 74, 67, + 8, 47, 110, 81, 87, 75, 84, 65, 79, 2, 53, 81, 79, 61, 102, 65, 20, 2, 53, 81, 61, + 64, 81, 62, 61, 82, 69, 74, 67, 65, 74, 69, 65, 82, 79, 32, 8, 37, 69, 81, 81, 74, + 65, 79, 18, 8, 46, 109, 74, 69, 67, 80, 4, 2, 84, 65, 67, 8, 27, 27, 20, 2, 47, + 61, 67, 65, 79, 78, 72, 61, 81, 87, 4, 56, 75, 79, 132, 81, 65, 68, 65, 79, 32, 8, + 46, 61, 73, 62, 61, 63, 68, 18, 8, 53, 61, 72, 87, 4, 2, 82, 66, 65, 79, 8, 24, + 23, 20, 2, 47, 61, 67, 65, 79, 78, 72, 61, 81, 87, 4, 36, 132, 132, 69, 132, 81, 65, + 74, 81, 32, 8, 37, 65, 102, 65, 79, 18, 8, 57, 65, 132, 81, 65, 74, 64, 18, 2, 55, + 72, 73, 65, 74, 4, 36, 72, 72, 65, 65, 8, 26, 30, 20, 2, 47, 61, 67, 65, 79, 78, + 72, 61, 81, 87, 4, 42, 65, 68, 69, 72, 66, 65, 32, 8, 42, 79, 61, 73, 73, 18, 8, + 53, 63, 68, 61, 79, 79, 65, 74, 4, 2, 132, 81, 79, 61, 102, 65, 8, 24, 20, 2, 53, + 81, 65, 66, 66, 65, 74, 18, 8, 54, 61, 82, 79, 75, 67, 67, 65, 74, 65, 79, 8, 53, + 81, 79, 20, 8, 31, 20, 2, 47, 61, 67, 65, 79, 78, 72, 61, 81, 87, 8, 48, 75, 72, + 72, 84, 69, 81, 87, 132, 81, 79, 61, 102, 65, 20, 2, 47, 61, 67, 65, 79, 78, 72, 61, + 81, 87, 4, 36, 82, 66, 132, 65, 68, 65, 79, 32, 8, 53, 63, 68, 82, 72, 87, 65, 18, + 8, 53, 75, 78, 68, 69, 65, 4, 2, 38, 68, 61, 79, 72, 75, 81, 81, 65, 74, 4, 53, + 81, 79, 20, 8, 23, 23, 26, 20, 2, 14, 47, 61, 67, 65, 79, 78, 72, 61, 81, 87, 8, + 49, 75, 74, 74, 65, 74, 64, 61, 73, 73, 8, 44, 20, 15, 2, 41, 65, 79, 74, 79, 82, + 66, 32, 8, 36, 73, 81, 8, 38, 68, 20, 8, 28, 22, 30, 26, 20, 2, 47, 61, 67, 65, + 79, 78, 72, 61, 81, 87, 19, 36, 82, 66, 132, 65, 68, 65, 79, 32, 8, 37, 61, 72, 71, + 65, 18, 8, 46, 61, 69, 132, 65, 79, 69, 74, 4, 2, 36, 82, 67, 82, 132, 81, 61, 19, + 36, 72, 72, 65, 65, 8, 27, 31, 20, 2, 47, 61, 67, 65, 79, 78, 72, 61, 81, 87, 8, + 49, 75, 74, 74, 65, 74, 64, 61, 73, 73, 8, 44, 44, 20, 2, 47, 61, 67, 65, 79, 78, + 72, 61, 81, 87, 4, 36, 82, 66, 132, 65, 68, 65, 79, 32, 8, 46, 82, 74, 87, 65, 18, + 8, 46, 74, 75, 62, 65, 72, 80, 4, 2, 64, 75, 79, 66, 66, 132, 81, 79, 20, 8, 26, + 23, 20, 2, 53, 81, 103, 64, 81, 69, 132, 63, 68, 65, 8, 51, 75, 72, 69, 87, 65, 69, + 4, 56, 65, 79, 84, 61, 72, 81, 82, 74, 67, 20, 2, 14, 3, 8, 36, 62, 81, 65, 69, + 72, 82, 74, 67, 8, 44, 44, 8, 3, 15, 2, 56, 65, 79, 73, 65, 132, 132, 82, 74, 67, + 80, 61, 73, 81, 20, 2, 39, 61, 80, 8, 56, 65, 79, 73, 65, 132, 132, 82, 74, 67, 80, + 61, 73, 81, 8, 132, 81, 65, 68, 81, 8, 87, 82, 79, 2, 56, 65, 79, 66, 110, 67, 82, + 74, 67, 8, 14, 70, 65, 64, 65, 79, 8, 65, 69, 74, 87, 65, 72, 74, 65, 74, 8, 53, + 75, 74, 64, 65, 79, 4, 2, 83, 65, 79, 84, 61, 72, 81, 82, 74, 67, 15, 18, 8, 84, + 65, 72, 63, 68, 65, 8, 64, 65, 73, 132, 65, 72, 62, 65, 74, 8, 82, 74, 4, 2, 73, + 69, 81, 81, 65, 72, 62, 61, 79, 8, 36, 82, 66, 81, 79, 103, 67, 65, 8, 87, 82, 8, + 65, 79, 81, 65, 69, 72, 65, 74, 8, 82, 74, 64, 8, 73, 69, 81, 2, 69, 68, 73, 8, + 73, 110, 74, 64, 72, 69, 63, 68, 8, 75, 64, 65, 79, 8, 132, 63, 68, 79, 69, 66, 81, + 72, 69, 63, 68, 8, 87, 82, 8, 83, 65, 79, 4, 2, 71, 65, 68, 79, 65, 74, 8, 87, + 82, 132, 81, 103, 74, 64, 69, 67, 8, 69, 132, 81, 20, 2, 52, 61, 81, 68, 20, 18, 8, + 44, 44, 44, 20, 8, 50, 62, 65, 79, 67, 65, 132, 63, 68, 20, 18, 8, 60, 69, 73, 73, + 65, 79, 8, 25, 23, 25, 2, 62, 69, 80, 8, 25, 23, 27, 20, 2, 56, 65, 79, 73, 65, + 132, 132, 82, 74, 67, 80, 69, 74, 132, 78, 65, 71, 81, 75, 79, 32, 8, 53, 81, 82, 73, + 78, 66, 18, 8, 57, 69, 72, 4, 2, 73, 65, 79, 80, 64, 75, 79, 66, 18, 8, 49, 61, + 132, 132, 61, 82, 69, 132, 63, 68, 65, 132, 81, 79, 20, 8, 25, 29, 20, 2, 54, 65, 63, + 68, 74, 69, 132, 63, 68, 65, 8, 37, 65, 61, 73, 81, 65, 20, 2, 36, 62, 81, 65, 69, + 72, 82, 74, 67, 8, 44, 20, 2, 47, 61, 74, 64, 73, 65, 132, 132, 65, 79, 32, 8, 37, + 65, 79, 74, 68, 61, 79, 64, 81, 18, 8, 48, 65, 65, 79, 132, 63, 68, 65, 69, 64, 4, + 2, 132, 81, 79, 61, 102, 65, 8, 40, 63, 71, 65, 8, 41, 79, 65, 64, 65, 79, 69, 63, + 69, 61, 132, 81, 79, 61, 102, 65, 20, 2, 60, 65, 69, 63, 68, 74, 65, 79, 32, 8, 37, + 72, 61, 74, 71, 18, 8, 42, 79, 110, 74, 132, 81, 79, 20, 8, 24, 23, 20, 2, 53, 63, + 68, 73, 69, 64, 81, 18, 8, 43, 75, 79, 132, 81, 84, 65, 67, 8, 30, 21, 31, 20, 2, + 36, 62, 81, 65, 69, 72, 82, 74, 67, 8, 44, 44, 20, 2, 50, 62, 65, 79, 72, 61, 74, + 64, 73, 65, 132, 132, 65, 79, 32, 8, 52, 65, 78, 71, 65, 84, 69, 81, 87, 18, 8, 46, + 61, 69, 132, 65, 79, 4, 2, 41, 79, 69, 65, 64, 79, 69, 63, 68, 4, 53, 81, 79, 20, + 8, 25, 61, 20, 2, 56, 65, 79, 73, 65, 132, 132, 82, 74, 67, 80, 4, 36, 132, 132, 69, + 132, 81, 65, 74, 81, 32, 8, 43, 65, 79, 67, 65, 132, 65, 72, 72, 18, 2, 52, 110, 63, + 71, 65, 79, 81, 132, 81, 79, 20, 8, 30, 20, 2, 60, 65, 69, 63, 68, 74, 65, 79, 32, + 8, 49, 65, 82, 65, 74, 64, 75, 79, 66, 66, 18, 8, 53, 109, 73, 73, 65, 79, 69, 74, + 67, 4, 2, 132, 81, 79, 61, 102, 65, 8, 24, 26, 20, 2, 53, 81, 65, 72, 72, 65, 8, + 58, 32, 8, 43, 75, 63, 68, 62, 61, 82, 20, 2, 36, 74, 67, 65, 72, 65, 67, 65, 74, + 68, 65, 69, 81, 65, 74, 8, 64, 65, 79, 8, 43, 75, 63, 68, 62, 61, 82, 4, 2, 39, + 65, 78, 82, 81, 61, 81, 69, 75, 74, 8, 82, 74, 64, 8, 64, 65, 79, 8, 39, 65, 78, + 82, 81, 61, 81, 69, 75, 74, 8, 66, 110, 79, 2, 64, 69, 65, 8, 40, 79, 79, 69, 63, + 68, 81, 82, 74, 67, 8, 65, 69, 74, 65, 79, 8, 42, 79, 75, 102, 73, 61, 79, 71, 81, + 68, 61, 72, 72, 65, 20, 2, 41, 65, 79, 74, 132, 78, 79, 65, 63, 68, 132, 61, 63, 68, + 65, 74, 20, 8, 41, 65, 82, 65, 79, 83, 65, 79, 132, 69, 63, 68, 65, 79, 82, 74, 67, + 2, 64, 65, 79, 8, 132, 81, 103, 64, 81, 69, 132, 63, 68, 65, 74, 8, 42, 65, 62, 103, + 82, 64, 65, 18, 8, 36, 74, 72, 61, 67, 65, 74, 8, 82, 74, 64, 2, 48, 75, 62, 69, + 72, 69, 65, 74, 8, 132, 75, 84, 69, 65, 8, 64, 65, 79, 8, 57, 61, 72, 64, 82, 74, + 67, 65, 74, 20, 2, 56, 65, 79, 84, 61, 72, 81, 65, 79, 32, 8, 83, 75, 74, 8, 43, + 61, 86, 74, 18, 8, 48, 61, 67, 61, 87, 69, 74, 132, 81, 79, 20, 8, 25, 20, 2, 48, + 61, 132, 63, 68, 69, 74, 65, 74, 73, 65, 69, 132, 81, 65, 79, 32, 8, 60, 69, 73, 73, + 65, 79, 73, 61, 74, 74, 18, 8, 69, 74, 8, 64, 65, 79, 2, 36, 74, 132, 81, 61, 72, + 81, 20, 2, 39, 69, 65, 8, 49, 61, 73, 65, 74, 8, 82, 74, 64, 8, 57, 75, 68, 74, + 82, 74, 67, 65, 74, 8, 64, 65, 80, 2, 110, 62, 79, 69, 67, 65, 74, 8, 51, 65, 79, + 132, 75, 74, 61, 72, 80, 8, 132, 69, 74, 64, 8, 69, 74, 8, 64, 65, 79, 8, 36, 74, + 132, 81, 61, 72, 81, 2, 87, 82, 8, 65, 79, 66, 79, 61, 67, 65, 74, 20, 2, 63, 15, + 8, 39, 61, 80, 8, 41, 72, 65, 69, 132, 63, 68, 132, 63, 68, 61, 82, 61, 73, 81, 20, + 2, 53, 78, 79, 65, 65, 132, 81, 79, 20, 8, 24, 29, 21, 25, 22, 20, 2, 39, 69, 65, + 8, 56, 69, 65, 68, 19, 8, 82, 74, 64, 8, 41, 72, 65, 69, 132, 63, 68, 132, 63, 68, + 61, 82, 8, 14, 69, 132, 81, 8, 64, 82, 79, 63, 68, 2, 51, 75, 72, 69, 87, 65, 69, + 4, 56, 65, 79, 75, 79, 64, 74, 82, 74, 67, 8, 83, 75, 73, 8, 24, 30, 20, 8, 45, + 82, 74, 69, 8, 23, 30, 31, 29, 15, 2, 65, 69, 74, 67, 65, 66, 110, 68, 79, 81, 20, + 8, 39, 69, 65, 8, 54, 79, 69, 63, 68, 69, 74, 65, 74, 132, 63, 68, 61, 82, 8, 69, + 132, 81, 8, 64, 82, 79, 63, 68, 2, 36, 62, 81, 75, 73, 73, 65, 74, 8, 83, 75, 73, + 8, 25, 23, 20, 8, 36, 82, 67, 82, 80, 81, 8, 21, 8, 24, 25, 20, 8, 53, 65, 78, + 81, 65, 73, 62, 65, 79, 8, 23, 30, 31, 29, 8, 61, 73, 2, 23, 27, 20, 8, 50, 71, + 81, 75, 62, 65, 79, 8, 23, 30, 31, 29, 8, 83, 75, 74, 8, 64, 65, 79, 8, 68, 69, + 65, 132, 69, 67, 65, 74, 2, 46, 67, 72, 20, 8, 51, 75, 72, 69, 87, 65, 69, 19, 39, + 69, 79, 65, 71, 81, 69, 75, 74, 8, 110, 62, 65, 79, 74, 75, 73, 73, 65, 74, 2, 84, + 75, 79, 64, 65, 74, 20, 2, 39, 61, 80, 8, 36, 73, 81, 8, 69, 132, 81, 8, 67, 65, + 109, 66, 66, 74, 65, 81, 32, 2, 61, 15, 8, 66, 110, 79, 8, 64, 69, 65, 8, 41, 72, + 65, 69, 132, 63, 68, 132, 63, 68, 61, 82, 20, 2, 61, 15, 8, 69, 73, 8, 53, 75, 73, + 73, 65, 79, 68, 61, 72, 62, 70, 61, 68, 79, 32, 8, 56, 75, 79, 73, 69, 81, 81, 61, + 67, 80, 2, 83, 75, 74, 8, 28, 97, 8, 62, 69, 80, 8, 31, 97, 8, 55, 68, 79, 33, + 8, 61, 82, 102, 65, 79, 64, 65, 73, 2, 53, 75, 74, 74, 61, 62, 65, 74, 64, 80, 8, + 83, 75, 74, 8, 25, 97, 8, 62, 69, 80, 8, 26, 97, 8, 55, 68, 79, 2, 49, 61, 63, + 68, 73, 69, 81, 81, 61, 67, 80, 20, 2, 62, 15, 8, 69, 73, 8, 57, 69, 74, 81, 65, + 79, 68, 61, 72, 62, 70, 61, 68, 79, 32, 8, 56, 75, 79, 73, 69, 81, 81, 61, 67, 80, + 8, 83, 75, 74, 2, 29, 8, 62, 69, 80, 8, 31, 97, 8, 55, 68, 79, 33, 8, 61, 82, + 102, 65, 79, 64, 65, 73, 8, 53, 75, 74, 74, 4, 2, 61, 62, 65, 74, 64, 80, 8, 83, + 75, 74, 8, 25, 97, 8, 62, 69, 80, 8, 26, 97, 8, 55, 68, 79, 8, 49, 61, 63, 68, + 4, 2, 73, 69, 81, 81, 61, 67, 80, 20, 2, 62, 15, 8, 66, 110, 79, 8, 64, 69, 65, + 8, 14, 54, 79, 69, 63, 68, 69, 74, 65, 74, 132, 63, 68, 61, 82, 15, 20, 2, 61, 15, + 8, 69, 73, 8, 53, 75, 73, 73, 65, 79, 62, 61, 72, 62, 132, 61, 68, 79, 32, 8, 56, + 75, 79, 73, 69, 81, 81, 61, 67, 80, 2, 83, 75, 74, 8, 28, 97, 8, 62, 69, 80, 8, + 31, 97, 8, 55, 68, 79, 20, 8, 49, 61, 63, 68, 73, 69, 81, 81, 61, 67, 80, 2, 83, + 75, 74, 8, 27, 8, 62, 69, 80, 8, 29, 8, 72, 69, 65, 79, 8, 73, 69, 81, 8, 36, + 82, 80, 74, 61, 68, 73, 65, 2, 64, 65, 80, 8, 53, 75, 74, 74, 61, 62, 65, 74, 64, + 80, 33, 8, 61, 74, 8, 64, 69, 65, 132, 65, 73, 8, 83, 75, 74, 2, 25, 97, 8, 62, + 69, 80, 8, 26, 97, 8, 55, 68, 79, 20, 2, 62, 15, 8, 69, 73, 8, 57, 69, 74, 81, + 65, 79, 68, 61, 72, 62, 70, 61, 68, 79, 32, 8, 56, 75, 79, 73, 69, 81, 81, 61, 67, + 80, 8, 83, 75, 74, 2, 29, 8, 62, 69, 80, 8, 31, 97, 8, 55, 68, 79, 20, 8, 49, + 61, 63, 68, 73, 69, 81, 81, 61, 67, 80, 8, 83, 75, 74, 2, 27, 8, 62, 69, 80, 8, + 29, 8, 55, 68, 79, 18, 8, 73, 69, 81, 8, 36, 82, 80, 74, 61, 68, 73, 65, 8, 64, + 65, 80, 2, 53, 75, 74, 74, 61, 62, 65, 74, 64, 80, 33, 8, 61, 74, 8, 64, 69, 65, + 132, 65, 73, 8, 83, 75, 74, 8, 25, 97, 2, 62, 69, 80, 8, 26, 97, 8, 55, 68, 79, + 20, 2, 41, 79, 61, 74, 8, 43, 75, 78, 78, 65, 18, 8, 51, 65, 132, 81, 61, 72, 75, + 87, 87, 69, 132, 81, 79, 20, 8, 29, 28, 18, 2, 44, 44, 20, 8, 36, 82, 66, 67, 61, + 74, 67, 20, 2, 64, 15, 8, 39, 69, 65, 8, 39, 65, 80, 69, 74, 66, 65, 71, 81, 69, + 75, 74, 80, 61, 74, 132, 81, 61, 72, 81, 20, 2, 48, 75, 72, 72, 84, 69, 81, 87, 132, + 81, 79, 20, 8, 74, 65, 62, 65, 74, 8, 64, 65, 73, 8, 43, 61, 82, 78, 81, 78, 82, + 73, 78, 4, 2, 84, 65, 79, 71, 18, 8, 53, 75, 78, 68, 69, 65, 8, 38, 68, 61, 79, + 72, 75, 81, 81, 65, 74, 132, 81, 79, 20, 8, 23, 23, 26, 20, 2, 14, 54, 65, 72, 20, + 8, 38, 68, 20, 8, 26, 24, 29, 20, 15, 2, 39, 69, 65, 8, 54, 68, 103, 81, 69, 67, + 71, 65, 69, 81, 8, 64, 65, 79, 8, 36, 74, 132, 81, 61, 72, 81, 8, 65, 79, 132, 81, + 79, 65, 63, 71, 81, 2, 132, 69, 63, 68, 8, 74, 69, 63, 68, 81, 8, 74, 82, 79, 8, + 61, 82, 66, 8, 64, 69, 65, 8, 39, 65, 80, 69, 74, 66, 65, 71, 81, 69, 75, 74, 2, + 64, 65, 79, 70, 65, 74, 69, 67, 65, 74, 8, 42, 65, 67, 65, 74, 132, 81, 103, 74, 64, + 65, 18, 8, 84, 65, 72, 63, 68, 65, 8, 74, 61, 63, 68, 8, 64, 65, 79, 2, 51, 75, + 72, 69, 87, 65, 69, 19, 56, 65, 79, 75, 79, 64, 74, 82, 74, 67, 8, 14, 83, 75, 73, + 8, 23, 22, 20, 8, 45, 82, 74, 69, 8, 23, 30, 31, 25, 15, 2, 65, 69, 74, 65, 79, + 8, 39, 65, 80, 69, 74, 132, 65, 71, 81, 69, 75, 74, 8, 82, 74, 81, 65, 79, 87, 75, + 67, 65, 74, 8, 84, 65, 79, 64, 65, 74, 2, 73, 110, 132, 132, 65, 74, 20, 8, 40, 80, + 8, 84, 65, 79, 64, 65, 74, 8, 83, 69, 65, 72, 73, 65, 68, 79, 8, 61, 82, 63, 68, + 8, 132, 75, 72, 63, 68, 65, 2, 46, 72, 65, 69, 64, 82, 74, 67, 80, 132, 81, 110, 63, + 71, 65, 18, 8, 37, 65, 81, 81, 65, 74, 8, 82, 20, 8, 132, 20, 8, 84, 20, 8, 87, + 82, 79, 2, 39, 65, 80, 69, 74, 66, 65, 71, 81, 69, 75, 74, 8, 110, 62, 65, 79, 74, + 75, 73, 73, 65, 74, 18, 8, 66, 110, 79, 8, 84, 65, 72, 63, 68, 65, 2, 65, 69, 74, + 65, 8, 56, 65, 79, 78, 66, 72, 69, 63, 68, 81, 82, 74, 67, 8, 87, 82, 79, 8, 52, + 65, 69, 74, 69, 67, 82, 74, 67, 8, 74, 69, 63, 68, 81, 2, 62, 65, 132, 81, 65, 68, + 81, 20, 8, 39, 69, 65, 8, 36, 74, 81, 79, 103, 67, 65, 8, 132, 69, 74, 64, 8, 61, + 74, 8, 64, 69, 65, 8, 36, 74, 132, 81, 61, 72, 81, 2, 87, 82, 8, 79, 69, 63, 68, + 81, 65, 74, 20, 2, 39, 69, 65, 8, 39, 65, 80, 69, 74, 66, 65, 71, 81, 69, 75, 74, + 80, 61, 74, 132, 81, 61, 72, 81, 8, 69, 132, 81, 8, 67, 65, 109, 66, 66, 74, 65, 81, + 32, 2, 61, 15, 8, 61, 74, 8, 64, 65, 74, 8, 57, 75, 63, 68, 65, 74, 81, 61, 67, + 65, 74, 32, 8, 83, 75, 74, 8, 29, 8, 55, 68, 79, 2, 56, 75, 79, 73, 69, 81, 81, + 61, 67, 80, 8, 62, 69, 80, 8, 28, 8, 55, 68, 79, 8, 49, 61, 63, 68, 4, 2, 73, + 69, 81, 81, 61, 67, 80, 20, 2, 62, 15, 8, 61, 82, 8, 64, 65, 74, 8, 53, 75, 74, + 74, 81, 61, 67, 65, 74, 32, 8, 83, 75, 74, 8, 31, 8, 55, 68, 79, 2, 56, 75, 79, + 73, 69, 81, 81, 61, 67, 80, 8, 62, 69, 80, 8, 26, 8, 55, 68, 79, 8, 49, 61, 63, + 68, 4, 2, 73, 69, 81, 81, 61, 67, 80, 20, 2, 14, 56, 65, 79, 84, 61, 72, 81, 65, + 79, 32, 8, 42, 79, 65, 82, 72, 69, 63, 68, 18, 8, 53, 75, 78, 68, 69, 65, 8, 38, + 68, 61, 79, 72, 75, 81, 81, 65, 74, 4, 2, 132, 79, 61, 102, 65, 8, 24, 23, 20, 15, + 2, 63, 15, 8, 38, 68, 61, 79, 72, 75, 81, 81, 65, 74, 62, 82, 79, 67, 65, 79, 8, + 82, 74, 64, 2, 47, 110, 102, 75, 84, 65, 79, 8, 36, 63, 71, 65, 79, 67, 65, 73, 65, + 69, 74, 132, 63, 68, 61, 66, 81, 65, 74, 20, 2, 39, 69, 65, 8, 36, 74, 67, 65, 72, + 65, 67, 65, 74, 68, 65, 69, 81, 65, 74, 8, 64, 69, 65, 132, 65, 79, 8, 36, 63, 71, + 65, 79, 4, 2, 67, 65, 73, 65, 69, 74, 132, 63, 68, 61, 66, 81, 65, 74, 8, 84, 65, + 79, 64, 65, 74, 8, 83, 75, 73, 8, 48, 61, 67, 69, 132, 81, 79, 61, 81, 2, 83, 65, + 79, 84, 61, 72, 81, 65, 81, 20, 8, 60, 82, 79, 8, 37, 65, 61, 82, 66, 132, 69, 63, + 68, 81, 69, 67, 82, 74, 67, 8, 64, 65, 79, 2, 70, 65, 74, 132, 65, 69, 81, 80, 8, + 64, 65, 79, 8, 53, 78, 79, 65, 65, 8, 67, 65, 72, 65, 67, 65, 74, 65, 74, 8, 41, + 65, 72, 64, 73, 61, 79, 71, 2, 84, 69, 79, 64, 8, 84, 103, 68, 79, 65, 74, 64, 8, + 64, 65, 79, 8, 53, 75, 73, 73, 65, 79, 73, 75, 74, 61, 81, 65, 8, 65, 69, 74, 2, + 41, 65, 72, 64, 68, 110, 81, 65, 79, 8, 62, 65, 132, 81, 65, 72, 72, 81, 8, 14, 83, + 65, 79, 67, 72, 20, 8, 53, 75, 74, 64, 65, 79, 4, 40, 81, 61, 81, 8, 28, 2, 36, + 62, 132, 63, 68, 74, 69, 81, 81, 8, 24, 25, 8, 82, 74, 64, 8, 24, 26, 15, 20, 2, + 46, 75, 73, 73, 69, 132, 132, 61, 79, 8, 64, 65, 80, 8, 48, 61, 67, 69, 102, 81, 79, + 61, 81, 80, 32, 2, 53, 63, 68, 75, 72, 87, 18, 8, 53, 81, 20, 8, 56, 20, 8, 52, + 61, 81, 8, 82, 74, 64, 8, 69, 74, 8, 64, 65, 74, 8, 53, 81, 103, 64, 81, 65, 81, + 61, 67, 65, 74, 18, 8, 14, 46, 109, 74, 69, 67, 4, 2, 72, 69, 63, 68, 65, 8, 43, + 61, 82, 80, 132, 61, 63, 68, 65, 74, 15, 18, 8, 40, 68, 79, 82, 74, 67, 65, 74, 18, + 8, 41, 65, 69, 65, 79, 72, 69, 63, 68, 4, 2, 71, 65, 69, 81, 65, 74, 18, 8, 39, + 65, 74, 71, 84, 110, 79, 64, 69, 67, 71, 65, 69, 81, 65, 74, 18, 8, 39, 65, 74, 71, + 73, 103, 72, 65, 79, 20, 2, 51, 65, 79, 132, 75, 74, 61, 72, 69, 65, 74, 8, 64, 65, + 79, 8, 48, 61, 67, 69, 132, 81, 79, 61, 81, 80, 73, 69, 81, 67, 72, 69, 65, 64, 65, + 79, 18, 2, 64, 65, 80, 67, 72, 20, 8, 64, 65, 79, 8, 37, 65, 61, 73, 81, 65, 74, + 8, 69, 74, 8, 62, 65, 87, 82, 67, 8, 61, 82, 66, 2, 51, 79, 110, 66, 82, 74, 67, + 18, 8, 36, 74, 132, 81, 65, 72, 72, 82, 74, 67, 18, 8, 37, 65, 132, 75, 72, 64, 82, + 74, 67, 18, 8, 37, 65, 4, 2, 82, 79, 72, 61, 82, 62, 82, 74, 67, 18, 8, 60, 82, + 79, 79, 82, 68, 65, 132, 65, 81, 87, 82, 74, 67, 8, 82, 132, 84, 20, 18, 8, 132, 75, + 84, 69, 65, 2, 64, 65, 79, 8, 61, 82, 66, 8, 51, 79, 69, 83, 61, 81, 64, 69, 65, + 74, 132, 81, 4, 8, 75, 64, 65, 79, 8, 36, 79, 62, 65, 69, 81, 80, 4, 2, 83, 65, + 79, 81, 79, 61, 67, 8, 61, 74, 67, 65, 74, 75, 73, 73, 65, 74, 65, 74, 8, 51, 65, + 79, 132, 75, 74, 65, 74, 8, 64, 65, 79, 2, 61, 72, 72, 67, 65, 73, 65, 69, 74, 65, + 74, 8, 56, 65, 79, 84, 61, 72, 81, 82, 74, 67, 20, 8, 49, 75, 79, 73, 61, 72, 62, + 65, 4, 2, 132, 75, 72, 64, 82, 74, 67, 80, 65, 81, 61, 81, 20, 8, 14, 52, 65, 69, + 132, 65, 71, 75, 132, 81, 65, 74, 8, 82, 74, 64, 8, 54, 61, 67, 65, 4, 2, 67, 65, + 72, 64, 65, 79, 15, 20, 8, 36, 74, 67, 65, 72, 65, 67, 65, 74, 68, 65, 69, 81, 65, + 74, 8, 64, 65, 79, 8, 57, 69, 81, 84, 65, 74, 4, 2, 82, 74, 64, 8, 57, 61, 69, + 132, 65, 74, 83, 65, 79, 132, 75, 79, 67, 82, 74, 67, 8, 66, 110, 79, 8, 64, 69, 65, + 8, 37, 65, 61, 73, 81, 65, 74, 20, 2, 53, 103, 63, 68, 72, 69, 63, 68, 65, 8, 42, + 65, 132, 63, 68, 103, 66, 81, 80, 62, 65, 64, 110, 79, 66, 74, 69, 132, 132, 65, 20, 8, + 41, 110, 68, 79, 82, 74, 67, 2, 64, 65, 80, 8, 37, 65, 132, 63, 68, 72, 82, 102, 62, + 82, 63, 68, 65, 80, 8, 66, 110, 79, 8, 64, 69, 65, 8, 48, 61, 67, 69, 132, 81, 79, + 61, 81, 80, 4, 2, 132, 69, 81, 87, 82, 74, 67, 65, 74, 18, 8, 36, 74, 67, 65, 72, + 65, 67, 65, 74, 68, 65, 69, 81, 65, 74, 8, 64, 65, 79, 8, 41, 79, 69, 81, 132, 63, + 68, 65, 4, 18, 2, 45, 65, 74, 132, 65, 74, 4, 18, 8, 48, 110, 74, 63, 68, 68, 75, + 66, 66, 4, 18, 8, 57, 69, 72, 68, 65, 72, 73, 4, 36, 82, 67, 82, 132, 81, 61, 4, + 18, 2, 57, 69, 72, 68, 65, 72, 73, 69, 74, 65, 8, 41, 79, 61, 74, 63, 71, 65, 4, + 8, 82, 74, 64, 8, 37, 65, 74, 74, 75, 8, 82, 74, 64, 2, 43, 65, 72, 65, 74, 65, + 8, 45, 61, 66, 66, 104, 19, 53, 81, 69, 66, 81, 82, 74, 67, 18, 8, 64, 65, 80, 8, + 49, 61, 81, 69, 75, 74, 61, 72, 4, 2, 64, 61, 74, 71, 80, 8, 66, 110, 79, 8, 56, + 65, 81, 65, 79, 61, 74, 65, 74, 18, 8, 64, 65, 79, 8, 42, 65, 62, 61, 82, 65, 79, + 4, 2, 53, 81, 69, 66, 81, 82, 74, 67, 18, 8, 64, 65, 80, 8, 57, 65, 69, 68, 65, + 79, 13, 132, 63, 68, 65, 74, 8, 82, 74, 64, 8, 64, 65, 80, 2, 41, 65, 68, 72, 75, + 84, 13, 132, 63, 68, 65, 74, 8, 56, 65, 79, 73, 103, 63, 68, 81, 74, 69, 132, 132, 65, + 80, 18, 8, 64, 65, 80, 8, 45, 75, 79, 61, 64, 13, 2, 132, 63, 68, 65, 74, 8, 47, + 65, 69, 62, 79, 65, 74, 81, 65, 74, 66, 75, 74, 64, 80, 8, 82, 74, 64, 8, 64, 65, + 80, 8, 39, 69, 80, 78, 75, 4, 2, 132, 69, 81, 69, 75, 74, 80, 66, 75, 74, 64, 80, + 8, 64, 65, 80, 8, 48, 61, 67, 69, 132, 81, 79, 61, 81, 80, 20, 8, 53, 81, 103, 64, + 81, 69, 132, 63, 68, 65, 2, 56, 65, 79, 84, 61, 72, 81, 82, 74, 67, 80, 67, 65, 62, + 103, 82, 64, 65, 20, 8, 41, 65, 82, 65, 79, 83, 65, 79, 132, 69, 63, 68, 65, 79, 82, + 74, 67, 2, 64, 65, 79, 8, 132, 81, 103, 64, 81, 69, 132, 63, 68, 65, 74, 8, 42, 65, + 62, 103, 82, 64, 65, 18, 8, 36, 74, 72, 61, 67, 65, 74, 8, 82, 74, 64, 2, 48, 75, + 62, 69, 72, 69, 65, 74, 8, 132, 75, 84, 69, 65, 8, 64, 65, 79, 8, 57, 61, 72, 64, + 82, 74, 67, 65, 74, 20, 8, 14, 56, 65, 79, 4, 2, 84, 61, 72, 81, 82, 74, 67, 8, + 64, 65, 80, 8, 52, 61, 81, 68, 61, 82, 132, 65, 80, 20, 15, 8, 42, 65, 79, 69, 63, + 68, 81, 80, 4, 2, 83, 65, 79, 84, 61, 72, 81, 82, 74, 67, 20, 8, 39, 69, 65, 8, + 37, 65, 81, 65, 69, 72, 69, 67, 82, 74, 67, 8, 64, 65, 80, 2, 50, 62, 65, 79, 62, + 110, 79, 67, 65, 79, 73, 65, 69, 132, 81, 65, 79, 80, 8, 61, 74, 8, 84, 75, 68, 72, + 81, 103, 81, 69, 67, 65, 74, 8, 82, 74, 64, 2, 67, 65, 73, 65, 69, 74, 74, 110, 81, + 87, 69, 67, 65, 74, 8, 55, 74, 81, 65, 79, 74, 65, 68, 73, 82, 74, 67, 65, 74, 8, + 82, 132, 84, 20, 2, 14, 37, 75, 81, 65, 15, 32, 8, 39, 103, 68, 74, 65, 18, 8, 46, + 109, 74, 69, 67, 69, 74, 8, 40, 72, 69, 132, 61, 62, 65, 81, 68, 132, 81, 79, 20, 8, + 27, 24, 20, 2, 46, 72, 109, 81, 65, 79, 18, 8, 46, 61, 69, 132, 65, 79, 8, 41, 79, + 69, 65, 64, 79, 69, 63, 68, 132, 81, 79, 20, 8, 24, 28, 20, 2, 39, 65, 79, 8, 36, + 82, 80, 132, 63, 68, 82, 102, 8, 87, 82, 79, 8, 40, 74, 81, 132, 63, 68, 65, 69, 64, + 82, 74, 67, 8, 110, 62, 65, 79, 2, 64, 69, 65, 8, 56, 65, 79, 67, 65, 62, 82, 74, + 67, 8, 82, 74, 64, 8, 37, 65, 74, 82, 81, 87, 82, 74, 67, 8, 64, 65, 79, 2, 52, + 61, 81, 68, 61, 82, 80, 4, 41, 65, 132, 81, 132, 103, 72, 65, 32, 2, 53, 63, 68, 82, + 132, 81, 65, 68, 79, 82, 80, 18, 8, 50, 62, 65, 79, 62, 110, 79, 67, 65, 79, 73, 65, + 69, 132, 81, 65, 79, 18, 2, 48, 61, 81, 81, 69, 74, 67, 18, 8, 37, 110, 79, 67, 65, + 79, 73, 65, 69, 132, 81, 65, 79, 18, 2, 52, 75, 132, 65, 74, 62, 65, 79, 67, 18, 8, + 14, 53, 81, 61, 64, 81, 83, 20, 4, 56, 75, 79, 132, 81, 65, 68, 65, 79, 15, 18, 2, + 46, 61, 82, 66, 73, 61, 74, 74, 18, 8, 14, 53, 81, 61, 64, 81, 83, 20, 4, 56, 75, + 79, 132, 81, 20, 4, 53, 81, 65, 72, 72, 83, 20, 15, 2, 46, 61, 74, 87, 72, 65, 69, + 20, 2, 52, 61, 81, 68, 61, 82, 80, 18, 8, 44, 20, 8, 50, 62, 65, 79, 67, 65, 132, + 63, 68, 75, 102, 18, 2, 14, 60, 69, 73, 73, 65, 79, 8, 23, 24, 28, 21, 23, 25, 22, + 20, 15, 2, 47, 65, 69, 81, 65, 79, 32, 8, 53, 65, 71, 79, 65, 81, 20, 8, 36, 72, + 65, 85, 61, 74, 64, 65, 79, 18, 8, 50, 80, 74, 61, 62, 79, 110, 63, 71, 65, 79, 4, + 2, 132, 81, 79, 20, 8, 24, 20, 2, 37, 82, 79, 20, 4, 42, 65, 68, 20, 32, 8, 53, + 63, 68, 73, 82, 64, 65, 18, 8, 53, 63, 68, 72, 110, 81, 65, 79, 132, 81, 79, 20, 8, + 31, 61, 20, 2, 57, 82, 74, 63, 71, 18, 8, 53, 78, 79, 65, 65, 132, 81, 79, 20, 8, + 23, 26, 20, 2, 51, 65, 74, 74, 65, 63, 71, 65, 8, 53, 78, 61, 74, 64, 61, 82, 65, + 79, 8, 37, 65, 79, 67, 8, 23, 30, 20, 2, 51, 61, 65, 81, 87, 18, 8, 53, 78, 69, + 65, 72, 68, 61, 67, 65, 74, 132, 81, 79, 20, 8, 25, 20, 2, 14, 39, 69, 65, 8, 49, + 61, 73, 65, 74, 8, 82, 74, 64, 8, 57, 75, 68, 74, 82, 74, 67, 65, 74, 8, 64, 65, + 79, 2, 46, 61, 74, 87, 72, 69, 132, 81, 65, 74, 8, 132, 75, 84, 69, 65, 8, 64, 65, + 80, 8, 53, 63, 68, 79, 65, 69, 62, 73, 61, 132, 63, 68, 69, 74, 65, 74, 4, 2, 78, + 65, 79, 132, 75, 74, 61, 72, 80, 8, 132, 69, 74, 64, 8, 69, 74, 8, 64, 65, 79, 8, + 46, 61, 74, 87, 72, 65, 69, 8, 87, 82, 8, 65, 79, 66, 79, 61, 67, 65, 74, 20, 15, + 2, 37, 75, 81, 65, 74, 73, 65, 69, 132, 81, 65, 79, 65, 69, 20, 2, 52, 61, 81, 68, + 61, 82, 80, 18, 8, 44, 20, 8, 50, 62, 65, 79, 67, 65, 132, 63, 68, 75, 102, 18, 2, + 60, 69, 73, 73, 65, 79, 8, 23, 23, 29, 21, 23, 23, 30, 20, 2, 25, 20, 8, 64, 65, + 79, 8, 53, 63, 68, 82, 72, 64, 65, 78, 82, 81, 61, 81, 69, 75, 74, 32, 2, 91, 8, + 24, 23, 20, 2, 14, 132, 75, 84, 69, 65, 8, 91, 8, 26, 18, 8, 91, 8, 23, 23, 8, + 61, 18, 8, 91, 8, 24, 24, 8, 62, 69, 80, 8, 24, 26, 18, 8, 91, 8, 30, 8, 62, + 8, 82, 74, 64, 8, 65, 15, 2, 64, 69, 65, 8, 37, 65, 82, 79, 72, 61, 82, 62, 82, + 74, 67, 8, 83, 75, 74, 8, 47, 65, 68, 79, 78, 65, 79, 132, 75, 74, 65, 74, 8, 62, + 69, 80, 8, 87, 82, 2, 65, 69, 74, 65, 73, 8, 68, 61, 72, 62, 65, 74, 8, 45, 61, + 68, 79, 65, 20, 2, 44, 74, 8, 61, 72, 72, 65, 74, 8, 41, 103, 72, 72, 65, 74, 8, + 64, 69, 65, 132, 65, 80, 8, 51, 61, 79, 61, 67, 79, 61, 78, 68, 65, 74, 18, 8, 132, + 75, 84, 69, 65, 8, 62, 65, 69, 2, 132, 81, 103, 64, 81, 69, 132, 63, 68, 65, 79, 132, + 65, 69, 81, 80, 8, 67, 65, 132, 81, 65, 72, 72, 81, 65, 74, 8, 36, 74, 81, 79, 103, + 67, 65, 74, 8, 69, 74, 8, 36, 74, 67, 65, 72, 65, 67, 65, 74, 68, 65, 69, 81, 65, + 74, 18, 2, 84, 65, 72, 63, 68, 65, 8, 74, 61, 63, 68, 8, 91, 8, 27, 8, 64, 65, + 79, 8, 46, 109, 74, 69, 67, 72, 69, 63, 68, 65, 74, 8, 52, 65, 67, 69, 65, 79, 82, + 74, 67, 8, 83, 75, 79, 62, 65, 68, 61, 72, 81, 65, 74, 2, 66, 69, 74, 64, 18, 8, + 68, 75, 72, 81, 8, 64, 65, 79, 8, 48, 61, 67, 69, 132, 81, 79, 61, 81, 8, 64, 61, + 80, 8, 42, 82, 81, 61, 63, 68, 81, 65, 74, 8, 82, 74, 64, 8, 64, 69, 65, 8, 56, + 75, 79, 4, 2, 132, 63, 68, 72, 103, 67, 65, 8, 64, 65, 79, 8, 53, 63, 68, 82, 72, + 64, 65, 78, 82, 81, 61, 81, 69, 75, 74, 8, 65, 69, 74, 18, 8, 61, 82, 63, 68, 8, + 81, 65, 69, 72, 81, 8, 65, 79, 8, 69, 68, 79, 8, 64, 69, 65, 2, 65, 79, 67, 65, + 68, 65, 74, 64, 65, 74, 8, 40, 74, 81, 132, 63, 68, 65, 69, 64, 82, 74, 67, 65, 74, + 8, 73, 69, 81, 20, 2, 44, 74, 8, 64, 65, 74, 8, 41, 103, 72, 72, 65, 74, 8, 87, + 82, 8, 23, 61, 8, 82, 74, 64, 8, 65, 18, 8, 132, 75, 84, 69, 65, 8, 25, 8, 64, + 69, 65, 132, 65, 80, 2, 51, 61, 79, 61, 67, 79, 61, 78, 68, 65, 74, 8, 132, 65, 81, + 87, 81, 8, 132, 69, 63, 68, 8, 64, 69, 65, 8, 53, 63, 68, 82, 72, 64, 65, 78, 82, + 81, 61, 81, 69, 75, 74, 8, 73, 69, 81, 8, 64, 65, 73, 2, 87, 82, 132, 81, 103, 74, + 64, 69, 67, 65, 74, 8, 46, 79, 65, 69, 80, 132, 63, 68, 82, 72, 69, 74, 132, 78, 65, + 71, 81, 75, 79, 8, 83, 75, 79, 8, 64, 65, 79, 8, 37, 65, 132, 63, 68, 72, 82, 102, + 66, 61, 132, 132, 82, 74, 67, 2, 69, 74, 8, 56, 65, 79, 62, 69, 74, 64, 82, 74, 67, + 20, 2, 91, 8, 29, 20, 8, 7, 39, 69, 65, 8, 53, 63, 68, 82, 72, 64, 65, 78, 82, + 81, 61, 81, 69, 75, 74, 8, 62, 65, 64, 61, 79, 66, 8, 87, 82, 79, 8, 36, 82, 80, + 66, 110, 68, 79, 82, 74, 67, 2, 69, 68, 79, 65, 79, 8, 37, 65, 132, 63, 68, 72, 110, + 132, 132, 65, 8, 64, 65, 79, 8, 42, 65, 74, 65, 68, 73, 69, 67, 82, 74, 67, 8, 64, + 65, 80, 8, 48, 61, 67, 69, 132, 81, 79, 61, 81, 80, 8, 69, 74, 2, 61, 72, 72, 65, + 74, 8, 41, 103, 72, 72, 65, 74, 18, 8, 84, 75, 8, 65, 80, 8, 132, 69, 63, 68, 8, + 82, 73, 8, 74, 65, 82, 65, 8, 40, 69, 74, 79, 69, 63, 68, 81, 82, 74, 67, 65, 74, + 8, 69, 74, 2, 64, 65, 79, 8, 53, 63, 68, 82, 72, 83, 65, 79, 66, 61, 132, 132, 82, + 74, 67, 8, 75, 64, 65, 79, 8, 82, 73, 8, 36, 82, 66, 62, 79, 69, 74, 67, 82, 74, + 67, 8, 83, 75, 74, 8, 42, 65, 72, 64, 4, 2, 73, 69, 81, 81, 65, 72, 74, 8, 68, + 61, 74, 64, 65, 72, 81, 18, 8, 84, 65, 72, 63, 68, 65, 8, 69, 74, 8, 64, 65, 73, + 8, 132, 81, 103, 64, 81, 69, 132, 63, 68, 65, 74, 8, 43, 61, 82, 80, 68, 61, 72, 81, + 80, 4, 2, 78, 72, 61, 74, 65, 8, 74, 69, 63, 68, 81, 8, 83, 75, 79, 67, 65, 132, + 65, 68, 65, 74, 8, 132, 69, 74, 64, 20, 6, 8, 14, 91, 8, 29, 18, 8, 91, 8, 30, + 18, 8, 91, 8, 31, 8, 62, 69, 80, 8, 91, 8, 23, 24, 15, 2, 91, 8, 30, 20, 8, + 14, 49, 65, 84, 8, 59, 75, 79, 71, 18, 8, 59, 65, 73, 65, 74, 18, 8, 59, 65, 79, + 65, 84, 61, 74, 18, 8, 45, 61, 66, 66, 104, 18, 8, 47, 69, 74, 74, 104, 18, 8, 47, + 61, 82, 79, 104, 80, 15, 2, 39, 69, 65, 8, 53, 63, 68, 82, 72, 64, 65, 78, 82, 81, + 61, 81, 69, 75, 74, 8, 84, 69, 79, 71, 81, 8, 82, 74, 81, 65, 79, 8, 64, 65, 79, + 8, 36, 82, 66, 132, 69, 63, 68, 81, 2, 64, 65, 79, 8, 46, 109, 74, 69, 67, 72, 69, + 63, 68, 65, 74, 8, 52, 65, 67, 69, 65, 79, 82, 74, 67, 8, 87, 82, 8, 51, 75, 81, + 80, 64, 61, 73, 8, 61, 72, 80, 8, 132, 81, 61, 61, 81, 72, 69, 63, 68, 65, 80, 2, + 53, 63, 68, 82, 72, 61, 82, 66, 132, 69, 63, 68, 81, 80, 75, 79, 67, 61, 74, 8, 82, + 74, 64, 8, 71, 79, 61, 66, 81, 8, 132, 81, 61, 61, 81, 72, 69, 63, 68, 65, 74, 8, + 36, 82, 66, 81, 79, 61, 67, 65, 80, 2, 74, 65, 62, 65, 74, 8, 64, 65, 74, 8, 46, + 79, 65, 69, 80, 132, 63, 68, 82, 72, 69, 74, 132, 78, 65, 71, 81, 75, 79, 65, 74, 20, + 8, 44, 68, 79, 65, 8, 37, 65, 79, 69, 63, 68, 81, 65, 8, 82, 74, 64, 2, 56, 65, + 79, 66, 110, 67, 82, 74, 67, 65, 74, 18, 8, 132, 75, 84, 69, 65, 8, 64, 69, 65, 8, + 61, 74, 8, 132, 69, 65, 8, 67, 65, 79, 69, 63, 68, 81, 65, 81, 65, 74, 8, 56, 65, + 79, 66, 110, 67, 82, 74, 67, 65, 74, 2, 82, 74, 64, 8, 37, 65, 132, 63, 68, 65, 69, + 64, 65, 8, 65, 79, 67, 65, 68, 65, 74, 8, 82, 74, 81, 65, 79, 8, 64, 65, 79, 8, + 41, 69, 79, 73, 61, 8, 7, 53, 63, 68, 82, 72, 4, 2, 64, 65, 78, 82, 81, 61, 81, + 69, 75, 74, 8, 38, 68, 61, 79, 72, 75, 81, 81, 65, 74, 62, 82, 79, 67, 6, 20, 2, + 14, 44, 74, 8, 64, 65, 74, 8, 103, 82, 102, 65, 79, 65, 74, 8, 53, 63, 68, 82, 72, + 61, 74, 67, 65, 72, 65, 67, 65, 74, 68, 65, 69, 81, 65, 74, 15, 8, 132, 81, 65, 68, + 81, 8, 64, 69, 65, 2, 53, 63, 68, 82, 72, 64, 65, 78, 82, 81, 61, 81, 69, 75, 74, + 8, 87, 82, 73, 8, 48, 61, 67, 69, 132, 81, 79, 61, 81, 8, 69, 74, 8, 64, 65, 73, + 8, 56, 65, 79, 68, 103, 72, 81, 74, 69, 80, 2, 65, 69, 74, 65, 79, 8, 56, 65, 79, + 84, 61, 72, 81, 82, 74, 67, 80, 4, 39, 65, 78, 82, 81, 61, 81, 69, 75, 74, 8, 69, + 73, 8, 53, 69, 74, 74, 65, 8, 64, 65, 80, 8, 91, 8, 27, 31, 2, 64, 65, 79, 8, + 53, 81, 103, 64, 81, 65, 4, 50, 79, 64, 74, 82, 74, 67, 8, 83, 75, 73, 8, 25, 22, + 20, 8, 48, 61, 69, 8, 23, 30, 27, 25, 8, 14, 42, 20, 8, 53, 20, 2, 53, 20, 8, + 24, 28, 23, 8, 66, 66, 20, 15, 8, 41, 110, 79, 8, 69, 68, 79, 65, 8, 42, 65, 132, + 63, 68, 103, 66, 81, 80, 83, 65, 79, 84, 61, 72, 81, 82, 74, 67, 8, 62, 72, 65, 69, + 62, 65, 74, 8, 69, 74, 2, 64, 69, 65, 132, 65, 79, 8, 37, 65, 87, 69, 65, 68, 82, + 74, 67, 8, 64, 69, 65, 8, 37, 65, 132, 81, 69, 73, 73, 82, 74, 67, 65, 74, 8, 64, + 65, 79, 8, 48, 69, 74, 69, 132, 81, 65, 79, 69, 61, 72, 4, 2, 44, 74, 132, 81, 79, + 82, 71, 81, 69, 75, 74, 8, 66, 110, 79, 8, 64, 69, 65, 8, 53, 81, 61, 64, 81, 73, + 61, 67, 69, 132, 81, 79, 61, 81, 65, 8, 83, 75, 73, 8, 24, 27, 20, 8, 48, 61, 69, + 8, 23, 30, 25, 27, 2, 73, 61, 102, 67, 65, 62, 65, 74, 64, 20, 2, 91, 8, 31, 8, + 82, 74, 64, 8, 91, 8, 23, 24, 20, 2, 14, 132, 75, 84, 69, 65, 8, 91, 8, 23, 25, + 18, 8, 23, 26, 18, 8, 25, 23, 8, 61, 8, 62, 69, 80, 8, 66, 18, 8, 91, 8, 24, + 18, 8, 91, 8, 25, 18, 8, 91, 8, 26, 26, 18, 8, 91, 8, 27, 24, 15, 2, 39, 69, + 65, 8, 53, 69, 81, 87, 82, 74, 67, 65, 74, 18, 8, 87, 82, 8, 84, 65, 72, 63, 68, + 65, 74, 8, 64, 65, 79, 8, 56, 75, 79, 132, 69, 81, 87, 65, 74, 64, 65, 8, 64, 65, + 79, 2, 39, 65, 78, 82, 81, 61, 81, 69, 75, 74, 8, 64, 69, 65, 8, 48, 69, 72, 67, + 72, 69, 65, 64, 65, 79, 8, 82, 74, 64, 8, 64, 69, 65, 8, 46, 79, 65, 69, 80, 132, + 63, 68, 82, 72, 4, 2, 69, 74, 132, 78, 65, 71, 81, 75, 79, 65, 74, 8, 73, 69, 81, + 8, 36, 74, 67, 61, 62, 65, 8, 64, 65, 79, 8, 54, 61, 67, 65, 80, 75, 79, 64, 74, + 82, 74, 67, 8, 65, 69, 74, 72, 61, 64, 65, 81, 18, 2, 84, 65, 79, 64, 65, 74, 8, + 74, 61, 63, 68, 8, 14, 37, 65, 64, 110, 79, 66, 74, 69, 80, 8, 67, 65, 68, 61, 72, + 81, 65, 74, 15, 18, 8, 64, 75, 63, 68, 8, 69, 132, 81, 8, 64, 65, 79, 8, 56, 75, + 79, 4, 2, 132, 69, 81, 87, 65, 74, 64, 65, 8, 83, 65, 79, 78, 66, 72, 69, 63, 68, + 81, 65, 81, 18, 8, 61, 82, 66, 8, 36, 74, 81, 79, 61, 67, 8, 64, 79, 65, 69, 65, + 79, 8, 48, 69, 81, 67, 72, 69, 65, 64, 65, 79, 2, 75, 64, 65, 79, 8, 65, 69, 74, + 65, 80, 8, 64, 65, 79, 8, 62, 65, 69, 64, 65, 74, 8, 46, 79, 65, 69, 80, 132, 63, + 68, 82, 72, 69, 74, 132, 78, 65, 71, 81, 75, 79, 65, 74, 8, 65, 69, 74, 65, 2, 53, + 69, 81, 87, 82, 74, 67, 8, 69, 74, 74, 65, 79, 68, 61, 72, 62, 8, 23, 22, 8, 54, + 61, 67, 65, 74, 8, 61, 74, 87, 82, 62, 65, 79, 61, 82, 73, 65, 74, 20, 8, 14, 39, + 69, 65, 2, 84, 69, 64, 65, 79, 132, 78, 79, 65, 63, 68, 65, 74, 8, 70, 65, 64, 75, + 63, 68, 8, 69, 74, 8, 65, 69, 74, 65, 73, 8, 132, 75, 72, 63, 68, 65, 74, 15, 2, + 41, 61, 72, 72, 65, 8, 87, 84, 65, 69, 8, 64, 65, 79, 8, 61, 74, 84, 65, 132, 65, + 74, 64, 65, 74, 8, 48, 69, 81, 67, 72, 69, 65, 64, 65, 79, 8, 75, 64, 65, 79, 8, + 64, 65, 79, 8, 56, 75, 79, 4, 2, 132, 69, 81, 87, 65, 74, 64, 65, 8, 64, 65, 79, + 8, 37, 65, 132, 63, 68, 72, 82, 102, 66, 61, 132, 132, 82, 74, 67, 18, 8, 132, 75, 8, + 73, 82, 102, 8, 64, 69, 65, 8, 40, 79, 72, 65, 64, 69, 67, 82, 74, 67, 2, 64, 65, + 79, 8, 69, 74, 8, 41, 79, 61, 67, 65, 8, 132, 81, 65, 68, 65, 74, 64, 65, 74, 8, + 53, 61, 63, 68, 65, 74, 8, 62, 69, 80, 8, 87, 82, 79, 8, 74, 103, 63, 68, 132, 81, + 65, 74, 2, 14, 75, 79, 64, 65, 74, 81, 72, 69, 63, 68, 65, 74, 8, 75, 64, 65, 79, + 8, 61, 82, 102, 65, 79, 75, 79, 64, 65, 74, 81, 72, 69, 63, 68, 65, 74, 15, 8, 39, + 65, 78, 82, 81, 61, 81, 69, 75, 74, 80, 4, 2, 132, 69, 81, 87, 82, 74, 67, 8, 61, + 82, 80, 67, 65, 132, 65, 81, 87, 81, 8, 84, 65, 79, 64, 65, 74, 20, 8, 44, 74, 8, + 64, 69, 65, 132, 65, 79, 8, 74, 103, 63, 68, 132, 81, 65, 74, 8, 53, 69, 81, 87, 82, + 74, 67, 2, 69, 132, 81, 8, 61, 72, 80, 64, 61, 74, 74, 8, 64, 69, 65, 8, 39, 65, + 78, 82, 81, 61, 81, 69, 75, 74, 8, 75, 68, 74, 65, 8, 52, 110, 63, 71, 132, 69, 63, + 68, 81, 8, 61, 82, 66, 8, 64, 69, 65, 2, 60, 61, 68, 72, 8, 64, 65, 79, 8, 36, + 74, 84, 65, 132, 65, 74, 64, 65, 74, 8, 14, 61, 72, 132, 75, 8, 61, 82, 63, 68, 8, + 69, 74, 8, 64, 65, 73, 8, 41, 61, 72, 72, 65, 18, 8, 64, 61, 102, 2, 84, 65, 74, + 69, 67, 65, 79, 8, 61, 72, 80, 8, 65, 69, 74, 8, 39, 79, 69, 81, 81, 65, 72, 8, + 64, 65, 79, 8, 48, 69, 81, 67, 72, 69, 65, 64, 65, 79, 8, 61, 74, 84, 65, 132, 65, + 74, 64, 8, 69, 132, 81, 15, 2, 62, 65, 132, 63, 68, 72, 82, 102, 66, 103, 68, 69, 67, + 8, 69, 74, 8, 64, 65, 74, 70, 65, 74, 69, 67, 65, 74, 8, 53, 61, 63, 68, 65, 74, + 18, 8, 69, 74, 8, 64, 65, 74, 65, 74, 8, 64, 69, 65, 8, 37, 65, 4, 2, 132, 63, + 68, 72, 82, 102, 66, 61, 132, 132, 82, 74, 67, 8, 69, 74, 8, 64, 65, 79, 8, 72, 65, + 81, 87, 81, 65, 74, 8, 53, 69, 81, 87, 82, 74, 67, 8, 61, 82, 80, 67, 65, 132, 65, + 81, 87, 81, 8, 84, 61, 79, 20, 2, 14, 40, 69, 74, 8, 84, 65, 69, 81, 65, 79, 65, + 79, 8, 40, 69, 74, 84, 61, 74, 64, 8, 67, 65, 67, 65, 74, 8, 64, 69, 65, 8, 37, + 65, 132, 63, 68, 72, 82, 102, 66, 103, 66, 66, 82, 74, 67, 8, 69, 74, 2, 64, 65, 74, + 8, 69, 74, 8, 41, 79, 61, 67, 65, 8, 132, 81, 65, 68, 65, 74, 64, 65, 74, 8, 36, + 74, 67, 65, 72, 65, 67, 65, 74, 68, 65, 69, 81, 65, 74, 8, 69, 132, 81, 8, 61, 72, + 80, 64, 61, 74, 74, 2, 82, 74, 87, 82, 72, 103, 132, 132, 69, 67, 20, 15, 8, 37, 65, + 69, 8, 64, 65, 79, 8, 40, 69, 74, 72, 61, 64, 82, 74, 67, 8, 87, 82, 8, 64, 69, + 65, 132, 65, 79, 8, 87, 84, 65, 69, 81, 65, 74, 2, 53, 69, 81, 87, 82, 74, 67, 8, + 68, 61, 81, 8, 64, 65, 79, 8, 56, 75, 79, 132, 69, 81, 87, 65, 74, 64, 65, 8, 61, + 82, 66, 8, 64, 69, 65, 132, 65, 8, 37, 65, 132, 81, 69, 73, 73, 82, 74, 67, 2, 70, + 65, 64, 65, 80, 73, 61, 72, 8, 62, 65, 132, 75, 74, 64, 65, 79, 80, 8, 68, 69, 74, + 87, 82, 84, 65, 69, 132, 65, 74, 20, 2, 91, 8, 23, 23, 20, 2, 101, 62, 65, 79, 8, + 64, 69, 65, 8, 37, 65, 132, 63, 68, 72, 110, 132, 132, 65, 8, 64, 65, 79, 8, 39, 65, + 78, 82, 81, 61, 81, 69, 75, 74, 8, 69, 132, 81, 8, 65, 69, 74, 2, 51, 79, 75, 81, + 75, 71, 75, 72, 72, 8, 87, 82, 8, 66, 110, 68, 79, 65, 74, 18, 8, 64, 61, 80, 8, + 74, 61, 63, 68, 8, 56, 75, 79, 72, 65, 132, 82, 74, 67, 8, 83, 75, 73, 8, 56, 75, + 79, 4, 2, 69, 69, 81, 87, 65, 74, 64, 65, 74, 8, 82, 74, 64, 8, 73, 69, 74, 64, + 65, 132, 81, 65, 74, 80, 8, 87, 84, 65, 69, 8, 48, 69, 81, 67, 72, 69, 65, 64, 65, + 79, 74, 8, 87, 82, 8, 82, 74, 81, 65, 79, 4, 2, 132, 63, 68, 79, 65, 69, 62, 65, + 74, 8, 69, 132, 81, 20, 2, 91, 8, 23, 24, 20, 2, 99, 74, 64, 65, 79, 82, 74, 67, + 65, 74, 8, 64, 69, 65, 132, 65, 79, 8, 42, 65, 132, 63, 68, 103, 66, 81, 80, 61, 74, + 84, 65, 69, 132, 82, 74, 67, 8, 62, 65, 64, 110, 79, 66, 65, 74, 2, 64, 65, 79, 8, + 42, 65, 74, 65, 68, 73, 69, 67, 82, 74, 67, 8, 64, 65, 79, 8, 46, 109, 74, 69, 67, + 72, 69, 63, 68, 65, 74, 8, 52, 65, 67, 69, 65, 79, 82, 74, 67, 18, 8, 36, 62, 4, + 2, 81, 65, 69, 72, 82, 74, 67, 8, 66, 110, 79, 8, 46, 69, 79, 63, 68, 65, 74, 4, + 8, 82, 74, 64, 8, 53, 63, 68, 82, 72, 84, 65, 132, 65, 74, 8, 69, 74, 8, 51, 75, + 81, 80, 64, 61, 73, 20, 2, 14, 39, 65, 79, 8, 48, 61, 67, 69, 132, 81, 79, 61, 81, + 20, 15, 2, 24, 28, 20, 8, 45, 82, 74, 69, 8, 23, 30, 23, 23, 8, 82, 74, 64, 8, + 23, 23, 20, 8, 48, 103, 79, 87, 8, 23, 30, 31, 30, 2, 62, 65, 84, 65, 79, 71, 132, + 81, 65, 72, 72, 69, 67, 81, 8, 84, 65, 79, 64, 65, 74, 18, 8, 132, 75, 64, 61, 102, + 8, 62, 69, 80, 8, 64, 61, 68, 69, 74, 8, 61, 82, 63, 68, 8, 74, 75, 63, 68, 8, + 64, 69, 65, 2, 52, 65, 69, 74, 69, 67, 82, 74, 67, 8, 78, 78, 20, 8, 64, 65, 80, + 8, 43, 69, 74, 81, 65, 79, 67, 65, 62, 103, 82, 64, 65, 80, 8, 64, 65, 80, 8, 52, + 61, 81, 68, 68, 61, 82, 132, 65, 80, 2, 65, 79, 66, 75, 79, 64, 65, 79, 72, 69, 63, + 68, 8, 84, 61, 79, 20, 8, 14, 48, 69, 81, 81, 65, 72, 8, 84, 61, 79, 65, 74, 8, + 66, 110, 79, 8, 64, 69, 65, 132, 65, 74, 8, 60, 84, 65, 63, 71, 2, 69, 73, 8, 40, + 81, 61, 81, 8, 74, 69, 63, 68, 81, 8, 73, 65, 68, 79, 8, 83, 75, 79, 67, 65, 132, + 65, 68, 65, 74, 8, 82, 74, 64, 8, 68, 61, 62, 65, 74, 8, 84, 69, 79, 8, 64, 65, + 80, 4, 2, 68, 61, 72, 62, 8, 64, 65, 73, 8, 43, 61, 82, 80, 84, 61, 79, 81, 8, + 57, 65, 79, 64, 65, 79, 73, 61, 74, 74, 8, 75, 62, 69, 67, 65, 79, 8, 51, 75, 132, + 69, 81, 69, 75, 74, 2, 66, 110, 79, 8, 64, 69, 65, 8, 48, 75, 74, 61, 81, 65, 8, + 36, 78, 79, 69, 72, 21, 45, 82, 74, 69, 8, 64, 69, 65, 8, 62, 69, 80, 68, 65, 79, + 8, 62, 65, 87, 75, 67, 65, 74, 65, 2, 56, 65, 79, 67, 110, 81, 82, 74, 67, 8, 83, + 75, 74, 8, 73, 75, 74, 61, 81, 72, 69, 63, 68, 8, 29, 27, 8, 1, 112, 8, 24, 24, + 27, 8, 48, 61, 79, 71, 2, 82, 74, 81, 65, 79, 73, 8, 25, 22, 20, 8, 48, 103, 79, + 87, 8, 64, 20, 8, 45, 80, 20, 8, 62, 65, 84, 69, 72, 72, 69, 67, 81, 20, 15, 2, + 53, 65, 69, 81, 8, 64, 65, 73, 8, 23, 20, 8, 45, 82, 74, 69, 8, 64, 20, 8, 45, + 80, 20, 8, 83, 65, 79, 132, 69, 65, 68, 81, 8, 64, 65, 79, 8, 65, 68, 65, 73, 20, + 2, 51, 66, 65, 79, 64, 65, 62, 61, 68, 74, 4, 53, 63, 68, 61, 66, 66, 74, 65, 79, + 8, 53, 63, 68, 109, 74, 65, 62, 65, 79, 67, 8, 64, 69, 65, 8, 42, 65, 132, 63, 68, + 103, 66, 81, 65, 8, 64, 65, 80, 2, 43, 61, 82, 80, 84, 61, 79, 81, 80, 8, 14, 66, + 110, 79, 8, 46, 69, 79, 63, 68, 132, 81, 79, 61, 102, 65, 8, 23, 22, 15, 8, 82, 74, + 64, 8, 62, 65, 87, 69, 65, 68, 81, 8, 65, 79, 8, 61, 82, 63, 68, 2, 14, 83, 75, + 74, 8, 64, 69, 65, 132, 65, 73, 8, 54, 61, 67, 65, 8, 61, 62, 8, 62, 69, 80, 8, + 87, 82, 73, 8, 25, 23, 20, 8, 48, 103, 79, 87, 8, 23, 31, 22, 22, 15, 8, 64, 69, + 65, 2, 52, 65, 73, 82, 74, 65, 79, 61, 81, 69, 75, 74, 8, 14, 83, 75, 74, 8, 23, + 22, 26, 18, 23, 28, 8, 1, 112, 8, 73, 75, 74, 61, 81, 72, 69, 63, 68, 15, 2, 23, + 22, 26, 23, 18, 28, 29, 8, 1, 112, 20, 8, 53, 63, 68, 109, 74, 65, 62, 65, 79, 67, + 8, 68, 61, 81, 8, 83, 75, 73, 8, 50, 71, 81, 75, 62, 65, 79, 8, 61, 62, 8, 69, + 73, 2, 56, 65, 79, 67, 72, 65, 69, 63, 68, 8, 87, 82, 8, 64, 65, 74, 8, 61, 74, + 64, 65, 79, 65, 74, 8, 43, 61, 82, 80, 84, 61, 79, 81, 65, 74, 8, 14, 132, 69, 65, + 68, 65, 8, 60, 82, 132, 61, 73, 73, 65, 74, 4, 2, 132, 81, 65, 72, 72, 82, 74, 67, + 8, 61, 82, 66, 8, 37, 72, 61, 81, 81, 8, 23, 26, 8, 52, 15, 8, 64, 69, 65, 8, + 67, 79, 109, 102, 81, 65, 8, 41, 72, 103, 63, 68, 65, 8, 3, 2, 23, 22, 26, 23, 18, + 30, 31, 8, 77, 73, 8, 3, 8, 82, 74, 64, 8, 64, 69, 65, 8, 67, 79, 109, 102, 81, + 65, 8, 36, 74, 87, 61, 68, 72, 8, 60, 69, 73, 73, 65, 79, 8, 78, 78, 20, 8, 24, + 27, 22, 8, 1, 112, 2, 87, 82, 8, 79, 65, 69, 74, 69, 67, 65, 74, 8, 14, 82, 74, + 64, 8, 87, 82, 8, 68, 65, 69, 87, 65, 74, 15, 18, 8, 84, 61, 80, 8, 65, 79, 8, + 75, 68, 74, 65, 8, 66, 79, 65, 73, 64, 65, 2, 43, 110, 72, 66, 65, 8, 74, 69, 63, + 68, 81, 8, 62, 65, 84, 103, 72, 81, 69, 67, 65, 74, 8, 71, 61, 74, 74, 20, 8, 57, + 69, 79, 8, 68, 61, 62, 65, 74, 8, 64, 65, 80, 68, 61, 72, 62, 2, 64, 69, 65, 8, + 40, 79, 68, 109, 68, 82, 74, 67, 8, 132, 65, 69, 74, 65, 79, 8, 52, 65, 73, 82, 74, + 65, 79, 61, 81, 69, 75, 74, 8, 83, 75, 73, 8, 23, 20, 8, 50, 71, 81, 75, 62, 65, + 79, 2, 64, 20, 8, 45, 80, 20, 8, 61, 62, 8, 83, 75, 74, 8, 23, 24, 27, 22, 8, + 1, 112, 8, 61, 82, 66, 8, 23, 27, 22, 22, 8, 1, 112, 8, 70, 103, 68, 79, 72, 69, + 63, 68, 8, 62, 65, 4, 2, 132, 63, 68, 72, 75, 132, 132, 65, 74, 20, 8, 39, 69, 65, + 80, 8, 73, 61, 63, 68, 81, 8, 66, 110, 79, 8, 64, 69, 65, 8, 60, 65, 69, 81, 8, + 83, 75, 73, 8, 23, 20, 8, 50, 71, 81, 75, 62, 65, 79, 2, 23, 30, 31, 31, 8, 62, + 69, 80, 8, 87, 82, 73, 8, 25, 23, 20, 8, 48, 103, 79, 87, 8, 23, 31, 22, 22, 8, + 23, 24, 27, 8, 48, 61, 79, 71, 20, 2, 39, 61, 8, 68, 69, 65, 79, 74, 61, 63, 68, + 8, 24, 24, 27, 8, 1, 112, 8, 1, 17, 8, 23, 22, 26, 23, 18, 28, 29, 8, 1, 112, + 8, 1, 17, 8, 23, 24, 27, 8, 48, 61, 79, 71, 2, 23, 25, 31, 23, 18, 28, 29, 8, + 1, 112, 8, 67, 65, 62, 79, 61, 82, 63, 68, 81, 8, 84, 65, 79, 64, 65, 74, 8, 14, + 69, 73, 8, 40, 81, 61, 81, 8, 61, 62, 65, 79, 8, 74, 82, 79, 8, 23, 24, 27, 22, + 18, 22, 22, 8, 1, 112, 2, 83, 75, 79, 67, 65, 132, 65, 68, 65, 74, 8, 132, 69, 74, + 64, 15, 18, 8, 132, 75, 8, 69, 132, 81, 8, 65, 69, 74, 65, 8, 56, 65, 79, 132, 81, + 103, 79, 71, 82, 74, 67, 8, 64, 65, 79, 8, 51, 75, 132, 69, 81, 69, 75, 74, 2, 50, + 20, 8, 44, 3, 23, 23, 3, 28, 8, 82, 73, 8, 20, 8, 20, 8, 20, 8, 23, 26, 23, + 18, 28, 29, 8, 1, 112, 8, 74, 75, 81, 68, 84, 65, 74, 64, 69, 67, 20, 2, 41, 110, + 79, 8, 51, 79, 110, 66, 82, 74, 67, 8, 64, 65, 79, 8, 51, 79, 75, 70, 65, 71, 81, + 65, 18, 8, 55, 65, 62, 65, 79, 84, 61, 63, 68, 82, 74, 67, 8, 82, 74, 64, 2, 36, + 62, 74, 61, 68, 73, 65, 8, 64, 65, 79, 8, 44, 74, 132, 81, 61, 72, 72, 61, 81, 69, + 75, 74, 8, 69, 132, 81, 8, 65, 69, 74, 65, 8, 65, 69, 74, 73, 61, 72, 69, 67, 65, + 8, 56, 65, 79, 4, 2, 67, 110, 81, 82, 74, 67, 8, 83, 75, 74, 8, 26, 11, 8, 64, + 65, 79, 8, 36, 74, 72, 61, 67, 65, 71, 75, 132, 81, 65, 74, 18, 8, 70, 65, 64, 75, + 63, 68, 8, 74, 69, 63, 68, 81, 8, 73, 65, 68, 79, 2, 61, 72, 80, 8, 25, 22, 22, + 8, 1, 112, 8, 66, 110, 79, 8, 70, 65, 64, 65, 8, 44, 74, 132, 81, 61, 72, 72, 61, + 81, 69, 75, 74, 18, 8, 87, 82, 8, 87, 61, 68, 72, 65, 74, 18, 8, 84, 75, 62, 65, + 69, 2, 64, 69, 65, 8, 46, 75, 132, 81, 65, 74, 8, 66, 110, 79, 8, 37, 65, 72, 65, + 82, 63, 68, 81, 82, 74, 67, 80, 71, 109, 79, 78, 65, 79, 18, 8, 47, 61, 73, 78, 65, + 74, 8, 82, 74, 64, 2, 48, 75, 81, 75, 79, 65, 74, 8, 74, 69, 63, 68, 81, 8, 73, + 69, 81, 8, 87, 82, 8, 62, 65, 79, 65, 63, 68, 74, 65, 74, 8, 132, 69, 74, 64, 20, + 8, 27, 22, 22, 8, 62, 69, 80, 8, 28, 27, 24, 8, 1, 112, 8, 39, 69, 65, 8, 46, + 75, 132, 81, 65, 74, 2, 64, 65, 80, 8, 53, 81, 79, 75, 73, 83, 65, 79, 62, 79, 61, + 82, 63, 68, 80, 8, 14, 84, 103, 68, 79, 65, 74, 64, 8, 64, 65, 79, 8, 36, 62, 74, + 61, 68, 73, 65, 78, 79, 110, 66, 82, 74, 67, 15, 2, 64, 65, 79, 8, 44, 74, 132, 81, + 61, 72, 72, 61, 81, 69, 75, 74, 8, 84, 65, 79, 64, 65, 74, 8, 64, 65, 73, 8, 36, + 62, 74, 65, 68, 73, 65, 79, 8, 74, 61, 63, 68, 8, 48, 61, 102, 4, 2, 67, 61, 62, + 65, 8, 64, 65, 80, 8, 69, 73, 8, 91, 8, 28, 8, 65, 74, 81, 68, 61, 72, 81, 65, + 74, 65, 74, 8, 54, 61, 79, 69, 66, 80, 8, 69, 74, 8, 52, 65, 63, 68, 74, 82, 74, + 67, 8, 67, 65, 132, 81, 65, 72, 72, 81, 20, 2, 39, 69, 65, 8, 48, 69, 65, 81, 68, + 65, 8, 84, 69, 79, 64, 8, 83, 75, 74, 8, 65, 79, 66, 75, 72, 67, 81, 65, 79, 8, + 44, 74, 62, 65, 81, 79, 69, 65, 62, 132, 65, 81, 87, 82, 74, 67, 2, 64, 65, 80, 8, + 48, 65, 132, 132, 65, 79, 80, 8, 61, 74, 8, 66, 110, 79, 8, 64, 65, 74, 8, 83, 75, + 72, 72, 65, 74, 8, 46, 61, 72, 65, 74, 64, 65, 79, 73, 75, 74, 61, 81, 8, 62, 65, + 79, 65, 63, 68, 74, 65, 81, 18, 2, 82, 74, 64, 8, 87, 84, 61, 79, 8, 61, 82, 63, + 68, 8, 64, 61, 74, 74, 8, 84, 65, 74, 74, 8, 64, 65, 79, 8, 91, 8, 25, 18, 8, + 91, 8, 23, 23, 18, 8, 91, 8, 23, 30, 8, 82, 74, 64, 8, 91, 8, 23, 29, 8, 42, + 65, 72, 81, 82, 74, 67, 2, 62, 65, 68, 61, 72, 81, 65, 74, 8, 14, 69, 73, 8, 41, + 61, 72, 72, 65, 8, 65, 69, 74, 65, 80, 8, 56, 65, 79, 73, 69, 74, 64, 65, 79, 82, + 74, 67, 8, 82, 73, 8, 27, 11, 8, 62, 69, 80, 8, 29, 11, 8, 75, 64, 65, 79, 8, + 83, 75, 74, 2, 23, 23, 11, 8, 62, 69, 80, 8, 23, 24, 11, 15, 20, 2, 48, 61, 102, + 74, 61, 68, 73, 65, 74, 8, 56, 75, 79, 132, 63, 68, 82, 62, 8, 67, 65, 72, 65, 69, + 132, 81, 65, 81, 8, 84, 65, 79, 64, 65, 74, 18, 8, 64, 69, 65, 8, 64, 61, 68, 69, + 74, 2, 61, 62, 87, 69, 65, 72, 65, 74, 18, 8, 64, 61, 102, 8, 64, 69, 65, 8, 47, + 65, 82, 81, 65, 8, 61, 82, 80, 8, 64, 65, 74, 8, 67, 79, 75, 102, 65, 74, 8, 53, + 81, 103, 64, 81, 65, 74, 2, 74, 61, 63, 68, 8, 64, 65, 73, 8, 78, 72, 61, 81, 81, + 65, 74, 8, 47, 61, 74, 64, 65, 8, 61, 62, 87, 69, 65, 68, 65, 74, 20, 2, 91, 8, + 27, 20, 2, 7, 40, 80, 8, 69, 132, 81, 8, 61, 72, 132, 75, 8, 14, 73, 65, 69, 74, + 65, 79, 8, 55, 65, 62, 65, 79, 87, 65, 82, 67, 82, 74, 67, 8, 74, 61, 63, 68, 15, + 8, 67, 61, 74, 87, 8, 82, 74, 4, 2, 84, 61, 68, 79, 132, 63, 68, 65, 69, 74, 72, + 69, 63, 68, 18, 8, 64, 61, 102, 8, 87, 61, 68, 72, 79, 65, 69, 63, 68, 65, 8, 47, + 65, 82, 81, 65, 8, 83, 75, 73, 8, 47, 61, 74, 64, 65, 8, 74, 61, 63, 68, 2, 64, + 65, 79, 8, 67, 79, 75, 102, 65, 74, 8, 53, 81, 61, 64, 81, 8, 87, 69, 65, 68, 65, + 74, 8, 84, 65, 79, 64, 65, 74, 33, 8, 83, 69, 65, 72, 73, 65, 68, 79, 8, 84, 69, + 79, 64, 8, 65, 80, 2, 82, 73, 67, 65, 71, 65, 68, 79, 81, 8, 132, 65, 69, 74, 18, + 8, 64, 61, 102, 8, 65, 69, 74, 65, 8, 36, 62, 84, 61, 74, 64, 65, 79, 82, 74, 67, + 8, 61, 82, 80, 8, 64, 65, 79, 2, 67, 79, 75, 102, 65, 74, 8, 53, 81, 61, 64, 81, + 8, 74, 61, 63, 68, 8, 64, 65, 73, 8, 47, 61, 74, 64, 65, 8, 82, 74, 64, 8, 64, + 65, 74, 8, 71, 72, 65, 69, 74, 65, 74, 2, 53, 81, 103, 64, 81, 65, 74, 8, 65, 79, + 66, 75, 72, 67, 65, 74, 8, 84, 69, 79, 64, 20, 8, 14, 39, 65, 80, 68, 61, 72, 62, + 8, 67, 72, 61, 82, 62, 65, 8, 69, 63, 68, 8, 74, 69, 63, 68, 81, 18, 2, 64, 61, + 102, 8, 68, 69, 65, 79, 8, 65, 69, 74, 65, 8, 57, 75, 68, 74, 82, 74, 67, 80, 74, + 75, 81, 8, 65, 69, 74, 81, 79, 65, 81, 65, 74, 8, 84, 69, 79, 64, 20, 15, 6, 2, + 53, 63, 68, 75, 74, 8, 62, 65, 69, 8, 64, 65, 79, 8, 83, 75, 79, 69, 67, 65, 74, + 8, 37, 65, 79, 61, 81, 82, 74, 67, 8, 69, 132, 81, 8, 64, 61, 79, 61, 82, 66, 8, + 68, 69, 74, 4, 2, 67, 65, 84, 69, 65, 132, 65, 74, 8, 84, 75, 79, 64, 65, 74, 18, + 8, 64, 61, 102, 8, 64, 69, 65, 8, 36, 74, 132, 69, 63, 68, 81, 65, 74, 8, 68, 69, + 65, 79, 110, 62, 65, 79, 8, 132, 65, 68, 79, 2, 84, 65, 69, 81, 8, 61, 82, 80, 65, + 69, 74, 61, 74, 64, 65, 79, 67, 65, 68, 65, 74, 18, 8, 14, 82, 74, 64, 8, 69, 74, + 8, 64, 65, 74, 8, 46, 79, 65, 69, 132, 65, 74, 18, 8, 73, 69, 81, 2, 64, 65, 74, + 65, 74, 8, 69, 63, 68, 8, 41, 110, 68, 72, 82, 74, 67, 8, 68, 61, 62, 65, 15, 18, + 8, 69, 132, 81, 8, 64, 82, 79, 63, 68, 61, 82, 80, 8, 64, 69, 65, 8, 36, 82, 66, + 66, 61, 132, 132, 82, 74, 67, 2, 83, 65, 79, 62, 79, 65, 69, 81, 65, 81, 18, 8, 64, + 61, 102, 8, 65, 68, 65, 79, 8, 65, 69, 74, 8, 57, 75, 68, 74, 82, 74, 67, 80, 110, + 62, 65, 79, 132, 63, 68, 82, 102, 8, 61, 72, 80, 8, 65, 69, 74, 2, 57, 75, 68, 74, + 82, 74, 67, 80, 73, 61, 74, 67, 65, 72, 8, 65, 69, 74, 81, 79, 65, 81, 65, 74, 8, + 84, 69, 79, 64, 20, 2, 7, 44, 63, 68, 8, 73, 109, 63, 68, 81, 65, 8, 74, 75, 63, + 68, 8, 64, 61, 79, 61, 74, 8, 65, 79, 69, 74, 74, 65, 79, 74, 18, 8, 64, 61, 102, + 8, 64, 65, 79, 8, 43, 65, 79, 79, 2, 53, 81, 61, 64, 81, 132, 86, 74, 64, 69, 71, + 82, 80, 8, 14, 64, 61, 80, 8, 83, 75, 79, 69, 67, 65, 8, 48, 61, 72, 15, 8, 61, + 82, 80, 67, 65, 66, 110, 68, 79, 81, 8, 68, 61, 81, 18, 8, 83, 75, 79, 2, 64, 65, + 73, 8, 46, 79, 69, 65, 67, 65, 8, 68, 103, 81, 81, 65, 74, 8, 69, 74, 8, 64, 65, + 73, 8, 65, 69, 67, 65, 74, 81, 72, 69, 63, 68, 65, 74, 8, 37, 65, 79, 72, 69, 74, + 8, 24, 30, 8, 22, 22, 22, 2, 57, 75, 68, 74, 82, 74, 67, 65, 74, 8, 83, 75, 74, + 8, 23, 8, 62, 69, 80, 8, 24, 8, 60, 69, 73, 73, 65, 79, 74, 8, 72, 65, 65, 79, + 8, 67, 65, 132, 81, 61, 74, 64, 65, 74, 20, 2, 44, 74, 87, 84, 69, 132, 63, 68, 65, + 74, 8, 84, 65, 79, 64, 65, 74, 8, 64, 69, 65, 132, 65, 8, 24, 30, 8, 22, 22, 22, + 8, 57, 75, 68, 74, 82, 74, 67, 65, 74, 8, 84, 75, 68, 72, 8, 61, 82, 63, 68, 2, + 74, 69, 63, 68, 81, 8, 62, 65, 87, 75, 67, 65, 74, 8, 84, 75, 79, 64, 65, 74, 8, + 132, 65, 69, 74, 18, 8, 82, 74, 64, 8, 84, 65, 74, 74, 8, 73, 61, 74, 8, 74, 82, + 74, 8, 61, 74, 4, 2, 74, 69, 73, 73, 81, 18, 8, 64, 61, 102, 8, 69, 74, 8, 70, + 65, 64, 65, 8, 64, 69, 65, 132, 65, 79, 8, 57, 75, 68, 74, 82, 74, 67, 65, 74, 8, + 65, 81, 84, 61, 8, 27, 8, 51, 65, 79, 4, 2, 132, 75, 74, 65, 74, 8, 82, 74, 81, + 65, 79, 67, 65, 62, 79, 61, 63, 68, 81, 8, 84, 65, 79, 64, 65, 74, 8, 71, 109, 74, + 74, 65, 74, 18, 8, 64, 61, 74, 74, 8, 71, 109, 74, 74, 65, 74, 18, 2, 84, 65, 74, + 74, 8, 61, 72, 132, 75, 8, 74, 82, 79, 8, 64, 69, 65, 132, 65, 8, 24, 30, 8, 22, + 22, 22, 8, 57, 75, 68, 74, 82, 74, 67, 65, 74, 8, 62, 65, 87, 75, 67, 65, 74, 2, + 84, 65, 79, 64, 65, 74, 18, 8, 14, 69, 74, 8, 37, 65, 79, 72, 69, 74, 15, 8, 61, + 72, 72, 65, 69, 74, 8, 65, 81, 84, 61, 8, 23, 26, 22, 8, 22, 22, 22, 8, 48, 65, + 74, 132, 63, 68, 65, 74, 2, 65, 69, 74, 65, 8, 57, 75, 68, 74, 82, 74, 67, 80, 67, + 65, 72, 65, 67, 65, 74, 68, 65, 69, 81, 8, 66, 69, 74, 64, 65, 74, 20, 6, 2, 7, + 44, 63, 68, 8, 67, 72, 61, 82, 62, 65, 18, 8, 84, 69, 79, 8, 71, 109, 74, 74, 65, + 74, 8, 61, 72, 132, 75, 8, 67, 61, 74, 87, 8, 67, 65, 81, 79, 75, 132, 81, 8, 64, + 65, 79, 8, 14, 60, 82, 4, 2, 71, 82, 74, 66, 81, 8, 65, 74, 81, 67, 65, 67, 65, + 74, 132, 65, 68, 65, 74, 15, 8, 82, 74, 64, 8, 62, 79, 61, 82, 63, 68, 65, 74, 8, + 74, 69, 63, 68, 81, 8, 87, 82, 8, 62, 65, 66, 110, 79, 63, 68, 81, 65, 74, 18, 2, + 64, 61, 102, 8, 65, 69, 74, 65, 8, 46, 72, 65, 69, 74, 84, 75, 68, 74, 82, 74, 67, + 80, 74, 75, 81, 8, 65, 69, 74, 81, 79, 65, 81, 65, 74, 8, 84, 69, 79, 64, 20, 6, + 2, 7, 49, 61, 63, 68, 8, 82, 74, 132, 65, 79, 65, 79, 8, 36, 82, 66, 66, 61, 132, + 132, 82, 74, 67, 8, 69, 132, 81, 8, 65, 80, 8, 74, 69, 63, 68, 81, 8, 74, 109, 81, + 69, 67, 18, 8, 64, 61, 102, 8, 65, 81, 84, 61, 80, 8, 37, 65, 4, 2, 132, 75, 74, + 64, 65, 79, 65, 80, 8, 69, 74, 8, 64, 69, 65, 132, 65, 79, 8, 37, 65, 87, 69, 65, + 68, 82, 74, 67, 8, 67, 65, 132, 63, 68, 69, 65, 68, 81, 8, 82, 74, 64, 8, 74, 65, + 82, 65, 2, 48, 61, 102, 74, 61, 68, 73, 65, 74, 8, 61, 82, 66, 8, 64, 69, 65, 132, + 65, 73, 8, 42, 65, 62, 69, 65, 81, 65, 8, 67, 65, 81, 79, 75, 66, 66, 65, 74, 8, + 84, 65, 79, 64, 65, 74, 20, 6, 2, 14, 53, 81, 61, 64, 81, 132, 86, 74, 64, 69, 71, + 82, 80, 8, 53, 65, 73, 62, 79, 69, 81, 87, 71, 69, 15, 32, 8, 7, 48, 65, 69, 74, + 65, 8, 43, 65, 79, 79, 65, 74, 18, 2, 65, 80, 8, 84, 61, 79, 8, 64, 61, 68, 65, + 79, 8, 64, 69, 65, 8, 65, 79, 132, 81, 65, 8, 51, 66, 72, 69, 63, 68, 81, 18, 2, + 82, 73, 8, 110, 62, 65, 79, 68, 61, 82, 78, 81, 8, 65, 69, 74, 65, 74, 8, 81, 79, + 61, 67, 66, 103, 68, 69, 67, 65, 74, 8, 37, 75, 64, 65, 74, 8, 66, 110, 79, 8, 84, + 65, 69, 81, 65, 79, 65, 2, 40, 79, 84, 103, 67, 82, 74, 67, 65, 74, 8, 87, 82, 8, + 66, 69, 74, 64, 65, 74, 18, 8, 64, 69, 65, 8, 56, 65, 79, 68, 103, 72, 81, 74, 69, + 132, 132, 65, 18, 8, 84, 69, 65, 8, 132, 69, 65, 8, 62, 65, 69, 2, 82, 74, 80, 18, + 8, 14, 64, 20, 8, 68, 20, 8, 74, 69, 63, 68, 81, 8, 74, 82, 79, 8, 69, 74, 8, + 38, 68, 61, 79, 72, 75, 81, 81, 65, 74, 62, 82, 79, 67, 18, 8, 132, 75, 74, 64, 65, + 79, 74, 8, 69, 74, 2, 42, 79, 75, 102, 4, 37, 65, 79, 72, 69, 74, 18, 8, 67, 65, + 72, 61, 67, 65, 79, 81, 8, 132, 69, 74, 64, 15, 18, 8, 66, 65, 132, 81, 87, 82, 132, + 81, 65, 72, 72, 65, 74, 20, 8, 60, 82, 8, 64, 69, 65, 132, 65, 73, 2, 60, 84, 65, + 63, 71, 65, 8, 69, 132, 81, 18, 8, 84, 69, 65, 8, 69, 74, 8, 64, 65, 79, 8, 48, + 69, 81, 81, 65, 69, 72, 82, 74, 67, 18, 8, 64, 69, 65, 8, 44, 68, 74, 65, 74, 8, + 67, 65, 4, 2, 64, 79, 82, 63, 71, 81, 8, 87, 82, 67, 65, 67, 61, 74, 67, 65, 74, + 8, 69, 132, 81, 18, 8, 64, 61, 79, 67, 65, 72, 65, 67, 81, 8, 69, 132, 81, 18, 8, + 65, 69, 74, 65, 8, 60, 103, 68, 72, 82, 74, 67, 2, 64, 65, 79, 8, 72, 65, 65, 79, + 8, 132, 81, 65, 68, 65, 74, 64, 65, 74, 8, 57, 75, 68, 74, 82, 74, 67, 65, 74, 8, + 69, 74, 8, 42, 79, 75, 102, 4, 37, 65, 79, 72, 69, 74, 8, 3, 2, 14, 64, 69, 65, + 8, 65, 79, 132, 81, 65, 8, 64, 65, 79, 61, 79, 81, 69, 67, 65, 8, 60, 103, 68, 72, + 82, 74, 67, 8, 74, 61, 63, 68, 8, 65, 69, 74, 65, 73, 8, 65, 69, 74, 68, 65, 69, + 81, 72, 69, 63, 68, 65, 74, 2, 53, 63, 68, 65, 73, 61, 15, 8, 3, 8, 83, 75, 79, + 67, 65, 74, 75, 73, 73, 65, 74, 8, 84, 75, 79, 64, 65, 74, 20, 6, 2, 7, 44, 63, + 68, 8, 73, 109, 63, 68, 81, 65, 8, 69, 74, 8, 51, 61, 79, 65, 74, 81, 68, 65, 132, + 65, 8, 87, 82, 8, 64, 65, 79, 8, 36, 82, 66, 66, 61, 132, 132, 82, 74, 67, 18, 8, + 64, 65, 79, 8, 48, 61, 67, 69, 132, 81, 79, 61, 81, 8, 68, 103, 81, 81, 65, 2, 69, + 74, 8, 64, 69, 65, 132, 65, 79, 8, 53, 61, 63, 68, 65, 8, 74, 69, 63, 68, 81, 80, + 8, 67, 65, 81, 61, 74, 18, 8, 62, 65, 73, 65, 79, 71, 65, 74, 18, 8, 64, 61, 102, + 8, 64, 69, 65, 132, 65, 2, 132, 81, 61, 81, 69, 132, 81, 69, 132, 63, 68, 65, 8, 36, + 82, 66, 74, 61, 68, 73, 65, 8, 14, 67, 65, 67, 65, 74, 8, 74, 69, 63, 68, 81, 8, + 82, 74, 65, 79, 68, 65, 62, 72, 69, 63, 68, 65, 8, 57, 69, 64, 65, 79, 4, 2, 132, + 81, 103, 74, 64, 65, 8, 69, 74, 8, 42, 79, 75, 102, 4, 37, 65, 79, 72, 69, 74, 15, + 8, 61, 82, 66, 8, 44, 74, 69, 81, 69, 61, 81, 69, 83, 65, 74, 8, 64, 65, 80, 8, + 37, 65, 79, 72, 69, 74, 65, 79, 2, 56, 65, 79, 65, 69, 74, 80, 8, 66, 110, 79, 8, + 46, 72, 65, 69, 74, 84, 75, 68, 74, 82, 74, 67, 80, 84, 65, 132, 65, 74, 8, 87, 82, + 132, 81, 61, 74, 64, 65, 8, 67, 65, 71, 75, 73, 4, 2, 73, 65, 74, 8, 82, 74, 64, + 8, 69, 74, 8, 64, 69, 65, 132, 65, 73, 8, 56, 65, 79, 65, 69, 74, 8, 64, 65, 79, + 8, 68, 69, 65, 73, 82, 66, 8, 62, 65, 87, 110, 67, 4, 2, 72, 69, 63, 68, 65, 8, + 36, 74, 81, 79, 61, 67, 8, 83, 75, 74, 8, 64, 65, 73, 8, 56, 65, 79, 81, 79, 65, + 81, 65, 79, 8, 64, 65, 79, 8, 53, 81, 61, 64, 81, 8, 38, 68, 61, 79, 72, 75, 81, + 4, 2, 81, 65, 74, 62, 82, 79, 67, 8, 61, 82, 80, 67, 65, 67, 61, 74, 67, 65, 74, + 8, 69, 132, 81, 20, 6, 2, 14, 7, 68, 65, 66, 81, 69, 67, 65, 79, 8, 36, 78, 78, + 72, 61, 82, 80, 6, 15, 20, 8, 91, 8, 23, 26, 18, 8, 23, 27, 8, 82, 74, 64, 8, + 23, 29, 8, 61, 8, 62, 69, 80, 8, 66, 20, 8, 91, 8, 24, 24, 20, 8, 14, 91, 8, + 26, 8, 62, 69, 80, 8, 28, 15, 2, 39, 61, 80, 8, 132, 63, 68, 65, 69, 74, 81, 8, + 73, 69, 79, 8, 71, 65, 69, 74, 65, 8, 14, 7, 67, 61, 74, 87, 8, 82, 74, 84, 69, + 63, 68, 81, 69, 67, 65, 8, 48, 61, 102, 79, 65, 67, 65, 72, 6, 15, 8, 69, 74, 8, + 64, 69, 65, 132, 65, 79, 8, 53, 61, 63, 68, 65, 8, 67, 65, 84, 65, 132, 65, 74, 2, + 87, 82, 8, 132, 65, 69, 74, 8, 14, 64, 69, 65, 8, 41, 65, 132, 81, 132, 81, 65, 72, + 72, 82, 74, 67, 8, 64, 65, 79, 8, 40, 79, 67, 65, 62, 74, 69, 132, 132, 65, 8, 64, + 69, 65, 132, 65, 79, 8, 53, 81, 61, 4, 2, 81, 69, 132, 81, 69, 71, 8, 69, 132, 81, + 8, 75, 72, 72, 65, 79, 64, 69, 74, 67, 80, 8, 62, 65, 69, 8, 64, 65, 79, 8, 67, + 65, 67, 65, 74, 84, 103, 79, 81, 69, 67, 65, 74, 8, 42, 65, 132, 63, 68, 103, 66, 81, + 80, 4, 2, 72, 61, 67, 65, 8, 64, 65, 79, 8, 37, 65, 68, 109, 79, 64, 65, 74, 8, + 69, 74, 8, 42, 79, 75, 102, 4, 37, 65, 79, 72, 69, 74, 8, 73, 69, 81, 8, 52, 110, + 63, 71, 132, 69, 63, 68, 81, 8, 61, 82, 66, 2, 64, 65, 74, 8, 55, 73, 132, 81, 61, + 74, 64, 18, 8, 64, 61, 102, 8, 65, 69, 74, 65, 8, 36, 74, 87, 61, 68, 72, 8, 83, + 75, 74, 8, 42, 79, 75, 102, 4, 37, 65, 79, 72, 69, 74, 65, 79, 2, 64, 65, 74, 8, + 55, 73, 132, 81, 61, 74, 64, 18, 8, 64, 61, 102, 8, 65, 69, 74, 65, 8, 36, 74, 87, + 61, 68, 72, 8, 83, 75, 74, 8, 42, 79, 75, 102, 4, 37, 65, 79, 72, 69, 74, 65, 79, + 2, 42, 65, 73, 65, 69, 74, 64, 65, 74, 8, 71, 65, 69, 74, 65, 8, 132, 81, 61, 81, + 69, 132, 81, 69, 132, 63, 68, 65, 74, 8, 36, 65, 73, 81, 65, 79, 8, 75, 64, 65, 79, + 8, 64, 65, 79, 67, 72, 65, 69, 63, 68, 65, 74, 2, 42, 65, 73, 65, 69, 74, 64, 65, + 74, 8, 71, 65, 69, 74, 65, 8, 132, 81, 61, 81, 69, 132, 81, 69, 132, 63, 68, 65, 74, + 8, 36, 65, 73, 81, 65, 79, 8, 75, 64, 65, 79, 8, 64, 65, 79, 67, 72, 65, 69, 63, + 68, 65, 74, 2, 40, 69, 74, 79, 69, 63, 68, 81, 82, 74, 67, 65, 74, 8, 68, 61, 62, + 65, 74, 18, 8, 74, 69, 63, 68, 81, 8, 67, 61, 74, 87, 8, 72, 65, 69, 63, 68, 81, + 8, 67, 65, 84, 65, 132, 65, 74, 15, 20, 2, 40, 69, 74, 79, 69, 63, 68, 81, 82, 74, + 67, 65, 74, 8, 68, 61, 62, 65, 74, 18, 8, 74, 69, 63, 68, 81, 8, 67, 61, 74, 87, + 8, 72, 65, 69, 63, 68, 81, 8, 67, 65, 84, 65, 132, 65, 74, 15, 20, 2, 7, 54, 61, + 81, 132, 103, 63, 68, 72, 69, 63, 68, 8, 69, 132, 81, 8, 64, 65, 73, 8, 48, 61, 67, + 69, 132, 81, 79, 61, 81, 8, 64, 61, 80, 8, 61, 73, 81, 72, 69, 63, 68, 65, 8, 40, + 79, 67, 65, 62, 74, 69, 80, 8, 64, 65, 79, 2, 7, 54, 61, 81, 132, 103, 63, 68, 72, + 69, 63, 68, 8, 69, 132, 81, 8, 64, 65, 73, 8, 48, 61, 67, 69, 132, 81, 79, 61, 81, + 8, 64, 61, 80, 8, 61, 73, 81, 72, 69, 63, 68, 65, 8, 40, 79, 67, 65, 62, 74, 69, + 80, 8, 64, 65, 79, 2, 42, 79, 75, 102, 8, 37, 65, 79, 72, 69, 74, 65, 79, 8, 53, + 81, 61, 81, 69, 132, 81, 69, 71, 8, 61, 82, 63, 68, 8, 65, 79, 132, 81, 8, 69, 74, + 8, 64, 65, 74, 8, 72, 65, 81, 87, 81, 65, 74, 8, 54, 61, 67, 65, 74, 2, 42, 79, + 75, 102, 8, 37, 65, 79, 72, 69, 74, 65, 79, 8, 53, 81, 61, 81, 69, 132, 81, 69, 71, + 8, 61, 82, 63, 68, 8, 65, 79, 132, 81, 8, 69, 74, 8, 64, 65, 74, 8, 72, 65, 81, + 87, 81, 65, 74, 8, 54, 61, 67, 65, 74, 2, 64, 65, 80, 8, 48, 75, 74, 61, 81, 80, + 8, 39, 65, 87, 65, 73, 62, 65, 79, 8, 87, 82, 67, 65, 67, 61, 74, 67, 65, 74, 20, + 6, 2, 64, 65, 80, 8, 48, 75, 74, 61, 81, 80, 8, 39, 65, 87, 65, 73, 62, 65, 79, + 8, 87, 82, 67, 65, 67, 61, 74, 67, 65, 74, 20, 6, 2, 68, 65, 82, 81, 65, 8, 64, + 69, 65, 8, 53, 81, 61, 64, 81, 83, 65, 79, 84, 61, 72, 81, 82, 74, 67, 8, 83, 75, + 74, 18, 8, 38, 68, 61, 79, 72, 75, 81, 81, 65, 74, 62, 82, 79, 67, 2, 68, 65, 82, + 81, 65, 8, 64, 69, 65, 8, 53, 81, 61, 64, 81, 83, 65, 79, 84, 61, 72, 81, 82, 74, + 67, 8, 83, 75, 74, 18, 8, 38, 68, 61, 79, 72, 75, 81, 81, 65, 74, 62, 82, 79, 67, + 2, 69, 74, 8, 53, 81, 61, 74, 64, 8, 132, 65, 81, 87, 65, 74, 18, 8, 64, 69, 65, + 132, 65, 79, 8, 61, 71, 82, 81, 65, 74, 8, 57, 75, 68, 74, 82, 74, 67, 80, 74, 75, + 81, 8, 65, 74, 81, 4, 2, 69, 74, 8, 53, 81, 61, 74, 64, 8, 132, 65, 81, 87, 65, + 74, 18, 8, 64, 69, 65, 132, 65, 79, 8, 61, 71, 82, 81, 65, 74, 8, 57, 75, 68, 74, + 82, 74, 67, 80, 74, 75, 81, 8, 65, 74, 81, 4, 2, 67, 65, 67, 65, 74, 87, 82, 81, + 79, 65, 81, 65, 74, 20, 8, 39, 61, 80, 8, 69, 132, 81, 8, 64, 69, 65, 8, 41, 79, + 61, 67, 65, 18, 8, 82, 73, 8, 64, 69, 65, 8, 65, 80, 8, 132, 69, 63, 68, 8, 14, + 91, 8, 30, 15, 2, 67, 65, 67, 65, 74, 87, 82, 81, 79, 65, 81, 65, 74, 20, 8, 39, + 61, 80, 8, 69, 132, 81, 8, 64, 69, 65, 8, 41, 79, 61, 67, 65, 18, 8, 82, 73, 8, + 64, 69, 65, 8, 65, 80, 8, 132, 69, 63, 68, 8, 14, 91, 8, 30, 15, 2, 64, 79, 65, + 68, 81, 20, 8, 40, 69, 74, 87, 69, 67, 8, 82, 74, 64, 8, 61, 72, 72, 65, 69, 74, + 8, 83, 75, 74, 8, 64, 69, 65, 132, 65, 73, 8, 53, 81, 61, 74, 64, 78, 82, 74, 71, + 81, 2, 64, 79, 65, 68, 81, 20, 8, 40, 69, 74, 87, 69, 67, 8, 82, 74, 64, 8, 61, + 72, 72, 65, 69, 74, 8, 83, 75, 74, 8, 64, 69, 65, 132, 65, 73, 8, 53, 81, 61, 74, + 64, 78, 82, 74, 71, 81, 2, 61, 82, 80, 8, 68, 61, 62, 65, 74, 8, 84, 69, 79, 8, + 69, 73, 8, 83, 75, 79, 69, 67, 65, 74, 8, 45, 61, 68, 79, 65, 8, 64, 65, 74, 8, + 36, 74, 81, 79, 61, 67, 8, 67, 65, 4, 2, 61, 82, 80, 8, 68, 61, 62, 65, 74, 8, + 84, 69, 79, 8, 69, 73, 8, 83, 75, 79, 69, 67, 65, 74, 8, 45, 61, 68, 79, 65, 8, + 64, 65, 74, 8, 36, 74, 81, 79, 61, 67, 8, 67, 65, 4, 2, 132, 81, 65, 72, 72, 81, + 20, 8, 57, 69, 79, 8, 68, 61, 62, 65, 74, 8, 61, 82, 63, 68, 8, 74, 69, 63, 68, + 81, 8, 74, 109, 81, 69, 61, 18, 8, 61, 82, 66, 8, 64, 69, 65, 8, 110, 62, 79, 69, + 67, 65, 74, 2, 132, 81, 65, 72, 72, 81, 20, 8, 57, 69, 79, 8, 68, 61, 62, 65, 74, + 8, 61, 82, 63, 68, 8, 74, 69, 63, 68, 81, 8, 74, 109, 81, 69, 61, 18, 8, 61, 82, + 66, 8, 64, 69, 65, 8, 110, 62, 79, 69, 67, 65, 74, 2, 42, 79, 75, 102, 4, 56, 65, + 79, 72, 69, 74, 65, 79, 8, 42, 65, 73, 65, 69, 74, 64, 65, 74, 8, 52, 110, 63, 71, + 132, 68, 81, 8, 87, 82, 8, 69, 65, 2, 42, 79, 75, 102, 4, 56, 65, 79, 72, 69, 74, + 65, 79, 8, 42, 65, 73, 65, 69, 74, 64, 65, 74, 8, 52, 110, 63, 71, 132, 68, 81, 8, + 87, 82, 8, 69, 65, 2, 132, 75, 74, 64, 65, 79, 74, 8, 84, 69, 79, 8, 68, 61, 62, + 65, 74, 8, 82, 74, 132, 65, 79, 65, 8, 65, 69, 67, 65, 74, 65, 74, 8, 56, 65, 79, + 68, 103, 72, 81, 74, 69, 132, 132, 65, 8, 87, 82, 2, 132, 75, 74, 64, 65, 79, 74, 8, + 84, 69, 79, 8, 68, 61, 62, 65, 74, 8, 82, 74, 132, 65, 79, 65, 8, 65, 69, 67, 65, + 74, 65, 74, 8, 56, 65, 79, 68, 103, 72, 81, 74, 69, 132, 132, 65, 8, 87, 82, 2, 78, + 79, 110, 66, 65, 74, 18, 8, 82, 74, 64, 8, 84, 65, 74, 74, 8, 84, 69, 79, 8, 74, + 61, 63, 68, 8, 67, 65, 84, 69, 132, 132, 65, 74, 68, 61, 66, 81, 65, 79, 8, 51, 79, + 110, 66, 82, 74, 67, 2, 78, 79, 110, 66, 65, 74, 18, 8, 82, 74, 64, 8, 84, 65, 74, + 74, 8, 84, 69, 79, 8, 74, 61, 63, 68, 8, 67, 65, 84, 69, 132, 132, 65, 74, 68, 61, + 66, 81, 65, 79, 8, 51, 79, 110, 66, 82, 74, 67, 2, 87, 82, 8, 64, 65, 79, 8, 36, + 74, 132, 63, 68, 61, 82, 82, 74, 67, 8, 71, 75, 73, 73, 65, 74, 18, 8, 64, 61, 102, + 8, 65, 69, 74, 65, 8, 57, 75, 68, 74, 82, 74, 67, 80, 4, 2, 87, 82, 8, 64, 65, + 79, 8, 36, 74, 132, 63, 68, 61, 82, 82, 74, 67, 8, 71, 75, 73, 73, 65, 74, 18, 8, + 64, 61, 102, 8, 65, 69, 74, 65, 8, 57, 75, 68, 74, 82, 74, 67, 80, 4, 2, 74, 75, + 81, 8, 87, 82, 8, 65, 79, 84, 61, 79, 81, 65, 74, 8, 69, 132, 81, 18, 8, 64, 61, + 74, 74, 8, 69, 132, 81, 8, 65, 80, 8, 82, 74, 132, 65, 79, 65, 8, 83, 65, 79, 64, + 61, 73, 73, 81, 65, 2, 74, 75, 81, 8, 87, 82, 8, 65, 79, 84, 61, 79, 81, 65, 74, + 8, 69, 132, 81, 18, 8, 64, 61, 74, 74, 8, 69, 132, 81, 8, 65, 80, 8, 82, 74, 132, + 65, 79, 65, 8, 83, 65, 79, 64, 61, 73, 73, 81, 65, 2, 51, 66, 72, 69, 63, 68, 81, + 8, 82, 74, 64, 8, 53, 63, 68, 82, 72, 64, 69, 67, 71, 65, 69, 81, 18, 8, 62, 65, + 69, 8, 60, 65, 69, 81, 65, 74, 8, 65, 69, 74, 87, 82, 67, 79, 65, 69, 66, 65, 74, + 20, 2, 51, 66, 72, 69, 63, 68, 81, 8, 82, 74, 64, 8, 53, 63, 68, 82, 72, 64, 69, + 67, 71, 65, 69, 81, 18, 8, 62, 65, 69, 8, 60, 65, 69, 81, 65, 74, 8, 65, 69, 74, + 87, 82, 67, 79, 65, 69, 66, 65, 74, 20, 2, 40, 80, 8, 84, 69, 79, 64, 8, 70, 61, + 8, 64, 65, 74, 8, 43, 65, 79, 79, 65, 74, 18, 8, 64, 69, 65, 8, 72, 103, 74, 67, + 65, 79, 8, 69, 74, 8, 64, 65, 79, 8, 53, 81, 61, 64, 81, 4, 2, 40, 80, 8, 84, + 69, 79, 64, 8, 70, 61, 8, 64, 65, 74, 8, 43, 65, 79, 79, 65, 74, 18, 8, 64, 69, + 65, 8, 72, 103, 74, 67, 65, 79, 8, 69, 74, 8, 64, 65, 79, 8, 53, 81, 61, 64, 81, + 4, 2, 83, 65, 79, 75, 79, 64, 74, 65, 81, 65, 74, 83, 65, 79, 132, 61, 73, 73, 72, + 82, 74, 67, 8, 132, 69, 81, 87, 65, 74, 18, 8, 62, 65, 71, 61, 74, 74, 81, 8, 132, + 65, 69, 74, 18, 8, 64, 61, 102, 8, 84, 69, 79, 2, 83, 65, 79, 75, 79, 64, 74, 65, + 81, 65, 74, 83, 65, 79, 132, 61, 73, 73, 72, 82, 74, 67, 8, 132, 69, 81, 87, 65, 74, + 18, 8, 62, 65, 71, 61, 74, 74, 81, 8, 132, 65, 69, 74, 18, 8, 64, 61, 102, 8, 84, + 69, 79, 2, 82, 74, 80, 8, 132, 63, 68, 75, 74, 8, 65, 69, 74, 73, 61, 72, 18, 8, + 83, 75, 79, 8, 73, 65, 68, 79, 8, 61, 72, 80, 8, 65, 69, 74, 65, 73, 8, 45, 61, + 68, 79, 87, 65, 68, 74, 81, 18, 2, 82, 74, 80, 8, 132, 63, 68, 75, 74, 8, 65, 69, + 74, 73, 61, 72, 18, 8, 83, 75, 79, 8, 73, 65, 68, 79, 8, 61, 72, 80, 8, 65, 69, + 74, 65, 73, 8, 45, 61, 68, 79, 87, 65, 68, 74, 81, 18, 2, 73, 69, 81, 8, 64, 65, + 79, 8, 41, 79, 61, 67, 65, 8, 62, 65, 132, 63, 68, 103, 66, 81, 69, 67, 81, 8, 68, + 61, 62, 65, 74, 18, 8, 82, 74, 64, 8, 69, 63, 68, 8, 73, 82, 102, 8, 132, 61, 67, + 65, 74, 18, 2, 73, 69, 81, 8, 64, 65, 79, 8, 41, 79, 61, 67, 65, 8, 62, 65, 132, + 63, 68, 103, 66, 81, 69, 67, 81, 8, 68, 61, 62, 65, 74, 18, 8, 82, 74, 64, 8, 69, + 63, 68, 8, 73, 82, 102, 8, 132, 61, 67, 65, 74, 18, 2, 64, 61, 102, 8, 73, 69, 63, + 68, 8, 64, 69, 65, 8, 36, 79, 81, 8, 82, 74, 64, 8, 57, 65, 69, 132, 65, 18, 8, + 84, 69, 65, 8, 64, 69, 65, 80, 73, 61, 72, 8, 64, 69, 65, 8, 53, 61, 63, 68, 65, + 2, 64, 61, 102, 8, 73, 69, 63, 68, 8, 64, 69, 65, 8, 36, 79, 81, 8, 82, 74, 64, + 8, 57, 65, 69, 132, 65, 18, 8, 84, 69, 65, 8, 64, 69, 65, 80, 73, 61, 72, 8, 64, + 69, 65, 8, 53, 61, 63, 68, 65, 2, 62, 65, 68, 61, 74, 64, 65, 72, 81, 8, 84, 69, + 79, 64, 18, 8, 72, 65, 62, 68, 61, 66, 81, 8, 61, 74, 8, 64, 69, 65, 8, 64, 61, + 73, 61, 72, 69, 67, 65, 8, 37, 65, 68, 61, 74, 64, 72, 82, 74, 67, 2, 62, 65, 68, + 61, 74, 64, 65, 72, 81, 8, 84, 69, 79, 64, 18, 8, 72, 65, 62, 68, 61, 66, 81, 8, + 61, 74, 8, 64, 69, 65, 8, 64, 61, 73, 61, 72, 69, 67, 65, 8, 37, 65, 68, 61, 74, + 64, 72, 82, 74, 67, 2, 65, 79, 69, 74, 74, 65, 79, 81, 20, 8, 14, 23, 30, 30, 31, + 8, 62, 65, 132, 81, 61, 74, 64, 8, 68, 69, 65, 79, 8, 69, 74, 8, 38, 68, 61, 79, + 72, 75, 81, 81, 65, 74, 62, 82, 79, 67, 2, 65, 79, 69, 74, 74, 65, 79, 81, 20, 8, + 14, 23, 30, 30, 31, 8, 62, 65, 132, 81, 61, 74, 64, 8, 68, 69, 65, 79, 8, 69, 74, + 8, 38, 68, 61, 79, 72, 75, 81, 81, 65, 74, 62, 82, 79, 67, 2, 65, 69, 74, 65, 8, + 67, 61, 74, 87, 8, 71, 75, 72, 75, 132, 132, 61, 72, 65, 8, 57, 75, 68, 74, 82, 74, + 67, 80, 74, 75, 81, 20, 15, 8, 40, 80, 8, 84, 61, 79, 8, 132, 75, 84, 65, 69, 81, + 2, 65, 69, 74, 65, 8, 67, 61, 74, 87, 8, 71, 75, 72, 75, 132, 132, 61, 72, 65, 8, + 57, 75, 68, 74, 82, 74, 67, 80, 74, 75, 81, 20, 15, 8, 40, 80, 8, 84, 61, 79, 8, + 132, 75, 84, 65, 69, 81, 2, 67, 65, 71, 75, 73, 73, 65, 74, 18, 8, 64, 61, 102, 8, + 47, 65, 82, 81, 65, 18, 8, 64, 69, 65, 8, 65, 69, 74, 65, 8, 67, 79, 75, 102, 65, + 8, 36, 74, 87, 61, 68, 72, 8, 83, 75, 74, 2, 67, 65, 71, 75, 73, 73, 65, 74, 18, + 8, 64, 61, 102, 8, 47, 65, 82, 81, 65, 18, 8, 64, 69, 65, 8, 65, 69, 74, 65, 8, + 67, 79, 75, 102, 65, 8, 36, 74, 87, 61, 68, 72, 8, 83, 75, 74, 2, 46, 69, 74, 64, + 65, 79, 74, 8, 68, 61, 81, 81, 65, 74, 18, 8, 110, 62, 65, 79, 68, 61, 82, 78, 81, + 8, 71, 65, 69, 74, 65, 8, 57, 75, 68, 74, 82, 74, 67, 65, 74, 8, 73, 65, 68, 79, + 2, 46, 69, 74, 64, 65, 79, 74, 8, 68, 61, 81, 81, 65, 74, 18, 8, 110, 62, 65, 79, + 68, 61, 82, 78, 81, 8, 71, 65, 69, 74, 65, 8, 57, 75, 68, 74, 82, 74, 67, 65, 74, + 8, 73, 65, 68, 79, 2, 62, 65, 71, 61, 73, 65, 74, 20, 8, 14, 23, 31, 22, 23, 15, + 8, 57, 69, 79, 8, 84, 61, 79, 65, 74, 8, 67, 65, 87, 84, 82, 74, 67, 65, 74, 18, + 8, 37, 61, 79, 61, 63, 71, 65, 74, 8, 61, 82, 66, 87, 82, 4, 2, 62, 65, 71, 61, + 73, 65, 74, 20, 8, 14, 23, 31, 22, 23, 15, 8, 57, 69, 79, 8, 84, 61, 79, 65, 74, + 8, 67, 65, 87, 84, 82, 74, 67, 65, 74, 18, 8, 37, 61, 79, 61, 63, 71, 65, 74, 8, + 61, 82, 66, 87, 82, 4, 2, 132, 81, 65, 72, 72, 65, 74, 18, 8, 69, 74, 8, 64, 65, + 74, 65, 74, 8, 84, 69, 79, 8, 47, 65, 82, 81, 65, 8, 82, 74, 81, 65, 79, 62, 79, + 61, 63, 68, 81, 65, 74, 18, 8, 64, 69, 65, 8, 23, 22, 2, 132, 81, 65, 72, 72, 65, + 74, 18, 8, 69, 74, 8, 64, 65, 74, 65, 74, 8, 84, 69, 79, 8, 47, 65, 82, 81, 65, + 8, 82, 74, 81, 65, 79, 62, 79, 61, 63, 68, 81, 65, 74, 18, 8, 64, 69, 65, 8, 23, + 22, 2, 45, 61, 68, 79, 65, 8, 82, 74, 64, 8, 72, 103, 74, 67, 65, 79, 8, 69, 74, + 8, 65, 69, 74, 65, 73, 8, 43, 61, 82, 132, 65, 8, 67, 65, 84, 75, 68, 74, 81, 8, + 82, 74, 64, 2, 45, 61, 68, 79, 65, 8, 82, 74, 64, 8, 72, 103, 74, 67, 65, 79, 8, + 69, 74, 8, 65, 69, 74, 65, 73, 8, 43, 61, 82, 132, 65, 8, 67, 65, 84, 75, 68, 74, + 81, 8, 82, 74, 64, 2, 78, 110, 74, 71, 81, 72, 69, 63, 68, 8, 69, 68, 79, 65, 8, + 48, 69, 65, 81, 65, 8, 67, 65, 87, 61, 68, 72, 81, 8, 68, 61, 81, 81, 65, 74, 8, + 14, 61, 62, 65, 79, 8, 84, 65, 67, 65, 74, 8, 69, 68, 79, 65, 79, 2, 78, 110, 74, + 71, 81, 72, 69, 63, 68, 8, 69, 68, 79, 65, 8, 48, 69, 65, 81, 65, 8, 67, 65, 87, + 61, 68, 72, 81, 8, 68, 61, 81, 81, 65, 74, 8, 14, 61, 62, 65, 79, 8, 84, 65, 67, + 65, 74, 8, 69, 68, 79, 65, 79, 2, 68, 75, 68, 65, 74, 8, 46, 69, 74, 64, 65, 79, + 87, 61, 68, 72, 8, 67, 65, 71, 110, 74, 64, 69, 67, 81, 8, 82, 74, 64, 8, 61, 82, + 66, 8, 64, 69, 65, 8, 53, 81, 79, 61, 102, 65, 8, 67, 65, 4, 2, 68, 75, 68, 65, + 74, 8, 46, 69, 74, 64, 65, 79, 87, 61, 68, 72, 8, 67, 65, 71, 110, 74, 64, 69, 67, + 81, 8, 82, 74, 64, 8, 61, 82, 66, 8, 64, 69, 65, 8, 53, 81, 79, 61, 102, 65, 8, + 67, 65, 4, 2, 84, 75, 79, 66, 65, 74, 8, 84, 61, 79, 65, 74, 15, 20, 8, 40, 80, + 8, 71, 61, 73, 8, 64, 61, 74, 74, 8, 61, 82, 63, 68, 8, 65, 69, 74, 8, 36, 74, + 81, 79, 61, 67, 18, 8, 64, 65, 79, 2, 84, 75, 79, 66, 65, 74, 8, 84, 61, 79, 65, + 74, 15, 20, 8, 40, 80, 8, 71, 61, 73, 8, 64, 61, 74, 74, 8, 61, 82, 63, 68, 8, + 65, 69, 74, 8, 36, 74, 81, 79, 61, 67, 18, 8, 64, 65, 79, 2, 25, 18, 23, 24, 11, + 8, 69, 74, 8, 64, 65, 79, 8, 53, 81, 61, 64, 81, 83, 65, 79, 75, 79, 64, 74, 65, + 81, 65, 74, 83, 65, 79, 132, 61, 73, 73, 72, 82, 74, 67, 8, 62, 65, 132, 63, 68, 103, + 66, 81, 69, 67, 81, 2, 25, 18, 23, 24, 11, 8, 69, 74, 8, 64, 65, 79, 8, 53, 81, + 61, 64, 81, 83, 65, 79, 75, 79, 64, 74, 65, 81, 65, 74, 83, 65, 79, 132, 61, 73, 73, + 72, 82, 74, 67, 8, 62, 65, 132, 63, 68, 103, 66, 81, 69, 67, 81, 2, 65, 69, 74, 65, + 8, 39, 65, 78, 82, 81, 61, 81, 69, 75, 74, 8, 64, 61, 73, 69, 81, 8, 62, 65, 66, + 61, 102, 81, 20, 8, 91, 8, 23, 24, 20, 8, 39, 69, 65, 8, 53, 61, 63, 68, 65, 8, + 68, 61, 81, 2, 65, 69, 74, 65, 8, 39, 65, 78, 82, 81, 61, 81, 69, 75, 74, 8, 64, + 61, 73, 69, 81, 8, 62, 65, 66, 61, 102, 81, 20, 8, 91, 8, 23, 24, 20, 8, 39, 69, + 65, 8, 53, 61, 63, 68, 65, 8, 68, 61, 81, 2, 132, 69, 63, 68, 8, 68, 69, 74, 67, + 65, 87, 75, 67, 65, 74, 8, 82, 74, 64, 8, 69, 132, 81, 8, 132, 63, 68, 72, 69, 65, + 102, 72, 69, 63, 68, 8, 83, 109, 72, 72, 69, 67, 8, 61, 82, 80, 8, 64, 65, 73, 2, + 132, 69, 63, 68, 8, 68, 69, 74, 67, 65, 87, 75, 67, 65, 74, 8, 82, 74, 64, 8, 69, + 132, 81, 8, 132, 63, 68, 72, 69, 65, 102, 72, 69, 63, 68, 8, 83, 109, 72, 72, 69, 67, + 8, 61, 82, 80, 8, 64, 65, 73, 2, 40, 80, 8, 84, 82, 79, 64, 65, 8, 65, 69, 74, + 8, 36, 82, 80, 132, 63, 68, 82, 102, 8, 65, 69, 74, 67, 65, 132, 65, 81, 87, 81, 8, + 82, 74, 64, 8, 91, 8, 24, 23, 28, 8, 36, 62, 80, 61, 81, 87, 8, 25, 2, 40, 80, + 8, 84, 82, 79, 64, 65, 8, 65, 69, 74, 8, 36, 82, 80, 132, 63, 68, 82, 102, 8, 65, + 69, 74, 67, 65, 132, 65, 81, 87, 81, 8, 82, 74, 64, 8, 91, 8, 24, 23, 28, 8, 36, + 62, 80, 61, 81, 87, 8, 25, 2, 132, 69, 63, 68, 8, 46, 72, 65, 69, 74, 84, 75, 68, + 74, 82, 74, 67, 65, 74, 8, 132, 75, 8, 67, 65, 84, 61, 72, 81, 69, 67, 8, 69, 132, + 81, 8, 84, 69, 65, 8, 132, 65, 72, 81, 65, 74, 8, 83, 75, 79, 68, 65, 79, 20, 2, + 132, 69, 63, 68, 8, 46, 72, 65, 69, 74, 84, 75, 68, 74, 82, 74, 67, 65, 74, 8, 132, + 75, 8, 67, 65, 84, 61, 72, 81, 69, 67, 8, 69, 132, 81, 8, 84, 69, 65, 8, 132, 65, + 72, 81, 65, 74, 8, 83, 75, 79, 68, 65, 79, 20, 2, 49, 82, 74, 8, 69, 132, 81, 8, + 64, 69, 65, 8, 41, 79, 61, 67, 65, 8, 64, 69, 65, 18, 8, 7, 75, 62, 8, 84, 69, + 79, 8, 74, 61, 63, 68, 8, 64, 65, 73, 8, 46, 79, 69, 65, 67, 65, 2, 49, 82, 74, + 8, 69, 132, 81, 8, 64, 69, 65, 8, 41, 79, 61, 67, 65, 8, 64, 69, 65, 18, 8, 7, + 75, 62, 8, 84, 69, 79, 8, 74, 61, 63, 68, 8, 64, 65, 73, 8, 46, 79, 69, 65, 67, + 65, 2, 83, 75, 79, 61, 82, 80, 132, 69, 63, 68, 81, 72, 69, 63, 68, 8, 73, 69, 81, + 8, 65, 69, 74, 65, 79, 8, 57, 75, 68, 74, 82, 74, 67, 80, 71, 75, 81, 8, 87, 82, + 8, 79, 65, 63, 68, 74, 65, 74, 2, 83, 75, 79, 61, 82, 80, 132, 69, 63, 68, 81, 72, + 69, 63, 68, 8, 73, 69, 81, 8, 65, 69, 74, 65, 79, 8, 57, 75, 68, 74, 82, 74, 67, + 80, 71, 75, 81, 8, 87, 82, 8, 79, 65, 63, 68, 74, 65, 74, 2, 68, 61, 62, 65, 74, + 20, 6, 8, 91, 8, 25, 25, 20, 8, 42, 65, 84, 69, 102, 8, 67, 65, 68, 65, 74, 8, + 64, 69, 65, 8, 36, 74, 132, 69, 63, 68, 81, 65, 74, 8, 64, 61, 79, 110, 62, 65, 79, + 8, 61, 82, 80, 65, 69, 74, 4, 2, 68, 61, 62, 65, 74, 20, 6, 8, 91, 8, 25, 25, + 20, 8, 42, 65, 84, 69, 102, 8, 67, 65, 68, 65, 74, 8, 64, 69, 65, 8, 36, 74, 132, + 69, 63, 68, 81, 65, 74, 8, 64, 61, 79, 110, 62, 65, 79, 8, 61, 82, 80, 65, 69, 74, + 4, 2, 61, 74, 64, 65, 79, 33, 8, 84, 69, 79, 8, 68, 61, 62, 65, 74, 8, 70, 61, + 8, 67, 65, 68, 109, 79, 81, 18, 8, 64, 61, 102, 8, 43, 65, 79, 79, 8, 53, 81, 61, + 64, 81, 83, 20, 8, 39, 79, 20, 2, 61, 74, 64, 65, 79, 33, 8, 84, 69, 79, 8, 68, + 61, 62, 65, 74, 8, 70, 61, 8, 67, 65, 68, 109, 79, 81, 18, 8, 64, 61, 102, 8, 43, + 65, 79, 79, 8, 53, 81, 61, 64, 81, 83, 20, 8, 39, 79, 20, 2, 37, 86, 71, 8, 132, + 69, 63, 68, 8, 132, 75, 67, 61, 79, 8, 61, 82, 66, 8, 64, 65, 74, 8, 53, 81, 61, + 74, 64, 78, 82, 74, 71, 81, 8, 132, 81, 65, 72, 72, 81, 18, 8, 64, 61, 102, 8, 84, + 69, 79, 2, 37, 86, 71, 8, 132, 69, 63, 68, 8, 132, 75, 67, 61, 79, 8, 61, 82, 66, + 8, 64, 65, 74, 8, 53, 81, 61, 74, 64, 78, 82, 74, 71, 81, 8, 132, 81, 65, 72, 72, + 81, 18, 8, 64, 61, 102, 8, 84, 69, 79, 2, 65, 69, 74, 65, 74, 8, 55, 65, 62, 65, + 79, 66, 72, 82, 102, 8, 61, 74, 8, 57, 75, 68, 74, 82, 74, 67, 65, 74, 8, 68, 61, + 62, 65, 74, 20, 8, 7, 36, 62, 65, 79, 18, 2, 65, 69, 74, 65, 74, 8, 55, 65, 62, + 65, 79, 66, 72, 82, 102, 8, 61, 74, 8, 57, 75, 68, 74, 82, 74, 67, 65, 74, 8, 68, + 61, 62, 65, 74, 20, 8, 7, 36, 62, 65, 79, 18, 2, 43, 65, 79, 79, 8, 46, 75, 72, + 72, 65, 74, 67, 65, 8, 39, 79, 20, 8, 37, 86, 71, 18, 8, 73, 61, 74, 8, 64, 61, + 79, 66, 8, 64, 69, 65, 132, 65, 8, 41, 79, 61, 67, 65, 8, 74, 69, 63, 68, 81, 2, + 43, 65, 79, 79, 8, 46, 75, 72, 72, 65, 74, 67, 65, 8, 39, 79, 20, 8, 37, 86, 71, + 18, 8, 73, 61, 74, 8, 64, 61, 79, 66, 8, 64, 69, 65, 132, 65, 8, 41, 79, 61, 67, + 65, 8, 74, 69, 63, 68, 81, 2, 83, 75, 74, 8, 64, 65, 73, 8, 65, 69, 74, 132, 65, + 69, 81, 69, 67, 65, 74, 8, 44, 74, 81, 65, 79, 65, 132, 132, 65, 74, 132, 81, 61, 74, + 64, 78, 82, 74, 71, 81, 8, 64, 65, 80, 8, 43, 61, 82, 80, 4, 2, 83, 75, 74, 8, + 64, 65, 73, 8, 65, 69, 74, 132, 65, 69, 81, 69, 67, 65, 74, 8, 44, 74, 81, 65, 79, + 65, 132, 132, 65, 74, 132, 81, 61, 74, 64, 78, 82, 74, 71, 81, 8, 64, 65, 80, 8, 43, + 61, 82, 80, 4, 2, 62, 65, 132, 69, 81, 87, 65, 79, 80, 8, 61, 82, 80, 8, 62, 65, + 81, 79, 61, 63, 68, 81, 65, 74, 20, 6, 2, 62, 65, 132, 69, 81, 87, 65, 79, 80, 8, + 61, 82, 80, 8, 62, 65, 81, 79, 61, 63, 68, 81, 65, 74, 20, 6, 2, 14, 60, 82, 79, + 82, 66, 8, 64, 65, 80, 8, 53, 81, 61, 64, 81, 83, 20, 8, 39, 79, 8, 37, 86, 81, + 20, 15, 2, 14, 60, 82, 79, 82, 66, 8, 64, 65, 80, 8, 53, 81, 61, 64, 81, 83, 20, + 8, 39, 79, 8, 37, 86, 81, 20, 15, 2, 7, 39, 61, 80, 18, 8, 68, 61, 62, 65, 74, + 8, 53, 69, 65, 8, 74, 69, 63, 68, 81, 8, 67, 65, 81, 61, 74, 35, 6, 8, 14, 36, + 62, 65, 79, 8, 69, 63, 68, 8, 68, 61, 81, 81, 65, 8, 64, 65, 74, 2, 7, 39, 61, + 80, 18, 8, 68, 61, 62, 65, 74, 8, 53, 69, 65, 8, 74, 69, 63, 68, 81, 8, 67, 65, + 81, 61, 74, 35, 6, 8, 14, 36, 62, 65, 79, 8, 69, 63, 68, 8, 68, 61, 81, 81, 65, + 8, 64, 65, 74, 2, 40, 69, 74, 64, 79, 82, 63, 71, 20, 15, 8, 48, 61, 74, 8, 64, + 61, 79, 66, 8, 64, 69, 65, 132, 65, 8, 41, 79, 61, 67, 65, 8, 74, 69, 63, 68, 81, + 8, 83, 75, 73, 8, 65, 69, 74, 132, 65, 69, 81, 69, 4, 2, 40, 69, 74, 64, 79, 82, + 63, 71, 20, 15, 8, 48, 61, 74, 8, 64, 61, 79, 66, 8, 64, 69, 65, 132, 65, 8, 41, + 79, 61, 67, 65, 8, 74, 69, 63, 68, 81, 8, 83, 75, 73, 8, 65, 69, 74, 132, 65, 69, + 81, 69, 4, 2, 67, 65, 74, 8, 44, 74, 81, 65, 79, 65, 132, 132, 65, 74, 132, 81, 61, + 74, 64, 78, 82, 74, 71, 81, 8, 64, 65, 80, 8, 43, 61, 82, 80, 62, 65, 132, 69, 81, + 87, 65, 79, 80, 8, 61, 82, 80, 2, 67, 65, 74, 8, 44, 74, 81, 65, 79, 65, 132, 132, + 65, 74, 132, 81, 61, 74, 64, 78, 82, 74, 71, 81, 8, 64, 65, 80, 8, 43, 61, 82, 80, + 62, 65, 132, 69, 81, 87, 65, 79, 80, 8, 61, 82, 80, 2, 62, 65, 81, 79, 61, 63, 68, + 81, 65, 74, 18, 8, 132, 75, 74, 64, 65, 79, 74, 8, 73, 61, 74, 8, 73, 82, 102, 8, + 132, 69, 65, 8, 67, 61, 74, 87, 8, 75, 62, 70, 65, 71, 81, 69, 83, 8, 78, 79, 110, + 4, 2, 62, 65, 81, 79, 61, 63, 68, 81, 65, 74, 18, 8, 132, 75, 74, 64, 65, 79, 74, + 8, 73, 61, 74, 8, 73, 82, 102, 8, 132, 69, 65, 8, 67, 61, 74, 87, 8, 75, 62, 70, + 65, 71, 81, 69, 83, 8, 78, 79, 110, 4, 2, 66, 65, 74, 18, 8, 82, 74, 64, 8, 64, + 61, 8, 84, 65, 79, 64, 65, 74, 8, 53, 69, 65, 8, 73, 69, 79, 8, 87, 82, 67, 65, + 62, 65, 74, 18, 8, 64, 61, 102, 8, 66, 61, 132, 81, 8, 61, 72, 72, 65, 2, 66, 65, + 74, 18, 8, 82, 74, 64, 8, 64, 61, 8, 84, 65, 79, 64, 65, 74, 8, 53, 69, 65, 8, + 73, 69, 79, 8, 87, 82, 67, 65, 62, 65, 74, 18, 8, 64, 61, 102, 8, 66, 61, 132, 81, + 8, 61, 72, 72, 65, 2, 46, 65, 74, 74, 65, 79, 8, 64, 65, 79, 8, 56, 65, 79, 68, + 103, 72, 81, 74, 69, 132, 132, 65, 8, 64, 65, 79, 8, 36, 74, 132, 69, 63, 68, 81, 8, + 132, 69, 74, 64, 18, 8, 64, 61, 102, 8, 65, 69, 74, 65, 2, 46, 65, 74, 74, 65, 79, + 8, 64, 65, 79, 8, 56, 65, 79, 68, 103, 72, 81, 74, 69, 132, 132, 65, 8, 64, 65, 79, + 8, 36, 74, 132, 69, 63, 68, 81, 8, 132, 69, 74, 64, 18, 8, 64, 61, 102, 8, 65, 69, + 74, 65, 2, 7, 57, 75, 68, 74, 82, 74, 67, 80, 74, 75, 81, 6, 8, 62, 65, 83, 75, + 79, 132, 81, 65, 68, 81, 20, 8, 43, 65, 79, 79, 8, 46, 75, 72, 72, 65, 67, 65, 8, + 57, 109, 72, 72, 73, 65, 79, 2, 7, 57, 75, 68, 74, 82, 74, 67, 80, 74, 75, 81, 6, + 8, 62, 65, 83, 75, 79, 132, 81, 65, 68, 81, 20, 8, 43, 65, 79, 79, 8, 46, 75, 72, + 72, 65, 67, 65, 8, 57, 109, 72, 72, 73, 65, 79, 2, 73, 65, 69, 74, 81, 8, 61, 72, + 72, 65, 79, 64, 69, 74, 67, 80, 8, 14, 3, 8, 64, 61, 80, 8, 68, 61, 62, 65, 8, + 69, 63, 68, 18, 8, 75, 66, 66, 65, 74, 8, 67, 65, 132, 61, 67, 81, 18, 8, 74, 69, + 63, 68, 81, 2, 73, 65, 69, 74, 81, 8, 61, 72, 72, 65, 79, 64, 69, 74, 67, 80, 8, + 14, 3, 8, 64, 61, 80, 8, 68, 61, 62, 65, 8, 69, 63, 68, 18, 8, 75, 66, 66, 65, + 74, 8, 67, 65, 132, 61, 67, 81, 18, 8, 74, 69, 63, 68, 81, 2, 79, 65, 63, 68, 81, + 8, 83, 65, 79, 132, 81, 61, 74, 64, 65, 74, 8, 3, 15, 18, 8, 83, 75, 74, 8, 65, + 69, 74, 65, 79, 8, 57, 75, 68, 74, 82, 74, 61, 80, 74, 75, 81, 8, 132, 65, 69, 8, + 70, 65, 81, 87, 81, 2, 79, 65, 63, 68, 81, 8, 83, 65, 79, 132, 81, 61, 74, 64, 65, + 74, 8, 3, 15, 18, 8, 83, 75, 74, 8, 65, 69, 74, 65, 79, 8, 57, 75, 68, 74, 82, + 74, 61, 80, 74, 75, 81, 8, 132, 65, 69, 8, 70, 65, 81, 87, 81, 2, 74, 69, 63, 68, + 81, 8, 64, 69, 65, 8, 52, 65, 64, 65, 18, 8, 132, 75, 74, 64, 65, 79, 74, 8, 74, + 82, 79, 8, 83, 75, 74, 8, 65, 69, 74, 65, 73, 8, 67, 65, 84, 69, 132, 132, 65, 74, + 2, 74, 69, 63, 68, 81, 8, 64, 69, 65, 8, 52, 65, 64, 65, 18, 8, 132, 75, 74, 64, + 65, 79, 74, 8, 74, 82, 79, 8, 83, 75, 74, 8, 65, 69, 74, 65, 73, 8, 67, 65, 84, + 69, 132, 132, 65, 74, 2, 48, 61, 74, 67, 65, 72, 8, 61, 74, 8, 57, 75, 68, 74, 82, + 74, 67, 65, 74, 33, 8, 64, 65, 79, 8, 132, 65, 69, 8, 61, 62, 65, 79, 8, 69, 73, + 73, 65, 79, 8, 83, 75, 79, 4, 2, 48, 61, 74, 67, 65, 72, 8, 61, 74, 8, 57, 75, + 68, 74, 82, 74, 67, 65, 74, 33, 8, 64, 65, 79, 8, 132, 65, 69, 8, 61, 62, 65, 79, + 8, 69, 73, 73, 65, 79, 8, 83, 75, 79, 4, 2, 68, 61, 74, 64, 65, 74, 20, 8, 7, + 45, 61, 18, 8, 43, 65, 79, 79, 8, 46, 75, 72, 72, 65, 67, 65, 8, 57, 109, 72, 72, + 73, 65, 79, 18, 8, 84, 69, 79, 8, 84, 69, 132, 132, 65, 74, 8, 70, 61, 18, 2, 68, + 61, 74, 64, 65, 74, 20, 8, 7, 45, 61, 18, 8, 43, 65, 79, 79, 8, 46, 75, 72, 72, + 65, 67, 65, 8, 57, 109, 72, 72, 73, 65, 79, 18, 8, 84, 69, 79, 8, 84, 69, 132, 132, + 65, 74, 8, 70, 61, 18, 2, 64, 61, 102, 8, 69, 73, 73, 65, 79, 8, 65, 69, 74, 8, + 67, 65, 84, 69, 132, 132, 65, 79, 8, 48, 61, 74, 67, 65, 72, 8, 61, 74, 8, 46, 72, + 65, 69, 74, 84, 75, 68, 74, 82, 74, 67, 65, 74, 2, 64, 61, 102, 8, 69, 73, 73, 65, + 79, 8, 65, 69, 74, 8, 67, 65, 84, 69, 132, 132, 65, 79, 8, 48, 61, 74, 67, 65, 72, + 8, 61, 74, 8, 46, 72, 65, 69, 74, 84, 75, 68, 74, 82, 74, 67, 65, 74, 2, 83, 75, + 79, 68, 61, 74, 64, 65, 74, 8, 69, 132, 81, 6, 20, 8, 36, 62, 65, 79, 8, 61, 82, + 80, 8, 64, 65, 79, 8, 53, 81, 61, 81, 69, 132, 81, 69, 71, 8, 83, 75, 74, 8, 23, + 30, 30, 29, 8, 65, 79, 132, 65, 68, 65, 74, 8, 53, 69, 65, 2, 83, 75, 79, 68, 61, + 74, 64, 65, 74, 8, 69, 132, 81, 6, 20, 8, 36, 62, 65, 79, 8, 61, 82, 80, 8, 64, + 65, 79, 8, 53, 81, 61, 81, 69, 132, 81, 69, 71, 8, 83, 75, 74, 8, 23, 30, 30, 29, + 8, 65, 79, 132, 65, 68, 65, 74, 8, 53, 69, 65, 2, 64, 61, 102, 8, 23, 30, 30, 31, + 18, 8, 23, 30, 31, 22, 18, 8, 23, 30, 31, 23, 8, 67, 65, 79, 61, 64, 65, 8, 66, + 110, 79, 8, 38, 68, 61, 79, 72, 75, 81, 81, 65, 74, 62, 82, 79, 67, 8, 64, 65, 79, + 8, 48, 61, 74, 67, 65, 72, 8, 61, 74, 2, 64, 61, 102, 8, 23, 30, 30, 31, 18, 8, + 23, 30, 31, 22, 18, 8, 23, 30, 31, 23, 8, 67, 65, 79, 61, 64, 65, 8, 66, 110, 79, + 8, 38, 68, 61, 79, 72, 75, 81, 81, 65, 74, 62, 82, 79, 67, 8, 64, 65, 79, 8, 48, + 61, 74, 67, 65, 72, 8, 61, 74, 2, 57, 75, 68, 74, 82, 74, 67, 65, 74, 8, 65, 69, + 74, 67, 65, 81, 79, 65, 81, 65, 74, 8, 66, 69, 74, 64, 18, 8, 84, 65, 74, 74, 8, + 25, 29, 18, 27, 8, 57, 75, 68, 74, 82, 74, 67, 65, 74, 2, 57, 75, 68, 74, 82, 74, + 67, 65, 74, 8, 65, 69, 74, 67, 65, 81, 79, 65, 81, 65, 74, 8, 66, 69, 74, 64, 18, + 8, 84, 65, 74, 74, 8, 25, 29, 18, 27, 8, 57, 75, 68, 74, 82, 74, 67, 65, 74, 2, + 83, 75, 74, 8, 132, 65, 72, 62, 132, 81, 8, 62, 65, 68, 75, 62, 65, 74, 8, 68, 61, + 81, 81, 65, 20, 8, 27, 25, 11, 8, 61, 72, 72, 65, 79, 8, 57, 75, 68, 74, 82, 74, + 67, 65, 74, 8, 14, 23, 31, 22, 23, 8, 82, 74, 64, 2, 83, 75, 74, 8, 132, 65, 72, + 62, 132, 81, 8, 62, 65, 68, 75, 62, 65, 74, 8, 68, 61, 81, 81, 65, 20, 8, 27, 25, + 11, 8, 61, 72, 72, 65, 79, 8, 57, 75, 68, 74, 82, 74, 67, 65, 74, 8, 14, 23, 31, + 22, 23, 8, 82, 74, 64, 2, 23, 31, 22, 24, 15, 18, 8, 132, 78, 103, 81, 65, 79, 8, + 23, 24, 18, 22, 24, 11, 18, 8, 67, 61, 79, 8, 24, 26, 18, 27, 24, 11, 8, 75, 64, + 65, 79, 8, 23, 30, 11, 8, 64, 65, 79, 8, 57, 75, 68, 74, 82, 74, 67, 65, 74, 2, + 23, 31, 22, 24, 15, 18, 8, 132, 78, 103, 81, 65, 79, 8, 23, 24, 18, 22, 24, 11, 18, + 8, 67, 61, 79, 8, 24, 26, 18, 27, 24, 11, 8, 75, 64, 65, 79, 8, 23, 30, 11, 8, + 64, 65, 79, 8, 57, 75, 68, 74, 82, 74, 67, 65, 74, 2, 68, 61, 62, 65, 74, 8, 69, + 74, 8, 64, 65, 74, 8, 45, 61, 68, 79, 65, 74, 8, 23, 30, 29, 30, 18, 8, 23, 30, + 29, 31, 18, 8, 23, 30, 30, 22, 18, 8, 23, 30, 30, 23, 18, 8, 23, 30, 30, 24, 18, + 8, 23, 30, 30, 25, 2, 68, 61, 62, 65, 74, 8, 69, 74, 8, 64, 65, 74, 8, 45, 61, + 68, 79, 65, 74, 8, 23, 30, 29, 30, 18, 8, 23, 30, 29, 31, 18, 8, 23, 30, 30, 22, + 18, 8, 23, 30, 30, 23, 18, 8, 23, 30, 30, 24, 18, 8, 23, 30, 30, 25, 2, 23, 30, + 30, 26, 18, 8, 23, 30, 30, 27, 8, 82, 74, 64, 8, 132, 75, 67, 61, 79, 8, 23, 30, + 31, 22, 18, 8, 23, 30, 31, 23, 18, 8, 23, 30, 31, 24, 18, 8, 23, 30, 31, 25, 18, + 8, 23, 30, 31, 26, 18, 8, 132, 65, 72, 62, 80, 81, 2, 23, 30, 30, 26, 18, 8, 23, + 30, 30, 27, 8, 82, 74, 64, 8, 132, 75, 67, 61, 79, 8, 23, 30, 31, 22, 18, 8, 23, + 30, 31, 23, 18, 8, 23, 30, 31, 24, 18, 8, 23, 30, 31, 25, 18, 8, 23, 30, 31, 26, + 18, 8, 132, 65, 72, 62, 80, 81, 2, 23, 30, 31, 27, 8, 62, 69, 80, 8, 23, 31, 22, + 23, 18, 8, 23, 31, 22, 24, 18, 8, 23, 31, 22, 25, 18, 8, 23, 31, 22, 26, 18, 8, + 23, 31, 22, 27, 18, 8, 23, 31, 22, 28, 18, 8, 23, 31, 22, 29, 18, 8, 23, 31, 22, + 30, 2, 23, 30, 31, 27, 8, 62, 69, 80, 8, 23, 31, 22, 23, 18, 8, 23, 31, 22, 24, + 18, 8, 23, 31, 22, 25, 18, 8, 23, 31, 22, 26, 18, 8, 23, 31, 22, 27, 18, 8, 23, + 31, 22, 28, 18, 8, 23, 31, 22, 29, 18, 8, 23, 31, 22, 30, 2, 14, 53, 65, 68, 79, + 8, 67, 82, 81, 20, 15, 8, 91, 8, 26, 20, 2, 14, 53, 65, 68, 79, 8, 67, 82, 81, + 20, 15, 8, 91, 8, 26, 20, 2, 48, 65, 69, 74, 65, 8, 43, 65, 79, 79, 65, 74, 18, + 8, 69, 63, 68, 8, 132, 78, 79, 65, 63, 68, 65, 8, 64, 61, 80, 8, 61, 82, 80, 8, + 73, 69, 81, 8, 61, 72, 72, 65, 79, 2, 48, 65, 69, 74, 65, 8, 43, 65, 79, 79, 65, + 74, 18, 8, 69, 63, 68, 8, 132, 78, 79, 65, 63, 68, 65, 8, 64, 61, 80, 8, 61, 82, + 80, 8, 73, 69, 81, 8, 61, 72, 72, 65, 79, 2, 52, 65, 132, 65, 79, 83, 65, 20, 8, + 40, 80, 8, 69, 132, 81, 8, 71, 65, 69, 74, 65, 79, 8, 82, 74, 81, 65, 79, 8, 82, + 74, 80, 18, 8, 64, 65, 79, 8, 73, 69, 81, 8, 7, 78, 75, 132, 69, 81, 69, 83, 65, + 79, 2, 52, 65, 132, 65, 79, 83, 65, 20, 8, 40, 80, 8, 69, 132, 81, 8, 71, 65, 69, + 74, 65, 79, 8, 82, 74, 81, 65, 79, 8, 82, 74, 80, 18, 8, 64, 65, 79, 8, 73, 69, + 81, 8, 7, 78, 75, 132, 69, 81, 69, 83, 65, 79, 2, 53, 69, 63, 68, 65, 79, 68, 65, + 69, 81, 6, 8, 132, 61, 67, 65, 74, 8, 84, 69, 79, 64, 32, 8, 74, 82, 79, 8, 64, + 69, 65, 132, 65, 80, 8, 75, 64, 65, 79, 8, 70, 65, 74, 65, 80, 8, 69, 132, 81, 8, + 64, 61, 80, 2, 53, 69, 63, 68, 65, 79, 68, 65, 69, 81, 6, 8, 132, 61, 67, 65, 74, + 8, 84, 69, 79, 64, 32, 8, 74, 82, 79, 8, 64, 69, 65, 132, 65, 80, 8, 75, 64, 65, + 79, 8, 70, 65, 74, 65, 80, 8, 69, 132, 81, 8, 64, 61, 80, 2, 52, 69, 63, 68, 81, + 69, 67, 65, 20, 8, 36, 62, 65, 79, 8, 65, 69, 74, 65, 8, 79, 65, 69, 66, 72, 69, + 63, 68, 65, 8, 51, 79, 110, 66, 82, 74, 67, 8, 65, 79, 132, 63, 68, 65, 69, 74, 81, + 8, 73, 69, 79, 2, 52, 69, 63, 68, 81, 69, 67, 65, 20, 8, 36, 62, 65, 79, 8, 65, + 69, 74, 65, 8, 79, 65, 69, 66, 72, 69, 63, 68, 65, 8, 51, 79, 110, 66, 82, 74, 67, + 8, 65, 79, 132, 63, 68, 65, 69, 74, 81, 8, 73, 69, 79, 2, 69, 74, 8, 70, 65, 64, + 65, 79, 8, 57, 65, 69, 132, 65, 8, 61, 82, 80, 132, 69, 63, 68, 81, 80, 83, 75, 72, + 72, 33, 8, 65, 80, 8, 69, 132, 81, 8, 132, 65, 68, 79, 8, 72, 65, 69, 63, 68, 81, + 8, 73, 109, 67, 72, 69, 63, 68, 18, 2, 69, 74, 8, 70, 65, 64, 65, 79, 8, 57, 65, + 69, 132, 65, 8, 61, 82, 80, 132, 69, 63, 68, 81, 80, 83, 75, 72, 72, 33, 8, 65, 80, + 8, 69, 132, 81, 8, 132, 65, 68, 79, 8, 72, 65, 69, 63, 68, 81, 8, 73, 109, 67, 72, + 69, 63, 68, 18, 2, 64, 61, 102, 8, 64, 65, 79, 8, 40, 81, 61, 81, 8, 69, 74, 8, + 61, 74, 64, 65, 79, 65, 79, 8, 42, 65, 132, 81, 61, 72, 81, 8, 39, 65, 74, 8, 36, + 82, 80, 132, 63, 68, 82, 102, 8, 83, 65, 79, 4, 2, 64, 61, 102, 8, 64, 65, 79, 8, + 40, 81, 61, 81, 8, 69, 74, 8, 61, 74, 64, 65, 79, 65, 79, 8, 42, 65, 132, 81, 61, + 72, 81, 8, 39, 65, 74, 8, 36, 82, 80, 132, 63, 68, 82, 102, 8, 83, 65, 79, 4, 2, + 72, 103, 102, 81, 18, 8, 61, 72, 80, 8, 65, 79, 8, 69, 74, 8, 69, 68, 74, 8, 68, + 69, 74, 65, 69, 74, 67, 65, 67, 61, 74, 67, 65, 74, 8, 69, 132, 81, 20, 8, 44, 63, + 68, 8, 132, 78, 79, 65, 63, 68, 65, 2, 72, 103, 102, 81, 18, 8, 61, 72, 80, 8, 65, + 79, 8, 69, 74, 8, 69, 68, 74, 8, 68, 69, 74, 65, 69, 74, 67, 65, 67, 61, 74, 67, + 65, 74, 8, 69, 132, 81, 20, 8, 44, 63, 68, 8, 132, 78, 79, 65, 63, 68, 65, 2, 64, + 61, 80, 8, 61, 82, 80, 8, 73, 69, 81, 8, 83, 75, 72, 72, 65, 73, 8, 37, 65, 84, + 82, 102, 81, 132, 65, 69, 74, 8, 64, 65, 79, 8, 39, 69, 74, 67, 65, 18, 8, 64, 69, + 65, 8, 69, 74, 2, 64, 61, 80, 8, 61, 82, 80, 8, 73, 69, 81, 8, 83, 75, 72, 72, + 65, 73, 8, 37, 65, 84, 82, 102, 81, 132, 65, 69, 74, 8, 64, 65, 79, 8, 39, 69, 74, + 67, 65, 18, 8, 64, 69, 65, 8, 69, 74, 2, 82, 74, 132, 65, 79, 73, 8, 43, 61, 82, + 80, 68, 61, 72, 81, 8, 73, 61, 74, 67, 65, 72, 68, 61, 66, 81, 8, 82, 74, 64, 8, + 64, 82, 74, 71, 65, 72, 8, 132, 69, 74, 64, 20, 8, 14, 40, 80, 2, 82, 74, 132, 65, + 79, 73, 8, 43, 61, 82, 80, 68, 61, 72, 81, 8, 73, 61, 74, 67, 65, 72, 68, 61, 66, + 81, 8, 82, 74, 64, 8, 64, 82, 74, 71, 65, 72, 8, 132, 69, 74, 64, 20, 8, 14, 40, + 80, 2, 67, 65, 74, 110, 67, 81, 8, 74, 103, 73, 72, 69, 63, 68, 18, 8, 84, 65, 74, + 74, 8, 84, 69, 79, 8, 64, 69, 65, 8, 23, 22, 22, 8, 11, 8, 64, 65, 80, 8, 40, + 69, 74, 4, 2, 67, 65, 74, 110, 67, 81, 8, 74, 103, 73, 72, 69, 63, 68, 18, 8, 84, + 65, 74, 74, 8, 84, 69, 79, 8, 64, 69, 65, 8, 23, 22, 22, 8, 11, 8, 64, 65, 80, + 8, 40, 69, 74, 4, 2, 71, 75, 73, 73, 65, 74, 132, 81, 65, 82, 65, 79, 132, 75, 72, + 72, 80, 8, 82, 73, 8, 23, 97, 8, 48, 69, 72, 72, 69, 75, 74, 8, 65, 79, 68, 109, + 68, 65, 74, 18, 8, 82, 73, 8, 64, 65, 74, 2, 71, 75, 73, 73, 65, 74, 132, 81, 65, + 82, 65, 79, 132, 75, 72, 72, 80, 8, 82, 73, 8, 23, 97, 8, 48, 69, 72, 72, 69, 75, + 74, 8, 65, 79, 68, 109, 68, 65, 74, 18, 8, 82, 73, 8, 64, 65, 74, 2, 65, 81, 84, + 61, 69, 67, 65, 74, 8, 36, 82, 80, 66, 61, 72, 72, 8, 64, 65, 79, 8, 23, 22, 29, + 29, 8, 1, 112, 8, 36, 82, 66, 132, 63, 68, 72, 61, 67, 8, 87, 82, 8, 64, 65, 63, + 71, 65, 74, 20, 15, 2, 65, 81, 84, 61, 69, 67, 65, 74, 8, 36, 82, 80, 66, 61, 72, + 72, 8, 64, 65, 79, 8, 23, 22, 29, 29, 8, 1, 112, 8, 36, 82, 66, 132, 63, 68, 72, + 61, 67, 8, 87, 82, 8, 64, 65, 63, 71, 65, 74, 20, 15, 2, 57, 69, 79, 8, 64, 110, + 79, 66, 65, 74, 8, 82, 74, 80, 8, 70, 61, 8, 73, 69, 81, 8, 64, 65, 73, 8, 42, + 65, 66, 110, 68, 72, 18, 8, 64, 61, 102, 8, 84, 69, 79, 8, 64, 65, 74, 2, 57, 69, + 79, 8, 64, 110, 79, 66, 65, 74, 8, 82, 74, 80, 8, 70, 61, 8, 73, 69, 81, 8, 64, + 65, 73, 8, 42, 65, 66, 110, 68, 72, 18, 8, 64, 61, 102, 8, 84, 69, 79, 8, 64, 65, + 74, 2, 53, 81, 65, 82, 65, 79, 132, 61, 81, 87, 8, 74, 69, 63, 68, 81, 8, 65, 79, + 68, 109, 68, 65, 74, 18, 8, 74, 69, 63, 68, 81, 8, 62, 65, 79, 82, 68, 69, 67, 65, + 74, 20, 8, 39, 61, 80, 2, 53, 81, 65, 82, 65, 79, 132, 61, 81, 87, 8, 74, 69, 63, + 68, 81, 8, 65, 79, 68, 109, 68, 65, 74, 18, 8, 74, 69, 63, 68, 81, 8, 62, 65, 79, + 82, 68, 69, 67, 65, 74, 20, 8, 39, 61, 80, 2, 71, 109, 74, 74, 81, 65, 74, 8, 84, + 69, 79, 8, 81, 82, 74, 18, 8, 84, 65, 74, 74, 8, 84, 69, 79, 8, 23, 22, 22, 8, + 1, 112, 8, 75, 64, 65, 79, 8, 83, 69, 65, 72, 72, 65, 69, 63, 68, 81, 8, 23, 23, + 22, 8, 1, 112, 2, 71, 109, 74, 74, 81, 65, 74, 8, 84, 69, 79, 8, 81, 82, 74, 18, + 8, 84, 65, 74, 74, 8, 84, 69, 79, 8, 23, 22, 22, 8, 1, 112, 8, 75, 64, 65, 79, + 8, 83, 69, 65, 72, 72, 65, 69, 63, 68, 81, 8, 23, 23, 22, 8, 1, 112, 2, 74, 65, + 68, 73, 65, 74, 20, 8, 49, 61, 63, 68, 64, 65, 73, 8, 84, 69, 79, 8, 70, 65, 64, + 75, 63, 68, 8, 65, 69, 74, 73, 61, 72, 8, 61, 82, 66, 8, 23, 29, 22, 8, 11, 18, + 8, 23, 30, 22, 8, 11, 8, 75, 64, 65, 79, 8, 67, 61, 79, 2, 74, 65, 68, 73, 65, + 74, 20, 8, 49, 61, 63, 68, 64, 65, 73, 8, 84, 69, 79, 8, 70, 65, 64, 75, 63, 68, + 8, 65, 69, 74, 73, 61, 72, 8, 61, 82, 66, 8, 23, 29, 22, 8, 11, 18, 8, 23, 30, + 22, 8, 11, 8, 75, 64, 65, 79, 8, 67, 61, 79, 2, 24, 23, 22, 8, 11, 8, 67, 65, + 67, 61, 74, 67, 65, 74, 8, 132, 69, 74, 64, 18, 8, 73, 110, 132, 132, 65, 74, 8, 84, + 69, 79, 8, 82, 74, 80, 8, 64, 69, 65, 8, 41, 79, 61, 67, 65, 8, 65, 79, 74, 132, + 81, 72, 69, 63, 68, 2, 24, 23, 22, 8, 11, 8, 67, 65, 67, 61, 74, 67, 65, 74, 8, + 132, 69, 74, 64, 18, 8, 73, 110, 132, 132, 65, 74, 8, 84, 69, 79, 8, 82, 74, 80, 8, + 64, 69, 65, 8, 41, 79, 61, 67, 65, 8, 65, 79, 74, 132, 81, 72, 69, 63, 68, 2, 83, + 75, 79, 72, 65, 67, 65, 74, 32, 8, 68, 61, 62, 65, 74, 8, 84, 69, 79, 8, 65, 80, + 8, 74, 69, 63, 68, 81, 8, 83, 69, 65, 72, 72, 65, 69, 63, 68, 81, 8, 69, 74, 8, + 67, 82, 81, 65, 79, 8, 36, 62, 4, 2, 83, 75, 79, 72, 65, 67, 65, 74, 32, 8, 68, + 61, 62, 65, 74, 8, 84, 69, 79, 8, 65, 80, 8, 74, 69, 63, 68, 81, 8, 83, 69, 65, + 72, 72, 65, 69, 63, 68, 81, 8, 69, 74, 8, 67, 82, 81, 65, 79, 8, 36, 62, 4, 2, + 132, 69, 63, 68, 81, 8, 82, 74, 64, 8, 67, 65, 132, 81, 110, 81, 87, 81, 8, 61, 82, + 66, 8, 64, 69, 65, 8, 64, 61, 73, 61, 72, 80, 8, 83, 75, 79, 72, 69, 65, 67, 65, + 74, 64, 65, 74, 8, 60, 61, 68, 72, 65, 74, 8, 83, 75, 74, 2, 132, 69, 63, 68, 81, + 8, 82, 74, 64, 8, 67, 65, 132, 81, 110, 81, 87, 81, 8, 61, 82, 66, 8, 64, 69, 65, + 8, 64, 61, 73, 61, 72, 80, 8, 83, 75, 79, 72, 69, 65, 67, 65, 74, 64, 65, 74, 8, + 60, 61, 68, 72, 65, 74, 8, 83, 75, 74, 2, 23, 30, 31, 23, 18, 8, 23, 29, 30, 22, + 18, 8, 23, 31, 24, 22, 18, 8, 23, 31, 23, 30, 18, 8, 23, 31, 23, 27, 18, 8, 23, + 31, 22, 24, 18, 8, 23, 31, 22, 23, 18, 8, 23, 31, 22, 22, 18, 8, 23, 31, 22, 24, + 2, 23, 30, 31, 23, 18, 8, 23, 29, 30, 22, 18, 8, 23, 31, 24, 22, 18, 8, 23, 31, + 23, 30, 18, 8, 23, 31, 23, 27, 18, 8, 23, 31, 22, 24, 18, 8, 23, 31, 22, 23, 18, + 8, 23, 31, 22, 22, 18, 8, 23, 31, 22, 24, 2, 65, 81, 84, 61, 80, 8, 87, 82, 8, + 84, 65, 69, 81, 8, 67, 65, 81, 79, 69, 65, 62, 65, 74, 35, 8, 44, 63, 68, 8, 132, + 78, 79, 65, 63, 68, 65, 8, 64, 69, 65, 80, 8, 61, 82, 80, 18, 8, 75, 62, 4, 2, + 65, 81, 84, 61, 80, 8, 87, 82, 8, 84, 65, 69, 81, 8, 67, 65, 81, 79, 69, 65, 62, + 65, 74, 35, 8, 44, 63, 68, 8, 132, 78, 79, 65, 63, 68, 65, 8, 64, 69, 65, 80, 8, + 61, 82, 80, 18, 8, 75, 62, 4, 2, 67, 72, 65, 69, 63, 68, 8, 69, 63, 68, 18, 8, + 14, 84, 69, 65, 8, 64, 65, 79, 8, 43, 65, 79, 79, 8, 46, 103, 73, 73, 65, 79, 65, + 79, 15, 18, 8, 67, 61, 74, 87, 8, 67, 65, 74, 61, 82, 8, 84, 65, 69, 102, 18, 2, + 67, 72, 65, 69, 63, 68, 8, 69, 63, 68, 18, 8, 14, 84, 69, 65, 8, 64, 65, 79, 8, + 43, 65, 79, 79, 8, 46, 103, 73, 73, 65, 79, 65, 79, 15, 18, 8, 67, 61, 74, 87, 8, + 67, 65, 74, 61, 82, 8, 84, 65, 69, 102, 18, 2, 14, 36, 78, 78, 72, 61, 82, 80, 8, + 64, 65, 79, 8, 68, 69, 74, 81, 65, 79, 65, 74, 8, 52, 65, 69, 68, 65, 74, 20, 8, + 60, 82, 79, 82, 66, 32, 8, 7, 57, 61, 68, 79, 72, 69, 63, 68, 9, 6, 15, 2, 14, + 36, 78, 78, 72, 61, 82, 80, 8, 64, 65, 79, 8, 68, 69, 74, 81, 65, 79, 65, 74, 8, + 52, 65, 69, 68, 65, 74, 20, 8, 60, 82, 79, 82, 66, 32, 8, 7, 57, 61, 68, 79, 72, + 69, 63, 68, 9, 6, 15, 2, 64, 61, 102, 8, 69, 74, 8, 82, 74, 132, 65, 79, 73, 8, + 40, 81, 61, 81, 8, 51, 75, 132, 81, 65, 74, 8, 132, 69, 74, 64, 18, 8, 64, 69, 65, + 8, 83, 69, 65, 72, 72, 65, 69, 63, 68, 81, 8, 74, 69, 63, 68, 81, 2, 64, 61, 102, + 8, 69, 74, 8, 82, 74, 132, 65, 79, 73, 8, 40, 81, 61, 81, 8, 51, 75, 132, 81, 65, + 74, 8, 132, 69, 74, 64, 18, 8, 64, 69, 65, 8, 83, 69, 65, 72, 72, 65, 69, 63, 68, + 81, 8, 74, 69, 63, 68, 81, 2, 65, 69, 74, 67, 65, 68, 65, 74, 8, 84, 65, 79, 64, + 65, 74, 18, 8, 87, 20, 8, 37, 20, 8, 64, 69, 65, 8, 54, 68, 65, 61, 81, 65, 79, + 78, 75, 132, 81, 65, 74, 8, 83, 75, 74, 8, 23, 30, 31, 31, 18, 8, 75, 62, 84, 75, + 68, 72, 8, 82, 74, 80, 2, 65, 69, 74, 67, 65, 68, 65, 74, 8, 84, 65, 79, 64, 65, + 74, 18, 8, 87, 20, 8, 37, 20, 8, 64, 69, 65, 8, 54, 68, 65, 61, 81, 65, 79, 78, + 75, 132, 81, 65, 74, 8, 83, 75, 74, 8, 23, 30, 31, 31, 18, 8, 75, 62, 84, 75, 68, + 72, 8, 82, 74, 80, 2, 64, 65, 79, 8, 83, 75, 79, 68, 69, 74, 8, 67, 65, 66, 61, + 102, 81, 65, 8, 37, 65, 132, 63, 68, 72, 82, 102, 8, 69, 74, 8, 64, 69, 65, 132, 65, + 79, 8, 37, 65, 87, 69, 65, 68, 82, 74, 67, 8, 65, 69, 74, 65, 2, 64, 65, 79, 8, + 83, 75, 79, 68, 69, 74, 8, 67, 65, 66, 61, 102, 81, 65, 8, 37, 65, 132, 63, 68, 72, + 82, 102, 8, 69, 74, 8, 64, 69, 65, 132, 65, 79, 8, 37, 65, 87, 69, 65, 68, 82, 74, + 67, 8, 65, 69, 74, 65, 2, 37, 65, 132, 132, 65, 79, 82, 74, 67, 8, 62, 79, 69, 74, + 67, 65, 74, 8, 84, 69, 79, 64, 20, 8, 44, 63, 68, 8, 132, 78, 79, 65, 63, 68, 65, + 8, 64, 69, 65, 80, 8, 61, 82, 80, 18, 8, 75, 62, 4, 2, 37, 65, 132, 132, 65, 79, + 82, 74, 67, 8, 62, 79, 69, 74, 67, 65, 74, 8, 84, 69, 79, 64, 20, 8, 44, 63, 68, + 8, 132, 78, 79, 65, 63, 68, 65, 8, 64, 69, 65, 80, 8, 61, 82, 80, 18, 8, 75, 62, + 4, 2, 67, 72, 65, 69, 63, 68, 8, 69, 63, 68, 8, 73, 69, 79, 8, 83, 75, 72, 72, + 71, 75, 73, 73, 65, 74, 8, 64, 61, 79, 110, 62, 65, 79, 8, 71, 72, 61, 79, 8, 62, + 69, 74, 18, 8, 64, 61, 102, 8, 61, 74, 2, 67, 72, 65, 69, 63, 68, 8, 69, 63, 68, + 8, 73, 69, 79, 8, 83, 75, 72, 72, 71, 75, 73, 73, 65, 74, 8, 64, 61, 79, 110, 62, + 65, 79, 8, 71, 72, 61, 79, 8, 62, 69, 74, 18, 8, 64, 61, 102, 8, 61, 74, 2, 64, + 65, 74, 8, 36, 82, 80, 67, 61, 62, 65, 74, 8, 84, 65, 132, 65, 74, 81, 72, 69, 63, + 68, 65, 8, 36, 62, 132, 81, 79, 69, 63, 68, 65, 8, 74, 69, 63, 68, 81, 8, 67, 65, + 73, 61, 63, 68, 81, 2, 64, 65, 74, 8, 36, 82, 80, 67, 61, 62, 65, 74, 8, 84, 65, + 132, 65, 74, 81, 72, 69, 63, 68, 65, 8, 36, 62, 132, 81, 79, 69, 63, 68, 65, 8, 74, + 69, 63, 68, 81, 8, 67, 65, 73, 61, 63, 68, 81, 2, 84, 65, 79, 64, 65, 74, 8, 71, + 109, 74, 74, 65, 74, 18, 8, 65, 80, 8, 132, 65, 69, 8, 64, 65, 74, 74, 8, 62, 65, + 69, 73, 8, 39, 69, 80, 78, 75, 132, 69, 81, 69, 75, 74, 80, 66, 75, 74, 64, 80, 20, + 2, 84, 65, 79, 64, 65, 74, 8, 71, 109, 74, 74, 65, 74, 18, 8, 65, 80, 8, 132, 65, + 69, 8, 64, 65, 74, 74, 8, 62, 65, 69, 73, 8, 39, 69, 80, 78, 75, 132, 69, 81, 69, + 75, 74, 80, 66, 75, 74, 64, 80, 20, 2, 55, 74, 64, 8, 69, 63, 68, 8, 132, 78, 79, + 65, 63, 68, 65, 8, 64, 69, 65, 80, 8, 61, 82, 80, 18, 8, 75, 62, 67, 72, 65, 69, + 63, 68, 8, 69, 63, 68, 8, 84, 65, 69, 102, 18, 8, 64, 61, 102, 8, 84, 69, 79, 2, + 55, 74, 64, 8, 69, 63, 68, 8, 132, 78, 79, 65, 63, 68, 65, 8, 64, 69, 65, 80, 8, + 61, 82, 80, 18, 8, 75, 62, 67, 72, 65, 69, 63, 68, 8, 69, 63, 68, 8, 84, 65, 69, + 102, 18, 8, 64, 61, 102, 8, 84, 69, 79, 2, 64, 69, 65, 8, 54, 69, 72, 67, 82, 74, + 67, 80, 79, 61, 81, 65, 8, 83, 75, 79, 72, 103, 82, 66, 69, 67, 8, 74, 75, 63, 68, + 8, 74, 69, 63, 68, 81, 8, 62, 65, 84, 69, 72, 72, 69, 67, 81, 8, 62, 65, 4, 2, + 64, 69, 65, 8, 54, 69, 72, 67, 82, 74, 67, 80, 79, 61, 81, 65, 8, 83, 75, 79, 72, + 103, 82, 66, 69, 67, 8, 74, 75, 63, 68, 8, 74, 69, 63, 68, 81, 8, 62, 65, 84, 69, + 72, 72, 69, 67, 81, 8, 62, 65, 4, 2, 71, 75, 73, 73, 65, 74, 8, 68, 61, 62, 65, + 74, 20, 8, 36, 62, 65, 79, 8, 69, 63, 68, 8, 132, 75, 72, 72, 81, 65, 8, 67, 72, + 61, 82, 62, 65, 74, 18, 8, 64, 61, 102, 8, 65, 69, 74, 2, 71, 75, 73, 73, 65, 74, + 8, 68, 61, 62, 65, 74, 20, 8, 36, 62, 65, 79, 8, 69, 63, 68, 8, 132, 75, 72, 72, + 81, 65, 8, 67, 72, 61, 82, 62, 65, 74, 18, 8, 64, 61, 102, 8, 65, 69, 74, 2, 40, + 81, 61, 81, 18, 8, 64, 65, 79, 8, 73, 69, 81, 8, 23, 28, 22, 8, 11, 18, 8, 23, + 29, 22, 8, 11, 18, 8, 67, 61, 79, 8, 25, 22, 22, 8, 11, 8, 64, 69, 65, 8, 37, + 65, 64, 110, 79, 66, 74, 69, 132, 132, 65, 8, 64, 65, 63, 71, 81, 18, 2, 40, 81, 61, + 81, 18, 8, 64, 65, 79, 8, 73, 69, 81, 8, 23, 28, 22, 8, 11, 18, 8, 23, 29, 22, + 8, 11, 18, 8, 67, 61, 79, 8, 25, 22, 22, 8, 11, 8, 64, 69, 65, 8, 37, 65, 64, + 110, 79, 66, 74, 69, 132, 132, 65, 8, 64, 65, 63, 71, 81, 18, 2, 83, 75, 74, 8, 64, + 65, 79, 8, 52, 65, 67, 69, 65, 79, 82, 74, 67, 8, 64, 75, 63, 68, 8, 61, 82, 63, + 68, 8, 61, 72, 80, 8, 65, 69, 74, 8, 64, 82, 79, 63, 68, 61, 82, 80, 8, 132, 75, + 72, 69, 64, 65, 79, 8, 82, 74, 64, 8, 83, 75, 79, 4, 2, 83, 75, 74, 8, 64, 65, + 79, 8, 52, 65, 67, 69, 65, 79, 82, 74, 67, 8, 64, 75, 63, 68, 8, 61, 82, 63, 68, + 8, 61, 72, 80, 8, 65, 69, 74, 8, 64, 82, 79, 63, 68, 61, 82, 80, 8, 132, 75, 72, + 69, 64, 65, 79, 8, 82, 74, 64, 8, 83, 75, 79, 4, 2, 132, 69, 63, 68, 81, 69, 67, + 65, 79, 8, 61, 74, 67, 65, 132, 65, 68, 65, 74, 8, 84, 65, 79, 64, 65, 74, 8, 84, + 69, 79, 64, 20, 8, 44, 63, 68, 8, 73, 82, 102, 8, 70, 65, 64, 75, 63, 68, 8, 61, + 82, 63, 68, 8, 74, 75, 63, 68, 18, 8, 65, 62, 65, 74, 132, 75, 2, 132, 69, 63, 68, + 81, 69, 67, 65, 79, 8, 61, 74, 67, 65, 132, 65, 68, 65, 74, 8, 84, 65, 79, 64, 65, + 74, 8, 84, 69, 79, 64, 20, 8, 44, 63, 68, 8, 73, 82, 102, 8, 70, 65, 64, 75, 63, + 68, 8, 61, 82, 63, 68, 8, 74, 75, 63, 68, 18, 8, 65, 62, 65, 74, 132, 75, 2, 84, + 69, 65, 8, 64, 65, 79, 8, 46, 103, 73, 73, 65, 79, 65, 79, 18, 8, 73, 69, 81, 8, + 65, 69, 74, 69, 67, 65, 74, 8, 57, 75, 79, 81, 65, 74, 8, 61, 82, 66, 8, 64, 65, + 74, 2, 84, 69, 65, 8, 64, 65, 79, 8, 46, 103, 73, 73, 65, 79, 65, 79, 18, 8, 73, + 69, 81, 8, 65, 69, 74, 69, 67, 65, 74, 8, 57, 75, 79, 81, 65, 74, 8, 61, 82, 66, + 8, 64, 65, 74, 2, 37, 65, 132, 63, 68, 72, 82, 102, 8, 84, 65, 67, 65, 74, 8, 64, + 65, 80, 8, 36, 82, 80, 67, 72, 65, 69, 63, 68, 80, 66, 75, 74, 64, 80, 8, 87, 82, + 79, 110, 63, 71, 71, 75, 73, 73, 65, 74, 18, 2, 37, 65, 132, 63, 68, 72, 82, 102, 8, + 84, 65, 67, 65, 74, 8, 64, 65, 80, 8, 36, 82, 80, 67, 72, 65, 69, 63, 68, 80, 66, + 75, 74, 64, 80, 8, 87, 82, 79, 110, 63, 71, 71, 75, 73, 73, 65, 74, 18, 2, 55, 65, + 62, 65, 79, 132, 63, 68, 110, 132, 132, 65, 18, 8, 64, 69, 65, 8, 87, 84, 69, 132, 63, + 68, 65, 74, 8, 24, 22, 22, 8, 22, 22, 22, 8, 1, 112, 8, 82, 74, 64, 8, 24, 8, + 48, 69, 72, 72, 69, 75, 74, 65, 74, 2, 55, 65, 62, 65, 79, 132, 63, 68, 110, 132, 132, + 65, 18, 8, 64, 69, 65, 8, 87, 84, 69, 132, 63, 68, 65, 74, 8, 24, 22, 22, 8, 22, + 22, 22, 8, 1, 112, 8, 82, 74, 64, 8, 24, 8, 48, 69, 72, 72, 69, 75, 74, 65, 74, + 2, 132, 63, 68, 84, 61, 74, 71, 81, 65, 74, 18, 8, 65, 69, 74, 67, 65, 132, 81, 65, + 72, 72, 81, 8, 82, 74, 64, 8, 65, 80, 8, 66, 82, 79, 63, 68, 81, 62, 61, 79, 2, + 132, 63, 68, 84, 61, 74, 71, 81, 65, 74, 18, 8, 65, 69, 74, 67, 65, 132, 81, 65, 72, + 72, 81, 8, 82, 74, 64, 8, 65, 80, 8, 66, 82, 79, 63, 68, 81, 62, 61, 79, 2, 132, + 63, 68, 84, 65, 79, 8, 67, 65, 73, 61, 63, 68, 81, 18, 8, 64, 65, 74, 8, 40, 81, + 61, 81, 8, 62, 65, 69, 8, 67, 72, 65, 69, 63, 68, 65, 74, 8, 53, 81, 65, 82, 65, + 79, 132, 103, 3, 2, 132, 63, 68, 84, 65, 79, 8, 67, 65, 73, 61, 63, 68, 81, 18, 8, + 64, 65, 74, 8, 40, 81, 61, 81, 8, 62, 65, 69, 8, 67, 72, 65, 69, 63, 68, 65, 74, + 8, 53, 81, 65, 82, 65, 79, 132, 103, 3, 2, 87, 82, 8, 62, 69, 72, 61, 74, 87, 69, + 65, 79, 65, 74, 20, 8, 53, 78, 103, 81, 65, 79, 18, 8, 61, 72, 80, 8, 73, 61, 74, + 8, 74, 103, 73, 72, 69, 63, 68, 8, 61, 74, 74, 61, 68, 73, 18, 2, 87, 82, 8, 62, + 69, 72, 61, 74, 87, 69, 65, 79, 65, 74, 20, 8, 53, 78, 103, 81, 65, 79, 18, 8, 61, + 72, 80, 8, 73, 61, 74, 8, 74, 103, 73, 72, 69, 63, 68, 8, 61, 74, 74, 61, 68, 73, + 18, 2, 64, 61, 102, 8, 64, 69, 65, 8, 53, 103, 81, 87, 65, 8, 69, 73, 73, 65, 79, + 8, 87, 84, 69, 132, 63, 68, 65, 74, 8, 23, 8, 82, 74, 64, 8, 24, 8, 48, 69, 72, + 72, 69, 75, 74, 65, 74, 8, 1, 112, 2, 64, 61, 102, 8, 64, 69, 65, 8, 53, 103, 81, + 87, 65, 8, 69, 73, 73, 65, 79, 8, 87, 84, 69, 132, 63, 68, 65, 74, 8, 23, 8, 82, + 74, 64, 8, 24, 8, 48, 69, 72, 72, 69, 75, 74, 65, 74, 8, 1, 112, 2, 132, 63, 68, + 84, 61, 74, 71, 65, 74, 8, 84, 110, 79, 64, 65, 74, 18, 8, 68, 61, 81, 8, 73, 61, + 74, 8, 65, 69, 74, 65, 74, 8, 48, 69, 81, 81, 65, 72, 84, 65, 67, 8, 65, 69, 74, + 67, 65, 4, 2, 132, 63, 68, 84, 61, 74, 71, 65, 74, 8, 84, 110, 79, 64, 65, 74, 18, + 8, 68, 61, 81, 8, 73, 61, 74, 8, 65, 69, 74, 65, 74, 8, 48, 69, 81, 81, 65, 72, + 84, 65, 67, 8, 65, 69, 74, 67, 65, 4, 2, 132, 63, 68, 72, 61, 67, 65, 74, 8, 82, + 74, 64, 8, 67, 65, 132, 61, 67, 81, 32, 8, 74, 82, 79, 8, 23, 8, 48, 69, 72, 72, + 69, 75, 74, 8, 1, 112, 8, 69, 74, 8, 64, 65, 74, 8, 74, 65, 82, 65, 74, 2, 132, + 63, 68, 72, 61, 67, 65, 74, 8, 82, 74, 64, 8, 67, 65, 132, 61, 67, 81, 32, 8, 74, + 82, 79, 8, 23, 8, 48, 69, 72, 72, 69, 75, 74, 8, 1, 112, 8, 69, 74, 8, 64, 65, + 74, 8, 74, 65, 82, 65, 74, 2, 40, 81, 61, 81, 8, 82, 74, 64, 8, 74, 82, 79, 8, + 64, 65, 74, 8, 55, 65, 62, 65, 79, 132, 63, 68, 82, 102, 8, 110, 62, 65, 79, 8, 23, + 8, 48, 69, 72, 72, 69, 75, 74, 8, 1, 112, 8, 69, 74, 2, 40, 81, 61, 81, 8, 82, + 74, 64, 8, 74, 82, 79, 8, 64, 65, 74, 8, 55, 65, 62, 65, 79, 132, 63, 68, 82, 102, + 8, 110, 62, 65, 79, 8, 23, 8, 48, 69, 72, 72, 69, 75, 74, 8, 1, 112, 8, 69, 74, + 2, 64, 65, 74, 8, 36, 82, 80, 67, 72, 65, 69, 63, 68, 80, 66, 75, 74, 64, 80, 9, + 8, 56, 69, 65, 72, 72, 65, 69, 63, 68, 81, 8, 69, 132, 81, 8, 73, 61, 74, 8, 73, + 69, 81, 8, 64, 69, 65, 132, 65, 74, 2, 64, 65, 74, 8, 36, 82, 80, 67, 72, 65, 69, + 63, 68, 80, 66, 75, 74, 64, 80, 9, 8, 56, 69, 65, 72, 72, 65, 69, 63, 68, 81, 8, + 69, 132, 81, 8, 73, 61, 74, 8, 73, 69, 81, 8, 64, 69, 65, 132, 65, 74, 2, 60, 61, + 68, 72, 65, 74, 8, 74, 69, 63, 68, 81, 8, 79, 69, 63, 68, 81, 69, 67, 8, 83, 75, + 79, 67, 65, 67, 61, 74, 67, 65, 74, 18, 8, 68, 103, 81, 81, 65, 8, 83, 69, 65, 72, + 72, 65, 69, 63, 68, 81, 2, 60, 61, 68, 72, 65, 74, 8, 74, 69, 63, 68, 81, 8, 79, + 69, 63, 68, 81, 69, 67, 8, 83, 75, 79, 67, 65, 67, 61, 74, 67, 65, 74, 18, 8, 68, + 103, 81, 81, 65, 8, 83, 69, 65, 72, 72, 65, 69, 63, 68, 81, 2, 65, 69, 74, 65, 8, + 61, 74, 64, 65, 79, 65, 8, 60, 61, 68, 72, 8, 84, 103, 68, 72, 65, 74, 8, 82, 74, + 64, 8, 61, 82, 66, 8, 64, 69, 65, 132, 65, 8, 57, 65, 69, 132, 65, 8, 65, 80, 2, + 65, 69, 74, 65, 8, 61, 74, 64, 65, 79, 65, 8, 60, 61, 68, 72, 8, 84, 103, 68, 72, + 65, 74, 8, 82, 74, 64, 8, 61, 82, 66, 8, 64, 69, 65, 132, 65, 8, 57, 65, 69, 132, + 65, 8, 65, 80, 2, 83, 65, 79, 68, 110, 81, 65, 74, 8, 132, 75, 72, 72, 65, 74, 18, + 8, 64, 61, 102, 8, 69, 73, 73, 65, 79, 8, 65, 69, 74, 8, 45, 61, 68, 79, 8, 83, + 75, 74, 8, 64, 65, 73, 8, 61, 74, 4, 2, 83, 65, 79, 68, 110, 81, 65, 74, 8, 132, + 75, 72, 72, 65, 74, 18, 8, 64, 61, 102, 8, 69, 73, 73, 65, 79, 8, 65, 69, 74, 8, + 45, 61, 68, 79, 8, 83, 75, 74, 8, 64, 65, 73, 8, 61, 74, 4, 2, 64, 65, 79, 74, + 8, 69, 74, 8, 132, 75, 72, 63, 68, 65, 79, 8, 66, 69, 74, 61, 74, 87, 69, 65, 72, + 72, 65, 74, 8, 36, 62, 68, 103, 74, 67, 69, 67, 71, 65, 69, 81, 8, 132, 81, 61, 74, + 64, 20, 2, 64, 65, 79, 74, 8, 69, 74, 8, 132, 75, 72, 63, 68, 65, 79, 8, 66, 69, + 74, 61, 74, 87, 69, 65, 72, 72, 65, 74, 8, 36, 62, 68, 103, 74, 67, 69, 67, 71, 65, + 69, 81, 8, 132, 81, 61, 74, 64, 20, 2, 39, 65, 74, 74, 8, 64, 61, 79, 110, 62, 65, + 79, 8, 73, 110, 132, 132, 65, 74, 8, 84, 69, 79, 8, 82, 74, 80, 8, 64, 75, 63, 68, + 8, 71, 72, 61, 79, 8, 132, 65, 69, 74, 18, 8, 64, 61, 2, 39, 65, 74, 74, 8, 64, + 61, 79, 110, 62, 65, 79, 8, 73, 110, 132, 132, 65, 74, 8, 84, 69, 79, 8, 82, 74, 80, + 8, 64, 75, 63, 68, 8, 71, 72, 61, 79, 8, 132, 65, 69, 74, 18, 8, 64, 61, 2, 62, + 65, 69, 8, 64, 65, 73, 8, 66, 79, 110, 68, 65, 79, 65, 74, 8, 53, 86, 132, 81, 65, + 73, 8, 65, 69, 74, 8, 132, 63, 68, 65, 69, 74, 62, 61, 79, 65, 79, 8, 55, 65, 62, + 65, 79, 132, 63, 68, 82, 81, 2, 62, 65, 69, 8, 64, 65, 73, 8, 66, 79, 110, 68, 65, + 79, 65, 74, 8, 53, 86, 132, 81, 65, 73, 8, 65, 69, 74, 8, 132, 63, 68, 65, 69, 74, + 62, 61, 79, 65, 79, 8, 55, 65, 62, 65, 79, 132, 63, 68, 82, 81, 2, 83, 75, 74, 8, + 30, 22, 22, 8, 22, 22, 22, 8, 1, 112, 8, 87, 20, 8, 37, 20, 8, 65, 69, 67, 65, + 74, 81, 72, 69, 63, 68, 8, 65, 69, 74, 8, 39, 65, 66, 69, 87, 69, 81, 8, 83, 75, + 74, 2, 83, 75, 74, 8, 30, 22, 22, 8, 22, 22, 22, 8, 1, 112, 8, 87, 20, 8, 37, + 20, 8, 65, 69, 67, 65, 74, 81, 72, 69, 63, 68, 8, 65, 69, 74, 8, 39, 65, 66, 69, + 87, 69, 81, 8, 83, 75, 74, 2, 24, 22, 22, 8, 22, 22, 22, 8, 1, 112, 8, 62, 65, + 64, 65, 82, 81, 65, 81, 65, 20, 8, 39, 61, 80, 8, 132, 75, 72, 72, 8, 74, 82, 74, + 8, 61, 62, 67, 65, 103, 74, 64, 65, 79, 81, 2, 84, 65, 79, 64, 65, 74, 18, 8, 82, + 74, 64, 8, 69, 74, 132, 75, 84, 65, 69, 81, 8, 132, 81, 69, 73, 73, 65, 74, 8, 84, + 69, 79, 8, 73, 69, 81, 8, 64, 65, 73, 8, 48, 61, 67, 69, 4, 2, 132, 81, 79, 61, + 81, 8, 83, 75, 72, 72, 71, 75, 73, 73, 65, 74, 8, 110, 62, 65, 79, 65, 69, 74, 20, + 8, 57, 69, 79, 8, 67, 72, 61, 82, 62, 65, 74, 8, 61, 62, 65, 79, 18, 8, 64, 61, + 102, 2, 64, 69, 65, 8, 56, 75, 79, 72, 61, 67, 65, 8, 69, 74, 8, 64, 69, 65, 132, + 65, 79, 8, 41, 75, 79, 73, 18, 8, 69, 74, 8, 69, 68, 79, 65, 79, 8, 46, 110, 79, + 87, 65, 8, 74, 69, 63, 68, 81, 2, 61, 74, 67, 65, 74, 75, 73, 73, 65, 74, 8, 84, + 65, 79, 64, 65, 74, 8, 71, 61, 74, 74, 20, 8, 57, 69, 79, 8, 73, 110, 132, 132, 65, + 74, 8, 84, 69, 132, 132, 65, 74, 18, 2, 84, 61, 80, 8, 65, 69, 67, 65, 74, 81, 72, + 69, 63, 68, 8, 64, 69, 65, 132, 65, 79, 8, 41, 75, 74, 64, 80, 8, 132, 65, 69, 74, + 8, 82, 74, 64, 8, 84, 69, 65, 8, 65, 79, 8, 61, 74, 4, 2, 67, 65, 84, 65, 74, + 64, 65, 81, 8, 84, 65, 79, 64, 65, 74, 8, 132, 75, 72, 72, 20, 8, 57, 65, 74, 74, + 8, 64, 65, 79, 8, 43, 65, 79, 79, 8, 46, 103, 73, 73, 65, 79, 65, 79, 2, 67, 72, + 61, 82, 62, 81, 18, 8, 64, 61, 102, 8, 132, 63, 68, 75, 74, 8, 61, 82, 80, 8, 64, + 65, 79, 8, 39, 65, 66, 69, 74, 69, 81, 69, 75, 74, 18, 8, 61, 82, 80, 8, 64, 65, + 73, 2, 57, 75, 79, 81, 65, 8, 36, 82, 80, 67, 72, 65, 69, 63, 68, 80, 66, 75, 74, + 64, 80, 8, 132, 65, 69, 74, 65, 8, 37, 65, 132, 81, 69, 73, 73, 82, 74, 67, 8, 68, + 65, 79, 83, 75, 79, 4, 2, 67, 65, 68, 81, 18, 8, 132, 75, 8, 73, 61, 67, 8, 64, + 61, 80, 8, 132, 65, 69, 74, 20, 8, 14, 40, 80, 8, 71, 61, 74, 74, 8, 61, 62, 65, + 79, 8, 61, 82, 63, 68, 8, 65, 69, 74, 73, 61, 72, 2, 65, 69, 74, 8, 61, 74, 64, + 65, 79, 65, 79, 8, 46, 103, 73, 73, 65, 79, 65, 79, 8, 82, 74, 64, 8, 65, 69, 74, + 8, 61, 74, 64, 65, 79, 65, 79, 8, 48, 61, 67, 69, 132, 81, 79, 61, 81, 2, 71, 75, + 73, 73, 65, 74, 15, 18, 8, 64, 65, 79, 8, 74, 69, 63, 68, 81, 80, 8, 83, 75, 74, + 8, 45, 61, 71, 75, 62, 8, 82, 74, 64, 8, 132, 65, 69, 74, 65, 74, 8, 37, 79, 110, + 64, 65, 79, 74, 2, 84, 82, 102, 81, 65, 18, 8, 82, 74, 64, 8, 64, 69, 65, 8, 71, + 109, 74, 74, 65, 74, 8, 74, 61, 63, 68, 68, 65, 79, 8, 83, 69, 65, 72, 72, 65, 69, + 63, 68, 81, 8, 67, 72, 61, 82, 62, 65, 74, 18, 2, 64, 61, 102, 8, 64, 69, 65, 132, + 65, 79, 8, 41, 75, 74, 64, 80, 8, 74, 69, 63, 68, 81, 80, 8, 84, 65, 69, 81, 65, + 79, 8, 132, 65, 69, 74, 8, 132, 75, 72, 72, 8, 61, 72, 80, 8, 65, 69, 74, 2, 53, + 78, 61, 79, 66, 75, 74, 64, 80, 18, 8, 69, 74, 8, 64, 65, 74, 8, 69, 73, 73, 65, + 79, 8, 65, 81, 84, 61, 80, 8, 68, 69, 74, 65, 69, 74, 67, 65, 81, 61, 74, 8, 82, + 74, 64, 2, 74, 69, 63, 68, 81, 80, 8, 68, 65, 79, 61, 82, 80, 67, 65, 74, 75, 73, + 73, 65, 74, 8, 84, 69, 79, 64, 20, 8, 57, 69, 79, 8, 73, 109, 63, 68, 81, 65, 74, + 8, 64, 75, 63, 68, 2, 61, 82, 63, 68, 8, 71, 110, 74, 66, 81, 69, 67, 65, 8, 39, + 69, 80, 71, 82, 132, 132, 69, 75, 74, 65, 74, 8, 68, 69, 65, 79, 110, 62, 65, 79, 8, + 83, 65, 79, 73, 65, 69, 64, 65, 74, 20, 8, 49, 82, 74, 2, 68, 65, 69, 102, 81, 8, + 65, 80, 32, 8, 65, 79, 8, 132, 75, 72, 72, 8, 74, 82, 79, 8, 69, 74, 8, 36, 82, + 80, 74, 61, 68, 73, 65, 66, 103, 72, 72, 65, 74, 8, 83, 65, 79, 84, 61, 74, 64, 81, + 8, 59, 75, 79, 71, 8, 82, 74, 64, 8, 59, 65, 73, 65, 74, 2, 84, 65, 79, 64, 65, + 74, 18, 8, 59, 69, 74, 67, 8, 84, 65, 74, 74, 8, 62, 65, 69, 8, 64, 65, 79, 8, + 36, 82, 66, 132, 81, 65, 72, 72, 82, 74, 67, 8, 64, 65, 80, 8, 43, 61, 82, 80, 68, + 61, 72, 81, 80, 4, 2, 78, 72, 61, 74, 65, 80, 8, 64, 65, 79, 8, 36, 82, 80, 67, + 72, 65, 69, 63, 68, 8, 69, 74, 66, 75, 72, 67, 65, 8, 83, 75, 79, 110, 62, 65, 79, + 67, 65, 68, 65, 74, 64, 65, 79, 2, 53, 63, 68, 84, 61, 74, 71, 82, 74, 67, 8, 64, + 65, 79, 8, 41, 69, 74, 61, 74, 87, 72, 61, 67, 65, 8, 53, 63, 68, 84, 69, 65, 79, + 69, 67, 71, 65, 69, 81, 65, 74, 8, 62, 65, 79, 65, 69, 81, 65, 81, 20, 2, 48, 65, + 69, 74, 65, 8, 43, 65, 79, 79, 65, 74, 18, 8, 64, 61, 79, 110, 62, 65, 79, 8, 71, + 61, 74, 74, 8, 73, 61, 74, 8, 132, 65, 68, 79, 8, 83, 65, 79, 132, 63, 68, 69, 65, + 64, 65, 74, 65, 79, 2, 48, 65, 69, 74, 82, 74, 67, 8, 132, 65, 69, 74, 18, 8, 84, + 61, 80, 8, 53, 63, 68, 84, 69, 65, 79, 69, 67, 71, 65, 69, 81, 65, 74, 8, 132, 69, + 74, 64, 8, 82, 74, 64, 8, 84, 61, 80, 2, 83, 75, 79, 110, 62, 65, 79, 67, 65, 68, + 65, 74, 64, 65, 8, 56, 65, 79, 68, 103, 72, 81, 74, 69, 132, 132, 65, 8, 132, 69, 74, + 64, 20, 8, 14, 59, 61, 63, 68, 81, 20, 15, 8, 40, 69, 67, 65, 74, 81, 72, 69, 63, + 68, 8, 71, 61, 74, 74, 2, 64, 65, 79, 8, 36, 82, 80, 67, 72, 65, 69, 63, 68, 8, + 74, 69, 65, 73, 61, 72, 80, 8, 53, 63, 68, 84, 69, 65, 79, 69, 67, 71, 65, 69, 81, + 65, 74, 8, 62, 65, 79, 65, 69, 81, 65, 74, 18, 8, 132, 75, 4, 2, 72, 61, 74, 67, + 65, 8, 84, 69, 79, 8, 73, 69, 81, 8, 64, 65, 74, 8, 53, 81, 65, 82, 65, 79, 74, + 8, 132, 75, 8, 68, 75, 63, 68, 8, 67, 65, 68, 65, 74, 8, 71, 109, 74, 74, 65, 74, + 18, 2, 84, 69, 65, 8, 84, 69, 79, 8, 84, 75, 72, 72, 65, 74, 18, 8, 83, 69, 65, + 72, 72, 65, 69, 63, 68, 81, 8, 82, 74, 132, 65, 79, 73, 8, 42, 65, 84, 69, 132, 132, + 65, 74, 18, 8, 61, 62, 65, 79, 2, 74, 69, 63, 68, 81, 8, 64, 65, 74, 8, 37, 65, + 132, 63, 68, 72, 110, 132, 132, 65, 74, 8, 82, 74, 64, 8, 74, 69, 63, 68, 81, 8, 64, + 65, 73, 8, 51, 61, 78, 69, 65, 79, 20, 8, 23, 31, 24, 24, 2, 14, 91, 8, 23, 20, + 15, 8, 49, 103, 63, 68, 132, 81, 8, 64, 65, 74, 8, 36, 132, 132, 69, 132, 81, 65, 74, + 81, 65, 74, 8, 69, 132, 81, 8, 87, 82, 8, 74, 65, 74, 74, 65, 74, 8, 64, 69, 65, + 8, 42, 79, 82, 78, 78, 65, 2, 64, 65, 79, 8, 56, 75, 72, 72, 87, 69, 65, 68, 65, + 79, 18, 8, 46, 61, 132, 132, 65, 74, 62, 75, 81, 65, 74, 8, 82, 74, 64, 8, 37, 82, + 79, 65, 61, 82, 67, 65, 68, 69, 72, 66, 65, 74, 18, 8, 64, 69, 65, 2, 62, 65, 69, + 8, 82, 74, 80, 8, 65, 69, 74, 8, 42, 65, 68, 61, 72, 81, 8, 83, 75, 74, 8, 24, + 22, 27, 22, 8, 62, 69, 80, 8, 25, 23, 22, 22, 8, 1, 112, 18, 8, 24, 22, 22, 22, + 2, 62, 69, 80, 8, 25, 22, 27, 22, 8, 1, 112, 18, 8, 23, 30, 27, 22, 8, 62, 69, + 80, 8, 24, 31, 22, 22, 8, 1, 112, 8, 124, 63, 20, 18, 8, 69, 74, 8, 37, 65, 79, + 72, 69, 74, 8, 64, 61, 67, 65, 67, 65, 74, 2, 83, 75, 74, 8, 23, 30, 22, 22, 8, + 62, 69, 80, 8, 25, 22, 22, 22, 8, 1, 112, 18, 8, 64, 69, 65, 8, 37, 82, 79, 65, + 61, 82, 67, 65, 68, 69, 72, 66, 65, 74, 8, 132, 75, 67, 61, 79, 2, 74, 82, 79, 8, + 83, 75, 74, 8, 23, 24, 22, 22, 8, 62, 69, 80, 8, 25, 22, 22, 22, 8, 1, 112, 8, + 62, 65, 87, 69, 65, 68, 65, 74, 20, 8, 39, 69, 65, 8, 37, 75, 81, 65, 74, 2, 65, + 79, 68, 61, 72, 81, 65, 74, 8, 62, 65, 69, 8, 82, 74, 80, 8, 23, 29, 22, 22, 8, + 62, 69, 80, 8, 24, 26, 22, 22, 8, 1, 112, 18, 8, 69, 74, 8, 37, 65, 79, 72, 69, + 74, 8, 23, 28, 22, 22, 2, 62, 69, 80, 8, 24, 25, 22, 22, 8, 1, 112, 20, 8, 39, + 61, 74, 74, 18, 8, 82, 73, 8, 71, 72, 65, 69, 74, 65, 79, 65, 8, 42, 79, 82, 78, + 78, 65, 74, 8, 87, 82, 8, 110, 62, 65, 79, 4, 2, 67, 65, 68, 65, 74, 8, 71, 61, + 73, 65, 74, 8, 61, 82, 80, 8, 59, 65, 73, 65, 74, 8, 82, 74, 64, 8, 49, 65, 84, + 8, 59, 75, 79, 71, 18, 8, 62, 65, 87, 69, 65, 68, 82, 74, 67, 80, 84, 65, 69, 80, + 65, 2, 61, 82, 80, 8, 64, 65, 73, 8, 37, 61, 79, 61, 69, 74, 8, 82, 74, 64, 8, + 37, 61, 74, 67, 72, 61, 64, 65, 132, 63, 68, 8, 23, 24, 22, 22, 18, 8, 24, 22, 22, + 22, 18, 8, 26, 27, 31, 30, 23, 23, 18, 2, 30, 29, 18, 8, 29, 30, 30, 23, 18, 8, + 30, 22, 22, 18, 8, 31, 27, 30, 29, 26, 23, 24, 25, 28, 8, 1, 112, 8, 68, 69, 74, + 87, 82, 20, 8, 23, 24, 18, 8, 25, 26, 18, 8, 28, 27, 18, 8, 31, 30, 29, 23, 18, + 2, 29, 30, 31, 31, 27, 18, 8, 23, 24, 27, 18, 8, 25, 27, 26, 29, 18, 8, 26, 22, + 22, 22, 22, 18, 8, 24, 23, 22, 22, 18, 8, 26, 30, 22, 22, 18, 8, 30, 22, 22, 22, + 18, 8, 31, 30, 22, 22, 8, 1, 112, 18, 2, 23, 24, 22, 22, 8, 1, 112, 18, 8, 26, + 27, 22, 8, 1, 112, 18, 8, 23, 22, 22, 8, 1, 112, 18, 8, 31, 30, 8, 1, 112, 18, + 8, 29, 30, 28, 27, 26, 18, 26, 22, 8, 1, 112, 18, 8, 24, 23, 18, 31, 31, 8, 1, + 112, 2, 48, 69, 81, 81, 65, 72, 80, 8, 62, 65, 69, 72, 103, 82, 66, 69, 67, 65, 79, + 8, 55, 74, 81, 65, 79, 80, 81, 110, 81, 87, 82, 74, 67, 8, 65, 79, 68, 103, 72, 81, + 8, 65, 69, 74, 8, 36, 79, 62, 65, 69, 81, 65, 79, 2, 73, 69, 74, 64, 65, 132, 81, + 65, 74, 80, 8, 25, 27, 22, 8, 1, 112, 18, 8, 67, 65, 79, 69, 74, 67, 65, 79, 8, + 69, 73, 8, 56, 75, 72, 72, 87, 82, 67, 18, 8, 83, 69, 65, 72, 72, 65, 69, 63, 68, + 81, 8, 61, 82, 63, 68, 2, 69, 73, 8, 56, 65, 79, 67, 72, 65, 69, 63, 68, 8, 73, + 69, 81, 8, 53, 81, 61, 61, 81, 80, 61, 74, 72, 65, 69, 68, 65, 74, 8, 78, 20, 61, + 20, 8, 83, 75, 74, 8, 31, 27, 22, 8, 1, 112, 8, 62, 69, 80, 8, 87, 82, 2, 23, + 20, 27, 29, 27, 8, 1, 112, 18, 8, 23, 24, 20, 22, 22, 22, 8, 1, 112, 18, 8, 26, + 27, 22, 22, 8, 1, 112, 18, 8, 28, 30, 29, 26, 27, 31, 8, 1, 112, 18, 8, 28, 27, + 20, 27, 24, 23, 26, 8, 1, 112, 8, 124, 63, 20, 2, 61, 72, 80, 8, 61, 82, 63, 68, + 8, 56, 65, 79, 65, 69, 74, 132, 73, 69, 81, 67, 72, 69, 65, 64, 20, 8, 14, 23, 30, + 31, 31, 18, 8, 23, 29, 27, 23, 18, 8, 23, 29, 28, 27, 18, 8, 23, 25, 26, 29, 18, + 8, 23, 24, 31, 30, 18, 8, 23, 30, 27, 28, 18, 8, 23, 30, 27, 23, 2, 23, 30, 27, + 25, 18, 8, 23, 30, 27, 26, 18, 8, 23, 30, 26, 27, 18, 8, 23, 30, 26, 24, 18, 8, + 23, 30, 26, 25, 18, 8, 23, 30, 31, 24, 18, 8, 23, 30, 26, 22, 18, 8, 23, 30, 22, + 24, 18, 8, 23, 30, 22, 29, 18, 8, 23, 30, 22, 31, 18, 8, 23, 30, 23, 22, 15, 2, + 40, 80, 8, 68, 61, 74, 64, 65, 72, 81, 8, 132, 69, 63, 68, 8, 64, 61, 8, 79, 65, + 67, 65, 72, 73, 103, 102, 69, 67, 18, 8, 73, 65, 69, 74, 65, 8, 7, 43, 65, 79, 79, + 65, 74, 6, 18, 8, 82, 73, 8, 84, 69, 79, 81, 132, 63, 68, 61, 66, 81, 72, 69, 63, + 68, 65, 2, 49, 75, 81, 132, 81, 103, 74, 64, 65, 18, 8, 64, 69, 65, 8, 69, 74, 8, + 68, 75, 68, 65, 8, 43, 82, 74, 64, 65, 79, 81, 65, 8, 68, 69, 74, 65, 69, 74, 67, + 65, 68, 65, 74, 18, 8, 25, 22, 22, 18, 8, 26, 22, 22, 18, 8, 27, 22, 22, 8, 1, + 112, 18, 8, 83, 75, 74, 2, 64, 65, 74, 65, 74, 8, 84, 69, 79, 8, 64, 61, 74, 74, + 8, 23, 27, 22, 18, 8, 24, 22, 22, 18, 8, 24, 27, 22, 18, 8, 25, 22, 22, 18, 8, + 25, 27, 22, 18, 8, 26, 22, 22, 18, 8, 26, 27, 22, 18, 8, 27, 22, 22, 18, 8, 27, + 27, 22, 18, 8, 28, 22, 22, 2, 28, 27, 22, 18, 8, 29, 22, 22, 18, 8, 29, 27, 22, + 18, 8, 30, 22, 22, 18, 8, 30, 27, 22, 18, 8, 31, 22, 22, 18, 8, 31, 27, 22, 18, + 8, 23, 22, 22, 22, 8, 1, 112, 8, 65, 79, 132, 81, 61, 81, 81, 65, 74, 8, 73, 110, + 132, 132, 65, 74, 20, 2, 56, 65, 79, 67, 72, 65, 69, 63, 68, 132, 84, 65, 69, 132, 65, + 8, 65, 69, 74, 66, 61, 63, 68, 8, 65, 79, 132, 63, 68, 65, 69, 74, 81, 8, 68, 69, + 65, 79, 8, 83, 75, 74, 8, 37, 65, 79, 67, 71, 61, 73, 73, 65, 79, 132, 8, 56, 75, + 79, 132, 63, 68, 72, 61, 67, 2, 132, 69, 63, 68, 8, 83, 75, 74, 8, 42, 79, 82, 74, + 64, 61, 82, 66, 8, 87, 82, 8, 83, 65, 79, 61, 62, 132, 63, 68, 69, 65, 64, 65, 74, + 20, 8, 39, 69, 65, 8, 45, 61, 68, 79, 65, 8, 23, 30, 24, 27, 18, 8, 23, 30, 24, + 28, 18, 8, 23, 30, 24, 29, 18, 8, 23, 30, 24, 30, 8, 82, 74, 64, 2, 23, 30, 24, + 31, 8, 84, 61, 79, 65, 74, 8, 68, 61, 79, 81, 20, 8, 24, 22, 18, 27, 8, 11, 8, + 82, 74, 132, 65, 79, 65, 79, 8, 37, 110, 79, 67, 65, 79, 132, 63, 68, 61, 66, 81, 8, + 81, 79, 69, 66, 66, 81, 32, 8, 64, 65, 74, 74, 8, 28, 27, 8, 11, 8, 73, 61, 63, + 68, 65, 74, 2, 61, 72, 72, 65, 69, 74, 8, 64, 69, 65, 8, 40, 69, 74, 71, 75, 73, + 73, 65, 74, 8, 62, 69, 80, 8, 23, 27, 22, 22, 8, 1, 112, 8, 61, 82, 80, 18, 8, + 64, 61, 79, 110, 62, 65, 79, 8, 29, 30, 8, 11, 8, 82, 74, 64, 8, 65, 69, 74, 8, + 40, 69, 74, 71, 75, 73, 73, 65, 74, 8, 83, 75, 74, 8, 74, 69, 63, 68, 81, 8, 73, + 65, 68, 79, 2, 61, 72, 80, 8, 25, 22, 22, 22, 8, 1, 112, 20, 8, 7, 48, 65, 69, + 74, 65, 8, 43, 65, 79, 79, 65, 74, 6, 18, 8, 68, 61, 62, 65, 74, 8, 82, 74, 67, + 65, 66, 103, 68, 79, 8, 31, 22, 8, 11, 8, 75, 64, 65, 79, 8, 31, 30, 8, 11, 8, + 82, 74, 132, 65, 79, 65, 79, 8, 37, 110, 79, 67, 65, 79, 132, 63, 68, 61, 66, 81, 20, + 2, 31, 22, 8, 11, 18, 8, 30, 27, 8, 11, 8, 75, 64, 65, 79, 8, 31, 31, 8, 11, + 8, 82, 74, 132, 65, 79, 65, 79, 8, 37, 110, 79, 67, 65, 79, 80, 63, 68, 61, 66, 81, + 9, 8, 14, 7, 53, 81, 109, 68, 74, 65, 74, 9, 8, 36, 78, 78, 72, 61, 82, 132, 9, + 6, 15, 2, 65, 79, 66, 75, 79, 64, 65, 79, 81, 18, 8, 64, 61, 8, 84, 69, 79, 8, + 23, 28, 22, 8, 22, 22, 22, 8, 1, 112, 8, 66, 110, 79, 8, 65, 72, 65, 71, 81, 79, + 69, 132, 63, 68, 65, 8, 37, 65, 72, 65, 82, 63, 68, 4, 2, 81, 82, 74, 67, 18, 8, + 84, 65, 72, 63, 68, 65, 8, 70, 61, 8, 65, 81, 84, 61, 80, 8, 81, 65, 82, 79, 65, + 79, 8, 69, 132, 81, 18, 8, 73, 65, 68, 79, 8, 61, 82, 66, 84, 65, 74, 64, 65, 74, + 2, 73, 110, 132, 132, 65, 74, 20, 8, 14, 37, 65, 69, 8, 64, 69, 65, 132, 65, 73, 8, + 46, 61, 78, 69, 81, 65, 72, 15, 8, 65, 79, 132, 63, 68, 65, 69, 74, 81, 8, 87, 82, + 73, 8, 65, 79, 132, 81, 65, 74, 73, 75, 72, 8, 64, 61, 80, 2, 53, 63, 68, 69, 72, + 72, 65, 79, 4, 54, 68, 65, 61, 81, 65, 79, 8, 73, 69, 81, 8, 65, 69, 74, 65, 73, + 8, 40, 69, 74, 74, 61, 68, 73, 65, 62, 65, 81, 79, 61, 67, 65, 8, 83, 75, 74, 8, + 110, 62, 65, 79, 2, 30, 27, 8, 22, 22, 22, 8, 1, 112, 33, 8, 65, 80, 8, 65, 79, + 132, 63, 68, 65, 69, 74, 81, 8, 69, 74, 8, 64, 69, 65, 132, 65, 73, 8, 46, 61, 78, + 69, 81, 65, 72, 8, 75, 68, 74, 65, 8, 84, 65, 132, 65, 74, 81, 4, 2, 72, 69, 63, + 68, 65, 8, 36, 82, 80, 67, 61, 62, 65, 20, 8, 39, 61, 80, 8, 69, 132, 81, 8, 74, + 61, 81, 82, 79, 67, 65, 73, 103, 102, 18, 8, 84, 65, 69, 72, 8, 64, 69, 65, 8, 56, + 65, 79, 4, 2, 87, 69, 74, 132, 82, 74, 67, 8, 64, 65, 79, 8, 53, 82, 73, 73, 65, + 8, 70, 61, 8, 61, 82, 80, 8, 36, 74, 72, 65, 69, 68, 65, 73, 69, 81, 81, 65, 72, + 74, 8, 67, 65, 74, 75, 73, 73, 65, 74, 2, 84, 75, 79, 64, 65, 74, 8, 69, 132, 81, + 8, 82, 74, 64, 8, 64, 69, 65, 8, 56, 65, 79, 87, 69, 74, 132, 82, 74, 67, 8, 132, + 69, 63, 68, 8, 62, 65, 69, 73, 8, 46, 61, 78, 69, 81, 65, 72, 8, 58, 44, 44, 44, + 18, 2, 64, 65, 73, 8, 53, 63, 68, 82, 72, 64, 65, 74, 64, 69, 65, 74, 132, 81, 8, + 62, 65, 66, 69, 74, 64, 65, 81, 20, 8, 7, 57, 65, 74, 74, 8, 53, 69, 65, 8, 74, + 82, 74, 18, 8, 73, 20, 8, 43, 20, 18, 2, 65, 69, 74, 65, 74, 8, 37, 72, 69, 63, + 71, 8, 69, 74, 8, 64, 65, 74, 8, 40, 79, 72, 103, 82, 81, 65, 79, 82, 74, 67, 80, + 62, 65, 79, 69, 63, 68, 81, 8, 81, 82, 74, 8, 84, 75, 72, 72, 65, 74, 6, 18, 8, + 132, 75, 2, 68, 61, 62, 65, 74, 8, 84, 69, 79, 8, 44, 68, 74, 65, 74, 8, 64, 75, + 79, 81, 8, 61, 74, 8, 64, 69, 65, 132, 65, 79, 8, 53, 81, 65, 72, 72, 65, 8, 61, + 82, 80, 67, 65, 79, 65, 63, 68, 74, 65, 81, 18, 2, 64, 61, 102, 8, 64, 61, 80, 8, + 53, 63, 68, 69, 72, 72, 65, 79, 4, 54, 68, 65, 61, 81, 65, 79, 8, 69, 74, 8, 64, + 69, 65, 132, 65, 73, 8, 45, 61, 68, 79, 65, 8, 71, 65, 69, 74, 65, 74, 8, 65, 79, + 4, 2, 68, 65, 62, 72, 69, 63, 68, 65, 74, 8, 60, 82, 132, 63, 68, 82, 102, 8, 65, + 79, 66, 75, 79, 64, 65, 79, 81, 8, 3, 8, 65, 80, 8, 84, 65, 79, 64, 65, 74, 8, + 74, 82, 79, 8, 67, 65, 67, 65, 74, 2, 23, 27, 22, 22, 8, 1, 112, 8, 132, 65, 69, + 74, 8, 14, 23, 30, 18, 27, 29, 8, 11, 18, 8, 83, 69, 65, 72, 72, 65, 69, 63, 68, + 81, 8, 61, 82, 63, 68, 8, 74, 82, 79, 8, 23, 27, 18, 27, 27, 8, 11, 15, 18, 2, + 64, 61, 102, 8, 61, 62, 65, 79, 8, 61, 82, 63, 68, 8, 69, 73, 8, 74, 103, 63, 68, + 132, 81, 65, 74, 8, 45, 61, 68, 79, 65, 8, 23, 30, 31, 29, 18, 8, 23, 30, 31, 30, + 18, 8, 23, 30, 31, 31, 2, 82, 74, 64, 8, 61, 82, 63, 68, 8, 66, 110, 79, 8, 64, + 69, 65, 8, 87, 82, 71, 110, 74, 66, 72, 69, 67, 65, 74, 8, 45, 61, 68, 79, 65, 8, + 65, 69, 74, 8, 65, 79, 68, 109, 68, 81, 65, 79, 8, 60, 82, 4, 2, 132, 63, 68, 82, + 102, 8, 65, 79, 66, 75, 79, 64, 65, 79, 72, 69, 63, 68, 8, 132, 65, 69, 74, 8, 84, + 69, 79, 64, 18, 8, 84, 65, 69, 72, 8, 83, 75, 73, 8, 74, 103, 63, 68, 132, 81, 65, + 74, 8, 45, 61, 68, 79, 65, 2, 61, 62, 8, 87, 82, 73, 8, 65, 79, 132, 81, 65, 74, + 73, 61, 72, 8, 64, 69, 65, 8, 36, 73, 75, 79, 81, 69, 132, 61, 81, 69, 75, 74, 8, + 64, 65, 79, 8, 53, 82, 73, 73, 65, 8, 66, 110, 79, 2, 64, 61, 80, 8, 53, 63, 68, + 69, 72, 72, 65, 79, 4, 54, 68, 65, 61, 81, 65, 79, 8, 61, 82, 66, 87, 82, 84, 65, + 74, 64, 65, 74, 8, 69, 132, 81, 20, 8, 57, 65, 74, 74, 8, 53, 69, 65, 8, 64, 69, + 65, 132, 65, 2, 36, 73, 75, 79, 81, 69, 132, 61, 81, 69, 75, 74, 18, 8, 64, 69, 65, + 8, 24, 18, 23, 8, 11, 18, 8, 62, 65, 81, 79, 103, 67, 81, 18, 8, 62, 65, 79, 110, + 63, 71, 132, 69, 63, 68, 81, 69, 67, 65, 74, 18, 8, 132, 75, 2, 84, 69, 79, 64, 8, + 64, 69, 65, 8, 53, 81, 61, 64, 81, 67, 65, 73, 65, 69, 74, 64, 65, 8, 14, 66, 110, + 79, 8, 64, 61, 80, 8, 53, 63, 68, 69, 72, 72, 65, 79, 4, 54, 68, 65, 61, 81, 65, + 79, 15, 8, 69, 73, 2, 74, 103, 63, 68, 132, 81, 65, 74, 8, 82, 74, 64, 8, 69, 74, + 8, 64, 65, 74, 8, 66, 75, 72, 67, 65, 74, 64, 65, 74, 8, 45, 61, 68, 79, 65, 74, + 8, 82, 74, 67, 65, 66, 103, 68, 79, 2, 25, 27, 22, 22, 22, 8, 1, 112, 8, 87, 82, + 87, 82, 132, 63, 68, 69, 65, 102, 65, 74, 8, 68, 61, 62, 65, 74, 20, 2, 39, 69, 65, + 8, 61, 72, 72, 67, 65, 73, 65, 69, 74, 65, 8, 56, 65, 79, 84, 61, 72, 81, 82, 74, + 67, 8, 84, 103, 63, 68, 132, 81, 8, 69, 74, 8, 64, 65, 73, 132, 65, 72, 62, 65, 74, + 2, 48, 61, 102, 65, 18, 8, 53, 69, 65, 8, 68, 61, 62, 65, 74, 8, 64, 61, 8, 65, + 79, 68, 65, 62, 72, 69, 63, 68, 65, 8, 36, 74, 66, 75, 79, 64, 65, 79, 82, 74, 67, + 65, 74, 8, 61, 82, 66, 2, 64, 65, 73, 8, 42, 65, 62, 69, 65, 81, 65, 8, 64, 65, + 79, 8, 37, 65, 132, 75, 72, 64, 82, 74, 67, 65, 74, 18, 8, 61, 82, 66, 8, 64, 65, + 73, 8, 42, 65, 62, 69, 65, 81, 65, 8, 64, 65, 79, 2, 47, 109, 68, 74, 65, 18, 8, + 61, 82, 66, 8, 70, 65, 67, 72, 69, 63, 68, 65, 74, 8, 61, 74, 64, 65, 79, 65, 74, + 8, 42, 65, 62, 69, 65, 81, 65, 74, 18, 8, 64, 69, 65, 8, 110, 62, 65, 79, 68, 61, + 82, 78, 81, 2, 73, 69, 81, 8, 64, 65, 79, 8, 42, 65, 132, 63, 68, 103, 66, 81, 80, + 65, 79, 72, 65, 64, 69, 67, 82, 74, 67, 8, 69, 74, 8, 64, 65, 79, 8, 61, 72, 72, + 67, 65, 73, 65, 69, 74, 65, 74, 8, 56, 65, 79, 4, 2, 84, 61, 72, 81, 82, 74, 67, + 8, 87, 82, 132, 61, 73, 73, 65, 74, 68, 103, 74, 67, 65, 74, 20, 8, 53, 69, 65, 8, + 66, 69, 74, 64, 65, 74, 8, 61, 82, 63, 68, 8, 69, 74, 8, 64, 69, 65, 132, 65, 73, + 2, 45, 61, 68, 79, 65, 8, 65, 69, 74, 65, 74, 8, 37, 65, 81, 79, 61, 67, 8, 83, + 75, 74, 8, 24, 22, 22, 22, 22, 8, 1, 112, 8, 84, 69, 65, 64, 65, 79, 82, 73, 8, + 65, 69, 74, 4, 2, 67, 65, 132, 81, 65, 72, 72, 81, 8, 66, 110, 79, 8, 64, 69, 65, + 8, 71, 110, 74, 132, 81, 72, 65, 79, 69, 132, 63, 68, 65, 8, 36, 82, 80, 67, 65, 132, + 81, 61, 72, 81, 82, 74, 67, 8, 64, 65, 80, 8, 52, 61, 81, 4, 2, 68, 61, 82, 132, + 65, 80, 8, 39, 69, 65, 132, 65, 79, 8, 37, 65, 81, 79, 61, 67, 8, 68, 61, 81, 81, + 65, 8, 69, 73, 8, 83, 75, 79, 69, 67, 65, 74, 8, 82, 74, 64, 18, 8, 84, 65, 74, + 74, 2, 69, 63, 68, 8, 74, 69, 63, 68, 81, 8, 69, 79, 79, 65, 18, 8, 61, 82, 63, + 68, 8, 69, 73, 8, 83, 75, 79, 83, 75, 79, 69, 67, 65, 74, 8, 45, 61, 68, 79, 65, + 8, 84, 65, 132, 65, 74, 81, 72, 69, 63, 68, 2, 67, 65, 132, 81, 79, 69, 63, 68, 65, + 74, 8, 84, 65, 79, 64, 65, 74, 8, 73, 110, 132, 132, 65, 74, 20, 8, 57, 69, 79, 8, + 68, 61, 62, 65, 74, 8, 69, 74, 8, 64, 69, 65, 132, 65, 73, 8, 45, 61, 68, 79, 65, + 2, 84, 69, 65, 64, 65, 79, 8, 64, 69, 65, 8, 83, 75, 72, 72, 65, 8, 53, 82, 73, + 73, 65, 8, 83, 75, 74, 8, 24, 22, 22, 22, 22, 8, 1, 112, 8, 83, 75, 79, 67, 65, + 132, 65, 68, 65, 74, 20, 2, 37, 65, 69, 8, 64, 69, 65, 132, 65, 73, 8, 46, 61, 78, + 69, 81, 65, 72, 8, 69, 132, 81, 8, 64, 65, 79, 8, 39, 69, 80, 78, 75, 132, 69, 81, + 69, 75, 74, 80, 66, 75, 74, 64, 80, 8, 70, 61, 2, 69, 73, 73, 65, 79, 8, 65, 69, + 74, 8, 82, 73, 132, 81, 79, 69, 81, 81, 65, 74, 65, 79, 8, 51, 82, 74, 71, 81, 20, + 8, 40, 79, 8, 69, 132, 81, 8, 61, 82, 66, 8, 27, 27, 22, 8, 22, 22, 22, 8, 1, + 112, 18, 2, 62, 82, 79, 67, 65, 79, 8, 37, 79, 82, 63, 71, 65, 18, 8, 61, 74, 8, + 64, 61, 80, 8, 57, 75, 68, 74, 68, 61, 82, 80, 8, 57, 75, 79, 73, 132, 65, 79, 132, + 81, 79, 61, 102, 65, 8, 23, 23, 18, 2, 75, 74, 8, 64, 61, 80, 8, 53, 63, 68, 69, + 72, 72, 65, 79, 19, 54, 68, 65, 61, 81, 65, 79, 18, 8, 61, 74, 8, 64, 69, 65, 8, + 40, 79, 84, 65, 69, 81, 65, 79, 82, 74, 67, 80, 62, 61, 82, 81, 65, 74, 2, 61, 73, + 8, 46, 79, 61, 74, 71, 65, 74, 68, 61, 82, 80, 18, 8, 61, 74, 8, 64, 69, 65, 8, + 46, 61, 69, 132, 65, 79, 64, 61, 73, 73, 62, 79, 110, 63, 71, 65, 20, 2, 40, 69, 74, + 8, 51, 82, 74, 71, 81, 8, 69, 132, 81, 8, 62, 65, 69, 73, 8, 46, 61, 78, 69, 81, + 65, 72, 8, 7, 53, 63, 68, 82, 72, 64, 65, 74, 83, 65, 79, 84, 61, 72, 81, 82, 74, + 67, 6, 2, 87, 82, 8, 65, 79, 84, 103, 68, 74, 65, 74, 18, 8, 64, 65, 79, 8, 66, + 110, 79, 8, 53, 69, 65, 8, 64, 65, 74, 8, 53, 63, 68, 72, 82, 102, 8, 87, 82, 72, + 103, 102, 81, 18, 8, 64, 61, 102, 2, 84, 69, 79, 8, 82, 74, 80, 8, 83, 75, 74, 8, + 74, 65, 82, 65, 73, 8, 14, 73, 69, 81, 8, 36, 74, 72, 65, 69, 68, 65, 67, 65, 64, + 61, 74, 71, 65, 74, 15, 8, 81, 79, 61, 67, 65, 74, 2, 73, 110, 132, 132, 65, 74, 20, + 8, 53, 69, 65, 8, 66, 69, 74, 64, 65, 74, 8, 74, 103, 73, 72, 69, 63, 68, 8, 30, + 22, 8, 22, 22, 22, 8, 66, 110, 79, 8, 64, 65, 74, 8, 39, 79, 82, 63, 71, 2, 82, + 74, 64, 8, 64, 65, 74, 8, 53, 81, 65, 73, 78, 65, 72, 8, 65, 69, 74, 65, 79, 8, + 74, 65, 82, 65, 74, 8, 36, 74, 72, 65, 69, 68, 65, 18, 8, 64, 69, 65, 8, 132, 69, + 63, 68, 8, 69, 74, 2, 37, 65, 61, 79, 62, 65, 69, 81, 82, 74, 67, 8, 62, 65, 132, + 69, 74, 64, 65, 81, 18, 8, 65, 69, 74, 67, 65, 132, 81, 65, 72, 72, 81, 8, 82, 74, + 64, 8, 84, 75, 79, 110, 62, 65, 79, 8, 53, 69, 65, 2, 65, 69, 74, 65, 8, 62, 65, + 132, 75, 74, 64, 65, 79, 65, 8, 56, 75, 79, 72, 61, 67, 65, 8, 69, 74, 8, 74, 103, + 63, 68, 132, 81, 65, 79, 8, 75, 64, 65, 79, 8, 69, 74, 8, 66, 65, 79, 74, 65, 79, + 65, 79, 2, 60, 65, 69, 81, 8, 65, 79, 68, 61, 72, 81, 65, 74, 8, 84, 65, 79, 64, + 65, 74, 20, 2, 26, 23, 22, 22, 8, 1, 112, 18, 8, 27, 30, 28, 24, 8, 1, 112, 18, + 8, 27, 25, 24, 27, 8, 1, 112, 18, 8, 29, 30, 8, 1, 112, 20, 8, 31, 30, 27, 18, + 8, 27, 24, 24, 18, 8, 23, 24, 22, 22, 8, 1, 112, 20, 2, 23, 30, 22, 23, 18, 8, + 23, 25, 22, 24, 18, 8, 23, 30, 22, 25, 18, 8, 14, 23, 30, 22, 26, 18, 8, 23, 30, + 22, 27, 18, 8, 23, 22, 22, 28, 15, 18, 8, 23, 27, 22, 29, 18, 8, 23, 30, 22, 30, + 18, 8, 23, 30, 22, 31, 18, 8, 23, 30, 23, 22, 18, 2, 23, 30, 23, 23, 18, 8, 23, + 27, 23, 24, 18, 8, 23, 30, 23, 25, 18, 8, 23, 30, 23, 26, 18, 8, 23, 30, 23, 27, + 18, 8, 23, 30, 23, 28, 18, 8, 23, 27, 23, 29, 18, 8, 23, 30, 23, 30, 18, 8, 23, + 26, 23, 31, 18, 8, 23, 28, 24, 22, 18, 2, 23, 26, 24, 23, 18, 8, 23, 30, 24, 24, + 18, 8, 23, 27, 24, 25, 8, 1, 112, 18, 8, 23, 24, 24, 26, 18, 8, 23, 30, 24, 27, + 18, 8, 23, 30, 24, 28, 18, 8, 23, 26, 24, 29, 18, 8, 23, 26, 24, 30, 18, 8, 23, + 30, 24, 31, 8, 1, 112, 18, 8, 23, 28, 25, 22, 18, 2, 23, 26, 25, 23, 18, 8, 23, + 25, 25, 24, 18, 8, 23, 30, 25, 25, 18, 8, 23, 30, 25, 26, 18, 8, 23, 30, 25, 27, + 18, 8, 23, 30, 25, 28, 18, 8, 23, 30, 25, 29, 18, 8, 23, 28, 25, 30, 8, 1, 112, + 18, 8, 23, 28, 25, 31, 18, 8, 23, 30, 26, 22, 18, 2, 23, 30, 26, 23, 18, 8, 23, + 25, 26, 24, 18, 8, 23, 30, 26, 25, 18, 8, 23, 22, 30, 26, 26, 18, 8, 23, 28, 26, + 27, 8, 1, 112, 18, 8, 23, 30, 26, 28, 18, 8, 23, 30, 26, 29, 18, 8, 23, 30, 26, + 30, 18, 8, 23, 30, 26, 31, 18, 8, 23, 30, 27, 22, 18, 2, 23, 30, 27, 23, 18, 8, + 23, 22, 27, 24, 18, 8, 23, 25, 27, 25, 18, 8, 23, 22, 27, 26, 18, 8, 23, 30, 27, + 27, 18, 8, 23, 30, 27, 28, 18, 8, 23, 30, 27, 29, 18, 8, 23, 30, 27, 30, 18, 8, + 23, 30, 27, 31, 18, 8, 23, 29, 28, 22, 18, 2, 23, 26, 28, 23, 18, 8, 23, 22, 28, + 24, 18, 8, 23, 30, 28, 25, 18, 8, 23, 30, 28, 26, 18, 8, 23, 30, 28, 27, 18, 8, + 23, 30, 28, 28, 18, 8, 23, 28, 28, 29, 18, 8, 23, 29, 28, 30, 18, 8, 23, 29, 28, + 31, 18, 8, 23, 30, 29, 22, 18, 2, 23, 22, 29, 23, 8, 1, 112, 18, 8, 23, 30, 29, + 24, 8, 1, 112, 18, 8, 23, 30, 29, 25, 18, 8, 23, 30, 29, 26, 18, 8, 23, 29, 29, + 27, 18, 8, 23, 22, 29, 28, 18, 8, 23, 30, 29, 29, 18, 8, 23, 30, 29, 30, 18, 8, + 23, 30, 29, 31, 18, 8, 23, 30, 30, 22, 18, 2, 23, 25, 30, 23, 18, 8, 23, 24, 30, + 24, 18, 8, 23, 30, 30, 25, 18, 8, 23, 30, 30, 26, 18, 8, 23, 30, 30, 27, 18, 8, + 23, 22, 22, 28, 18, 8, 23, 25, 30, 29, 18, 8, 23, 30, 30, 30, 18, 8, 23, 30, 30, + 31, 18, 8, 23, 30, 31, 22, 18, 2, 23, 30, 31, 23, 18, 8, 23, 24, 31, 24, 18, 8, + 23, 26, 31, 25, 18, 8, 23, 30, 31, 26, 8, 1, 112, 18, 8, 23, 22, 31, 27, 18, 8, + 23, 30, 31, 28, 18, 8, 23, 30, 31, 29, 18, 8, 23, 30, 31, 30, 18, 8, 23, 30, 31, + 31, 18, 8, 23, 31, 22, 22, 8, 1, 112, 18, 2, 54, 65, 82, 65, 79, 82, 74, 67, 80, + 87, 82, 72, 61, 67, 65, 8, 61, 62, 67, 65, 72, 65, 68, 74, 81, 8, 68, 61, 81, 20, + 8, 14, 40, 69, 74, 73, 61, 72, 8, 65, 79, 71, 72, 103, 79, 81, 8, 64, 65, 79, 2, + 48, 61, 67, 69, 132, 81, 79, 61, 81, 18, 8, 64, 61, 102, 8, 64, 69, 65, 8, 54, 65, + 82, 65, 79, 82, 74, 67, 8, 70, 61, 8, 64, 75, 63, 68, 8, 62, 61, 72, 64, 8, 83, + 75, 79, 110, 62, 65, 79, 4, 2, 67, 65, 68, 65, 74, 8, 84, 69, 79, 64, 33, 15, 8, + 64, 61, 74, 74, 8, 84, 69, 65, 64, 65, 79, 8, 71, 61, 74, 74, 8, 65, 79, 8, 132, + 69, 63, 68, 8, 64, 65, 79, 8, 48, 65, 69, 74, 82, 74, 67, 2, 44, 63, 68, 8, 68, + 103, 81, 81, 65, 8, 64, 61, 74, 74, 8, 74, 75, 63, 68, 8, 71, 82, 79, 87, 8, 64, + 61, 80, 8, 46, 61, 78, 69, 81, 65, 72, 8, 64, 65, 79, 8, 83, 65, 79, 4, 2, 132, + 63, 68, 69, 65, 64, 65, 74, 65, 74, 8, 40, 69, 74, 74, 61, 68, 73, 65, 74, 8, 82, + 74, 64, 8, 36, 82, 80, 67, 61, 62, 65, 74, 8, 87, 82, 8, 65, 79, 84, 103, 68, 74, + 65, 74, 2, 82, 74, 64, 8, 73, 109, 63, 68, 81, 65, 8, 64, 61, 8, 74, 82, 79, 8, + 65, 69, 74, 65, 74, 8, 51, 82, 74, 71, 81, 8, 68, 65, 79, 61, 82, 80, 67, 79, 65, + 69, 66, 65, 74, 32, 8, 64, 61, 80, 2, 132, 69, 74, 64, 8, 64, 69, 65, 8, 25, 24, + 22, 22, 8, 1, 112, 20, 8, 39, 69, 65, 8, 51, 79, 75, 83, 69, 74, 87, 69, 61, 72, + 61, 62, 67, 61, 62, 65, 74, 2, 132, 69, 74, 64, 8, 69, 74, 8, 64, 69, 65, 132, 65, + 73, 8, 45, 61, 68, 79, 65, 8, 132, 65, 68, 79, 8, 65, 79, 68, 65, 62, 72, 69, 63, + 68, 8, 67, 65, 132, 81, 69, 65, 67, 65, 74, 20, 8, 57, 69, 79, 2, 68, 61, 62, 65, + 74, 8, 23, 23, 22, 8, 22, 22, 22, 8, 1, 112, 8, 67, 65, 67, 65, 74, 8, 64, 61, + 80, 8, 56, 75, 79, 132, 61, 68, 79, 8, 73, 65, 68, 79, 8, 65, 69, 74, 67, 65, 4, + 2, 132, 81, 65, 72, 72, 81, 20, 8, 36, 62, 65, 79, 18, 8, 73, 65, 69, 74, 65, 8, + 43, 65, 79, 79, 65, 74, 18, 8, 84, 69, 79, 8, 84, 65, 79, 64, 65, 74, 8, 73, 69, + 81, 8, 64, 69, 65, 132, 65, 73, 2, 37, 65, 81, 79, 61, 67, 65, 8, 74, 69, 63, 68, + 81, 8, 79, 65, 69, 63, 68, 65, 74, 18, 8, 84, 69, 79, 8, 84, 65, 79, 64, 65, 74, + 8, 74, 75, 63, 68, 8, 28, 27, 8, 22, 22, 22, 8, 1, 112, 2, 87, 82, 72, 65, 67, + 65, 74, 8, 73, 110, 132, 132, 65, 74, 20, 8, 40, 80, 8, 69, 132, 81, 8, 64, 61, 80, + 8, 65, 69, 74, 73, 61, 72, 8, 64, 61, 64, 82, 79, 63, 68, 8, 68, 65, 79, 62, 65, + 69, 4, 2, 67, 65, 66, 110, 68, 79, 81, 8, 84, 75, 79, 64, 65, 74, 18, 8, 64, 61, + 102, 8, 65, 69, 74, 65, 8, 74, 65, 82, 65, 8, 37, 65, 79, 65, 63, 68, 74, 82, 74, + 67, 8, 65, 69, 74, 67, 65, 81, 79, 65, 81, 65, 74, 2, 69, 132, 81, 18, 8, 82, 74, + 64, 8, 87, 84, 65, 69, 81, 65, 74, 80, 8, 64, 61, 64, 82, 79, 63, 68, 18, 8, 64, + 61, 102, 8, 64, 69, 65, 8, 38, 68, 61, 82, 132, 132, 65, 65, 62, 61, 82, 78, 79, 103, + 73, 69, 65, 74, 2, 69, 74, 8, 64, 65, 79, 8, 51, 79, 75, 83, 69, 74, 87, 8, 84, + 65, 132, 65, 74, 81, 72, 69, 63, 68, 8, 65, 79, 68, 109, 68, 81, 8, 84, 75, 79, 64, + 65, 74, 8, 132, 69, 74, 64, 8, 82, 74, 64, 2, 84, 69, 79, 8, 69, 74, 66, 75, 72, + 67, 65, 64, 65, 132, 132, 65, 74, 8, 65, 79, 68, 109, 68, 81, 65, 8, 37, 65, 69, 81, + 79, 103, 67, 65, 8, 87, 82, 8, 87, 61, 68, 72, 65, 74, 8, 68, 61, 62, 65, 74, 20, + 2, 39, 69, 65, 8, 36, 74, 64, 65, 79, 82, 74, 67, 8, 64, 65, 79, 8, 37, 65, 79, + 65, 63, 68, 74, 82, 74, 67, 8, 69, 132, 81, 8, 64, 61, 68, 69, 74, 8, 65, 79, 66, + 75, 72, 67, 81, 18, 8, 64, 61, 102, 18, 2, 84, 103, 68, 79, 65, 74, 64, 8, 69, 74, + 8, 66, 79, 110, 68, 65, 79, 65, 74, 8, 45, 61, 68, 79, 65, 74, 8, 64, 61, 80, 8, + 53, 81, 65, 82, 65, 79, 132, 75, 72, 72, 8, 14, 64, 65, 80, 8, 61, 62, 4, 2, 67, + 65, 72, 61, 82, 66, 65, 74, 65, 74, 8, 52, 65, 63, 68, 74, 82, 74, 67, 80, 70, 61, + 68, 79, 65, 80, 15, 8, 64, 65, 66, 69, 74, 69, 81, 69, 83, 8, 61, 72, 80, 8, 42, + 79, 82, 74, 64, 72, 61, 67, 65, 2, 132, 65, 74, 75, 73, 73, 65, 74, 8, 84, 82, 79, + 64, 65, 18, 8, 70, 65, 81, 87, 81, 8, 64, 61, 80, 8, 53, 81, 65, 82, 65, 79, 132, + 75, 72, 72, 18, 8, 64, 61, 80, 8, 61, 73, 8, 23, 20, 2, 73, 82, 61, 79, 8, 83, + 75, 79, 8, 64, 65, 73, 8, 40, 81, 61, 81, 80, 70, 61, 68, 79, 65, 8, 83, 75, 79, + 68, 61, 74, 64, 65, 74, 8, 69, 132, 81, 18, 8, 87, 82, 8, 42, 79, 82, 74, 64, 65, + 2, 67, 65, 72, 65, 67, 81, 8, 84, 69, 79, 64, 20, 8, 57, 103, 68, 79, 65, 74, 64, + 8, 61, 72, 132, 75, 8, 66, 110, 79, 8, 64, 61, 80, 8, 45, 61, 68, 79, 8, 23, 31, + 22, 29, 8, 62, 65, 69, 2, 64, 65, 73, 8, 61, 72, 81, 65, 74, 8, 48, 75, 64, 82, + 80, 8, 74, 61, 63, 68, 8, 64, 65, 73, 8, 53, 81, 65, 82, 65, 79, 132, 75, 72, 72, + 8, 14, 83, 75, 73, 8, 25, 23, 20, 8, 48, 103, 79, 87, 2, 23, 31, 22, 30, 15, 8, + 67, 65, 79, 65, 63, 68, 74, 65, 81, 8, 84, 75, 79, 64, 65, 74, 8, 84, 103, 79, 65, + 18, 8, 84, 69, 79, 64, 8, 69, 74, 8, 64, 69, 65, 132, 65, 73, 8, 45, 61, 68, 79, + 65, 2, 74, 61, 63, 68, 8, 64, 65, 73, 8, 53, 81, 61, 81, 82, 80, 8, 83, 75, 73, + 8, 23, 20, 8, 45, 61, 74, 82, 61, 79, 8, 23, 31, 22, 29, 8, 67, 65, 79, 65, 63, + 68, 74, 65, 81, 20, 2, 39, 61, 64, 82, 79, 63, 68, 8, 84, 69, 79, 64, 8, 132, 63, + 68, 75, 74, 8, 14, 75, 68, 74, 65, 8, 84, 65, 69, 81, 65, 79, 65, 80, 15, 8, 65, + 69, 74, 65, 8, 40, 79, 68, 109, 68, 82, 74, 67, 8, 64, 65, 80, 2, 51, 79, 75, 87, + 65, 74, 81, 132, 61, 81, 87, 65, 80, 8, 62, 65, 64, 69, 74, 67, 81, 18, 8, 84, 65, + 69, 72, 8, 70, 61, 8, 69, 73, 8, 74, 103, 63, 68, 132, 81, 65, 74, 8, 45, 61, 68, + 79, 65, 8, 75, 68, 74, 65, 2, 46, 8, 64, 61, 80, 8, 53, 81, 65, 82, 65, 79, 132, + 75, 72, 72, 8, 27, 26, 8, 11, 8, 74, 75, 63, 68, 8, 67, 65, 84, 61, 63, 68, 132, + 65, 74, 8, 132, 65, 69, 74, 8, 84, 110, 79, 64, 65, 20, 2, 72, 82, 102, 65, 79, 64, + 65, 73, 8, 132, 69, 74, 64, 8, 61, 62, 65, 79, 8, 74, 75, 63, 68, 8, 64, 69, 65, + 8, 51, 79, 75, 83, 69, 74, 87, 69, 61, 72, 61, 62, 67, 61, 62, 65, 74, 8, 65, 79, + 4, 2, 36, 82, 63, 68, 8, 64, 69, 65, 80, 8, 65, 73, 78, 66, 69, 65, 68, 72, 81, + 8, 132, 69, 63, 68, 8, 67, 61, 74, 87, 8, 83, 75, 74, 8, 132, 65, 72, 81, 132, 81, + 20, 8, 37, 65, 69, 2, 64, 65, 74, 8, 68, 65, 82, 81, 69, 67, 65, 74, 8, 67, 65, + 132, 81, 65, 69, 67, 65, 79, 81, 65, 74, 8, 36, 74, 132, 78, 79, 110, 63, 68, 65, 74, + 8, 61, 74, 8, 70, 82, 74, 67, 65, 8, 47, 65, 82, 81, 65, 18, 2, 64, 69, 65, 8, + 69, 73, 8, 37, 82, 63, 68, 68, 61, 72, 81, 65, 79, 132, 81, 61, 74, 64, 65, 18, 8, + 110, 62, 65, 79, 68, 61, 82, 78, 81, 8, 69, 73, 8, 46, 61, 82, 66, 73, 61, 74, 74, + 80, 66, 61, 63, 68, 2, 66, 75, 79, 81, 71, 75, 73, 73, 65, 74, 8, 84, 75, 72, 72, + 65, 74, 18, 8, 84, 69, 79, 64, 8, 64, 69, 65, 8, 46, 65, 74, 74, 81, 74, 69, 80, + 8, 64, 65, 79, 8, 53, 63, 68, 79, 65, 69, 62, 4, 2, 73, 61, 132, 63, 68, 69, 74, + 65, 74, 81, 65, 63, 68, 74, 69, 71, 8, 75, 68, 74, 65, 8, 84, 65, 69, 81, 65, 79, + 65, 80, 8, 83, 65, 79, 72, 61, 74, 67, 81, 20, 8, 44, 63, 68, 8, 62, 69, 81, 81, + 65, 2, 53, 69, 65, 18, 8, 61, 82, 63, 68, 8, 64, 69, 65, 132, 65, 73, 8, 36, 74, + 81, 79, 61, 67, 65, 8, 87, 82, 87, 82, 132, 81, 69, 73, 73, 65, 74, 20, 2, 44, 73, + 8, 40, 81, 61, 81, 80, 61, 82, 80, 132, 63, 68, 82, 102, 8, 84, 82, 79, 64, 65, 8, + 66, 65, 79, 74, 65, 79, 8, 61, 74, 67, 65, 79, 65, 67, 81, 18, 8, 69, 74, 2, 64, + 65, 79, 8, 37, 65, 132, 75, 72, 64, 82, 74, 67, 80, 64, 69, 65, 74, 132, 81, 75, 79, + 64, 74, 82, 74, 67, 8, 14, 69, 74, 8, 46, 61, 78, 69, 81, 65, 72, 8, 44, 44, 44, + 18, 8, 91, 8, 31, 15, 8, 64, 69, 65, 2, 53, 65, 71, 79, 65, 81, 61, 79, 69, 61, + 81, 80, 132, 81, 65, 72, 72, 65, 8, 66, 110, 79, 8, 64, 69, 65, 8, 46, 82, 74, 132, + 81, 67, 65, 84, 65, 79, 62, 65, 18, 8, 82, 74, 64, 8, 43, 61, 74, 64, 4, 2, 84, + 65, 79, 71, 65, 79, 132, 63, 68, 82, 72, 65, 8, 73, 69, 81, 8, 64, 65, 74, 8, 53, + 81, 65, 72, 72, 65, 74, 8, 64, 65, 79, 8, 67, 65, 78, 79, 110, 66, 81, 65, 74, 8, + 53, 65, 71, 79, 65, 81, 103, 79, 65, 2, 67, 72, 65, 69, 63, 68, 87, 82, 66, 81, 65, + 72, 72, 65, 74, 20, 8, 39, 69, 65, 132, 65, 79, 8, 36, 74, 81, 79, 61, 67, 8, 25, + 25, 18, 25, 8, 11, 8, 69, 132, 81, 8, 61, 62, 65, 79, 8, 83, 75, 73, 8, 40, 81, + 61, 81, 80, 4, 2, 61, 82, 80, 132, 63, 68, 82, 102, 8, 61, 62, 67, 65, 72, 65, 68, + 74, 81, 8, 84, 75, 79, 64, 65, 74, 20, 2, 14, 53, 81, 61, 64, 81, 132, 63, 68, 82, + 72, 79, 61, 81, 8, 39, 79, 20, 8, 49, 65, 82, 66, 65, 79, 81, 15, 32, 8, 7, 39, + 65, 79, 8, 42, 65, 64, 61, 74, 71, 65, 18, 8, 69, 74, 2, 64, 65, 79, 8, 41, 75, + 79, 81, 62, 69, 72, 64, 82, 74, 67, 80, 132, 63, 68, 82, 72, 65, 6, 18, 8, 82, 74, + 64, 8, 87, 84, 61, 79, 8, 69, 74, 8, 64, 65, 79, 8, 41, 75, 79, 81, 4, 2, 62, + 69, 72, 64, 82, 74, 67, 80, 132, 63, 68, 82, 72, 65, 8, 66, 110, 79, 8, 73, 103, 74, + 74, 72, 69, 63, 68, 65, 8, 51, 65, 79, 132, 75, 74, 65, 74, 18, 8, 37, 110, 79, 67, + 65, 79, 71, 82, 74, 64, 65, 2, 82, 74, 64, 8, 47, 65, 62, 65, 74, 80, 71, 82, 74, + 64, 65, 8, 65, 69, 74, 87, 82, 66, 110, 68, 79, 65, 74, 18, 8, 65, 74, 81, 132, 78, + 79, 69, 63, 68, 81, 8, 64, 82, 79, 63, 68, 61, 82, 80, 2, 64, 65, 74, 8, 40, 79, + 84, 103, 67, 82, 74, 67, 65, 74, 18, 8, 64, 69, 65, 8, 69, 73, 8, 7, 48, 61, 67, + 69, 132, 81, 79, 61, 81, 6, 8, 132, 63, 68, 75, 74, 8, 132, 65, 69, 81, 8, 72, 61, + 74, 67, 65, 79, 2, 60, 65, 69, 81, 8, 67, 65, 78, 66, 72, 75, 67, 65, 74, 8, 84, + 75, 79, 64, 65, 74, 8, 132, 69, 74, 64, 20, 8, 40, 80, 8, 132, 69, 74, 64, 8, 62, + 69, 80, 68, 65, 79, 8, 132, 63, 68, 75, 74, 2, 46, 61, 78, 69, 81, 65, 72, 8, 61, + 82, 80, 8, 64, 69, 65, 132, 65, 74, 8, 42, 65, 62, 69, 65, 81, 65, 74, 8, 69, 73, + 8, 64, 65, 82, 81, 132, 63, 68, 65, 74, 8, 55, 74, 81, 65, 79, 79, 69, 63, 68, 81, + 2, 73, 69, 81, 8, 64, 82, 79, 63, 68, 67, 65, 74, 75, 73, 73, 65, 74, 8, 84, 75, + 79, 64, 65, 74, 18, 8, 82, 74, 64, 8, 83, 75, 79, 8, 65, 69, 74, 65, 73, 8, 45, + 61, 68, 79, 65, 2, 81, 79, 61, 81, 65, 74, 8, 84, 69, 79, 8, 64, 65, 73, 8, 42, + 65, 64, 61, 74, 71, 65, 74, 8, 74, 61, 68, 65, 18, 8, 65, 69, 74, 65, 74, 8, 84, + 61, 68, 72, 66, 79, 65, 69, 65, 74, 2, 46, 82, 79, 132, 82, 80, 8, 61, 82, 80, 132, + 63, 68, 72, 69, 65, 102, 72, 69, 63, 68, 8, 66, 110, 79, 8, 37, 110, 79, 67, 65, 79, + 4, 8, 82, 74, 64, 8, 47, 65, 62, 65, 74, 80, 71, 82, 74, 64, 65, 2, 65, 69, 74, + 87, 82, 66, 110, 68, 79, 65, 74, 20, 8, 40, 80, 8, 69, 132, 81, 8, 64, 61, 73, 61, + 72, 80, 8, 64, 65, 79, 8, 7, 39, 69, 79, 65, 71, 81, 75, 79, 8, 64, 65, 79, 8, + 36, 74, 132, 81, 61, 72, 81, 6, 2, 62, 65, 61, 82, 66, 81, 79, 61, 67, 81, 8, 84, + 75, 79, 64, 65, 74, 18, 8, 65, 69, 74, 65, 74, 8, 47, 65, 68, 79, 78, 72, 61, 74, + 8, 64, 61, 66, 110, 79, 8, 61, 82, 80, 87, 82, 61, 79, 62, 65, 69, 81, 65, 74, 20, + 2, 39, 65, 79, 132, 65, 72, 62, 65, 8, 72, 69, 65, 67, 81, 8, 132, 65, 69, 81, 8, + 71, 82, 79, 87, 65, 73, 8, 69, 74, 8, 64, 65, 74, 8, 84, 65, 132, 65, 74, 81, 72, + 69, 63, 68, 65, 74, 8, 40, 72, 65, 73, 65, 74, 81, 65, 74, 2, 83, 75, 79, 18, 8, + 82, 74, 64, 8, 69, 63, 68, 8, 67, 72, 61, 82, 62, 65, 8, 74, 69, 63, 68, 81, 18, + 8, 64, 61, 102, 8, 83, 75, 74, 8, 132, 65, 69, 81, 65, 74, 8, 64, 65, 80, 8, 23, + 24, 27, 22, 8, 1, 112, 2, 29, 25, 28, 18, 25, 30, 8, 1, 112, 18, 8, 28, 22, 22, + 8, 1, 112, 18, 8, 29, 25, 28, 31, 8, 1, 112, 8, 82, 74, 64, 8, 84, 65, 69, 81, + 65, 79, 8, 30, 22, 22, 8, 1, 112, 18, 8, 24, 27, 22, 22, 18, 25, 22, 8, 1, 112, + 2, 14, 91, 8, 23, 8, 62, 69, 80, 8, 23, 23, 18, 8, 91, 8, 23, 24, 8, 82, 74, + 64, 8, 91, 8, 30, 31, 15, 2, 48, 61, 67, 69, 132, 81, 79, 61, 81, 80, 8, 65, 81, + 84, 61, 80, 8, 64, 61, 67, 65, 67, 65, 74, 8, 65, 69, 74, 67, 65, 84, 65, 74, 64, + 65, 81, 8, 84, 65, 79, 64, 65, 74, 8, 71, 109, 74, 74, 81, 65, 20, 8, 30, 31, 31, + 8, 1, 112, 2, 36, 82, 63, 68, 8, 69, 74, 8, 64, 65, 79, 8, 41, 75, 79, 81, 62, + 69, 72, 64, 82, 74, 67, 80, 132, 63, 68, 82, 72, 65, 8, 66, 110, 79, 8, 48, 103, 64, + 63, 68, 65, 74, 8, 68, 61, 62, 65, 74, 2, 84, 69, 79, 8, 62, 65, 79, 65, 69, 81, + 80, 8, 55, 74, 81, 65, 79, 79, 69, 63, 68, 81, 8, 69, 74, 8, 7, 37, 110, 79, 67, + 65, 79, 4, 8, 82, 74, 64, 8, 47, 65, 62, 65, 74, 80, 71, 82, 74, 64, 65, 6, 2, + 65, 69, 74, 67, 65, 66, 110, 68, 79, 81, 20, 8, 43, 69, 65, 79, 8, 68, 61, 62, 65, + 74, 8, 84, 69, 79, 8, 132, 69, 65, 8, 75, 79, 67, 61, 74, 69, 132, 63, 68, 8, 73, + 69, 81, 8, 64, 65, 73, 2, 64, 65, 82, 81, 132, 63, 68, 65, 74, 8, 55, 74, 81, 65, + 79, 79, 69, 63, 68, 81, 8, 87, 82, 8, 83, 65, 79, 62, 69, 74, 64, 65, 74, 8, 67, + 65, 132, 82, 63, 68, 81, 18, 8, 64, 65, 79, 8, 69, 74, 2, 65, 81, 84, 61, 80, 8, + 61, 74, 64, 65, 79, 65, 79, 8, 57, 65, 69, 132, 65, 18, 8, 84, 65, 74, 69, 67, 65, + 79, 8, 61, 62, 68, 103, 74, 67, 69, 67, 8, 83, 75, 74, 8, 64, 65, 79, 2, 57, 65, + 74, 74, 8, 61, 72, 132, 75, 8, 55, 74, 81, 65, 79, 79, 69, 63, 68, 81, 8, 69, 73, + 8, 7, 48, 61, 132, 63, 68, 69, 74, 65, 74, 4, 2, 132, 63, 68, 79, 65, 69, 62, 65, + 74, 6, 8, 65, 79, 81, 65, 69, 72, 81, 8, 84, 65, 79, 64, 65, 74, 8, 132, 75, 72, + 72, 18, 8, 64, 61, 74, 74, 8, 73, 82, 102, 8, 61, 82, 63, 68, 2, 42, 65, 72, 65, + 67, 65, 74, 68, 65, 69, 81, 8, 83, 75, 79, 68, 61, 74, 64, 65, 74, 8, 132, 65, 69, + 74, 18, 8, 67, 72, 65, 69, 63, 68, 87, 65, 69, 81, 69, 67, 8, 73, 69, 74, 64, 65, + 132, 81, 65, 74, 80, 2, 23, 22, 8, 11, 8, 64, 65, 79, 8, 53, 63, 68, 110, 72, 65, + 79, 8, 87, 82, 8, 62, 65, 132, 63, 68, 103, 66, 81, 69, 67, 65, 74, 33, 8, 132, 75, + 74, 132, 81, 8, 69, 132, 81, 8, 65, 80, 8, 87, 82, 8, 81, 65, 82, 65, 79, 20, 2, + 57, 65, 74, 74, 8, 53, 69, 65, 8, 61, 62, 65, 79, 8, 73, 65, 69, 74, 65, 74, 8, + 132, 75, 72, 72, 81, 65, 74, 18, 8, 64, 61, 102, 8, 64, 69, 65, 132, 65, 2, 48, 61, + 132, 63, 68, 69, 74, 65, 74, 8, 74, 82, 79, 8, 61, 82, 66, 67, 65, 132, 81, 65, 72, + 72, 81, 8, 84, 65, 79, 64, 65, 74, 8, 132, 75, 72, 72, 65, 74, 18, 8, 64, 61, 73, + 69, 81, 2, 68, 69, 65, 79, 8, 82, 74, 64, 8, 64, 61, 8, 73, 61, 72, 8, 70, 65, + 73, 61, 74, 64, 8, 64, 61, 79, 61, 82, 66, 8, 110, 62, 81, 18, 8, 132, 75, 8, 68, + 61, 62, 65, 8, 69, 63, 68, 2, 61, 82, 63, 68, 8, 64, 61, 67, 65, 67, 65, 74, 8, + 72, 65, 62, 68, 61, 66, 81, 65, 8, 37, 65, 64, 65, 74, 71, 65, 74, 20, 8, 40, 69, + 74, 8, 132, 75, 72, 63, 68, 65, 80, 8, 101, 62, 65, 74, 2, 61, 82, 66, 8, 65, 69, + 74, 65, 79, 8, 71, 75, 132, 81, 132, 78, 69, 65, 72, 69, 67, 65, 74, 8, 48, 61, 132, + 63, 68, 69, 74, 65, 8, 14, 66, 110, 79, 8, 23, 27, 8, 62, 69, 80, 8, 23, 28, 70, + 103, 68, 79, 69, 67, 65, 15, 2, 79, 82, 74, 64, 8, 25, 22, 8, 11, 18, 8, 73, 61, + 74, 63, 68, 73, 61, 72, 8, 61, 62, 65, 79, 8, 61, 82, 63, 68, 8, 74, 69, 63, 68, + 81, 8, 84, 65, 74, 69, 67, 65, 79, 8, 61, 72, 132, 8, 24, 26, 18, 25, 25, 8, 11, + 2, 83, 75, 74, 8, 25, 31, 31, 31, 8, 1, 112, 18, 8, 65, 79, 67, 65, 62, 65, 74, + 8, 26, 22, 22, 22, 8, 1, 112, 20, 8, 39, 61, 79, 110, 62, 65, 79, 8, 80, 69, 74, + 64, 8, 87, 82, 8, 74, 65, 74, 74, 65, 74, 8, 27, 22, 8, 11, 2, 64, 65, 79, 8, + 48, 103, 64, 63, 68, 65, 74, 18, 8, 24, 25, 8, 11, 8, 64, 65, 79, 8, 45, 110, 74, + 67, 72, 69, 74, 67, 65, 18, 8, 67, 61, 79, 8, 29, 27, 18, 27, 8, 11, 8, 64, 65, + 79, 8, 36, 64, 72, 69, 67, 65, 74, 2, 82, 74, 64, 8, 73, 65, 68, 79, 8, 79, 82, + 74, 64, 8, 30, 30, 18, 27, 26, 8, 11, 8, 64, 65, 79, 8, 51, 66, 65, 79, 64, 65, + 20, 8, 36, 82, 63, 68, 8, 23, 24, 8, 11, 8, 64, 65, 79, 8, 59, 61, 63, 68, 81, + 65, 74, 8, 69, 73, 8, 59, 65, 73, 65, 74, 18, 2, 29, 28, 8, 11, 8, 64, 65, 79, + 8, 49, 65, 84, 8, 59, 75, 79, 71, 65, 79, 18, 8, 23, 23, 8, 11, 8, 83, 75, 73, + 8, 59, 65, 79, 65, 84, 61, 74, 18, 8, 61, 82, 63, 68, 8, 61, 82, 80, 8, 59, 78, + 65, 79, 74, 8, 71, 61, 73, 65, 74, 2, 26, 26, 8, 11, 18, 8, 84, 75, 62, 65, 69, + 8, 26, 22, 18, 29, 27, 8, 11, 8, 64, 65, 79, 8, 48, 69, 81, 67, 72, 69, 65, 64, + 65, 79, 8, 83, 65, 79, 84, 61, 74, 64, 81, 8, 62, 72, 69, 65, 62, 65, 74, 18, 8, + 64, 75, 63, 68, 8, 25, 25, 18, 31, 30, 8, 11, 2, 62, 69, 80, 8, 87, 82, 8, 26, + 29, 18, 30, 30, 8, 11, 8, 68, 61, 81, 81, 65, 74, 8, 42, 65, 64, 82, 72, 64, 20, + 8, 48, 65, 68, 79, 8, 59, 65, 74, 8, 61, 72, 80, 8, 59, 69, 74, 67, 8, 82, 74, + 64, 8, 59, 61, 74, 67, 20, 8, 39, 61, 80, 2, 26, 22, 22, 22, 8, 1, 112, 8, 84, + 65, 74, 69, 67, 65, 79, 8, 84, 103, 79, 65, 74, 8, 61, 72, 80, 8, 25, 22, 22, 22, + 8, 1, 112, 8, 75, 64, 65, 79, 8, 24, 22, 22, 22, 8, 1, 112, 8, 65, 79, 67, 61, + 62, 8, 71, 65, 69, 74, 65, 79, 72, 65, 69, 8, 53, 69, 74, 74, 20, 2, 56, 75, 74, + 8, 79, 82, 74, 64, 8, 26, 24, 25, 8, 1, 112, 18, 8, 30, 31, 31, 22, 8, 1, 112, + 18, 8, 23, 24, 8, 22, 22, 22, 8, 1, 112, 18, 8, 24, 22, 8, 22, 22, 22, 8, 1, + 112, 20, 8, 40, 79, 67, 69, 62, 81, 8, 23, 24, 8, 11, 18, 2, 23, 26, 8, 11, 8, + 75, 64, 65, 79, 8, 23, 28, 8, 11, 20, 8, 47, 65, 64, 69, 67, 72, 69, 63, 68, 8, + 29, 28, 8, 11, 8, 68, 61, 81, 81, 65, 74, 8, 51, 79, 75, 62, 72, 65, 73, 65, 18, + 8, 64, 61, 79, 82, 74, 81, 65, 79, 8, 24, 25, 8, 11, 8, 41, 79, 61, 82, 65, 74, + 18, 2, 26, 26, 8, 11, 8, 48, 103, 74, 74, 65, 79, 18, 8, 23, 24, 18, 26, 27, 8, + 11, 8, 45, 82, 74, 67, 65, 74, 8, 82, 74, 64, 8, 23, 30, 18, 29, 8, 11, 8, 48, + 103, 64, 63, 68, 65, 74, 8, 82, 74, 64, 8, 30, 18, 26, 8, 11, 8, 43, 82, 74, 64, + 65, 20, 2, 45, 110, 74, 67, 72, 69, 74, 67, 65, 8, 68, 61, 72, 81, 65, 8, 69, 63, + 68, 8, 66, 110, 79, 8, 65, 69, 74, 8, 61, 82, 102, 65, 79, 75, 79, 64, 65, 74, 81, + 72, 69, 63, 68, 8, 67, 65, 84, 61, 67, 81, 65, 80, 2, 82, 74, 64, 8, 67, 65, 66, + 103, 68, 79, 72, 69, 63, 68, 65, 80, 8, 40, 85, 78, 65, 79, 69, 73, 65, 74, 81, 20, + 8, 48, 65, 69, 74, 65, 8, 43, 65, 79, 79, 65, 74, 18, 8, 53, 69, 65, 2, 71, 109, + 74, 74, 65, 74, 8, 110, 62, 65, 79, 87, 65, 82, 67, 81, 8, 132, 65, 69, 74, 18, 8, + 64, 61, 102, 8, 61, 72, 80, 64, 61, 74, 74, 8, 83, 75, 74, 8, 64, 65, 74, 2, 26, + 8, 48, 61, 132, 63, 68, 69, 74, 65, 74, 8, 73, 61, 74, 63, 68, 73, 61, 72, 8, 74, + 82, 79, 8, 64, 69, 65, 8, 43, 103, 72, 66, 72, 65, 8, 69, 73, 8, 42, 61, 74, 67, + 65, 2, 64, 69, 65, 8, 61, 74, 64, 65, 79, 65, 8, 79, 65, 78, 61, 79, 61, 81, 82, + 79, 62, 65, 64, 110, 79, 66, 81, 69, 67, 8, 132, 65, 69, 74, 8, 84, 110, 79, 64, 65, + 74, 20, 8, 40, 80, 8, 67, 65, 68, 109, 79, 81, 2, 82, 74, 81, 65, 79, 8, 61, 72, + 72, 65, 74, 8, 55, 73, 132, 81, 103, 74, 64, 65, 74, 8, 61, 82, 63, 68, 8, 65, 69, + 74, 65, 8, 36, 82, 66, 132, 69, 63, 68, 81, 8, 64, 61, 87, 82, 18, 8, 82, 74, 64, + 2, 64, 61, 80, 8, 73, 61, 63, 68, 81, 8, 64, 69, 65, 8, 53, 61, 63, 68, 65, 8, + 71, 75, 132, 81, 132, 78, 69, 65, 72, 69, 67, 20, 2, 55, 74, 81, 65, 79, 8, 64, 69, + 65, 132, 65, 73, 8, 7, 56, 75, 79, 62, 65, 68, 61, 72, 81, 6, 8, 84, 69, 72, 72, + 8, 69, 63, 68, 8, 61, 62, 65, 79, 8, 64, 69, 65, 8, 46, 79, 69, 81, 69, 71, 65, + 74, 2, 82, 74, 64, 8, 36, 82, 80, 132, 81, 65, 72, 72, 82, 74, 67, 65, 74, 8, 64, + 65, 80, 8, 43, 65, 79, 79, 74, 8, 53, 81, 61, 64, 81, 83, 20, 8, 39, 79, 20, 8, + 51, 65, 74, 87, 69, 67, 2, 67, 65, 79, 74, 8, 61, 74, 65, 79, 71, 65, 74, 74, 65, + 74, 8, 14, 82, 74, 64, 8, 87, 82, 132, 69, 63, 68, 65, 79, 74, 18, 8, 84, 65, 74, + 74, 8, 53, 69, 65, 8, 82, 74, 80, 8, 64, 69, 65, 2, 109, 81, 69, 67, 65, 8, 60, + 65, 69, 81, 8, 72, 61, 132, 132, 65, 74, 15, 18, 8, 64, 65, 73, 8, 36, 82, 80, 132, + 63, 68, 82, 102, 8, 48, 61, 81, 65, 79, 69, 61, 72, 8, 83, 75, 79, 87, 82, 4, 2, + 65, 67, 65, 74, 18, 8, 132, 75, 84, 65, 69, 81, 8, 69, 63, 68, 8, 69, 74, 8, 64, + 65, 79, 8, 47, 61, 67, 65, 8, 62, 69, 74, 18, 8, 65, 80, 8, 87, 82, 8, 62, 65, + 132, 63, 68, 61, 66, 66, 65, 74, 20, 2, 14, 39, 69, 65, 8, 37, 65, 79, 61, 81, 82, + 74, 67, 8, 84, 69, 79, 64, 8, 67, 65, 132, 63, 68, 72, 75, 132, 132, 65, 74, 20, 8, + 39, 69, 65, 8, 56, 65, 79, 132, 61, 73, 73, 4, 2, 82, 74, 67, 8, 62, 65, 132, 63, + 68, 72, 69, 65, 102, 81, 8, 64, 69, 65, 8, 40, 69, 74, 132, 65, 81, 87, 82, 74, 67, + 8, 65, 69, 74, 65, 80, 8, 36, 82, 80, 132, 63, 68, 82, 132, 132, 65, 80, 8, 83, 75, + 74, 2, 48, 69, 81, 67, 72, 69, 65, 64, 65, 79, 74, 8, 82, 74, 64, 8, 84, 103, 68, + 72, 81, 8, 87, 82, 8, 36, 82, 80, 132, 63, 68, 82, 102, 73, 69, 81, 67, 72, 69, 65, + 64, 65, 79, 74, 2, 69, 65, 8, 53, 81, 61, 64, 81, 83, 20, 8, 37, 61, 79, 74, 65, + 84, 69, 81, 87, 18, 8, 37, 72, 61, 74, 63, 71, 18, 8, 39, 79, 20, 8, 37, 75, 79, + 63, 68, 61, 79, 64, 81, 18, 8, 46, 61, 82, 66, 3, 4, 2, 83, 20, 8, 47, 69, 80, + 87, 81, 18, 8, 39, 79, 20, 8, 51, 65, 74, 87, 69, 67, 18, 8, 53, 61, 63, 68, 80, + 18, 8, 54, 68, 69, 65, 73, 65, 8, 82, 74, 64, 2, 56, 75, 67, 65, 72, 20, 15, 8, + 56, 75, 79, 132, 81, 65, 68, 65, 79, 8, 52, 75, 132, 65, 74, 62, 65, 79, 67, 32, 8, + 51, 82, 74, 71, 81, 8, 23, 29, 8, 64, 65, 79, 8, 54, 61, 67, 65, 80, 75, 79, 64, + 74, 82, 74, 67, 32, 2, 23, 24, 11, 8, 64, 65, 79, 8, 56, 75, 79, 72, 61, 67, 65, + 8, 62, 65, 81, 79, 20, 8, 49, 65, 82, 62, 61, 82, 8, 65, 69, 74, 65, 79, 8, 42, + 65, 73, 65, 69, 74, 64, 65, 64, 75, 78, 78, 65, 72, 4, 2, 132, 63, 68, 82, 72, 65, + 8, 69, 74, 8, 64, 65, 79, 8, 53, 86, 62, 65, 72, 132, 81, 79, 61, 102, 65, 20, 8, + 3, 8, 39, 79, 82, 63, 71, 132, 61, 63, 68, 65, 8, 91, 8, 27, 23, 20, 2, 37, 65, + 79, 69, 63, 68, 81, 65, 79, 132, 81, 61, 81, 81, 65, 79, 8, 53, 81, 61, 64, 81, 83, + 20, 8, 57, 75, 72, 66, 66, 65, 74, 132, 81, 65, 69, 74, 32, 2, 43, 65, 79, 79, 65, + 74, 18, 8, 64, 69, 65, 8, 56, 75, 79, 72, 61, 67, 65, 8, 62, 65, 66, 61, 102, 81, + 8, 132, 69, 63, 68, 8, 73, 69, 81, 8, 65, 69, 74, 65, 73, 8, 53, 63, 68, 82, 72, + 62, 61, 82, 2, 61, 82, 66, 8, 65, 69, 74, 65, 73, 8, 42, 79, 82, 74, 64, 132, 81, + 110, 63, 71, 18, 8, 69, 74, 8, 64, 65, 79, 8, 53, 86, 62, 65, 72, 132, 81, 79, 61, + 102, 65, 8, 67, 65, 72, 65, 67, 65, 74, 18, 2, 84, 65, 72, 63, 68, 65, 80, 8, 64, + 69, 65, 8, 53, 81, 61, 64, 81, 8, 69, 73, 8, 45, 61, 68, 79, 65, 8, 23, 31, 22, + 24, 8, 14, 79, 82, 74, 64, 8, 24, 29, 20, 27, 28, 8, 11, 15, 8, 65, 79, 84, 75, + 79, 62, 65, 74, 8, 68, 61, 81, 20, 2, 40, 69, 74, 65, 8, 53, 81, 79, 61, 102, 65, + 18, 8, 64, 69, 65, 8, 52, 75, 132, 63, 68, 65, 79, 132, 81, 79, 61, 102, 65, 18, 8, + 66, 110, 68, 79, 81, 8, 64, 69, 79, 65, 71, 81, 8, 83, 75, 73, 8, 46, 82, 79, 4, + 2, 66, 110, 79, 132, 81, 65, 74, 64, 61, 73, 73, 8, 61, 82, 66, 8, 64, 61, 80, 8, + 42, 79, 82, 74, 64, 132, 81, 110, 63, 71, 8, 87, 82, 20, 8, 39, 61, 80, 8, 51, 79, + 75, 70, 65, 71, 81, 2, 84, 65, 69, 132, 81, 8, 25, 31, 8, 53, 63, 68, 82, 72, 71, + 72, 61, 132, 132, 65, 74, 8, 61, 82, 66, 18, 8, 83, 75, 74, 8, 64, 65, 74, 65, 74, + 8, 24, 22, 8, 61, 82, 66, 8, 46, 74, 61, 62, 65, 74, 18, 2, 61, 82, 63, 68, 18, + 8, 64, 61, 102, 8, 64, 61, 80, 8, 56, 65, 132, 81, 69, 62, 82, 110, 72, 8, 66, 82, + 79, 8, 64, 69, 65, 8, 46, 74, 61, 62, 65, 74, 61, 62, 81, 65, 69, 72, 81, 82, 74, + 67, 8, 132, 65, 68, 79, 20, 2, 57, 61, 80, 8, 64, 69, 65, 8, 46, 75, 132, 81, 65, + 74, 66, 79, 61, 67, 65, 8, 61, 74, 62, 65, 81, 79, 69, 66, 66, 81, 18, 8, 132, 75, + 8, 69, 132, 81, 8, 64, 65, 79, 8, 46, 82, 62, 69, 71, 4, 2, 73, 65, 81, 65, 79, + 8, 82, 73, 62, 61, 82, 81, 65, 79, 8, 52, 61, 82, 73, 8, 73, 69, 81, 8, 24, 23, + 8, 11, 8, 61, 74, 67, 65, 132, 65, 81, 87, 81, 20, 8, 39, 61, 80, 2, 69, 132, 81, + 8, 65, 69, 74, 8, 51, 79, 65, 69, 80, 18, 8, 64, 65, 79, 8, 73, 69, 79, 8, 64, + 82, 79, 63, 68, 61, 82, 80, 8, 61, 74, 67, 65, 73, 65, 132, 132, 65, 74, 8, 87, 82, + 8, 132, 65, 69, 74, 2, 132, 63, 68, 65, 69, 74, 81, 20, 8, 39, 69, 65, 8, 42, 65, + 132, 61, 73, 81, 132, 82, 73, 73, 65, 8, 64, 65, 79, 8, 46, 75, 132, 81, 65, 74, 8, + 62, 65, 72, 103, 82, 66, 81, 8, 132, 69, 63, 68, 8, 61, 82, 66, 2, 30, 23, 27, 8, + 22, 22, 22, 8, 1, 112, 20, 8, 57, 65, 74, 74, 8, 69, 63, 68, 8, 61, 82, 66, 8, + 64, 69, 65, 8, 36, 79, 63, 68, 69, 81, 65, 71, 81, 82, 79, 8, 74, 75, 63, 68, 8, + 87, 82, 8, 132, 78, 79, 65, 63, 68, 65, 74, 2, 71, 75, 73, 73, 65, 18, 8, 132, 75, + 8, 73, 109, 63, 68, 81, 65, 8, 69, 63, 68, 8, 132, 61, 67, 65, 74, 18, 8, 64, 61, + 102, 8, 132, 69, 65, 8, 69, 73, 8, 52, 61, 68, 73, 65, 74, 8, 64, 65, 80, 2, 42, + 79, 82, 74, 64, 79, 69, 132, 132, 65, 80, 8, 61, 74, 67, 65, 73, 65, 132, 132, 65, 74, + 8, 82, 74, 64, 8, 69, 74, 8, 67, 82, 81, 65, 79, 8, 42, 79, 82, 78, 78, 69, 65, + 79, 82, 74, 67, 2, 73, 61, 72, 65, 79, 69, 132, 63, 68, 8, 61, 82, 66, 67, 65, 72, + 109, 132, 81, 8, 69, 132, 81, 20, 8, 40, 80, 8, 69, 132, 81, 8, 65, 69, 74, 8, 37, + 61, 63, 71, 132, 81, 65, 69, 74, 62, 61, 82, 8, 73, 69, 81, 2, 46, 72, 75, 132, 81, + 65, 79, 66, 75, 79, 73, 61, 81, 8, 78, 79, 75, 70, 65, 71, 81, 69, 65, 79, 81, 18, + 8, 65, 69, 74, 87, 65, 72, 74, 65, 8, 54, 65, 69, 72, 65, 8, 69, 74, 8, 53, 61, + 74, 64, 132, 81, 65, 69, 74, 18, 2, 65, 69, 74, 87, 65, 72, 74, 65, 8, 41, 72, 103, + 63, 68, 65, 74, 8, 14, 79, 82, 74, 64, 8, 25, 27, 18, 23, 24, 8, 11, 15, 8, 69, + 74, 8, 51, 82, 81, 87, 20, 8, 36, 62, 65, 79, 8, 64, 82, 79, 63, 68, 8, 65, 69, + 74, 65, 8, 56, 65, 79, 4, 2, 103, 74, 64, 65, 79, 82, 74, 67, 8, 64, 65, 80, 8, + 42, 79, 82, 74, 64, 79, 69, 132, 132, 65, 80, 8, 87, 82, 8, 28, 28, 8, 11, 8, 64, + 65, 80, 8, 56, 75, 79, 64, 65, 79, 68, 61, 82, 132, 65, 80, 18, 8, 64, 69, 65, 8, + 69, 63, 68, 2, 66, 110, 79, 8, 84, 110, 74, 132, 63, 68, 65, 74, 80, 84, 65, 79, 81, + 8, 68, 61, 72, 81, 65, 18, 8, 84, 69, 79, 64, 8, 132, 69, 63, 68, 8, 65, 62, 65, + 74, 8, 61, 82, 63, 68, 8, 65, 69, 74, 65, 2, 61, 74, 64, 65, 79, 65, 8, 41, 61, + 132, 132, 61, 64, 65, 8, 73, 69, 81, 8, 79, 82, 74, 64, 8, 23, 24, 8, 11, 8, 65, + 79, 67, 65, 62, 65, 74, 20, 8, 36, 82, 80, 8, 64, 65, 74, 8, 42, 79, 110, 74, 64, + 65, 74, 18, 8, 64, 69, 65, 8, 69, 63, 68, 8, 61, 74, 67, 65, 66, 110, 68, 79, 81, + 8, 68, 61, 62, 65, 18, 8, 132, 69, 74, 64, 2, 73, 65, 69, 74, 65, 8, 41, 79, 61, + 71, 81, 69, 75, 74, 80, 67, 65, 74, 75, 132, 132, 65, 74, 8, 14, 69, 74, 8, 54, 65, + 69, 72, 65, 74, 8, 83, 75, 74, 8, 24, 27, 8, 11, 18, 8, 26, 27, 8, 11, 18, 8, + 27, 22, 8, 11, 15, 8, 64, 61, 66, 110, 79, 18, 8, 64, 69, 65, 8, 56, 75, 79, 72, + 61, 67, 65, 8, 65, 69, 74, 65, 73, 2, 36, 82, 80, 132, 63, 68, 82, 132, 132, 65, 8, + 83, 75, 74, 8, 31, 8, 48, 69, 81, 67, 72, 69, 65, 64, 65, 79, 74, 8, 87, 82, 8, + 110, 62, 65, 79, 84, 65, 69, 132, 65, 74, 20, 2, 14, 39, 69, 65, 8, 37, 65, 79, 61, + 81, 82, 74, 67, 8, 84, 69, 79, 64, 8, 67, 65, 132, 63, 68, 72, 75, 132, 132, 65, 74, + 20, 8, 39, 69, 65, 8, 56, 65, 79, 132, 61, 73, 73, 72, 82, 74, 67, 2, 62, 65, 132, + 63, 68, 72, 69, 65, 102, 81, 8, 64, 69, 65, 8, 40, 69, 74, 132, 65, 81, 87, 82, 74, + 67, 8, 65, 69, 74, 65, 80, 8, 36, 82, 80, 132, 63, 68, 82, 132, 132, 65, 80, 8, 83, + 75, 74, 8, 31, 8, 11, 2, 91, 8, 23, 24, 20, 8, 48, 69, 81, 67, 72, 69, 65, 64, + 65, 79, 74, 8, 82, 74, 64, 8, 84, 103, 68, 72, 81, 8, 87, 82, 8, 36, 82, 80, 132, + 63, 68, 82, 102, 73, 69, 81, 67, 72, 69, 65, 64, 65, 79, 74, 8, 64, 69, 65, 2, 53, + 81, 61, 64, 81, 83, 20, 8, 39, 79, 20, 8, 37, 61, 82, 65, 79, 18, 8, 39, 79, 20, + 8, 37, 75, 79, 63, 68, 61, 79, 64, 81, 18, 8, 42, 79, 65, 64, 86, 18, 8, 46, 72, + 69, 63, 71, 18, 2, 47, 69, 74, 67, 74, 65, 79, 18, 8, 48, 69, 81, 81, 61, 67, 18, + 8, 53, 63, 68, 73, 69, 64, 81, 18, 8, 39, 79, 20, 8, 53, 81, 61, 64, 81, 68, 61, + 67, 65, 74, 8, 82, 74, 64, 2, 57, 75, 72, 66, 66, 65, 74, 132, 81, 65, 69, 74, 20, + 15, 8, 56, 75, 79, 132, 81, 65, 68, 65, 79, 8, 52, 75, 132, 65, 74, 62, 65, 79, 67, + 32, 8, 7, 39, 61, 80, 8, 51, 79, 75, 81, 75, 71, 75, 72, 72, 8, 83, 75, 72, 72, + 87, 69, 65, 68, 65, 74, 2, 68, 65, 82, 81, 65, 8, 64, 69, 65, 8, 43, 65, 79, 79, + 65, 74, 8, 53, 81, 61, 64, 81, 83, 20, 8, 56, 75, 67, 65, 72, 18, 8, 57, 65, 74, + 69, 67, 8, 82, 74, 64, 8, 57, 75, 72, 66, 66, 65, 74, 132, 81, 65, 69, 74, 20, 6, + 2, 51, 82, 74, 71, 81, 8, 23, 30, 8, 64, 65, 79, 8, 54, 61, 67, 65, 80, 75, 79, + 64, 74, 82, 74, 67, 18, 8, 91, 8, 24, 24, 18, 8, 91, 8, 31, 27, 18, 8, 91, 27, + 8, 62, 69, 80, 8, 91, 29, 18, 8, 91, 8, 23, 22, 24, 32, 2, 43, 65, 79, 79, 8, + 48, 61, 132, 63, 68, 69, 74, 65, 79, 69, 65, 64, 69, 79, 65, 71, 81, 75, 79, 8, 47, + 61, 82, 81, 65, 74, 132, 63, 68, 72, 103, 67, 65, 79, 18, 8, 84, 65, 72, 63, 68, 65, + 79, 2, 65, 79, 71, 72, 103, 79, 81, 8, 68, 61, 81, 18, 8, 66, 110, 79, 8, 132, 65, + 69, 74, 65, 8, 51, 65, 79, 132, 75, 74, 8, 61, 82, 66, 8, 69, 79, 67, 65, 74, 64, + 84, 65, 72, 63, 68, 65, 74, 8, 56, 65, 79, 4, 2, 64, 69, 65, 74, 132, 81, 8, 87, + 82, 8, 83, 65, 79, 87, 69, 63, 68, 81, 65, 74, 18, 8, 84, 65, 74, 74, 8, 69, 73, + 8, 53, 63, 68, 69, 72, 72, 65, 79, 81, 68, 65, 61, 81, 65, 79, 8, 65, 69, 74, 65, + 2, 39, 79, 65, 68, 62, 110, 68, 74, 65, 8, 132, 65, 69, 74, 65, 79, 8, 46, 75, 74, + 132, 81, 79, 82, 71, 81, 69, 75, 74, 8, 65, 69, 74, 67, 65, 79, 69, 63, 68, 81, 65, + 81, 8, 84, 65, 79, 64, 65, 74, 2, 132, 75, 72, 72, 81, 65, 18, 8, 62, 65, 79, 65, + 63, 68, 74, 65, 81, 8, 64, 69, 65, 8, 46, 75, 132, 81, 65, 74, 8, 65, 69, 74, 65, + 79, 8, 132, 75, 72, 63, 68, 65, 74, 8, 46, 75, 74, 132, 81, 79, 82, 71, 81, 69, 75, + 74, 8, 14, 91, 8, 30, 20, 15, 2, 61, 82, 66, 8, 63, 61, 20, 8, 26, 29, 22, 22, + 22, 18, 22, 22, 8, 1, 112, 8, 75, 64, 65, 79, 8, 30, 29, 25, 25, 8, 1, 112, 8, + 83, 69, 65, 72, 73, 65, 68, 79, 8, 30, 29, 25, 25, 18, 31, 31, 8, 1, 112, 8, 82, + 74, 64, 8, 65, 79, 8, 68, 61, 81, 8, 82, 74, 80, 8, 81, 61, 81, 132, 103, 63, 68, + 72, 69, 63, 68, 8, 64, 65, 74, 2, 49, 61, 63, 68, 84, 65, 69, 80, 8, 67, 65, 72, + 69, 65, 66, 65, 79, 81, 18, 8, 64, 61, 102, 8, 73, 69, 81, 8, 65, 69, 74, 65, 79, + 8, 74, 69, 65, 64, 79, 69, 67, 65, 79, 65, 74, 8, 53, 82, 73, 73, 65, 2, 65, 69, + 74, 65, 8, 84, 69, 79, 71, 72, 69, 63, 68, 8, 83, 75, 72, 72, 71, 75, 73, 73, 65, + 74, 65, 8, 46, 75, 74, 132, 81, 79, 82, 71, 81, 69, 75, 74, 8, 74, 69, 63, 68, 81, + 8, 68, 65, 79, 87, 82, 4, 2, 132, 81, 65, 72, 72, 65, 74, 8, 69, 132, 81, 20, 8, + 40, 69, 74, 65, 8, 39, 79, 65, 68, 62, 110, 68, 74, 65, 8, 62, 69, 65, 81, 65, 81, + 8, 87, 84, 65, 69, 66, 65, 72, 72, 75, 80, 8, 61, 82, 102, 65, 79, 75, 79, 64, 65, + 74, 81, 4, 2, 74, 65, 68, 73, 18, 8, 84, 65, 74, 74, 8, 73, 61, 74, 8, 132, 69, + 65, 8, 65, 69, 74, 65, 79, 8, 82, 74, 64, 8, 132, 69, 65, 8, 69, 132, 81, 8, 69, + 73, 8, 68, 109, 63, 68, 132, 81, 65, 74, 8, 42, 79, 61, 64, 65, 8, 61, 74, 67, 65, + 4, 2, 66, 65, 79, 81, 69, 67, 65, 74, 8, 37, 110, 68, 74, 65, 74, 4, 40, 69, 74, + 79, 69, 63, 68, 81, 82, 74, 67, 8, 74, 75, 63, 68, 8, 61, 74, 67, 72, 69, 65, 64, + 65, 79, 74, 8, 71, 61, 74, 74, 20, 8, 37, 65, 83, 75, 79, 8, 132, 69, 65, 8, 70, + 65, 64, 75, 63, 68, 2, 61, 74, 67, 65, 132, 63, 68, 61, 66, 66, 81, 8, 84, 69, 79, + 64, 18, 8, 132, 75, 72, 72, 81, 65, 74, 8, 64, 69, 65, 132, 65, 8, 39, 69, 74, 67, + 65, 8, 83, 75, 79, 4, 2, 68, 61, 74, 64, 65, 74, 8, 132, 65, 69, 74, 8, 82, 74, + 64, 8, 64, 61, 8, 84, 69, 79, 8, 64, 65, 79, 8, 66, 65, 132, 81, 65, 74, 8, 55, + 62, 65, 79, 87, 65, 82, 67, 82, 74, 67, 8, 132, 69, 74, 64, 18, 2, 84, 69, 65, 8, + 64, 61, 80, 8, 64, 69, 65, 8, 37, 65, 69, 132, 78, 69, 65, 72, 65, 8, 65, 69, 74, + 65, 79, 8, 67, 61, 74, 87, 65, 74, 8, 52, 65, 69, 68, 65, 2, 82, 74, 132, 65, 79, + 65, 79, 8, 67, 79, 109, 102, 65, 79, 65, 74, 8, 37, 110, 68, 74, 65, 74, 8, 87, 65, + 69, 67, 81, 18, 8, 84, 75, 68, 72, 8, 64, 69, 65, 8, 39, 79, 65, 68, 62, 110, 68, + 74, 65, 18, 2, 74, 69, 63, 68, 81, 8, 61, 62, 65, 79, 8, 61, 74, 64, 65, 79, 65, + 8, 40, 69, 74, 79, 69, 63, 68, 81, 82, 74, 67, 65, 74, 8, 65, 74, 81, 62, 65, 68, + 79, 65, 74, 8, 71, 61, 74, 74, 18, 8, 132, 75, 2, 65, 73, 78, 66, 65, 68, 72, 65, + 74, 8, 84, 69, 79, 18, 8, 64, 69, 65, 8, 132, 65, 69, 81, 65, 74, 80, 8, 64, 65, + 79, 8, 132, 81, 103, 64, 81, 69, 132, 63, 68, 65, 74, 8, 46, 75, 72, 72, 65, 67, 69, + 65, 74, 2, 73, 69, 81, 8, 64, 65, 79, 8, 36, 62, 132, 69, 63, 68, 81, 8, 65, 69, + 74, 65, 79, 8, 56, 65, 79, 62, 65, 132, 132, 65, 79, 82, 74, 67, 8, 82, 74, 132, 65, + 79, 65, 79, 8, 37, 110, 68, 74, 65, 74, 4, 2, 61, 74, 72, 61, 67, 65, 8, 67, 65, + 74, 65, 68, 73, 69, 67, 81, 65, 74, 8, 25, 22, 8, 22, 22, 22, 8, 1, 112, 8, 69, + 74, 8, 61, 74, 64, 65, 79, 65, 79, 8, 57, 65, 69, 132, 65, 2, 87, 82, 8, 83, 65, + 79, 84, 65, 74, 64, 65, 74, 8, 82, 74, 64, 8, 87, 84, 61, 79, 8, 132, 63, 68, 72, + 61, 67, 65, 74, 8, 84, 69, 79, 8, 83, 75, 79, 18, 8, 87, 82, 8, 64, 65, 79, 2, + 68, 65, 82, 81, 69, 67, 65, 74, 8, 37, 110, 68, 74, 65, 74, 65, 69, 74, 79, 69, 63, + 68, 81, 82, 74, 67, 8, 74, 75, 63, 68, 8, 41, 75, 72, 67, 65, 74, 64, 65, 80, 8, + 61, 72, 80, 8, 40, 79, 4, 2, 67, 103, 74, 87, 82, 74, 67, 8, 68, 69, 74, 87, 82, + 87, 82, 66, 110, 67, 65, 74, 8, 14, 72, 61, 82, 81, 8, 91, 8, 23, 28, 18, 8, 36, + 62, 20, 8, 61, 15, 32, 2, 14, 23, 8, 53, 63, 68, 69, 65, 62, 65, 87, 82, 67, 15, + 2, 23, 8, 46, 61, 132, 132, 65, 81, 81, 65, 74, 71, 72, 61, 78, 78, 65, 2, 14, 27, + 8, 41, 79, 65, 69, 66, 61, 68, 79, 81, 83, 65, 79, 132, 63, 68, 72, 110, 132, 132, 65, + 15, 2, 31, 8, 46, 82, 72, 69, 132, 132, 65, 74, 84, 103, 67, 65, 74, 2, 23, 8, 57, + 61, 74, 64, 65, 72, 78, 61, 74, 75, 79, 61, 73, 61, 2, 39, 65, 79, 8, 55, 73, 67, + 65, 73, 65, 69, 74, 64, 82, 74, 67, 8, 64, 65, 79, 8, 87, 82, 73, 8, 42, 82, 81, + 80, 62, 65, 87, 69, 79, 71, 8, 54, 65, 67, 65, 72, 65, 79, 2, 41, 75, 79, 132, 81, + 8, 67, 65, 68, 109, 79, 69, 67, 65, 74, 8, 51, 61, 79, 87, 65, 72, 72, 65, 74, 8, + 25, 23, 30, 21, 23, 8, 124, 63, 20, 18, 8, 25, 23, 31, 21, 27, 8, 124, 63, 20, 18, + 2, 25, 24, 22, 21, 27, 18, 8, 25, 24, 23, 21, 24, 26, 8, 124, 63, 20, 18, 8, 25, + 24, 24, 21, 24, 26, 18, 8, 25, 24, 25, 21, 24, 24, 8, 44, 44, 44, 18, 8, 25, 24, + 26, 21, 24, 24, 8, 124, 63, 20, 18, 2, 44, 56, 18, 8, 25, 24, 27, 21, 24, 24, 8, + 56, 8, 124, 63, 20, 18, 8, 25, 24, 28, 21, 24, 24, 8, 44, 44, 18, 8, 25, 24, 29, + 21, 24, 24, 8, 44, 18, 8, 25, 25, 24, 21, 24, 25, 2, 44, 44, 18, 8, 25, 25, 25, + 21, 24, 26, 8, 124, 63, 20, 18, 8, 25, 25, 26, 21, 26, 30, 8, 124, 63, 20, 18, 8, + 25, 24, 30, 21, 24, 30, 8, 44, 44, 18, 8, 25, 24, 31, 21, 24, 25, 8, 124, 63, 20, + 18, 2, 25, 25, 22, 21, 23, 25, 8, 82, 74, 64, 8, 25, 25, 23, 21, 23, 25, 8, 124, + 63, 20, 18, 8, 46, 61, 79, 81, 65, 74, 62, 72, 61, 81, 81, 8, 23, 18, 8, 132, 75, + 84, 69, 65, 8, 64, 65, 79, 2, 61, 74, 67, 79, 65, 74, 87, 65, 74, 64, 65, 74, 8, + 54, 65, 69, 72, 65, 8, 64, 65, 79, 8, 53, 61, 61, 81, 84, 69, 74, 71, 65, 72, 65, + 79, 8, 38, 68, 61, 82, 132, 132, 65, 65, 18, 2, 132, 75, 8, 84, 69, 65, 8, 132, 69, + 65, 8, 61, 82, 66, 8, 64, 65, 73, 8, 62, 65, 69, 8, 64, 65, 74, 8, 36, 71, 81, + 65, 74, 8, 7, 56, 65, 79, 68, 61, 74, 64, 4, 2, 72, 82, 74, 67, 65, 74, 8, 62, + 65, 81, 79, 20, 8, 64, 69, 65, 8, 45, 82, 74, 67, 66, 65, 79, 74, 68, 65, 69, 64, + 65, 6, 8, 14, 43, 65, 66, 81, 8, 49, 79, 20, 8, 25, 30, 26, 15, 2, 62, 65, 66, + 69, 74, 64, 72, 69, 63, 68, 65, 74, 8, 47, 61, 67, 65, 78, 72, 61, 74, 8, 14, 37, + 72, 61, 81, 81, 8, 24, 26, 25, 15, 8, 73, 69, 81, 8, 64, 65, 74, 2, 37, 82, 63, + 68, 132, 81, 61, 62, 65, 74, 8, 63, 8, 64, 8, 79, 8, 77, 8, 65, 8, 82, 74, 64, + 8, 68, 8, 69, 8, 81, 8, 82, 8, 72, 8, 75, 8, 83, 8, 80, 8, 68, 2, 62, 65, + 87, 65, 69, 63, 68, 74, 65, 81, 8, 132, 69, 74, 64, 18, 8, 74, 61, 63, 68, 8, 64, + 65, 79, 8, 53, 81, 61, 64, 81, 67, 65, 73, 65, 69, 74, 64, 65, 8, 38, 68, 61, 79, + 4, 2, 72, 75, 81, 81, 65, 74, 62, 82, 79, 67, 18, 8, 84, 69, 79, 64, 8, 87, 82, + 67, 65, 132, 81, 69, 73, 73, 81, 20, 2, 36, 82, 66, 8, 42, 79, 82, 74, 64, 8, 64, + 65, 79, 8, 42, 65, 73, 65, 69, 74, 64, 65, 62, 65, 132, 63, 68, 72, 110, 132, 132, 65, + 8, 83, 75, 73, 8, 24, 20, 21, 23, 27, 20, 8, 45, 82, 74, 69, 2, 82, 74, 64, 8, + 23, 27, 20, 21, 24, 23, 20, 8, 39, 65, 87, 65, 73, 62, 65, 79, 8, 83, 20, 8, 45, + 80, 20, 8, 14, 39, 79, 82, 63, 71, 132, 61, 63, 68, 65, 8, 49, 79, 20, 8, 24, 30, + 26, 2, 82, 74, 64, 8, 27, 22, 24, 15, 8, 68, 61, 81, 8, 64, 69, 65, 8, 53, 81, + 61, 64, 81, 67, 65, 73, 65, 69, 74, 64, 65, 8, 87, 82, 79, 8, 36, 74, 72, 65, 67, + 82, 74, 67, 8, 65, 69, 74, 65, 80, 2, 56, 75, 72, 71, 80, 78, 61, 79, 71, 65, 80, + 8, 54, 65, 69, 72, 65, 8, 64, 65, 79, 8, 45, 82, 74, 67, 66, 65, 79, 74, 68, 65, + 69, 64, 65, 8, 83, 75, 73, 8, 41, 75, 79, 132, 81, 66, 69, 80, 71, 82, 80, 2, 65, + 79, 84, 75, 79, 62, 65, 74, 20, 8, 44, 74, 8, 64, 65, 74, 8, 62, 65, 87, 110, 67, + 72, 69, 63, 68, 65, 74, 8, 46, 61, 82, 66, 83, 65, 79, 81, 79, 103, 67, 65, 74, 8, + 14, 83, 75, 73, 2, 23, 30, 20, 8, 45, 82, 74, 69, 8, 82, 74, 64, 8, 23, 20, 8, + 45, 82, 72, 69, 8, 83, 20, 8, 45, 80, 20, 8, 3, 8, 49, 79, 20, 8, 26, 31, 31, + 8, 82, 74, 64, 8, 27, 22, 28, 15, 2, 64, 65, 80, 8, 55, 79, 71, 82, 74, 64, 65, + 74, 83, 65, 79, 87, 65, 69, 63, 68, 74, 69, 132, 132, 65, 80, 8, 64, 65, 79, 8, 53, + 81, 61, 64, 81, 8, 38, 68, 61, 79, 72, 75, 81, 81, 65, 74, 62, 82, 79, 67, 8, 3, + 2, 69, 132, 81, 8, 64, 69, 65, 8, 55, 73, 67, 65, 73, 65, 69, 74, 64, 82, 74, 67, + 8, 64, 65, 79, 8, 65, 79, 84, 75, 79, 62, 65, 74, 65, 74, 8, 41, 72, 103, 63, 68, + 65, 74, 8, 82, 74, 64, 2, 64, 65, 79, 8, 61, 74, 8, 132, 69, 65, 8, 61, 74, 132, + 81, 75, 102, 65, 74, 64, 65, 74, 8, 54, 65, 69, 72, 65, 8, 64, 65, 79, 8, 53, 61, + 61, 81, 84, 69, 74, 71, 65, 72, 65, 79, 2, 38, 68, 61, 82, 132, 132, 65, 65, 8, 83, + 75, 79, 67, 65, 132, 65, 68, 65, 74, 20, 8, 39, 69, 65, 8, 82, 73, 87, 82, 67, 65, + 73, 65, 69, 74, 64, 65, 74, 64, 65, 74, 8, 51, 61, 79, 4, 2, 87, 65, 72, 72, 65, + 74, 8, 64, 65, 79, 8, 45, 82, 74, 67, 66, 65, 79, 74, 68, 65, 69, 64, 65, 8, 68, + 61, 62, 65, 74, 8, 69, 74, 66, 75, 72, 67, 65, 8, 71, 61, 81, 61, 132, 81, 65, 79, + 73, 103, 102, 69, 67, 65, 79, 2, 41, 75, 79, 81, 132, 63, 68, 79, 65, 69, 62, 82, 74, + 67, 8, 64, 69, 65, 8, 69, 73, 8, 54, 65, 74, 75, 79, 8, 61, 82, 66, 67, 65, 66, + 110, 68, 79, 81, 65, 74, 8, 74, 65, 82, 65, 74, 2, 49, 82, 73, 73, 65, 79, 74, 8, + 65, 79, 68, 61, 72, 81, 65, 74, 20, 8, 44, 68, 79, 65, 8, 42, 65, 132, 61, 73, 81, + 67, 79, 109, 102, 65, 8, 3, 8, 23, 30, 26, 8, 68, 61, 2, 23, 30, 8, 61, 8, 22, + 28, 8, 77, 73, 8, 3, 8, 84, 65, 69, 63, 68, 81, 8, 83, 75, 74, 8, 64, 65, 79, + 8, 69, 73, 8, 56, 65, 79, 81, 79, 61, 67, 65, 8, 83, 75, 73, 2, 23, 30, 20, 8, + 45, 82, 74, 69, 8, 83, 20, 8, 45, 80, 20, 8, 83, 75, 79, 67, 65, 132, 65, 68, 65, + 74, 65, 74, 8, 82, 73, 8, 65, 69, 74, 8, 67, 65, 79, 69, 74, 67, 65, 80, 8, 61, + 62, 20, 2, 39, 61, 8, 83, 75, 79, 61, 82, 80, 132, 69, 63, 68, 81, 72, 69, 63, 68, + 8, 64, 65, 79, 8, 37, 65, 87, 69, 79, 71, 80, 61, 82, 80, 132, 63, 68, 82, 102, 8, + 69, 74, 8, 51, 75, 81, 80, 4, 2, 132, 81, 103, 79, 71, 82, 74, 67, 8, 83, 75, 74, + 8, 40, 81, 61, 81, 80, 74, 82, 73, 73, 65, 79, 74, 8, 14, 64, 65, 80, 8, 50, 79, + 64, 20, 8, 46, 61, 78, 69, 81, 65, 72, 8, 56, 44, 44, 8, 66, 110, 79, 8, 23, 31, + 22, 27, 15, 20, 2, 55, 79, 132, 63, 68, 79, 69, 66, 81, 72, 69, 63, 68, 8, 73, 69, + 81, 8, 65, 69, 74, 65, 73, 8, 43, 65, 66, 81, 8, 61, 74, 8, 64, 69, 65, 8, 53, + 81, 61, 64, 81, 83, 65, 79, 75, 79, 64, 74, 65, 81, 65, 74, 4, 56, 65, 79, 132, 61, + 73, 73, 72, 82, 74, 67, 2, 73, 69, 81, 8, 64, 65, 73, 8, 36, 74, 81, 79, 61, 67, + 65, 18, 8, 87, 82, 8, 62, 65, 132, 63, 68, 72, 69, 65, 102, 65, 74, 32, 8, 60, 82, + 79, 8, 56, 65, 79, 132, 81, 103, 79, 71, 82, 74, 67, 8, 66, 75, 72, 67, 65, 74, 64, + 65, 79, 8, 40, 81, 61, 81, 80, 74, 82, 73, 73, 65, 79, 74, 2, 64, 65, 80, 8, 50, + 79, 64, 69, 74, 61, 79, 69, 82, 73, 80, 8, 46, 61, 78, 69, 81, 65, 72, 8, 56, 44, + 44, 8, 66, 110, 79, 8, 23, 31, 22, 27, 8, 84, 65, 79, 64, 65, 74, 2, 61, 82, 80, + 8, 72, 61, 82, 66, 65, 74, 64, 65, 74, 8, 48, 69, 81, 81, 65, 72, 74, 8, 74, 61, + 63, 68, 62, 65, 84, 69, 72, 72, 69, 67, 81, 32, 2, 61, 15, 8, 36, 62, 132, 63, 68, + 74, 69, 81, 81, 8, 24, 8, 49, 79, 20, 8, 23, 61, 8, 14, 37, 61, 82, 72, 69, 63, + 68, 65, 8, 55, 74, 81, 65, 79, 68, 61, 72, 81, 82, 74, 67, 2, 64, 65, 80, 8, 52, + 61, 81, 68, 61, 82, 132, 65, 80, 15, 8, 25, 27, 22, 22, 8, 1, 112, 18, 8, 23, 24, + 8, 27, 22, 22, 8, 1, 112, 18, 8, 24, 26, 26, 26, 26, 8, 1, 112, 8, 75, 64, 65, + 79, 8, 25, 28, 25, 8, 28, 27, 28, 8, 1, 112, 2, 62, 15, 8, 36, 62, 132, 63, 68, + 74, 69, 81, 81, 8, 25, 8, 14, 49, 79, 20, 8, 23, 25, 15, 8, 14, 55, 74, 81, 65, + 79, 68, 61, 72, 81, 82, 74, 67, 8, 64, 65, 79, 8, 48, 109, 62, 65, 72, 2, 82, 74, + 64, 8, 42, 65, 79, 103, 81, 65, 8, 69, 73, 8, 52, 61, 81, 68, 61, 82, 132, 65, 15, + 8, 27, 28, 22, 22, 8, 1, 112, 18, 8, 25, 22, 22, 22, 8, 1, 112, 18, 8, 25, 24, + 27, 22, 8, 1, 112, 2, 60, 82, 8, 61, 20, 8, 39, 69, 65, 8, 62, 61, 82, 72, 69, + 63, 68, 65, 8, 55, 74, 81, 65, 79, 68, 61, 72, 81, 82, 74, 67, 8, 64, 65, 80, 8, + 52, 61, 81, 68, 61, 82, 132, 65, 80, 2, 68, 61, 81, 8, 69, 73, 8, 72, 61, 82, 66, + 65, 74, 64, 65, 74, 8, 52, 65, 63, 68, 74, 82, 74, 67, 80, 70, 61, 68, 79, 65, 8, + 65, 79, 68, 65, 62, 72, 69, 63, 68, 8, 67, 79, 109, 102, 65, 79, 65, 2, 36, 82, 66, + 84, 65, 74, 64, 82, 74, 67, 65, 74, 8, 83, 65, 79, 82, 79, 132, 61, 63, 68, 81, 18, + 8, 61, 72, 80, 8, 62, 65, 69, 8, 64, 65, 79, 8, 40, 81, 61, 81, 80, 61, 82, 66, + 132, 81, 65, 72, 72, 82, 74, 67, 2, 61, 74, 67, 65, 74, 75, 73, 73, 65, 74, 8, 84, + 82, 79, 64, 65, 18, 8, 132, 75, 8, 64, 61, 102, 8, 64, 65, 79, 8, 69, 73, 8, 40, + 72, 61, 81, 8, 83, 75, 79, 67, 65, 132, 65, 68, 65, 74, 65, 2, 37, 65, 81, 79, 61, + 67, 8, 83, 75, 74, 8, 29, 22, 22, 22, 8, 1, 112, 8, 62, 65, 79, 65, 69, 81, 80, + 8, 110, 62, 65, 79, 132, 63, 68, 79, 69, 81, 81, 65, 74, 8, 69, 132, 81, 20, 8, 44, + 74, 8, 64, 65, 79, 2, 43, 61, 82, 78, 81, 132, 61, 63, 68, 65, 8, 132, 69, 74, 64, + 8, 64, 69, 65, 8, 48, 65, 68, 79, 61, 82, 80, 67, 61, 62, 65, 74, 8, 64, 82, 79, + 63, 68, 8, 64, 69, 65, 8, 56, 65, 79, 4, 2, 103, 74, 64, 65, 79, 82, 74, 67, 65, + 74, 8, 64, 65, 79, 8, 40, 69, 74, 66, 61, 68, 79, 81, 65, 74, 8, 69, 73, 8, 67, + 79, 75, 102, 65, 74, 8, 43, 75, 66, 65, 18, 8, 64, 82, 79, 63, 68, 2, 56, 65, 79, + 103, 74, 64, 65, 79, 82, 74, 67, 8, 82, 74, 64, 8, 56, 65, 79, 132, 81, 103, 79, 71, + 82, 74, 67, 8, 64, 65, 79, 8, 37, 65, 72, 65, 82, 63, 68, 81, 82, 74, 67, 8, 69, + 74, 2, 64, 65, 74, 8, 46, 75, 79, 79, 69, 64, 75, 79, 65, 74, 8, 82, 74, 64, 8, + 60, 69, 73, 73, 65, 69, 74, 18, 8, 64, 82, 79, 63, 68, 8, 65, 79, 68, 65, 62, 72, + 69, 63, 68, 65, 8, 56, 65, 79, 4, 2, 103, 74, 64, 65, 79, 82, 74, 67, 65, 74, 8, + 82, 74, 64, 8, 56, 65, 79, 62, 65, 132, 132, 65, 79, 82, 74, 67, 65, 74, 8, 69, 74, + 8, 65, 69, 74, 65, 73, 8, 39, 65, 87, 65, 79, 74, 65, 74, 81, 65, 74, 4, 2, 87, + 69, 73, 73, 65, 79, 8, 132, 75, 84, 69, 65, 8, 64, 82, 79, 63, 68, 8, 71, 110, 74, + 132, 81, 72, 69, 63, 68, 65, 8, 40, 74, 81, 132, 81, 103, 82, 62, 82, 74, 67, 8, 64, + 65, 80, 2, 48, 61, 67, 69, 132, 81, 79, 61, 81, 80, 4, 8, 82, 74, 64, 8, 53, 81, + 61, 64, 81, 83, 65, 79, 75, 79, 64, 74, 65, 81, 65, 74, 19, 53, 69, 81, 87, 82, 74, + 67, 80, 132, 61, 61, 72, 65, 80, 8, 84, 69, 65, 2, 64, 65, 79, 8, 60, 69, 73, 73, + 65, 79, 8, 64, 65, 79, 8, 62, 65, 69, 64, 65, 74, 8, 37, 110, 79, 67, 65, 79, 73, + 65, 69, 132, 81, 65, 79, 8, 68, 65, 79, 83, 75, 79, 67, 65, 79, 82, 66, 65, 74, 2, + 84, 75, 79, 64, 65, 74, 20, 8, 39, 69, 65, 8, 101, 62, 65, 79, 132, 63, 68, 79, 65, + 69, 81, 82, 74, 67, 8, 84, 69, 79, 64, 8, 83, 75, 79, 61, 82, 80, 132, 69, 63, 68, + 81, 72, 69, 63, 68, 2, 25, 27, 22, 22, 8, 1, 112, 8, 62, 65, 81, 79, 61, 67, 65, + 74, 18, 8, 64, 69, 65, 8, 64, 82, 79, 63, 68, 8, 40, 79, 132, 78, 61, 79, 74, 69, + 132, 132, 65, 8, 62, 65, 69, 8, 64, 65, 74, 2, 82, 74, 81, 65, 79, 8, 64, 65, 79, + 132, 65, 72, 62, 65, 74, 8, 40, 81, 61, 81, 80, 74, 82, 73, 73, 65, 79, 8, 62, 65, + 79, 65, 69, 81, 132, 81, 65, 68, 65, 74, 64, 65, 74, 8, 48, 69, 81, 81, 65, 72, 74, + 2, 66, 110, 79, 8, 64, 69, 65, 8, 110, 62, 79, 69, 67, 65, 74, 8, 56, 65, 79, 84, + 61, 72, 81, 82, 74, 67, 80, 67, 65, 62, 103, 82, 64, 65, 8, 74, 69, 63, 68, 81, 8, + 67, 65, 64, 65, 63, 71, 81, 2, 84, 65, 79, 64, 65, 74, 8, 71, 61, 74, 74, 20, 8, + 60, 82, 8, 62, 15, 8, 39, 65, 79, 8, 66, 110, 79, 8, 64, 69, 65, 8, 55, 74, 81, + 65, 79, 68, 61, 72, 81, 82, 74, 67, 8, 64, 65, 79, 8, 48, 109, 62, 65, 72, 2, 82, + 74, 64, 8, 42, 65, 79, 103, 81, 65, 8, 69, 73, 8, 40, 72, 61, 81, 8, 83, 75, 79, + 67, 65, 132, 65, 68, 65, 74, 65, 8, 37, 65, 81, 79, 61, 67, 8, 83, 75, 74, 8, 28, + 25, 22, 22, 8, 1, 112, 20, 2, 68, 61, 81, 8, 132, 69, 63, 68, 8, 65, 62, 65, 74, + 66, 61, 72, 72, 80, 8, 61, 72, 80, 8, 82, 74, 87, 82, 79, 65, 69, 63, 68, 65, 74, + 64, 8, 65, 79, 84, 69, 65, 132, 65, 74, 20, 8, 44, 74, 66, 75, 72, 67, 65, 2, 64, + 65, 79, 8, 61, 73, 8, 23, 20, 8, 36, 78, 79, 69, 72, 8, 82, 74, 64, 8, 23, 20, + 8, 50, 71, 81, 75, 62, 65, 79, 8, 23, 31, 22, 27, 8, 65, 69, 74, 67, 65, 81, 79, + 65, 81, 65, 74, 65, 74, 2, 37, 65, 61, 73, 81, 65, 74, 83, 65, 79, 73, 65, 68, 79, + 82, 74, 67, 65, 74, 8, 82, 74, 64, 8, 64, 65, 79, 8, 64, 61, 73, 69, 81, 81, 8, + 83, 65, 79, 62, 82, 74, 64, 65, 74, 65, 74, 2, 7, 42, 65, 68, 65, 69, 73, 65, 74, + 8, 52, 65, 67, 69, 65, 79, 82, 74, 67, 80, 79, 61, 81, 80, 6, 8, 46, 74, 61, 63, + 71, 18, 8, 66, 110, 67, 65, 8, 69, 63, 68, 8, 69, 74, 2, 36, 62, 132, 63, 68, 79, + 69, 66, 81, 8, 62, 65, 69, 18, 8, 64, 65, 80, 67, 72, 65, 69, 63, 68, 65, 74, 8, + 64, 65, 74, 8, 132, 81, 65, 74, 75, 67, 79, 61, 78, 68, 69, 132, 63, 68, 65, 74, 8, + 37, 65, 79, 69, 63, 68, 81, 2, 110, 62, 65, 79, 8, 64, 69, 65, 8, 53, 69, 81, 87, + 82, 74, 67, 8, 64, 65, 80, 8, 52, 65, 69, 63, 68, 80, 81, 61, 67, 80, 8, 83, 75, + 73, 8, 24, 31, 20, 8, 49, 75, 83, 65, 73, 4, 2, 62, 65, 79, 8, 23, 31, 22, 27, + 20, 8, 36, 82, 80, 8, 72, 65, 81, 87, 81, 65, 79, 65, 73, 8, 69, 132, 81, 8, 87, + 82, 8, 65, 79, 132, 65, 68, 65, 74, 18, 8, 64, 61, 102, 8, 62, 65, 69, 8, 64, 65, + 79, 2, 69, 74, 8, 70, 65, 74, 65, 79, 8, 53, 69, 81, 87, 82, 74, 67, 8, 132, 81, + 61, 81, 81, 67, 65, 66, 82, 74, 64, 65, 74, 65, 74, 8, 51, 79, 103, 132, 69, 64, 65, + 74, 81, 65, 74, 84, 61, 68, 72, 8, 69, 74, 2, 64, 65, 79, 8, 64, 65, 74, 8, 51, + 79, 103, 132, 69, 64, 65, 74, 81, 65, 74, 8, 82, 74, 64, 8, 64, 65, 74, 8, 87, 84, + 65, 69, 81, 65, 74, 8, 56, 69, 87, 65, 4, 51, 79, 103, 66, 69, 64, 65, 74, 81, 65, + 74, 2, 62, 65, 81, 79, 65, 66, 66, 65, 74, 64, 65, 74, 8, 57, 61, 68, 72, 67, 103, + 74, 67, 65, 74, 8, 29, 24, 8, 62, 87, 84, 20, 8, 28, 26, 8, 82, 74, 62, 65, 132, + 63, 68, 79, 69, 65, 62, 65, 74, 65, 2, 53, 81, 69, 73, 73, 87, 65, 81, 81, 65, 72, + 8, 61, 72, 80, 8, 82, 74, 67, 110, 72, 81, 69, 67, 8, 83, 75, 74, 8, 64, 65, 79, + 8, 42, 65, 132, 61, 73, 81, 68, 65, 69, 81, 8, 64, 65, 79, 2, 61, 62, 67, 65, 67, + 65, 62, 65, 74, 65, 74, 8, 53, 72, 69, 73, 73, 87, 65, 81, 81, 65, 72, 8, 61, 62, + 67, 65, 87, 75, 67, 65, 74, 8, 84, 75, 79, 64, 65, 74, 8, 132, 69, 74, 64, 20, 2, + 44, 74, 8, 64, 65, 73, 8, 83, 75, 73, 8, 37, 82, 74, 64, 65, 80, 79, 61, 81, 8, + 87, 82, 79, 8, 36, 82, 80, 66, 110, 68, 79, 82, 74, 67, 8, 64, 65, 80, 2, 57, 61, + 68, 72, 67, 65, 132, 65, 81, 87, 65, 80, 8, 66, 110, 79, 8, 64, 65, 74, 8, 52, 65, + 69, 63, 68, 80, 81, 61, 67, 8, 65, 79, 72, 61, 132, 132, 65, 74, 65, 74, 8, 57, 61, + 68, 72, 4, 2, 79, 65, 67, 72, 65, 73, 65, 74, 81, 8, 83, 75, 73, 8, 24, 30, 20, + 8, 48, 61, 69, 8, 23, 30, 29, 22, 8, 69, 132, 81, 8, 61, 82, 80, 64, 79, 110, 63, + 71, 72, 69, 63, 68, 8, 62, 65, 4, 2, 132, 81, 69, 73, 73, 81, 18, 8, 64, 61, 102, + 8, 53, 81, 69, 73, 73, 87, 65, 81, 81, 65, 72, 18, 8, 84, 65, 72, 63, 68, 65, 8, + 71, 65, 69, 74, 65, 74, 8, 49, 61, 73, 65, 74, 8, 68, 61, 62, 65, 74, 18, 2, 82, + 74, 67, 110, 72, 81, 69, 67, 8, 132, 69, 74, 64, 8, 14, 91, 8, 23, 31, 8, 49, 79, + 20, 8, 24, 15, 18, 8, 64, 61, 102, 8, 64, 69, 65, 8, 82, 74, 67, 110, 72, 81, 69, + 67, 65, 74, 2, 53, 81, 69, 73, 73, 65, 74, 8, 14, 61, 72, 132, 75, 8, 61, 82, 63, + 68, 8, 82, 74, 62, 65, 132, 63, 68, 79, 69, 65, 62, 65, 74, 65, 8, 53, 81, 69, 73, + 73, 87, 65, 81, 81, 65, 72, 18, 8, 62, 65, 69, 2, 41, 65, 132, 81, 132, 81, 65, 72, + 72, 82, 74, 67, 8, 64, 65, 80, 8, 57, 61, 68, 72, 79, 65, 132, 82, 72, 81, 61, 81, + 80, 15, 8, 74, 69, 63, 68, 81, 8, 69, 74, 8, 36, 74, 79, 65, 63, 68, 74, 82, 74, + 67, 2, 71, 75, 73, 73, 65, 74, 8, 14, 91, 8, 24, 22, 8, 36, 62, 132, 20, 8, 24, + 15, 8, 82, 74, 64, 8, 64, 61, 102, 8, 64, 65, 79, 70, 65, 74, 69, 67, 65, 8, 46, + 61, 74, 64, 69, 64, 61, 81, 2, 61, 72, 80, 8, 67, 65, 84, 103, 68, 72, 81, 8, 67, + 69, 72, 81, 18, 8, 61, 82, 66, 8, 84, 65, 72, 63, 68, 65, 74, 8, 132, 69, 63, 68, + 8, 64, 69, 65, 8, 61, 62, 132, 75, 72, 82, 81, 65, 8, 48, 65, 68, 79, 68, 65, 69, + 81, 2, 64, 65, 79, 8, 61, 62, 67, 65, 67, 65, 62, 65, 74, 65, 74, 8, 67, 110, 72, + 81, 69, 67, 65, 74, 8, 53, 81, 69, 73, 73, 65, 74, 8, 14, 61, 72, 132, 75, 8, 61, + 62, 87, 110, 67, 72, 69, 63, 68, 8, 64, 65, 79, 2, 82, 74, 62, 65, 132, 63, 68, 79, + 69, 65, 62, 65, 74, 65, 74, 8, 53, 72, 69, 73, 73, 87, 65, 81, 81, 65, 81, 15, 8, + 83, 65, 79, 65, 69, 74, 69, 67, 81, 8, 14, 91, 8, 24, 30, 8, 36, 62, 132, 20, 8, + 23, 15, 20, 2, 40, 62, 65, 74, 132, 75, 8, 68, 65, 69, 102, 81, 8, 65, 80, 8, 69, + 74, 8, 64, 65, 73, 8, 64, 65, 79, 8, 51, 79, 75, 83, 69, 74, 87, 69, 61, 72, 75, + 79, 64, 74, 82, 74, 67, 8, 14, 83, 75, 73, 8, 24, 31, 20, 8, 45, 82, 74, 69, 8, + 23, 30, 29, 27, 15, 2, 62, 65, 69, 67, 65, 66, 110, 67, 81, 65, 74, 8, 7, 57, 61, + 68, 72, 79, 65, 67, 72, 65, 73, 65, 74, 81, 6, 18, 8, 64, 61, 102, 8, 10, 53, 81, + 69, 73, 73, 87, 65, 81, 81, 65, 72, 8, 75, 68, 74, 65, 8, 49, 61, 73, 65, 74, 10, + 8, 82, 74, 67, 110, 72, 81, 69, 67, 2, 14, 91, 8, 28, 15, 8, 82, 74, 64, 8, 64, + 61, 68, 65, 79, 8, 61, 72, 80, 8, 74, 69, 63, 68, 81, 8, 61, 62, 67, 65, 67, 65, + 62, 65, 74, 8, 87, 82, 8, 62, 65, 81, 79, 61, 63, 68, 81, 65, 74, 2, 132, 69, 74, + 64, 8, 14, 91, 8, 29, 15, 8, 82, 74, 64, 8, 14, 91, 8, 25, 24, 15, 8, 64, 65, + 79, 8, 51, 79, 75, 83, 69, 74, 87, 69, 61, 72, 75, 79, 64, 74, 82, 74, 67, 8, 132, + 63, 68, 79, 65, 69, 62, 81, 2, 83, 75, 79, 18, 8, 64, 61, 102, 8, 64, 65, 79, 8, + 56, 75, 79, 132, 69, 81, 87, 65, 74, 64, 65, 8, 64, 65, 80, 8, 51, 79, 75, 83, 69, + 74, 87, 69, 61, 72, 72, 61, 74, 64, 81, 61, 67, 65, 80, 18, 2, 64, 65, 132, 132, 65, + 74, 8, 53, 81, 65, 72, 72, 82, 74, 67, 8, 87, 82, 8, 64, 69, 65, 132, 65, 79, 8, + 46, 109, 79, 78, 65, 79, 132, 63, 68, 61, 66, 81, 8, 67, 65, 74, 61, 82, 8, 64, 65, + 79, 4, 2, 70, 65, 74, 69, 67, 65, 74, 8, 64, 65, 80, 8, 53, 81, 61, 64, 83, 65, + 79, 75, 79, 64, 74, 65, 81, 65, 74, 83, 75, 79, 132, 81, 65, 68, 65, 79, 80, 8, 87, + 82, 79, 8, 53, 81, 61, 64, 81, 4, 2, 83, 65, 79, 75, 79, 64, 74, 65, 81, 65, 74, + 83, 65, 79, 132, 61, 73, 73, 72, 82, 74, 67, 8, 65, 74, 81, 132, 78, 79, 69, 63, 68, + 81, 18, 8, 74, 61, 63, 68, 8, 64, 65, 74, 8, 56, 75, 79, 4, 2, 132, 63, 68, 79, + 69, 66, 81, 65, 74, 8, 64, 65, 80, 8, 64, 65, 79, 8, 51, 79, 75, 83, 69, 74, 87, + 69, 61, 72, 75, 79, 64, 74, 82, 74, 67, 8, 61, 74, 67, 65, 66, 110, 67, 81, 65, 74, + 8, 57, 61, 68, 72, 4, 2, 79, 65, 67, 72, 65, 73, 65, 74, 81, 80, 8, 87, 82, 8, + 84, 103, 68, 72, 65, 74, 8, 69, 132, 81, 20, 8, 39, 61, 80, 8, 52, 65, 69, 63, 68, + 80, 67, 65, 79, 69, 63, 68, 81, 8, 68, 61, 81, 8, 64, 69, 65, 8, 41, 79, 61, 67, + 65, 2, 69, 74, 8, 65, 69, 74, 65, 73, 8, 14, 69, 74, 8, 37, 64, 20, 8, 24, 22, + 18, 8, 53, 20, 8, 23, 26, 22, 8, 66, 66, 20, 15, 8, 61, 62, 67, 65, 64, 79, 82, + 63, 71, 81, 65, 74, 8, 67, 79, 82, 74, 64, 72, 65, 67, 65, 74, 64, 65, 74, 8, 82, + 74, 64, 8, 110, 62, 65, 79, 4, 2, 87, 65, 82, 67, 65, 74, 64, 65, 74, 8, 40, 79, + 71, 65, 74, 74, 81, 74, 69, 80, 8, 83, 75, 73, 8, 31, 20, 8, 48, 103, 79, 87, 8, + 23, 30, 30, 30, 8, 83, 65, 79, 74, 65, 69, 74, 81, 20, 2, 61, 15, 8, 40, 80, 8, + 66, 110, 68, 79, 81, 8, 61, 82, 80, 18, 8, 64, 61, 102, 8, 62, 65, 69, 8, 40, 79, + 73, 69, 81, 81, 65, 72, 82, 74, 67, 8, 64, 65, 80, 8, 53, 69, 74, 74, 65, 80, 8, + 64, 65, 79, 2, 69, 68, 79, 65, 79, 8, 36, 82, 80, 72, 65, 67, 82, 74, 67, 8, 74, + 61, 63, 68, 8, 132, 81, 79, 65, 69, 81, 69, 67, 65, 74, 8, 14, 42, 65, 132, 65, 81, + 87, 19, 8, 62, 65, 87, 84, 20, 8, 53, 81, 61, 81, 82, 81, 4, 2, 62, 65, 132, 81, + 69, 73, 73, 82, 74, 67, 15, 8, 61, 82, 66, 8, 69, 68, 79, 65, 74, 8, 60, 84, 65, + 63, 71, 8, 82, 74, 64, 8, 64, 69, 65, 8, 49, 61, 81, 82, 79, 8, 64, 65, 79, 2, + 53, 61, 63, 68, 65, 8, 64, 61, 80, 8, 65, 74, 81, 132, 63, 68, 65, 69, 64, 65, 74, + 64, 65, 8, 42, 65, 84, 69, 63, 68, 81, 8, 67, 65, 72, 65, 67, 81, 8, 84, 65, 79, + 64, 65, 74, 8, 73, 110, 132, 132, 65, 18, 2, 64, 61, 102, 8, 62, 65, 69, 8, 41, 65, + 132, 81, 132, 81, 65, 72, 72, 82, 74, 67, 8, 65, 69, 74, 65, 79, 8, 64, 82, 79, 63, + 68, 8, 36, 62, 132, 81, 69, 73, 73, 82, 74, 67, 8, 87, 82, 8, 65, 79, 4, 2, 87, + 69, 65, 72, 65, 74, 64, 65, 74, 8, 48, 65, 68, 79, 68, 65, 69, 81, 8, 69, 73, 8, + 61, 72, 72, 67, 65, 73, 65, 69, 74, 65, 74, 8, 82, 74, 64, 8, 73, 61, 74, 67, 65, + 72, 80, 2, 61, 62, 84, 65, 69, 63, 68, 65, 74, 64, 65, 79, 8, 61, 82, 80, 64, 79, + 110, 63, 71, 72, 69, 63, 68, 65, 79, 8, 7, 56, 75, 79, 132, 63, 68, 79, 69, 66, 81, + 8, 64, 65, 80, 8, 42, 65, 132, 65, 72, 72, 132, 63, 68, 61, 66, 81, 80, 4, 2, 83, + 65, 79, 81, 79, 61, 67, 65, 80, 6, 8, 74, 82, 79, 8, 64, 69, 65, 8, 53, 81, 69, + 73, 73, 65, 74, 8, 64, 65, 79, 70, 65, 74, 69, 67, 65, 74, 18, 8, 84, 65, 72, 63, + 68, 65, 8, 132, 69, 63, 68, 2, 83, 65, 79, 81, 79, 61, 67, 65, 80, 6, 8, 74, 82, + 79, 8, 64, 69, 65, 8, 53, 81, 69, 73, 73, 65, 74, 8, 64, 65, 79, 70, 65, 74, 69, + 67, 65, 74, 18, 8, 84, 65, 72, 63, 68, 65, 8, 132, 69, 63, 68, 2, 61, 74, 8, 64, + 65, 79, 8, 36, 62, 132, 81, 69, 73, 73, 82, 74, 67, 8, 62, 65, 81, 65, 69, 72, 69, + 67, 81, 8, 68, 61, 62, 65, 74, 18, 8, 69, 74, 8, 37, 65, 81, 79, 61, 63, 68, 81, + 8, 67, 65, 4, 2, 61, 74, 8, 64, 65, 79, 8, 36, 62, 132, 81, 69, 73, 73, 82, 74, + 67, 8, 62, 65, 81, 65, 69, 72, 69, 67, 81, 8, 68, 61, 62, 65, 74, 18, 8, 69, 74, + 8, 37, 65, 81, 79, 61, 63, 68, 81, 8, 67, 65, 4, 2, 87, 75, 67, 65, 74, 8, 84, + 65, 79, 64, 65, 74, 8, 71, 109, 74, 74, 81, 65, 74, 18, 8, 84, 65, 69, 72, 8, 64, + 65, 79, 70, 65, 74, 69, 67, 65, 18, 8, 84, 65, 72, 63, 68, 65, 79, 8, 132, 69, 63, + 68, 2, 87, 75, 67, 65, 74, 8, 84, 65, 79, 64, 65, 74, 8, 71, 109, 74, 74, 81, 65, + 74, 18, 8, 84, 65, 69, 72, 8, 64, 65, 79, 70, 65, 74, 69, 67, 65, 18, 8, 84, 65, + 72, 63, 68, 65, 79, 8, 132, 69, 63, 68, 2, 81, 79, 75, 81, 87, 8, 132, 65, 69, 74, + 65, 79, 8, 36, 74, 84, 65, 132, 65, 74, 68, 65, 69, 81, 8, 64, 65, 79, 8, 36, 62, + 132, 81, 69, 73, 73, 82, 74, 67, 8, 65, 74, 81, 68, 103, 72, 81, 18, 2, 81, 79, 75, + 81, 87, 8, 132, 65, 69, 74, 65, 79, 8, 36, 74, 84, 65, 132, 65, 74, 68, 65, 69, 81, + 8, 64, 65, 79, 8, 36, 62, 132, 81, 69, 73, 73, 82, 74, 67, 8, 65, 74, 81, 68, 103, + 72, 81, 18, 2, 74, 69, 63, 68, 81, 8, 74, 82, 79, 8, 61, 82, 66, 8, 132, 65, 69, + 74, 8, 53, 81, 69, 73, 73, 79, 65, 63, 68, 81, 8, 83, 65, 79, 87, 69, 63, 68, 81, + 65, 18, 8, 132, 75, 74, 64, 65, 79, 74, 8, 64, 61, 4, 2, 74, 69, 63, 68, 81, 8, + 74, 82, 79, 8, 61, 82, 66, 8, 132, 65, 69, 74, 8, 53, 81, 69, 73, 73, 79, 65, 63, + 68, 81, 8, 83, 65, 79, 87, 69, 63, 68, 81, 65, 18, 8, 132, 75, 74, 64, 65, 79, 74, + 8, 64, 61, 4, 2, 73, 69, 81, 8, 61, 82, 63, 68, 8, 64, 69, 65, 8, 40, 74, 81, + 132, 63, 68, 65, 69, 64, 82, 74, 67, 8, 110, 62, 65, 79, 8, 64, 65, 74, 8, 87, 82, + 79, 8, 36, 62, 132, 81, 69, 73, 73, 82, 74, 67, 2, 73, 69, 81, 8, 61, 82, 63, 68, + 8, 64, 69, 65, 8, 40, 74, 81, 132, 63, 68, 65, 69, 64, 82, 74, 67, 8, 110, 62, 65, + 79, 8, 64, 65, 74, 8, 87, 82, 79, 8, 36, 62, 132, 81, 69, 73, 73, 82, 74, 67, 2, + 132, 81, 65, 68, 65, 74, 64, 65, 74, 8, 36, 74, 81, 79, 61, 67, 8, 64, 65, 74, 8, + 110, 62, 79, 69, 67, 65, 74, 8, 37, 65, 81, 65, 69, 72, 69, 67, 81, 65, 74, 8, 61, + 74, 68, 65, 69, 73, 8, 67, 65, 62, 65, 18, 2, 132, 81, 65, 68, 65, 74, 64, 65, 74, + 8, 36, 74, 81, 79, 61, 67, 8, 64, 65, 74, 8, 110, 62, 79, 69, 67, 65, 74, 8, 37, + 65, 81, 65, 69, 72, 69, 67, 81, 65, 74, 8, 61, 74, 68, 65, 69, 73, 8, 67, 65, 62, + 65, 18, 2, 65, 79, 8, 73, 69, 81, 68, 69, 74, 8, 71, 65, 69, 74, 65, 8, 61, 74, + 64, 65, 79, 65, 8, 7, 53, 81, 65, 72, 72, 82, 74, 67, 8, 87, 82, 79, 8, 53, 61, + 63, 68, 65, 6, 8, 65, 69, 74, 74, 65, 68, 73, 65, 18, 2, 65, 79, 8, 73, 69, 81, + 68, 69, 74, 8, 71, 65, 69, 74, 65, 8, 61, 74, 64, 65, 79, 65, 8, 7, 53, 81, 65, + 72, 72, 82, 74, 67, 8, 87, 82, 79, 8, 53, 61, 63, 68, 65, 6, 8, 65, 69, 74, 74, + 65, 68, 73, 65, 18, 2, 84, 69, 65, 8, 64, 69, 65, 70, 65, 74, 69, 67, 65, 74, 8, + 53, 81, 69, 73, 73, 62, 65, 79, 65, 63, 68, 81, 69, 67, 81, 65, 74, 18, 8, 64, 69, + 65, 8, 132, 69, 63, 68, 8, 65, 74, 81, 84, 65, 64, 65, 79, 2, 84, 69, 65, 8, 64, + 69, 65, 70, 65, 74, 69, 67, 65, 74, 8, 53, 81, 69, 73, 73, 62, 65, 79, 65, 63, 68, + 81, 69, 67, 81, 65, 74, 18, 8, 64, 69, 65, 8, 132, 69, 63, 68, 8, 65, 74, 81, 84, + 65, 64, 65, 79, 2, 110, 62, 65, 79, 68, 61, 82, 78, 81, 8, 74, 69, 63, 68, 81, 8, + 65, 69, 74, 67, 65, 66, 82, 74, 64, 65, 74, 18, 8, 75, 64, 65, 79, 8, 83, 75, 79, + 8, 64, 65, 79, 8, 36, 62, 132, 81, 69, 73, 73, 82, 74, 67, 2, 110, 62, 65, 79, 68, + 61, 82, 78, 81, 8, 74, 69, 63, 68, 81, 8, 65, 69, 74, 67, 65, 66, 82, 74, 64, 65, + 74, 18, 8, 75, 64, 65, 79, 8, 83, 75, 79, 8, 64, 65, 79, 8, 36, 62, 132, 81, 69, + 73, 73, 82, 74, 67, 2, 61, 82, 80, 8, 64, 65, 79, 8, 56, 65, 79, 132, 61, 73, 73, + 72, 82, 74, 67, 8, 65, 74, 81, 66, 65, 79, 74, 81, 8, 68, 61, 62, 65, 74, 18, 8, + 82, 74, 64, 8, 132, 65, 69, 74, 65, 79, 2, 61, 82, 80, 8, 64, 65, 79, 8, 56, 65, + 79, 132, 61, 73, 73, 72, 82, 74, 67, 8, 65, 74, 81, 66, 65, 79, 74, 81, 8, 68, 61, + 62, 65, 74, 18, 8, 82, 74, 64, 8, 132, 65, 69, 74, 65, 79, 2, 36, 62, 132, 69, 63, + 68, 81, 8, 64, 65, 73, 67, 65, 73, 103, 102, 8, 74, 82, 79, 8, 64, 61, 64, 82, 79, + 63, 68, 8, 65, 74, 81, 132, 78, 79, 75, 63, 68, 65, 74, 8, 84, 65, 79, 64, 65, 18, + 2, 36, 62, 132, 69, 63, 68, 81, 8, 64, 65, 73, 67, 65, 73, 103, 102, 8, 74, 82, 79, + 8, 64, 61, 64, 82, 79, 63, 68, 8, 65, 74, 81, 132, 78, 79, 75, 63, 68, 65, 74, 8, + 84, 65, 79, 64, 65, 18, 2, 64, 61, 102, 8, 65, 79, 8, 68, 69, 74, 132, 69, 63, 68, + 81, 72, 69, 63, 68, 8, 64, 65, 79, 8, 48, 65, 68, 79, 68, 65, 69, 81, 80, 62, 65, + 79, 65, 63, 68, 74, 82, 74, 67, 8, 65, 62, 65, 74, 132, 75, 2, 64, 61, 102, 8, 65, + 79, 8, 68, 69, 74, 132, 69, 63, 68, 81, 72, 69, 63, 68, 8, 64, 65, 79, 8, 48, 65, + 68, 79, 68, 65, 69, 81, 80, 62, 65, 79, 65, 63, 68, 74, 82, 74, 67, 8, 65, 62, 65, + 74, 132, 75, 2, 84, 69, 65, 8, 64, 69, 65, 8, 61, 62, 84, 65, 132, 65, 74, 64, 65, + 74, 8, 53, 81, 69, 73, 73, 62, 65, 79, 65, 63, 68, 81, 69, 67, 81, 65, 74, 8, 62, + 65, 68, 61, 74, 64, 65, 72, 81, 2, 84, 69, 65, 8, 64, 69, 65, 8, 61, 62, 84, 65, + 132, 65, 74, 64, 65, 74, 8, 53, 81, 69, 73, 73, 62, 65, 79, 65, 63, 68, 81, 69, 67, + 81, 65, 74, 8, 62, 65, 68, 61, 74, 64, 65, 72, 81, 2, 37, 65, 81, 65, 69, 72, 69, + 67, 81, 65, 74, 8, 51, 65, 79, 132, 75, 74, 65, 74, 8, 67, 65, 67, 65, 74, 84, 103, + 79, 81, 69, 67, 8, 14, 64, 61, 73, 61, 72, 69, 67, 8, 25, 26, 8, 11, 18, 8, 23, + 24, 18, 27, 28, 8, 11, 2, 37, 65, 81, 65, 69, 72, 69, 67, 81, 65, 74, 8, 51, 65, + 79, 132, 75, 74, 65, 74, 8, 67, 65, 67, 65, 74, 84, 103, 79, 81, 69, 67, 8, 14, 64, + 61, 73, 61, 72, 69, 67, 8, 25, 26, 8, 11, 18, 8, 23, 24, 18, 27, 28, 8, 11, 2, + 67, 61, 79, 8, 23, 25, 18, 31, 27, 8, 11, 8, 75, 64, 65, 79, 8, 24, 26, 8, 11, + 15, 20, 8, 39, 61, 83, 75, 74, 8, 61, 62, 67, 65, 68, 65, 74, 64, 8, 23, 22, 22, + 8, 1, 112, 8, 62, 69, 80, 8, 24, 27, 22, 8, 1, 112, 2, 67, 61, 79, 8, 23, 25, + 18, 31, 27, 8, 11, 8, 75, 64, 65, 79, 8, 24, 26, 8, 11, 15, 20, 8, 39, 61, 83, + 75, 74, 8, 61, 62, 67, 65, 68, 65, 74, 64, 8, 23, 22, 22, 8, 1, 112, 8, 62, 69, + 80, 8, 24, 27, 22, 8, 1, 112, 2, 73, 61, 74, 63, 68, 73, 61, 72, 8, 67, 61, 79, + 8, 62, 69, 80, 8, 30, 22, 22, 8, 1, 112, 8, 75, 64, 65, 79, 8, 31, 22, 22, 8, + 1, 112, 8, 73, 75, 74, 61, 81, 72, 69, 63, 68, 8, 61, 62, 87, 69, 65, 68, 65, 74, + 64, 2, 73, 61, 74, 63, 68, 73, 61, 72, 8, 67, 61, 79, 8, 62, 69, 80, 8, 30, 22, + 22, 8, 1, 112, 8, 75, 64, 65, 79, 8, 31, 22, 22, 8, 1, 112, 8, 73, 75, 74, 61, + 81, 72, 69, 63, 68, 8, 61, 62, 87, 69, 65, 68, 65, 74, 64, 2, 83, 75, 73, 8, 42, + 65, 84, 69, 74, 74, 8, 69, 74, 8, 51, 79, 75, 87, 65, 74, 81, 32, 8, 23, 22, 8, + 11, 18, 8, 23, 22, 18, 27, 8, 11, 18, 8, 23, 24, 18, 29, 27, 8, 11, 18, 8, 24, + 22, 8, 11, 2, 83, 75, 73, 8, 42, 65, 84, 69, 74, 74, 8, 69, 74, 8, 51, 79, 75, + 87, 65, 74, 81, 32, 8, 23, 22, 8, 11, 18, 8, 23, 22, 18, 27, 8, 11, 18, 8, 23, + 24, 18, 29, 27, 8, 11, 18, 8, 24, 22, 8, 11, 2, 40, 74, 80, 81, 65, 68, 65, 74, + 64, 8, 64, 65, 79, 8, 36, 82, 66, 84, 61, 74, 64, 8, 83, 75, 74, 8, 23, 24, 8, + 27, 22, 22, 8, 1, 112, 8, 62, 69, 80, 8, 23, 26, 8, 22, 22, 22, 8, 1, 112, 2, + 40, 74, 80, 81, 65, 68, 65, 74, 64, 8, 64, 65, 79, 8, 36, 82, 66, 84, 61, 74, 64, + 8, 83, 75, 74, 8, 23, 24, 8, 27, 22, 22, 8, 1, 112, 8, 62, 69, 80, 8, 23, 26, + 8, 22, 22, 22, 8, 1, 112, 2, 71, 72, 65, 69, 74, 65, 79, 65, 8, 54, 65, 69, 72, + 65, 8, 65, 79, 67, 65, 62, 65, 74, 8, 29, 28, 22, 8, 1, 112, 18, 8, 30, 22, 22, + 8, 1, 112, 18, 8, 30, 24, 22, 8, 1, 112, 18, 8, 23, 22, 22, 22, 8, 1, 112, 8, + 73, 75, 74, 61, 81, 72, 69, 63, 68, 2, 71, 72, 65, 69, 74, 65, 79, 65, 8, 54, 65, + 69, 72, 65, 8, 65, 79, 67, 65, 62, 65, 74, 8, 29, 28, 22, 8, 1, 112, 18, 8, 30, + 22, 22, 8, 1, 112, 18, 8, 30, 24, 22, 8, 1, 112, 18, 8, 23, 22, 22, 22, 8, 1, + 112, 8, 73, 75, 74, 61, 81, 72, 69, 63, 68, 2, 75, 64, 65, 79, 8, 24, 22, 22, 22, + 8, 1, 112, 8, 62, 69, 80, 8, 24, 28, 22, 22, 8, 1, 112, 8, 73, 75, 74, 61, 81, + 72, 69, 63, 68, 18, 8, 73, 61, 85, 69, 73, 61, 72, 8, 26, 22, 22, 22, 8, 1, 112, + 18, 8, 26, 29, 22, 22, 8, 1, 112, 2, 75, 64, 65, 79, 8, 24, 22, 22, 22, 8, 1, + 112, 8, 62, 69, 80, 8, 24, 28, 22, 22, 8, 1, 112, 8, 73, 75, 74, 61, 81, 72, 69, + 63, 68, 18, 8, 73, 61, 85, 69, 73, 61, 72, 8, 26, 22, 22, 22, 8, 1, 112, 18, 8, + 26, 29, 22, 22, 8, 1, 112, 2, 26, 30, 22, 22, 8, 1, 112, 18, 8, 26, 31, 22, 22, + 8, 1, 112, 18, 8, 27, 22, 22, 22, 8, 1, 112, 18, 8, 27, 24, 22, 22, 8, 1, 112, + 18, 8, 27, 25, 22, 22, 8, 1, 112, 18, 8, 28, 22, 22, 22, 8, 1, 112, 20, 2, 26, + 30, 22, 22, 8, 1, 112, 18, 8, 26, 31, 22, 22, 8, 1, 112, 18, 8, 27, 22, 22, 22, + 8, 1, 112, 18, 8, 27, 24, 22, 22, 8, 1, 112, 18, 8, 27, 25, 22, 22, 8, 1, 112, + 18, 8, 28, 22, 22, 22, 8, 1, 112, 20, 2, 84, 65, 79, 64, 65, 20, 8, 14, 60, 82, + 8, 62, 15, 8, 36, 82, 80, 8, 64, 69, 65, 132, 65, 74, 8, 42, 79, 110, 74, 64, 65, + 74, 8, 71, 75, 73, 73, 81, 8, 64, 61, 80, 8, 52, 65, 69, 63, 68, 80, 67, 65, 79, + 69, 63, 68, 81, 8, 25, 26, 30, 25, 30, 8, 1, 112, 20, 2, 84, 65, 79, 64, 65, 20, + 8, 14, 60, 82, 8, 62, 15, 8, 36, 82, 80, 8, 64, 69, 65, 132, 65, 74, 8, 42, 79, + 110, 74, 64, 65, 74, 8, 71, 75, 73, 73, 81, 8, 64, 61, 80, 8, 52, 65, 69, 63, 68, + 80, 67, 65, 79, 69, 63, 68, 81, 8, 25, 26, 30, 25, 30, 8, 1, 112, 20, 2, 87, 82, + 8, 64, 65, 73, 8, 53, 63, 68, 72, 82, 102, 18, 8, 64, 61, 102, 8, 61, 72, 80, 8, + 7, 83, 65, 79, 81, 79, 65, 81, 65, 74, 8, 69, 74, 8, 64, 65, 79, 8, 42, 65, 74, + 65, 79, 61, 72, 4, 2, 87, 82, 8, 64, 65, 73, 8, 53, 63, 68, 72, 82, 102, 18, 8, + 64, 61, 102, 8, 61, 72, 80, 8, 7, 83, 65, 79, 81, 79, 65, 81, 65, 74, 8, 69, 74, + 8, 64, 65, 79, 8, 42, 65, 74, 65, 79, 61, 72, 4, 2, 83, 65, 79, 132, 61, 73, 73, + 72, 82, 74, 67, 6, 8, 74, 82, 79, 8, 64, 65, 79, 8, 36, 71, 81, 69, 65, 74, 62, + 65, 132, 69, 81, 87, 8, 64, 65, 79, 70, 65, 74, 69, 67, 65, 74, 8, 36, 71, 81, 69, + 75, 4, 2, 83, 65, 79, 132, 61, 73, 73, 72, 82, 74, 67, 6, 8, 74, 82, 79, 8, 64, + 65, 79, 8, 36, 71, 81, 69, 65, 74, 62, 65, 132, 69, 81, 87, 8, 64, 65, 79, 70, 65, + 74, 69, 67, 65, 74, 8, 36, 71, 81, 69, 75, 4, 2, 74, 103, 79, 65, 8, 61, 74, 67, + 65, 132, 65, 68, 65, 74, 8, 84, 65, 79, 64, 65, 74, 8, 71, 109, 74, 74, 65, 18, 8, + 84, 65, 72, 63, 68, 65, 8, 62, 65, 69, 8, 64, 65, 79, 8, 36, 62, 4, 2, 74, 103, + 79, 65, 8, 61, 74, 67, 65, 132, 65, 68, 65, 74, 8, 84, 65, 79, 64, 65, 74, 8, 71, + 109, 74, 74, 65, 18, 8, 84, 65, 72, 63, 68, 65, 8, 62, 65, 69, 8, 64, 65, 79, 8, + 36, 62, 4, 2, 132, 81, 69, 73, 73, 82, 74, 67, 8, 69, 68, 79, 65, 8, 53, 81, 69, + 73, 73, 65, 8, 61, 62, 67, 65, 67, 65, 62, 65, 74, 8, 68, 61, 62, 65, 74, 8, 82, + 74, 64, 8, 64, 61, 102, 2, 132, 81, 69, 73, 73, 82, 74, 67, 8, 69, 68, 79, 65, 8, + 53, 81, 69, 73, 73, 65, 8, 61, 62, 67, 65, 67, 65, 62, 65, 74, 8, 68, 61, 62, 65, + 74, 8, 82, 74, 64, 8, 64, 61, 102, 2, 65, 80, 8, 72, 65, 69, 74, 65, 74, 8, 55, + 74, 81, 65, 79, 132, 63, 68, 69, 65, 64, 8, 73, 61, 63, 68, 65, 18, 8, 75, 62, 8, + 64, 82, 79, 63, 68, 8, 64, 69, 65, 8, 36, 62, 132, 81, 69, 73, 4, 2, 65, 80, 8, + 72, 65, 69, 74, 65, 74, 8, 55, 74, 81, 65, 79, 132, 63, 68, 69, 65, 64, 8, 73, 61, + 63, 68, 65, 18, 8, 75, 62, 8, 64, 82, 79, 63, 68, 8, 64, 69, 65, 8, 36, 62, 132, + 81, 69, 73, 4, 2, 73, 82, 74, 67, 8, 65, 69, 74, 65, 8, 79, 65, 72, 61, 81, 69, + 83, 65, 18, 8, 61, 62, 132, 75, 72, 82, 81, 65, 8, 75, 64, 65, 79, 8, 77, 82, 61, + 72, 69, 66, 69, 87, 69, 65, 79, 81, 65, 8, 48, 65, 68, 79, 4, 2, 73, 82, 74, 67, + 8, 65, 69, 74, 65, 8, 79, 65, 72, 61, 81, 69, 83, 65, 18, 8, 61, 62, 132, 75, 72, + 82, 81, 65, 8, 75, 64, 65, 79, 8, 77, 82, 61, 72, 69, 66, 69, 87, 69, 65, 79, 81, + 65, 8, 48, 65, 68, 79, 4, 2, 68, 65, 69, 81, 18, 8, 23, 24, 23, 30, 21, 23, 8, + 124, 63, 20, 18, 8, 25, 21, 27, 8, 124, 63, 20, 18, 8, 54, 65, 69, 72, 73, 65, 74, + 67, 65, 8, 83, 75, 74, 8, 26, 22, 22, 22, 8, 1, 112, 20, 2, 68, 65, 69, 81, 18, + 8, 23, 24, 23, 30, 21, 23, 8, 124, 63, 20, 18, 8, 25, 21, 27, 8, 124, 63, 20, 18, + 8, 54, 65, 69, 72, 73, 65, 74, 67, 65, 8, 83, 75, 74, 8, 26, 22, 22, 22, 8, 1, + 112, 20, 2, 27, 24, 25, 27, 21, 27, 18, 8, 25, 24, 23, 21, 24, 26, 8, 124, 63, 20, + 18, 8, 25, 24, 24, 21, 24, 26, 18, 8, 27, 24, 25, 27, 21, 24, 24, 8, 44, 44, 44, + 18, 8, 25, 24, 26, 21, 24, 24, 8, 124, 63, 20, 18, 8, 24, 28, 11, 20, 2, 27, 24, + 25, 27, 21, 27, 18, 8, 25, 24, 23, 21, 24, 26, 8, 124, 63, 20, 18, 8, 25, 24, 24, + 21, 24, 26, 18, 8, 27, 24, 25, 27, 21, 24, 24, 8, 44, 44, 44, 18, 8, 25, 24, 26, + 21, 24, 24, 8, 124, 63, 20, 18, 8, 24, 28, 11, 20, 2, 44, 56, 18, 8, 24, 25, 27, + 21, 24, 24, 8, 56, 8, 124, 63, 20, 18, 8, 24, 25, 26, 25, 26, 21, 23, 8, 44, 44, + 18, 8, 30, 28, 27, 26, 21, 24, 24, 8, 44, 8, 124, 63, 20, 18, 8, 25, 26, 25, 26, + 21, 24, 25, 18, 8, 23, 24, 8, 11, 20, 2, 44, 56, 18, 8, 24, 25, 27, 21, 24, 24, + 8, 56, 8, 124, 63, 20, 18, 8, 24, 25, 26, 25, 26, 21, 23, 8, 44, 44, 18, 8, 30, + 28, 27, 26, 21, 24, 24, 8, 44, 8, 124, 63, 20, 18, 8, 25, 26, 25, 26, 21, 24, 25, + 18, 8, 23, 24, 8, 11, 20, 2, 44, 44, 18, 8, 25, 25, 25, 25, 25, 21, 24, 26, 8, + 124, 63, 20, 18, 8, 23, 24, 24, 23, 24, 21, 26, 30, 8, 124, 63, 20, 18, 8, 25, 24, + 30, 21, 24, 30, 8, 44, 44, 18, 8, 25, 24, 31, 21, 24, 25, 8, 124, 63, 20, 18, 8, + 30, 30, 8, 11, 20, 2, 44, 44, 18, 8, 25, 25, 25, 25, 25, 21, 24, 26, 8, 124, 63, + 20, 18, 8, 23, 24, 24, 23, 24, 21, 26, 30, 8, 124, 63, 20, 18, 8, 25, 24, 30, 21, + 24, 30, 8, 44, 44, 18, 8, 25, 24, 31, 21, 24, 25, 8, 124, 63, 20, 18, 8, 30, 30, + 8, 11, 20, 2, 23, 23, 25, 21, 23, 25, 8, 82, 74, 64, 8, 25, 25, 23, 21, 23, 25, + 8, 124, 63, 20, 8, 82, 73, 8, 73, 65, 68, 79, 65, 79, 65, 8, 23, 22, 22, 8, 11, + 20, 2, 23, 23, 25, 21, 23, 25, 8, 82, 74, 64, 8, 25, 25, 23, 21, 23, 25, 8, 124, + 63, 20, 8, 82, 73, 8, 73, 65, 68, 79, 65, 79, 65, 8, 23, 22, 22, 8, 11, 20, 2, + 25, 26, 30, 25, 30, 8, 1, 112, 18, 8, 24, 30, 30, 30, 8, 1, 112, 8, 82, 74, 64, + 8, 23, 22, 22, 22, 8, 1, 112, 20, 8, 48, 61, 74, 63, 68, 73, 61, 72, 8, 30, 30, + 30, 8, 1, 112, 8, 75, 64, 65, 79, 8, 23, 24, 8, 22, 22, 22, 8, 1, 112, 20, 2, + 25, 26, 30, 25, 30, 8, 1, 112, 18, 8, 24, 30, 30, 30, 8, 1, 112, 8, 82, 74, 64, + 8, 23, 22, 22, 22, 8, 1, 112, 20, 8, 48, 61, 74, 63, 68, 73, 61, 72, 8, 30, 30, + 30, 8, 1, 112, 8, 75, 64, 65, 79, 8, 23, 24, 8, 22, 22, 22, 8, 1, 112, 20, 2, + 43, 65, 79, 71, 109, 73, 73, 72, 69, 63, 68, 8, 82, 74, 81, 65, 79, 8, 26, 22, 22, + 8, 1, 112, 18, 8, 57, 65, 79, 71, 81, 61, 67, 65, 8, 27, 22, 22, 8, 1, 112, 20, + 8, 36, 74, 8, 41, 65, 69, 65, 79, 81, 61, 67, 65, 74, 2, 43, 65, 79, 71, 109, 73, + 73, 72, 69, 63, 68, 8, 82, 74, 81, 65, 79, 8, 26, 22, 22, 8, 1, 112, 18, 8, 57, + 65, 79, 71, 81, 61, 67, 65, 8, 27, 22, 22, 8, 1, 112, 20, 8, 36, 74, 8, 41, 65, + 69, 65, 79, 81, 61, 67, 65, 74, 2, 23, 24, 22, 22, 8, 1, 112, 20, 8, 28, 22, 8, + 1, 112, 20, 8, 56, 75, 74, 8, 29, 22, 8, 1, 112, 8, 62, 69, 80, 8, 87, 82, 8, + 24, 25, 22, 8, 1, 112, 20, 8, 39, 61, 79, 82, 74, 81, 65, 79, 8, 27, 22, 8, 1, + 112, 2, 23, 24, 22, 22, 8, 1, 112, 20, 8, 28, 22, 8, 1, 112, 20, 8, 56, 75, 74, + 8, 29, 22, 8, 1, 112, 8, 62, 69, 80, 8, 87, 82, 8, 24, 25, 22, 8, 1, 112, 20, + 8, 39, 61, 79, 82, 74, 81, 65, 79, 8, 27, 22, 8, 1, 112, 2, 40, 69, 74, 62, 65, + 68, 61, 72, 81, 8, 75, 64, 65, 79, 8, 73, 65, 68, 79, 20, 8, 54, 65, 69, 72, 65, + 8, 87, 65, 69, 67, 65, 74, 8, 61, 82, 66, 8, 25, 22, 8, 11, 8, 14, 25, 22, 22, + 8, 1, 112, 15, 18, 8, 25, 24, 8, 11, 8, 14, 26, 22, 22, 2, 40, 69, 74, 62, 65, + 68, 61, 72, 81, 8, 75, 64, 65, 79, 8, 73, 65, 68, 79, 20, 8, 54, 65, 69, 72, 65, + 8, 87, 65, 69, 67, 65, 74, 8, 61, 82, 66, 8, 25, 22, 8, 11, 8, 14, 25, 22, 22, + 8, 1, 112, 15, 18, 8, 25, 24, 8, 11, 8, 14, 26, 22, 22, 2, 1, 112, 15, 18, 8, + 26, 22, 8, 11, 8, 14, 27, 22, 22, 8, 1, 112, 15, 18, 8, 26, 27, 8, 11, 8, 14, + 29, 22, 22, 8, 1, 112, 15, 20, 8, 36, 82, 63, 68, 8, 110, 62, 65, 79, 8, 28, 22, + 8, 11, 8, 14, 23, 23, 26, 26, 8, 1, 112, 15, 18, 2, 1, 112, 15, 18, 8, 26, 22, + 8, 11, 8, 14, 27, 22, 22, 8, 1, 112, 15, 18, 8, 26, 27, 8, 11, 8, 14, 29, 22, + 22, 8, 1, 112, 15, 20, 8, 36, 82, 63, 68, 8, 110, 62, 65, 79, 8, 28, 22, 8, 11, + 8, 14, 23, 23, 26, 26, 8, 1, 112, 15, 18, 2, 28, 27, 8, 11, 8, 14, 23, 27, 22, + 22, 8, 1, 112, 15, 18, 8, 28, 29, 8, 11, 8, 14, 23, 27, 27, 27, 8, 1, 112, 15, + 20, 8, 23, 23, 22, 30, 24, 8, 1, 112, 8, 37, 86, 71, 8, 61, 82, 80, 132, 69, 63, + 68, 81, 80, 83, 75, 72, 72, 33, 8, 23, 31, 23, 30, 2, 28, 27, 8, 11, 8, 14, 23, + 27, 22, 22, 8, 1, 112, 15, 18, 8, 28, 29, 8, 11, 8, 14, 23, 27, 27, 27, 8, 1, + 112, 15, 20, 8, 23, 23, 22, 30, 24, 8, 1, 112, 8, 37, 86, 71, 8, 61, 82, 80, 132, + 69, 63, 68, 81, 80, 83, 75, 72, 72, 33, 8, 23, 31, 23, 30, 2, 67, 65, 68, 65, 74, + 8, 53, 61, 73, 73, 65, 72, 68, 65, 69, 87, 82, 74, 67, 8, 48, 65, 81, 68, 75, 64, + 65, 8, 124, 63, 20, 8, 26, 27, 22, 27, 8, 1, 112, 8, 64, 69, 65, 8, 52, 65, 61, + 72, 132, 63, 68, 82, 72, 65, 8, 39, 69, 80, 78, 75, 132, 69, 81, 69, 75, 74, 80, 66, + 75, 74, 64, 80, 2, 67, 65, 68, 65, 74, 8, 53, 61, 73, 73, 65, 72, 68, 65, 69, 87, + 82, 74, 67, 8, 48, 65, 81, 68, 75, 64, 65, 8, 124, 63, 20, 8, 26, 27, 22, 27, 8, + 1, 112, 8, 64, 69, 65, 8, 52, 65, 61, 72, 132, 63, 68, 82, 72, 65, 8, 39, 69, 80, + 78, 75, 132, 69, 81, 69, 75, 74, 80, 66, 75, 74, 64, 80, 2, 37, 65, 132, 81, 69, 73, + 73, 82, 74, 67, 65, 74, 8, 52, 61, 81, 68, 61, 82, 132, 65, 80, 20, 8, 29, 28, 22, + 28, 8, 1, 112, 8, 66, 110, 79, 8, 41, 65, 72, 64, 81, 79, 82, 78, 78, 65, 74, 8, + 30, 25, 8, 11, 8, 65, 69, 74, 65, 8, 64, 75, 63, 68, 8, 29, 29, 24, 27, 8, 1, + 112, 2, 37, 65, 132, 81, 69, 73, 73, 82, 74, 67, 65, 74, 8, 52, 61, 81, 68, 61, 82, + 132, 65, 80, 20, 8, 29, 28, 22, 28, 8, 1, 112, 8, 66, 110, 79, 8, 41, 65, 72, 64, + 81, 79, 82, 78, 78, 65, 74, 8, 30, 25, 8, 11, 8, 65, 69, 74, 65, 8, 64, 75, 63, + 68, 8, 29, 29, 24, 27, 8, 1, 112, 2, 37, 65, 69, 8, 62, 65, 87, 65, 69, 63, 68, + 74, 65, 81, 8, 47, 61, 67, 65, 78, 72, 61, 74, 20, 8, 29, 29, 27, 30, 8, 1, 112, + 8, 23, 30, 26, 30, 8, 14, 43, 65, 66, 81, 8, 30, 15, 8, 23, 29, 8, 11, 18, 8, + 24, 31, 8, 11, 18, 8, 25, 26, 8, 11, 8, 82, 74, 64, 8, 27, 31, 8, 11, 20, 2, + 37, 65, 69, 8, 62, 65, 87, 65, 69, 63, 68, 74, 65, 81, 8, 47, 61, 67, 65, 78, 72, + 61, 74, 20, 8, 29, 29, 27, 30, 8, 1, 112, 8, 23, 30, 26, 30, 8, 14, 43, 65, 66, + 81, 8, 30, 15, 8, 23, 29, 8, 11, 18, 8, 24, 31, 8, 11, 18, 8, 25, 26, 8, 11, + 8, 82, 74, 64, 8, 27, 31, 8, 11, 20, 2, 23, 28, 11, 18, 8, 23, 23, 11, 18, 8, + 23, 24, 11, 18, 8, 26, 26, 11, 20, 8, 37, 69, 80, 8, 87, 82, 8, 31, 31, 11, 8, + 75, 64, 65, 79, 8, 31, 31, 18, 31, 27, 11, 20, 8, 39, 61, 79, 110, 62, 65, 79, 8, + 67, 65, 68, 65, 74, 64, 2, 23, 28, 11, 18, 8, 23, 23, 11, 18, 8, 23, 24, 11, 18, + 8, 26, 26, 11, 20, 8, 37, 69, 80, 8, 87, 82, 8, 31, 31, 11, 8, 75, 64, 65, 79, + 8, 31, 31, 18, 31, 27, 11, 20, 8, 39, 61, 79, 110, 62, 65, 79, 8, 67, 65, 68, 65, + 74, 64, 2, 24, 25, 18, 25, 27, 8, 11, 20, 8, 36, 82, 63, 68, 8, 25, 24, 18, 25, + 24, 8, 11, 20, 8, 48, 61, 74, 63, 68, 73, 61, 72, 8, 73, 65, 68, 79, 8, 61, 72, + 80, 8, 27, 27, 8, 11, 20, 8, 23, 24, 8, 11, 8, 24, 25, 2, 24, 25, 18, 25, 27, + 8, 11, 20, 8, 36, 82, 63, 68, 8, 25, 24, 18, 25, 24, 8, 11, 20, 8, 48, 61, 74, + 63, 68, 73, 61, 72, 8, 73, 65, 68, 79, 8, 61, 72, 80, 8, 27, 27, 8, 11, 20, 8, + 23, 24, 8, 11, 8, 24, 25, 2, 25, 22, 8, 11, 8, 71, 109, 74, 74, 65, 74, 8, 55, + 72, 73, 65, 74, 4, 36, 72, 72, 65, 65, 8, 62, 61, 82, 72, 69, 63, 68, 65, 8, 83, + 75, 74, 8, 39, 61, 79, 82, 74, 81, 65, 79, 8, 29, 22, 8, 11, 8, 64, 65, 79, 8, + 23, 8, 11, 8, 65, 69, 74, 65, 8, 87, 82, 8, 73, 103, 74, 74, 72, 69, 63, 68, 65, + 74, 8, 23, 31, 23, 29, 2, 25, 22, 8, 11, 8, 71, 109, 74, 74, 65, 74, 8, 55, 72, + 73, 65, 74, 4, 36, 72, 72, 65, 65, 8, 62, 61, 82, 72, 69, 63, 68, 65, 8, 83, 75, + 74, 8, 39, 61, 79, 82, 74, 81, 65, 79, 8, 29, 22, 8, 11, 8, 64, 65, 79, 8, 23, + 8, 11, 8, 65, 69, 74, 65, 8, 87, 82, 8, 73, 103, 74, 74, 72, 69, 63, 68, 65, 74, + 8, 23, 31, 23, 29, 2, 65, 69, 74, 65, 79, 8, 55, 74, 81, 65, 79, 80, 81, 110, 81, + 87, 82, 74, 67, 8, 31, 27, 8, 11, 18, 8, 28, 26, 8, 11, 8, 64, 65, 79, 8, 47, + 65, 68, 79, 78, 72, 103, 74, 65, 20, 8, 27, 29, 8, 11, 8, 84, 69, 64, 65, 79, 132, + 78, 79, 65, 63, 68, 65, 74, 20, 2, 65, 69, 74, 65, 79, 8, 55, 74, 81, 65, 79, 80, + 81, 110, 81, 87, 82, 74, 67, 8, 31, 27, 8, 11, 18, 8, 28, 26, 8, 11, 8, 64, 65, + 79, 8, 47, 65, 68, 79, 78, 72, 103, 74, 65, 20, 8, 27, 29, 8, 11, 8, 84, 69, 64, + 65, 79, 132, 78, 79, 65, 63, 68, 65, 74, 20, 2, 23, 31, 8, 11, 8, 53, 82, 73, 73, + 65, 20, 8, 26, 29, 8, 11, 8, 46, 69, 79, 63, 68, 132, 81, 79, 61, 102, 65, 8, 132, + 75, 74, 64, 65, 79, 74, 8, 61, 74, 8, 23, 30, 31, 29, 8, 36, 62, 74, 61, 68, 73, + 65, 2, 23, 31, 8, 11, 8, 53, 82, 73, 73, 65, 20, 8, 26, 29, 8, 11, 8, 46, 69, + 79, 63, 68, 132, 81, 79, 61, 102, 65, 8, 132, 75, 74, 64, 65, 79, 74, 8, 61, 74, 8, + 23, 30, 31, 29, 8, 36, 62, 74, 61, 68, 73, 65, 2, 27, 29, 8, 11, 8, 46, 79, 65, + 69, 80, 132, 63, 68, 82, 72, 69, 74, 132, 78, 65, 71, 81, 75, 79, 65, 74, 18, 8, 28, + 27, 8, 11, 8, 42, 65, 73, 65, 69, 74, 64, 65, 74, 18, 8, 27, 25, 18, 25, 27, 8, + 11, 8, 47, 61, 74, 64, 71, 79, 65, 69, 132, 65, 18, 2, 27, 29, 8, 11, 8, 46, 79, + 65, 69, 80, 132, 63, 68, 82, 72, 69, 74, 132, 78, 65, 71, 81, 75, 79, 65, 74, 18, 8, + 28, 27, 8, 11, 8, 42, 65, 73, 65, 69, 74, 64, 65, 74, 18, 8, 27, 25, 18, 25, 27, + 8, 11, 8, 47, 61, 74, 64, 71, 79, 65, 69, 132, 65, 18, 2, 26, 29, 18, 24, 27, 8, + 11, 8, 53, 81, 103, 64, 81, 65, 8, 82, 74, 64, 8, 66, 79, 65, 69, 65, 8, 53, 81, + 103, 64, 81, 65, 18, 8, 24, 23, 8, 11, 8, 55, 74, 81, 65, 79, 72, 61, 74, 64, 71, + 79, 65, 69, 132, 65, 18, 8, 29, 8, 11, 8, 57, 61, 72, 64, 67, 65, 62, 69, 65, 81, + 80, 4, 2, 26, 29, 18, 24, 27, 8, 11, 8, 53, 81, 103, 64, 81, 65, 8, 82, 74, 64, + 8, 66, 79, 65, 69, 65, 8, 53, 81, 103, 64, 81, 65, 18, 8, 24, 23, 8, 11, 8, 55, + 74, 81, 65, 79, 72, 61, 74, 64, 71, 79, 65, 69, 132, 65, 18, 8, 29, 8, 11, 8, 57, + 61, 72, 64, 67, 65, 62, 69, 65, 81, 80, 4, 2, 83, 65, 79, 81, 65, 69, 72, 82, 74, + 67, 65, 74, 18, 8, 64, 61, 79, 82, 74, 81, 65, 79, 8, 25, 24, 8, 11, 8, 7, 43, + 75, 63, 68, 84, 103, 72, 64, 65, 79, 6, 18, 8, 24, 24, 8, 11, 8, 48, 69, 80, 63, + 68, 84, 103, 72, 64, 65, 79, 18, 8, 25, 29, 18, 24, 8, 11, 8, 49, 61, 64, 65, 72, + 4, 2, 83, 65, 79, 81, 65, 69, 72, 82, 74, 67, 65, 74, 18, 8, 64, 61, 79, 82, 74, + 81, 65, 79, 8, 25, 24, 8, 11, 8, 7, 43, 75, 63, 68, 84, 103, 72, 64, 65, 79, 6, + 18, 8, 24, 24, 8, 11, 8, 48, 69, 80, 63, 68, 84, 103, 72, 64, 65, 79, 18, 8, 25, + 29, 18, 24, 8, 11, 8, 49, 61, 64, 65, 72, 4, 2, 84, 103, 72, 64, 65, 79, 18, 8, + 23, 23, 8, 11, 8, 47, 61, 82, 62, 84, 103, 72, 64, 65, 79, 18, 8, 23, 22, 8, 11, + 8, 53, 110, 73, 78, 66, 65, 18, 8, 27, 18, 30, 29, 8, 11, 8, 53, 81, 65, 78, 78, + 65, 74, 18, 8, 24, 18, 24, 25, 8, 11, 8, 36, 72, 73, 65, 74, 2, 84, 103, 72, 64, + 65, 79, 18, 8, 23, 23, 8, 11, 8, 47, 61, 82, 62, 84, 103, 72, 64, 65, 79, 18, 8, + 23, 22, 8, 11, 8, 53, 110, 73, 78, 66, 65, 18, 8, 27, 18, 30, 29, 8, 11, 8, 53, + 81, 65, 78, 78, 65, 74, 18, 8, 24, 18, 24, 25, 8, 11, 8, 36, 72, 73, 65, 74, 2, + 64, 61, 67, 65, 67, 65, 74, 8, 49, 65, 84, 8, 59, 75, 79, 71, 18, 8, 59, 65, 79, + 84, 61, 74, 8, 82, 74, 64, 8, 59, 65, 73, 65, 74, 20, 8, 59, 61, 63, 68, 81, 83, + 65, 79, 71, 103, 82, 66, 65, 8, 87, 82, 8, 23, 27, 8, 11, 8, 67, 65, 132, 81, 69, + 65, 67, 65, 74, 20, 2, 64, 61, 67, 65, 67, 65, 74, 8, 49, 65, 84, 8, 59, 75, 79, + 71, 18, 8, 59, 65, 79, 84, 61, 74, 8, 82, 74, 64, 8, 59, 65, 73, 65, 74, 20, 8, + 59, 61, 63, 68, 81, 83, 65, 79, 71, 103, 82, 66, 65, 8, 87, 82, 8, 23, 27, 8, 11, + 8, 67, 65, 132, 81, 69, 65, 67, 65, 74, 20, 2, 59, 61, 63, 68, 81, 61, 62, 67, 103, + 74, 67, 65, 8, 87, 82, 8, 24, 27, 8, 11, 8, 67, 65, 66, 61, 72, 72, 65, 74, 20, + 8, 7, 39, 65, 79, 8, 59, 65, 74, 8, 66, 69, 65, 72, 8, 82, 73, 8, 23, 23, 8, + 11, 18, 8, 67, 65, 67, 65, 74, 110, 62, 65, 79, 8, 24, 27, 8, 11, 8, 64, 65, 80, + 2, 59, 61, 63, 68, 81, 61, 62, 67, 103, 74, 67, 65, 8, 87, 82, 8, 24, 27, 8, 11, + 8, 67, 65, 66, 61, 72, 72, 65, 74, 20, 8, 7, 39, 65, 79, 8, 59, 65, 74, 8, 66, + 69, 65, 72, 8, 82, 73, 8, 23, 23, 8, 11, 18, 8, 67, 65, 67, 65, 74, 110, 62, 65, + 79, 8, 24, 27, 8, 11, 8, 64, 65, 80, 2, 56, 75, 79, 70, 61, 68, 79, 65, 80, 20, + 6, 8, 14, 24, 26, 8, 11, 8, 67, 65, 67, 65, 74, 110, 62, 65, 79, 8, 26, 24, 8, + 11, 20, 8, 39, 61, 74, 65, 62, 65, 74, 8, 61, 82, 63, 68, 8, 36, 74, 132, 81, 69, + 65, 67, 65, 8, 82, 73, 8, 23, 23, 8, 11, 18, 8, 24, 24, 8, 11, 18, 8, 25, 26, + 8, 11, 18, 2, 56, 75, 79, 70, 61, 68, 79, 65, 80, 20, 6, 8, 14, 24, 26, 8, 11, + 8, 67, 65, 67, 65, 74, 110, 62, 65, 79, 8, 26, 24, 8, 11, 20, 8, 39, 61, 74, 65, + 62, 65, 74, 8, 61, 82, 63, 68, 8, 36, 74, 132, 81, 69, 65, 67, 65, 8, 82, 73, 8, + 23, 23, 8, 11, 18, 8, 24, 24, 8, 11, 18, 8, 25, 26, 8, 11, 18, 2, 27, 26, 8, + 11, 18, 8, 28, 31, 8, 11, 18, 8, 29, 28, 8, 11, 18, 8, 30, 29, 8, 11, 18, 8, + 31, 30, 8, 11, 8, 82, 74, 64, 8, 31, 31, 11, 20, 15, 2, 27, 26, 8, 11, 18, 8, + 28, 31, 8, 11, 18, 8, 29, 28, 8, 11, 18, 8, 30, 29, 8, 11, 18, 8, 31, 30, 8, + 11, 8, 82, 74, 64, 8, 31, 31, 11, 20, 15, 2, 39, 69, 65, 8, 52, 110, 63, 71, 132, + 69, 63, 68, 81, 8, 7, 36, 82, 80, 67, 65, 132, 81, 61, 72, 81, 82, 74, 67, 6, 8, + 75, 64, 65, 79, 8, 37, 75, 81, 65, 74, 73, 65, 69, 132, 81, 65, 79, 65, 69, 20, 8, + 55, 74, 132, 65, 79, 73, 8, 7, 40, 69, 74, 87, 65, 72, 66, 61, 72, 72, 65, 6, 8, + 64, 65, 80, 68, 61, 72, 62, 2, 39, 69, 65, 8, 52, 110, 63, 71, 132, 69, 63, 68, 81, + 8, 7, 36, 82, 80, 67, 65, 132, 81, 61, 72, 81, 82, 74, 67, 6, 8, 75, 64, 65, 79, + 8, 37, 75, 81, 65, 74, 73, 65, 69, 132, 81, 65, 79, 65, 69, 20, 8, 55, 74, 132, 65, + 79, 73, 8, 7, 40, 69, 74, 87, 65, 72, 66, 61, 72, 72, 65, 6, 8, 64, 65, 80, 68, + 61, 72, 62, 2, 7, 47, 61, 82, 62, 84, 103, 72, 64, 65, 79, 6, 18, 8, 64, 61, 79, + 82, 73, 8, 61, 62, 65, 79, 8, 82, 74, 132, 65, 79, 73, 8, 7, 36, 79, 62, 65, 69, + 81, 65, 79, 6, 18, 8, 61, 62, 65, 79, 8, 7, 62, 65, 132, 75, 74, 64, 65, 79, 80, + 6, 8, 64, 65, 80, 68, 61, 72, 62, 8, 62, 65, 132, 63, 68, 61, 66, 66, 65, 74, 20, + 2, 7, 47, 61, 82, 62, 84, 103, 72, 64, 65, 79, 6, 18, 8, 64, 61, 79, 82, 73, 8, + 61, 62, 65, 79, 8, 82, 74, 132, 65, 79, 73, 8, 7, 36, 79, 62, 65, 69, 81, 65, 79, + 6, 18, 8, 61, 62, 65, 79, 8, 7, 62, 65, 132, 75, 74, 64, 65, 79, 80, 6, 8, 64, + 65, 80, 68, 61, 72, 62, 8, 62, 65, 132, 63, 68, 61, 66, 66, 65, 74, 20, 2, 39, 69, + 65, 8, 53, 81, 69, 73, 73, 65, 74, 8, 7, 42, 65, 73, 65, 69, 74, 64, 65, 62, 65, + 132, 63, 68, 72, 82, 102, 6, 8, 64, 61, 79, 82, 73, 8, 132, 75, 72, 72, 81, 65, 18, + 8, 64, 65, 80, 68, 61, 72, 62, 8, 64, 65, 79, 8, 37, 65, 132, 63, 68, 72, 82, 102, + 8, 7, 132, 63, 68, 84, 61, 74, 71, 81, 65, 74, 6, 2, 39, 69, 65, 8, 53, 81, 69, + 73, 73, 65, 74, 8, 7, 42, 65, 73, 65, 69, 74, 64, 65, 62, 65, 132, 63, 68, 72, 82, + 102, 6, 8, 64, 61, 79, 82, 73, 8, 132, 75, 72, 72, 81, 65, 18, 8, 64, 65, 80, 68, + 61, 72, 62, 8, 64, 65, 79, 8, 37, 65, 132, 63, 68, 72, 82, 102, 8, 7, 132, 63, 68, + 84, 61, 74, 71, 81, 65, 74, 6, 2, 47, 65, 68, 79, 78, 72, 103, 74, 65, 20, 8, 57, + 65, 69, 72, 8, 7, 67, 65, 81, 79, 75, 132, 81, 6, 8, 37, 82, 79, 20, 4, 42, 65, + 68, 20, 32, 8, 64, 61, 79, 82, 73, 8, 7, 53, 63, 68, 82, 72, 65, 74, 6, 8, 65, + 69, 74, 73, 61, 72, 8, 67, 65, 72, 81, 65, 74, 20, 2, 47, 65, 68, 79, 78, 72, 103, + 74, 65, 20, 8, 57, 65, 69, 72, 8, 7, 67, 65, 81, 79, 75, 132, 81, 6, 8, 37, 82, + 79, 20, 4, 42, 65, 68, 20, 32, 8, 64, 61, 79, 82, 73, 8, 7, 53, 63, 68, 82, 72, + 65, 74, 6, 8, 65, 69, 74, 73, 61, 72, 8, 67, 65, 72, 81, 65, 74, 20, 2, 42, 65, + 79, 103, 81, 65, 8, 7, 42, 82, 132, 81, 61, 83, 6, 8, 75, 64, 65, 79, 8, 83, 75, + 72, 72, 71, 75, 73, 73, 65, 74, 18, 8, 64, 65, 80, 68, 61, 72, 62, 8, 7, 132, 63, + 68, 65, 69, 74, 81, 6, 8, 91, 8, 25, 24, 28, 21, 24, 24, 8, 64, 61, 79, 82, 73, + 8, 7, 56, 65, 79, 73, 103, 63, 68, 81, 74, 69, 132, 132, 65, 80, 6, 8, 132, 75, 72, + 72, 81, 65, 74, 18, 2, 42, 65, 79, 103, 81, 65, 8, 7, 42, 82, 132, 81, 61, 83, 6, + 8, 75, 64, 65, 79, 8, 83, 75, 72, 72, 71, 75, 73, 73, 65, 74, 18, 8, 64, 65, 80, + 68, 61, 72, 62, 8, 7, 132, 63, 68, 65, 69, 74, 81, 6, 8, 91, 8, 25, 24, 28, 21, + 24, 24, 8, 64, 61, 79, 82, 73, 8, 7, 56, 65, 79, 73, 103, 63, 68, 81, 74, 69, 132, + 132, 65, 80, 6, 8, 132, 75, 72, 72, 81, 65, 74, 18, 2, 67, 65, 71, 75, 73, 73, 65, + 74, 8, 7, 67, 65, 132, 81, 110, 81, 87, 81, 6, 8, 61, 62, 65, 79, 8, 54, 65, 69, + 72, 62, 65, 81, 79, 61, 67, 8, 82, 74, 64, 8, 7, 84, 65, 79, 64, 65, 74, 6, 8, + 36, 74, 132, 63, 68, 61, 82, 82, 74, 67, 8, 61, 62, 65, 79, 8, 7, 53, 63, 68, 82, + 72, 64, 65, 74, 83, 65, 79, 84, 61, 72, 81, 82, 74, 67, 6, 2, 67, 65, 71, 75, 73, + 73, 65, 74, 8, 7, 67, 65, 132, 81, 110, 81, 87, 81, 6, 8, 61, 62, 65, 79, 8, 54, + 65, 69, 72, 62, 65, 81, 79, 61, 67, 8, 82, 74, 64, 8, 7, 84, 65, 79, 64, 65, 74, + 6, 8, 36, 74, 132, 63, 68, 61, 82, 82, 74, 67, 8, 61, 62, 65, 79, 8, 7, 53, 63, + 68, 82, 72, 64, 65, 74, 83, 65, 79, 84, 61, 72, 81, 82, 74, 67, 6, 2, 73, 69, 81, + 81, 65, 72, 62, 61, 79, 8, 7, 46, 72, 65, 69, 74, 84, 75, 68, 74, 82, 74, 67, 65, + 74, 6, 8, 61, 62, 65, 79, 8, 47, 65, 68, 79, 78, 72, 103, 74, 65, 74, 8, 64, 61, + 79, 82, 73, 8, 7, 83, 75, 79, 67, 65, 67, 61, 74, 67, 65, 74, 6, 8, 51, 65, 74, + 87, 69, 67, 2, 73, 69, 81, 81, 65, 72, 62, 61, 79, 8, 7, 46, 72, 65, 69, 74, 84, + 75, 68, 74, 82, 74, 67, 65, 74, 6, 8, 61, 62, 65, 79, 8, 47, 65, 68, 79, 78, 72, + 103, 74, 65, 74, 8, 64, 61, 79, 82, 73, 8, 7, 83, 75, 79, 67, 65, 67, 61, 74, 67, + 65, 74, 6, 8, 51, 65, 74, 87, 69, 67, 2, 64, 61, 79, 82, 73, 8, 7, 69, 74, 80, + 62, 65, 132, 75, 74, 64, 65, 79, 65, 6, 8, 40, 69, 74, 67, 61, 74, 67, 33, 8, 45, + 61, 68, 79, 65, 74, 8, 7, 83, 75, 79, 61, 82, 80, 132, 69, 63, 68, 81, 72, 69, 63, + 68, 6, 8, 84, 65, 80, 68, 61, 72, 62, 8, 39, 65, 87, 65, 73, 62, 65, 79, 2, 64, + 61, 79, 82, 73, 8, 7, 69, 74, 80, 62, 65, 132, 75, 74, 64, 65, 79, 65, 6, 8, 40, + 69, 74, 67, 61, 74, 67, 33, 8, 45, 61, 68, 79, 65, 74, 8, 7, 83, 75, 79, 61, 82, + 80, 132, 69, 63, 68, 81, 72, 69, 63, 68, 6, 8, 84, 65, 80, 68, 61, 72, 62, 8, 39, + 65, 87, 65, 73, 62, 65, 79, 2, 82, 74, 64, 8, 7, 53, 82, 73, 73, 65, 20, 6, 8, + 56, 65, 79, 84, 61, 72, 81, 65, 79, 32, 8, 82, 74, 64, 8, 7, 83, 75, 79, 68, 69, + 74, 6, 8, 81, 79, 61, 81, 65, 74, 8, 61, 74, 64, 65, 79, 65, 8, 78, 110, 74, 71, + 81, 72, 69, 63, 68, 8, 7, 84, 65, 79, 64, 65, 74, 6, 2, 82, 74, 64, 8, 7, 53, + 82, 73, 73, 65, 20, 6, 8, 56, 65, 79, 84, 61, 72, 81, 65, 79, 32, 8, 82, 74, 64, + 8, 7, 83, 75, 79, 68, 69, 74, 6, 8, 81, 79, 61, 81, 65, 74, 8, 61, 74, 64, 65, + 79, 65, 8, 78, 110, 74, 71, 81, 72, 69, 63, 68, 8, 7, 84, 65, 79, 64, 65, 74, 6, + 2, 82, 74, 64, 8, 46, 72, 65, 69, 74, 84, 75, 68, 74, 82, 74, 67, 65, 74, 8, 82, + 74, 64, 8, 14, 7, 55, 74, 79, 82, 68, 65, 6, 15, 8, 60, 69, 73, 73, 65, 79, 8, + 61, 62, 65, 79, 8, 7, 61, 74, 64, 65, 79, 65, 79, 80, 65, 69, 81, 80, 6, 8, 71, + 75, 73, 73, 65, 74, 2, 82, 74, 64, 8, 46, 72, 65, 69, 74, 84, 75, 68, 74, 82, 74, + 67, 65, 74, 8, 82, 74, 64, 8, 14, 7, 55, 74, 79, 82, 68, 65, 6, 15, 8, 60, 69, + 73, 73, 65, 79, 8, 61, 62, 65, 79, 8, 7, 61, 74, 64, 65, 79, 65, 79, 80, 65, 69, + 81, 80, 6, 8, 71, 75, 73, 73, 65, 74, 2, 82, 73, 132, 81, 79, 69, 81, 81, 65, 74, + 65, 79, 8, 7, 42, 65, 132, 63, 68, 103, 66, 81, 80, 83, 65, 79, 84, 61, 72, 81, 82, + 74, 67, 6, 8, 75, 64, 65, 79, 8, 60, 65, 69, 81, 65, 74, 8, 64, 65, 80, 68, 61, + 72, 62, 8, 14, 7, 36, 78, 78, 72, 61, 82, 132, 6, 15, 2, 82, 73, 132, 81, 79, 69, + 81, 81, 65, 74, 65, 79, 8, 7, 42, 65, 132, 63, 68, 103, 66, 81, 80, 83, 65, 79, 84, + 61, 72, 81, 82, 74, 67, 6, 8, 75, 64, 65, 79, 8, 60, 65, 69, 81, 65, 74, 8, 64, + 65, 80, 68, 61, 72, 62, 8, 14, 7, 36, 78, 78, 72, 61, 82, 132, 6, 15, 2, 132, 78, + 79, 65, 63, 68, 65, 8, 64, 65, 80, 68, 61, 72, 62, 8, 7, 64, 69, 65, 132, 65, 79, + 6, 8, 14, 7, 57, 61, 68, 79, 72, 69, 63, 68, 9, 6, 15, 20, 8, 52, 65, 69, 68, + 65, 74, 20, 8, 7, 53, 81, 61, 81, 69, 132, 81, 69, 71, 6, 8, 53, 63, 68, 84, 69, + 65, 79, 69, 67, 71, 65, 69, 81, 65, 74, 2, 132, 78, 79, 65, 63, 68, 65, 8, 64, 65, + 80, 68, 61, 72, 62, 8, 7, 64, 69, 65, 132, 65, 79, 6, 8, 14, 7, 57, 61, 68, 79, + 72, 69, 63, 68, 9, 6, 15, 20, 8, 52, 65, 69, 68, 65, 74, 20, 8, 7, 53, 81, 61, + 81, 69, 132, 81, 69, 71, 6, 8, 53, 63, 68, 84, 69, 65, 79, 69, 67, 71, 65, 69, 81, + 65, 74, 2, 84, 65, 80, 68, 61, 72, 62, 8, 14, 7, 40, 69, 74, 84, 61, 74, 64, 6, + 15, 8, 7, 48, 61, 132, 63, 68, 69, 74, 65, 74, 4, 8, 64, 61, 79, 82, 73, 8, 7, + 52, 110, 63, 71, 67, 61, 74, 67, 6, 8, 62, 65, 69, 64, 65, 74, 8, 42, 65, 64, 61, + 74, 71, 65, 8, 7, 36, 62, 132, 81, 69, 73, 73, 82, 74, 67, 6, 2, 84, 65, 80, 68, + 61, 72, 62, 8, 14, 7, 40, 69, 74, 84, 61, 74, 64, 6, 15, 8, 7, 48, 61, 132, 63, + 68, 69, 74, 65, 74, 4, 8, 64, 61, 79, 82, 73, 8, 7, 52, 110, 63, 71, 67, 61, 74, + 67, 6, 8, 62, 65, 69, 64, 65, 74, 8, 42, 65, 64, 61, 74, 71, 65, 8, 7, 36, 62, + 132, 81, 69, 73, 73, 82, 74, 67, 6, 2, 91, 8, 26, 8, 64, 65, 80, 68, 61, 72, 62, + 8, 37, 72, 82, 73, 65, 74, 132, 81, 79, 61, 102, 65, 8, 14, 7, 36, 78, 78, 72, 61, + 82, 132, 6, 15, 20, 8, 91, 8, 29, 30, 8, 65, 79, 67, 65, 62, 65, 74, 8, 64, 65, + 80, 68, 61, 72, 62, 8, 7, 37, 82, 79, 20, 4, 42, 65, 68, 20, 32, 6, 8, 110, 62, + 79, 69, 67, 65, 74, 8, 91, 8, 24, 31, 2, 91, 8, 26, 8, 64, 65, 80, 68, 61, 72, + 62, 8, 37, 72, 82, 73, 65, 74, 132, 81, 79, 61, 102, 65, 8, 14, 7, 36, 78, 78, 72, + 61, 82, 132, 6, 15, 20, 8, 91, 8, 29, 30, 8, 65, 79, 67, 65, 62, 65, 74, 8, 64, + 65, 80, 68, 61, 72, 62, 8, 7, 37, 82, 79, 20, 4, 42, 65, 68, 20, 32, 6, 8, 110, + 62, 79, 69, 67, 65, 74, 8, 91, 8, 24, 31, 2, 48, 69, 63, 68, 61, 65, 72, 69, 80, + 8, 7, 64, 65, 73, 132, 65, 72, 62, 65, 74, 6, 8, 91, 8, 27, 8, 84, 65, 80, 68, + 61, 72, 62, 8, 41, 103, 72, 72, 65, 74, 8, 75, 64, 65, 79, 8, 14, 7, 55, 74, 79, + 82, 68, 65, 6, 15, 8, 91, 8, 26, 8, 62, 65, 69, 72, 103, 82, 66, 69, 67, 65, 79, + 18, 2, 48, 69, 63, 68, 61, 65, 72, 69, 80, 8, 7, 64, 65, 73, 132, 65, 72, 62, 65, + 74, 6, 8, 91, 8, 27, 8, 84, 65, 80, 68, 61, 72, 62, 8, 41, 103, 72, 72, 65, 74, + 8, 75, 64, 65, 79, 8, 14, 7, 55, 74, 79, 82, 68, 65, 6, 15, 8, 91, 8, 26, 8, + 62, 65, 69, 72, 103, 82, 66, 69, 67, 65, 79, 18, 2, 61, 62, 65, 79, 8, 7, 68, 65, + 82, 81, 69, 67, 65, 74, 8, 39, 61, 81, 82, 73, 80, 6, 8, 64, 65, 79, 70, 65, 74, + 69, 67, 65, 8, 91, 8, 23, 25, 8, 14, 53, 81, 61, 64, 81, 132, 63, 68, 82, 72, 79, + 61, 81, 15, 8, 7, 68, 69, 74, 81, 65, 79, 65, 74, 6, 8, 91, 8, 23, 23, 2, 61, + 62, 65, 79, 8, 7, 68, 65, 82, 81, 69, 67, 65, 74, 8, 39, 61, 81, 82, 73, 80, 6, + 8, 64, 65, 79, 70, 65, 74, 69, 67, 65, 8, 91, 8, 23, 25, 8, 14, 53, 81, 61, 64, + 81, 132, 63, 68, 82, 72, 79, 61, 81, 15, 8, 7, 68, 69, 74, 81, 65, 79, 65, 74, 6, + 8, 91, 8, 23, 23, 2, 82, 74, 64, 8, 84, 65, 69, 81, 65, 79, 65, 80, 8, 82, 74, + 64, 8, 14, 7, 36, 78, 78, 72, 61, 82, 132, 6, 15, 8, 91, 8, 27, 27, 18, 8, 84, + 65, 69, 72, 8, 7, 56, 65, 79, 73, 69, 74, 64, 65, 79, 82, 74, 67, 6, 8, 42, 65, + 64, 82, 72, 64, 20, 8, 91, 8, 24, 23, 2, 82, 74, 64, 8, 84, 65, 69, 81, 65, 79, + 65, 80, 8, 82, 74, 64, 8, 14, 7, 36, 78, 78, 72, 61, 82, 132, 6, 15, 8, 91, 8, + 27, 27, 18, 8, 84, 65, 69, 72, 8, 7, 56, 65, 79, 73, 69, 74, 64, 65, 79, 82, 74, + 67, 6, 8, 42, 65, 64, 82, 72, 64, 20, 8, 91, 8, 24, 23, 2, 40, 79, 68, 109, 68, + 82, 74, 67, 8, 7, 36, 74, 81, 79, 61, 67, 6, 8, 91, 8, 30, 26, 8, 61, 62, 65, + 79, 8, 65, 69, 74, 67, 65, 66, 82, 74, 64, 65, 74, 8, 84, 65, 80, 68, 61, 72, 62, + 8, 14, 7, 60, 84, 69, 132, 63, 68, 65, 74, 79, 82, 66, 6, 15, 8, 91, 8, 26, 26, + 8, 57, 61, 74, 64, 65, 79, 82, 74, 67, 2, 40, 79, 68, 109, 68, 82, 74, 67, 8, 7, + 36, 74, 81, 79, 61, 67, 6, 8, 91, 8, 30, 26, 8, 61, 62, 65, 79, 8, 65, 69, 74, + 67, 65, 66, 82, 74, 64, 65, 74, 8, 84, 65, 80, 68, 61, 72, 62, 8, 14, 7, 60, 84, + 69, 132, 63, 68, 65, 74, 79, 82, 66, 6, 15, 8, 91, 8, 26, 26, 8, 57, 61, 74, 64, + 65, 79, 82, 74, 67, 2, 64, 61, 79, 82, 73, 8, 7, 83, 69, 65, 72, 72, 65, 69, 63, + 68, 81, 6, 8, 7, 83, 65, 79, 81, 79, 65, 81, 65, 74, 8, 69, 74, 8, 91, 8, 23, + 26, 6, 18, 8, 64, 61, 73, 69, 81, 8, 7, 36, 82, 80, 67, 65, 132, 81, 61, 72, 81, + 82, 74, 67, 6, 8, 91, 8, 27, 28, 8, 82, 74, 64, 8, 51, 79, 110, 66, 82, 74, 67, + 2, 64, 61, 79, 82, 73, 8, 7, 83, 69, 65, 72, 72, 65, 69, 63, 68, 81, 6, 8, 7, + 83, 65, 79, 81, 79, 65, 81, 65, 74, 8, 69, 74, 8, 91, 8, 23, 26, 6, 18, 8, 64, + 61, 73, 69, 81, 8, 7, 36, 82, 80, 67, 65, 132, 81, 61, 72, 81, 82, 74, 67, 6, 8, + 91, 8, 27, 28, 8, 82, 74, 64, 8, 51, 79, 110, 66, 82, 74, 67, 2, 64, 65, 79, 8, + 14, 7, 55, 74, 79, 82, 68, 65, 6, 15, 8, 91, 8, 30, 30, 8, 55, 74, 81, 65, 79, + 72, 61, 74, 64, 71, 79, 65, 69, 132, 65, 8, 14, 84, 65, 69, 72, 8, 7, 48, 65, 68, + 79, 61, 82, 80, 67, 61, 62, 65, 74, 6, 8, 57, 61, 68, 72, 79, 65, 132, 82, 72, 81, + 61, 81, 80, 15, 2, 64, 65, 79, 8, 14, 7, 55, 74, 79, 82, 68, 65, 6, 15, 8, 91, + 8, 30, 30, 8, 55, 74, 81, 65, 79, 72, 61, 74, 64, 71, 79, 65, 69, 132, 65, 8, 14, + 84, 65, 69, 72, 8, 7, 48, 65, 68, 79, 61, 82, 80, 67, 61, 62, 65, 74, 6, 8, 57, + 61, 68, 72, 79, 65, 132, 82, 72, 81, 61, 81, 80, 15, 2, 91, 8, 29, 22, 8, 50, 62, + 65, 79, 72, 61, 74, 64, 73, 65, 132, 132, 65, 79, 32, 8, 7, 61, 74, 64, 65, 79, 65, + 79, 6, 8, 91, 8, 27, 27, 8, 64, 65, 80, 68, 61, 72, 62, 8, 56, 75, 79, 73, 69, + 81, 81, 61, 67, 80, 8, 84, 65, 80, 68, 61, 72, 62, 8, 14, 7, 40, 69, 74, 84, 61, + 74, 64, 6, 15, 2, 91, 8, 29, 22, 8, 50, 62, 65, 79, 72, 61, 74, 64, 73, 65, 132, + 132, 65, 79, 32, 8, 7, 61, 74, 64, 65, 79, 65, 79, 6, 8, 91, 8, 27, 27, 8, 64, + 65, 80, 68, 61, 72, 62, 8, 56, 75, 79, 73, 69, 81, 81, 61, 67, 80, 8, 84, 65, 80, + 68, 61, 72, 62, 8, 14, 7, 40, 69, 74, 84, 61, 74, 64, 6, 15, 2, 84, 65, 69, 81, + 65, 79, 65, 8, 7, 84, 75, 79, 64, 65, 74, 6, 8, 91, 8, 28, 29, 8, 82, 79, 72, + 61, 82, 62, 82, 74, 67, 8, 84, 65, 69, 72, 8, 7, 37, 82, 63, 68, 68, 61, 72, 81, + 65, 79, 132, 81, 61, 74, 64, 65, 6, 2, 84, 65, 69, 81, 65, 79, 65, 8, 7, 84, 75, + 79, 64, 65, 74, 6, 8, 91, 8, 28, 29, 8, 82, 79, 72, 61, 82, 62, 82, 74, 67, 8, + 84, 65, 69, 72, 8, 7, 37, 82, 63, 68, 68, 61, 72, 81, 65, 79, 132, 81, 61, 74, 64, + 65, 6, 2, 84, 65, 80, 68, 61, 72, 62, 8, 53, 81, 79, 65, 69, 81, 69, 67, 71, 65, + 69, 81, 65, 74, 8, 91, 8, 30, 26, 8, 82, 74, 64, 8, 14, 7, 60, 84, 69, 132, 63, + 68, 65, 74, 79, 82, 66, 6, 15, 8, 91, 8, 23, 25, 8, 40, 79, 68, 109, 68, 82, 74, + 67, 2, 84, 65, 80, 68, 61, 72, 62, 8, 53, 81, 79, 65, 69, 81, 69, 67, 71, 65, 69, + 81, 65, 74, 8, 91, 8, 30, 26, 8, 82, 74, 64, 8, 14, 7, 60, 84, 69, 132, 63, 68, + 65, 74, 79, 82, 66, 6, 15, 8, 91, 8, 23, 25, 8, 40, 79, 68, 109, 68, 82, 74, 67, + 2, 75, 64, 65, 79, 8, 7, 36, 62, 132, 81, 69, 73, 73, 82, 74, 67, 6, 8, 79, 65, + 67, 65, 72, 73, 103, 102, 69, 67, 8, 91, 8, 27, 24, 8, 53, 63, 68, 84, 69, 65, 79, + 69, 67, 71, 65, 69, 81, 65, 74, 8, 7, 61, 74, 64, 65, 79, 65, 6, 8, 84, 65, 80, + 68, 61, 72, 62, 8, 64, 69, 79, 65, 71, 81, 8, 91, 8, 28, 30, 8, 82, 74, 64, 2, + 75, 64, 65, 79, 8, 7, 36, 62, 132, 81, 69, 73, 73, 82, 74, 67, 6, 8, 79, 65, 67, + 65, 72, 73, 103, 102, 69, 67, 8, 91, 8, 27, 24, 8, 53, 63, 68, 84, 69, 65, 79, 69, + 67, 71, 65, 69, 81, 65, 74, 8, 7, 61, 74, 64, 65, 79, 65, 6, 8, 84, 65, 80, 68, + 61, 72, 62, 8, 64, 69, 79, 65, 71, 81, 8, 91, 8, 28, 30, 8, 82, 74, 64, 2, 14, + 7, 55, 74, 79, 82, 68, 65, 6, 15, 8, 91, 8, 23, 26, 8, 62, 65, 79, 65, 63, 68, + 74, 65, 81, 8, 75, 64, 65, 79, 8, 7, 64, 65, 80, 67, 72, 20, 6, 8, 53, 81, 65, + 72, 72, 65, 8, 91, 8, 25, 29, 2, 14, 7, 55, 74, 79, 82, 68, 65, 6, 15, 8, 91, + 8, 23, 26, 8, 62, 65, 79, 65, 63, 68, 74, 65, 81, 8, 75, 64, 65, 79, 8, 7, 64, + 65, 80, 67, 72, 20, 6, 8, 53, 81, 65, 72, 72, 65, 8, 91, 8, 25, 29, 2, 41, 65, + 69, 65, 79, 72, 69, 63, 68, 8, 7, 84, 65, 79, 64, 65, 74, 6, 8, 91, 8, 31, 8, + 64, 65, 80, 68, 61, 72, 62, 8, 64, 65, 79, 8, 56, 65, 79, 84, 61, 72, 81, 82, 74, + 67, 20, 8, 36, 82, 63, 68, 8, 14, 7, 60, 84, 69, 132, 63, 68, 65, 74, 79, 82, 66, + 6, 15, 8, 91, 8, 30, 22, 8, 65, 74, 81, 68, 103, 72, 81, 2, 41, 65, 69, 65, 79, + 72, 69, 63, 68, 8, 7, 84, 65, 79, 64, 65, 74, 6, 8, 91, 8, 31, 8, 64, 65, 80, + 68, 61, 72, 62, 8, 64, 65, 79, 8, 56, 65, 79, 84, 61, 72, 81, 82, 74, 67, 20, 8, + 36, 82, 63, 68, 8, 14, 7, 60, 84, 69, 132, 63, 68, 65, 74, 79, 82, 66, 6, 15, 8, + 91, 8, 30, 22, 8, 65, 74, 81, 68, 103, 72, 81, 2, 75, 64, 65, 79, 8, 7, 23, 31, + 23, 24, 21, 23, 29, 6, 18, 8, 132, 75, 74, 64, 65, 79, 74, 8, 91, 8, 31, 30, 33, + 8, 14, 68, 65, 66, 81, 69, 67, 65, 79, 8, 7, 53, 81, 69, 73, 73, 87, 65, 81, 81, + 65, 72, 6, 8, 91, 8, 26, 15, 18, 8, 61, 62, 65, 79, 8, 62, 65, 64, 69, 74, 67, + 81, 18, 2, 75, 64, 65, 79, 8, 7, 23, 31, 23, 24, 21, 23, 29, 6, 18, 8, 132, 75, + 74, 64, 65, 79, 74, 8, 91, 8, 31, 30, 33, 8, 14, 68, 65, 66, 81, 69, 67, 65, 79, + 8, 7, 53, 81, 69, 73, 73, 87, 65, 81, 81, 65, 72, 6, 8, 91, 8, 26, 15, 18, 8, + 61, 62, 65, 79, 8, 62, 65, 64, 69, 74, 67, 81, 18, 2, 64, 65, 80, 68, 61, 72, 62, + 8, 14, 7, 36, 78, 78, 72, 61, 82, 132, 6, 15, 8, 91, 8, 27, 31, 8, 53, 63, 68, + 84, 61, 74, 71, 82, 74, 67, 8, 82, 74, 64, 8, 7, 87, 84, 69, 132, 63, 68, 65, 74, + 6, 8, 36, 74, 81, 79, 61, 67, 8, 91, 8, 26, 22, 2, 64, 65, 80, 68, 61, 72, 62, + 8, 14, 7, 36, 78, 78, 72, 61, 82, 132, 6, 15, 8, 91, 8, 27, 31, 8, 53, 63, 68, + 84, 61, 74, 71, 82, 74, 67, 8, 82, 74, 64, 8, 7, 87, 84, 69, 132, 63, 68, 65, 74, + 6, 8, 36, 74, 81, 79, 61, 67, 8, 91, 8, 26, 22, 2, 7, 83, 75, 79, 67, 65, 67, + 61, 74, 67, 65, 74, 6, 8, 7, 53, 81, 65, 72, 72, 65, 6, 8, 91, 8, 25, 30, 8, + 82, 74, 64, 8, 67, 65, 81, 79, 69, 65, 62, 65, 74, 35, 8, 57, 65, 69, 72, 8, 14, + 7, 64, 69, 65, 132, 65, 73, 6, 15, 8, 91, 8, 25, 24, 8, 82, 74, 64, 8, 7, 71, + 109, 74, 74, 81, 65, 74, 6, 8, 74, 103, 63, 68, 132, 81, 65, 74, 2, 7, 83, 75, 79, + 67, 65, 67, 61, 74, 67, 65, 74, 6, 8, 7, 53, 81, 65, 72, 72, 65, 6, 8, 91, 8, + 25, 30, 8, 82, 74, 64, 8, 67, 65, 81, 79, 69, 65, 62, 65, 74, 35, 8, 57, 65, 69, + 72, 8, 14, 7, 64, 69, 65, 132, 65, 73, 6, 15, 8, 91, 8, 25, 24, 8, 82, 74, 64, + 8, 7, 71, 109, 74, 74, 81, 65, 74, 6, 8, 74, 103, 63, 68, 132, 81, 65, 74, 2, 91, + 8, 25, 29, 8, 48, 65, 69, 74, 82, 74, 67, 8, 7, 66, 75, 72, 67, 65, 74, 64, 65, + 79, 6, 8, 82, 74, 64, 8, 60, 61, 68, 72, 65, 74, 8, 82, 74, 64, 8, 91, 8, 23, + 27, 8, 14, 7, 64, 61, 73, 61, 72, 69, 67, 6, 15, 18, 8, 84, 65, 69, 72, 8, 7, + 65, 79, 132, 63, 68, 65, 69, 74, 81, 6, 8, 84, 69, 64, 65, 79, 132, 78, 79, 65, 63, + 68, 65, 74, 2, 91, 8, 25, 29, 8, 48, 65, 69, 74, 82, 74, 67, 8, 7, 66, 75, 72, + 67, 65, 74, 64, 65, 79, 6, 8, 82, 74, 64, 8, 60, 61, 68, 72, 65, 74, 8, 82, 74, + 64, 8, 91, 8, 23, 27, 8, 14, 7, 64, 61, 73, 61, 72, 69, 67, 6, 15, 18, 8, 84, + 65, 69, 72, 8, 7, 65, 79, 132, 63, 68, 65, 69, 74, 81, 6, 8, 84, 69, 64, 65, 79, + 132, 78, 79, 65, 63, 68, 65, 74, 2, 91, 8, 26, 28, 18, 8, 91, 8, 30, 24, 18, 8, + 91, 8, 31, 28, 18, 8, 91, 8, 31, 18, 8, 91, 8, 27, 29, 8, 82, 74, 64, 8, 61, + 82, 63, 68, 8, 91, 8, 28, 31, 18, 8, 91, 8, 27, 25, 18, 8, 91, 8, 25, 30, 20, + 2, 91, 8, 26, 28, 18, 8, 91, 8, 30, 24, 18, 8, 91, 8, 31, 28, 18, 8, 91, 8, + 31, 18, 8, 91, 8, 27, 29, 8, 82, 74, 64, 8, 61, 82, 63, 68, 8, 91, 8, 28, 31, + 18, 8, 91, 8, 27, 25, 18, 8, 91, 8, 25, 30, 20, 2, 41, 79, 61, 82, 65, 74, 8, + 7, 84, 65, 80, 68, 61, 72, 62, 6, 8, 82, 74, 64, 8, 56, 65, 79, 62, 69, 74, 64, + 82, 74, 67, 8, 64, 65, 79, 8, 14, 7, 52, 65, 61, 72, 132, 63, 68, 82, 72, 65, 6, + 15, 8, 91, 8, 30, 22, 8, 75, 64, 65, 79, 8, 7, 42, 79, 82, 62, 65, 74, 72, 61, + 73, 78, 65, 74, 6, 8, 84, 65, 79, 64, 65, 74, 2, 41, 79, 61, 82, 65, 74, 8, 7, + 84, 65, 80, 68, 61, 72, 62, 6, 8, 82, 74, 64, 8, 56, 65, 79, 62, 69, 74, 64, 82, + 74, 67, 8, 64, 65, 79, 8, 14, 7, 52, 65, 61, 72, 132, 63, 68, 82, 72, 65, 6, 15, + 8, 91, 8, 30, 22, 8, 75, 64, 65, 79, 8, 7, 42, 79, 82, 62, 65, 74, 72, 61, 73, + 78, 65, 74, 6, 8, 84, 65, 79, 64, 65, 74, 2, 47, 61, 67, 65, 79, 78, 72, 61, 81, + 87, 4, 36, 82, 66, 132, 65, 68, 65, 79, 32, 8, 7, 62, 65, 79, 65, 69, 81, 65, 81, + 6, 8, 91, 8, 31, 8, 61, 62, 65, 79, 8, 7, 46, 72, 65, 69, 74, 84, 75, 68, 74, + 82, 74, 67, 65, 74, 6, 8, 84, 65, 80, 68, 61, 72, 62, 8, 14, 7, 69, 74, 74, 65, + 79, 68, 61, 72, 62, 6, 15, 8, 91, 8, 30, 31, 8, 61, 62, 65, 79, 2, 47, 61, 67, + 65, 79, 78, 72, 61, 81, 87, 4, 36, 82, 66, 132, 65, 68, 65, 79, 32, 8, 7, 62, 65, + 79, 65, 69, 81, 65, 81, 6, 8, 91, 8, 31, 8, 61, 62, 65, 79, 8, 7, 46, 72, 65, + 69, 74, 84, 75, 68, 74, 82, 74, 67, 65, 74, 6, 8, 84, 65, 80, 68, 61, 72, 62, 8, + 14, 7, 69, 74, 74, 65, 79, 68, 61, 72, 62, 6, 15, 8, 91, 8, 30, 31, 8, 61, 62, + 65, 79, 2, 7, 61, 74, 64, 65, 79, 65, 79, 6, 8, 40, 79, 67, 65, 62, 74, 69, 80, + 8, 73, 69, 81, 81, 72, 65, 79, 65, 79, 8, 7, 59, 65, 73, 65, 74, 6, 20, 8, 14, + 91, 8, 23, 29, 15, 8, 36, 72, 72, 65, 79, 64, 69, 74, 67, 80, 8, 69, 74, 8, 37, + 65, 79, 72, 69, 74, 8, 75, 64, 65, 79, 8, 14, 7, 82, 74, 132, 65, 79, 73, 6, 15, + 2, 7, 61, 74, 64, 65, 79, 65, 79, 6, 8, 40, 79, 67, 65, 62, 74, 69, 80, 8, 73, + 69, 81, 81, 72, 65, 79, 65, 79, 8, 7, 59, 65, 73, 65, 74, 6, 20, 8, 14, 91, 8, + 23, 29, 15, 8, 36, 72, 72, 65, 79, 64, 69, 74, 67, 80, 8, 69, 74, 8, 37, 65, 79, + 72, 69, 74, 8, 75, 64, 65, 79, 8, 14, 7, 82, 74, 132, 65, 79, 73, 6, 15, 2, 91, + 8, 23, 8, 75, 64, 65, 79, 8, 7, 62, 65, 81, 79, 61, 63, 68, 81, 65, 74, 6, 8, + 61, 82, 80, 67, 65, 66, 110, 68, 79, 81, 33, 8, 64, 65, 74, 74, 8, 91, 8, 25, 23, + 8, 14, 64, 61, 68, 65, 79, 8, 64, 69, 65, 8, 52, 82, 66, 65, 15, 33, 8, 7, 64, + 61, 79, 110, 62, 65, 79, 6, 8, 79, 69, 63, 68, 81, 65, 81, 8, 91, 8, 30, 28, 8, + 61, 82, 63, 68, 8, 69, 74, 2, 91, 8, 23, 8, 75, 64, 65, 79, 8, 7, 62, 65, 81, + 79, 61, 63, 68, 81, 65, 74, 6, 8, 61, 82, 80, 67, 65, 66, 110, 68, 79, 81, 33, 8, + 64, 65, 74, 74, 8, 91, 8, 25, 23, 8, 14, 64, 61, 68, 65, 79, 8, 64, 69, 65, 8, + 52, 82, 66, 65, 15, 33, 8, 7, 64, 61, 79, 110, 62, 65, 79, 6, 8, 79, 69, 63, 68, + 81, 65, 81, 8, 91, 8, 30, 28, 8, 61, 82, 63, 68, 8, 69, 74, 2, 51, 79, 75, 83, + 69, 74, 87, 69, 61, 72, 72, 61, 74, 64, 81, 61, 67, 65, 80, 8, 64, 61, 79, 82, 73, + 8, 14, 7, 48, 61, 132, 63, 68, 69, 74, 65, 74, 6, 15, 8, 91, 8, 29, 28, 8, 64, + 65, 80, 68, 61, 72, 62, 8, 7, 132, 75, 72, 72, 81, 65, 6, 8, 54, 65, 69, 72, 62, + 65, 81, 79, 61, 67, 8, 69, 74, 8, 91, 8, 28, 24, 2, 51, 79, 75, 83, 69, 74, 87, + 69, 61, 72, 72, 61, 74, 64, 81, 61, 67, 65, 80, 8, 64, 61, 79, 82, 73, 8, 14, 7, + 48, 61, 132, 63, 68, 69, 74, 65, 74, 6, 15, 8, 91, 8, 29, 28, 8, 64, 65, 80, 68, + 61, 72, 62, 8, 7, 132, 75, 72, 72, 81, 65, 6, 8, 54, 65, 69, 72, 62, 65, 81, 79, + 61, 67, 8, 69, 74, 8, 91, 8, 28, 24, 2, 37, 65, 64, 65, 74, 71, 65, 74, 20, 8, + 7, 53, 63, 68, 84, 61, 74, 71, 65, 74, 6, 8, 91, 8, 25, 22, 8, 64, 61, 79, 82, + 73, 8, 48, 69, 65, 81, 65, 69, 74, 69, 67, 82, 74, 67, 80, 103, 73, 81, 65, 79, 8, + 84, 65, 80, 68, 61, 72, 62, 8, 14, 7, 48, 69, 81, 81, 61, 67, 80, 82, 74, 81, 65, + 79, 79, 69, 63, 68, 81, 6, 15, 8, 64, 65, 79, 2, 37, 65, 64, 65, 74, 71, 65, 74, + 20, 8, 7, 53, 63, 68, 84, 61, 74, 71, 65, 74, 6, 8, 91, 8, 25, 22, 8, 64, 61, + 79, 82, 73, 8, 48, 69, 65, 81, 65, 69, 74, 69, 67, 82, 74, 67, 80, 103, 73, 81, 65, + 79, 8, 84, 65, 80, 68, 61, 72, 62, 8, 14, 7, 48, 69, 81, 81, 61, 67, 80, 82, 74, + 81, 65, 79, 79, 69, 63, 68, 81, 6, 15, 8, 64, 65, 79, 2, 82, 74, 81, 65, 79, 65, + 8, 91, 8, 29, 28, 8, 64, 65, 80, 68, 61, 72, 62, 8, 7, 87, 84, 69, 132, 63, 68, + 65, 74, 6, 8, 73, 69, 81, 81, 65, 72, 62, 61, 79, 8, 91, 8, 24, 27, 8, 82, 74, + 64, 8, 67, 65, 132, 63, 68, 72, 75, 132, 132, 65, 74, 65, 74, 8, 7, 46, 109, 74, 69, + 67, 72, 69, 63, 68, 65, 74, 6, 2, 82, 74, 81, 65, 79, 65, 8, 91, 8, 29, 28, 8, + 64, 65, 80, 68, 61, 72, 62, 8, 7, 87, 84, 69, 132, 63, 68, 65, 74, 6, 8, 73, 69, + 81, 81, 65, 72, 62, 61, 79, 8, 91, 8, 24, 27, 8, 82, 74, 64, 8, 67, 65, 132, 63, + 68, 72, 75, 132, 132, 65, 74, 65, 74, 8, 7, 46, 109, 74, 69, 67, 72, 69, 63, 68, 65, + 74, 6, 2, 64, 61, 79, 82, 73, 8, 87, 65, 82, 67, 65, 74, 64, 65, 74, 8, 64, 65, + 80, 68, 61, 72, 62, 8, 91, 8, 31, 28, 8, 14, 7, 71, 109, 74, 74, 65, 74, 6, 15, + 8, 91, 8, 29, 25, 8, 84, 65, 80, 68, 61, 72, 62, 8, 7, 64, 69, 65, 132, 65, 80, + 6, 8, 14, 7, 55, 74, 79, 82, 68, 65, 6, 15, 8, 91, 8, 30, 8, 51, 79, 75, 70, + 65, 71, 81, 65, 2, 64, 61, 79, 82, 73, 8, 87, 65, 82, 67, 65, 74, 64, 65, 74, 8, + 64, 65, 80, 68, 61, 72, 62, 8, 91, 8, 31, 28, 8, 14, 7, 71, 109, 74, 74, 65, 74, + 6, 15, 8, 91, 8, 29, 25, 8, 84, 65, 80, 68, 61, 72, 62, 8, 7, 64, 69, 65, 132, + 65, 80, 6, 8, 14, 7, 55, 74, 79, 82, 68, 65, 6, 15, 8, 91, 8, 30, 8, 51, 79, + 75, 70, 65, 71, 81, 65, 2, 7, 48, 61, 102, 74, 61, 68, 73, 65, 74, 6, 8, 91, 8, + 26, 26, 8, 61, 62, 65, 79, 8, 45, 65, 74, 132, 65, 74, 62, 79, 110, 63, 71, 72, 65, + 18, 8, 84, 61, 79, 82, 73, 8, 14, 7, 55, 74, 79, 82, 68, 65, 6, 15, 8, 69, 73, + 8, 91, 8, 25, 26, 8, 82, 74, 64, 8, 7, 36, 78, 78, 72, 61, 82, 80, 6, 8, 64, + 65, 80, 8, 60, 82, 84, 61, 63, 68, 80, 2, 7, 48, 61, 102, 74, 61, 68, 73, 65, 74, + 6, 8, 91, 8, 26, 26, 8, 61, 62, 65, 79, 8, 45, 65, 74, 132, 65, 74, 62, 79, 110, + 63, 71, 72, 65, 18, 8, 84, 61, 79, 82, 73, 8, 14, 7, 55, 74, 79, 82, 68, 65, 6, + 15, 8, 69, 73, 8, 91, 8, 25, 26, 8, 82, 74, 64, 8, 7, 36, 78, 78, 72, 61, 82, + 80, 6, 8, 64, 65, 80, 8, 60, 82, 84, 61, 63, 68, 80, 2, 53, 63, 68, 82, 72, 64, + 65, 78, 82, 81, 61, 81, 69, 75, 74, 32, 8, 7, 46, 103, 73, 73, 65, 79, 65, 79, 6, + 8, 91, 8, 27, 25, 8, 84, 65, 80, 68, 61, 72, 62, 8, 132, 63, 68, 79, 65, 69, 62, + 65, 74, 8, 82, 74, 64, 8, 14, 7, 36, 82, 80, 72, 65, 67, 82, 74, 67, 6, 15, 8, + 91, 8, 28, 24, 8, 64, 65, 80, 68, 61, 72, 62, 2, 53, 63, 68, 82, 72, 64, 65, 78, + 82, 81, 61, 81, 69, 75, 74, 32, 8, 7, 46, 103, 73, 73, 65, 79, 65, 79, 6, 8, 91, + 8, 27, 25, 8, 84, 65, 80, 68, 61, 72, 62, 8, 132, 63, 68, 79, 65, 69, 62, 65, 74, + 8, 82, 74, 64, 8, 14, 7, 36, 82, 80, 72, 65, 67, 82, 74, 67, 6, 15, 8, 91, 8, + 28, 24, 8, 64, 65, 80, 68, 61, 72, 62, 2, 7, 71, 110, 74, 66, 81, 69, 67, 65, 6, + 8, 87, 61, 68, 72, 79, 65, 69, 63, 68, 65, 8, 7, 53, 110, 73, 78, 66, 65, 6, 8, + 91, 8, 30, 25, 18, 8, 61, 62, 65, 79, 8, 7, 43, 75, 63, 68, 84, 103, 72, 64, 65, + 79, 6, 8, 82, 74, 64, 8, 14, 7, 55, 65, 62, 65, 79, 132, 63, 68, 82, 102, 6, 15, + 8, 64, 65, 80, 8, 91, 8, 31, 8, 82, 74, 64, 2, 7, 71, 110, 74, 66, 81, 69, 67, + 65, 6, 8, 87, 61, 68, 72, 79, 65, 69, 63, 68, 65, 8, 7, 53, 110, 73, 78, 66, 65, + 6, 8, 91, 8, 30, 25, 18, 8, 61, 62, 65, 79, 8, 7, 43, 75, 63, 68, 84, 103, 72, + 64, 65, 79, 6, 8, 82, 74, 64, 8, 14, 7, 55, 65, 62, 65, 79, 132, 63, 68, 82, 102, + 6, 15, 8, 64, 65, 80, 8, 91, 8, 31, 8, 82, 74, 64, 2, 7, 51, 61, 79, 87, 65, + 72, 72, 65, 74, 6, 8, 84, 65, 74, 69, 67, 65, 79, 8, 69, 73, 8, 91, 8, 27, 30, + 18, 8, 64, 69, 65, 132, 65, 73, 8, 7, 72, 69, 65, 68, 65, 74, 6, 8, 14, 91, 8, + 31, 30, 15, 8, 82, 74, 64, 8, 48, 61, 67, 69, 132, 81, 79, 61, 81, 8, 75, 64, 65, + 79, 8, 14, 7, 62, 65, 66, 69, 74, 64, 72, 69, 63, 68, 65, 74, 6, 15, 2, 7, 51, + 61, 79, 87, 65, 72, 72, 65, 74, 6, 8, 84, 65, 74, 69, 67, 65, 79, 8, 69, 73, 8, + 91, 8, 27, 30, 18, 8, 64, 69, 65, 132, 65, 73, 8, 7, 72, 69, 65, 68, 65, 74, 6, + 8, 14, 91, 8, 31, 30, 15, 8, 82, 74, 64, 8, 48, 61, 67, 69, 132, 81, 79, 61, 81, + 8, 75, 64, 65, 79, 8, 14, 7, 62, 65, 66, 69, 74, 64, 72, 69, 63, 68, 65, 74, 6, + 15, 2, 91, 8, 31, 25, 8, 64, 61, 79, 82, 73, 8, 7, 64, 65, 80, 67, 72, 65, 69, + 63, 68, 65, 74, 6, 8, 53, 63, 68, 79, 69, 66, 81, 132, 81, 65, 72, 72, 65, 79, 8, + 73, 69, 81, 8, 37, 65, 73, 65, 79, 71, 82, 74, 67, 65, 74, 8, 14, 7, 36, 78, 78, + 72, 61, 82, 80, 6, 15, 20, 8, 91, 8, 25, 22, 8, 64, 65, 80, 68, 61, 72, 62, 2, + 91, 8, 31, 25, 8, 64, 61, 79, 82, 73, 8, 7, 64, 65, 80, 67, 72, 65, 69, 63, 68, + 65, 74, 6, 8, 53, 63, 68, 79, 69, 66, 81, 132, 81, 65, 72, 72, 65, 79, 8, 73, 69, + 81, 8, 37, 65, 73, 65, 79, 71, 82, 74, 67, 65, 74, 8, 14, 7, 36, 78, 78, 72, 61, + 82, 80, 6, 15, 20, 8, 91, 8, 25, 22, 8, 64, 65, 80, 68, 61, 72, 62, 2, 71, 109, + 74, 74, 65, 74, 8, 75, 64, 65, 79, 8, 14, 7, 53, 63, 68, 82, 81, 87, 65, 6, 15, + 8, 91, 8, 28, 31, 8, 82, 74, 64, 8, 7, 82, 74, 67, 65, 66, 103, 68, 79, 6, 8, + 79, 65, 72, 61, 81, 69, 83, 65, 8, 91, 8, 28, 26, 8, 7, 43, 75, 63, 68, 84, 103, + 72, 64, 65, 79, 6, 8, 14, 7, 60, 84, 69, 132, 63, 68, 65, 74, 79, 82, 66, 6, 15, + 8, 91, 8, 30, 25, 2, 71, 109, 74, 74, 65, 74, 8, 75, 64, 65, 79, 8, 14, 7, 53, + 63, 68, 82, 81, 87, 65, 6, 15, 8, 91, 8, 28, 31, 8, 82, 74, 64, 8, 7, 82, 74, + 67, 65, 66, 103, 68, 79, 6, 8, 79, 65, 72, 61, 81, 69, 83, 65, 8, 91, 8, 28, 26, + 8, 7, 43, 75, 63, 68, 84, 103, 72, 64, 65, 79, 6, 8, 14, 7, 60, 84, 69, 132, 63, + 68, 65, 74, 79, 82, 66, 6, 15, 8, 91, 8, 30, 25, 2, 64, 65, 80, 68, 61, 72, 62, + 8, 83, 69, 65, 72, 72, 65, 69, 63, 68, 81, 8, 84, 65, 80, 68, 61, 72, 62, 8, 14, + 7, 37, 65, 79, 61, 81, 82, 74, 67, 6, 15, 8, 91, 8, 23, 30, 8, 75, 64, 65, 79, + 8, 7, 51, 75, 81, 80, 64, 61, 73, 6, 20, 8, 68, 61, 82, 132, 65, 80, 2, 64, 65, + 80, 68, 61, 72, 62, 8, 83, 69, 65, 72, 72, 65, 69, 63, 68, 81, 8, 84, 65, 80, 68, + 61, 72, 62, 8, 14, 7, 37, 65, 79, 61, 81, 82, 74, 67, 6, 15, 8, 91, 8, 23, 30, + 8, 75, 64, 65, 79, 8, 7, 51, 75, 81, 80, 64, 61, 73, 6, 20, 8, 68, 61, 82, 132, + 65, 80, 2, 37, 65, 79, 67, 73, 61, 74, 74, 8, 7, 48, 61, 74, 67, 65, 72, 6, 8, + 91, 8, 25, 24, 8, 75, 64, 65, 79, 8, 70, 65, 64, 75, 63, 68, 8, 61, 62, 65, 79, + 8, 14, 7, 42, 79, 82, 78, 78, 65, 74, 6, 15, 8, 91, 8, 31, 22, 8, 64, 65, 80, + 68, 61, 72, 62, 8, 7, 39, 65, 80, 69, 74, 132, 65, 71, 81, 69, 75, 74, 6, 8, 37, + 65, 81, 81, 65, 74, 2, 37, 65, 79, 67, 73, 61, 74, 74, 8, 7, 48, 61, 74, 67, 65, + 72, 6, 8, 91, 8, 25, 24, 8, 75, 64, 65, 79, 8, 70, 65, 64, 75, 63, 68, 8, 61, + 62, 65, 79, 8, 14, 7, 42, 79, 82, 78, 78, 65, 74, 6, 15, 8, 91, 8, 31, 22, 8, + 64, 65, 80, 68, 61, 72, 62, 8, 7, 39, 65, 80, 69, 74, 132, 65, 71, 81, 69, 75, 74, + 6, 8, 37, 65, 81, 81, 65, 74, 2, 52, 65, 61, 72, 132, 63, 68, 82, 72, 65, 8, 7, + 36, 82, 80, 132, 63, 68, 82, 102, 6, 8, 91, 8, 28, 31, 18, 8, 91, 8, 29, 30, 8, + 64, 61, 79, 82, 73, 8, 68, 69, 74, 87, 82, 84, 65, 69, 132, 65, 74, 20, 8, 50, 64, + 65, 79, 8, 14, 7, 73, 110, 132, 132, 65, 74, 6, 15, 8, 91, 8, 27, 30, 8, 82, 74, + 64, 8, 91, 8, 30, 23, 8, 61, 62, 65, 79, 8, 7, 83, 75, 72, 72, 71, 75, 73, 73, + 65, 74, 6, 2, 52, 65, 61, 72, 132, 63, 68, 82, 72, 65, 8, 7, 36, 82, 80, 132, 63, + 68, 82, 102, 6, 8, 91, 8, 28, 31, 18, 8, 91, 8, 29, 30, 8, 64, 61, 79, 82, 73, + 8, 68, 69, 74, 87, 82, 84, 65, 69, 132, 65, 74, 20, 8, 50, 64, 65, 79, 8, 14, 7, + 73, 110, 132, 132, 65, 74, 6, 15, 8, 91, 8, 27, 30, 8, 82, 74, 64, 8, 91, 8, 30, + 23, 8, 61, 62, 65, 79, 8, 7, 83, 75, 72, 72, 71, 75, 73, 73, 65, 74, 6, 2, 65, + 74, 81, 68, 103, 72, 81, 8, 53, 61, 63, 68, 65, 74, 8, 7, 42, 82, 132, 81, 61, 83, + 6, 8, 91, 8, 31, 18, 8, 91, 8, 28, 22, 8, 18, 8, 91, 8, 23, 23, 18, 8, 61, + 62, 65, 79, 8, 53, 63, 68, 61, 74, 71, 84, 69, 79, 81, 8, 84, 65, 80, 68, 61, 72, + 62, 8, 14, 7, 53, 63, 68, 82, 72, 61, 82, 66, 132, 69, 63, 68, 81, 80, 75, 79, 67, + 61, 74, 6, 15, 2, 65, 74, 81, 68, 103, 72, 81, 8, 53, 61, 63, 68, 65, 74, 8, 7, + 42, 82, 132, 81, 61, 83, 6, 8, 91, 8, 31, 18, 8, 91, 8, 28, 22, 8, 18, 8, 91, + 8, 23, 23, 18, 8, 61, 62, 65, 79, 8, 53, 63, 68, 61, 74, 71, 84, 69, 79, 81, 8, + 84, 65, 80, 68, 61, 72, 62, 8, 14, 7, 53, 63, 68, 82, 72, 61, 82, 66, 132, 69, 63, + 68, 81, 80, 75, 79, 67, 61, 74, 6, 15, 2, 61, 62, 65, 79, 8, 7, 62, 65, 87, 75, + 67, 65, 74, 6, 8, 57, 61, 74, 64, 65, 72, 78, 61, 74, 75, 79, 61, 73, 61, 8, 91, + 8, 25, 26, 8, 36, 82, 66, 132, 69, 63, 68, 81, 8, 7, 64, 65, 80, 67, 72, 65, 69, + 63, 68, 65, 74, 6, 8, 75, 64, 65, 79, 8, 61, 62, 84, 65, 69, 63, 68, 65, 74, 64, + 65, 79, 18, 2, 61, 62, 65, 79, 8, 7, 62, 65, 87, 75, 67, 65, 74, 6, 8, 57, 61, + 74, 64, 65, 72, 78, 61, 74, 75, 79, 61, 73, 61, 8, 91, 8, 25, 26, 8, 36, 82, 66, + 132, 69, 63, 68, 81, 8, 7, 64, 65, 80, 67, 72, 65, 69, 63, 68, 65, 74, 6, 8, 75, + 64, 65, 79, 8, 61, 62, 84, 65, 69, 63, 68, 65, 74, 64, 65, 79, 18, 2, 84, 65, 80, + 68, 61, 72, 62, 8, 14, 7, 132, 63, 68, 65, 69, 74, 81, 80, 6, 15, 8, 91, 8, 29, + 25, 8, 75, 64, 65, 79, 8, 7, 67, 65, 72, 65, 67, 81, 6, 8, 46, 82, 64, 61, 73, + 73, 132, 81, 79, 61, 102, 65, 8, 91, 8, 27, 26, 8, 62, 65, 84, 65, 79, 71, 132, 81, + 65, 72, 72, 69, 67, 81, 33, 2, 84, 65, 80, 68, 61, 72, 62, 8, 14, 7, 132, 63, 68, + 65, 69, 74, 81, 80, 6, 15, 8, 91, 8, 29, 25, 8, 75, 64, 65, 79, 8, 7, 67, 65, + 72, 65, 67, 81, 6, 8, 46, 82, 64, 61, 73, 73, 132, 81, 79, 61, 102, 65, 8, 91, 8, + 27, 26, 8, 62, 65, 84, 65, 79, 71, 132, 81, 65, 72, 72, 69, 67, 81, 33, 2, 7, 68, + 69, 74, 132, 69, 63, 68, 81, 72, 69, 63, 68, 6, 8, 91, 8, 25, 27, 8, 75, 64, 65, + 79, 8, 110, 62, 65, 79, 74, 75, 73, 73, 65, 74, 8, 75, 64, 65, 79, 8, 14, 7, 48, + 103, 74, 74, 65, 79, 6, 15, 8, 91, 8, 23, 23, 18, 8, 84, 65, 69, 72, 8, 7, 84, + 65, 72, 63, 68, 65, 79, 6, 2, 7, 68, 69, 74, 132, 69, 63, 68, 81, 72, 69, 63, 68, + 6, 8, 91, 8, 25, 27, 8, 75, 64, 65, 79, 8, 110, 62, 65, 79, 74, 75, 73, 73, 65, + 74, 8, 75, 64, 65, 79, 8, 14, 7, 48, 103, 74, 74, 65, 79, 6, 15, 8, 91, 8, 23, + 23, 18, 8, 84, 65, 69, 72, 8, 7, 84, 65, 72, 63, 68, 65, 79, 6, 2, 37, 65, 81, + 81, 65, 74, 8, 91, 8, 27, 24, 8, 45, 65, 74, 132, 65, 74, 78, 72, 61, 81, 87, 8, + 7, 64, 65, 66, 69, 74, 69, 81, 69, 83, 6, 8, 91, 8, 23, 26, 18, 8, 61, 62, 65, + 79, 8, 14, 7, 61, 74, 64, 65, 79, 65, 6, 15, 8, 91, 8, 25, 26, 18, 8, 84, 65, + 69, 72, 8, 7, 83, 69, 65, 72, 72, 65, 69, 63, 68, 81, 6, 2, 37, 65, 81, 81, 65, + 74, 8, 91, 8, 27, 24, 8, 45, 65, 74, 132, 65, 74, 78, 72, 61, 81, 87, 8, 7, 64, + 65, 66, 69, 74, 69, 81, 69, 83, 6, 8, 91, 8, 23, 26, 18, 8, 61, 62, 65, 79, 8, + 14, 7, 61, 74, 64, 65, 79, 65, 6, 15, 8, 91, 8, 25, 26, 18, 8, 84, 65, 69, 72, + 8, 7, 83, 69, 65, 72, 72, 65, 69, 63, 68, 81, 6, 2, 46, 79, 65, 69, 80, 132, 63, + 68, 82, 72, 69, 74, 132, 78, 65, 71, 81, 75, 79, 8, 91, 8, 24, 29, 8, 62, 65, 72, + 65, 67, 81, 8, 7, 67, 65, 81, 79, 75, 66, 66, 65, 74, 65, 6, 8, 91, 8, 28, 23, + 8, 84, 65, 80, 68, 61, 72, 62, 8, 65, 79, 66, 75, 72, 67, 65, 74, 2, 46, 79, 65, + 69, 80, 132, 63, 68, 82, 72, 69, 74, 132, 78, 65, 71, 81, 75, 79, 8, 91, 8, 24, 29, + 8, 62, 65, 72, 65, 67, 81, 8, 7, 67, 65, 81, 79, 75, 66, 66, 65, 74, 65, 6, 8, + 91, 8, 28, 23, 8, 84, 65, 80, 68, 61, 72, 62, 8, 65, 79, 66, 75, 72, 67, 65, 74, + 2, 82, 74, 64, 8, 14, 7, 51, 79, 75, 70, 65, 71, 81, 65, 6, 15, 8, 91, 8, 30, + 26, 8, 84, 65, 80, 68, 61, 72, 62, 8, 7, 84, 65, 80, 68, 61, 72, 62, 6, 8, 39, + 69, 80, 78, 75, 4, 71, 79, 65, 64, 69, 81, 65, 8, 69, 74, 8, 91, 8, 27, 28, 2, + 82, 74, 64, 8, 14, 7, 51, 79, 75, 70, 65, 71, 81, 65, 6, 15, 8, 91, 8, 30, 26, + 8, 84, 65, 80, 68, 61, 72, 62, 8, 7, 84, 65, 80, 68, 61, 72, 62, 6, 8, 39, 69, + 80, 78, 75, 4, 71, 79, 65, 64, 69, 81, 65, 8, 69, 74, 8, 91, 8, 27, 28, 2, 65, + 79, 84, 75, 79, 62, 65, 74, 20, 8, 57, 61, 74, 64, 65, 79, 82, 74, 67, 8, 1, 114, + 8, 49, 65, 82, 62, 61, 82, 8, 23, 30, 31, 31, 8, 23, 31, 22, 24, 20, 8, 1, 114, + 8, 61, 74, 67, 65, 73, 65, 132, 132, 65, 74, 8, 82, 74, 64, 8, 82, 74, 64, 8, 26, + 31, 8, 96, 8, 83, 75, 74, 8, 25, 23, 11, 8, 84, 69, 79, 64, 20, 2, 65, 79, 84, + 75, 79, 62, 65, 74, 20, 8, 57, 61, 74, 64, 65, 79, 82, 74, 67, 8, 1, 114, 8, 49, + 65, 82, 62, 61, 82, 8, 23, 30, 31, 31, 8, 23, 31, 22, 24, 20, 8, 1, 114, 8, 61, + 74, 67, 65, 73, 65, 132, 132, 65, 74, 8, 82, 74, 64, 8, 82, 74, 64, 8, 26, 31, 8, + 96, 8, 83, 75, 74, 8, 25, 23, 11, 8, 84, 69, 79, 64, 20, 2, 87, 84, 65, 69, 8, + 64, 69, 65, 8, 1, 114, 8, 65, 69, 74, 65, 73, 8, 64, 65, 79, 8, 23, 30, 29, 25, + 1, 112, 8, 1, 121, 8, 23, 30, 26, 8, 42, 65, 62, 69, 65, 81, 65, 74, 8, 7, 64, + 61, 79, 110, 62, 65, 79, 6, 8, 27, 30, 8, 1, 114, 8, 61, 82, 80, 8, 24, 11, 8, + 66, 82, 79, 63, 68, 81, 62, 61, 79, 2, 87, 84, 65, 69, 8, 64, 69, 65, 8, 1, 114, + 8, 65, 69, 74, 65, 73, 8, 64, 65, 79, 8, 23, 30, 29, 25, 1, 112, 8, 1, 121, 8, + 23, 30, 26, 8, 42, 65, 62, 69, 65, 81, 65, 74, 8, 7, 64, 61, 79, 110, 62, 65, 79, + 6, 8, 27, 30, 8, 1, 114, 8, 61, 82, 80, 8, 24, 11, 8, 66, 82, 79, 63, 68, 81, + 62, 61, 79, 2, 23, 30, 31, 31, 8, 64, 61, 79, 110, 62, 65, 79, 8, 1, 118, 8, 36, + 82, 80, 67, 61, 62, 65, 74, 8, 49, 65, 82, 62, 61, 82, 8, 28, 27, 11, 8, 1, 127, + 8, 39, 69, 74, 67, 65, 8, 36, 74, 79, 82, 66, 65, 74, 8, 75, 64, 65, 79, 8, 23, + 23, 8, 96, 8, 23, 30, 29, 29, 8, 24, 22, 22, 22, 11, 8, 46, 109, 74, 69, 67, 69, + 74, 2, 23, 30, 31, 31, 8, 64, 61, 79, 110, 62, 65, 79, 8, 1, 118, 8, 36, 82, 80, + 67, 61, 62, 65, 74, 8, 49, 65, 82, 62, 61, 82, 8, 28, 27, 11, 8, 1, 127, 8, 39, + 69, 74, 67, 65, 8, 36, 74, 79, 82, 66, 65, 74, 8, 75, 64, 65, 79, 8, 23, 23, 8, + 96, 8, 23, 30, 29, 29, 8, 24, 22, 22, 22, 11, 8, 46, 109, 74, 69, 67, 69, 74, 2, + 64, 61, 67, 65, 67, 65, 74, 8, 132, 75, 74, 64, 65, 79, 74, 8, 1, 118, 8, 64, 69, + 65, 8, 70, 65, 64, 75, 63, 68, 8, 23, 30, 27, 27, 20, 8, 1, 117, 8, 56, 65, 79, + 67, 72, 65, 69, 63, 68, 8, 53, 69, 81, 87, 82, 74, 67, 8, 64, 65, 79, 8, 28, 31, + 8, 1, 125, 8, 132, 81, 69, 65, 67, 8, 23, 31, 22, 27, 11, 8, 64, 61, 80, 2, 64, + 61, 67, 65, 67, 65, 74, 8, 132, 75, 74, 64, 65, 79, 74, 8, 1, 118, 8, 64, 69, 65, + 8, 70, 65, 64, 75, 63, 68, 8, 23, 30, 27, 27, 20, 8, 1, 117, 8, 56, 65, 79, 67, + 72, 65, 69, 63, 68, 8, 53, 69, 81, 87, 82, 74, 67, 8, 64, 65, 79, 8, 28, 31, 8, + 1, 125, 8, 132, 81, 69, 65, 67, 8, 23, 31, 22, 27, 11, 8, 64, 61, 80, 2, 132, 78, + 79, 65, 63, 68, 65, 8, 69, 63, 68, 8, 1, 120, 8, 25, 30, 26, 8, 27, 30, 28, 24, + 8, 25, 27, 11, 8, 1, 117, 8, 29, 25, 28, 8, 75, 64, 65, 79, 8, 53, 61, 73, 73, + 65, 72, 68, 65, 69, 87, 82, 74, 67, 8, 24, 23, 8, 126, 8, 83, 65, 79, 62, 69, 74, + 64, 65, 74, 8, 23, 30, 31, 25, 11, 8, 74, 75, 63, 68, 2, 132, 78, 79, 65, 63, 68, + 65, 8, 69, 63, 68, 8, 1, 120, 8, 25, 30, 26, 8, 27, 30, 28, 24, 8, 25, 27, 11, + 8, 1, 117, 8, 29, 25, 28, 8, 75, 64, 65, 79, 8, 53, 61, 73, 73, 65, 72, 68, 65, + 69, 87, 82, 74, 67, 8, 24, 23, 8, 126, 8, 83, 65, 79, 62, 69, 74, 64, 65, 74, 8, + 23, 30, 31, 25, 11, 8, 74, 75, 63, 68, 2, 22, 30, 20, 8, 62, 65, 84, 69, 72, 72, + 69, 67, 81, 8, 1, 119, 8, 23, 28, 25, 31, 8, 65, 69, 74, 81, 79, 65, 81, 65, 74, + 8, 23, 22, 27, 26, 11, 8, 1, 114, 8, 68, 61, 62, 65, 74, 8, 64, 65, 79, 8, 27, + 27, 20, 8, 23, 28, 26, 27, 8, 97, 8, 7, 39, 65, 79, 8, 25, 22, 11, 8, 36, 74, + 132, 78, 79, 110, 63, 68, 65, 74, 2, 22, 30, 20, 8, 62, 65, 84, 69, 72, 72, 69, 67, + 81, 8, 1, 119, 8, 23, 28, 25, 31, 8, 65, 69, 74, 81, 79, 65, 81, 65, 74, 8, 23, + 22, 27, 26, 11, 8, 1, 114, 8, 68, 61, 62, 65, 74, 8, 64, 65, 79, 8, 27, 27, 20, + 8, 23, 28, 26, 27, 8, 97, 8, 7, 39, 65, 79, 8, 25, 22, 11, 8, 36, 74, 132, 78, + 79, 110, 63, 68, 65, 74, 2, 56, 65, 79, 4, 8, 42, 79, 82, 74, 64, 61, 82, 66, 8, + 1, 128, 8, 67, 65, 68, 65, 74, 64, 8, 64, 65, 73, 8, 23, 24, 1, 112, 8, 1, 128, + 8, 57, 75, 68, 74, 82, 74, 67, 65, 74, 8, 25, 25, 26, 8, 67, 65, 78, 79, 110, 66, + 81, 65, 74, 8, 23, 31, 23, 27, 8, 1, 113, 8, 23, 31, 24, 22, 8, 23, 31, 23, 28, + 11, 8, 23, 27, 22, 2, 56, 65, 79, 4, 8, 42, 79, 82, 74, 64, 61, 82, 66, 8, 1, + 128, 8, 67, 65, 68, 65, 74, 64, 8, 64, 65, 73, 8, 23, 24, 1, 112, 8, 1, 128, 8, + 57, 75, 68, 74, 82, 74, 67, 65, 74, 8, 25, 25, 26, 8, 67, 65, 78, 79, 110, 66, 81, + 65, 74, 8, 23, 31, 23, 27, 8, 1, 113, 8, 23, 31, 24, 22, 8, 23, 31, 23, 28, 11, + 8, 23, 27, 22, 2, 53, 69, 65, 8, 62, 65, 4, 8, 1, 114, 8, 36, 74, 67, 65, 72, + 65, 67, 65, 74, 68, 65, 69, 81, 65, 74, 8, 42, 79, 75, 72, 73, 61, 74, 4, 8, 23, + 31, 24, 22, 20, 8, 1, 122, 8, 124, 63, 20, 8, 23, 22, 22, 8, 64, 65, 79, 8, 28, + 29, 8, 1, 120, 8, 66, 110, 79, 8, 23, 25, 22, 24, 11, 8, 64, 69, 65, 2, 53, 69, + 65, 8, 62, 65, 4, 8, 1, 114, 8, 36, 74, 67, 65, 72, 65, 67, 65, 74, 68, 65, 69, + 81, 65, 74, 8, 42, 79, 75, 72, 73, 61, 74, 4, 8, 23, 31, 24, 22, 20, 8, 1, 122, + 8, 124, 63, 20, 8, 23, 22, 22, 8, 64, 65, 79, 8, 28, 29, 8, 1, 120, 8, 66, 110, + 79, 8, 23, 25, 22, 24, 11, 8, 64, 69, 65, 2, 65, 69, 74, 87, 65, 72, 74, 65, 74, + 8, 65, 69, 74, 132, 65, 69, 81, 69, 4, 8, 98, 8, 83, 65, 79, 81, 79, 61, 67, 8, + 64, 65, 79, 8, 30, 26, 20, 8, 126, 8, 67, 82, 81, 65, 79, 8, 73, 69, 81, 8, 69, + 132, 81, 8, 23, 30, 24, 28, 8, 1, 121, 8, 23, 30, 26, 25, 8, 23, 26, 11, 8, 65, + 79, 67, 65, 62, 65, 74, 20, 2, 65, 69, 74, 87, 65, 72, 74, 65, 74, 8, 65, 69, 74, + 132, 65, 69, 81, 69, 4, 8, 98, 8, 83, 65, 79, 81, 79, 61, 67, 8, 64, 65, 79, 8, + 30, 26, 20, 8, 126, 8, 67, 82, 81, 65, 79, 8, 73, 69, 81, 8, 69, 132, 81, 8, 23, + 30, 24, 28, 8, 1, 121, 8, 23, 30, 26, 25, 8, 23, 26, 11, 8, 65, 79, 67, 65, 62, + 65, 74, 20, 2, 70, 65, 64, 75, 63, 68, 8, 87, 82, 79, 8, 96, 8, 51, 79, 110, 66, + 82, 74, 67, 8, 46, 79, 69, 65, 67, 65, 8, 23, 24, 24, 26, 20, 8, 1, 122, 8, 84, + 69, 65, 8, 83, 75, 73, 8, 64, 65, 79, 8, 23, 23, 8, 1, 119, 8, 67, 65, 81, 79, + 75, 66, 66, 65, 74, 65, 8, 31, 11, 8, 84, 65, 69, 72, 2, 70, 65, 64, 75, 63, 68, + 8, 87, 82, 79, 8, 96, 8, 51, 79, 110, 66, 82, 74, 67, 8, 46, 79, 69, 65, 67, 65, + 8, 23, 24, 24, 26, 20, 8, 1, 122, 8, 84, 69, 65, 8, 83, 75, 73, 8, 64, 65, 79, + 8, 23, 23, 8, 1, 119, 8, 67, 65, 81, 79, 75, 66, 66, 65, 74, 65, 8, 31, 11, 8, + 84, 65, 69, 72, 2, 64, 61, 102, 8, 36, 73, 75, 79, 81, 69, 132, 61, 81, 69, 75, 74, + 8, 1, 115, 8, 48, 61, 102, 65, 8, 64, 65, 79, 8, 23, 29, 22, 33, 8, 1, 120, 8, + 23, 26, 27, 8, 57, 65, 69, 132, 65, 8, 83, 75, 74, 8, 27, 29, 8, 1, 128, 8, 41, + 103, 72, 72, 65, 74, 8, 29, 22, 22, 22, 11, 8, 82, 74, 64, 2, 64, 61, 102, 8, 36, + 73, 75, 79, 81, 69, 132, 61, 81, 69, 75, 74, 8, 1, 115, 8, 48, 61, 102, 65, 8, 64, + 65, 79, 8, 23, 29, 22, 33, 8, 1, 120, 8, 23, 26, 27, 8, 57, 65, 69, 132, 65, 8, + 83, 75, 74, 8, 27, 29, 8, 1, 128, 8, 41, 103, 72, 72, 65, 74, 8, 29, 22, 22, 22, + 11, 8, 82, 74, 64, 2, 87, 82, 73, 8, 64, 65, 79, 8, 1, 119, 8, 52, 61, 81, 68, + 61, 82, 132, 65, 80, 8, 64, 69, 65, 8, 24, 18, 8, 126, 8, 64, 65, 79, 8, 61, 82, + 66, 8, 87, 82, 79, 8, 28, 27, 22, 8, 96, 8, 84, 65, 79, 64, 65, 74, 8, 23, 29, + 28, 27, 11, 8, 61, 82, 66, 79, 65, 63, 68, 81, 2, 87, 82, 73, 8, 64, 65, 79, 8, + 1, 119, 8, 52, 61, 81, 68, 61, 82, 132, 65, 80, 8, 64, 69, 65, 8, 24, 18, 8, 126, + 8, 64, 65, 79, 8, 61, 82, 66, 8, 87, 82, 79, 8, 28, 27, 22, 8, 96, 8, 84, 65, + 79, 64, 65, 74, 8, 23, 29, 28, 27, 11, 8, 61, 82, 66, 79, 65, 63, 68, 81, 2, 37, + 65, 87, 69, 65, 68, 82, 74, 67, 8, 82, 74, 132, 65, 79, 73, 8, 1, 121, 8, 69, 68, + 79, 65, 79, 8, 82, 74, 64, 8, 23, 31, 20, 8, 1, 118, 8, 64, 65, 79, 8, 82, 74, + 64, 8, 83, 65, 79, 87, 69, 63, 68, 81, 65, 74, 8, 30, 22, 8, 1, 119, 8, 65, 74, + 81, 132, 78, 79, 69, 63, 68, 81, 8, 31, 11, 8, 82, 74, 80, 2, 37, 65, 87, 69, 65, + 68, 82, 74, 67, 8, 82, 74, 132, 65, 79, 73, 8, 1, 121, 8, 69, 68, 79, 65, 79, 8, + 82, 74, 64, 8, 23, 31, 20, 8, 1, 118, 8, 64, 65, 79, 8, 82, 74, 64, 8, 83, 65, + 79, 87, 69, 63, 68, 81, 65, 74, 8, 30, 22, 8, 1, 119, 8, 65, 74, 81, 132, 78, 79, + 69, 63, 68, 81, 8, 31, 11, 8, 82, 74, 80, 2, 82, 74, 80, 8, 87, 82, 79, 8, 126, + 8, 67, 65, 74, 8, 53, 63, 68, 110, 72, 65, 79, 8, 23, 28, 22, 11, 8, 1, 116, 8, + 132, 75, 84, 69, 65, 8, 64, 61, 79, 82, 73, 8, 52, 65, 61, 72, 132, 63, 68, 82, 72, + 65, 8, 23, 31, 24, 22, 8, 1, 114, 8, 64, 82, 79, 63, 68, 8, 24, 22, 22, 22, 11, + 8, 61, 74, 64, 65, 79, 65, 2, 82, 74, 80, 8, 87, 82, 79, 8, 126, 8, 67, 65, 74, + 8, 53, 63, 68, 110, 72, 65, 79, 8, 23, 28, 22, 11, 8, 1, 116, 8, 132, 75, 84, 69, + 65, 8, 64, 61, 79, 82, 73, 8, 52, 65, 61, 72, 132, 63, 68, 82, 72, 65, 8, 23, 31, + 24, 22, 8, 1, 114, 8, 64, 82, 79, 63, 68, 8, 24, 22, 22, 22, 11, 8, 61, 74, 64, + 65, 79, 65, 2, 64, 61, 102, 8, 64, 65, 79, 67, 72, 65, 69, 63, 68, 65, 74, 8, 98, + 8, 132, 69, 63, 68, 8, 62, 69, 80, 8, 24, 28, 22, 22, 18, 8, 1, 113, 8, 45, 82, + 74, 69, 8, 79, 82, 74, 64, 8, 62, 65, 69, 73, 8, 31, 8, 1, 118, 8, 57, 75, 68, + 74, 82, 74, 67, 65, 74, 8, 23, 22, 11, 8, 83, 75, 74, 2, 62, 72, 69, 65, 62, 8, + 74, 61, 63, 68, 8, 1, 121, 8, 79, 82, 74, 64, 8, 71, 109, 74, 74, 65, 74, 8, 26, + 20, 8, 1, 115, 8, 64, 65, 73, 8, 48, 61, 79, 71, 8, 84, 65, 69, 72, 8, 30, 8, + 1, 115, 8, 66, 110, 79, 8, 23, 30, 31, 23, 11, 8, 132, 79, 61, 102, 65, 2, 72, 65, + 81, 87, 81, 65, 74, 8, 36, 74, 81, 79, 61, 67, 8, 98, 8, 64, 61, 79, 82, 73, 8, + 23, 31, 23, 22, 21, 23, 27, 20, 8, 28, 29, 11, 8, 1, 114, 8, 64, 69, 65, 132, 65, + 73, 8, 45, 61, 68, 79, 8, 69, 63, 68, 8, 25, 29, 8, 1, 113, 8, 65, 69, 74, 65, + 74, 8, 24, 22, 27, 22, 11, 8, 53, 81, 65, 82, 65, 79, 74, 2, 47, 65, 68, 79, 78, + 72, 103, 74, 65, 74, 8, 23, 27, 23, 24, 8, 1, 113, 8, 36, 79, 87, 81, 8, 64, 65, + 79, 8, 23, 30, 22, 31, 20, 8, 97, 8, 124, 63, 20, 8, 51, 65, 79, 132, 75, 74, 8, + 65, 74, 81, 132, 63, 68, 65, 69, 64, 65, 74, 64, 65, 8, 26, 22, 8, 1, 116, 8, 74, + 61, 63, 68, 8, 23, 30, 31, 31, 11, 8, 64, 65, 79, 2, 36, 62, 84, 61, 74, 64, 65, + 79, 82, 74, 67, 8, 56, 75, 79, 64, 65, 79, 68, 61, 82, 132, 65, 80, 8, 1, 121, 8, + 64, 65, 73, 8, 70, 65, 64, 65, 79, 8, 23, 31, 22, 23, 20, 8, 22, 22, 20, 8, 64, + 69, 65, 8, 64, 69, 65, 8, 30, 24, 8, 1, 121, 8, 74, 61, 63, 68, 8, 26, 11, 8, + 7, 48, 103, 74, 74, 65, 79, 6, 2, 64, 65, 73, 8, 84, 69, 79, 8, 126, 8, 83, 75, + 74, 8, 84, 65, 72, 63, 68, 65, 8, 23, 31, 20, 8, 1, 117, 8, 64, 69, 65, 8, 74, + 61, 63, 68, 8, 87, 69, 74, 132, 82, 74, 67, 8, 25, 22, 8, 1, 127, 8, 61, 62, 65, + 79, 8, 23, 30, 27, 22, 11, 8, 83, 75, 74, 2, 82, 74, 64, 8, 45, 82, 74, 67, 65, + 74, 8, 1, 128, 8, 64, 65, 73, 8, 62, 69, 80, 8, 23, 22, 22, 18, 8, 98, 8, 36, + 74, 132, 78, 79, 82, 63, 68, 8, 64, 65, 79, 8, 64, 61, 79, 82, 73, 8, 23, 22, 8, + 1, 120, 8, 61, 82, 63, 68, 8, 25, 11, 8, 53, 63, 68, 69, 72, 72, 65, 79, 19, 54, + 68, 65, 61, 81, 65, 79, 2, 81, 65, 74, 62, 82, 79, 67, 8, 23, 24, 11, 8, 1, 122, + 8, 23, 24, 11, 8, 7, 57, 61, 68, 79, 72, 69, 63, 68, 9, 6, 8, 30, 30, 20, 8, + 1, 117, 8, 62, 69, 80, 8, 73, 109, 63, 68, 81, 65, 8, 73, 61, 74, 63, 68, 73, 61, + 72, 8, 23, 30, 8, 1, 122, 8, 87, 82, 72, 65, 67, 65, 74, 8, 25, 26, 11, 8, 73, + 61, 63, 68, 65, 2, 64, 65, 74, 8, 24, 27, 22, 22, 8, 96, 8, 82, 74, 64, 8, 65, + 79, 66, 79, 61, 67, 65, 74, 20, 8, 23, 22, 20, 8, 1, 123, 8, 69, 68, 79, 65, 8, + 132, 75, 84, 69, 65, 8, 64, 69, 65, 8, 23, 30, 29, 31, 8, 1, 118, 8, 65, 79, 132, + 81, 65, 74, 73, 75, 72, 8, 23, 30, 31, 30, 11, 8, 23, 30, 23, 23, 2, 83, 75, 73, + 8, 51, 79, 75, 70, 65, 71, 81, 8, 1, 123, 8, 69, 132, 81, 8, 84, 110, 74, 132, 63, + 68, 65, 74, 80, 84, 65, 79, 81, 8, 23, 24, 18, 8, 1, 113, 8, 75, 64, 65, 79, 8, + 132, 69, 74, 64, 20, 8, 24, 30, 26, 8, 23, 31, 23, 30, 8, 1, 119, 8, 52, 65, 69, + 68, 65, 8, 24, 11, 8, 48, 65, 69, 74, 82, 74, 67, 2, 68, 61, 62, 65, 74, 8, 67, + 65, 68, 65, 74, 8, 1, 128, 8, 64, 65, 79, 8, 44, 1, 94, 20, 8, 29, 25, 1, 112, + 8, 24, 26, 26, 26, 26, 8, 84, 103, 72, 64, 65, 79, 8, 39, 69, 65, 8, 23, 30, 27, + 23, 8, 83, 75, 73, 8, 24, 29, 11, 8, 37, 65, 81, 65, 69, 72, 69, 67, 81, 65, 74, + 2, 24, 27, 22, 22, 8, 30, 22, 22, 22, 8, 98, 8, 65, 69, 74, 67, 65, 66, 110, 68, + 79, 81, 20, 8, 54, 65, 74, 75, 79, 8, 24, 27, 20, 8, 1, 113, 8, 65, 69, 74, 8, + 87, 82, 132, 81, 103, 74, 64, 69, 67, 65, 74, 8, 64, 61, 79, 82, 73, 8, 23, 31, 22, + 23, 8, 1, 115, 8, 64, 65, 73, 8, 23, 31, 22, 26, 11, 8, 39, 65, 74, 2, 65, 69, + 74, 65, 73, 8, 64, 65, 79, 8, 97, 8, 23, 31, 22, 24, 8, 65, 69, 74, 65, 8, 23, + 30, 26, 22, 18, 8, 1, 128, 8, 39, 69, 65, 8, 37, 65, 132, 63, 68, 72, 82, 102, 8, + 84, 61, 79, 65, 74, 8, 23, 24, 8, 1, 117, 8, 64, 65, 79, 8, 27, 22, 22, 11, 8, + 64, 69, 65, 2, 84, 82, 79, 64, 65, 8, 65, 69, 74, 65, 80, 8, 126, 8, 62, 82, 79, + 67, 65, 79, 8, 64, 69, 65, 8, 28, 27, 22, 11, 8, 98, 8, 25, 22, 22, 8, 74, 69, + 73, 73, 81, 8, 64, 65, 80, 68, 61, 72, 62, 8, 25, 22, 22, 8, 1, 117, 8, 23, 22, + 27, 24, 8, 24, 22, 11, 8, 53, 63, 68, 82, 72, 65, 74, 2, 83, 75, 73, 8, 64, 65, + 79, 8, 1, 123, 8, 64, 65, 79, 8, 84, 69, 79, 64, 20, 6, 8, 30, 30, 20, 8, 1, + 125, 8, 27, 26, 31, 8, 23, 31, 23, 30, 8, 23, 27, 24, 25, 8, 23, 8, 126, 8, 44, + 74, 81, 65, 79, 65, 132, 132, 65, 74, 132, 81, 61, 74, 64, 78, 82, 74, 71, 81, 8, 25, + 22, 11, 8, 67, 65, 68, 65, 74, 2, 64, 69, 65, 8, 84, 61, 79, 8, 44, 74, 132, 81, + 61, 72, 72, 61, 81, 69, 75, 74, 8, 64, 65, 79, 8, 23, 25, 25, 24, 1, 112, 8, 1, + 125, 8, 64, 69, 65, 8, 27, 22, 22, 22, 8, 64, 65, 80, 8, 30, 30, 30, 8, 1, 116, + 8, 73, 69, 81, 8, 23, 26, 11, 8, 61, 62, 65, 79, 2, 72, 65, 81, 87, 81, 65, 79, + 65, 73, 8, 51, 65, 74, 87, 69, 67, 8, 98, 8, 64, 65, 79, 8, 65, 79, 68, 109, 68, + 65, 74, 8, 23, 27, 1, 112, 8, 1, 119, 8, 132, 65, 69, 8, 84, 65, 72, 63, 68, 65, + 8, 65, 69, 74, 65, 80, 8, 26, 24, 8, 96, 8, 39, 61, 80, 8, 27, 22, 22, 11, 8, + 39, 61, 80, 2, 74, 69, 63, 68, 81, 8, 62, 69, 80, 8, 1, 115, 8, 73, 69, 81, 8, + 59, 78, 80, 69, 72, 75, 74, 8, 29, 24, 20, 8, 1, 121, 8, 53, 81, 103, 64, 81, 65, + 8, 132, 65, 81, 87, 65, 74, 8, 31, 30, 27, 8, 24, 22, 8, 1, 118, 8, 64, 82, 79, + 63, 68, 8, 29, 30, 11, 8, 53, 63, 68, 82, 72, 64, 65, 78, 82, 81, 61, 81, 69, 75, + 74, 2, 64, 65, 74, 8, 23, 24, 27, 22, 8, 1, 118, 8, 73, 65, 68, 79, 8, 48, 75, + 73, 73, 132, 65, 74, 4, 8, 25, 22, 22, 22, 18, 8, 126, 8, 56, 75, 79, 132, 81, 65, + 68, 65, 79, 8, 49, 75, 74, 74, 65, 74, 64, 61, 73, 73, 8, 45, 80, 20, 8, 23, 31, + 23, 29, 8, 1, 114, 8, 64, 61, 79, 61, 74, 8, 27, 26, 11, 8, 75, 62, 4, 2, 52, + 65, 69, 68, 65, 74, 20, 8, 62, 65, 71, 61, 73, 65, 74, 20, 8, 1, 117, 8, 68, 65, + 79, 87, 82, 4, 8, 73, 69, 81, 8, 23, 30, 31, 23, 11, 8, 1, 122, 8, 61, 82, 66, + 8, 61, 82, 80, 8, 64, 61, 79, 82, 73, 8, 23, 30, 31, 22, 8, 126, 8, 64, 75, 63, + 68, 8, 23, 30, 24, 27, 11, 8, 44, 1, 92, 20, 2, 82, 74, 64, 8, 25, 24, 31, 21, + 24, 25, 8, 1, 121, 8, 61, 72, 80, 8, 37, 65, 81, 81, 65, 74, 8, 23, 30, 30, 25, + 18, 8, 1, 121, 8, 55, 74, 81, 65, 79, 62, 61, 82, 8, 64, 61, 80, 8, 67, 65, 132, + 81, 61, 72, 81, 65, 81, 65, 8, 30, 25, 8, 96, 8, 50, 79, 64, 20, 8, 23, 31, 23, + 28, 11, 8, 64, 65, 80, 2, 64, 65, 79, 8, 37, 65, 81, 79, 61, 67, 8, 126, 8, 43, + 110, 72, 66, 65, 8, 61, 82, 80, 8, 23, 31, 22, 30, 18, 8, 1, 125, 8, 64, 65, 79, + 8, 56, 65, 79, 84, 61, 72, 81, 82, 74, 67, 20, 8, 64, 65, 80, 8, 23, 30, 31, 23, + 8, 1, 118, 8, 61, 82, 66, 87, 82, 84, 65, 74, 64, 65, 74, 8, 23, 23, 11, 8, 51, + 82, 81, 87, 20, 2, 64, 65, 80, 8, 23, 30, 28, 27, 8, 1, 119, 8, 83, 75, 74, 8, + 46, 79, 65, 69, 80, 132, 63, 68, 82, 72, 4, 8, 24, 24, 33, 8, 1, 116, 8, 82, 74, + 64, 8, 7, 132, 63, 68, 65, 69, 74, 81, 6, 8, 64, 69, 65, 8, 23, 27, 8, 96, 8, + 83, 75, 79, 4, 8, 24, 27, 22, 11, 8, 73, 65, 68, 79, 2, 132, 63, 68, 72, 69, 65, + 102, 72, 69, 63, 68, 8, 64, 69, 65, 8, 1, 125, 8, 64, 69, 65, 132, 65, 8, 132, 69, + 65, 8, 23, 27, 11, 8, 96, 8, 84, 65, 72, 63, 68, 65, 8, 68, 61, 74, 64, 65, 72, + 81, 8, 61, 62, 65, 79, 8, 25, 25, 8, 96, 8, 23, 30, 28, 27, 8, 29, 11, 8, 37, + 65, 4, 2, 82, 74, 64, 8, 61, 72, 80, 8, 1, 119, 8, 132, 81, 103, 64, 81, 69, 132, + 63, 68, 65, 79, 132, 65, 69, 81, 80, 8, 132, 81, 79, 20, 8, 24, 11, 8, 96, 8, 73, + 69, 81, 8, 132, 69, 63, 68, 8, 65, 69, 74, 65, 8, 23, 30, 27, 27, 8, 1, 123, 8, + 48, 69, 72, 72, 69, 75, 74, 65, 74, 8, 27, 11, 8, 64, 65, 79, 2, 64, 61, 79, 82, + 73, 8, 39, 79, 65, 68, 62, 110, 68, 74, 65, 8, 1, 125, 8, 62, 65, 69, 8, 68, 61, + 62, 65, 74, 8, 23, 30, 27, 23, 18, 8, 1, 122, 8, 40, 69, 74, 87, 69, 67, 8, 61, + 82, 63, 68, 8, 82, 74, 80, 8, 30, 30, 8, 96, 8, 82, 74, 64, 8, 24, 31, 11, 8, + 42, 79, 75, 72, 73, 61, 74, 4, 2, 64, 65, 73, 8, 36, 82, 66, 132, 69, 63, 68, 81, + 8, 1, 114, 8, 70, 65, 74, 65, 80, 8, 47, 61, 67, 65, 79, 78, 72, 61, 81, 87, 8, + 24, 24, 33, 8, 1, 118, 8, 64, 65, 73, 8, 31, 27, 11, 20, 8, 83, 75, 73, 8, 23, + 31, 24, 22, 8, 1, 125, 8, 83, 65, 79, 4, 8, 23, 23, 11, 8, 73, 65, 68, 79, 2, + 67, 65, 74, 61, 82, 8, 73, 61, 74, 63, 68, 73, 61, 72, 8, 1, 115, 8, 84, 69, 79, + 8, 61, 82, 80, 8, 31, 20, 8, 1, 128, 8, 7, 87, 84, 69, 132, 63, 68, 65, 74, 6, + 8, 62, 69, 80, 8, 71, 61, 74, 74, 20, 8, 29, 27, 8, 1, 119, 8, 64, 69, 65, 8, + 25, 24, 27, 22, 11, 8, 60, 69, 73, 73, 65, 79, 2, 22, 22, 22, 8, 64, 65, 73, 8, + 1, 116, 8, 56, 65, 79, 62, 69, 74, 64, 82, 74, 67, 8, 43, 82, 67, 75, 8, 23, 24, + 22, 22, 20, 8, 96, 8, 64, 69, 65, 8, 75, 64, 65, 79, 8, 36, 74, 79, 82, 66, 65, + 74, 8, 30, 24, 8, 1, 120, 8, 64, 69, 65, 8, 24, 23, 22, 22, 11, 8, 83, 69, 65, + 72, 72, 65, 69, 63, 68, 81, 2, 64, 65, 79, 74, 8, 132, 75, 84, 65, 69, 81, 8, 1, + 125, 8, 65, 79, 132, 81, 65, 74, 8, 124, 63, 20, 8, 25, 28, 25, 20, 8, 97, 8, 65, + 69, 74, 65, 8, 23, 30, 22, 31, 8, 56, 65, 79, 132, 81, 103, 79, 71, 82, 74, 67, 8, + 23, 30, 31, 31, 8, 1, 119, 8, 23, 24, 20, 8, 31, 11, 8, 83, 75, 74, 2, 7, 51, + 61, 79, 87, 65, 72, 72, 65, 74, 6, 8, 69, 132, 81, 8, 126, 8, 67, 61, 74, 87, 8, + 64, 61, 80, 8, 23, 30, 26, 22, 20, 8, 1, 128, 8, 84, 82, 79, 64, 65, 8, 37, 86, + 71, 8, 82, 74, 64, 8, 28, 29, 8, 126, 8, 42, 79, 82, 74, 64, 8, 24, 29, 11, 8, + 23, 31, 22, 30, 21, 23, 25, 20, 2, 68, 65, 66, 81, 8, 83, 75, 74, 8, 1, 114, 8, + 73, 69, 79, 8, 47, 61, 73, 78, 65, 74, 8, 23, 30, 29, 30, 1, 112, 8, 1, 115, 8, + 22, 22, 22, 8, 41, 65, 79, 74, 65, 79, 8, 23, 29, 26, 8, 25, 22, 8, 126, 8, 64, + 61, 102, 8, 25, 23, 11, 8, 132, 75, 72, 72, 2, 42, 79, 75, 102, 4, 37, 65, 79, 72, + 69, 74, 8, 65, 69, 74, 73, 61, 72, 8, 96, 8, 44, 44, 44, 8, 82, 74, 64, 8, 23, + 22, 20, 8, 96, 8, 46, 109, 74, 69, 67, 4, 8, 44, 44, 44, 8, 61, 82, 66, 8, 23, + 26, 22, 8, 1, 122, 8, 25, 22, 20, 8, 24, 27, 22, 11, 8, 64, 69, 65, 2, 48, 103, + 64, 63, 68, 65, 74, 8, 84, 65, 79, 64, 65, 74, 8, 1, 115, 8, 64, 69, 65, 8, 43, + 61, 82, 80, 84, 61, 79, 81, 80, 8, 23, 24, 20, 8, 1, 123, 8, 83, 75, 79, 4, 8, + 23, 30, 29, 31, 8, 39, 61, 79, 110, 62, 65, 79, 8, 23, 30, 31, 28, 8, 1, 125, 8, + 83, 75, 74, 8, 25, 28, 25, 11, 8, 65, 79, 67, 61, 62, 2, 45, 61, 66, 66, 104, 8, + 31, 24, 11, 8, 1, 117, 8, 61, 82, 63, 68, 8, 61, 82, 66, 8, 30, 29, 20, 8, 126, + 8, 75, 64, 65, 79, 8, 36, 74, 72, 65, 69, 68, 65, 8, 65, 81, 84, 61, 69, 67, 65, + 74, 8, 30, 22, 22, 8, 1, 127, 8, 84, 65, 69, 72, 8, 25, 26, 30, 25, 30, 11, 8, + 64, 65, 79, 2, 53, 65, 69, 81, 8, 61, 74, 64, 65, 79, 65, 79, 8, 1, 122, 8, 83, + 75, 73, 8, 72, 69, 65, 68, 65, 74, 8, 24, 18, 8, 1, 115, 8, 64, 61, 80, 8, 23, + 23, 25, 21, 23, 25, 8, 24, 22, 22, 22, 8, 23, 26, 23, 8, 97, 8, 64, 69, 65, 8, + 23, 24, 11, 8, 64, 65, 79, 70, 65, 74, 69, 67, 65, 74, 2, 64, 61, 80, 8, 64, 61, + 102, 8, 1, 116, 8, 48, 61, 74, 67, 65, 72, 8, 84, 69, 79, 64, 8, 23, 29, 28, 31, + 1, 112, 8, 1, 120, 8, 23, 22, 22, 8, 81, 79, 75, 81, 87, 8, 64, 65, 74, 8, 28, + 23, 8, 1, 122, 8, 83, 65, 79, 62, 69, 74, 64, 65, 74, 8, 23, 30, 25, 27, 11, 8, + 61, 72, 132, 75, 2, 74, 69, 63, 68, 81, 8, 84, 65, 72, 63, 68, 65, 8, 98, 8, 64, + 61, 80, 8, 84, 65, 69, 63, 68, 81, 8, 23, 24, 27, 33, 8, 1, 125, 8, 64, 65, 79, + 8, 23, 30, 31, 25, 8, 132, 63, 68, 79, 69, 66, 81, 65, 74, 8, 23, 23, 8, 98, 8, + 24, 30, 20, 8, 24, 30, 26, 11, 8, 68, 69, 65, 132, 69, 67, 65, 74, 2, 60, 82, 132, + 63, 68, 82, 102, 8, 64, 65, 73, 67, 65, 73, 103, 102, 8, 1, 117, 8, 64, 65, 80, 8, + 65, 69, 74, 65, 79, 8, 31, 20, 8, 1, 123, 8, 83, 75, 79, 68, 61, 74, 64, 65, 74, + 8, 67, 65, 72, 65, 67, 65, 74, 8, 83, 75, 72, 72, 65, 74, 8, 27, 8, 1, 127, 8, + 39, 61, 80, 8, 27, 28, 11, 8, 65, 79, 68, 61, 72, 81, 65, 74, 2, 23, 28, 11, 8, + 55, 68, 79, 8, 1, 127, 8, 28, 28, 11, 8, 82, 74, 64, 8, 27, 18, 8, 1, 116, 8, + 84, 69, 79, 8, 65, 69, 74, 8, 68, 69, 74, 87, 82, 87, 82, 66, 110, 67, 65, 74, 8, + 23, 25, 30, 23, 8, 1, 127, 8, 82, 74, 64, 8, 24, 11, 8, 64, 61, 80, 2, 47, 61, + 67, 65, 79, 78, 72, 61, 81, 87, 4, 56, 75, 79, 132, 81, 65, 68, 65, 79, 32, 8, 75, + 64, 65, 79, 8, 1, 127, 8, 132, 69, 74, 64, 8, 64, 65, 79, 8, 28, 31, 20, 8, 1, + 123, 8, 110, 62, 65, 79, 8, 64, 69, 65, 8, 84, 103, 79, 65, 74, 8, 26, 22, 8, 22, + 22, 22, 8, 23, 22, 11, 8, 132, 69, 63, 68, 2, 132, 69, 74, 64, 8, 40, 79, 72, 61, + 102, 8, 1, 118, 8, 61, 82, 66, 8, 82, 74, 64, 8, 23, 30, 29, 22, 20, 8, 1, 125, + 8, 64, 65, 79, 8, 82, 74, 64, 8, 109, 81, 69, 67, 65, 8, 23, 30, 28, 28, 8, 1, + 120, 8, 64, 69, 65, 8, 25, 22, 22, 22, 11, 8, 87, 75, 67, 65, 74, 2, 65, 79, 109, + 66, 66, 74, 65, 81, 20, 8, 53, 81, 61, 74, 64, 78, 82, 74, 71, 81, 8, 1, 122, 8, + 22, 22, 22, 8, 48, 61, 69, 8, 23, 30, 30, 30, 20, 8, 1, 114, 8, 75, 68, 74, 65, + 8, 132, 69, 63, 68, 8, 64, 61, 102, 8, 25, 27, 8, 126, 8, 84, 65, 79, 64, 65, 74, + 20, 8, 23, 30, 31, 30, 11, 8, 36, 62, 62, 61, 82, 2, 132, 75, 72, 63, 68, 65, 74, + 8, 53, 63, 68, 84, 69, 65, 79, 69, 67, 71, 65, 69, 81, 65, 74, 8, 1, 128, 8, 64, + 69, 65, 132, 65, 74, 8, 57, 75, 68, 74, 82, 74, 67, 65, 74, 8, 31, 20, 8, 1, 120, + 8, 23, 26, 22, 8, 22, 22, 22, 8, 44, 63, 68, 8, 28, 8, 1, 123, 8, 23, 22, 20, + 8, 31, 22, 11, 8, 24, 22, 22, 2, 84, 65, 79, 64, 65, 74, 8, 74, 69, 63, 68, 81, + 8, 96, 8, 54, 65, 69, 72, 62, 65, 81, 79, 61, 67, 8, 42, 65, 64, 61, 74, 71, 65, + 74, 8, 25, 22, 22, 22, 1, 112, 8, 1, 120, 8, 64, 69, 65, 8, 70, 82, 74, 67, 65, + 8, 61, 82, 63, 68, 8, 23, 8, 1, 114, 8, 61, 82, 66, 8, 23, 30, 24, 28, 11, 8, + 23, 23, 26, 20, 2, 132, 65, 69, 74, 65, 8, 22, 30, 20, 8, 1, 116, 8, 64, 69, 65, + 8, 82, 74, 64, 8, 28, 27, 28, 18, 8, 1, 118, 8, 82, 74, 64, 8, 54, 61, 67, 65, + 80, 75, 79, 64, 74, 82, 74, 67, 32, 8, 64, 65, 79, 8, 23, 23, 8, 1, 125, 8, 64, + 65, 79, 8, 23, 24, 27, 11, 8, 61, 72, 132, 75, 2, 64, 65, 80, 68, 61, 72, 62, 8, + 67, 61, 74, 87, 8, 97, 8, 84, 69, 79, 8, 74, 82, 79, 8, 24, 22, 22, 22, 18, 8, + 1, 119, 8, 87, 82, 73, 8, 23, 30, 30, 25, 8, 65, 69, 74, 8, 24, 22, 8, 1, 119, + 8, 84, 65, 79, 64, 65, 74, 20, 6, 8, 23, 11, 8, 37, 61, 74, 67, 72, 61, 64, 65, + 132, 63, 68, 2, 53, 69, 65, 8, 69, 63, 68, 8, 1, 122, 8, 83, 75, 73, 8, 64, 65, + 79, 8, 26, 31, 31, 33, 8, 1, 127, 8, 53, 69, 65, 8, 79, 82, 74, 64, 8, 23, 25, + 26, 24, 8, 31, 22, 8, 37, 61, 72, 71, 65, 8, 27, 22, 22, 11, 8, 82, 74, 81, 65, + 79, 79, 69, 63, 68, 81, 65, 81, 20, 2, 84, 65, 80, 68, 61, 72, 62, 8, 64, 65, 80, + 8, 62, 65, 132, 63, 68, 72, 69, 65, 102, 81, 8, 37, 65, 61, 73, 81, 65, 74, 8, 23, + 24, 22, 22, 20, 8, 53, 82, 73, 73, 65, 20, 8, 23, 30, 24, 24, 8, 84, 65, 132, 65, + 74, 81, 4, 8, 23, 24, 22, 22, 8, 1, 123, 8, 64, 65, 79, 8, 23, 24, 11, 8, 64, + 65, 79, 2, 42, 79, 65, 64, 86, 8, 66, 110, 79, 8, 1, 118, 8, 23, 30, 27, 27, 8, + 84, 103, 63, 68, 132, 81, 8, 23, 30, 27, 30, 20, 8, 1, 122, 8, 61, 82, 66, 8, 36, + 82, 63, 68, 8, 37, 65, 102, 65, 79, 8, 23, 31, 24, 22, 8, 1, 125, 8, 46, 61, 73, + 62, 61, 63, 68, 8, 29, 11, 8, 39, 69, 65, 2, 36, 74, 84, 65, 132, 65, 74, 68, 65, + 69, 81, 8, 64, 61, 87, 82, 8, 98, 8, 82, 74, 64, 8, 23, 30, 29, 31, 8, 23, 31, + 23, 27, 20, 8, 1, 121, 8, 59, 61, 74, 67, 8, 41, 75, 74, 64, 80, 8, 82, 74, 64, + 8, 25, 22, 22, 22, 8, 1, 117, 8, 49, 61, 73, 65, 74, 10, 8, 23, 24, 22, 22, 11, + 8, 23, 31, 23, 28, 2, 67, 61, 74, 87, 8, 84, 65, 79, 64, 65, 74, 8, 64, 61, 102, + 8, 66, 82, 79, 63, 68, 81, 62, 61, 79, 8, 23, 24, 22, 22, 20, 8, 1, 127, 8, 82, + 74, 64, 8, 24, 24, 20, 8, 64, 65, 73, 8, 23, 25, 8, 1, 116, 8, 87, 82, 79, 8, + 29, 11, 8, 65, 69, 74, 65, 80, 2, 64, 65, 73, 8, 64, 65, 79, 8, 25, 28, 25, 8, + 64, 65, 73, 8, 23, 22, 22, 18, 8, 1, 121, 8, 23, 30, 20, 8, 30, 22, 22, 22, 8, + 52, 61, 81, 68, 68, 61, 82, 132, 65, 80, 8, 23, 22, 29, 28, 8, 1, 121, 8, 64, 65, + 74, 8, 31, 31, 11, 8, 82, 74, 64, 2, 69, 79, 67, 65, 74, 64, 84, 65, 72, 63, 68, + 65, 74, 8, 87, 82, 79, 8, 1, 122, 8, 70, 65, 74, 69, 67, 65, 74, 8, 53, 81, 79, + 20, 8, 24, 22, 20, 8, 1, 118, 8, 62, 65, 81, 79, 103, 67, 81, 8, 23, 30, 31, 22, + 8, 65, 69, 74, 65, 74, 8, 30, 8, 1, 113, 8, 23, 26, 25, 23, 8, 30, 23, 11, 8, + 23, 30, 29, 29, 2, 55, 74, 81, 65, 79, 74, 65, 68, 73, 82, 74, 67, 65, 74, 8, 42, + 79, 110, 74, 132, 81, 79, 20, 8, 1, 121, 8, 23, 27, 20, 8, 64, 65, 79, 8, 29, 1, + 112, 8, 1, 123, 8, 64, 69, 65, 8, 82, 74, 64, 8, 62, 69, 80, 8, 28, 8, 98, 8, + 65, 69, 74, 65, 73, 8, 23, 31, 24, 22, 11, 8, 132, 69, 81, 87, 82, 74, 67, 65, 74, + 2, 82, 74, 64, 8, 67, 65, 84, 61, 72, 81, 69, 67, 8, 1, 128, 8, 65, 69, 74, 87, + 65, 72, 74, 65, 8, 132, 69, 74, 64, 20, 8, 23, 23, 20, 8, 1, 127, 8, 132, 69, 74, + 64, 8, 61, 62, 65, 79, 8, 23, 30, 26, 30, 8, 23, 24, 8, 1, 120, 8, 36, 82, 66, + 66, 61, 132, 132, 82, 74, 67, 8, 30, 11, 8, 67, 61, 74, 87, 2, 65, 79, 84, 75, 79, + 62, 65, 74, 8, 67, 72, 61, 82, 62, 65, 8, 1, 116, 8, 84, 69, 79, 64, 8, 40, 69, + 74, 65, 8, 23, 30, 24, 28, 33, 8, 126, 8, 23, 26, 20, 8, 61, 82, 66, 8, 64, 65, + 74, 65, 74, 8, 23, 24, 8, 1, 115, 8, 53, 81, 65, 72, 72, 65, 74, 8, 27, 30, 11, + 8, 37, 65, 132, 63, 68, 84, 65, 79, 64, 65, 74, 2, 53, 63, 68, 82, 72, 64, 69, 67, + 71, 65, 69, 81, 8, 56, 65, 79, 65, 69, 74, 8, 1, 125, 8, 69, 132, 81, 8, 23, 30, + 29, 30, 8, 30, 25, 11, 8, 96, 8, 64, 65, 74, 8, 64, 69, 65, 132, 65, 73, 8, 61, + 82, 63, 68, 8, 25, 27, 8, 1, 123, 8, 25, 25, 23, 21, 23, 25, 8, 30, 23, 11, 8, + 53, 81, 82, 73, 78, 66, 2, 23, 22, 22, 8, 64, 65, 80, 68, 61, 72, 62, 8, 96, 8, + 68, 65, 79, 87, 82, 4, 8, 39, 82, 79, 63, 68, 8, 27, 22, 20, 8, 1, 123, 8, 83, + 65, 79, 82, 79, 132, 61, 63, 68, 81, 8, 43, 65, 79, 79, 65, 74, 8, 67, 65, 71, 110, + 74, 64, 69, 67, 81, 8, 27, 27, 8, 1, 114, 8, 36, 79, 81, 8, 31, 27, 22, 11, 8, + 64, 65, 74, 2, 36, 82, 66, 132, 63, 68, 72, 61, 67, 8, 124, 63, 20, 8, 1, 113, 8, + 87, 82, 79, 8, 68, 61, 81, 8, 23, 31, 23, 29, 20, 8, 98, 8, 64, 65, 80, 8, 64, + 65, 79, 8, 61, 62, 84, 65, 69, 63, 68, 65, 74, 64, 65, 79, 8, 30, 28, 8, 97, 8, + 37, 65, 69, 8, 31, 31, 11, 8, 7, 39, 61, 80, 2, 61, 72, 132, 75, 8, 73, 69, 81, + 8, 1, 115, 8, 64, 65, 74, 8, 31, 30, 27, 8, 27, 26, 11, 8, 45, 61, 68, 79, 65, + 80, 62, 65, 83, 109, 72, 71, 65, 79, 82, 74, 67, 8, 74, 61, 63, 68, 8, 22, 22, 22, + 8, 23, 31, 24, 22, 8, 1, 117, 8, 69, 132, 81, 8, 30, 30, 11, 8, 60, 61, 68, 72, + 2, 62, 65, 69, 8, 23, 31, 24, 25, 25, 8, 1, 120, 8, 61, 82, 80, 8, 64, 65, 80, + 8, 29, 25, 28, 31, 18, 8, 98, 8, 64, 65, 79, 8, 83, 75, 74, 8, 27, 30, 28, 24, + 8, 25, 26, 8, 1, 123, 8, 23, 27, 24, 25, 8, 23, 29, 22, 11, 8, 36, 74, 132, 81, + 69, 65, 67, 65, 2, 64, 69, 65, 8, 132, 75, 72, 72, 65, 74, 8, 1, 127, 8, 7, 68, + 65, 66, 81, 69, 67, 65, 79, 8, 71, 72, 65, 69, 74, 65, 74, 8, 24, 20, 8, 126, 8, + 23, 23, 26, 20, 8, 64, 65, 74, 8, 44, 44, 44, 8, 24, 22, 22, 8, 1, 125, 8, 53, + 69, 65, 8, 28, 31, 11, 8, 53, 75, 74, 74, 81, 61, 67, 65, 74, 32, 2, 132, 69, 63, + 68, 8, 65, 69, 74, 8, 97, 8, 64, 65, 79, 8, 36, 74, 79, 82, 66, 65, 74, 8, 23, + 31, 18, 8, 1, 118, 8, 84, 69, 79, 8, 61, 72, 132, 75, 8, 87, 82, 79, 8, 29, 28, + 8, 1, 120, 8, 79, 65, 63, 68, 81, 8, 23, 30, 25, 29, 11, 8, 64, 61, 68, 65, 79, + 2, 45, 82, 74, 67, 65, 74, 8, 40, 81, 61, 81, 8, 126, 8, 53, 81, 61, 64, 81, 8, + 48, 75, 74, 61, 81, 65, 74, 8, 25, 24, 22, 22, 18, 8, 96, 8, 64, 61, 102, 8, 64, + 65, 73, 8, 132, 63, 68, 84, 61, 74, 71, 65, 74, 8, 23, 24, 8, 1, 121, 8, 64, 65, + 79, 8, 26, 11, 8, 37, 65, 64, 110, 79, 66, 74, 69, 80, 2, 25, 24, 27, 22, 8, 64, + 69, 65, 8, 1, 115, 8, 60, 82, 79, 8, 64, 69, 65, 132, 65, 79, 8, 31, 30, 27, 1, + 112, 8, 97, 8, 45, 61, 68, 79, 65, 8, 64, 69, 65, 8, 61, 62, 87, 69, 65, 72, 65, + 74, 8, 25, 27, 8, 97, 8, 132, 69, 63, 68, 8, 23, 24, 27, 22, 11, 8, 83, 75, 74, + 2, 67, 65, 73, 103, 102, 8, 132, 75, 72, 63, 68, 65, 80, 8, 126, 8, 62, 69, 80, 8, + 57, 69, 79, 8, 29, 30, 1, 112, 8, 1, 128, 8, 64, 69, 65, 8, 42, 65, 73, 65, 69, + 74, 64, 65, 62, 65, 68, 109, 79, 64, 65, 8, 74, 65, 82, 65, 74, 8, 23, 27, 22, 29, + 8, 1, 127, 8, 64, 65, 79, 8, 23, 24, 11, 8, 64, 61, 102, 2, 57, 65, 74, 69, 67, + 8, 71, 61, 74, 74, 8, 1, 113, 8, 48, 69, 65, 81, 83, 65, 79, 81, 79, 61, 67, 8, + 70, 82, 74, 67, 65, 8, 23, 20, 8, 1, 119, 8, 68, 61, 81, 8, 68, 61, 62, 65, 74, + 8, 61, 72, 80, 8, 24, 22, 8, 1, 116, 8, 24, 25, 27, 21, 24, 24, 8, 29, 30, 11, + 8, 64, 65, 79, 2, 36, 74, 64, 65, 79, 82, 74, 67, 8, 23, 31, 23, 24, 21, 23, 29, + 20, 8, 97, 8, 70, 65, 73, 61, 74, 64, 8, 25, 23, 25, 8, 25, 22, 22, 11, 8, 1, + 122, 8, 64, 61, 64, 82, 79, 63, 68, 8, 64, 61, 102, 8, 82, 74, 64, 8, 23, 30, 29, + 22, 8, 1, 122, 8, 65, 69, 74, 65, 8, 23, 30, 27, 29, 11, 8, 132, 78, 79, 65, 63, + 68, 65, 2, 64, 65, 79, 8, 51, 79, 75, 81, 75, 71, 75, 72, 72, 8, 98, 8, 23, 30, + 31, 23, 8, 64, 65, 80, 8, 23, 24, 1, 112, 8, 1, 117, 8, 132, 75, 84, 69, 65, 8, + 64, 61, 102, 8, 61, 72, 80, 8, 31, 8, 1, 122, 8, 83, 75, 73, 8, 23, 26, 31, 22, + 11, 8, 67, 65, 73, 69, 65, 81, 65, 81, 65, 74, 2, 31, 31, 11, 20, 8, 64, 65, 79, + 8, 1, 119, 8, 73, 61, 102, 67, 65, 62, 65, 74, 64, 20, 8, 23, 30, 23, 23, 8, 29, + 28, 33, 8, 1, 128, 8, 64, 69, 65, 8, 23, 24, 20, 8, 23, 31, 23, 22, 21, 23, 27, + 20, 8, 23, 31, 22, 28, 8, 1, 120, 8, 39, 61, 80, 8, 23, 30, 30, 24, 11, 8, 36, + 74, 132, 78, 79, 110, 63, 68, 65, 74, 2, 65, 79, 68, 61, 72, 81, 65, 74, 20, 8, 132, + 69, 81, 87, 65, 74, 64, 65, 8, 1, 113, 8, 71, 72, 61, 79, 8, 68, 61, 81, 81, 65, + 74, 8, 27, 31, 11, 8, 1, 115, 8, 53, 81, 69, 73, 73, 65, 74, 8, 87, 82, 73, 8, + 62, 69, 80, 8, 30, 25, 8, 96, 8, 64, 65, 79, 8, 23, 30, 11, 8, 39, 79, 20, 2, + 55, 74, 81, 65, 79, 79, 69, 63, 68, 81, 8, 64, 65, 79, 8, 1, 116, 8, 47, 65, 62, + 65, 74, 80, 71, 82, 74, 64, 65, 8, 64, 65, 73, 8, 23, 30, 31, 27, 20, 8, 1, 128, + 8, 61, 82, 63, 68, 8, 87, 61, 68, 72, 65, 74, 8, 30, 22, 22, 8, 25, 8, 1, 128, + 8, 83, 75, 74, 8, 23, 11, 8, 83, 75, 74, 2, 67, 65, 132, 81, 65, 69, 67, 65, 79, + 81, 65, 74, 8, 23, 27, 23, 24, 8, 1, 120, 8, 40, 69, 74, 4, 8, 57, 65, 69, 132, + 65, 8, 27, 24, 24, 11, 8, 1, 121, 8, 87, 82, 20, 8, 30, 22, 22, 8, 132, 81, 69, + 65, 67, 8, 28, 27, 8, 1, 118, 8, 132, 63, 68, 82, 72, 65, 8, 24, 25, 11, 8, 42, + 79, 82, 78, 78, 65, 74, 2, 87, 65, 69, 67, 65, 74, 8, 62, 65, 81, 79, 61, 67, 65, + 74, 8, 1, 122, 8, 62, 69, 80, 8, 23, 30, 24, 31, 8, 23, 30, 30, 22, 11, 8, 1, + 128, 8, 36, 82, 80, 132, 81, 61, 81, 81, 82, 74, 67, 8, 71, 65, 69, 74, 65, 74, 8, + 124, 63, 20, 8, 23, 27, 23, 29, 8, 1, 116, 8, 64, 61, 102, 8, 23, 23, 11, 8, 42, + 65, 62, 65, 79, 81, 2, 23, 26, 23, 8, 36, 82, 63, 68, 8, 1, 121, 8, 45, 80, 20, + 8, 64, 69, 65, 132, 65, 79, 8, 24, 29, 1, 112, 8, 1, 122, 8, 64, 65, 80, 8, 36, + 82, 80, 67, 72, 65, 69, 63, 68, 8, 83, 75, 74, 8, 23, 31, 23, 27, 8, 98, 8, 23, + 31, 23, 31, 8, 24, 11, 8, 65, 69, 74, 132, 65, 69, 81, 69, 4, 2, 75, 64, 65, 79, + 8, 44, 63, 68, 8, 1, 119, 8, 73, 69, 81, 8, 43, 65, 79, 71, 109, 73, 73, 72, 69, + 63, 68, 8, 23, 27, 20, 8, 1, 116, 8, 83, 75, 74, 8, 40, 81, 61, 81, 8, 64, 61, + 74, 74, 8, 23, 31, 22, 27, 8, 36, 62, 4, 8, 25, 27, 22, 11, 8, 41, 65, 68, 72, + 75, 84, 13, 132, 63, 68, 65, 74, 2, 47, 109, 68, 74, 65, 8, 64, 61, 102, 8, 1, 117, + 8, 45, 82, 72, 69, 8, 47, 65, 69, 81, 65, 79, 32, 8, 26, 27, 20, 8, 1, 120, 8, + 7, 48, 65, 69, 74, 65, 8, 64, 65, 80, 8, 67, 65, 79, 69, 63, 68, 81, 65, 81, 65, + 74, 8, 23, 23, 8, 1, 115, 8, 71, 109, 74, 74, 65, 74, 8, 23, 31, 11, 8, 23, 27, + 22, 2, 62, 69, 80, 8, 65, 69, 74, 8, 1, 120, 8, 48, 61, 69, 8, 72, 65, 69, 63, + 68, 81, 8, 23, 30, 31, 22, 20, 8, 1, 123, 8, 67, 65, 81, 79, 69, 65, 62, 65, 74, + 35, 8, 57, 69, 72, 68, 65, 72, 73, 8, 68, 61, 62, 65, 74, 8, 25, 22, 22, 8, 1, + 127, 8, 61, 72, 80, 8, 24, 23, 11, 8, 83, 75, 79, 2, 69, 68, 79, 65, 79, 8, 31, + 27, 11, 20, 8, 1, 125, 8, 74, 69, 63, 68, 81, 8, 36, 74, 132, 81, 61, 72, 81, 8, + 23, 28, 26, 27, 20, 8, 1, 119, 8, 61, 82, 63, 68, 8, 84, 69, 79, 64, 8, 51, 75, + 132, 69, 81, 69, 75, 74, 8, 27, 8, 1, 114, 8, 23, 24, 11, 8, 23, 30, 22, 30, 11, + 8, 64, 65, 79, 2, 41, 79, 61, 67, 65, 8, 65, 69, 74, 65, 8, 1, 128, 8, 68, 61, + 81, 81, 65, 20, 8, 27, 24, 20, 8, 25, 22, 20, 8, 1, 113, 8, 124, 63, 20, 8, 57, + 75, 68, 74, 82, 74, 67, 65, 74, 8, 84, 75, 79, 64, 65, 74, 20, 8, 23, 31, 24, 22, + 8, 1, 128, 8, 83, 75, 73, 8, 23, 22, 11, 8, 62, 65, 69, 64, 65, 74, 2, 84, 65, + 69, 102, 8, 43, 61, 82, 80, 68, 61, 72, 81, 80, 4, 8, 97, 8, 84, 82, 79, 64, 65, + 8, 60, 82, 67, 65, 87, 75, 67, 65, 74, 65, 74, 8, 23, 24, 20, 8, 97, 8, 66, 110, + 79, 8, 66, 110, 79, 8, 44, 44, 44, 8, 23, 26, 8, 97, 8, 22, 22, 22, 8, 23, 31, + 23, 30, 11, 8, 61, 62, 132, 75, 72, 82, 81, 65, 2, 84, 69, 79, 64, 8, 61, 82, 66, + 79, 65, 63, 68, 81, 8, 1, 113, 8, 84, 69, 79, 8, 132, 81, 69, 65, 67, 8, 23, 31, + 23, 28, 33, 8, 96, 8, 64, 65, 74, 8, 60, 84, 65, 63, 71, 65, 8, 68, 61, 62, 65, + 8, 23, 8, 96, 8, 42, 65, 62, 103, 82, 64, 65, 8, 27, 11, 8, 74, 61, 63, 68, 2, + 23, 23, 20, 8, 132, 69, 65, 8, 1, 114, 8, 64, 69, 65, 8, 53, 81, 61, 64, 81, 8, + 25, 30, 20, 8, 1, 128, 8, 53, 63, 68, 82, 72, 64, 65, 78, 82, 81, 61, 81, 69, 75, + 74, 8, 64, 65, 74, 8, 66, 110, 79, 8, 28, 31, 8, 1, 120, 8, 43, 65, 79, 79, 8, + 23, 23, 11, 8, 64, 65, 80, 68, 61, 72, 62, 2, 64, 65, 79, 8, 36, 78, 79, 69, 72, + 21, 45, 82, 74, 69, 8, 1, 119, 8, 36, 62, 81, 65, 69, 72, 82, 74, 67, 8, 62, 65, + 69, 64, 65, 74, 8, 27, 24, 20, 8, 1, 115, 8, 65, 69, 74, 65, 73, 8, 61, 82, 63, + 68, 8, 64, 69, 65, 132, 65, 79, 8, 25, 27, 22, 8, 1, 117, 8, 124, 63, 20, 8, 26, + 29, 11, 8, 84, 69, 65, 64, 65, 79, 2, 41, 65, 79, 74, 79, 82, 66, 32, 8, 39, 61, + 80, 8, 1, 118, 8, 23, 31, 23, 29, 8, 48, 61, 67, 69, 132, 81, 79, 61, 81, 8, 27, + 29, 1, 112, 8, 1, 117, 8, 39, 69, 65, 8, 64, 65, 80, 8, 36, 62, 81, 75, 73, 73, + 65, 74, 8, 30, 26, 8, 1, 115, 8, 82, 132, 84, 20, 8, 29, 26, 11, 8, 67, 65, 73, + 103, 102, 2, 74, 75, 63, 68, 8, 79, 82, 74, 64, 8, 1, 114, 8, 82, 74, 64, 8, 83, + 65, 79, 132, 63, 68, 69, 65, 64, 65, 74, 65, 79, 8, 23, 1, 112, 8, 1, 113, 8, 62, + 65, 79, 8, 23, 22, 20, 8, 64, 61, 102, 8, 29, 27, 22, 8, 1, 128, 8, 51, 66, 72, + 69, 63, 68, 81, 8, 23, 22, 11, 8, 62, 65, 87, 75, 67, 65, 74, 2, 64, 69, 65, 8, + 49, 65, 82, 66, 65, 79, 81, 8, 96, 8, 29, 22, 22, 22, 8, 44, 1, 92, 20, 8, 23, + 22, 30, 26, 26, 33, 8, 98, 8, 23, 23, 22, 8, 132, 63, 68, 72, 61, 67, 65, 74, 8, + 71, 72, 65, 69, 74, 65, 74, 8, 24, 29, 8, 1, 115, 8, 43, 65, 65, 79, 65, 80, 64, + 69, 65, 74, 132, 81, 65, 8, 25, 24, 11, 8, 64, 65, 79, 2, 65, 79, 68, 65, 62, 72, + 69, 63, 68, 8, 73, 65, 69, 74, 65, 74, 8, 83, 75, 74, 8, 39, 69, 65, 8, 26, 31, + 20, 8, 1, 128, 8, 23, 30, 26, 28, 8, 82, 74, 64, 8, 37, 79, 75, 64, 65, 8, 28, + 27, 24, 8, 1, 118, 8, 23, 31, 23, 29, 8, 31, 30, 11, 8, 69, 132, 81, 2, 73, 109, + 63, 68, 81, 65, 8, 23, 24, 27, 22, 8, 1, 118, 8, 84, 65, 72, 63, 68, 65, 74, 8, + 64, 69, 65, 8, 24, 22, 22, 18, 8, 1, 116, 8, 64, 61, 80, 8, 59, 78, 80, 69, 72, + 75, 74, 8, 73, 109, 63, 68, 81, 65, 8, 30, 8, 1, 127, 8, 82, 74, 64, 8, 28, 30, + 11, 8, 87, 69, 65, 72, 65, 74, 64, 65, 74, 2, 61, 62, 65, 79, 8, 64, 75, 63, 68, + 8, 1, 114, 8, 68, 69, 74, 65, 69, 74, 67, 65, 81, 61, 74, 8, 43, 75, 66, 65, 8, + 24, 22, 22, 22, 33, 8, 96, 8, 7, 37, 65, 79, 61, 81, 82, 74, 67, 6, 8, 84, 65, + 79, 64, 65, 74, 8, 124, 63, 20, 8, 28, 27, 28, 8, 1, 125, 8, 73, 61, 74, 8, 23, + 29, 11, 8, 65, 81, 84, 61, 80, 2, 52, 75, 132, 63, 68, 65, 79, 132, 81, 79, 61, 102, + 65, 8, 69, 132, 81, 8, 96, 8, 84, 65, 69, 81, 65, 79, 65, 80, 8, 64, 65, 79, 8, + 30, 27, 22, 11, 8, 98, 8, 83, 75, 74, 8, 51, 65, 74, 87, 69, 67, 8, 64, 65, 79, + 8, 23, 30, 31, 22, 8, 1, 121, 8, 40, 69, 74, 65, 8, 24, 29, 11, 8, 72, 65, 81, + 87, 81, 65, 74, 2, 64, 65, 80, 8, 132, 81, 79, 61, 102, 65, 8, 1, 125, 8, 44, 1, + 89, 20, 8, 23, 22, 24, 32, 8, 24, 31, 18, 8, 1, 116, 8, 68, 61, 62, 65, 8, 83, + 65, 79, 84, 61, 72, 81, 65, 81, 20, 8, 51, 79, 110, 66, 82, 74, 67, 8, 23, 28, 25, + 30, 8, 1, 115, 8, 23, 22, 26, 8, 27, 30, 28, 24, 11, 8, 68, 65, 82, 81, 65, 2, + 61, 62, 65, 79, 8, 74, 82, 79, 8, 1, 118, 8, 49, 79, 20, 8, 73, 65, 81, 65, 79, + 8, 25, 23, 18, 8, 98, 8, 83, 75, 74, 8, 132, 69, 63, 68, 8, 61, 62, 65, 74, 64, + 80, 8, 31, 8, 98, 8, 69, 63, 68, 8, 23, 24, 11, 8, 53, 81, 65, 73, 78, 65, 72, + 2, 68, 109, 68, 65, 79, 65, 8, 66, 75, 72, 67, 65, 74, 64, 65, 32, 8, 1, 113, 8, + 64, 65, 79, 8, 82, 74, 64, 8, 23, 31, 22, 23, 20, 8, 1, 119, 8, 65, 69, 74, 65, + 8, 45, 61, 68, 79, 8, 61, 72, 72, 65, 79, 8, 23, 23, 8, 1, 120, 8, 84, 69, 79, + 64, 8, 23, 22, 28, 24, 11, 8, 64, 65, 80, 2, 53, 81, 79, 20, 8, 132, 69, 63, 68, + 8, 60, 65, 69, 63, 68, 74, 65, 79, 32, 8, 62, 65, 69, 8, 29, 1, 112, 8, 1, 123, + 8, 62, 65, 81, 79, 61, 63, 68, 81, 65, 74, 20, 6, 8, 64, 69, 65, 8, 22, 30, 20, + 8, 30, 30, 8, 1, 125, 8, 59, 61, 63, 68, 81, 8, 23, 27, 11, 8, 64, 69, 65, 2, + 42, 65, 84, 69, 102, 8, 37, 65, 69, 8, 1, 122, 8, 23, 31, 23, 22, 21, 23, 27, 20, + 8, 26, 24, 25, 8, 23, 22, 22, 18, 8, 1, 115, 8, 64, 61, 102, 8, 132, 81, 79, 20, + 8, 64, 65, 79, 8, 23, 22, 29, 29, 8, 1, 119, 8, 66, 110, 79, 8, 30, 31, 31, 11, + 8, 61, 72, 72, 67, 65, 73, 65, 69, 74, 65, 2, 65, 79, 84, 75, 79, 62, 65, 74, 20, + 8, 57, 61, 74, 64, 65, 79, 82, 74, 67, 8, 1, 114, 8, 49, 65, 82, 62, 61, 82, 8, + 23, 30, 31, 31, 8, 23, 31, 22, 24, 20, 8, 1, 114, 8, 61, 74, 67, 65, 73, 65, 132, + 132, 65, 74, 8, 82, 74, 64, 8, 82, 74, 64, 8, 26, 31, 8, 96, 8, 83, 75, 74, 8, + 25, 23, 11, 8, 84, 69, 79, 64, 20, 2, 87, 84, 65, 69, 8, 64, 69, 65, 8, 1, 114, + 8, 65, 69, 74, 65, 73, 8, 64, 65, 79, 8, 23, 30, 29, 25, 1, 112, 8, 1, 121, 8, + 23, 30, 26, 8, 42, 65, 62, 69, 65, 81, 65, 74, 8, 7, 64, 61, 79, 110, 62, 65, 79, + 6, 8, 27, 30, 8, 1, 114, 8, 61, 82, 80, 8, 24, 11, 8, 66, 82, 79, 63, 68, 81, + 62, 61, 79, 2, 23, 30, 31, 31, 8, 64, 61, 79, 110, 62, 65, 79, 8, 1, 118, 8, 36, + 82, 80, 67, 61, 62, 65, 74, 8, 49, 65, 82, 62, 61, 82, 8, 28, 27, 11, 8, 1, 127, + 8, 39, 69, 74, 67, 65, 8, 36, 74, 79, 82, 66, 65, 74, 8, 75, 64, 65, 79, 8, 23, + 23, 8, 96, 8, 23, 30, 29, 29, 8, 24, 22, 22, 22, 11, 8, 46, 109, 74, 69, 67, 69, + 74, 2, 64, 61, 67, 65, 67, 65, 74, 8, 132, 75, 74, 64, 65, 79, 74, 8, 1, 118, 8, + 64, 69, 65, 8, 70, 65, 64, 75, 63, 68, 8, 23, 30, 27, 27, 20, 8, 1, 117, 8, 56, + 65, 79, 67, 72, 65, 69, 63, 68, 8, 53, 69, 81, 87, 82, 74, 67, 8, 64, 65, 79, 8, + 28, 31, 8, 1, 125, 8, 132, 81, 69, 65, 67, 8, 23, 31, 22, 27, 11, 8, 64, 61, 80, + 2, 132, 78, 79, 65, 63, 68, 65, 8, 69, 63, 68, 8, 1, 120, 8, 25, 30, 26, 8, 27, + 30, 28, 24, 8, 25, 27, 11, 8, 1, 117, 8, 29, 25, 28, 8, 75, 64, 65, 79, 8, 53, + 61, 73, 73, 65, 72, 68, 65, 69, 87, 82, 74, 67, 8, 24, 23, 8, 126, 8, 83, 65, 79, + 62, 69, 74, 64, 65, 74, 8, 23, 30, 31, 25, 11, 8, 74, 75, 63, 68, 2, 22, 30, 20, + 8, 62, 65, 84, 69, 72, 72, 69, 67, 81, 8, 1, 119, 8, 23, 28, 25, 31, 8, 65, 69, + 74, 81, 79, 65, 81, 65, 74, 8, 23, 22, 27, 26, 11, 8, 1, 114, 8, 68, 61, 62, 65, + 74, 8, 64, 65, 79, 8, 27, 27, 20, 8, 23, 28, 26, 27, 8, 97, 8, 7, 39, 65, 79, + 8, 25, 22, 11, 8, 36, 74, 132, 78, 79, 110, 63, 68, 65, 74, 2, 56, 65, 79, 4, 8, + 42, 79, 82, 74, 64, 61, 82, 66, 8, 1, 128, 8, 67, 65, 68, 65, 74, 64, 8, 64, 65, + 73, 8, 23, 24, 1, 112, 8, 1, 128, 8, 57, 75, 68, 74, 82, 74, 67, 65, 74, 8, 25, + 25, 26, 8, 67, 65, 78, 79, 110, 66, 81, 65, 74, 8, 23, 31, 23, 27, 8, 1, 113, 8, + 23, 31, 24, 22, 8, 23, 31, 23, 28, 11, 8, 23, 27, 22, 2, 53, 69, 65, 8, 62, 65, + 4, 8, 1, 114, 8, 36, 74, 67, 65, 72, 65, 67, 65, 74, 68, 65, 69, 81, 65, 74, 8, + 42, 79, 75, 72, 73, 61, 74, 4, 8, 23, 31, 24, 22, 20, 8, 1, 122, 8, 124, 63, 20, + 8, 23, 22, 22, 8, 64, 65, 79, 8, 28, 29, 8, 1, 120, 8, 66, 110, 79, 8, 23, 25, + 22, 24, 11, 8, 64, 69, 65, 2, 65, 69, 74, 87, 65, 72, 74, 65, 74, 8, 65, 69, 74, + 132, 65, 69, 81, 69, 4, 8, 98, 8, 83, 65, 79, 81, 79, 61, 67, 8, 64, 65, 79, 8, + 30, 26, 20, 8, 126, 8, 67, 82, 81, 65, 79, 8, 73, 69, 81, 8, 69, 132, 81, 8, 23, + 30, 24, 28, 8, 1, 121, 8, 23, 30, 26, 25, 8, 23, 26, 11, 8, 65, 79, 67, 65, 62, + 65, 74, 20, 2, 70, 65, 64, 75, 63, 68, 8, 87, 82, 79, 8, 96, 8, 51, 79, 110, 66, + 82, 74, 67, 8, 46, 79, 69, 65, 67, 65, 8, 23, 24, 24, 26, 20, 8, 1, 122, 8, 84, + 69, 65, 8, 83, 75, 73, 8, 64, 65, 79, 8, 23, 23, 8, 1, 119, 8, 67, 65, 81, 79, + 75, 66, 66, 65, 74, 65, 8, 31, 11, 8, 84, 65, 69, 72, 2, 64, 61, 102, 8, 36, 73, + 75, 79, 81, 69, 132, 61, 81, 69, 75, 74, 8, 1, 115, 8, 48, 61, 102, 65, 8, 64, 65, + 79, 8, 23, 29, 22, 33, 8, 1, 120, 8, 23, 26, 27, 8, 57, 65, 69, 132, 65, 8, 83, + 75, 74, 8, 27, 29, 8, 1, 128, 8, 41, 103, 72, 72, 65, 74, 8, 29, 22, 22, 22, 11, + 8, 82, 74, 64, 2, 87, 82, 73, 8, 64, 65, 79, 8, 1, 119, 8, 52, 61, 81, 68, 61, + 82, 132, 65, 80, 8, 64, 69, 65, 8, 24, 18, 8, 126, 8, 64, 65, 79, 8, 61, 82, 66, + 8, 87, 82, 79, 8, 28, 27, 22, 8, 96, 8, 84, 65, 79, 64, 65, 74, 8, 23, 29, 28, + 27, 11, 8, 61, 82, 66, 79, 65, 63, 68, 81, 2, 37, 65, 87, 69, 65, 68, 82, 74, 67, + 8, 82, 74, 132, 65, 79, 73, 8, 1, 121, 8, 69, 68, 79, 65, 79, 8, 82, 74, 64, 8, + 23, 31, 20, 8, 1, 118, 8, 64, 65, 79, 8, 82, 74, 64, 8, 83, 65, 79, 87, 69, 63, + 68, 81, 65, 74, 8, 30, 22, 8, 1, 119, 8, 65, 74, 81, 132, 78, 79, 69, 63, 68, 81, + 8, 31, 11, 8, 82, 74, 80, 2, 82, 74, 80, 8, 87, 82, 79, 8, 126, 8, 67, 65, 74, + 8, 53, 63, 68, 110, 72, 65, 79, 8, 23, 28, 22, 11, 8, 1, 116, 8, 132, 75, 84, 69, + 65, 8, 64, 61, 79, 82, 73, 8, 52, 65, 61, 72, 132, 63, 68, 82, 72, 65, 8, 23, 31, + 24, 22, 8, 1, 114, 8, 64, 82, 79, 63, 68, 8, 24, 22, 22, 22, 11, 8, 61, 74, 64, + 65, 79, 65, 2, 64, 61, 102, 8, 64, 65, 79, 67, 72, 65, 69, 63, 68, 65, 74, 8, 98, + 8, 132, 69, 63, 68, 8, 62, 69, 80, 8, 24, 28, 22, 22, 18, 8, 1, 113, 8, 45, 82, + 74, 69, 8, 79, 82, 74, 64, 8, 62, 65, 69, 73, 8, 31, 8, 1, 118, 8, 57, 75, 68, + 74, 82, 74, 67, 65, 74, 8, 23, 22, 11, 8, 83, 75, 74, 2, 62, 72, 69, 65, 62, 8, + 74, 61, 63, 68, 8, 1, 121, 8, 79, 82, 74, 64, 8, 71, 109, 74, 74, 65, 74, 8, 26, + 20, 8, 1, 115, 8, 64, 65, 73, 8, 48, 61, 79, 71, 8, 84, 65, 69, 72, 8, 30, 8, + 1, 115, 8, 66, 110, 79, 8, 23, 30, 31, 23, 11, 8, 132, 79, 61, 102, 65, 2, 72, 65, + 81, 87, 81, 65, 74, 8, 36, 74, 81, 79, 61, 67, 8, 98, 8, 64, 61, 79, 82, 73, 8, + 23, 31, 23, 22, 21, 23, 27, 20, 8, 28, 29, 11, 8, 1, 114, 8, 64, 69, 65, 132, 65, + 73, 8, 45, 61, 68, 79, 8, 69, 63, 68, 8, 25, 29, 8, 1, 113, 8, 65, 69, 74, 65, + 74, 8, 24, 22, 27, 22, 11, 8, 53, 81, 65, 82, 65, 79, 74, 2, 47, 65, 68, 79, 78, + 72, 103, 74, 65, 74, 8, 23, 27, 23, 24, 8, 1, 113, 8, 36, 79, 87, 81, 8, 64, 65, + 79, 8, 23, 30, 22, 31, 20, 8, 97, 8, 124, 63, 20, 8, 51, 65, 79, 132, 75, 74, 8, + 65, 74, 81, 132, 63, 68, 65, 69, 64, 65, 74, 64, 65, 8, 26, 22, 8, 1, 116, 8, 74, + 61, 63, 68, 8, 23, 30, 31, 31, 11, 8, 64, 65, 79, 2, 36, 62, 84, 61, 74, 64, 65, + 79, 82, 74, 67, 8, 56, 75, 79, 64, 65, 79, 68, 61, 82, 132, 65, 80, 8, 1, 121, 8, + 64, 65, 73, 8, 70, 65, 64, 65, 79, 8, 23, 31, 22, 23, 20, 8, 22, 22, 20, 8, 64, + 69, 65, 8, 64, 69, 65, 8, 30, 24, 8, 1, 121, 8, 74, 61, 63, 68, 8, 26, 11, 8, + 7, 48, 103, 74, 74, 65, 79, 6, 2, 64, 65, 73, 8, 84, 69, 79, 8, 126, 8, 83, 75, + 74, 8, 84, 65, 72, 63, 68, 65, 8, 23, 31, 20, 8, 1, 117, 8, 64, 69, 65, 8, 74, + 61, 63, 68, 8, 87, 69, 74, 132, 82, 74, 67, 8, 25, 22, 8, 1, 127, 8, 61, 62, 65, + 79, 8, 23, 30, 27, 22, 11, 8, 83, 75, 74, 2, 82, 74, 64, 8, 45, 82, 74, 67, 65, + 74, 8, 1, 128, 8, 64, 65, 73, 8, 62, 69, 80, 8, 23, 22, 22, 18, 8, 98, 8, 36, + 74, 132, 78, 79, 82, 63, 68, 8, 64, 65, 79, 8, 64, 61, 79, 82, 73, 8, 23, 22, 8, + 1, 120, 8, 61, 82, 63, 68, 8, 25, 11, 8, 53, 63, 68, 69, 72, 72, 65, 79, 19, 54, + 68, 65, 61, 81, 65, 79, 2, 81, 65, 74, 62, 82, 79, 67, 8, 23, 24, 11, 8, 1, 122, + 8, 23, 24, 11, 8, 7, 57, 61, 68, 79, 72, 69, 63, 68, 9, 6, 8, 30, 30, 20, 8, + 1, 117, 8, 62, 69, 80, 8, 73, 109, 63, 68, 81, 65, 8, 73, 61, 74, 63, 68, 73, 61, + 72, 8, 23, 30, 8, 1, 122, 8, 87, 82, 72, 65, 67, 65, 74, 8, 25, 26, 11, 8, 73, + 61, 63, 68, 65, 2, 64, 65, 74, 8, 24, 27, 22, 22, 8, 96, 8, 82, 74, 64, 8, 65, + 79, 66, 79, 61, 67, 65, 74, 20, 8, 23, 22, 20, 8, 1, 123, 8, 69, 68, 79, 65, 8, + 132, 75, 84, 69, 65, 8, 64, 69, 65, 8, 23, 30, 29, 31, 8, 1, 118, 8, 65, 79, 132, + 81, 65, 74, 73, 75, 72, 8, 23, 30, 31, 30, 11, 8, 23, 30, 23, 23, 2, 83, 75, 73, + 8, 51, 79, 75, 70, 65, 71, 81, 8, 1, 123, 8, 69, 132, 81, 8, 84, 110, 74, 132, 63, + 68, 65, 74, 80, 84, 65, 79, 81, 8, 23, 24, 18, 8, 1, 113, 8, 75, 64, 65, 79, 8, + 132, 69, 74, 64, 20, 8, 24, 30, 26, 8, 23, 31, 23, 30, 8, 1, 119, 8, 52, 65, 69, + 68, 65, 8, 24, 11, 8, 48, 65, 69, 74, 82, 74, 67, 2, 68, 61, 62, 65, 74, 8, 67, + 65, 68, 65, 74, 8, 1, 128, 8, 64, 65, 79, 8, 44, 1, 94, 20, 8, 29, 25, 1, 112, + 8, 24, 26, 26, 26, 26, 8, 84, 103, 72, 64, 65, 79, 8, 39, 69, 65, 8, 23, 30, 27, + 23, 8, 83, 75, 73, 8, 24, 29, 11, 8, 37, 65, 81, 65, 69, 72, 69, 67, 81, 65, 74, + 2, 24, 27, 22, 22, 8, 30, 22, 22, 22, 8, 98, 8, 65, 69, 74, 67, 65, 66, 110, 68, + 79, 81, 20, 8, 54, 65, 74, 75, 79, 8, 24, 27, 20, 8, 1, 113, 8, 65, 69, 74, 8, + 87, 82, 132, 81, 103, 74, 64, 69, 67, 65, 74, 8, 64, 61, 79, 82, 73, 8, 23, 31, 22, + 23, 8, 1, 115, 8, 64, 65, 73, 8, 23, 31, 22, 26, 11, 8, 39, 65, 74, 2, 65, 69, + 74, 65, 73, 8, 64, 65, 79, 8, 97, 8, 23, 31, 22, 24, 8, 65, 69, 74, 65, 8, 23, + 30, 26, 22, 18, 8, 1, 128, 8, 39, 69, 65, 8, 37, 65, 132, 63, 68, 72, 82, 102, 8, + 84, 61, 79, 65, 74, 8, 23, 24, 8, 1, 117, 8, 64, 65, 79, 8, 27, 22, 22, 11, 8, + 64, 69, 65, 2, 84, 82, 79, 64, 65, 8, 65, 69, 74, 65, 80, 8, 126, 8, 62, 82, 79, + 67, 65, 79, 8, 64, 69, 65, 8, 28, 27, 22, 11, 8, 98, 8, 25, 22, 22, 8, 74, 69, + 73, 73, 81, 8, 64, 65, 80, 68, 61, 72, 62, 8, 25, 22, 22, 8, 1, 117, 8, 23, 22, + 27, 24, 8, 24, 22, 11, 8, 53, 63, 68, 82, 72, 65, 74, 2, 83, 75, 73, 8, 64, 65, + 79, 8, 1, 123, 8, 64, 65, 79, 8, 84, 69, 79, 64, 20, 6, 8, 30, 30, 20, 8, 1, + 125, 8, 27, 26, 31, 8, 23, 31, 23, 30, 8, 23, 27, 24, 25, 8, 23, 8, 126, 8, 44, + 74, 81, 65, 79, 65, 132, 132, 65, 74, 132, 81, 61, 74, 64, 78, 82, 74, 71, 81, 8, 25, + 22, 11, 8, 67, 65, 68, 65, 74, 2, 64, 69, 65, 8, 84, 61, 79, 8, 44, 74, 132, 81, + 61, 72, 72, 61, 81, 69, 75, 74, 8, 64, 65, 79, 8, 23, 25, 25, 24, 1, 112, 8, 1, + 125, 8, 64, 69, 65, 8, 27, 22, 22, 22, 8, 64, 65, 80, 8, 30, 30, 30, 8, 1, 116, + 8, 73, 69, 81, 8, 23, 26, 11, 8, 61, 62, 65, 79, 2, 72, 65, 81, 87, 81, 65, 79, + 65, 73, 8, 51, 65, 74, 87, 69, 67, 8, 98, 8, 64, 65, 79, 8, 65, 79, 68, 109, 68, + 65, 74, 8, 23, 27, 1, 112, 8, 1, 119, 8, 132, 65, 69, 8, 84, 65, 72, 63, 68, 65, + 8, 65, 69, 74, 65, 80, 8, 26, 24, 8, 96, 8, 39, 61, 80, 8, 27, 22, 22, 11, 8, + 39, 61, 80, 2, 74, 69, 63, 68, 81, 8, 62, 69, 80, 8, 1, 115, 8, 73, 69, 81, 8, + 59, 78, 80, 69, 72, 75, 74, 8, 29, 24, 20, 8, 1, 121, 8, 53, 81, 103, 64, 81, 65, + 8, 132, 65, 81, 87, 65, 74, 8, 31, 30, 27, 8, 24, 22, 8, 1, 118, 8, 64, 82, 79, + 63, 68, 8, 29, 30, 11, 8, 53, 63, 68, 82, 72, 64, 65, 78, 82, 81, 61, 81, 69, 75, + 74, 2, 64, 65, 74, 8, 23, 24, 27, 22, 8, 1, 118, 8, 73, 65, 68, 79, 8, 48, 75, + 73, 73, 132, 65, 74, 4, 8, 25, 22, 22, 22, 18, 8, 126, 8, 56, 75, 79, 132, 81, 65, + 68, 65, 79, 8, 49, 75, 74, 74, 65, 74, 64, 61, 73, 73, 8, 45, 80, 20, 8, 23, 31, + 23, 29, 8, 1, 114, 8, 64, 61, 79, 61, 74, 8, 27, 26, 11, 8, 75, 62, 4, 2, 52, + 65, 69, 68, 65, 74, 20, 8, 62, 65, 71, 61, 73, 65, 74, 20, 8, 1, 117, 8, 68, 65, + 79, 87, 82, 4, 8, 73, 69, 81, 8, 23, 30, 31, 23, 11, 8, 1, 122, 8, 61, 82, 66, + 8, 61, 82, 80, 8, 64, 61, 79, 82, 73, 8, 23, 30, 31, 22, 8, 126, 8, 64, 75, 63, + 68, 8, 23, 30, 24, 27, 11, 8, 44, 1, 92, 20, 2, 82, 74, 64, 8, 25, 24, 31, 21, + 24, 25, 8, 1, 121, 8, 61, 72, 80, 8, 37, 65, 81, 81, 65, 74, 8, 23, 30, 30, 25, + 18, 8, 1, 121, 8, 55, 74, 81, 65, 79, 62, 61, 82, 8, 64, 61, 80, 8, 67, 65, 132, + 81, 61, 72, 81, 65, 81, 65, 8, 30, 25, 8, 96, 8, 50, 79, 64, 20, 8, 23, 31, 23, + 28, 11, 8, 64, 65, 80, 2, 64, 65, 79, 8, 37, 65, 81, 79, 61, 67, 8, 126, 8, 43, + 110, 72, 66, 65, 8, 61, 82, 80, 8, 23, 31, 22, 30, 18, 8, 1, 125, 8, 64, 65, 79, + 8, 56, 65, 79, 84, 61, 72, 81, 82, 74, 67, 20, 8, 64, 65, 80, 8, 23, 30, 31, 23, + 8, 1, 118, 8, 61, 82, 66, 87, 82, 84, 65, 74, 64, 65, 74, 8, 23, 23, 11, 8, 51, + 82, 81, 87, 20, 2, 64, 65, 80, 8, 23, 30, 28, 27, 8, 1, 119, 8, 83, 75, 74, 8, + 46, 79, 65, 69, 80, 132, 63, 68, 82, 72, 4, 8, 24, 24, 33, 8, 1, 116, 8, 82, 74, + 64, 8, 7, 132, 63, 68, 65, 69, 74, 81, 6, 8, 64, 69, 65, 8, 23, 27, 8, 96, 8, + 83, 75, 79, 4, 8, 24, 27, 22, 11, 8, 73, 65, 68, 79, 2, 132, 63, 68, 72, 69, 65, + 102, 72, 69, 63, 68, 8, 64, 69, 65, 8, 1, 125, 8, 64, 69, 65, 132, 65, 8, 132, 69, + 65, 8, 23, 27, 11, 8, 96, 8, 84, 65, 72, 63, 68, 65, 8, 68, 61, 74, 64, 65, 72, + 81, 8, 61, 62, 65, 79, 8, 25, 25, 8, 96, 8, 23, 30, 28, 27, 8, 29, 11, 8, 37, + 65, 4, 2, 82, 74, 64, 8, 61, 72, 80, 8, 1, 119, 8, 132, 81, 103, 64, 81, 69, 132, + 63, 68, 65, 79, 132, 65, 69, 81, 80, 8, 132, 81, 79, 20, 8, 24, 11, 8, 96, 8, 73, + 69, 81, 8, 132, 69, 63, 68, 8, 65, 69, 74, 65, 8, 23, 30, 27, 27, 8, 1, 123, 8, + 48, 69, 72, 72, 69, 75, 74, 65, 74, 8, 27, 11, 8, 64, 65, 79, 2, 64, 61, 79, 82, + 73, 8, 39, 79, 65, 68, 62, 110, 68, 74, 65, 8, 1, 125, 8, 62, 65, 69, 8, 68, 61, + 62, 65, 74, 8, 23, 30, 27, 23, 18, 8, 1, 122, 8, 40, 69, 74, 87, 69, 67, 8, 61, + 82, 63, 68, 8, 82, 74, 80, 8, 30, 30, 8, 96, 8, 82, 74, 64, 8, 24, 31, 11, 8, + 42, 79, 75, 72, 73, 61, 74, 4, 2, 64, 65, 73, 8, 36, 82, 66, 132, 69, 63, 68, 81, + 8, 1, 114, 8, 70, 65, 74, 65, 80, 8, 47, 61, 67, 65, 79, 78, 72, 61, 81, 87, 8, + 24, 24, 33, 8, 1, 118, 8, 64, 65, 73, 8, 31, 27, 11, 20, 8, 83, 75, 73, 8, 23, + 31, 24, 22, 8, 1, 125, 8, 83, 65, 79, 4, 8, 23, 23, 11, 8, 73, 65, 68, 79, 2, + 67, 65, 74, 61, 82, 8, 73, 61, 74, 63, 68, 73, 61, 72, 8, 1, 115, 8, 84, 69, 79, + 8, 61, 82, 80, 8, 31, 20, 8, 1, 128, 8, 7, 87, 84, 69, 132, 63, 68, 65, 74, 6, + 8, 62, 69, 80, 8, 71, 61, 74, 74, 20, 8, 29, 27, 8, 1, 119, 8, 64, 69, 65, 8, + 25, 24, 27, 22, 11, 8, 60, 69, 73, 73, 65, 79, 2, 22, 22, 22, 8, 64, 65, 73, 8, + 1, 116, 8, 56, 65, 79, 62, 69, 74, 64, 82, 74, 67, 8, 43, 82, 67, 75, 8, 23, 24, + 22, 22, 20, 8, 96, 8, 64, 69, 65, 8, 75, 64, 65, 79, 8, 36, 74, 79, 82, 66, 65, + 74, 8, 30, 24, 8, 1, 120, 8, 64, 69, 65, 8, 24, 23, 22, 22, 11, 8, 83, 69, 65, + 72, 72, 65, 69, 63, 68, 81, 2, 64, 65, 79, 74, 8, 132, 75, 84, 65, 69, 81, 8, 1, + 125, 8, 65, 79, 132, 81, 65, 74, 8, 124, 63, 20, 8, 25, 28, 25, 20, 8, 97, 8, 65, + 69, 74, 65, 8, 23, 30, 22, 31, 8, 56, 65, 79, 132, 81, 103, 79, 71, 82, 74, 67, 8, + 23, 30, 31, 31, 8, 1, 119, 8, 23, 24, 20, 8, 31, 11, 8, 83, 75, 74, 2, 7, 51, + 61, 79, 87, 65, 72, 72, 65, 74, 6, 8, 69, 132, 81, 8, 126, 8, 67, 61, 74, 87, 8, + 64, 61, 80, 8, 23, 30, 26, 22, 20, 8, 1, 128, 8, 84, 82, 79, 64, 65, 8, 37, 86, + 71, 8, 82, 74, 64, 8, 28, 29, 8, 126, 8, 42, 79, 82, 74, 64, 8, 24, 29, 11, 8, + 23, 31, 22, 30, 21, 23, 25, 20, 2, 68, 65, 66, 81, 8, 83, 75, 74, 8, 1, 114, 8, + 73, 69, 79, 8, 47, 61, 73, 78, 65, 74, 8, 23, 30, 29, 30, 1, 112, 8, 1, 115, 8, + 22, 22, 22, 8, 41, 65, 79, 74, 65, 79, 8, 23, 29, 26, 8, 25, 22, 8, 126, 8, 64, + 61, 102, 8, 25, 23, 11, 8, 132, 75, 72, 72, 2, 42, 79, 75, 102, 4, 37, 65, 79, 72, + 69, 74, 8, 65, 69, 74, 73, 61, 72, 8, 96, 8, 44, 44, 44, 8, 82, 74, 64, 8, 23, + 22, 20, 8, 96, 8, 46, 109, 74, 69, 67, 4, 8, 44, 44, 44, 8, 61, 82, 66, 8, 23, + 26, 22, 8, 1, 122, 8, 25, 22, 20, 8, 24, 27, 22, 11, 8, 64, 69, 65, 2, 48, 103, + 64, 63, 68, 65, 74, 8, 84, 65, 79, 64, 65, 74, 8, 1, 115, 8, 64, 69, 65, 8, 43, + 61, 82, 80, 84, 61, 79, 81, 80, 8, 23, 24, 20, 8, 1, 123, 8, 83, 75, 79, 4, 8, + 23, 30, 29, 31, 8, 39, 61, 79, 110, 62, 65, 79, 8, 23, 30, 31, 28, 8, 1, 125, 8, + 83, 75, 74, 8, 25, 28, 25, 11, 8, 65, 79, 67, 61, 62, 2, 45, 61, 66, 66, 104, 8, + 31, 24, 11, 8, 1, 117, 8, 61, 82, 63, 68, 8, 61, 82, 66, 8, 30, 29, 20, 8, 126, + 8, 75, 64, 65, 79, 8, 36, 74, 72, 65, 69, 68, 65, 8, 65, 81, 84, 61, 69, 67, 65, + 74, 8, 30, 22, 22, 8, 1, 127, 8, 84, 65, 69, 72, 8, 25, 26, 30, 25, 30, 11, 8, + 64, 65, 79, 2, 53, 65, 69, 81, 8, 61, 74, 64, 65, 79, 65, 79, 8, 1, 122, 8, 83, + 75, 73, 8, 72, 69, 65, 68, 65, 74, 8, 24, 18, 8, 1, 115, 8, 64, 61, 80, 8, 23, + 23, 25, 21, 23, 25, 8, 24, 22, 22, 22, 8, 23, 26, 23, 8, 97, 8, 64, 69, 65, 8, + 23, 24, 11, 8, 64, 65, 79, 70, 65, 74, 69, 67, 65, 74, 2, 64, 61, 80, 8, 64, 61, + 102, 8, 1, 116, 8, 48, 61, 74, 67, 65, 72, 8, 84, 69, 79, 64, 8, 23, 29, 28, 31, + 1, 112, 8, 1, 120, 8, 23, 22, 22, 8, 81, 79, 75, 81, 87, 8, 64, 65, 74, 8, 28, + 23, 8, 1, 122, 8, 83, 65, 79, 62, 69, 74, 64, 65, 74, 8, 23, 30, 25, 27, 11, 8, + 61, 72, 132, 75, 2, 74, 69, 63, 68, 81, 8, 84, 65, 72, 63, 68, 65, 8, 98, 8, 64, + 61, 80, 8, 84, 65, 69, 63, 68, 81, 8, 23, 24, 27, 33, 8, 1, 125, 8, 64, 65, 79, + 8, 23, 30, 31, 25, 8, 132, 63, 68, 79, 69, 66, 81, 65, 74, 8, 23, 23, 8, 98, 8, + 24, 30, 20, 8, 24, 30, 26, 11, 8, 68, 69, 65, 132, 69, 67, 65, 74, 2, 60, 82, 132, + 63, 68, 82, 102, 8, 64, 65, 73, 67, 65, 73, 103, 102, 8, 1, 117, 8, 64, 65, 80, 8, + 65, 69, 74, 65, 79, 8, 31, 20, 8, 1, 123, 8, 83, 75, 79, 68, 61, 74, 64, 65, 74, + 8, 67, 65, 72, 65, 67, 65, 74, 8, 83, 75, 72, 72, 65, 74, 8, 27, 8, 1, 127, 8, + 39, 61, 80, 8, 27, 28, 11, 8, 65, 79, 68, 61, 72, 81, 65, 74, 2, 23, 28, 11, 8, + 55, 68, 79, 8, 1, 127, 8, 28, 28, 11, 8, 82, 74, 64, 8, 27, 18, 8, 1, 116, 8, + 84, 69, 79, 8, 65, 69, 74, 8, 68, 69, 74, 87, 82, 87, 82, 66, 110, 67, 65, 74, 8, + 23, 25, 30, 23, 8, 1, 127, 8, 82, 74, 64, 8, 24, 11, 8, 64, 61, 80, 2, 47, 61, + 67, 65, 79, 78, 72, 61, 81, 87, 4, 56, 75, 79, 132, 81, 65, 68, 65, 79, 32, 8, 75, + 64, 65, 79, 8, 1, 127, 8, 132, 69, 74, 64, 8, 64, 65, 79, 8, 28, 31, 20, 8, 1, + 123, 8, 110, 62, 65, 79, 8, 64, 69, 65, 8, 84, 103, 79, 65, 74, 8, 26, 22, 8, 22, + 22, 22, 8, 23, 22, 11, 8, 132, 69, 63, 68, 2, 132, 69, 74, 64, 8, 40, 79, 72, 61, + 102, 8, 1, 118, 8, 61, 82, 66, 8, 82, 74, 64, 8, 23, 30, 29, 22, 20, 8, 1, 125, + 8, 64, 65, 79, 8, 82, 74, 64, 8, 109, 81, 69, 67, 65, 8, 23, 30, 28, 28, 8, 1, + 120, 8, 64, 69, 65, 8, 25, 22, 22, 22, 11, 8, 87, 75, 67, 65, 74, 2, 65, 79, 109, + 66, 66, 74, 65, 81, 20, 8, 53, 81, 61, 74, 64, 78, 82, 74, 71, 81, 8, 1, 122, 8, + 22, 22, 22, 8, 48, 61, 69, 8, 23, 30, 30, 30, 20, 8, 1, 114, 8, 75, 68, 74, 65, + 8, 132, 69, 63, 68, 8, 64, 61, 102, 8, 25, 27, 8, 126, 8, 84, 65, 79, 64, 65, 74, + 20, 8, 23, 30, 31, 30, 11, 8, 36, 62, 62, 61, 82, 2, 132, 75, 72, 63, 68, 65, 74, + 8, 53, 63, 68, 84, 69, 65, 79, 69, 67, 71, 65, 69, 81, 65, 74, 8, 1, 128, 8, 64, + 69, 65, 132, 65, 74, 8, 57, 75, 68, 74, 82, 74, 67, 65, 74, 8, 31, 20, 8, 1, 120, + 8, 23, 26, 22, 8, 22, 22, 22, 8, 44, 63, 68, 8, 28, 8, 1, 123, 8, 23, 22, 20, + 8, 31, 22, 11, 8, 24, 22, 22, 2, 84, 65, 79, 64, 65, 74, 8, 74, 69, 63, 68, 81, + 8, 96, 8, 54, 65, 69, 72, 62, 65, 81, 79, 61, 67, 8, 42, 65, 64, 61, 74, 71, 65, + 74, 8, 25, 22, 22, 22, 1, 112, 8, 1, 120, 8, 64, 69, 65, 8, 70, 82, 74, 67, 65, + 8, 61, 82, 63, 68, 8, 23, 8, 1, 114, 8, 61, 82, 66, 8, 23, 30, 24, 28, 11, 8, + 23, 23, 26, 20, 2, 132, 65, 69, 74, 65, 8, 22, 30, 20, 8, 1, 116, 8, 64, 69, 65, + 8, 82, 74, 64, 8, 28, 27, 28, 18, 8, 1, 118, 8, 82, 74, 64, 8, 54, 61, 67, 65, + 80, 75, 79, 64, 74, 82, 74, 67, 32, 8, 64, 65, 79, 8, 23, 23, 8, 1, 125, 8, 64, + 65, 79, 8, 23, 24, 27, 11, 8, 61, 72, 132, 75, 2, 64, 65, 80, 68, 61, 72, 62, 8, + 67, 61, 74, 87, 8, 97, 8, 84, 69, 79, 8, 74, 82, 79, 8, 24, 22, 22, 22, 18, 8, + 1, 119, 8, 87, 82, 73, 8, 23, 30, 30, 25, 8, 65, 69, 74, 8, 24, 22, 8, 1, 119, + 8, 84, 65, 79, 64, 65, 74, 20, 6, 8, 23, 11, 8, 37, 61, 74, 67, 72, 61, 64, 65, + 132, 63, 68, 2, 53, 69, 65, 8, 69, 63, 68, 8, 1, 122, 8, 83, 75, 73, 8, 64, 65, + 79, 8, 26, 31, 31, 33, 8, 1, 127, 8, 53, 69, 65, 8, 79, 82, 74, 64, 8, 23, 25, + 26, 24, 8, 31, 22, 8, 37, 61, 72, 71, 65, 8, 27, 22, 22, 11, 8, 82, 74, 81, 65, + 79, 79, 69, 63, 68, 81, 65, 81, 20, 2, 84, 65, 80, 68, 61, 72, 62, 8, 64, 65, 80, + 8, 62, 65, 132, 63, 68, 72, 69, 65, 102, 81, 8, 37, 65, 61, 73, 81, 65, 74, 8, 23, + 24, 22, 22, 20, 8, 53, 82, 73, 73, 65, 20, 8, 23, 30, 24, 24, 8, 84, 65, 132, 65, + 74, 81, 4, 8, 23, 24, 22, 22, 8, 1, 123, 8, 64, 65, 79, 8, 23, 24, 11, 8, 64, + 65, 79, 2, 42, 79, 65, 64, 86, 8, 66, 110, 79, 8, 1, 118, 8, 23, 30, 27, 27, 8, + 84, 103, 63, 68, 132, 81, 8, 23, 30, 27, 30, 20, 8, 1, 122, 8, 61, 82, 66, 8, 36, + 82, 63, 68, 8, 37, 65, 102, 65, 79, 8, 23, 31, 24, 22, 8, 1, 125, 8, 46, 61, 73, + 62, 61, 63, 68, 8, 29, 11, 8, 39, 69, 65, 2, 36, 74, 84, 65, 132, 65, 74, 68, 65, + 69, 81, 8, 64, 61, 87, 82, 8, 98, 8, 82, 74, 64, 8, 23, 30, 29, 31, 8, 23, 31, + 23, 27, 20, 8, 1, 121, 8, 59, 61, 74, 67, 8, 41, 75, 74, 64, 80, 8, 82, 74, 64, + 8, 25, 22, 22, 22, 8, 1, 117, 8, 49, 61, 73, 65, 74, 10, 8, 23, 24, 22, 22, 11, + 8, 23, 31, 23, 28, 2, 67, 61, 74, 87, 8, 84, 65, 79, 64, 65, 74, 8, 64, 61, 102, + 8, 66, 82, 79, 63, 68, 81, 62, 61, 79, 8, 23, 24, 22, 22, 20, 8, 1, 127, 8, 82, + 74, 64, 8, 24, 24, 20, 8, 64, 65, 73, 8, 23, 25, 8, 1, 116, 8, 87, 82, 79, 8, + 29, 11, 8, 65, 69, 74, 65, 80, 2, 64, 65, 73, 8, 64, 65, 79, 8, 25, 28, 25, 8, + 64, 65, 73, 8, 23, 22, 22, 18, 8, 1, 121, 8, 23, 30, 20, 8, 30, 22, 22, 22, 8, + 52, 61, 81, 68, 68, 61, 82, 132, 65, 80, 8, 23, 22, 29, 28, 8, 1, 121, 8, 64, 65, + 74, 8, 31, 31, 11, 8, 82, 74, 64, 2, 69, 79, 67, 65, 74, 64, 84, 65, 72, 63, 68, + 65, 74, 8, 87, 82, 79, 8, 1, 122, 8, 70, 65, 74, 69, 67, 65, 74, 8, 53, 81, 79, + 20, 8, 24, 22, 20, 8, 1, 118, 8, 62, 65, 81, 79, 103, 67, 81, 8, 23, 30, 31, 22, + 8, 65, 69, 74, 65, 74, 8, 30, 8, 1, 113, 8, 23, 26, 25, 23, 8, 30, 23, 11, 8, + 23, 30, 29, 29, 2, 55, 74, 81, 65, 79, 74, 65, 68, 73, 82, 74, 67, 65, 74, 8, 42, + 79, 110, 74, 132, 81, 79, 20, 8, 1, 121, 8, 23, 27, 20, 8, 64, 65, 79, 8, 29, 1, + 112, 8, 1, 123, 8, 64, 69, 65, 8, 82, 74, 64, 8, 62, 69, 80, 8, 28, 8, 98, 8, + 65, 69, 74, 65, 73, 8, 23, 31, 24, 22, 11, 8, 132, 69, 81, 87, 82, 74, 67, 65, 74, + 2, 82, 74, 64, 8, 67, 65, 84, 61, 72, 81, 69, 67, 8, 1, 128, 8, 65, 69, 74, 87, + 65, 72, 74, 65, 8, 132, 69, 74, 64, 20, 8, 23, 23, 20, 8, 1, 127, 8, 132, 69, 74, + 64, 8, 61, 62, 65, 79, 8, 23, 30, 26, 30, 8, 23, 24, 8, 1, 120, 8, 36, 82, 66, + 66, 61, 132, 132, 82, 74, 67, 8, 30, 11, 8, 67, 61, 74, 87, 2, 65, 79, 84, 75, 79, + 62, 65, 74, 8, 67, 72, 61, 82, 62, 65, 8, 1, 116, 8, 84, 69, 79, 64, 8, 40, 69, + 74, 65, 8, 23, 30, 24, 28, 33, 8, 126, 8, 23, 26, 20, 8, 61, 82, 66, 8, 64, 65, + 74, 65, 74, 8, 23, 24, 8, 1, 115, 8, 53, 81, 65, 72, 72, 65, 74, 8, 27, 30, 11, + 8, 37, 65, 132, 63, 68, 84, 65, 79, 64, 65, 74, 2, 53, 63, 68, 82, 72, 64, 69, 67, + 71, 65, 69, 81, 8, 56, 65, 79, 65, 69, 74, 8, 1, 125, 8, 69, 132, 81, 8, 23, 30, + 29, 30, 8, 30, 25, 11, 8, 96, 8, 64, 65, 74, 8, 64, 69, 65, 132, 65, 73, 8, 61, + 82, 63, 68, 8, 25, 27, 8, 1, 123, 8, 25, 25, 23, 21, 23, 25, 8, 30, 23, 11, 8, + 53, 81, 82, 73, 78, 66, 2, 23, 22, 22, 8, 64, 65, 80, 68, 61, 72, 62, 8, 96, 8, + 68, 65, 79, 87, 82, 4, 8, 39, 82, 79, 63, 68, 8, 27, 22, 20, 8, 1, 123, 8, 83, + 65, 79, 82, 79, 132, 61, 63, 68, 81, 8, 43, 65, 79, 79, 65, 74, 8, 67, 65, 71, 110, + 74, 64, 69, 67, 81, 8, 27, 27, 8, 1, 114, 8, 36, 79, 81, 8, 31, 27, 22, 11, 8, + 64, 65, 74, 2, 36, 82, 66, 132, 63, 68, 72, 61, 67, 8, 124, 63, 20, 8, 1, 113, 8, + 87, 82, 79, 8, 68, 61, 81, 8, 23, 31, 23, 29, 20, 8, 98, 8, 64, 65, 80, 8, 64, + 65, 79, 8, 61, 62, 84, 65, 69, 63, 68, 65, 74, 64, 65, 79, 8, 30, 28, 8, 97, 8, + 37, 65, 69, 8, 31, 31, 11, 8, 7, 39, 61, 80, 2, 61, 72, 132, 75, 8, 73, 69, 81, + 8, 1, 115, 8, 64, 65, 74, 8, 31, 30, 27, 8, 27, 26, 11, 8, 45, 61, 68, 79, 65, + 80, 62, 65, 83, 109, 72, 71, 65, 79, 82, 74, 67, 8, 74, 61, 63, 68, 8, 22, 22, 22, + 8, 23, 31, 24, 22, 8, 1, 117, 8, 69, 132, 81, 8, 30, 30, 11, 8, 60, 61, 68, 72, + 2, 62, 65, 69, 8, 23, 31, 24, 25, 25, 8, 1, 120, 8, 61, 82, 80, 8, 64, 65, 80, + 8, 29, 25, 28, 31, 18, 8, 98, 8, 64, 65, 79, 8, 83, 75, 74, 8, 27, 30, 28, 24, + 8, 25, 26, 8, 1, 123, 8, 23, 27, 24, 25, 8, 23, 29, 22, 11, 8, 36, 74, 132, 81, + 69, 65, 67, 65, 2, 64, 69, 65, 8, 132, 75, 72, 72, 65, 74, 8, 1, 127, 8, 7, 68, + 65, 66, 81, 69, 67, 65, 79, 8, 71, 72, 65, 69, 74, 65, 74, 8, 24, 20, 8, 126, 8, + 23, 23, 26, 20, 8, 64, 65, 74, 8, 44, 44, 44, 8, 24, 22, 22, 8, 1, 125, 8, 53, + 69, 65, 8, 28, 31, 11, 8, 53, 75, 74, 74, 81, 61, 67, 65, 74, 32, 2, 132, 69, 63, + 68, 8, 65, 69, 74, 8, 97, 8, 64, 65, 79, 8, 36, 74, 79, 82, 66, 65, 74, 8, 23, + 31, 18, 8, 1, 118, 8, 84, 69, 79, 8, 61, 72, 132, 75, 8, 87, 82, 79, 8, 29, 28, + 8, 1, 120, 8, 79, 65, 63, 68, 81, 8, 23, 30, 25, 29, 11, 8, 64, 61, 68, 65, 79, + 2, 45, 82, 74, 67, 65, 74, 8, 40, 81, 61, 81, 8, 126, 8, 53, 81, 61, 64, 81, 8, + 48, 75, 74, 61, 81, 65, 74, 8, 25, 24, 22, 22, 18, 8, 96, 8, 64, 61, 102, 8, 64, + 65, 73, 8, 132, 63, 68, 84, 61, 74, 71, 65, 74, 8, 23, 24, 8, 1, 121, 8, 64, 65, + 79, 8, 26, 11, 8, 37, 65, 64, 110, 79, 66, 74, 69, 80, 2, 25, 24, 27, 22, 8, 64, + 69, 65, 8, 1, 115, 8, 60, 82, 79, 8, 64, 69, 65, 132, 65, 79, 8, 31, 30, 27, 1, + 112, 8, 97, 8, 45, 61, 68, 79, 65, 8, 64, 69, 65, 8, 61, 62, 87, 69, 65, 72, 65, + 74, 8, 25, 27, 8, 97, 8, 132, 69, 63, 68, 8, 23, 24, 27, 22, 11, 8, 83, 75, 74, + 2, 67, 65, 73, 103, 102, 8, 132, 75, 72, 63, 68, 65, 80, 8, 126, 8, 62, 69, 80, 8, + 57, 69, 79, 8, 29, 30, 1, 112, 8, 1, 128, 8, 64, 69, 65, 8, 42, 65, 73, 65, 69, + 74, 64, 65, 62, 65, 68, 109, 79, 64, 65, 8, 74, 65, 82, 65, 74, 8, 23, 27, 22, 29, + 8, 1, 127, 8, 64, 65, 79, 8, 23, 24, 11, 8, 64, 61, 102, 2, 57, 65, 74, 69, 67, + 8, 71, 61, 74, 74, 8, 1, 113, 8, 48, 69, 65, 81, 83, 65, 79, 81, 79, 61, 67, 8, + 70, 82, 74, 67, 65, 8, 23, 20, 8, 1, 119, 8, 68, 61, 81, 8, 68, 61, 62, 65, 74, + 8, 61, 72, 80, 8, 24, 22, 8, 1, 116, 8, 24, 25, 27, 21, 24, 24, 8, 29, 30, 11, + 8, 64, 65, 79, 2, 36, 74, 64, 65, 79, 82, 74, 67, 8, 23, 31, 23, 24, 21, 23, 29, + 20, 8, 97, 8, 70, 65, 73, 61, 74, 64, 8, 25, 23, 25, 8, 25, 22, 22, 11, 8, 1, + 122, 8, 64, 61, 64, 82, 79, 63, 68, 8, 64, 61, 102, 8, 82, 74, 64, 8, 23, 30, 29, + 22, 8, 1, 122, 8, 65, 69, 74, 65, 8, 23, 30, 27, 29, 11, 8, 132, 78, 79, 65, 63, + 68, 65, 2, 64, 65, 79, 8, 51, 79, 75, 81, 75, 71, 75, 72, 72, 8, 98, 8, 23, 30, + 31, 23, 8, 64, 65, 80, 8, 23, 24, 1, 112, 8, 1, 117, 8, 132, 75, 84, 69, 65, 8, + 64, 61, 102, 8, 61, 72, 80, 8, 31, 8, 1, 122, 8, 83, 75, 73, 8, 23, 26, 31, 22, + 11, 8, 67, 65, 73, 69, 65, 81, 65, 81, 65, 74, 2, 31, 31, 11, 20, 8, 64, 65, 79, + 8, 1, 119, 8, 73, 61, 102, 67, 65, 62, 65, 74, 64, 20, 8, 23, 30, 23, 23, 8, 29, + 28, 33, 8, 1, 128, 8, 64, 69, 65, 8, 23, 24, 20, 8, 23, 31, 23, 22, 21, 23, 27, + 20, 8, 23, 31, 22, 28, 8, 1, 120, 8, 39, 61, 80, 8, 23, 30, 30, 24, 11, 8, 36, + 74, 132, 78, 79, 110, 63, 68, 65, 74, 2, 65, 79, 68, 61, 72, 81, 65, 74, 20, 8, 132, + 69, 81, 87, 65, 74, 64, 65, 8, 1, 113, 8, 71, 72, 61, 79, 8, 68, 61, 81, 81, 65, + 74, 8, 27, 31, 11, 8, 1, 115, 8, 53, 81, 69, 73, 73, 65, 74, 8, 87, 82, 73, 8, + 62, 69, 80, 8, 30, 25, 8, 96, 8, 64, 65, 79, 8, 23, 30, 11, 8, 39, 79, 20, 2, + 55, 74, 81, 65, 79, 79, 69, 63, 68, 81, 8, 64, 65, 79, 8, 1, 116, 8, 47, 65, 62, + 65, 74, 80, 71, 82, 74, 64, 65, 8, 64, 65, 73, 8, 23, 30, 31, 27, 20, 8, 1, 128, + 8, 61, 82, 63, 68, 8, 87, 61, 68, 72, 65, 74, 8, 30, 22, 22, 8, 25, 8, 1, 128, + 8, 83, 75, 74, 8, 23, 11, 8, 83, 75, 74, 2, 67, 65, 132, 81, 65, 69, 67, 65, 79, + 81, 65, 74, 8, 23, 27, 23, 24, 8, 1, 120, 8, 40, 69, 74, 4, 8, 57, 65, 69, 132, + 65, 8, 27, 24, 24, 11, 8, 1, 121, 8, 87, 82, 20, 8, 30, 22, 22, 8, 132, 81, 69, + 65, 67, 8, 28, 27, 8, 1, 118, 8, 132, 63, 68, 82, 72, 65, 8, 24, 25, 11, 8, 42, + 79, 82, 78, 78, 65, 74, 2, 87, 65, 69, 67, 65, 74, 8, 62, 65, 81, 79, 61, 67, 65, + 74, 8, 1, 122, 8, 62, 69, 80, 8, 23, 30, 24, 31, 8, 23, 30, 30, 22, 11, 8, 1, + 128, 8, 36, 82, 80, 132, 81, 61, 81, 81, 82, 74, 67, 8, 71, 65, 69, 74, 65, 74, 8, + 124, 63, 20, 8, 23, 27, 23, 29, 8, 1, 116, 8, 64, 61, 102, 8, 23, 23, 11, 8, 42, + 65, 62, 65, 79, 81, 2, 23, 26, 23, 8, 36, 82, 63, 68, 8, 1, 121, 8, 45, 80, 20, + 8, 64, 69, 65, 132, 65, 79, 8, 24, 29, 1, 112, 8, 1, 122, 8, 64, 65, 80, 8, 36, + 82, 80, 67, 72, 65, 69, 63, 68, 8, 83, 75, 74, 8, 23, 31, 23, 27, 8, 98, 8, 23, + 31, 23, 31, 8, 24, 11, 8, 65, 69, 74, 132, 65, 69, 81, 69, 4, 2, 75, 64, 65, 79, + 8, 44, 63, 68, 8, 1, 119, 8, 73, 69, 81, 8, 43, 65, 79, 71, 109, 73, 73, 72, 69, + 63, 68, 8, 23, 27, 20, 8, 1, 116, 8, 83, 75, 74, 8, 40, 81, 61, 81, 8, 64, 61, + 74, 74, 8, 23, 31, 22, 27, 8, 36, 62, 4, 8, 25, 27, 22, 11, 8, 41, 65, 68, 72, + 75, 84, 13, 132, 63, 68, 65, 74, 2, 47, 109, 68, 74, 65, 8, 64, 61, 102, 8, 1, 117, + 8, 45, 82, 72, 69, 8, 47, 65, 69, 81, 65, 79, 32, 8, 26, 27, 20, 8, 1, 120, 8, + 7, 48, 65, 69, 74, 65, 8, 64, 65, 80, 8, 67, 65, 79, 69, 63, 68, 81, 65, 81, 65, + 74, 8, 23, 23, 8, 1, 115, 8, 71, 109, 74, 74, 65, 74, 8, 23, 31, 11, 8, 23, 27, + 22, 2, 62, 69, 80, 8, 65, 69, 74, 8, 1, 120, 8, 48, 61, 69, 8, 72, 65, 69, 63, + 68, 81, 8, 23, 30, 31, 22, 20, 8, 1, 123, 8, 67, 65, 81, 79, 69, 65, 62, 65, 74, + 35, 8, 57, 69, 72, 68, 65, 72, 73, 8, 68, 61, 62, 65, 74, 8, 25, 22, 22, 8, 1, + 127, 8, 61, 72, 80, 8, 24, 23, 11, 8, 83, 75, 79, 2, 69, 68, 79, 65, 79, 8, 31, + 27, 11, 20, 8, 1, 125, 8, 74, 69, 63, 68, 81, 8, 36, 74, 132, 81, 61, 72, 81, 8, + 23, 28, 26, 27, 20, 8, 1, 119, 8, 61, 82, 63, 68, 8, 84, 69, 79, 64, 8, 51, 75, + 132, 69, 81, 69, 75, 74, 8, 27, 8, 1, 114, 8, 23, 24, 11, 8, 23, 30, 22, 30, 11, + 8, 64, 65, 79, 2, 41, 79, 61, 67, 65, 8, 65, 69, 74, 65, 8, 1, 128, 8, 68, 61, + 81, 81, 65, 20, 8, 27, 24, 20, 8, 25, 22, 20, 8, 1, 113, 8, 124, 63, 20, 8, 57, + 75, 68, 74, 82, 74, 67, 65, 74, 8, 84, 75, 79, 64, 65, 74, 20, 8, 23, 31, 24, 22, + 8, 1, 128, 8, 83, 75, 73, 8, 23, 22, 11, 8, 62, 65, 69, 64, 65, 74, 2, 84, 65, + 69, 102, 8, 43, 61, 82, 80, 68, 61, 72, 81, 80, 4, 8, 97, 8, 84, 82, 79, 64, 65, + 8, 60, 82, 67, 65, 87, 75, 67, 65, 74, 65, 74, 8, 23, 24, 20, 8, 97, 8, 66, 110, + 79, 8, 66, 110, 79, 8, 44, 44, 44, 8, 23, 26, 8, 97, 8, 22, 22, 22, 8, 23, 31, + 23, 30, 11, 8, 61, 62, 132, 75, 72, 82, 81, 65, 2, 84, 69, 79, 64, 8, 61, 82, 66, + 79, 65, 63, 68, 81, 8, 1, 113, 8, 84, 69, 79, 8, 132, 81, 69, 65, 67, 8, 23, 31, + 23, 28, 33, 8, 96, 8, 64, 65, 74, 8, 60, 84, 65, 63, 71, 65, 8, 68, 61, 62, 65, + 8, 23, 8, 96, 8, 42, 65, 62, 103, 82, 64, 65, 8, 27, 11, 8, 74, 61, 63, 68, 2, + 23, 23, 20, 8, 132, 69, 65, 8, 1, 114, 8, 64, 69, 65, 8, 53, 81, 61, 64, 81, 8, + 25, 30, 20, 8, 1, 128, 8, 53, 63, 68, 82, 72, 64, 65, 78, 82, 81, 61, 81, 69, 75, + 74, 8, 64, 65, 74, 8, 66, 110, 79, 8, 28, 31, 8, 1, 120, 8, 43, 65, 79, 79, 8, + 23, 23, 11, 8, 64, 65, 80, 68, 61, 72, 62, 2, 64, 65, 79, 8, 36, 78, 79, 69, 72, + 21, 45, 82, 74, 69, 8, 1, 119, 8, 36, 62, 81, 65, 69, 72, 82, 74, 67, 8, 62, 65, + 69, 64, 65, 74, 8, 27, 24, 20, 8, 1, 115, 8, 65, 69, 74, 65, 73, 8, 61, 82, 63, + 68, 8, 64, 69, 65, 132, 65, 79, 8, 25, 27, 22, 8, 1, 117, 8, 124, 63, 20, 8, 26, + 29, 11, 8, 84, 69, 65, 64, 65, 79, 2, 45, 61, 68, 79, 65, 8, 23, 30, 24, 30, 8, + 97, 8, 64, 65, 79, 8, 64, 65, 79, 8, 30, 30, 20, 8, 1, 116, 8, 46, 109, 74, 69, + 67, 72, 69, 63, 68, 65, 74, 8, 45, 61, 68, 79, 8, 66, 110, 79, 8, 24, 26, 26, 26, + 26, 8, 1, 122, 8, 65, 69, 74, 8, 23, 28, 24, 22, 11, 8, 41, 79, 61, 67, 65, 2, + 41, 65, 79, 74, 79, 82, 66, 32, 8, 39, 61, 80, 8, 1, 118, 8, 23, 31, 23, 29, 8, + 48, 61, 67, 69, 132, 81, 79, 61, 81, 8, 27, 29, 1, 112, 8, 1, 117, 8, 39, 69, 65, + 8, 64, 65, 80, 8, 36, 62, 81, 75, 73, 73, 65, 74, 8, 30, 26, 8, 1, 115, 8, 82, + 132, 84, 20, 8, 29, 26, 11, 8, 67, 65, 73, 103, 102, 2, 74, 75, 63, 68, 8, 79, 82, + 74, 64, 8, 1, 114, 8, 82, 74, 64, 8, 83, 65, 79, 132, 63, 68, 69, 65, 64, 65, 74, + 65, 79, 8, 23, 1, 112, 8, 1, 113, 8, 62, 65, 79, 8, 23, 22, 20, 8, 64, 61, 102, + 8, 29, 27, 22, 8, 1, 128, 8, 51, 66, 72, 69, 63, 68, 81, 8, 23, 22, 11, 8, 62, + 65, 87, 75, 67, 65, 74, 2, 64, 69, 65, 8, 49, 65, 82, 66, 65, 79, 81, 8, 96, 8, + 29, 22, 22, 22, 8, 44, 1, 92, 20, 8, 23, 22, 30, 26, 26, 33, 8, 98, 8, 23, 23, + 22, 8, 132, 63, 68, 72, 61, 67, 65, 74, 8, 71, 72, 65, 69, 74, 65, 74, 8, 24, 29, + 8, 1, 115, 8, 43, 65, 65, 79, 65, 80, 64, 69, 65, 74, 132, 81, 65, 8, 25, 24, 11, + 8, 64, 65, 79, 2, 65, 79, 68, 65, 62, 72, 69, 63, 68, 8, 73, 65, 69, 74, 65, 74, + 8, 83, 75, 74, 8, 39, 69, 65, 8, 26, 31, 20, 8, 1, 128, 8, 23, 30, 26, 28, 8, + 82, 74, 64, 8, 37, 79, 75, 64, 65, 8, 28, 27, 24, 8, 1, 118, 8, 23, 31, 23, 29, + 8, 31, 30, 11, 8, 69, 132, 81, 2, 73, 109, 63, 68, 81, 65, 8, 23, 24, 27, 22, 8, + 1, 118, 8, 84, 65, 72, 63, 68, 65, 74, 8, 64, 69, 65, 8, 24, 22, 22, 18, 8, 1, + 116, 8, 64, 61, 80, 8, 59, 78, 80, 69, 72, 75, 74, 8, 73, 109, 63, 68, 81, 65, 8, + 30, 8, 1, 127, 8, 82, 74, 64, 8, 28, 30, 11, 8, 87, 69, 65, 72, 65, 74, 64, 65, + 74, 2, 61, 62, 65, 79, 8, 64, 75, 63, 68, 8, 1, 114, 8, 68, 69, 74, 65, 69, 74, + 67, 65, 81, 61, 74, 8, 43, 75, 66, 65, 8, 24, 22, 22, 22, 33, 8, 96, 8, 7, 37, + 65, 79, 61, 81, 82, 74, 67, 6, 8, 84, 65, 79, 64, 65, 74, 8, 124, 63, 20, 8, 28, + 27, 28, 8, 1, 125, 8, 73, 61, 74, 8, 23, 29, 11, 8, 65, 81, 84, 61, 80, 2, 52, + 75, 132, 63, 68, 65, 79, 132, 81, 79, 61, 102, 65, 8, 69, 132, 81, 8, 96, 8, 84, 65, + 69, 81, 65, 79, 65, 80, 8, 64, 65, 79, 8, 30, 27, 22, 11, 8, 98, 8, 83, 75, 74, + 8, 51, 65, 74, 87, 69, 67, 8, 64, 65, 79, 8, 23, 30, 31, 22, 8, 1, 121, 8, 40, + 69, 74, 65, 8, 24, 29, 11, 8, 72, 65, 81, 87, 81, 65, 74, 2, 64, 65, 80, 8, 132, + 81, 79, 61, 102, 65, 8, 1, 125, 8, 44, 1, 89, 20, 8, 23, 22, 24, 32, 8, 24, 31, + 18, 8, 1, 116, 8, 68, 61, 62, 65, 8, 83, 65, 79, 84, 61, 72, 81, 65, 81, 20, 8, + 51, 79, 110, 66, 82, 74, 67, 8, 23, 28, 25, 30, 8, 1, 115, 8, 23, 22, 26, 8, 27, + 30, 28, 24, 11, 8, 68, 65, 82, 81, 65, 2, 61, 62, 65, 79, 8, 74, 82, 79, 8, 1, + 118, 8, 49, 79, 20, 8, 73, 65, 81, 65, 79, 8, 25, 23, 18, 8, 98, 8, 83, 75, 74, + 8, 132, 69, 63, 68, 8, 61, 62, 65, 74, 64, 80, 8, 31, 8, 98, 8, 69, 63, 68, 8, + 23, 24, 11, 8, 53, 81, 65, 73, 78, 65, 72, 2, 68, 109, 68, 65, 79, 65, 8, 66, 75, + 72, 67, 65, 74, 64, 65, 32, 8, 1, 113, 8, 64, 65, 79, 8, 82, 74, 64, 8, 23, 31, + 22, 23, 20, 8, 1, 119, 8, 65, 69, 74, 65, 8, 45, 61, 68, 79, 8, 61, 72, 72, 65, + 79, 8, 23, 23, 8, 1, 120, 8, 84, 69, 79, 64, 8, 23, 22, 28, 24, 11, 8, 64, 65, + 80, 2, 53, 81, 79, 20, 8, 132, 69, 63, 68, 8, 60, 65, 69, 63, 68, 74, 65, 79, 32, + 8, 62, 65, 69, 8, 29, 1, 112, 8, 1, 123, 8, 62, 65, 81, 79, 61, 63, 68, 81, 65, + 74, 20, 6, 8, 64, 69, 65, 8, 22, 30, 20, 8, 30, 30, 8, 1, 125, 8, 59, 61, 63, + 68, 81, 8, 23, 27, 11, 8, 64, 69, 65, 2, 42, 65, 84, 69, 102, 8, 37, 65, 69, 8, + 1, 122, 8, 23, 31, 23, 22, 21, 23, 27, 20, 8, 26, 24, 25, 8, 23, 22, 22, 18, 8, + 1, 115, 8, 64, 61, 102, 8, 132, 81, 79, 20, 8, 64, 65, 79, 8, 23, 22, 29, 29, 8, + 1, 119, 8, 66, 110, 79, 8, 30, 31, 31, 11, 8, 61, 72, 72, 67, 65, 73, 65, 69, 74, + 65, 2, 65, 79, 84, 75, 79, 62, 65, 74, 20, 8, 57, 61, 74, 64, 65, 79, 82, 74, 67, + 8, 1, 114, 8, 49, 65, 82, 62, 61, 82, 8, 23, 30, 31, 31, 8, 23, 31, 22, 24, 20, + 8, 1, 114, 8, 61, 74, 67, 65, 73, 65, 132, 132, 65, 74, 8, 82, 74, 64, 8, 82, 74, + 64, 8, 26, 31, 8, 96, 8, 83, 75, 74, 8, 25, 23, 11, 8, 84, 69, 79, 64, 20, 2, + 87, 84, 65, 69, 8, 64, 69, 65, 8, 1, 114, 8, 65, 69, 74, 65, 73, 8, 64, 65, 79, + 8, 23, 30, 29, 25, 1, 112, 8, 1, 121, 8, 23, 30, 26, 8, 42, 65, 62, 69, 65, 81, + 65, 74, 8, 7, 64, 61, 79, 110, 62, 65, 79, 6, 8, 27, 30, 8, 1, 114, 8, 61, 82, + 80, 8, 24, 11, 8, 66, 82, 79, 63, 68, 81, 62, 61, 79, 2, 23, 30, 31, 31, 8, 64, + 61, 79, 110, 62, 65, 79, 8, 1, 118, 8, 36, 82, 80, 67, 61, 62, 65, 74, 8, 49, 65, + 82, 62, 61, 82, 8, 28, 27, 11, 8, 1, 127, 8, 39, 69, 74, 67, 65, 8, 36, 74, 79, + 82, 66, 65, 74, 8, 75, 64, 65, 79, 8, 23, 23, 8, 96, 8, 23, 30, 29, 29, 8, 24, + 22, 22, 22, 11, 8, 46, 109, 74, 69, 67, 69, 74, 2, 64, 61, 67, 65, 67, 65, 74, 8, + 132, 75, 74, 64, 65, 79, 74, 8, 1, 118, 8, 64, 69, 65, 8, 70, 65, 64, 75, 63, 68, + 8, 23, 30, 27, 27, 20, 8, 1, 117, 8, 56, 65, 79, 67, 72, 65, 69, 63, 68, 8, 53, + 69, 81, 87, 82, 74, 67, 8, 64, 65, 79, 8, 28, 31, 8, 1, 125, 8, 132, 81, 69, 65, + 67, 8, 23, 31, 22, 27, 11, 8, 64, 61, 80, 2, 132, 78, 79, 65, 63, 68, 65, 8, 69, + 63, 68, 8, 1, 120, 8, 25, 30, 26, 8, 27, 30, 28, 24, 8, 25, 27, 11, 8, 1, 117, + 8, 29, 25, 28, 8, 75, 64, 65, 79, 8, 53, 61, 73, 73, 65, 72, 68, 65, 69, 87, 82, + 74, 67, 8, 24, 23, 8, 126, 8, 83, 65, 79, 62, 69, 74, 64, 65, 74, 8, 23, 30, 31, + 25, 11, 8, 74, 75, 63, 68, 2, 22, 30, 20, 8, 62, 65, 84, 69, 72, 72, 69, 67, 81, + 8, 1, 119, 8, 23, 28, 25, 31, 8, 65, 69, 74, 81, 79, 65, 81, 65, 74, 8, 23, 22, + 27, 26, 11, 8, 1, 114, 8, 68, 61, 62, 65, 74, 8, 64, 65, 79, 8, 27, 27, 20, 8, + 23, 28, 26, 27, 8, 97, 8, 7, 39, 65, 79, 8, 25, 22, 11, 8, 36, 74, 132, 78, 79, + 110, 63, 68, 65, 74, 2, 56, 65, 79, 4, 8, 42, 79, 82, 74, 64, 61, 82, 66, 8, 1, + 128, 8, 67, 65, 68, 65, 74, 64, 8, 64, 65, 73, 8, 23, 24, 1, 112, 8, 1, 128, 8, + 57, 75, 68, 74, 82, 74, 67, 65, 74, 8, 25, 25, 26, 8, 67, 65, 78, 79, 110, 66, 81, + 65, 74, 8, 23, 31, 23, 27, 8, 1, 113, 8, 23, 31, 24, 22, 8, 23, 31, 23, 28, 11, + 8, 23, 27, 22, 2, 53, 69, 65, 8, 62, 65, 4, 8, 1, 114, 8, 36, 74, 67, 65, 72, + 65, 67, 65, 74, 68, 65, 69, 81, 65, 74, 8, 42, 79, 75, 72, 73, 61, 74, 4, 8, 23, + 31, 24, 22, 20, 8, 1, 122, 8, 124, 63, 20, 8, 23, 22, 22, 8, 64, 65, 79, 8, 28, + 29, 8, 1, 120, 8, 66, 110, 79, 8, 23, 25, 22, 24, 11, 8, 64, 69, 65, 2, 65, 69, + 74, 87, 65, 72, 74, 65, 74, 8, 65, 69, 74, 132, 65, 69, 81, 69, 4, 8, 98, 8, 83, + 65, 79, 81, 79, 61, 67, 8, 64, 65, 79, 8, 30, 26, 20, 8, 126, 8, 67, 82, 81, 65, + 79, 8, 73, 69, 81, 8, 69, 132, 81, 8, 23, 30, 24, 28, 8, 1, 121, 8, 23, 30, 26, + 25, 8, 23, 26, 11, 8, 65, 79, 67, 65, 62, 65, 74, 20, 2, 70, 65, 64, 75, 63, 68, + 8, 87, 82, 79, 8, 96, 8, 51, 79, 110, 66, 82, 74, 67, 8, 46, 79, 69, 65, 67, 65, + 8, 23, 24, 24, 26, 20, 8, 1, 122, 8, 84, 69, 65, 8, 83, 75, 73, 8, 64, 65, 79, + 8, 23, 23, 8, 1, 119, 8, 67, 65, 81, 79, 75, 66, 66, 65, 74, 65, 8, 31, 11, 8, + 84, 65, 69, 72, 2, 64, 61, 102, 8, 36, 73, 75, 79, 81, 69, 132, 61, 81, 69, 75, 74, + 8, 1, 115, 8, 48, 61, 102, 65, 8, 64, 65, 79, 8, 23, 29, 22, 33, 8, 1, 120, 8, + 23, 26, 27, 8, 57, 65, 69, 132, 65, 8, 83, 75, 74, 8, 27, 29, 8, 1, 128, 8, 41, + 103, 72, 72, 65, 74, 8, 29, 22, 22, 22, 11, 8, 82, 74, 64, 2, 87, 82, 73, 8, 64, + 65, 79, 8, 1, 119, 8, 52, 61, 81, 68, 61, 82, 132, 65, 80, 8, 64, 69, 65, 8, 24, + 18, 8, 126, 8, 64, 65, 79, 8, 61, 82, 66, 8, 87, 82, 79, 8, 28, 27, 22, 8, 96, + 8, 84, 65, 79, 64, 65, 74, 8, 23, 29, 28, 27, 11, 8, 61, 82, 66, 79, 65, 63, 68, + 81, 2, 37, 65, 87, 69, 65, 68, 82, 74, 67, 8, 82, 74, 132, 65, 79, 73, 8, 1, 121, + 8, 69, 68, 79, 65, 79, 8, 82, 74, 64, 8, 23, 31, 20, 8, 1, 118, 8, 64, 65, 79, + 8, 82, 74, 64, 8, 83, 65, 79, 87, 69, 63, 68, 81, 65, 74, 8, 30, 22, 8, 1, 119, + 8, 65, 74, 81, 132, 78, 79, 69, 63, 68, 81, 8, 31, 11, 8, 82, 74, 80, 2, 82, 74, + 80, 8, 87, 82, 79, 8, 126, 8, 67, 65, 74, 8, 53, 63, 68, 110, 72, 65, 79, 8, 23, + 28, 22, 11, 8, 1, 116, 8, 132, 75, 84, 69, 65, 8, 64, 61, 79, 82, 73, 8, 52, 65, + 61, 72, 132, 63, 68, 82, 72, 65, 8, 23, 31, 24, 22, 8, 1, 114, 8, 64, 82, 79, 63, + 68, 8, 24, 22, 22, 22, 11, 8, 61, 74, 64, 65, 79, 65, 2, 64, 61, 102, 8, 64, 65, + 79, 67, 72, 65, 69, 63, 68, 65, 74, 8, 98, 8, 132, 69, 63, 68, 8, 62, 69, 80, 8, + 24, 28, 22, 22, 18, 8, 1, 113, 8, 45, 82, 74, 69, 8, 79, 82, 74, 64, 8, 62, 65, + 69, 73, 8, 31, 8, 1, 118, 8, 57, 75, 68, 74, 82, 74, 67, 65, 74, 8, 23, 22, 11, + 8, 83, 75, 74, 2, 62, 72, 69, 65, 62, 8, 74, 61, 63, 68, 8, 1, 121, 8, 79, 82, + 74, 64, 8, 71, 109, 74, 74, 65, 74, 8, 26, 20, 8, 1, 115, 8, 64, 65, 73, 8, 48, + 61, 79, 71, 8, 84, 65, 69, 72, 8, 30, 8, 1, 115, 8, 66, 110, 79, 8, 23, 30, 31, + 23, 11, 8, 132, 79, 61, 102, 65, 2, 72, 65, 81, 87, 81, 65, 74, 8, 36, 74, 81, 79, + 61, 67, 8, 98, 8, 64, 61, 79, 82, 73, 8, 23, 31, 23, 22, 21, 23, 27, 20, 8, 28, + 29, 11, 8, 1, 114, 8, 64, 69, 65, 132, 65, 73, 8, 45, 61, 68, 79, 8, 69, 63, 68, + 8, 25, 29, 8, 1, 113, 8, 65, 69, 74, 65, 74, 8, 24, 22, 27, 22, 11, 8, 53, 81, + 65, 82, 65, 79, 74, 2, 47, 65, 68, 79, 78, 72, 103, 74, 65, 74, 8, 23, 27, 23, 24, + 8, 1, 113, 8, 36, 79, 87, 81, 8, 64, 65, 79, 8, 23, 30, 22, 31, 20, 8, 97, 8, + 124, 63, 20, 8, 51, 65, 79, 132, 75, 74, 8, 65, 74, 81, 132, 63, 68, 65, 69, 64, 65, + 74, 64, 65, 8, 26, 22, 8, 1, 116, 8, 74, 61, 63, 68, 8, 23, 30, 31, 31, 11, 8, + 64, 65, 79, 2, 36, 62, 84, 61, 74, 64, 65, 79, 82, 74, 67, 8, 56, 75, 79, 64, 65, + 79, 68, 61, 82, 132, 65, 80, 8, 1, 121, 8, 64, 65, 73, 8, 70, 65, 64, 65, 79, 8, + 23, 31, 22, 23, 20, 8, 22, 22, 20, 8, 64, 69, 65, 8, 64, 69, 65, 8, 30, 24, 8, + 1, 121, 8, 74, 61, 63, 68, 8, 26, 11, 8, 7, 48, 103, 74, 74, 65, 79, 6, 2, 64, + 65, 73, 8, 84, 69, 79, 8, 126, 8, 83, 75, 74, 8, 84, 65, 72, 63, 68, 65, 8, 23, + 31, 20, 8, 1, 117, 8, 64, 69, 65, 8, 74, 61, 63, 68, 8, 87, 69, 74, 132, 82, 74, + 67, 8, 25, 22, 8, 1, 127, 8, 61, 62, 65, 79, 8, 23, 30, 27, 22, 11, 8, 83, 75, + 74, 2, 82, 74, 64, 8, 45, 82, 74, 67, 65, 74, 8, 1, 128, 8, 64, 65, 73, 8, 62, + 69, 80, 8, 23, 22, 22, 18, 8, 98, 8, 36, 74, 132, 78, 79, 82, 63, 68, 8, 64, 65, + 79, 8, 64, 61, 79, 82, 73, 8, 23, 22, 8, 1, 120, 8, 61, 82, 63, 68, 8, 25, 11, + 8, 53, 63, 68, 69, 72, 72, 65, 79, 19, 54, 68, 65, 61, 81, 65, 79, 2, 81, 65, 74, + 62, 82, 79, 67, 8, 23, 24, 11, 8, 1, 122, 8, 23, 24, 11, 8, 7, 57, 61, 68, 79, + 72, 69, 63, 68, 9, 6, 8, 30, 30, 20, 8, 1, 117, 8, 62, 69, 80, 8, 73, 109, 63, + 68, 81, 65, 8, 73, 61, 74, 63, 68, 73, 61, 72, 8, 23, 30, 8, 1, 122, 8, 87, 82, + 72, 65, 67, 65, 74, 8, 25, 26, 11, 8, 73, 61, 63, 68, 65, 2, 64, 65, 74, 8, 24, + 27, 22, 22, 8, 96, 8, 82, 74, 64, 8, 65, 79, 66, 79, 61, 67, 65, 74, 20, 8, 23, + 22, 20, 8, 1, 123, 8, 69, 68, 79, 65, 8, 132, 75, 84, 69, 65, 8, 64, 69, 65, 8, + 23, 30, 29, 31, 8, 1, 118, 8, 65, 79, 132, 81, 65, 74, 73, 75, 72, 8, 23, 30, 31, + 30, 11, 8, 23, 30, 23, 23, 2, 83, 75, 73, 8, 51, 79, 75, 70, 65, 71, 81, 8, 1, + 123, 8, 69, 132, 81, 8, 84, 110, 74, 132, 63, 68, 65, 74, 80, 84, 65, 79, 81, 8, 23, + 24, 18, 8, 1, 113, 8, 75, 64, 65, 79, 8, 132, 69, 74, 64, 20, 8, 24, 30, 26, 8, + 23, 31, 23, 30, 8, 1, 119, 8, 52, 65, 69, 68, 65, 8, 24, 11, 8, 48, 65, 69, 74, + 82, 74, 67, 2, 68, 61, 62, 65, 74, 8, 67, 65, 68, 65, 74, 8, 1, 128, 8, 64, 65, + 79, 8, 44, 1, 94, 20, 8, 29, 25, 1, 112, 8, 24, 26, 26, 26, 26, 8, 84, 103, 72, + 64, 65, 79, 8, 39, 69, 65, 8, 23, 30, 27, 23, 8, 83, 75, 73, 8, 24, 29, 11, 8, + 37, 65, 81, 65, 69, 72, 69, 67, 81, 65, 74, 2, 24, 27, 22, 22, 8, 30, 22, 22, 22, + 8, 98, 8, 65, 69, 74, 67, 65, 66, 110, 68, 79, 81, 20, 8, 54, 65, 74, 75, 79, 8, + 24, 27, 20, 8, 1, 113, 8, 65, 69, 74, 8, 87, 82, 132, 81, 103, 74, 64, 69, 67, 65, + 74, 8, 64, 61, 79, 82, 73, 8, 23, 31, 22, 23, 8, 1, 115, 8, 64, 65, 73, 8, 23, + 31, 22, 26, 11, 8, 39, 65, 74, 2, 65, 69, 74, 65, 73, 8, 64, 65, 79, 8, 97, 8, + 23, 31, 22, 24, 8, 65, 69, 74, 65, 8, 23, 30, 26, 22, 18, 8, 1, 128, 8, 39, 69, + 65, 8, 37, 65, 132, 63, 68, 72, 82, 102, 8, 84, 61, 79, 65, 74, 8, 23, 24, 8, 1, + 117, 8, 64, 65, 79, 8, 27, 22, 22, 11, 8, 64, 69, 65, 2, 84, 82, 79, 64, 65, 8, + 65, 69, 74, 65, 80, 8, 126, 8, 62, 82, 79, 67, 65, 79, 8, 64, 69, 65, 8, 28, 27, + 22, 11, 8, 98, 8, 25, 22, 22, 8, 74, 69, 73, 73, 81, 8, 64, 65, 80, 68, 61, 72, + 62, 8, 25, 22, 22, 8, 1, 117, 8, 23, 22, 27, 24, 8, 24, 22, 11, 8, 53, 63, 68, + 82, 72, 65, 74, 2, 83, 75, 73, 8, 64, 65, 79, 8, 1, 123, 8, 64, 65, 79, 8, 84, + 69, 79, 64, 20, 6, 8, 30, 30, 20, 8, 1, 125, 8, 27, 26, 31, 8, 23, 31, 23, 30, + 8, 23, 27, 24, 25, 8, 23, 8, 126, 8, 44, 74, 81, 65, 79, 65, 132, 132, 65, 74, 132, + 81, 61, 74, 64, 78, 82, 74, 71, 81, 8, 25, 22, 11, 8, 67, 65, 68, 65, 74, 2, 64, + 69, 65, 8, 84, 61, 79, 8, 44, 74, 132, 81, 61, 72, 72, 61, 81, 69, 75, 74, 8, 64, + 65, 79, 8, 23, 25, 25, 24, 1, 112, 8, 1, 125, 8, 64, 69, 65, 8, 27, 22, 22, 22, + 8, 64, 65, 80, 8, 30, 30, 30, 8, 1, 116, 8, 73, 69, 81, 8, 23, 26, 11, 8, 61, + 62, 65, 79, 2, 72, 65, 81, 87, 81, 65, 79, 65, 73, 8, 51, 65, 74, 87, 69, 67, 8, + 98, 8, 64, 65, 79, 8, 65, 79, 68, 109, 68, 65, 74, 8, 23, 27, 1, 112, 8, 1, 119, + 8, 132, 65, 69, 8, 84, 65, 72, 63, 68, 65, 8, 65, 69, 74, 65, 80, 8, 26, 24, 8, + 96, 8, 39, 61, 80, 8, 27, 22, 22, 11, 8, 39, 61, 80, 2, 74, 69, 63, 68, 81, 8, + 62, 69, 80, 8, 1, 115, 8, 73, 69, 81, 8, 59, 78, 80, 69, 72, 75, 74, 8, 29, 24, + 20, 8, 1, 121, 8, 53, 81, 103, 64, 81, 65, 8, 132, 65, 81, 87, 65, 74, 8, 31, 30, + 27, 8, 24, 22, 8, 1, 118, 8, 64, 82, 79, 63, 68, 8, 29, 30, 11, 8, 53, 63, 68, + 82, 72, 64, 65, 78, 82, 81, 61, 81, 69, 75, 74, 2, 64, 65, 74, 8, 23, 24, 27, 22, + 8, 1, 118, 8, 73, 65, 68, 79, 8, 48, 75, 73, 73, 132, 65, 74, 4, 8, 25, 22, 22, + 22, 18, 8, 126, 8, 56, 75, 79, 132, 81, 65, 68, 65, 79, 8, 49, 75, 74, 74, 65, 74, + 64, 61, 73, 73, 8, 45, 80, 20, 8, 23, 31, 23, 29, 8, 1, 114, 8, 64, 61, 79, 61, + 74, 8, 27, 26, 11, 8, 75, 62, 4, 2, 52, 65, 69, 68, 65, 74, 20, 8, 62, 65, 71, + 61, 73, 65, 74, 20, 8, 1, 117, 8, 68, 65, 79, 87, 82, 4, 8, 73, 69, 81, 8, 23, + 30, 31, 23, 11, 8, 1, 122, 8, 61, 82, 66, 8, 61, 82, 80, 8, 64, 61, 79, 82, 73, + 8, 23, 30, 31, 22, 8, 126, 8, 64, 75, 63, 68, 8, 23, 30, 24, 27, 11, 8, 44, 1, + 92, 20, 2, 82, 74, 64, 8, 25, 24, 31, 21, 24, 25, 8, 1, 121, 8, 61, 72, 80, 8, + 37, 65, 81, 81, 65, 74, 8, 23, 30, 30, 25, 18, 8, 1, 121, 8, 55, 74, 81, 65, 79, + 62, 61, 82, 8, 64, 61, 80, 8, 67, 65, 132, 81, 61, 72, 81, 65, 81, 65, 8, 30, 25, + 8, 96, 8, 50, 79, 64, 20, 8, 23, 31, 23, 28, 11, 8, 64, 65, 80, 2, 64, 65, 79, + 8, 37, 65, 81, 79, 61, 67, 8, 126, 8, 43, 110, 72, 66, 65, 8, 61, 82, 80, 8, 23, + 31, 22, 30, 18, 8, 1, 125, 8, 64, 65, 79, 8, 56, 65, 79, 84, 61, 72, 81, 82, 74, + 67, 20, 8, 64, 65, 80, 8, 23, 30, 31, 23, 8, 1, 118, 8, 61, 82, 66, 87, 82, 84, + 65, 74, 64, 65, 74, 8, 23, 23, 11, 8, 51, 82, 81, 87, 20, 2, 64, 65, 80, 8, 23, + 30, 28, 27, 8, 1, 119, 8, 83, 75, 74, 8, 46, 79, 65, 69, 80, 132, 63, 68, 82, 72, + 4, 8, 24, 24, 33, 8, 1, 116, 8, 82, 74, 64, 8, 7, 132, 63, 68, 65, 69, 74, 81, + 6, 8, 64, 69, 65, 8, 23, 27, 8, 96, 8, 83, 75, 79, 4, 8, 24, 27, 22, 11, 8, + 73, 65, 68, 79, 2, 132, 63, 68, 72, 69, 65, 102, 72, 69, 63, 68, 8, 64, 69, 65, 8, + 1, 125, 8, 64, 69, 65, 132, 65, 8, 132, 69, 65, 8, 23, 27, 11, 8, 96, 8, 84, 65, + 72, 63, 68, 65, 8, 68, 61, 74, 64, 65, 72, 81, 8, 61, 62, 65, 79, 8, 25, 25, 8, + 96, 8, 23, 30, 28, 27, 8, 29, 11, 8, 37, 65, 4, 2, 82, 74, 64, 8, 61, 72, 80, + 8, 1, 119, 8, 132, 81, 103, 64, 81, 69, 132, 63, 68, 65, 79, 132, 65, 69, 81, 80, 8, + 132, 81, 79, 20, 8, 24, 11, 8, 96, 8, 73, 69, 81, 8, 132, 69, 63, 68, 8, 65, 69, + 74, 65, 8, 23, 30, 27, 27, 8, 1, 123, 8, 48, 69, 72, 72, 69, 75, 74, 65, 74, 8, + 27, 11, 8, 64, 65, 79, 2, 64, 61, 79, 82, 73, 8, 39, 79, 65, 68, 62, 110, 68, 74, + 65, 8, 1, 125, 8, 62, 65, 69, 8, 68, 61, 62, 65, 74, 8, 23, 30, 27, 23, 18, 8, + 1, 122, 8, 40, 69, 74, 87, 69, 67, 8, 61, 82, 63, 68, 8, 82, 74, 80, 8, 30, 30, + 8, 96, 8, 82, 74, 64, 8, 24, 31, 11, 8, 42, 79, 75, 72, 73, 61, 74, 4, 2, 64, + 65, 73, 8, 36, 82, 66, 132, 69, 63, 68, 81, 8, 1, 114, 8, 70, 65, 74, 65, 80, 8, + 47, 61, 67, 65, 79, 78, 72, 61, 81, 87, 8, 24, 24, 33, 8, 1, 118, 8, 64, 65, 73, + 8, 31, 27, 11, 20, 8, 83, 75, 73, 8, 23, 31, 24, 22, 8, 1, 125, 8, 83, 65, 79, + 4, 8, 23, 23, 11, 8, 73, 65, 68, 79, 2, 67, 65, 74, 61, 82, 8, 73, 61, 74, 63, + 68, 73, 61, 72, 8, 1, 115, 8, 84, 69, 79, 8, 61, 82, 80, 8, 31, 20, 8, 1, 128, + 8, 7, 87, 84, 69, 132, 63, 68, 65, 74, 6, 8, 62, 69, 80, 8, 71, 61, 74, 74, 20, + 8, 29, 27, 8, 1, 119, 8, 64, 69, 65, 8, 25, 24, 27, 22, 11, 8, 60, 69, 73, 73, + 65, 79, 2, 22, 22, 22, 8, 64, 65, 73, 8, 1, 116, 8, 56, 65, 79, 62, 69, 74, 64, + 82, 74, 67, 8, 43, 82, 67, 75, 8, 23, 24, 22, 22, 20, 8, 96, 8, 64, 69, 65, 8, + 75, 64, 65, 79, 8, 36, 74, 79, 82, 66, 65, 74, 8, 30, 24, 8, 1, 120, 8, 64, 69, + 65, 8, 24, 23, 22, 22, 11, 8, 83, 69, 65, 72, 72, 65, 69, 63, 68, 81, 2, 64, 65, + 79, 74, 8, 132, 75, 84, 65, 69, 81, 8, 1, 125, 8, 65, 79, 132, 81, 65, 74, 8, 124, + 63, 20, 8, 25, 28, 25, 20, 8, 97, 8, 65, 69, 74, 65, 8, 23, 30, 22, 31, 8, 56, + 65, 79, 132, 81, 103, 79, 71, 82, 74, 67, 8, 23, 30, 31, 31, 8, 1, 119, 8, 23, 24, + 20, 8, 31, 11, 8, 83, 75, 74, 2, 7, 51, 61, 79, 87, 65, 72, 72, 65, 74, 6, 8, + 69, 132, 81, 8, 126, 8, 67, 61, 74, 87, 8, 64, 61, 80, 8, 23, 30, 26, 22, 20, 8, + 1, 128, 8, 84, 82, 79, 64, 65, 8, 37, 86, 71, 8, 82, 74, 64, 8, 28, 29, 8, 126, + 8, 42, 79, 82, 74, 64, 8, 24, 29, 11, 8, 23, 31, 22, 30, 21, 23, 25, 20, 2, 68, + 65, 66, 81, 8, 83, 75, 74, 8, 1, 114, 8, 73, 69, 79, 8, 47, 61, 73, 78, 65, 74, + 8, 23, 30, 29, 30, 1, 112, 8, 1, 115, 8, 22, 22, 22, 8, 41, 65, 79, 74, 65, 79, + 8, 23, 29, 26, 8, 25, 22, 8, 126, 8, 64, 61, 102, 8, 25, 23, 11, 8, 132, 75, 72, + 72, 2, 42, 79, 75, 102, 4, 37, 65, 79, 72, 69, 74, 8, 65, 69, 74, 73, 61, 72, 8, + 96, 8, 44, 44, 44, 8, 82, 74, 64, 8, 23, 22, 20, 8, 96, 8, 46, 109, 74, 69, 67, + 4, 8, 44, 44, 44, 8, 61, 82, 66, 8, 23, 26, 22, 8, 1, 122, 8, 25, 22, 20, 8, + 24, 27, 22, 11, 8, 64, 69, 65, 2, 48, 103, 64, 63, 68, 65, 74, 8, 84, 65, 79, 64, + 65, 74, 8, 1, 115, 8, 64, 69, 65, 8, 43, 61, 82, 80, 84, 61, 79, 81, 80, 8, 23, + 24, 20, 8, 1, 123, 8, 83, 75, 79, 4, 8, 23, 30, 29, 31, 8, 39, 61, 79, 110, 62, + 65, 79, 8, 23, 30, 31, 28, 8, 1, 125, 8, 83, 75, 74, 8, 25, 28, 25, 11, 8, 65, + 79, 67, 61, 62, 2, 45, 61, 66, 66, 104, 8, 31, 24, 11, 8, 1, 117, 8, 61, 82, 63, + 68, 8, 61, 82, 66, 8, 30, 29, 20, 8, 126, 8, 75, 64, 65, 79, 8, 36, 74, 72, 65, + 69, 68, 65, 8, 65, 81, 84, 61, 69, 67, 65, 74, 8, 30, 22, 22, 8, 1, 127, 8, 84, + 65, 69, 72, 8, 25, 26, 30, 25, 30, 11, 8, 64, 65, 79, 2, 53, 65, 69, 81, 8, 61, + 74, 64, 65, 79, 65, 79, 8, 1, 122, 8, 83, 75, 73, 8, 72, 69, 65, 68, 65, 74, 8, + 24, 18, 8, 1, 115, 8, 64, 61, 80, 8, 23, 23, 25, 21, 23, 25, 8, 24, 22, 22, 22, + 8, 23, 26, 23, 8, 97, 8, 64, 69, 65, 8, 23, 24, 11, 8, 64, 65, 79, 70, 65, 74, + 69, 67, 65, 74, 2, 64, 61, 80, 8, 64, 61, 102, 8, 1, 116, 8, 48, 61, 74, 67, 65, + 72, 8, 84, 69, 79, 64, 8, 23, 29, 28, 31, 1, 112, 8, 1, 120, 8, 23, 22, 22, 8, + 81, 79, 75, 81, 87, 8, 64, 65, 74, 8, 28, 23, 8, 1, 122, 8, 83, 65, 79, 62, 69, + 74, 64, 65, 74, 8, 23, 30, 25, 27, 11, 8, 61, 72, 132, 75, 2, 74, 69, 63, 68, 81, + 8, 84, 65, 72, 63, 68, 65, 8, 98, 8, 64, 61, 80, 8, 84, 65, 69, 63, 68, 81, 8, + 23, 24, 27, 33, 8, 1, 125, 8, 64, 65, 79, 8, 23, 30, 31, 25, 8, 132, 63, 68, 79, + 69, 66, 81, 65, 74, 8, 23, 23, 8, 98, 8, 24, 30, 20, 8, 24, 30, 26, 11, 8, 68, + 69, 65, 132, 69, 67, 65, 74, 2, 60, 82, 132, 63, 68, 82, 102, 8, 64, 65, 73, 67, 65, + 73, 103, 102, 8, 1, 117, 8, 64, 65, 80, 8, 65, 69, 74, 65, 79, 8, 31, 20, 8, 1, + 123, 8, 83, 75, 79, 68, 61, 74, 64, 65, 74, 8, 67, 65, 72, 65, 67, 65, 74, 8, 83, + 75, 72, 72, 65, 74, 8, 27, 8, 1, 127, 8, 39, 61, 80, 8, 27, 28, 11, 8, 65, 79, + 68, 61, 72, 81, 65, 74, 2, 23, 28, 11, 8, 55, 68, 79, 8, 1, 127, 8, 28, 28, 11, + 8, 82, 74, 64, 8, 27, 18, 8, 1, 116, 8, 84, 69, 79, 8, 65, 69, 74, 8, 68, 69, + 74, 87, 82, 87, 82, 66, 110, 67, 65, 74, 8, 23, 25, 30, 23, 8, 1, 127, 8, 82, 74, + 64, 8, 24, 11, 8, 64, 61, 80, 2, 47, 61, 67, 65, 79, 78, 72, 61, 81, 87, 4, 56, + 75, 79, 132, 81, 65, 68, 65, 79, 32, 8, 75, 64, 65, 79, 8, 1, 127, 8, 132, 69, 74, + 64, 8, 64, 65, 79, 8, 28, 31, 20, 8, 1, 123, 8, 110, 62, 65, 79, 8, 64, 69, 65, + 8, 84, 103, 79, 65, 74, 8, 26, 22, 8, 22, 22, 22, 8, 23, 22, 11, 8, 132, 69, 63, + 68, 2, 132, 69, 74, 64, 8, 40, 79, 72, 61, 102, 8, 1, 118, 8, 61, 82, 66, 8, 82, + 74, 64, 8, 23, 30, 29, 22, 20, 8, 1, 125, 8, 64, 65, 79, 8, 82, 74, 64, 8, 109, + 81, 69, 67, 65, 8, 23, 30, 28, 28, 8, 1, 120, 8, 64, 69, 65, 8, 25, 22, 22, 22, + 11, 8, 87, 75, 67, 65, 74, 2, 65, 79, 109, 66, 66, 74, 65, 81, 20, 8, 53, 81, 61, + 74, 64, 78, 82, 74, 71, 81, 8, 1, 122, 8, 22, 22, 22, 8, 48, 61, 69, 8, 23, 30, + 30, 30, 20, 8, 1, 114, 8, 75, 68, 74, 65, 8, 132, 69, 63, 68, 8, 64, 61, 102, 8, + 25, 27, 8, 126, 8, 84, 65, 79, 64, 65, 74, 20, 8, 23, 30, 31, 30, 11, 8, 36, 62, + 62, 61, 82, 2, 132, 75, 72, 63, 68, 65, 74, 8, 53, 63, 68, 84, 69, 65, 79, 69, 67, + 71, 65, 69, 81, 65, 74, 8, 1, 128, 8, 64, 69, 65, 132, 65, 74, 8, 57, 75, 68, 74, + 82, 74, 67, 65, 74, 8, 31, 20, 8, 1, 120, 8, 23, 26, 22, 8, 22, 22, 22, 8, 44, + 63, 68, 8, 28, 8, 1, 123, 8, 23, 22, 20, 8, 31, 22, 11, 8, 24, 22, 22, 2, 84, + 65, 79, 64, 65, 74, 8, 74, 69, 63, 68, 81, 8, 96, 8, 54, 65, 69, 72, 62, 65, 81, + 79, 61, 67, 8, 42, 65, 64, 61, 74, 71, 65, 74, 8, 25, 22, 22, 22, 1, 112, 8, 1, + 120, 8, 64, 69, 65, 8, 70, 82, 74, 67, 65, 8, 61, 82, 63, 68, 8, 23, 8, 1, 114, + 8, 61, 82, 66, 8, 23, 30, 24, 28, 11, 8, 23, 23, 26, 20, 2, 132, 65, 69, 74, 65, + 8, 22, 30, 20, 8, 1, 116, 8, 64, 69, 65, 8, 82, 74, 64, 8, 28, 27, 28, 18, 8, + 1, 118, 8, 82, 74, 64, 8, 54, 61, 67, 65, 80, 75, 79, 64, 74, 82, 74, 67, 32, 8, + 64, 65, 79, 8, 23, 23, 8, 1, 125, 8, 64, 65, 79, 8, 23, 24, 27, 11, 8, 61, 72, + 132, 75, 2, 64, 65, 80, 68, 61, 72, 62, 8, 67, 61, 74, 87, 8, 97, 8, 84, 69, 79, + 8, 74, 82, 79, 8, 24, 22, 22, 22, 18, 8, 1, 119, 8, 87, 82, 73, 8, 23, 30, 30, + 25, 8, 65, 69, 74, 8, 24, 22, 8, 1, 119, 8, 84, 65, 79, 64, 65, 74, 20, 6, 8, + 23, 11, 8, 37, 61, 74, 67, 72, 61, 64, 65, 132, 63, 68, 2, 53, 69, 65, 8, 69, 63, + 68, 8, 1, 122, 8, 83, 75, 73, 8, 64, 65, 79, 8, 26, 31, 31, 33, 8, 1, 127, 8, + 53, 69, 65, 8, 79, 82, 74, 64, 8, 23, 25, 26, 24, 8, 31, 22, 8, 37, 61, 72, 71, + 65, 8, 27, 22, 22, 11, 8, 82, 74, 81, 65, 79, 79, 69, 63, 68, 81, 65, 81, 20, 2, + 84, 65, 80, 68, 61, 72, 62, 8, 64, 65, 80, 8, 62, 65, 132, 63, 68, 72, 69, 65, 102, + 81, 8, 37, 65, 61, 73, 81, 65, 74, 8, 23, 24, 22, 22, 20, 8, 53, 82, 73, 73, 65, + 20, 8, 23, 30, 24, 24, 8, 84, 65, 132, 65, 74, 81, 4, 8, 23, 24, 22, 22, 8, 1, + 123, 8, 64, 65, 79, 8, 23, 24, 11, 8, 64, 65, 79, 2, 42, 79, 65, 64, 86, 8, 66, + 110, 79, 8, 1, 118, 8, 23, 30, 27, 27, 8, 84, 103, 63, 68, 132, 81, 8, 23, 30, 27, + 30, 20, 8, 1, 122, 8, 61, 82, 66, 8, 36, 82, 63, 68, 8, 37, 65, 102, 65, 79, 8, + 23, 31, 24, 22, 8, 1, 125, 8, 46, 61, 73, 62, 61, 63, 68, 8, 29, 11, 8, 39, 69, + 65, 2, 36, 74, 84, 65, 132, 65, 74, 68, 65, 69, 81, 8, 64, 61, 87, 82, 8, 98, 8, + 82, 74, 64, 8, 23, 30, 29, 31, 8, 23, 31, 23, 27, 20, 8, 1, 121, 8, 59, 61, 74, + 67, 8, 41, 75, 74, 64, 80, 8, 82, 74, 64, 8, 25, 22, 22, 22, 8, 1, 117, 8, 49, + 61, 73, 65, 74, 10, 8, 23, 24, 22, 22, 11, 8, 23, 31, 23, 28, 2, 67, 61, 74, 87, + 8, 84, 65, 79, 64, 65, 74, 8, 64, 61, 102, 8, 66, 82, 79, 63, 68, 81, 62, 61, 79, + 8, 23, 24, 22, 22, 20, 8, 1, 127, 8, 82, 74, 64, 8, 24, 24, 20, 8, 64, 65, 73, + 8, 23, 25, 8, 1, 116, 8, 87, 82, 79, 8, 29, 11, 8, 65, 69, 74, 65, 80, 2, 64, + 65, 73, 8, 64, 65, 79, 8, 25, 28, 25, 8, 64, 65, 73, 8, 23, 22, 22, 18, 8, 1, + 121, 8, 23, 30, 20, 8, 30, 22, 22, 22, 8, 52, 61, 81, 68, 68, 61, 82, 132, 65, 80, + 8, 23, 22, 29, 28, 8, 1, 121, 8, 64, 65, 74, 8, 31, 31, 11, 8, 82, 74, 64, 2, + 69, 79, 67, 65, 74, 64, 84, 65, 72, 63, 68, 65, 74, 8, 87, 82, 79, 8, 1, 122, 8, + 70, 65, 74, 69, 67, 65, 74, 8, 53, 81, 79, 20, 8, 24, 22, 20, 8, 1, 118, 8, 62, + 65, 81, 79, 103, 67, 81, 8, 23, 30, 31, 22, 8, 65, 69, 74, 65, 74, 8, 30, 8, 1, + 113, 8, 23, 26, 25, 23, 8, 30, 23, 11, 8, 23, 30, 29, 29, 2, 55, 74, 81, 65, 79, + 74, 65, 68, 73, 82, 74, 67, 65, 74, 8, 42, 79, 110, 74, 132, 81, 79, 20, 8, 1, 121, + 8, 23, 27, 20, 8, 64, 65, 79, 8, 29, 1, 112, 8, 1, 123, 8, 64, 69, 65, 8, 82, + 74, 64, 8, 62, 69, 80, 8, 28, 8, 98, 8, 65, 69, 74, 65, 73, 8, 23, 31, 24, 22, + 11, 8, 132, 69, 81, 87, 82, 74, 67, 65, 74, 2, 82, 74, 64, 8, 67, 65, 84, 61, 72, + 81, 69, 67, 8, 1, 128, 8, 65, 69, 74, 87, 65, 72, 74, 65, 8, 132, 69, 74, 64, 20, + 8, 23, 23, 20, 8, 1, 127, 8, 132, 69, 74, 64, 8, 61, 62, 65, 79, 8, 23, 30, 26, + 30, 8, 23, 24, 8, 1, 120, 8, 36, 82, 66, 66, 61, 132, 132, 82, 74, 67, 8, 30, 11, + 8, 67, 61, 74, 87, 2, 65, 79, 84, 75, 79, 62, 65, 74, 8, 67, 72, 61, 82, 62, 65, + 8, 1, 116, 8, 84, 69, 79, 64, 8, 40, 69, 74, 65, 8, 23, 30, 24, 28, 33, 8, 126, + 8, 23, 26, 20, 8, 61, 82, 66, 8, 64, 65, 74, 65, 74, 8, 23, 24, 8, 1, 115, 8, + 53, 81, 65, 72, 72, 65, 74, 8, 27, 30, 11, 8, 37, 65, 132, 63, 68, 84, 65, 79, 64, + 65, 74, 2, 53, 63, 68, 82, 72, 64, 69, 67, 71, 65, 69, 81, 8, 56, 65, 79, 65, 69, + 74, 8, 1, 125, 8, 69, 132, 81, 8, 23, 30, 29, 30, 8, 30, 25, 11, 8, 96, 8, 64, + 65, 74, 8, 64, 69, 65, 132, 65, 73, 8, 61, 82, 63, 68, 8, 25, 27, 8, 1, 123, 8, + 25, 25, 23, 21, 23, 25, 8, 30, 23, 11, 8, 53, 81, 82, 73, 78, 66, 2, 23, 22, 22, + 8, 64, 65, 80, 68, 61, 72, 62, 8, 96, 8, 68, 65, 79, 87, 82, 4, 8, 39, 82, 79, + 63, 68, 8, 27, 22, 20, 8, 1, 123, 8, 83, 65, 79, 82, 79, 132, 61, 63, 68, 81, 8, + 43, 65, 79, 79, 65, 74, 8, 67, 65, 71, 110, 74, 64, 69, 67, 81, 8, 27, 27, 8, 1, + 114, 8, 36, 79, 81, 8, 31, 27, 22, 11, 8, 64, 65, 74, 2, 36, 82, 66, 132, 63, 68, + 72, 61, 67, 8, 124, 63, 20, 8, 1, 113, 8, 87, 82, 79, 8, 68, 61, 81, 8, 23, 31, + 23, 29, 20, 8, 98, 8, 64, 65, 80, 8, 64, 65, 79, 8, 61, 62, 84, 65, 69, 63, 68, + 65, 74, 64, 65, 79, 8, 30, 28, 8, 97, 8, 37, 65, 69, 8, 31, 31, 11, 8, 7, 39, + 61, 80, 2, 61, 72, 132, 75, 8, 73, 69, 81, 8, 1, 115, 8, 64, 65, 74, 8, 31, 30, + 27, 8, 27, 26, 11, 8, 45, 61, 68, 79, 65, 80, 62, 65, 83, 109, 72, 71, 65, 79, 82, + 74, 67, 8, 74, 61, 63, 68, 8, 22, 22, 22, 8, 23, 31, 24, 22, 8, 1, 117, 8, 69, + 132, 81, 8, 30, 30, 11, 8, 60, 61, 68, 72, 2, 62, 65, 69, 8, 23, 31, 24, 25, 25, + 8, 1, 120, 8, 61, 82, 80, 8, 64, 65, 80, 8, 29, 25, 28, 31, 18, 8, 98, 8, 64, + 65, 79, 8, 83, 75, 74, 8, 27, 30, 28, 24, 8, 25, 26, 8, 1, 123, 8, 23, 27, 24, + 25, 8, 23, 29, 22, 11, 8, 36, 74, 132, 81, 69, 65, 67, 65, 2, 64, 69, 65, 8, 132, + 75, 72, 72, 65, 74, 8, 1, 127, 8, 7, 68, 65, 66, 81, 69, 67, 65, 79, 8, 71, 72, + 65, 69, 74, 65, 74, 8, 24, 20, 8, 126, 8, 23, 23, 26, 20, 8, 64, 65, 74, 8, 44, + 44, 44, 8, 24, 22, 22, 8, 1, 125, 8, 53, 69, 65, 8, 28, 31, 11, 8, 53, 75, 74, + 74, 81, 61, 67, 65, 74, 32, 2, 132, 69, 63, 68, 8, 65, 69, 74, 8, 97, 8, 64, 65, + 79, 8, 36, 74, 79, 82, 66, 65, 74, 8, 23, 31, 18, 8, 1, 118, 8, 84, 69, 79, 8, + 61, 72, 132, 75, 8, 87, 82, 79, 8, 29, 28, 8, 1, 120, 8, 79, 65, 63, 68, 81, 8, + 23, 30, 25, 29, 11, 8, 64, 61, 68, 65, 79, 2, 45, 82, 74, 67, 65, 74, 8, 40, 81, + 61, 81, 8, 126, 8, 53, 81, 61, 64, 81, 8, 48, 75, 74, 61, 81, 65, 74, 8, 25, 24, + 22, 22, 18, 8, 96, 8, 64, 61, 102, 8, 64, 65, 73, 8, 132, 63, 68, 84, 61, 74, 71, + 65, 74, 8, 23, 24, 8, 1, 121, 8, 64, 65, 79, 8, 26, 11, 8, 37, 65, 64, 110, 79, + 66, 74, 69, 80, 2, 25, 24, 27, 22, 8, 64, 69, 65, 8, 1, 115, 8, 60, 82, 79, 8, + 64, 69, 65, 132, 65, 79, 8, 31, 30, 27, 1, 112, 8, 97, 8, 45, 61, 68, 79, 65, 8, + 64, 69, 65, 8, 61, 62, 87, 69, 65, 72, 65, 74, 8, 25, 27, 8, 97, 8, 132, 69, 63, + 68, 8, 23, 24, 27, 22, 11, 8, 83, 75, 74, 2, 67, 65, 73, 103, 102, 8, 132, 75, 72, + 63, 68, 65, 80, 8, 126, 8, 62, 69, 80, 8, 57, 69, 79, 8, 29, 30, 1, 112, 8, 1, + 128, 8, 64, 69, 65, 8, 42, 65, 73, 65, 69, 74, 64, 65, 62, 65, 68, 109, 79, 64, 65, + 8, 74, 65, 82, 65, 74, 8, 23, 27, 22, 29, 8, 1, 127, 8, 64, 65, 79, 8, 23, 24, + 11, 8, 64, 61, 102, 2, 57, 65, 74, 69, 67, 8, 71, 61, 74, 74, 8, 1, 113, 8, 48, + 69, 65, 81, 83, 65, 79, 81, 79, 61, 67, 8, 70, 82, 74, 67, 65, 8, 23, 20, 8, 1, + 119, 8, 68, 61, 81, 8, 68, 61, 62, 65, 74, 8, 61, 72, 80, 8, 24, 22, 8, 1, 116, + 8, 24, 25, 27, 21, 24, 24, 8, 29, 30, 11, 8, 64, 65, 79, 2, 36, 74, 64, 65, 79, + 82, 74, 67, 8, 23, 31, 23, 24, 21, 23, 29, 20, 8, 97, 8, 70, 65, 73, 61, 74, 64, + 8, 25, 23, 25, 8, 25, 22, 22, 11, 8, 1, 122, 8, 64, 61, 64, 82, 79, 63, 68, 8, + 64, 61, 102, 8, 82, 74, 64, 8, 23, 30, 29, 22, 8, 1, 122, 8, 65, 69, 74, 65, 8, + 23, 30, 27, 29, 11, 8, 132, 78, 79, 65, 63, 68, 65, 2, 64, 65, 79, 8, 51, 79, 75, + 81, 75, 71, 75, 72, 72, 8, 98, 8, 23, 30, 31, 23, 8, 64, 65, 80, 8, 23, 24, 1, + 112, 8, 1, 117, 8, 132, 75, 84, 69, 65, 8, 64, 61, 102, 8, 61, 72, 80, 8, 31, 8, + 1, 122, 8, 83, 75, 73, 8, 23, 26, 31, 22, 11, 8, 67, 65, 73, 69, 65, 81, 65, 81, + 65, 74, 2, 31, 31, 11, 20, 8, 64, 65, 79, 8, 1, 119, 8, 73, 61, 102, 67, 65, 62, + 65, 74, 64, 20, 8, 23, 30, 23, 23, 8, 29, 28, 33, 8, 1, 128, 8, 64, 69, 65, 8, + 23, 24, 20, 8, 23, 31, 23, 22, 21, 23, 27, 20, 8, 23, 31, 22, 28, 8, 1, 120, 8, + 39, 61, 80, 8, 23, 30, 30, 24, 11, 8, 36, 74, 132, 78, 79, 110, 63, 68, 65, 74, 2, + 65, 79, 68, 61, 72, 81, 65, 74, 20, 8, 132, 69, 81, 87, 65, 74, 64, 65, 8, 1, 113, + 8, 71, 72, 61, 79, 8, 68, 61, 81, 81, 65, 74, 8, 27, 31, 11, 8, 1, 115, 8, 53, + 81, 69, 73, 73, 65, 74, 8, 87, 82, 73, 8, 62, 69, 80, 8, 30, 25, 8, 96, 8, 64, + 65, 79, 8, 23, 30, 11, 8, 39, 79, 20, 2, 55, 74, 81, 65, 79, 79, 69, 63, 68, 81, + 8, 64, 65, 79, 8, 1, 116, 8, 47, 65, 62, 65, 74, 80, 71, 82, 74, 64, 65, 8, 64, + 65, 73, 8, 23, 30, 31, 27, 20, 8, 1, 128, 8, 61, 82, 63, 68, 8, 87, 61, 68, 72, + 65, 74, 8, 30, 22, 22, 8, 25, 8, 1, 128, 8, 83, 75, 74, 8, 23, 11, 8, 83, 75, + 74, 2, 67, 65, 132, 81, 65, 69, 67, 65, 79, 81, 65, 74, 8, 23, 27, 23, 24, 8, 1, + 120, 8, 40, 69, 74, 4, 8, 57, 65, 69, 132, 65, 8, 27, 24, 24, 11, 8, 1, 121, 8, + 87, 82, 20, 8, 30, 22, 22, 8, 132, 81, 69, 65, 67, 8, 28, 27, 8, 1, 118, 8, 132, + 63, 68, 82, 72, 65, 8, 24, 25, 11, 8, 42, 79, 82, 78, 78, 65, 74, 2, 87, 65, 69, + 67, 65, 74, 8, 62, 65, 81, 79, 61, 67, 65, 74, 8, 1, 122, 8, 62, 69, 80, 8, 23, + 30, 24, 31, 8, 23, 30, 30, 22, 11, 8, 1, 128, 8, 36, 82, 80, 132, 81, 61, 81, 81, + 82, 74, 67, 8, 71, 65, 69, 74, 65, 74, 8, 124, 63, 20, 8, 23, 27, 23, 29, 8, 1, + 116, 8, 64, 61, 102, 8, 23, 23, 11, 8, 42, 65, 62, 65, 79, 81, 2, 23, 26, 23, 8, + 36, 82, 63, 68, 8, 1, 121, 8, 45, 80, 20, 8, 64, 69, 65, 132, 65, 79, 8, 24, 29, + 1, 112, 8, 1, 122, 8, 64, 65, 80, 8, 36, 82, 80, 67, 72, 65, 69, 63, 68, 8, 83, + 75, 74, 8, 23, 31, 23, 27, 8, 98, 8, 23, 31, 23, 31, 8, 24, 11, 8, 65, 69, 74, + 132, 65, 69, 81, 69, 4, 2, 75, 64, 65, 79, 8, 44, 63, 68, 8, 1, 119, 8, 73, 69, + 81, 8, 43, 65, 79, 71, 109, 73, 73, 72, 69, 63, 68, 8, 23, 27, 20, 8, 1, 116, 8, + 83, 75, 74, 8, 40, 81, 61, 81, 8, 64, 61, 74, 74, 8, 23, 31, 22, 27, 8, 36, 62, + 4, 8, 25, 27, 22, 11, 8, 41, 65, 68, 72, 75, 84, 13, 132, 63, 68, 65, 74, 2, 47, + 109, 68, 74, 65, 8, 64, 61, 102, 8, 1, 117, 8, 45, 82, 72, 69, 8, 47, 65, 69, 81, + 65, 79, 32, 8, 26, 27, 20, 8, 1, 120, 8, 7, 48, 65, 69, 74, 65, 8, 64, 65, 80, + 8, 67, 65, 79, 69, 63, 68, 81, 65, 81, 65, 74, 8, 23, 23, 8, 1, 115, 8, 71, 109, + 74, 74, 65, 74, 8, 23, 31, 11, 8, 23, 27, 22, 2, 62, 69, 80, 8, 65, 69, 74, 8, + 1, 120, 8, 48, 61, 69, 8, 72, 65, 69, 63, 68, 81, 8, 23, 30, 31, 22, 20, 8, 1, + 123, 8, 67, 65, 81, 79, 69, 65, 62, 65, 74, 35, 8, 57, 69, 72, 68, 65, 72, 73, 8, + 68, 61, 62, 65, 74, 8, 25, 22, 22, 8, 1, 127, 8, 61, 72, 80, 8, 24, 23, 11, 8, + 83, 75, 79, 2, 69, 68, 79, 65, 79, 8, 31, 27, 11, 20, 8, 1, 125, 8, 74, 69, 63, + 68, 81, 8, 36, 74, 132, 81, 61, 72, 81, 8, 23, 28, 26, 27, 20, 8, 1, 119, 8, 61, + 82, 63, 68, 8, 84, 69, 79, 64, 8, 51, 75, 132, 69, 81, 69, 75, 74, 8, 27, 8, 1, + 114, 8, 23, 24, 11, 8, 23, 30, 22, 30, 11, 8, 64, 65, 79, 2, 41, 79, 61, 67, 65, + 8, 65, 69, 74, 65, 8, 1, 128, 8, 68, 61, 81, 81, 65, 20, 8, 27, 24, 20, 8, 25, + 22, 20, 8, 1, 113, 8, 124, 63, 20, 8, 57, 75, 68, 74, 82, 74, 67, 65, 74, 8, 84, + 75, 79, 64, 65, 74, 20, 8, 23, 31, 24, 22, 8, 1, 128, 8, 83, 75, 73, 8, 23, 22, + 11, 8, 62, 65, 69, 64, 65, 74, 2, 84, 65, 69, 102, 8, 43, 61, 82, 80, 68, 61, 72, + 81, 80, 4, 8, 97, 8, 84, 82, 79, 64, 65, 8, 60, 82, 67, 65, 87, 75, 67, 65, 74, + 65, 74, 8, 23, 24, 20, 8, 97, 8, 66, 110, 79, 8, 66, 110, 79, 8, 44, 44, 44, 8, + 23, 26, 8, 97, 8, 22, 22, 22, 8, 23, 31, 23, 30, 11, 8, 61, 62, 132, 75, 72, 82, + 81, 65, 2, 84, 69, 79, 64, 8, 61, 82, 66, 79, 65, 63, 68, 81, 8, 1, 113, 8, 84, + 69, 79, 8, 132, 81, 69, 65, 67, 8, 23, 31, 23, 28, 33, 8, 96, 8, 64, 65, 74, 8, + 60, 84, 65, 63, 71, 65, 8, 68, 61, 62, 65, 8, 23, 8, 96, 8, 42, 65, 62, 103, 82, + 64, 65, 8, 27, 11, 8, 74, 61, 63, 68, 2, 23, 23, 20, 8, 132, 69, 65, 8, 1, 114, + 8, 64, 69, 65, 8, 53, 81, 61, 64, 81, 8, 25, 30, 20, 8, 1, 128, 8, 53, 63, 68, + 82, 72, 64, 65, 78, 82, 81, 61, 81, 69, 75, 74, 8, 64, 65, 74, 8, 66, 110, 79, 8, + 28, 31, 8, 1, 120, 8, 43, 65, 79, 79, 8, 23, 23, 11, 8, 64, 65, 80, 68, 61, 72, + 62, 2, 64, 65, 79, 8, 36, 78, 79, 69, 72, 21, 45, 82, 74, 69, 8, 1, 119, 8, 36, + 62, 81, 65, 69, 72, 82, 74, 67, 8, 62, 65, 69, 64, 65, 74, 8, 27, 24, 20, 8, 1, + 115, 8, 65, 69, 74, 65, 73, 8, 61, 82, 63, 68, 8, 64, 69, 65, 132, 65, 79, 8, 25, + 27, 22, 8, 1, 117, 8, 124, 63, 20, 8, 26, 29, 11, 8, 84, 69, 65, 64, 65, 79, 2, + 45, 61, 68, 79, 65, 8, 23, 30, 24, 30, 8, 97, 8, 64, 65, 79, 8, 64, 65, 79, 8, + 30, 30, 20, 8, 1, 116, 8, 46, 109, 74, 69, 67, 72, 69, 63, 68, 65, 74, 8, 45, 61, + 68, 79, 8, 66, 110, 79, 8, 24, 26, 26, 26, 26, 8, 1, 122, 8, 65, 69, 74, 8, 23, + 28, 24, 22, 11, 8, 41, 79, 61, 67, 65, 2, 41, 65, 79, 74, 79, 82, 66, 32, 8, 39, + 61, 80, 8, 1, 118, 8, 23, 31, 23, 29, 8, 48, 61, 67, 69, 132, 81, 79, 61, 81, 8, + 27, 29, 1, 112, 8, 1, 117, 8, 39, 69, 65, 8, 64, 65, 80, 8, 36, 62, 81, 75, 73, + 73, 65, 74, 8, 30, 26, 8, 1, 115, 8, 82, 132, 84, 20, 8, 29, 26, 11, 8, 67, 65, + 73, 103, 102, 2, 74, 75, 63, 68, 8, 79, 82, 74, 64, 8, 1, 114, 8, 82, 74, 64, 8, + 83, 65, 79, 132, 63, 68, 69, 65, 64, 65, 74, 65, 79, 8, 23, 1, 112, 8, 1, 113, 8, + 62, 65, 79, 8, 23, 22, 20, 8, 64, 61, 102, 8, 29, 27, 22, 8, 1, 128, 8, 51, 66, + 72, 69, 63, 68, 81, 8, 23, 22, 11, 8, 62, 65, 87, 75, 67, 65, 74, 2, 64, 69, 65, + 8, 49, 65, 82, 66, 65, 79, 81, 8, 96, 8, 29, 22, 22, 22, 8, 44, 1, 92, 20, 8, + 23, 22, 30, 26, 26, 33, 8, 98, 8, 23, 23, 22, 8, 132, 63, 68, 72, 61, 67, 65, 74, + 8, 71, 72, 65, 69, 74, 65, 74, 8, 24, 29, 8, 1, 115, 8, 43, 65, 65, 79, 65, 80, + 64, 69, 65, 74, 132, 81, 65, 8, 25, 24, 11, 8, 64, 65, 79, 2, 65, 79, 68, 65, 62, + 72, 69, 63, 68, 8, 73, 65, 69, 74, 65, 74, 8, 83, 75, 74, 8, 39, 69, 65, 8, 26, + 31, 20, 8, 1, 128, 8, 23, 30, 26, 28, 8, 82, 74, 64, 8, 37, 79, 75, 64, 65, 8, + 28, 27, 24, 8, 1, 118, 8, 23, 31, 23, 29, 8, 31, 30, 11, 8, 69, 132, 81, 2, 73, + 109, 63, 68, 81, 65, 8, 23, 24, 27, 22, 8, 1, 118, 8, 84, 65, 72, 63, 68, 65, 74, + 8, 64, 69, 65, 8, 24, 22, 22, 18, 8, 1, 116, 8, 64, 61, 80, 8, 59, 78, 80, 69, + 72, 75, 74, 8, 73, 109, 63, 68, 81, 65, 8, 30, 8, 1, 127, 8, 82, 74, 64, 8, 28, + 30, 11, 8, 87, 69, 65, 72, 65, 74, 64, 65, 74, 2, 61, 62, 65, 79, 8, 64, 75, 63, + 68, 8, 1, 114, 8, 68, 69, 74, 65, 69, 74, 67, 65, 81, 61, 74, 8, 43, 75, 66, 65, + 8, 24, 22, 22, 22, 33, 8, 96, 8, 7, 37, 65, 79, 61, 81, 82, 74, 67, 6, 8, 84, + 65, 79, 64, 65, 74, 8, 124, 63, 20, 8, 28, 27, 28, 8, 1, 125, 8, 73, 61, 74, 8, + 23, 29, 11, 8, 65, 81, 84, 61, 80, 2, 52, 75, 132, 63, 68, 65, 79, 132, 81, 79, 61, + 102, 65, 8, 69, 132, 81, 8, 96, 8, 84, 65, 69, 81, 65, 79, 65, 80, 8, 64, 65, 79, + 8, 30, 27, 22, 11, 8, 98, 8, 83, 75, 74, 8, 51, 65, 74, 87, 69, 67, 8, 64, 65, + 79, 8, 23, 30, 31, 22, 8, 1, 121, 8, 40, 69, 74, 65, 8, 24, 29, 11, 8, 72, 65, + 81, 87, 81, 65, 74, 2, 64, 65, 80, 8, 132, 81, 79, 61, 102, 65, 8, 1, 125, 8, 44, + 1, 89, 20, 8, 23, 22, 24, 32, 8, 24, 31, 18, 8, 1, 116, 8, 68, 61, 62, 65, 8, + 83, 65, 79, 84, 61, 72, 81, 65, 81, 20, 8, 51, 79, 110, 66, 82, 74, 67, 8, 23, 28, + 25, 30, 8, 1, 115, 8, 23, 22, 26, 8, 27, 30, 28, 24, 11, 8, 68, 65, 82, 81, 65, + 2, 61, 62, 65, 79, 8, 74, 82, 79, 8, 1, 118, 8, 49, 79, 20, 8, 73, 65, 81, 65, + 79, 8, 25, 23, 18, 8, 98, 8, 83, 75, 74, 8, 132, 69, 63, 68, 8, 61, 62, 65, 74, + 64, 80, 8, 31, 8, 98, 8, 69, 63, 68, 8, 23, 24, 11, 8, 53, 81, 65, 73, 78, 65, + 72, 2, 68, 109, 68, 65, 79, 65, 8, 66, 75, 72, 67, 65, 74, 64, 65, 32, 8, 1, 113, + 8, 64, 65, 79, 8, 82, 74, 64, 8, 23, 31, 22, 23, 20, 8, 1, 119, 8, 65, 69, 74, + 65, 8, 45, 61, 68, 79, 8, 61, 72, 72, 65, 79, 8, 23, 23, 8, 1, 120, 8, 84, 69, + 79, 64, 8, 23, 22, 28, 24, 11, 8, 64, 65, 80, 2, 53, 81, 79, 20, 8, 132, 69, 63, + 68, 8, 60, 65, 69, 63, 68, 74, 65, 79, 32, 8, 62, 65, 69, 8, 29, 1, 112, 8, 1, + 123, 8, 62, 65, 81, 79, 61, 63, 68, 81, 65, 74, 20, 6, 8, 64, 69, 65, 8, 22, 30, + 20, 8, 30, 30, 8, 1, 125, 8, 59, 61, 63, 68, 81, 8, 23, 27, 11, 8, 64, 69, 65, + 2, 42, 65, 84, 69, 102, 8, 37, 65, 69, 8, 1, 122, 8, 23, 31, 23, 22, 21, 23, 27, + 20, 8, 26, 24, 25, 8, 23, 22, 22, 18, 8, 1, 115, 8, 64, 61, 102, 8, 132, 81, 79, + 20, 8, 64, 65, 79, 8, 23, 22, 29, 29, 8, 1, 119, 8, 66, 110, 79, 8, 30, 31, 31, + 11, 8, 61, 72, 72, 67, 65, 73, 65, 69, 74, 65, 2, 65, 79, 84, 75, 79, 62, 65, 74, + 20, 8, 57, 61, 74, 64, 65, 79, 82, 74, 67, 8, 1, 114, 8, 49, 65, 82, 62, 61, 82, + 8, 23, 30, 31, 31, 8, 23, 31, 22, 24, 20, 8, 1, 114, 8, 61, 74, 67, 65, 73, 65, + 132, 132, 65, 74, 8, 82, 74, 64, 8, 82, 74, 64, 8, 26, 31, 8, 96, 8, 83, 75, 74, + 8, 25, 23, 11, 8, 84, 69, 79, 64, 20, 2, 87, 84, 65, 69, 8, 64, 69, 65, 8, 1, + 114, 8, 65, 69, 74, 65, 73, 8, 64, 65, 79, 8, 23, 30, 29, 25, 1, 112, 8, 1, 121, + 8, 23, 30, 26, 8, 42, 65, 62, 69, 65, 81, 65, 74, 8, 7, 64, 61, 79, 110, 62, 65, + 79, 6, 8, 27, 30, 8, 1, 114, 8, 61, 82, 80, 8, 24, 11, 8, 66, 82, 79, 63, 68, + 81, 62, 61, 79, 2, 23, 30, 31, 31, 8, 64, 61, 79, 110, 62, 65, 79, 8, 1, 118, 8, + 36, 82, 80, 67, 61, 62, 65, 74, 8, 49, 65, 82, 62, 61, 82, 8, 28, 27, 11, 8, 1, + 127, 8, 39, 69, 74, 67, 65, 8, 36, 74, 79, 82, 66, 65, 74, 8, 75, 64, 65, 79, 8, + 23, 23, 8, 96, 8, 23, 30, 29, 29, 8, 24, 22, 22, 22, 11, 8, 46, 109, 74, 69, 67, + 69, 74, 2, 64, 61, 67, 65, 67, 65, 74, 8, 132, 75, 74, 64, 65, 79, 74, 8, 1, 118, + 8, 64, 69, 65, 8, 70, 65, 64, 75, 63, 68, 8, 23, 30, 27, 27, 20, 8, 1, 117, 8, + 56, 65, 79, 67, 72, 65, 69, 63, 68, 8, 53, 69, 81, 87, 82, 74, 67, 8, 64, 65, 79, + 8, 28, 31, 8, 1, 125, 8, 132, 81, 69, 65, 67, 8, 23, 31, 22, 27, 11, 8, 64, 61, + 80, 2, 132, 78, 79, 65, 63, 68, 65, 8, 69, 63, 68, 8, 1, 120, 8, 25, 30, 26, 8, + 27, 30, 28, 24, 8, 25, 27, 11, 8, 1, 117, 8, 29, 25, 28, 8, 75, 64, 65, 79, 8, + 53, 61, 73, 73, 65, 72, 68, 65, 69, 87, 82, 74, 67, 8, 24, 23, 8, 126, 8, 83, 65, + 79, 62, 69, 74, 64, 65, 74, 8, 23, 30, 31, 25, 11, 8, 74, 75, 63, 68, 2, 22, 30, + 20, 8, 62, 65, 84, 69, 72, 72, 69, 67, 81, 8, 1, 119, 8, 23, 28, 25, 31, 8, 65, + 69, 74, 81, 79, 65, 81, 65, 74, 8, 23, 22, 27, 26, 11, 8, 1, 114, 8, 68, 61, 62, + 65, 74, 8, 64, 65, 79, 8, 27, 27, 20, 8, 23, 28, 26, 27, 8, 97, 8, 7, 39, 65, + 79, 8, 25, 22, 11, 8, 36, 74, 132, 78, 79, 110, 63, 68, 65, 74, 2, 56, 65, 79, 4, + 8, 42, 79, 82, 74, 64, 61, 82, 66, 8, 1, 128, 8, 67, 65, 68, 65, 74, 64, 8, 64, + 65, 73, 8, 23, 24, 1, 112, 8, 1, 128, 8, 57, 75, 68, 74, 82, 74, 67, 65, 74, 8, + 25, 25, 26, 8, 67, 65, 78, 79, 110, 66, 81, 65, 74, 8, 23, 31, 23, 27, 8, 1, 113, + 8, 23, 31, 24, 22, 8, 23, 31, 23, 28, 11, 8, 23, 27, 22, 2, 53, 69, 65, 8, 62, + 65, 4, 8, 1, 114, 8, 36, 74, 67, 65, 72, 65, 67, 65, 74, 68, 65, 69, 81, 65, 74, + 8, 42, 79, 75, 72, 73, 61, 74, 4, 8, 23, 31, 24, 22, 20, 8, 1, 122, 8, 124, 63, + 20, 8, 23, 22, 22, 8, 64, 65, 79, 8, 28, 29, 8, 1, 120, 8, 66, 110, 79, 8, 23, + 25, 22, 24, 11, 8, 64, 69, 65, 2, 65, 69, 74, 87, 65, 72, 74, 65, 74, 8, 65, 69, + 74, 132, 65, 69, 81, 69, 4, 8, 98, 8, 83, 65, 79, 81, 79, 61, 67, 8, 64, 65, 79, + 8, 30, 26, 20, 8, 126, 8, 67, 82, 81, 65, 79, 8, 73, 69, 81, 8, 69, 132, 81, 8, + 23, 30, 24, 28, 8, 1, 121, 8, 23, 30, 26, 25, 8, 23, 26, 11, 8, 65, 79, 67, 65, + 62, 65, 74, 20, 2, 70, 65, 64, 75, 63, 68, 8, 87, 82, 79, 8, 96, 8, 51, 79, 110, + 66, 82, 74, 67, 8, 46, 79, 69, 65, 67, 65, 8, 23, 24, 24, 26, 20, 8, 1, 122, 8, + 84, 69, 65, 8, 83, 75, 73, 8, 64, 65, 79, 8, 23, 23, 8, 1, 119, 8, 67, 65, 81, + 79, 75, 66, 66, 65, 74, 65, 8, 31, 11, 8, 84, 65, 69, 72, 2, 64, 61, 102, 8, 36, + 73, 75, 79, 81, 69, 132, 61, 81, 69, 75, 74, 8, 1, 115, 8, 48, 61, 102, 65, 8, 64, + 65, 79, 8, 23, 29, 22, 33, 8, 1, 120, 8, 23, 26, 27, 8, 57, 65, 69, 132, 65, 8, + 83, 75, 74, 8, 27, 29, 8, 1, 128, 8, 41, 103, 72, 72, 65, 74, 8, 29, 22, 22, 22, + 11, 8, 82, 74, 64, 2, 87, 82, 73, 8, 64, 65, 79, 8, 1, 119, 8, 52, 61, 81, 68, + 61, 82, 132, 65, 80, 8, 64, 69, 65, 8, 24, 18, 8, 126, 8, 64, 65, 79, 8, 61, 82, + 66, 8, 87, 82, 79, 8, 28, 27, 22, 8, 96, 8, 84, 65, 79, 64, 65, 74, 8, 23, 29, + 28, 27, 11, 8, 61, 82, 66, 79, 65, 63, 68, 81, 2, 37, 65, 87, 69, 65, 68, 82, 74, + 67, 8, 82, 74, 132, 65, 79, 73, 8, 1, 121, 8, 69, 68, 79, 65, 79, 8, 82, 74, 64, + 8, 23, 31, 20, 8, 1, 118, 8, 64, 65, 79, 8, 82, 74, 64, 8, 83, 65, 79, 87, 69, + 63, 68, 81, 65, 74, 8, 30, 22, 8, 1, 119, 8, 65, 74, 81, 132, 78, 79, 69, 63, 68, + 81, 8, 31, 11, 8, 82, 74, 80, 2, 82, 74, 80, 8, 87, 82, 79, 8, 126, 8, 67, 65, + 74, 8, 53, 63, 68, 110, 72, 65, 79, 8, 23, 28, 22, 11, 8, 1, 116, 8, 132, 75, 84, + 69, 65, 8, 64, 61, 79, 82, 73, 8, 52, 65, 61, 72, 132, 63, 68, 82, 72, 65, 8, 23, + 31, 24, 22, 8, 1, 114, 8, 64, 82, 79, 63, 68, 8, 24, 22, 22, 22, 11, 8, 61, 74, + 64, 65, 79, 65, 2, 64, 61, 102, 8, 64, 65, 79, 67, 72, 65, 69, 63, 68, 65, 74, 8, + 98, 8, 132, 69, 63, 68, 8, 62, 69, 80, 8, 24, 28, 22, 22, 18, 8, 1, 113, 8, 45, + 82, 74, 69, 8, 79, 82, 74, 64, 8, 62, 65, 69, 73, 8, 31, 8, 1, 118, 8, 57, 75, + 68, 74, 82, 74, 67, 65, 74, 8, 23, 22, 11, 8, 83, 75, 74, 2, 62, 72, 69, 65, 62, + 8, 74, 61, 63, 68, 8, 1, 121, 8, 79, 82, 74, 64, 8, 71, 109, 74, 74, 65, 74, 8, + 26, 20, 8, 1, 115, 8, 64, 65, 73, 8, 48, 61, 79, 71, 8, 84, 65, 69, 72, 8, 30, + 8, 1, 115, 8, 66, 110, 79, 8, 23, 30, 31, 23, 11, 8, 132, 79, 61, 102, 65, 2, 72, + 65, 81, 87, 81, 65, 74, 8, 36, 74, 81, 79, 61, 67, 8, 98, 8, 64, 61, 79, 82, 73, + 8, 23, 31, 23, 22, 21, 23, 27, 20, 8, 28, 29, 11, 8, 1, 114, 8, 64, 69, 65, 132, + 65, 73, 8, 45, 61, 68, 79, 8, 69, 63, 68, 8, 25, 29, 8, 1, 113, 8, 65, 69, 74, + 65, 74, 8, 24, 22, 27, 22, 11, 8, 53, 81, 65, 82, 65, 79, 74, 2, 47, 65, 68, 79, + 78, 72, 103, 74, 65, 74, 8, 23, 27, 23, 24, 8, 1, 113, 8, 36, 79, 87, 81, 8, 64, + 65, 79, 8, 23, 30, 22, 31, 20, 8, 97, 8, 124, 63, 20, 8, 51, 65, 79, 132, 75, 74, + 8, 65, 74, 81, 132, 63, 68, 65, 69, 64, 65, 74, 64, 65, 8, 26, 22, 8, 1, 116, 8, + 74, 61, 63, 68, 8, 23, 30, 31, 31, 11, 8, 64, 65, 79, 2, 36, 62, 84, 61, 74, 64, + 65, 79, 82, 74, 67, 8, 56, 75, 79, 64, 65, 79, 68, 61, 82, 132, 65, 80, 8, 1, 121, + 8, 64, 65, 73, 8, 70, 65, 64, 65, 79, 8, 23, 31, 22, 23, 20, 8, 22, 22, 20, 8, + 64, 69, 65, 8, 64, 69, 65, 8, 30, 24, 8, 1, 121, 8, 74, 61, 63, 68, 8, 26, 11, + 8, 7, 48, 103, 74, 74, 65, 79, 6, 2, 64, 65, 73, 8, 84, 69, 79, 8, 126, 8, 83, + 75, 74, 8, 84, 65, 72, 63, 68, 65, 8, 23, 31, 20, 8, 1, 117, 8, 64, 69, 65, 8, + 74, 61, 63, 68, 8, 87, 69, 74, 132, 82, 74, 67, 8, 25, 22, 8, 1, 127, 8, 61, 62, + 65, 79, 8, 23, 30, 27, 22, 11, 8, 83, 75, 74, 2, 82, 74, 64, 8, 45, 82, 74, 67, + 65, 74, 8, 1, 128, 8, 64, 65, 73, 8, 62, 69, 80, 8, 23, 22, 22, 18, 8, 98, 8, + 36, 74, 132, 78, 79, 82, 63, 68, 8, 64, 65, 79, 8, 64, 61, 79, 82, 73, 8, 23, 22, + 8, 1, 120, 8, 61, 82, 63, 68, 8, 25, 11, 8, 53, 63, 68, 69, 72, 72, 65, 79, 19, + 54, 68, 65, 61, 81, 65, 79, 2, 81, 65, 74, 62, 82, 79, 67, 8, 23, 24, 11, 8, 1, + 122, 8, 23, 24, 11, 8, 7, 57, 61, 68, 79, 72, 69, 63, 68, 9, 6, 8, 30, 30, 20, + 8, 1, 117, 8, 62, 69, 80, 8, 73, 109, 63, 68, 81, 65, 8, 73, 61, 74, 63, 68, 73, + 61, 72, 8, 23, 30, 8, 1, 122, 8, 87, 82, 72, 65, 67, 65, 74, 8, 25, 26, 11, 8, + 73, 61, 63, 68, 65, 2, 64, 65, 74, 8, 24, 27, 22, 22, 8, 96, 8, 82, 74, 64, 8, + 65, 79, 66, 79, 61, 67, 65, 74, 20, 8, 23, 22, 20, 8, 1, 123, 8, 69, 68, 79, 65, + 8, 132, 75, 84, 69, 65, 8, 64, 69, 65, 8, 23, 30, 29, 31, 8, 1, 118, 8, 65, 79, + 132, 81, 65, 74, 73, 75, 72, 8, 23, 30, 31, 30, 11, 8, 23, 30, 23, 23, 2, 83, 75, + 73, 8, 51, 79, 75, 70, 65, 71, 81, 8, 1, 123, 8, 69, 132, 81, 8, 84, 110, 74, 132, + 63, 68, 65, 74, 80, 84, 65, 79, 81, 8, 23, 24, 18, 8, 1, 113, 8, 75, 64, 65, 79, + 8, 132, 69, 74, 64, 20, 8, 24, 30, 26, 8, 23, 31, 23, 30, 8, 1, 119, 8, 52, 65, + 69, 68, 65, 8, 24, 11, 8, 48, 65, 69, 74, 82, 74, 67, 2, 68, 61, 62, 65, 74, 8, + 67, 65, 68, 65, 74, 8, 1, 128, 8, 64, 65, 79, 8, 44, 1, 94, 20, 8, 29, 25, 1, + 112, 8, 24, 26, 26, 26, 26, 8, 84, 103, 72, 64, 65, 79, 8, 39, 69, 65, 8, 23, 30, + 27, 23, 8, 83, 75, 73, 8, 24, 29, 11, 8, 37, 65, 81, 65, 69, 72, 69, 67, 81, 65, + 74, 2, 24, 27, 22, 22, 8, 30, 22, 22, 22, 8, 98, 8, 65, 69, 74, 67, 65, 66, 110, + 68, 79, 81, 20, 8, 54, 65, 74, 75, 79, 8, 24, 27, 20, 8, 1, 113, 8, 65, 69, 74, + 8, 87, 82, 132, 81, 103, 74, 64, 69, 67, 65, 74, 8, 64, 61, 79, 82, 73, 8, 23, 31, + 22, 23, 8, 1, 115, 8, 64, 65, 73, 8, 23, 31, 22, 26, 11, 8, 39, 65, 74, 2, 65, + 69, 74, 65, 73, 8, 64, 65, 79, 8, 97, 8, 23, 31, 22, 24, 8, 65, 69, 74, 65, 8, + 23, 30, 26, 22, 18, 8, 1, 128, 8, 39, 69, 65, 8, 37, 65, 132, 63, 68, 72, 82, 102, + 8, 84, 61, 79, 65, 74, 8, 23, 24, 8, 1, 117, 8, 64, 65, 79, 8, 27, 22, 22, 11, + 8, 64, 69, 65, 2, 84, 82, 79, 64, 65, 8, 65, 69, 74, 65, 80, 8, 126, 8, 62, 82, + 79, 67, 65, 79, 8, 64, 69, 65, 8, 28, 27, 22, 11, 8, 98, 8, 25, 22, 22, 8, 74, + 69, 73, 73, 81, 8, 64, 65, 80, 68, 61, 72, 62, 8, 25, 22, 22, 8, 1, 117, 8, 23, + 22, 27, 24, 8, 24, 22, 11, 8, 53, 63, 68, 82, 72, 65, 74, 2, 83, 75, 73, 8, 64, + 65, 79, 8, 1, 123, 8, 64, 65, 79, 8, 84, 69, 79, 64, 20, 6, 8, 30, 30, 20, 8, + 1, 125, 8, 27, 26, 31, 8, 23, 31, 23, 30, 8, 23, 27, 24, 25, 8, 23, 8, 126, 8, + 44, 74, 81, 65, 79, 65, 132, 132, 65, 74, 132, 81, 61, 74, 64, 78, 82, 74, 71, 81, 8, + 25, 22, 11, 8, 67, 65, 68, 65, 74, 2, 64, 69, 65, 8, 84, 61, 79, 8, 44, 74, 132, + 81, 61, 72, 72, 61, 81, 69, 75, 74, 8, 64, 65, 79, 8, 23, 25, 25, 24, 1, 112, 8, + 1, 125, 8, 64, 69, 65, 8, 27, 22, 22, 22, 8, 64, 65, 80, 8, 30, 30, 30, 8, 1, + 116, 8, 73, 69, 81, 8, 23, 26, 11, 8, 61, 62, 65, 79, 2, 72, 65, 81, 87, 81, 65, + 79, 65, 73, 8, 51, 65, 74, 87, 69, 67, 8, 98, 8, 64, 65, 79, 8, 65, 79, 68, 109, + 68, 65, 74, 8, 23, 27, 1, 112, 8, 1, 119, 8, 132, 65, 69, 8, 84, 65, 72, 63, 68, + 65, 8, 65, 69, 74, 65, 80, 8, 26, 24, 8, 96, 8, 39, 61, 80, 8, 27, 22, 22, 11, + 8, 39, 61, 80, 2, 74, 69, 63, 68, 81, 8, 62, 69, 80, 8, 1, 115, 8, 73, 69, 81, + 8, 59, 78, 80, 69, 72, 75, 74, 8, 29, 24, 20, 8, 1, 121, 8, 53, 81, 103, 64, 81, + 65, 8, 132, 65, 81, 87, 65, 74, 8, 31, 30, 27, 8, 24, 22, 8, 1, 118, 8, 64, 82, + 79, 63, 68, 8, 29, 30, 11, 8, 53, 63, 68, 82, 72, 64, 65, 78, 82, 81, 61, 81, 69, + 75, 74, 2, 64, 65, 74, 8, 23, 24, 27, 22, 8, 1, 118, 8, 73, 65, 68, 79, 8, 48, + 75, 73, 73, 132, 65, 74, 4, 8, 25, 22, 22, 22, 18, 8, 126, 8, 56, 75, 79, 132, 81, + 65, 68, 65, 79, 8, 49, 75, 74, 74, 65, 74, 64, 61, 73, 73, 8, 45, 80, 20, 8, 23, + 31, 23, 29, 8, 1, 114, 8, 64, 61, 79, 61, 74, 8, 27, 26, 11, 8, 75, 62, 4, 2, + 52, 65, 69, 68, 65, 74, 20, 8, 62, 65, 71, 61, 73, 65, 74, 20, 8, 1, 117, 8, 68, + 65, 79, 87, 82, 4, 8, 73, 69, 81, 8, 23, 30, 31, 23, 11, 8, 1, 122, 8, 61, 82, + 66, 8, 61, 82, 80, 8, 64, 61, 79, 82, 73, 8, 23, 30, 31, 22, 8, 126, 8, 64, 75, + 63, 68, 8, 23, 30, 24, 27, 11, 8, 44, 1, 92, 20, 2, 82, 74, 64, 8, 25, 24, 31, + 21, 24, 25, 8, 1, 121, 8, 61, 72, 80, 8, 37, 65, 81, 81, 65, 74, 8, 23, 30, 30, + 25, 18, 8, 1, 121, 8, 55, 74, 81, 65, 79, 62, 61, 82, 8, 64, 61, 80, 8, 67, 65, + 132, 81, 61, 72, 81, 65, 81, 65, 8, 30, 25, 8, 96, 8, 50, 79, 64, 20, 8, 23, 31, + 23, 28, 11, 8, 64, 65, 80, 2, 64, 65, 79, 8, 37, 65, 81, 79, 61, 67, 8, 126, 8, + 43, 110, 72, 66, 65, 8, 61, 82, 80, 8, 23, 31, 22, 30, 18, 8, 1, 125, 8, 64, 65, + 79, 8, 56, 65, 79, 84, 61, 72, 81, 82, 74, 67, 20, 8, 64, 65, 80, 8, 23, 30, 31, + 23, 8, 1, 118, 8, 61, 82, 66, 87, 82, 84, 65, 74, 64, 65, 74, 8, 23, 23, 11, 8, + 51, 82, 81, 87, 20, 2, 64, 65, 80, 8, 23, 30, 28, 27, 8, 1, 119, 8, 83, 75, 74, + 8, 46, 79, 65, 69, 80, 132, 63, 68, 82, 72, 4, 8, 24, 24, 33, 8, 1, 116, 8, 82, + 74, 64, 8, 7, 132, 63, 68, 65, 69, 74, 81, 6, 8, 64, 69, 65, 8, 23, 27, 8, 96, + 8, 83, 75, 79, 4, 8, 24, 27, 22, 11, 8, 73, 65, 68, 79, 2, 132, 63, 68, 72, 69, + 65, 102, 72, 69, 63, 68, 8, 64, 69, 65, 8, 1, 125, 8, 64, 69, 65, 132, 65, 8, 132, + 69, 65, 8, 23, 27, 11, 8, 96, 8, 84, 65, 72, 63, 68, 65, 8, 68, 61, 74, 64, 65, + 72, 81, 8, 61, 62, 65, 79, 8, 25, 25, 8, 96, 8, 23, 30, 28, 27, 8, 29, 11, 8, + 37, 65, 4, 2, 82, 74, 64, 8, 61, 72, 80, 8, 1, 119, 8, 132, 81, 103, 64, 81, 69, + 132, 63, 68, 65, 79, 132, 65, 69, 81, 80, 8, 132, 81, 79, 20, 8, 24, 11, 8, 96, 8, + 73, 69, 81, 8, 132, 69, 63, 68, 8, 65, 69, 74, 65, 8, 23, 30, 27, 27, 8, 1, 123, + 8, 48, 69, 72, 72, 69, 75, 74, 65, 74, 8, 27, 11, 8, 64, 65, 79, 2, 64, 61, 79, + 82, 73, 8, 39, 79, 65, 68, 62, 110, 68, 74, 65, 8, 1, 125, 8, 62, 65, 69, 8, 68, + 61, 62, 65, 74, 8, 23, 30, 27, 23, 18, 8, 1, 122, 8, 40, 69, 74, 87, 69, 67, 8, + 61, 82, 63, 68, 8, 82, 74, 80, 8, 30, 30, 8, 96, 8, 82, 74, 64, 8, 24, 31, 11, + 8, 42, 79, 75, 72, 73, 61, 74, 4, 2, 64, 65, 73, 8, 36, 82, 66, 132, 69, 63, 68, + 81, 8, 1, 114, 8, 70, 65, 74, 65, 80, 8, 47, 61, 67, 65, 79, 78, 72, 61, 81, 87, + 8, 24, 24, 33, 8, 1, 118, 8, 64, 65, 73, 8, 31, 27, 11, 20, 8, 83, 75, 73, 8, + 23, 31, 24, 22, 8, 1, 125, 8, 83, 65, 79, 4, 8, 23, 23, 11, 8, 73, 65, 68, 79, + 2, 67, 65, 74, 61, 82, 8, 73, 61, 74, 63, 68, 73, 61, 72, 8, 1, 115, 8, 84, 69, + 79, 8, 61, 82, 80, 8, 31, 20, 8, 1, 128, 8, 7, 87, 84, 69, 132, 63, 68, 65, 74, + 6, 8, 62, 69, 80, 8, 71, 61, 74, 74, 20, 8, 29, 27, 8, 1, 119, 8, 64, 69, 65, + 8, 25, 24, 27, 22, 11, 8, 60, 69, 73, 73, 65, 79, 2, 22, 22, 22, 8, 64, 65, 73, + 8, 1, 116, 8, 56, 65, 79, 62, 69, 74, 64, 82, 74, 67, 8, 43, 82, 67, 75, 8, 23, + 24, 22, 22, 20, 8, 96, 8, 64, 69, 65, 8, 75, 64, 65, 79, 8, 36, 74, 79, 82, 66, + 65, 74, 8, 30, 24, 8, 1, 120, 8, 64, 69, 65, 8, 24, 23, 22, 22, 11, 8, 83, 69, + 65, 72, 72, 65, 69, 63, 68, 81, 2, 64, 65, 79, 74, 8, 132, 75, 84, 65, 69, 81, 8, + 1, 125, 8, 65, 79, 132, 81, 65, 74, 8, 124, 63, 20, 8, 25, 28, 25, 20, 8, 97, 8, + 65, 69, 74, 65, 8, 23, 30, 22, 31, 8, 56, 65, 79, 132, 81, 103, 79, 71, 82, 74, 67, + 8, 23, 30, 31, 31, 8, 1, 119, 8, 23, 24, 20, 8, 31, 11, 8, 83, 75, 74, 2, 7, + 51, 61, 79, 87, 65, 72, 72, 65, 74, 6, 8, 69, 132, 81, 8, 126, 8, 67, 61, 74, 87, + 8, 64, 61, 80, 8, 23, 30, 26, 22, 20, 8, 1, 128, 8, 84, 82, 79, 64, 65, 8, 37, + 86, 71, 8, 82, 74, 64, 8, 28, 29, 8, 126, 8, 42, 79, 82, 74, 64, 8, 24, 29, 11, + 8, 23, 31, 22, 30, 21, 23, 25, 20, 2, 68, 65, 66, 81, 8, 83, 75, 74, 8, 1, 114, + 8, 73, 69, 79, 8, 47, 61, 73, 78, 65, 74, 8, 23, 30, 29, 30, 1, 112, 8, 1, 115, + 8, 22, 22, 22, 8, 41, 65, 79, 74, 65, 79, 8, 23, 29, 26, 8, 25, 22, 8, 126, 8, + 64, 61, 102, 8, 25, 23, 11, 8, 132, 75, 72, 72, 2, 42, 79, 75, 102, 4, 37, 65, 79, + 72, 69, 74, 8, 65, 69, 74, 73, 61, 72, 8, 96, 8, 44, 44, 44, 8, 82, 74, 64, 8, + 23, 22, 20, 8, 96, 8, 46, 109, 74, 69, 67, 4, 8, 44, 44, 44, 8, 61, 82, 66, 8, + 23, 26, 22, 8, 1, 122, 8, 25, 22, 20, 8, 24, 27, 22, 11, 8, 64, 69, 65, 2, 48, + 103, 64, 63, 68, 65, 74, 8, 84, 65, 79, 64, 65, 74, 8, 1, 115, 8, 64, 69, 65, 8, + 43, 61, 82, 80, 84, 61, 79, 81, 80, 8, 23, 24, 20, 8, 1, 123, 8, 83, 75, 79, 4, + 8, 23, 30, 29, 31, 8, 39, 61, 79, 110, 62, 65, 79, 8, 23, 30, 31, 28, 8, 1, 125, + 8, 83, 75, 74, 8, 25, 28, 25, 11, 8, 65, 79, 67, 61, 62, 2, 45, 61, 66, 66, 104, + 8, 31, 24, 11, 8, 1, 117, 8, 61, 82, 63, 68, 8, 61, 82, 66, 8, 30, 29, 20, 8, + 126, 8, 75, 64, 65, 79, 8, 36, 74, 72, 65, 69, 68, 65, 8, 65, 81, 84, 61, 69, 67, + 65, 74, 8, 30, 22, 22, 8, 1, 127, 8, 84, 65, 69, 72, 8, 25, 26, 30, 25, 30, 11, + 8, 64, 65, 79, 2, 53, 65, 69, 81, 8, 61, 74, 64, 65, 79, 65, 79, 8, 1, 122, 8, + 83, 75, 73, 8, 72, 69, 65, 68, 65, 74, 8, 24, 18, 8, 1, 115, 8, 64, 61, 80, 8, + 23, 23, 25, 21, 23, 25, 8, 24, 22, 22, 22, 8, 23, 26, 23, 8, 97, 8, 64, 69, 65, + 8, 23, 24, 11, 8, 64, 65, 79, 70, 65, 74, 69, 67, 65, 74, 2, 64, 61, 80, 8, 64, + 61, 102, 8, 1, 116, 8, 48, 61, 74, 67, 65, 72, 8, 84, 69, 79, 64, 8, 23, 29, 28, + 31, 1, 112, 8, 1, 120, 8, 23, 22, 22, 8, 81, 79, 75, 81, 87, 8, 64, 65, 74, 8, + 28, 23, 8, 1, 122, 8, 83, 65, 79, 62, 69, 74, 64, 65, 74, 8, 23, 30, 25, 27, 11, + 8, 61, 72, 132, 75, 2, 74, 69, 63, 68, 81, 8, 84, 65, 72, 63, 68, 65, 8, 98, 8, + 64, 61, 80, 8, 84, 65, 69, 63, 68, 81, 8, 23, 24, 27, 33, 8, 1, 125, 8, 64, 65, + 79, 8, 23, 30, 31, 25, 8, 132, 63, 68, 79, 69, 66, 81, 65, 74, 8, 23, 23, 8, 98, + 8, 24, 30, 20, 8, 24, 30, 26, 11, 8, 68, 69, 65, 132, 69, 67, 65, 74, 2, 60, 82, + 132, 63, 68, 82, 102, 8, 64, 65, 73, 67, 65, 73, 103, 102, 8, 1, 117, 8, 64, 65, 80, + 8, 65, 69, 74, 65, 79, 8, 31, 20, 8, 1, 123, 8, 83, 75, 79, 68, 61, 74, 64, 65, + 74, 8, 67, 65, 72, 65, 67, 65, 74, 8, 83, 75, 72, 72, 65, 74, 8, 27, 8, 1, 127, + 8, 39, 61, 80, 8, 27, 28, 11, 8, 65, 79, 68, 61, 72, 81, 65, 74, 2}; diff --git a/src/external/rapidfuzz-cpp/test/distance/examples/ocr.hpp b/src/external/rapidfuzz-cpp/test/distance/examples/ocr.hpp new file mode 100644 index 00000000..f277d54b --- /dev/null +++ b/src/external/rapidfuzz-cpp/test/distance/examples/ocr.hpp @@ -0,0 +1,6 @@ +#pragma once +#include +#include + +extern std::vector ocr_example1; +extern std::vector ocr_example2; diff --git a/src/external/rapidfuzz-cpp/test/distance/examples/pythonLevenshteinIssue9.cpp b/src/external/rapidfuzz-cpp/test/distance/examples/pythonLevenshteinIssue9.cpp new file mode 100644 index 00000000..fdd636f9 --- /dev/null +++ b/src/external/rapidfuzz-cpp/test/distance/examples/pythonLevenshteinIssue9.cpp @@ -0,0 +1,426 @@ +#include "pythonLevenshteinIssue9.hpp" + +namespace pythonLevenshteinIssue9 { + +std::vector example1 = { + 8, 14, 4, 2, 3, 7, 15, 6, 4, 5, 8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 2, 10, 11, 12, 13, 8, + 2, 8, 14, 4, 2, 3, 7, 15, 6, 4, 5, 8, 6, 7, 16, 7, 13, 17, 2, 4, 16, 14, 7, 14, 18, 19, + 8, 20, 14, 4, 21, 13, 20, 22, 8, 2, 3, 4, 5, 6, 20, 8, 9, 10, 2, 10, 11, 12, 13, 8, 18, 14, + 10, 7, 23, 17, 13, 4, 8, 11, 4, 14, 8, 15, 7, 12, 8, 14, 18, 16, 7, 5, 13, 8, 18, 8, 11, 4, + 3, 9, 10, 21, 13, 4, 2, 3, 18, 24, 20, 25, 10, 8, 26, 26, 8, 23, 10, 3, 8, 2, 4, 16, 14, 7, + 10, 19, 8, 6, 4, 27, 19, 4, 27, 9, 3, 8, 18, 8, 20, 22, 3, 28, 8, 14, 10, 23, 7, 29, 8, 6, + 7, 30, 10, 2, 3, 15, 10, 13, 13, 20, 22, 8, 18, 8, 6, 9, 7, 2, 18, 15, 20, 22, 8, 19, 10, 21, + 10, 23, 17, 8, 14, 23, 29, 8, 4, 27, 18, 2, 4, 15, 8, 18, 8, 21, 18, 16, 13, 10, 2, 7, 31, 8, + 21, 10, 2, 11, 23, 7, 3, 13, 32, 5, 8, 15, 32, 10, 16, 14, 8, 14, 18, 16, 7, 5, 13, 10, 9, 7, + 8, 2, 8, 4, 21, 9, 7, 16, 33, 7, 19, 18, 28, 8, 26, 34, 34, 35, 8, 11, 4, 14, 21, 10, 9, 10, + 19, 8, 18, 14, 10, 7, 23, 17, 13, 32, 10, 8, 19, 7, 3, 10, 9, 18, 7, 23, 32, 8, 18, 8, 6, 4, + 19, 11, 23, 10, 6, 3, 36, 8, 11, 4, 14, 7, 9, 4, 6, 8, 15, 2, 10, 19, 28, 8, 6, 3, 4, 8, + 11, 9, 18, 12, 10, 23, 8, 2, 8, 7, 15, 18, 3, 4, 8, 15, 8, 6, 4, 13, 33, 10, 8, 4, 21, 37, + 29, 15, 23, 10, 13, 18, 29, 24, 38, 8, 9, 7, 2, 2, 9, 4, 30, 6, 7, 8, 21, 10, 16, 8, 11, 10, + 9, 10, 11, 23, 7, 3, 8, 4, 3, 8, 3, 18, 13, 17, 6, 4, 27, 27, 8, 39, 34, 35, 40, 8, 18, 8, + 41, 7, 9, 7, 13, 3, 18, 29, 8, 14, 4, 8, 26, 34, 8, 23, 10, 3, 24, 42, 8, 42, 8, 42, 8, 42, + 8, 42, 8, 42, 8, 42, 43, 44, 8, 16, 15, 4, 13, 18, 3, 10, 8, 18, 23, 18, 8, 11, 18, 12, 18, 3, + 10, 8, 15, 8, 30, 7, 3, 8, 2, 23, 4, 15, 4, 8, 45, 6, 20, 11, 10, 45, 8, 18, 8, 19, 32, 8, + 4, 3, 11, 9, 7, 15, 18, 19, 8, 15, 7, 19, 8, 13, 7, 12, 18, 8, 6, 10, 5, 2, 32, 8, 18, 8, + 16, 7, 21, 9, 4, 13, 18, 9, 20, 10, 19, 8, 15, 32, 10, 16, 14, 8, 14, 18, 16, 7, 5, 13, 10, 9, + 7, 24, 43, 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, 46, 8, 2, 4, 21, 2, 3, 15, 10, 13, + 13, 4, 10, 8, 11, 9, 4, 18, 16, 15, 4, 14, 2, 3, 15, 4, 8, 47, 8, 33, 10, 13, 7, 8, 2, 3, + 4, 5, 6, 18, 8, 9, 10, 2, 10, 12, 11, 13, 8, 13, 18, 25, 10, 28, 8, 30, 10, 19, 8, 20, 8, 6, + 4, 13, 6, 20, 9, 10, 13, 3, 4, 15, 8, 13, 7, 8, 26, 48, 42, 49, 34, 35, 28, 8, 7, 8, 6, 7, + 30, 10, 2, 3, 15, 4, 8, 50, 8, 13, 7, 8, 20, 9, 4, 15, 10, 13, 17, 8, 15, 32, 12, 10, 24, 16, + 7, 6, 7, 16, 32, 15, 7, 29, 8, 19, 10, 21, 10, 23, 17, 8, 20, 8, 13, 7, 2, 8, 15, 32, 8, 11, + 4, 23, 20, 30, 7, 10, 3, 10, 51, 31, 8, 15, 32, 10, 16, 14, 8, 16, 7, 19, 10, 9, 52, 18, 6, 7, + 8, 2, 8, 4, 21, 9, 7, 16, 33, 7, 19, 18, 43, 31, 8, 11, 9, 4, 27, 10, 2, 2, 18, 4, 13, 7, + 23, 17, 13, 32, 5, 8, 16, 7, 19, 10, 9, 8, 18, 8, 14, 18, 16, 7, 5, 13, 8, 11, 9, 4, 10, 6, + 3, 43, 31, 8, 41, 7, 9, 7, 13, 3, 18, 29, 8, 13, 7, 8, 15, 2, 53, 8, 42, 8, 48, 8, 23, 10, + 3, 54, 8, 20, 6, 4, 19, 11, 23, 10, 6, 3, 20, 10, 19, 8, 19, 10, 21, 10, 23, 17, 22, 8, 15, 7, + 12, 20, 8, 6, 15, 7, 9, 3, 18, 9, 20, 8, 18, 23, 18, 8, 14, 4, 19, 8, 42, 8, 11, 4, 8, 4, + 11, 3, 4, 15, 32, 19, 8, 20, 2, 23, 4, 15, 18, 29, 19, 8, 2, 8, 21, 4, 13, 20, 2, 7, 19, 18, + 8, 18, 8, 2, 6, 18, 14, 6, 7, 19, 18, 55, 8, 9, 7, 21, 4, 3, 7, 10, 19, 8, 11, 4, 8, 14, + 4, 41, 4, 15, 4, 9, 20, 43, 56, 57, 8, 18, 16, 41, 4, 3, 4, 15, 23, 10, 13, 18, 10, 8, 4, 3, + 8, 58, 42, 59, 48, 8, 14, 13, 10, 5, 43, 60, 8, 21, 10, 2, 11, 23, 7, 3, 13, 7, 29, 8, 14, 4, + 2, 3, 7, 15, 6, 7, 8, 15, 7, 12, 10, 5, 8, 19, 10, 21, 10, 23, 18, 61, 62, 57, 8, 11, 4, 14, + 13, 18, 19, 7, 10, 19, 8, 19, 10, 21, 10, 23, 17, 8, 13, 7, 8, 23, 22, 21, 4, 5, 8, 63, 3, 7, + 25, 8, 18, 8, 15, 8, 23, 22, 21, 20, 22, 8, 3, 4, 30, 6, 20, 8, 6, 15, 7, 9, 3, 18, 9, 32, + 8, 42, 8, 21, 10, 2, 11, 23, 7, 3, 13, 4, 31, 8, 2, 4, 21, 10, 9, 10, 19, 8, 19, 10, 21, 10, + 23, 17, 8, 21, 10, 16, 8, 14, 4, 11, 23, 7, 3, 43, 31, 8, 2, 15, 4, 10, 8, 11, 9, 4, 18, 16, + 15, 4, 14, 2, 3, 15, 4, 8, 19, 10, 21, 10, 23, 18, 28, 8, 2, 15, 4, 29, 8, 14, 4, 2, 3, 7, + 15, 6, 7, 28, 8, 2, 15, 4, 29, 8, 20, 2, 3, 7, 13, 4, 15, 6, 7, 43, 31, 8, 20, 21, 18, 9, + 7, 10, 19, 8, 16, 7, 8, 2, 4, 21, 4, 5, 8, 19, 20, 2, 4, 9, 8, 11, 4, 2, 23, 10, 8, 19, + 4, 13, 3, 7, 25, 7, 43, 31, 8, 11, 9, 18, 8, 11, 9, 4, 18, 16, 15, 4, 14, 2, 3, 15, 10, 8, + 19, 32, 8, 18, 2, 11, 4, 23, 17, 16, 20, 10, 19, 8, 18, 2, 6, 23, 22, 30, 18, 3, 10, 23, 17, 13, + 4, 8, 63, 6, 4, 23, 4, 41, 18, 30, 10, 2, 6, 18, 8, 30, 18, 2, 3, 32, 10, 8, 18, 8, 41, 18, + 11, 4, 7, 23, 23, 10, 9, 41, 10, 13, 13, 32, 10, 8, 19, 7, 3, 10, 9, 18, 7, 23, 32, 8, 64, 57, + 8, 19, 32, 8, 23, 22, 21, 18, 19, 8, 2, 15, 4, 53, 8, 14, 10, 23, 4, 8, 18, 8, 14, 4, 9, 4, + 25, 18, 19, 8, 9, 10, 11, 20, 3, 7, 33, 18, 10, 5, 61, 8, 65, 34, 34, 31, 8, 2, 3, 4, 10, 6, + 8, 20, 25, 10, 8, 20, 2, 3, 7, 13, 4, 15, 23, 10, 13, 32, 8, 13, 7, 8, 2, 4, 15, 10, 2, 3, + 17, 8, 18, 8, 9, 7, 14, 20, 22, 3, 8, 25, 18, 23, 17, 33, 4, 15, 24, 8, 47, 8, 42, 8, 47, 8, + 42, 8, 47, 8, 42, 8, 47, 8, 42, 8, 47, 66, 8, 14, 4, 21, 7, 15, 17, 3, 10, 8, 4, 21, 37, 29, + 15, 23, 10, 13, 18, 10, 8, 15, 8, 18, 16, 21, 9, 7, 13, 13, 4, 10, 28, 8, 30, 3, 4, 21, 8, 13, + 10, 8, 11, 4, 3, 10, 9, 29, 3, 17, 8, 15, 32, 41, 4, 14, 13, 4, 10, 8, 11, 9, 10, 14, 23, 4, + 25, 10, 13, 18, 10, 8, 18, 8, 13, 10, 8, 11, 9, 4, 11, 20, 2, 3, 18, 3, 17, 8, 7, 6, 33, 18, + 18, 47, 8, 42, 8, 47, 8, 42, 8, 47, 8, 42, 8, 47, 8, 42, 8, 47, 67, 57, 8, 26, 34, 8, 41, 23, + 7, 15, 13, 32, 68, 8, 11, 9, 18, 30, 18, 13, 8, 16, 7, 6, 7, 16, 7, 3, 17, 8, 19, 10, 21, 10, + 23, 17, 8, 20, 8, 13, 7, 2, 51, 26, 40, 8, 6, 9, 4, 19, 23, 10, 13, 18, 10, 8, 14, 10, 3, 7, + 23, 10, 5, 8, 11, 4, 8, 15, 2, 10, 19, 20, 8, 11, 10, 9, 18, 19, 10, 3, 9, 20, 43, 69, 40, 8, + 20, 14, 7, 9, 4, 2, 3, 4, 5, 6, 7, 29, 8, 11, 23, 7, 2, 3, 18, 6, 4, 15, 7, 29, 8, 6, + 9, 4, 19, 6, 7, 8, 50, 8, 26, 8, 19, 19, 61, 43, 59, 40, 8, 63, 6, 4, 23, 4, 41, 18, 30, 10, + 13, 4, 10, 8, 18, 8, 11, 23, 4, 3, 13, 4, 10, 8, 23, 14, 2, 11, 61, 43, 49, 40, 8, 20, 2, 18, + 23, 10, 13, 13, 32, 5, 8, 14, 15, 10, 9, 13, 4, 5, 8, 11, 9, 4, 27, 18, 23, 17, 8, 14, 23, 29, + 8, 12, 6, 7, 27, 4, 15, 42, 6, 20, 11, 10, 61, 43, 48, 40, 8, 11, 9, 18, 2, 7, 14, 6, 7, 8, + 13, 7, 8, 63, 6, 2, 33, 10, 13, 3, 9, 18, 6, 18, 8, 2, 8, 15, 18, 14, 18, 19, 32, 68, 8, 2, + 3, 4, 9, 4, 13, 61, 43, 70, 40, 8, 16, 7, 52, 18, 3, 7, 8, 20, 41, 23, 4, 15, 8, 11, 9, 18, + 8, 14, 4, 2, 3, 7, 15, 6, 10, 43, 58, 40, 8, 21, 10, 16, 4, 11, 7, 2, 13, 32, 10, 8, 2, 3, + 10, 6, 23, 7, 8, 18, 8, 16, 10, 9, 6, 7, 23, 7, 43, 65, 40, 8, 9, 4, 15, 13, 32, 10, 8, 16, + 10, 9, 6, 7, 23, 7, 8, 21, 10, 16, 8, 18, 2, 6, 7, 25, 10, 13, 18, 5, 43, 71, 40, 8, 13, 7, + 14, 10, 25, 13, 7, 29, 8, 27, 20, 9, 13, 18, 3, 20, 9, 7, 8, 2, 9, 10, 14, 13, 10, 41, 4, 8, + 18, 8, 11, 9, 10, 19, 18, 20, 19, 8, 2, 10, 41, 19, 10, 13, 3, 7, 43, 26, 34, 40, 8, 6, 9, 10, + 11, 23, 10, 13, 18, 29, 8, 11, 4, 15, 32, 12, 10, 13, 13, 4, 5, 8, 11, 9, 4, 30, 13, 4, 2, 3, + 18, 72, 8, 14, 4, 2, 3, 20, 11, 13, 7, 29, 8, 33, 10, 13, 7, 8, 31, 8, 15, 2, 10, 8, 15, 7, + 25, 13, 32, 10, 8, 13, 22, 7, 13, 2, 32, 28, 8, 19, 7, 6, 2, 18, 19, 7, 23, 17, 13, 4, 10, 8, + 6, 7, 30, 10, 2, 3, 15, 4, 8, 13, 7, 8, 2, 7, 19, 32, 68, 8, 15, 32, 41, 4, 14, 13, 32, 68, + 8, 20, 2, 23, 4, 15, 18, 29, 68, 24, 8, 44, 4, 2, 3, 7, 15, 23, 29, 5, 3, 10, 8, 16, 7, 29, + 15, 6, 20, 8, 13, 7, 8, 9, 7, 2, 30, 10, 3, 8, 15, 7, 12, 10, 5, 8, 21, 20, 14, 20, 52, 10, + 5, 8, 19, 10, 21, 10, 23, 18, 28, 8, 19, 32, 8, 11, 4, 14, 21, 10, 9, 10, 19, 8, 14, 23, 29, 8, + 15, 7, 2, 8, 13, 7, 14, 10, 25, 13, 20, 22, 8, 27, 20, 9, 13, 18, 3, 20, 9, 20, 28, 8, 20, 30, + 18, 3, 32, 15, 7, 22, 52, 20, 22, 8, 15, 7, 12, 18, 8, 11, 4, 25, 10, 23, 7, 13, 18, 29, 8, 18, + 8, 15, 7, 12, 8, 14, 10, 13, 10, 25, 13, 32, 5, 8, 21, 22, 14, 25, 10, 3, 61, 42, 8, 42, 8, 42, + 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, + 8, 42, 8, 42, 8, 42, 8, 42, 73, 8, 6, 4, 13, 2, 20, 23, 17, 3, 7, 33, 18, 29, 8, 11, 4, 8, + 14, 18, 16, 7, 5, 13, 20, 8, 18, 8, 19, 7, 3, 10, 9, 18, 7, 23, 7, 19, 51, 8, 11, 4, 14, 21, + 10, 9, 10, 19, 8, 19, 7, 3, 10, 9, 18, 7, 23, 28, 8, 33, 15, 10, 3, 28, 8, 3, 18, 11, 28, 8, + 27, 9, 10, 16, 10, 9, 4, 15, 6, 20, 8, 27, 7, 2, 7, 14, 4, 15, 28, 8, 15, 13, 20, 3, 9, 10, + 13, 13, 18, 10, 8, 19, 10, 68, 7, 13, 18, 16, 19, 32, 8, 18, 8, 9, 7, 2, 11, 4, 23, 4, 25, 10, + 13, 18, 10, 8, 11, 4, 23, 4, 6, 8, 31, 8, 14, 4, 11, 4, 23, 13, 18, 3, 10, 23, 17, 13, 20, 22, + 8, 27, 20, 9, 13, 18, 3, 20, 9, 20, 51, 8, 9, 20, 30, 6, 18, 28, 8, 11, 4, 14, 2, 15, 10, 3, + 6, 7, 8, 18, 8, 11, 9, 61, 74, 8, 4, 41, 9, 4, 19, 13, 32, 5, 8, 15, 32, 21, 4, 9, 8, 14, + 10, 6, 4, 9, 4, 15, 8, 39, 21, 4, 23, 10, 10, 8, 26, 58, 34, 34, 8, 33, 15, 10, 3, 4, 15, 40, + 28, 8, 19, 10, 68, 7, 13, 18, 16, 19, 4, 15, 28, 8, 19, 7, 3, 10, 9, 18, 7, 23, 4, 15, 8, 4, + 3, 8, 2, 9, 10, 14, 13, 10, 41, 4, 8, 14, 4, 8, 11, 9, 10, 19, 18, 20, 19, 8, 6, 23, 7, 2, + 2, 7, 61, 8, 11, 4, 19, 4, 25, 10, 19, 8, 15, 32, 21, 9, 7, 3, 17, 8, 11, 4, 14, 8, 23, 22, + 21, 4, 5, 8, 21, 22, 14, 25, 10, 3, 8, 18, 8, 18, 13, 3, 10, 9, 17, 10, 9, 61, 75, 8, 6, 4, + 13, 3, 9, 4, 23, 17, 8, 6, 7, 30, 10, 2, 3, 15, 7, 8, 13, 7, 8, 6, 7, 25, 14, 4, 19, 8, + 63, 3, 7, 11, 10, 8, 4, 3, 8, 21, 10, 2, 11, 23, 7, 3, 13, 4, 41, 4, 8, 14, 18, 16, 7, 5, + 13, 42, 11, 9, 4, 10, 6, 3, 7, 8, 14, 4, 8, 20, 2, 3, 7, 13, 4, 15, 6, 18, 8, 41, 4, 3, + 4, 15, 4, 41, 4, 8, 18, 16, 14, 10, 23, 18, 29, 61, 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, + 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, + 42, 76, 8, 14, 23, 29, 8, 15, 7, 2, 8, 19, 32, 8, 2, 4, 16, 14, 7, 14, 18, 19, 8, 18, 14, 10, + 7, 23, 17, 13, 32, 5, 8, 12, 6, 7, 27, 8, 18, 23, 18, 8, 6, 20, 68, 13, 22, 28, 8, 30, 3, 4, + 21, 32, 43, 77, 8, 15, 8, 13, 18, 68, 8, 20, 19, 10, 2, 3, 18, 23, 4, 2, 17, 8, 6, 7, 6, 8, + 19, 4, 25, 13, 4, 8, 21, 4, 23, 17, 12, 10, 8, 15, 10, 52, 10, 5, 28, 8, 18, 8, 15, 2, 10, 8, + 21, 32, 23, 4, 8, 45, 13, 7, 8, 2, 15, 4, 10, 19, 8, 19, 10, 2, 3, 10, 45, 43, 77, 8, 27, 20, + 9, 13, 18, 3, 20, 9, 7, 8, 9, 7, 14, 4, 15, 7, 23, 7, 8, 14, 4, 23, 41, 18, 10, 8, 41, 4, + 14, 32, 8, 2, 15, 4, 10, 5, 8, 13, 7, 14, 10, 25, 13, 4, 2, 3, 17, 22, 61, 8, 19, 32, 8, 14, + 7, 10, 19, 8, 2, 4, 21, 2, 3, 15, 10, 13, 13, 20, 22, 8, 41, 7, 9, 7, 13, 3, 18, 22, 8, 13, + 7, 8, 27, 20, 9, 13, 18, 3, 20, 9, 20, 8, 50, 8, 4, 3, 8, 48, 8, 23, 10, 3, 61, 42, 8, 42, + 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, + 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, 78, 57, 8, 70, 8, 12, 7, 41, 4, 15, 8, 14, 4, 8, 15, 7, + 12, 10, 41, 4, 8, 20, 14, 4, 21, 13, 4, 41, 4, 8, 12, 6, 7, 27, 7, 8, 18, 23, 18, 8, 6, 20, + 68, 13, 18, 61, 26, 40, 8, 16, 15, 4, 13, 18, 3, 10, 8, 13, 7, 19, 28, 8, 4, 2, 3, 7, 15, 23, + 29, 10, 3, 10, 8, 11, 9, 18, 19, 10, 9, 13, 32, 10, 8, 9, 7, 16, 19, 10, 9, 32, 8, 12, 6, 7, + 27, 7, 8, 18, 8, 14, 4, 11, 61, 8, 11, 4, 25, 10, 23, 7, 13, 18, 29, 8, 4, 8, 6, 7, 30, 10, + 2, 3, 15, 10, 8, 19, 7, 3, 10, 9, 18, 7, 23, 4, 15, 28, 8, 27, 20, 9, 13, 18, 3, 20, 9, 10, + 28, 8, 27, 7, 2, 7, 14, 4, 15, 8, 18, 8, 3, 61, 14, 61, 43, 69, 40, 8, 19, 32, 8, 14, 10, 23, + 7, 10, 19, 8, 11, 9, 10, 14, 15, 7, 9, 18, 3, 10, 23, 17, 13, 32, 5, 8, 9, 7, 2, 30, 10, 3, + 61, 8, 10, 2, 23, 18, 8, 33, 10, 13, 7, 8, 15, 7, 2, 8, 20, 2, 3, 9, 7, 18, 15, 7, 10, 3, + 28, 8, 2, 4, 41, 23, 7, 2, 4, 15, 32, 15, 7, 10, 19, 8, 15, 9, 10, 19, 29, 8, 14, 23, 29, 8, + 16, 7, 19, 10, 9, 42, 14, 18, 16, 7, 5, 13, 7, 61, 43, 59, 40, 8, 14, 18, 16, 7, 5, 13, 10, 9, + 8, 16, 7, 19, 10, 9, 52, 18, 6, 8, 11, 9, 18, 10, 16, 25, 7, 10, 3, 8, 2, 4, 8, 15, 2, 10, + 19, 18, 8, 4, 21, 9, 7, 16, 33, 7, 19, 18, 28, 8, 2, 4, 16, 14, 7, 10, 3, 8, 11, 9, 4, 10, + 6, 3, 8, 15, 7, 12, 10, 41, 4, 8, 12, 6, 7, 27, 7, 61, 8, 4, 6, 4, 13, 30, 7, 3, 10, 23, + 17, 13, 4, 8, 2, 4, 41, 23, 7, 2, 4, 15, 32, 15, 7, 10, 19, 8, 63, 2, 6, 18, 16, 8, 18, 8, + 2, 3, 4, 18, 19, 4, 2, 3, 17, 61, 8, 10, 2, 23, 18, 8, 15, 7, 2, 8, 15, 2, 10, 8, 20, 2, + 3, 9, 7, 18, 15, 7, 10, 3, 28, 8, 16, 7, 6, 23, 22, 30, 7, 10, 19, 8, 14, 4, 41, 4, 15, 4, + 9, 61, 8, 15, 32, 8, 15, 13, 4, 2, 18, 3, 10, 8, 48, 34, 35, 8, 4, 3, 8, 2, 3, 4, 18, 19, + 4, 2, 3, 18, 8, 12, 6, 7, 27, 7, 61, 8, 39, 15, 4, 16, 19, 4, 25, 13, 7, 8, 9, 7, 2, 2, + 9, 4, 30, 6, 7, 8, 13, 7, 8, 59, 8, 19, 10, 2, 29, 33, 7, 40, 61, 43, 49, 40, 8, 20, 25, 10, + 8, 13, 7, 8, 2, 23, 10, 14, 20, 22, 52, 18, 5, 8, 14, 10, 13, 17, 8, 11, 9, 18, 2, 3, 20, 11, + 7, 10, 19, 8, 6, 8, 18, 16, 41, 4, 3, 4, 15, 23, 10, 13, 18, 22, 8, 12, 6, 7, 27, 7, 61, 43, + 48, 40, 8, 14, 4, 2, 3, 7, 15, 23, 29, 10, 19, 8, 15, 7, 12, 8, 12, 6, 7, 27, 28, 8, 11, 4, + 14, 13, 18, 19, 7, 10, 19, 8, 13, 7, 8, 63, 3, 7, 25, 8, 18, 8, 20, 2, 3, 7, 13, 7, 15, 23, + 18, 15, 7, 10, 19, 8, 21, 10, 2, 11, 23, 7, 3, 13, 4, 61, 8, 20, 21, 18, 9, 7, 10, 19, 8, 18, + 8, 20, 15, 4, 16, 18, 19, 8, 16, 7, 8, 2, 4, 21, 4, 5, 8, 15, 10, 2, 17, 8, 19, 20, 2, 4, + 9, 24, 43, 70, 40, 8, 3, 4, 23, 17, 6, 4, 8, 10, 2, 23, 18, 8, 15, 7, 19, 8, 15, 2, 10, 8, + 11, 4, 13, 9, 7, 15, 18, 23, 4, 2, 17, 28, 8, 15, 32, 8, 15, 13, 4, 2, 18, 3, 10, 8, 4, 2, + 3, 7, 3, 4, 6, 8, 14, 10, 13, 10, 25, 13, 32, 68, 8, 2, 9, 10, 14, 2, 3, 15, 8, 18, 23, 18, + 8, 18, 2, 11, 4, 23, 17, 16, 20, 10, 3, 10, 8, 11, 9, 10, 14, 23, 4, 25, 10, 13, 13, 20, 22, 8, + 9, 7, 2, 2, 9, 4, 30, 6, 20, 8, 13, 7, 8, 59, 8, 19, 10, 2, 29, 33, 7, 61, 47, 8, 42, 8, + 47, 8, 42, 8, 47, 8, 42, 8, 47, 8, 42, 8, 47, 8, 42, 8, 47, 8, 42, 8, 47, 8, 42, 8, 47, 43, + 79, 8, 30, 7, 2, 3, 4, 8, 16, 7, 14, 7, 15, 7, 10, 19, 32, 10, 8, 15, 4, 11, 9, 4, 2, 32, + 51, 80, 8, 6, 7, 6, 18, 10, 8, 20, 8, 15, 7, 2, 8, 2, 9, 4, 6, 18, 8, 18, 16, 41, 4, 3, + 4, 15, 23, 10, 13, 18, 29, 81, 8, 2, 9, 4, 6, 18, 8, 18, 16, 41, 4, 3, 4, 15, 23, 10, 13, 18, + 29, 8, 2, 3, 4, 5, 6, 18, 8, 9, 10, 2, 10, 11, 12, 13, 8, 4, 3, 8, 58, 8, 14, 4, 8, 59, + 48, 8, 14, 13, 10, 5, 61, 8, 6, 8, 18, 16, 41, 4, 3, 4, 15, 23, 10, 13, 18, 22, 8, 11, 9, 4, + 14, 20, 6, 33, 18, 18, 8, 11, 9, 18, 2, 3, 20, 11, 7, 10, 19, 8, 2, 9, 7, 16, 20, 8, 11, 4, + 2, 23, 10, 8, 11, 4, 14, 11, 18, 2, 7, 13, 18, 29, 8, 14, 4, 41, 4, 15, 4, 9, 7, 8, 18, 8, + 2, 4, 41, 23, 7, 2, 4, 15, 7, 13, 18, 29, 8, 15, 2, 10, 68, 8, 14, 10, 3, 7, 23, 10, 5, 8, + 11, 9, 4, 10, 6, 3, 7, 61, 80, 8, 2, 6, 4, 23, 17, 6, 4, 8, 14, 23, 18, 3, 17, 2, 29, 8, + 41, 7, 9, 7, 13, 3, 18, 29, 81, 8, 4, 3, 8, 69, 49, 8, 19, 10, 2, 29, 33, 10, 15, 61, 80, 8, + 11, 23, 7, 3, 13, 7, 29, 8, 23, 18, 8, 20, 8, 15, 7, 2, 28, 8, 14, 4, 2, 3, 7, 15, 6, 7, + 28, 8, 11, 4, 14, 13, 29, 3, 18, 10, 28, 8, 2, 21, 4, 9, 6, 7, 81, 8, 11, 4, 14, 13, 29, 3, + 18, 10, 28, 8, 14, 4, 2, 3, 7, 15, 6, 7, 28, 8, 20, 2, 3, 7, 13, 4, 15, 6, 7, 28, 8, 20, + 25, 10, 8, 15, 6, 23, 22, 30, 10, 13, 32, 8, 15, 8, 2, 3, 4, 18, 19, 4, 2, 3, 17, 8, 15, 7, + 12, 10, 5, 8, 2, 3, 4, 5, 6, 18, 8, 9, 10, 2, 10, 11, 12, 13, 8, 82, 8, 2, 3, 4, 5, 6, + 18, 8, 7, 14, 19, 18, 13, 18, 2, 3, 9, 7, 3, 4, 9, 7, 61, 80, 8, 6, 7, 6, 20, 22, 8, 10, + 52, 10, 8, 19, 10, 21, 10, 23, 17, 8, 15, 32, 8, 14, 10, 23, 7, 10, 3, 10, 81, 8, 13, 7, 12, 18, + 8, 3, 10, 68, 13, 18, 30, 10, 2, 6, 18, 10, 8, 15, 4, 16, 19, 4, 25, 13, 4, 2, 3, 18, 8, 18, + 8, 4, 11, 32, 3, 8, 11, 4, 16, 15, 4, 23, 29, 22, 3, 8, 11, 4, 23, 13, 4, 2, 3, 17, 22, 8, + 4, 21, 20, 2, 3, 9, 4, 18, 3, 17, 8, 6, 4, 9, 11, 20, 2, 13, 4, 5, 8, 19, 10, 21, 10, 23, + 17, 22, 8, 15, 7, 12, 8, 14, 4, 19, 8, 18, 23, 18, 8, 6, 15, 7, 9, 3, 18, 9, 20, 61, 3, 7, + 6, 25, 10, 8, 13, 7, 2, 8, 30, 7, 2, 3, 4, 8, 18, 52, 20, 3, 8, 11, 4, 8, 3, 7, 6, 18, + 19, 8, 16, 7, 11, 9, 4, 2, 7, 19, 8, 6, 7, 6, 51, 8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 2, + 10, 11, 12, 13, 8, 82, 8, 2, 3, 4, 5, 6, 7, 8, 7, 14, 19, 18, 13, 18, 2, 3, 9, 7, 3, 4, + 9, 7, 8, 82, 8, 2, 3, 4, 5, 6, 7, 8, 14, 23, 29, 8, 15, 2, 3, 9, 10, 30, 18, 8, 41, 4, + 2, 3, 10, 5, 66, 4, 2, 3, 7, 23, 18, 2, 17, 8, 15, 4, 11, 9, 4, 2, 32, 81, 8, 14, 23, 29, + 8, 3, 10, 68, 8, 6, 3, 4, 8, 33, 10, 13, 18, 3, 8, 2, 15, 4, 10, 8, 15, 9, 10, 19, 29, 28, + 8, 2, 4, 4, 21, 52, 18, 3, 10, 8, 11, 9, 18, 19, 10, 9, 13, 32, 10, 8, 9, 7, 16, 19, 10, 9, + 32, 8, 19, 10, 21, 10, 23, 18, 8, 18, 8, 29, 8, 9, 7, 2, 2, 30, 18, 3, 7, 22, 8, 2, 3, 4, + 18, 19, 4, 2, 3, 17, 8, 15, 7, 12, 10, 5, 8, 19, 10, 21, 10, 23, 18, 8, 15, 8, 3, 10, 30, 10, + 13, 18, 18, 8, 50, 8, 26, 34, 8, 19, 18, 13, 20, 3, 61, 36, 8, 14, 23, 29, 8, 3, 10, 68, 28, 8, + 6, 3, 4, 8, 11, 9, 18, 13, 18, 19, 7, 10, 3, 8, 9, 10, 12, 10, 13, 18, 10, 8, 21, 32, 2, 3, + 9, 4, 28, 8, 15, 8, 11, 4, 14, 7, 9, 4, 6, 8, 50, 8, 48, 35, 8, 2, 6, 18, 14, 6, 7, 8, + 13, 7, 8, 11, 10, 9, 15, 32, 5, 8, 16, 7, 6, 7, 16, 24, 83, 8, 29, 8, 23, 18, 30, 13, 4, 8, + 21, 20, 14, 20, 8, 15, 10, 2, 3, 18, 8, 15, 7, 12, 8, 16, 7, 6, 7, 16, 28, 8, 4, 3, 8, 19, + 4, 19, 10, 13, 3, 7, 8, 16, 15, 4, 13, 6, 7, 28, 8, 14, 18, 16, 7, 5, 13, 42, 11, 9, 4, 10, + 6, 3, 7, 8, 14, 4, 8, 20, 2, 3, 7, 13, 4, 15, 6, 18, 61, 8, 23, 18, 30, 13, 4, 8, 15, 32, + 10, 16, 25, 7, 22, 8, 13, 7, 8, 16, 7, 19, 10, 9, 42, 14, 18, 16, 7, 5, 13, 28, 8, 30, 3, 4, + 21, 32, 8, 19, 7, 6, 2, 18, 19, 7, 23, 17, 13, 4, 8, 3, 4, 30, 13, 4, 8, 11, 4, 13, 29, 3, + 17, 8, 15, 7, 12, 8, 16, 7, 11, 9, 4, 2, 28, 8, 11, 4, 14, 2, 6, 7, 16, 7, 3, 17, 28, 8, + 30, 3, 4, 8, 2, 14, 10, 23, 7, 3, 17, 8, 23, 20, 30, 12, 10, 8, 15, 8, 9, 7, 19, 6, 7, 68, + 8, 15, 7, 12, 10, 41, 4, 8, 21, 22, 14, 25, 10, 3, 7, 8, 18, 8, 2, 4, 16, 14, 7, 3, 17, 8, + 14, 23, 29, 8, 15, 7, 2, 8, 18, 14, 10, 7, 23, 17, 13, 32, 5, 8, 12, 6, 7, 27, 28, 8, 6, 4, + 3, 4, 9, 32, 5, 8, 21, 20, 14, 10, 3, 8, 9, 7, 14, 4, 15, 7, 3, 17, 8, 15, 7, 2, 8, 14, + 4, 23, 41, 18, 10, 8, 41, 4, 14, 32, 61, 84, 8, 10, 2, 3, 17, 8, 15, 4, 11, 9, 4, 2, 32, 81, + 8, 16, 7, 14, 7, 5, 3, 10, 8, 18, 68, 8, 19, 13, 10, 61, 8, 10, 2, 3, 17, 8, 11, 9, 18, 19, + 10, 9, 13, 32, 5, 8, 63, 2, 6, 18, 16, 28, 8, 11, 9, 18, 2, 32, 23, 7, 5, 3, 10, 8, 10, 41, + 4, 8, 13, 7, 8, 9, 7, 2, 30, 10, 3, 61, 8, 16, 7, 11, 18, 2, 32, 15, 7, 5, 3, 10, 2, 17, + 8, 13, 7, 8, 16, 7, 19, 10, 9, 42, 14, 18, 16, 7, 5, 13, 8, 4, 3, 8, 9, 20, 6, 4, 15, 4, + 14, 18, 3, 10, 23, 29, 28, 8, 2, 8, 4, 11, 32, 3, 4, 19, 8, 9, 7, 21, 4, 3, 8, 21, 4, 23, + 10, 10, 8, 26, 48, 8, 23, 10, 3, 8, 18, 8, 30, 10, 9, 10, 16, 8, 26, 48, 42, 69, 48, 8, 14, 13, + 10, 5, 8, 11, 4, 23, 17, 16, 20, 5, 3, 10, 2, 17, 8, 20, 14, 4, 21, 13, 32, 19, 8, 18, 8, 6, + 9, 7, 2, 18, 15, 32, 19, 8, 12, 6, 7, 27, 4, 19, 61, 78, 57, 68, 7, 9, 7, 6, 3, 10, 9, 18, + 2, 3, 18, 6, 18, 51, 43, 21, 20, 6, 4, 15, 32, 5, 8, 2, 23, 63, 21, 43, 7, 9, 3, 18, 6, 20, + 23, 8, 19, 10, 21, 10, 23, 18, 51, 8, 0, 0, 69, 58, 85, 48, 43, 86, 8, 19, 7, 2, 3, 10, 9, 2, + 6, 4, 5, 8, 18, 16, 41, 4, 3, 4, 15, 23, 10, 13, 18, 29, 51, 8, 87, 85, 0, 48, 13, 70, 0, 59, + 43, 86, 8, 6, 4, 23, 23, 10, 6, 33, 18, 18, 51, 8, 0, 0, 0, 26, 70, 0, 59, 43, 13, 4, 19, 10, + 9, 8, 6, 7, 3, 7, 23, 4, 41, 7, 51, 8, 48, 69, 43, 6, 4, 9, 11, 20, 2, 13, 32, 5, 8, 18, + 16}; + +std::vector example2 = { + 3, 4, 5, 6, 7, 8, 9, 10, 2, 10, 11, 12, 13, 8, 2, 8, 41, 7, 9, 7, 13, 3, 18, 10, 5, 2, + 4, 16, 14, 7, 14, 18, 19, 8, 20, 14, 4, 21, 13, 20, 22, 8, 2, 3, 4, 5, 6, 20, 8, 9, 10, 2, + 10, 11, 12, 13, 8, 18, 14, 10, 7, 23, 17, 13, 4, 8, 11, 4, 14, 8, 15, 7, 12, 8, 14, 18, 16, 7, + 5, 13, 8, 18, 8, 11, 4, 3, 9, 10, 21, 13, 4, 2, 3, 18, 24, 20, 25, 10, 8, 26, 26, 8, 23, 10, + 3, 8, 2, 4, 16, 14, 7, 10, 19, 8, 6, 4, 27, 19, 4, 27, 9, 3, 8, 18, 8, 20, 22, 3, 28, 8, + 14, 10, 23, 7, 29, 8, 6, 7, 30, 10, 2, 3, 15, 10, 13, 13, 20, 22, 8, 18, 8, 6, 9, 7, 2, 18, + 15, 20, 22, 8, 19, 10, 21, 10, 23, 17, 8, 14, 23, 29, 8, 4, 27, 18, 2, 4, 15, 8, 18, 8, 21, 18, + 16, 13, 10, 2, 7, 31, 8, 21, 10, 2, 11, 23, 7, 3, 13, 32, 5, 8, 15, 32, 10, 16, 14, 8, 14, 18, + 16, 7, 5, 13, 10, 9, 7, 8, 2, 8, 4, 21, 9, 7, 16, 33, 7, 19, 18, 28, 8, 26, 34, 34, 35, 8, + 11, 4, 14, 21, 10, 9, 10, 19, 8, 18, 14, 10, 7, 23, 17, 13, 32, 10, 8, 19, 7, 3, 10, 9, 18, 7, + 23, 32, 8, 18, 8, 6, 4, 19, 11, 23, 10, 6, 3, 36, 8, 11, 4, 14, 7, 9, 4, 6, 8, 15, 2, 10, + 19, 28, 8, 6, 3, 4, 8, 11, 9, 18, 12, 10, 23, 8, 2, 8, 7, 15, 18, 3, 4, 8, 15, 8, 6, 4, + 13, 33, 10, 8, 4, 21, 37, 29, 15, 23, 10, 13, 18, 29, 24, 38, 8, 9, 7, 2, 2, 9, 4, 30, 6, 7, + 8, 21, 10, 16, 8, 11, 10, 9, 10, 11, 23, 7, 3, 8, 4, 3, 8, 3, 18, 13, 17, 6, 4, 27, 27, 8, + 39, 34, 35, 40, 8, 18, 8, 41, 7, 9, 7, 13, 3, 18, 29, 8, 14, 4, 8, 26, 34, 8, 23, 10, 3, 24, + 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, 43, 44, 8, 16, 15, 4, 13, 18, 3, 10, 8, 18, 23, + 18, 8, 11, 18, 12, 18, 3, 10, 8, 15, 8, 30, 7, 3, 8, 2, 23, 4, 15, 4, 8, 45, 12, 6, 7, 27, + 45, 8, 18, 8, 19, 32, 8, 4, 3, 11, 9, 7, 15, 18, 19, 8, 15, 7, 19, 8, 13, 7, 12, 18, 8, 9, + 7, 21, 4, 3, 32, 8, 18, 8, 15, 7, 9, 18, 7, 13, 3, 32, 8, 6, 4, 19, 11, 23, 10, 6, 3, 7, + 33, 18, 5, 24, 43, 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, 67, 57, 8, 26, 34, 8, 41, 23, + 7, 15, 13, 32, 68, 8, 11, 9, 18, 30, 18, 13, 8, 16, 7, 6, 7, 16, 7, 3, 17, 8, 19, 10, 21, 10, + 23, 17, 8, 20, 8, 13, 7, 2, 51, 26, 40, 8, 6, 9, 4, 19, 23, 10, 13, 18, 10, 8, 14, 10, 3, 7, + 23, 10, 5, 8, 11, 4, 8, 15, 2, 10, 19, 20, 8, 11, 10, 9, 18, 19, 10, 3, 9, 20, 43, 69, 40, 8, + 20, 14, 7, 9, 4, 2, 3, 4, 5, 6, 7, 29, 8, 11, 23, 7, 2, 3, 18, 6, 4, 15, 7, 29, 8, 6, + 9, 4, 19, 6, 7, 8, 50, 8, 26, 8, 19, 19, 61, 43, 59, 40, 8, 63, 6, 4, 23, 4, 41, 18, 30, 10, + 13, 4, 10, 8, 18, 8, 11, 23, 4, 3, 13, 4, 10, 8, 23, 14, 2, 11, 61, 43, 49, 40, 8, 20, 2, 18, + 23, 10, 13, 13, 32, 5, 8, 14, 15, 10, 9, 13, 4, 5, 8, 11, 9, 4, 27, 18, 23, 17, 8, 14, 23, 29, + 8, 12, 6, 7, 27, 4, 15, 42, 6, 20, 11, 10, 61, 43, 48, 40, 8, 11, 9, 18, 2, 7, 14, 6, 7, 8, + 13, 7, 8, 63, 6, 2, 33, 10, 13, 3, 9, 18, 6, 18, 8, 2, 8, 15, 18, 14, 18, 19, 32, 68, 8, 2, + 3, 4, 9, 4, 13, 61, 43, 70, 40, 8, 16, 7, 52, 18, 3, 7, 8, 20, 41, 23, 4, 15, 8, 11, 9, 18, + 8, 14, 4, 2, 3, 7, 15, 6, 10, 43, 58, 40, 8, 21, 10, 16, 4, 11, 7, 2, 13, 32, 10, 8, 2, 3, + 10, 6, 23, 7, 8, 18, 8, 16, 10, 9, 6, 7, 23, 7, 43, 65, 40, 8, 9, 4, 15, 13, 32, 10, 8, 16, + 10, 9, 6, 7, 23, 7, 8, 21, 10, 16, 8, 18, 2, 6, 7, 25, 10, 13, 18, 5, 43, 71, 40, 8, 13, 7, + 14, 10, 25, 13, 7, 29, 8, 27, 20, 9, 13, 18, 3, 20, 9, 7, 8, 2, 9, 10, 14, 13, 10, 41, 4, 8, + 18, 8, 11, 9, 10, 19, 18, 20, 19, 8, 2, 10, 41, 19, 10, 13, 3, 7, 43, 26, 34, 40, 8, 6, 9, 10, + 11, 23, 10, 13, 18, 29, 8, 11, 4, 15, 32, 12, 10, 13, 13, 4, 5, 8, 11, 9, 4, 30, 13, 4, 2, 3, + 18, 72, 8, 14, 4, 2, 3, 20, 11, 13, 7, 29, 8, 33, 10, 13, 7, 8, 31, 8, 15, 2, 10, 8, 15, 7, + 25, 13, 32, 10, 8, 13, 22, 7, 13, 2, 32, 28, 8, 19, 7, 6, 2, 18, 19, 7, 23, 17, 13, 4, 10, 8, + 6, 7, 30, 10, 2, 3, 15, 4, 8, 13, 7, 8, 2, 7, 19, 32, 68, 8, 15, 32, 41, 4, 14, 13, 32, 68, + 8, 20, 2, 23, 4, 15, 18, 29, 68, 24, 8, 44, 4, 2, 3, 7, 15, 23, 29, 5, 3, 10, 8, 16, 7, 29, + 15, 6, 20, 8, 13, 7, 8, 9, 7, 2, 30, 10, 3, 8, 15, 7, 12, 10, 5, 8, 21, 20, 14, 20, 52, 10, + 5, 8, 19, 10, 21, 10, 23, 18, 28, 8, 19, 32, 8, 11, 4, 14, 21, 10, 9, 10, 19, 8, 14, 23, 29, 8, + 15, 7, 2, 8, 13, 7, 14, 10, 25, 13, 20, 22, 8, 27, 20, 9, 13, 18, 3, 20, 9, 20, 28, 8, 20, 30, + 18, 3, 32, 15, 7, 22, 52, 20, 22, 8, 15, 7, 12, 18, 8, 11, 4, 25, 10, 23, 7, 13, 18, 29, 8, 18, + 8, 15, 7, 12, 8, 14, 10, 13, 10, 25, 13, 32, 5, 8, 21, 22, 14, 25, 10, 3, 61, 42, 8, 42, 8, 42, + 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, + 8, 42, 8, 42, 8, 42, 8, 42, 73, 8, 6, 4, 13, 2, 20, 23, 17, 3, 7, 33, 18, 29, 8, 11, 4, 8, + 14, 18, 16, 7, 5, 13, 20, 8, 18, 8, 19, 7, 3, 10, 9, 18, 7, 23, 7, 19, 51, 8, 11, 4, 14, 21, + 10, 9, 10, 19, 8, 19, 7, 3, 10, 9, 18, 7, 23, 28, 8, 33, 15, 10, 3, 28, 8, 3, 18, 11, 28, 8, + 27, 9, 10, 16, 10, 9, 4, 15, 6, 20, 8, 27, 7, 2, 7, 14, 4, 15, 28, 8, 15, 13, 20, 3, 9, 10, + 13, 13, 18, 10, 8, 19, 10, 68, 7, 13, 18, 16, 19, 32, 8, 18, 8, 9, 7, 2, 11, 4, 23, 4, 25, 10, + 13, 18, 10, 8, 11, 4, 23, 4, 6, 8, 31, 8, 14, 4, 11, 4, 23, 13, 18, 3, 10, 23, 17, 13, 20, 22, + 8, 27, 20, 9, 13, 18, 3, 20, 9, 20, 51, 8, 9, 20, 30, 6, 18, 28, 8, 11, 4, 14, 2, 15, 10, 3, + 6, 7, 8, 18, 8, 11, 9, 61, 74, 8, 4, 41, 9, 4, 19, 13, 32, 5, 8, 15, 32, 21, 4, 9, 8, 14, + 10, 6, 4, 9, 4, 15, 8, 39, 21, 4, 23, 10, 10, 8, 26, 58, 34, 34, 8, 33, 15, 10, 3, 4, 15, 40, + 28, 8, 19, 10, 68, 7, 13, 18, 16, 19, 4, 15, 28, 8, 19, 7, 3, 10, 9, 18, 7, 23, 4, 15, 8, 4, + 3, 8, 2, 9, 10, 14, 13, 10, 41, 4, 8, 14, 4, 8, 11, 9, 10, 19, 18, 20, 19, 8, 6, 23, 7, 2, + 2, 7, 61, 8, 11, 4, 19, 4, 25, 10, 19, 8, 15, 32, 21, 9, 7, 3, 17, 8, 11, 4, 14, 8, 23, 22, + 21, 4, 5, 8, 21, 22, 14, 25, 10, 3, 8, 18, 8, 18, 13, 3, 10, 9, 17, 10, 9, 61, 75, 8, 6, 4, + 13, 3, 9, 4, 23, 17, 8, 6, 7, 30, 10, 2, 3, 15, 7, 8, 13, 7, 8, 6, 7, 25, 14, 4, 19, 8, + 63, 3, 7, 11, 10, 8, 4, 3, 8, 21, 10, 2, 11, 23, 7, 3, 13, 4, 41, 4, 8, 14, 18, 16, 7, 5, + 13, 42, 11, 9, 4, 10, 6, 3, 7, 8, 14, 4, 8, 20, 2, 3, 7, 13, 4, 15, 6, 18, 8, 41, 4, 3, + 4, 15, 4, 41, 4, 8, 18, 16, 14, 10, 23, 18, 29, 61, 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, + 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, + 42, 76, 8, 14, 23, 29, 8, 15, 7, 2, 8, 19, 32, 8, 2, 4, 16, 14, 7, 14, 18, 19, 8, 18, 14, 10, + 7, 23, 17, 13, 32, 5, 8, 12, 6, 7, 27, 8, 18, 23, 18, 8, 6, 20, 68, 13, 22, 28, 8, 30, 3, 4, + 21, 32, 43, 77, 8, 15, 8, 13, 18, 68, 8, 20, 19, 10, 2, 3, 18, 23, 4, 2, 17, 8, 6, 7, 6, 8, + 19, 4, 25, 13, 4, 8, 21, 4, 23, 17, 12, 10, 8, 15, 10, 52, 10, 5, 28, 8, 18, 8, 15, 2, 10, 8, + 21, 32, 23, 4, 8, 45, 13, 7, 8, 2, 15, 4, 10, 19, 8, 19, 10, 2, 3, 10, 45, 43, 77, 8, 27, 20, + 9, 13, 18, 3, 20, 9, 7, 8, 9, 7, 14, 4, 15, 7, 23, 7, 8, 14, 4, 23, 41, 18, 10, 8, 41, 4, + 14, 32, 8, 2, 15, 4, 10, 5, 8, 13, 7, 14, 10, 25, 13, 4, 2, 3, 17, 22, 61, 8, 19, 32, 8, 14, + 7, 10, 19, 8, 2, 4, 21, 2, 3, 15, 10, 13, 13, 20, 22, 8, 41, 7, 9, 7, 13, 3, 18, 22, 8, 13, + 7, 8, 27, 20, 9, 13, 18, 3, 20, 9, 20, 8, 50, 8, 4, 3, 8, 48, 8, 23, 10, 3, 61, 42, 8, 42, + 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, + 8, 42, 8, 42, 8, 42, 8, 42, 8, 42, 78, 57, 8, 70, 8, 12, 7, 41, 4, 15, 8, 14, 4, 8, 15, 7, + 12, 10, 41, 4, 8, 20, 14, 4, 21, 13, 4, 41, 4, 8, 12, 6, 7, 27, 7, 8, 18, 23, 18, 8, 6, 20, + 68, 13, 18, 61, 26, 40, 8, 16, 15, 4, 13, 18, 3, 10, 8, 13, 7, 19, 28, 8, 4, 2, 3, 7, 15, 23, + 29, 10, 3, 10, 8, 11, 9, 18, 19, 10, 9, 13, 32, 10, 8, 9, 7, 16, 19, 10, 9, 32, 8, 12, 6, 7, + 27, 7, 8, 18, 8, 14, 4, 11, 61, 8, 11, 4, 25, 10, 23, 7, 13, 18, 29, 8, 4, 8, 6, 7, 30, 10, + 2, 3, 15, 10, 8, 19, 7, 3, 10, 9, 18, 7, 23, 4, 15, 28, 8, 27, 20, 9, 13, 18, 3, 20, 9, 10, + 28, 8, 27, 7, 2, 7, 14, 4, 15, 8, 18, 8, 3, 61, 14, 61, 43, 69, 40, 8, 19, 32, 8, 14, 10, 23, + 7, 10, 19, 8, 11, 9, 10, 14, 15, 7, 9, 18, 3, 10, 23, 17, 13, 32, 5, 8, 9, 7, 2, 30, 10, 3, + 61, 8, 10, 2, 23, 18, 8, 33, 10, 13, 7, 8, 15, 7, 2, 8, 20, 2, 3, 9, 7, 18, 15, 7, 10, 3, + 28, 8, 2, 4, 41, 23, 7, 2, 4, 15, 32, 15, 7, 10, 19, 8, 15, 9, 10, 19, 29, 8, 14, 23, 29, 8, + 16, 7, 19, 10, 9, 42, 14, 18, 16, 7, 5, 13, 7, 61, 43, 59, 40, 8, 14, 18, 16, 7, 5, 13, 10, 9, + 8, 16, 7, 19, 10, 9, 52, 18, 6, 8, 11, 9, 18, 10, 16, 25, 7, 10, 3, 8, 2, 4, 8, 15, 2, 10, + 19, 18, 8, 4, 21, 9, 7, 16, 33, 7, 19, 18, 28, 8, 2, 4, 16, 14, 7, 10, 3, 8, 11, 9, 4, 10, + 6, 3, 8, 15, 7, 12, 10, 41, 4, 8, 12, 6, 7, 27, 7, 61, 8, 4, 6, 4, 13, 30, 7, 3, 10, 23, + 17, 13, 4, 8, 2, 4, 41, 23, 7, 2, 4, 15, 32, 15, 7, 10, 19, 8, 63, 2, 6, 18, 16, 8, 18, 8, + 2, 3, 4, 18, 19, 4, 2, 3, 17, 61, 8, 10, 2, 23, 18, 8, 15, 7, 2, 8, 15, 2, 10, 8, 20, 2, + 3, 9, 7, 18, 15, 7, 10, 3, 28, 8, 16, 7, 6, 23, 22, 30, 7, 10, 19, 8, 14, 4, 41, 4, 15, 4, + 9, 61, 8, 15, 32, 8, 15, 13, 4, 2, 18, 3, 10, 8, 48, 34, 35, 8, 4, 3, 8, 2, 3, 4, 18, 19, + 4, 2, 3, 18, 8, 12, 6, 7, 27, 7, 61, 8, 39, 15, 4, 16, 19, 4, 25, 13, 7, 8, 9, 7, 2, 2, + 9, 4, 30, 6, 7, 8, 13, 7, 8, 59, 8, 19, 10, 2, 29, 33, 7, 40, 61, 43, 49, 40, 8, 20, 25, 10, + 8, 13, 7, 8, 2, 23, 10, 14, 20, 22, 52, 18, 5, 8, 14, 10, 13, 17, 8, 11, 9, 18, 2, 3, 20, 11, + 7, 10, 19, 8, 6, 8, 18, 16, 41, 4, 3, 4, 15, 23, 10, 13, 18, 22, 8, 12, 6, 7, 27, 7, 61, 43, + 48, 40, 8, 14, 4, 2, 3, 7, 15, 23, 29, 10, 19, 8, 15, 7, 12, 8, 12, 6, 7, 27, 28, 8, 11, 4, + 14, 13, 18, 19, 7, 10, 19, 8, 13, 7, 8, 63, 3, 7, 25, 8, 18, 8, 20, 2, 3, 7, 13, 7, 15, 23, + 18, 15, 7, 10, 19, 8, 21, 10, 2, 11, 23, 7, 3, 13, 4, 61, 8, 20, 21, 18, 9, 7, 10, 19, 8, 18, + 8, 20, 15, 4, 16, 18, 19, 8, 16, 7, 8, 2, 4, 21, 4, 5, 8, 15, 10, 2, 17, 8, 19, 20, 2, 4, + 9, 24, 43, 70, 40, 8, 3, 4, 23, 17, 6, 4, 8, 10, 2, 23, 18, 8, 15, 7, 19, 8, 15, 2, 10, 8, + 11, 4, 13, 9, 7, 15, 18, 23, 4, 2, 17, 28, 8, 15, 32, 8, 15, 13, 4, 2, 18, 3, 10, 8, 4, 2, + 3, 7, 3, 4, 6, 8, 14, 10, 13, 10, 25, 13, 32, 68, 8, 2, 9, 10, 14, 2, 3, 15, 8, 18, 23, 18, + 8, 18, 2, 11, 4, 23, 17, 16, 20, 10, 3, 10, 8, 11, 9, 10, 14, 23, 4, 25, 10, 13, 13, 20, 22, 8, + 9, 7, 2, 2, 9, 4, 30, 6, 20, 8, 13, 7, 8, 59, 8, 19, 10, 2, 29, 33, 7, 61, 47, 8, 42, 8, + 47, 8, 42, 8, 47, 8, 42, 8, 47, 8, 42, 8, 47, 8, 42, 8, 47, 8, 42, 8, 47, 8, 42, 8, 47, 43, + 46, 8, 19, 32, 8, 13, 10, 8, 11, 4, 2, 9, 10, 14, 13, 18, 6, 18, 28, 8, 20, 8, 13, 7, 2, 8, + 2, 4, 21, 2, 3, 15, 10, 13, 13, 4, 10, 8, 11, 9, 4, 18, 16, 15, 4, 14, 2, 3, 15, 4, 8, 11, + 4, 63, 3, 4, 19, 20, 8, 33, 10, 13, 7, 8, 16, 7, 8, 2, 3, 4, 5, 6, 18, 8, 9, 10, 2, 10, + 11, 12, 13, 8, 13, 18, 25, 10, 28, 8, 30, 10, 19, 8, 20, 8, 6, 4, 13, 6, 20, 9, 10, 13, 3, 4, + 15, 8, 13, 7, 8, 26, 48, 42, 49, 34, 35, 28, 8, 7, 8, 6, 7, 30, 10, 2, 3, 15, 4, 8, 50, 8, + 13, 7, 8, 20, 9, 4, 15, 10, 13, 17, 8, 15, 32, 12, 10, 24, 16, 7, 6, 7, 16, 32, 15, 7, 29, 8, + 19, 10, 21, 10, 23, 17, 8, 20, 8, 13, 7, 2, 8, 15, 32, 8, 11, 4, 23, 20, 30, 7, 10, 3, 10, 51, + 1, 57, 8, 15, 32, 10, 16, 14, 8, 16, 7, 19, 10, 9, 52, 18, 6, 7, 8, 2, 8, 4, 21, 9, 7, 16, + 33, 7, 19, 18, 1, 57, 8, 11, 9, 4, 27, 10, 2, 2, 18, 4, 13, 7, 23, 17, 13, 32, 5, 8, 16, 7, + 19, 10, 9, 8, 18, 8, 14, 18, 16, 7, 5, 13, 8, 11, 9, 4, 10, 6, 3, 1, 1, 57, 8, 41, 7, 9, + 7, 13, 3, 18, 29, 8, 13, 7, 8, 15, 2, 53, 8, 42, 8, 48, 8, 23, 10, 3, 1, 54, 8, 20, 6, 4, + 19, 11, 23, 10, 6, 3, 20, 10, 19, 8, 19, 10, 21, 10, 23, 17, 22, 8, 15, 7, 12, 20, 8, 6, 15, 7, + 9, 3, 18, 9, 20, 8, 18, 23, 18, 8, 14, 4, 19, 8, 42, 8, 11, 4, 8, 4, 11, 3, 4, 15, 32, 19, + 8, 20, 2, 23, 4, 15, 18, 29, 19, 8, 2, 8, 21, 4, 13, 20, 2, 7, 19, 18, 8, 18, 8, 2, 6, 18, + 14, 6, 7, 19, 18, 55, 8, 9, 7, 21, 4, 3, 7, 10, 19, 8, 11, 4, 8, 14, 4, 41, 4, 15, 4, 9, + 20, 8, 56, 57, 8, 18, 16, 41, 4, 3, 4, 15, 23, 10, 13, 18, 10, 8, 4, 3, 8, 58, 42, 59, 48, 8, + 14, 13, 10, 5, 61, 8, 16, 7, 15, 18, 2, 18, 3, 8, 4, 3, 8, 2, 23, 4, 25, 13, 4, 2, 3, 18, + 8, 18, 16, 14, 10, 23, 18, 29, 8, 18, 8, 4, 25, 18, 14, 7, 13, 18, 29, 8, 16, 7, 6, 7, 16, 32, + 15, 7, 10, 19, 32, 68, 8, 19, 7, 3, 10, 9, 18, 7, 23, 4, 15, 1, 1, 57, 8, 19, 10, 21, 10, 23, + 17, 8, 11, 4, 14, 8, 15, 7, 12, 18, 8, 18, 13, 14, 18, 15, 18, 14, 20, 7, 23, 17, 13, 32, 10, 8, + 9, 7, 16, 19, 10, 9, 32, 61, 8, 20, 8, 13, 7, 2, 8, 13, 10, 3, 8, 2, 3, 7, 13, 14, 7, 9, + 3, 13, 32, 68, 8, 9, 7, 16, 19, 10, 9, 4, 15, 8, 18, 8, 11, 10, 9, 10, 11, 23, 7, 3, 8, 16, + 7, 8, 13, 10, 8, 2, 3, 7, 13, 14, 7, 9, 3, 1, 1, 57, 8, 11, 9, 18, 8, 2, 4, 2, 3, 7, + 15, 23, 10, 13, 18, 18, 8, 11, 9, 4, 10, 6, 3, 7, 8, 19, 32, 8, 2, 3, 7, 9, 7, 10, 19, 2, + 29, 8, 20, 30, 10, 2, 3, 17, 8, 6, 7, 25, 14, 32, 5, 8, 19, 19, 8, 18, 8, 16, 7, 14, 10, 5, + 2, 3, 15, 4, 15, 7, 3, 17, 8, 10, 41, 4, 8, 2, 8, 19, 7, 6, 2, 18, 19, 7, 23, 17, 13, 4, + 5, 8, 11, 4, 23, 17, 16, 4, 5, 28, 8, 7, 8, 4, 11, 32, 3, 8, 13, 7, 12, 18, 68, 8, 14, 18, + 16, 7, 5, 13, 10, 9, 4, 15, 8, 11, 4, 16, 15, 4, 23, 29, 10, 3, 8, 63, 3, 4, 8, 2, 14, 10, + 23, 7, 3, 17, 8, 13, 7, 18, 23, 20, 30, 12, 18, 19, 8, 4, 21, 9, 7, 16, 4, 19, 1, 72, 8, 2, + 3, 9, 4, 41, 4, 10, 8, 2, 4, 21, 23, 22, 14, 10, 13, 18, 10, 8, 15, 2, 10, 68, 8, 2, 9, 4, + 6, 4, 15, 8, 18, 8, 4, 21, 29, 16, 7, 3, 10, 23, 17, 2, 3, 15, 8, 15, 8, 14, 4, 41, 4, 15, + 4, 9, 10, 61, 8, 19, 32, 8, 4, 2, 4, 21, 10, 13, 13, 4, 8, 33, 10, 13, 18, 19, 8, 15, 7, 12, + 10, 8, 15, 9, 10, 19, 29, 61, 60, 8, 21, 10, 2, 11, 23, 7, 3, 13, 7, 29, 8, 14, 4, 2, 3, 7, + 15, 6, 7, 8, 15, 7, 12, 10, 5, 8, 19, 10, 21, 10, 23, 18, 61, 62, 57, 8, 11, 4, 14, 13, 18, 19, + 7, 10, 19, 8, 19, 10, 21, 10, 23, 17, 8, 13, 7, 8, 23, 22, 21, 4, 5, 8, 63, 3, 7, 25, 8, 18, + 8, 15, 8, 23, 22, 21, 20, 22, 8, 3, 4, 30, 6, 20, 8, 6, 15, 7, 9, 3, 18, 9, 32, 8, 42, 8, + 21, 10, 2, 11, 23, 7, 3, 13, 4, 1, 57, 8, 2, 4, 21, 10, 9, 10, 19, 8, 19, 10, 21, 10, 23, 17, + 8, 21, 10, 16, 8, 14, 4, 11, 23, 7, 3, 1, 1, 57, 8, 2, 15, 4, 10, 8, 11, 9, 4, 18, 16, 15, + 4, 14, 2, 3, 15, 4, 8, 19, 10, 21, 10, 23, 18, 28, 8, 2, 15, 4, 29, 8, 14, 4, 2, 3, 7, 15, + 6, 7, 28, 8, 2, 15, 4, 29, 8, 20, 2, 3, 7, 13, 4, 15, 6, 7, 1, 1, 57, 8, 20, 21, 18, 9, + 7, 10, 19, 8, 16, 7, 8, 2, 4, 21, 4, 5, 8, 19, 20, 2, 4, 9, 8, 11, 4, 2, 23, 10, 8, 19, + 4, 13, 3, 7, 25, 7, 1, 1, 57, 8, 11, 9, 18, 8, 11, 9, 4, 18, 16, 15, 4, 14, 2, 3, 15, 10, + 8, 19, 32, 8, 18, 2, 11, 4, 23, 17, 16, 20, 10, 19, 8, 18, 2, 6, 23, 22, 30, 18, 3, 10, 23, 17, + 13, 4, 8, 63, 6, 4, 23, 4, 41, 18, 30, 10, 2, 6, 18, 8, 30, 18, 2, 3, 32, 10, 8, 18, 8, 41, + 18, 11, 4, 7, 23, 23, 10, 9, 41, 10, 13, 13, 32, 10, 8, 19, 7, 3, 10, 9, 18, 7, 23, 32, 8, 42, + 8, 19, 32, 8, 16, 7, 21, 4, 3, 18, 19, 2, 29, 8, 4, 8, 15, 7, 2, 8, 18, 8, 4, 8, 16, 14, + 4, 9, 4, 15, 17, 10, 8, 15, 7, 12, 18, 68, 8, 21, 23, 18, 16, 6, 18, 68, 24, 64, 57, 8, 19, 32, + 8, 23, 22, 21, 18, 19, 8, 2, 15, 4, 53, 8, 14, 10, 23, 4, 8, 18, 8, 14, 4, 9, 4, 25, 18, 19, + 8, 9, 10, 11, 20, 3, 7, 33, 18, 10, 5, 61, 8, 65, 34, 34, 31, 8, 2, 3, 4, 10, 6, 8, 20, 25, + 10, 8, 20, 2, 3, 7, 13, 4, 15, 23, 10, 13, 32, 8, 13, 7, 8, 2, 4, 15, 10, 2, 3, 17, 8, 18, + 8, 9, 7, 14, 20, 22, 3, 8, 25, 18, 23, 17, 33, 4, 15, 24, 8, 47, 8, 42, 8, 47, 8, 42, 8, 47, + 8, 42, 8, 47, 8, 42, 8, 47, 66, 8, 14, 4, 21, 7, 15, 17, 3, 10, 8, 4, 21, 37, 29, 15, 23, 10, + 13, 18, 10, 8, 15, 8, 18, 16, 21, 9, 7, 13, 13, 4, 10, 28, 8, 30, 3, 4, 21, 8, 13, 10, 8, 11, + 4, 3, 10, 9, 29, 3, 17, 8, 15, 32, 41, 4, 14, 13, 4, 10, 8, 11, 9, 10, 14, 23, 4, 25, 10, 13, + 18, 10, 8, 18, 8, 13, 10, 8, 11, 9, 4, 11, 20, 2, 3, 18, 3, 17, 8, 7, 6, 33, 18, 18, 47, 8, + 42, 8, 47, 8, 42, 8, 47, 8, 42, 8, 47, 8, 42, 8, 47, 79, 8, 30, 7, 2, 3, 4, 8, 16, 7, 14, + 7, 15, 7, 10, 19, 32, 10, 8, 15, 4, 11, 9, 4, 2, 32, 51, 80, 8, 6, 7, 6, 18, 10, 8, 20, 8, + 15, 7, 2, 8, 2, 9, 4, 6, 18, 8, 18, 16, 41, 4, 3, 4, 15, 23, 10, 13, 18, 29, 81, 8, 2, 9, + 4, 6, 18, 8, 18, 16, 41, 4, 3, 4, 15, 23, 10, 13, 18, 29, 8, 2, 3, 4, 5, 6, 18, 8, 9, 10, + 2, 10, 11, 12, 13, 8, 4, 3, 8, 58, 8, 14, 4, 8, 59, 48, 8, 14, 13, 10, 5, 61, 8, 6, 8, 18, + 16, 41, 4, 3, 4, 15, 23, 10, 13, 18, 22, 8, 11, 9, 4, 14, 20, 6, 33, 18, 18, 8, 11, 9, 18, 2, + 3, 20, 11, 7, 10, 19, 8, 2, 9, 7, 16, 20, 8, 11, 4, 2, 23, 10, 8, 11, 4, 14, 11, 18, 2, 7, + 13, 18, 29, 8, 14, 4, 41, 4, 15, 4, 9, 7, 8, 18, 8, 2, 4, 41, 23, 7, 2, 4, 15, 7, 13, 18, + 29, 8, 15, 2, 10, 68, 8, 14, 10, 3, 7, 23, 10, 5, 8, 11, 9, 4, 10, 6, 3, 7, 61, 80, 8, 2, + 6, 4, 23, 17, 6, 4, 8, 14, 23, 18, 3, 17, 2, 29, 8, 41, 7, 9, 7, 13, 3, 18, 29, 81, 8, 4, + 3, 8, 69, 49, 8, 19, 10, 2, 29, 33, 10, 15, 61, 80, 8, 11, 23, 7, 3, 13, 7, 29, 8, 23, 18, 8, + 20, 8, 15, 7, 2, 28, 8, 14, 4, 2, 3, 7, 15, 6, 7, 28, 8, 11, 4, 14, 13, 29, 3, 18, 10, 28, + 8, 2, 21, 4, 9, 6, 7, 81, 8, 11, 4, 14, 13, 29, 3, 18, 10, 28, 8, 14, 4, 2, 3, 7, 15, 6, + 7, 28, 8, 20, 2, 3, 7, 13, 4, 15, 6, 7, 28, 8, 20, 25, 10, 8, 15, 6, 23, 22, 30, 10, 13, 32, + 8, 15, 8, 2, 3, 4, 18, 19, 4, 2, 3, 17, 8, 15, 7, 12, 10, 5, 8, 2, 3, 4, 5, 6, 18, 8, + 9, 10, 2, 10, 11, 12, 13, 8, 82, 8, 2, 3, 4, 5, 6, 18, 8, 7, 14, 19, 18, 13, 18, 2, 3, 9, + 7, 3, 4, 9, 7, 61, 80, 8, 6, 7, 6, 20, 22, 8, 10, 52, 10, 8, 19, 10, 21, 10, 23, 17, 8, 15, + 32, 8, 14, 10, 23, 7, 10, 3, 10, 81, 8, 13, 7, 12, 18, 8, 3, 10, 68, 13, 18, 30, 10, 2, 6, 18, + 10, 8, 15, 4, 16, 19, 4, 25, 13, 4, 2, 3, 18, 8, 18, 8, 4, 11, 32, 3, 8, 11, 4, 16, 15, 4, + 23, 29, 22, 3, 8, 11, 4, 23, 13, 4, 2, 3, 17, 22, 8, 4, 21, 20, 2, 3, 9, 4, 18, 3, 17, 8, + 6, 4, 9, 11, 20, 2, 13, 4, 5, 8, 19, 10, 21, 10, 23, 17, 22, 8, 15, 7, 12, 8, 14, 4, 19, 8, + 18, 23, 18, 8, 6, 15, 7, 9, 3, 18, 9, 20, 61, 3, 7, 6, 25, 10, 8, 13, 7, 2, 8, 30, 7, 2, + 3, 4, 8, 18, 52, 20, 3, 8, 11, 4, 8, 3, 7, 6, 18, 19, 8, 16, 7, 11, 9, 4, 2, 7, 19, 8, + 6, 7, 6, 51, 8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 2, 10, 11, 12, 13, 8, 82, 8, 2, 3, 4, 5, + 6, 7, 8, 7, 14, 19, 18, 13, 18, 2, 3, 9, 7, 3, 4, 9, 7, 8, 82, 8, 2, 3, 4, 5, 6, 7, + 8, 14, 23, 29, 8, 15, 2, 3, 9, 10, 30, 18, 8, 41, 4, 2, 3, 10, 5, 66, 4, 2, 3, 7, 23, 18, + 2, 17, 8, 15, 4, 11, 9, 4, 2, 32, 81, 8, 14, 23, 29, 8, 3, 10, 68, 8, 6, 3, 4, 8, 33, 10, + 13, 18, 3, 8, 2, 15, 4, 10, 8, 15, 9, 10, 19, 29, 28, 8, 2, 4, 4, 21, 52, 18, 3, 10, 8, 11, + 9, 18, 19, 10, 9, 13, 32, 10, 8, 9, 7, 16, 19, 10, 9, 32, 8, 19, 10, 21, 10, 23, 18, 8, 18, 8, + 29, 8, 9, 7, 2, 2, 30, 18, 3, 7, 22, 8, 2, 3, 4, 18, 19, 4, 2, 3, 17, 8, 15, 7, 12, 10, + 5, 8, 19, 10, 21, 10, 23, 18, 8, 15, 8, 3, 10, 30, 10, 13, 18, 18, 8, 50, 8, 26, 34, 8, 19, 18, + 13, 20, 3, 61, 36, 8, 14, 23, 29, 8, 3, 10, 68, 28, 8, 6, 3, 4, 8, 11, 9, 18, 13, 18, 19, 7, + 10, 3, 8, 9, 10, 12, 10, 13, 18, 10, 8, 21, 32, 2, 3, 9, 4, 28, 8, 15, 8, 11, 4, 14, 7, 9, + 4, 6, 8, 50, 8, 48, 35, 8, 2, 6, 18, 14, 6, 7, 8, 13, 7, 8, 11, 10, 9, 15, 32, 5, 8, 16, + 7, 6, 7, 16, 24, 83, 8, 29, 8, 23, 18, 30, 13, 4, 8, 21, 20, 14, 20, 8, 15, 10, 2, 3, 18, 8, + 15, 7, 12, 8, 16, 7, 6, 7, 16, 28, 8, 4, 3, 8, 19, 4, 19, 10, 13, 3, 7, 8, 16, 15, 4, 13, + 6, 7, 28, 8, 14, 18, 16, 7, 5, 13, 42, 11, 9, 4, 10, 6, 3, 7, 8, 14, 4, 8, 20, 2, 3, 7, + 13, 4, 15, 6, 18, 61, 8, 30, 3, 4, 21, 32, 8, 2, 4, 16, 14, 7, 3, 17, 8, 14, 23, 29, 8, 15, + 7, 2, 8, 18, 14, 10, 7, 23, 17, 13, 32, 5, 8, 12, 6, 7, 27, 28, 8, 6, 4, 3, 4, 9, 32, 5, + 8, 21, 20, 14, 10, 3, 8, 9, 7, 14, 4, 15, 7, 3, 17, 8, 15, 7, 2, 8, 14, 4, 23, 41, 18, 10, + 8, 41, 4, 14, 32, 61, 84, 8, 10, 2, 3, 17, 8, 15, 4, 11, 9, 4, 2, 32, 81, 8, 16, 7, 14, 7, + 5, 3, 10, 8, 18, 68, 8, 19, 13, 10, 61, 8, 10, 2, 3, 17, 8, 11, 9, 18, 19, 10, 9, 13, 32, 5, + 8, 63, 2, 6, 18, 16, 28, 8, 11, 9, 18, 2, 32, 23, 7, 5, 3, 10, 8, 10, 41, 4, 8, 13, 7, 8, + 9, 7, 2, 30, 10, 3, 61, 8, 16, 7, 11, 18, 2, 32, 15, 7, 5, 3, 10, 2, 17, 8, 13, 7, 8, 16, + 7, 19, 10, 9, 42, 14, 18, 16, 7, 5, 13, 8, 4, 3, 8, 9, 20, 6, 4, 15, 4, 14, 18, 3, 10, 23, + 29, 28, 8, 2, 8, 4, 11, 32, 3, 4, 19, 8, 9, 7, 21, 4, 3, 8, 21, 4, 23, 10, 10, 8, 26, 48, + 8, 23, 10, 3, 8, 18, 8, 30, 10, 9, 10, 16, 8, 26, 48, 42, 69, 48, 8, 14, 13, 10, 5, 8, 11, 4, + 23, 17, 16, 20, 5, 3, 10, 2, 17, 8, 20, 14, 4, 21, 13, 32, 19, 8, 18, 8, 6, 9, 7, 2, 18, 15, + 32, 19, 8, 12, 6, 7, 27, 4, 19, 61, 78, 57, 68, 7, 9, 7, 6, 3, 10, 9, 18, 2, 3, 18, 6, 18, + 51, 43, 7, 9, 3, 18, 6, 20, 23, 8, 19, 10, 21, 10, 23, 18, 51, 8, 1, 1, 49, 1, 49, 1, 1, 43, + 86, 8, 19, 7, 2, 3, 10, 9, 2, 6, 4, 5, 8, 18, 16, 41, 4, 3, 4, 15, 23, 10, 13, 18, 29, 51, + 8, 87, 85, 1, 71, 49, 65, 43, 86, 8, 6, 4, 23, 23, 10, 6, 33, 18, 18, 51, 8, 1, 59, 1, 59, 48, + 70, 43, 13, 4, 19, 10, 9, 8, 6, 7, 3, 7, 23, 4, 41, 7, 51, 8, 26, 48, 43, 19, 7, 3, 10, 9, + 18, 7, 23, 8, 42}; + +} // namespace pythonLevenshteinIssue9 \ No newline at end of file diff --git a/src/external/rapidfuzz-cpp/test/distance/examples/pythonLevenshteinIssue9.hpp b/src/external/rapidfuzz-cpp/test/distance/examples/pythonLevenshteinIssue9.hpp new file mode 100644 index 00000000..b6e0cd78 --- /dev/null +++ b/src/external/rapidfuzz-cpp/test/distance/examples/pythonLevenshteinIssue9.hpp @@ -0,0 +1,8 @@ +#pragma once +#include +#include + +namespace pythonLevenshteinIssue9 { +extern std::vector example1; +extern std::vector example2; +} // namespace pythonLevenshteinIssue9 diff --git a/src/external/rapidfuzz-cpp/test/distance/tests-DamerauLevenshtein.cpp b/src/external/rapidfuzz-cpp/test/distance/tests-DamerauLevenshtein.cpp new file mode 100644 index 00000000..7a3b9648 --- /dev/null +++ b/src/external/rapidfuzz-cpp/test/distance/tests-DamerauLevenshtein.cpp @@ -0,0 +1,129 @@ +#include +#include +#include +#include + +#include + +#include "../common.hpp" + +using Catch::Matchers::WithinAbs; + +template +size_t damerau_levenshtein_distance(const Sentence1& s1, const Sentence2& s2, + size_t max = std::numeric_limits::max()) +{ + size_t res1 = rapidfuzz::experimental::damerau_levenshtein_distance(s1, s2, max); + size_t res2 = rapidfuzz::experimental::damerau_levenshtein_distance(s1.begin(), s1.end(), s2.begin(), + s2.end(), max); + size_t res3 = rapidfuzz::experimental::damerau_levenshtein_distance( + BidirectionalIterWrapper(s1.begin()), BidirectionalIterWrapper(s1.end()), + BidirectionalIterWrapper(s2.begin()), BidirectionalIterWrapper(s2.end()), max); + rapidfuzz::experimental::CachedDamerauLevenshtein scorer(s1); + size_t res4 = scorer.distance(s2, max); + size_t res5 = scorer.distance(s2.begin(), s2.end(), max); + REQUIRE(res1 == res2); + REQUIRE(res1 == res3); + REQUIRE(res1 == res4); + REQUIRE(res1 == res5); + return res1; +} + +template +size_t damerau_levenshtein_similarity(const Sentence1& s1, const Sentence2& s2, size_t max = 0) +{ + size_t res1 = rapidfuzz::experimental::damerau_levenshtein_similarity(s1, s2, max); + size_t res2 = rapidfuzz::experimental::damerau_levenshtein_similarity(s1.begin(), s1.end(), s2.begin(), + s2.end(), max); + size_t res3 = rapidfuzz::experimental::damerau_levenshtein_similarity( + BidirectionalIterWrapper(s1.begin()), BidirectionalIterWrapper(s1.end()), + BidirectionalIterWrapper(s2.begin()), BidirectionalIterWrapper(s2.end()), max); + rapidfuzz::experimental::CachedDamerauLevenshtein scorer(s1); + size_t res4 = scorer.similarity(s2, max); + size_t res5 = scorer.similarity(s2.begin(), s2.end(), max); + REQUIRE(res1 == res2); + REQUIRE(res1 == res3); + REQUIRE(res1 == res4); + REQUIRE(res1 == res5); + return res1; +} + +template +double damerau_levenshtein_normalized_distance(const Sentence1& s1, const Sentence2& s2, + double score_cutoff = 1.0) +{ + double res1 = rapidfuzz::experimental::damerau_levenshtein_normalized_distance(s1, s2, score_cutoff); + double res2 = rapidfuzz::experimental::damerau_levenshtein_normalized_distance( + s1.begin(), s1.end(), s2.begin(), s2.end(), score_cutoff); + double res3 = rapidfuzz::experimental::damerau_levenshtein_normalized_distance( + BidirectionalIterWrapper(s1.begin()), BidirectionalIterWrapper(s1.end()), + BidirectionalIterWrapper(s2.begin()), BidirectionalIterWrapper(s2.end()), score_cutoff); + rapidfuzz::experimental::CachedDamerauLevenshtein scorer(s1); + double res4 = scorer.normalized_distance(s2, score_cutoff); + double res5 = scorer.normalized_distance(s2.begin(), s2.end(), score_cutoff); + REQUIRE_THAT(res1, WithinAbs(res2, 0.0001)); + REQUIRE_THAT(res1, WithinAbs(res3, 0.0001)); + REQUIRE_THAT(res1, WithinAbs(res4, 0.0001)); + REQUIRE_THAT(res1, WithinAbs(res5, 0.0001)); + return res1; +} + +template +double damerau_levenshtein_normalized_similarity(const Sentence1& s1, const Sentence2& s2, + double score_cutoff = 0.0) +{ + double res1 = rapidfuzz::experimental::damerau_levenshtein_normalized_similarity(s1, s2, score_cutoff); + double res2 = rapidfuzz::experimental::damerau_levenshtein_normalized_similarity( + s1.begin(), s1.end(), s2.begin(), s2.end(), score_cutoff); + double res3 = rapidfuzz::experimental::damerau_levenshtein_normalized_similarity( + BidirectionalIterWrapper(s1.begin()), BidirectionalIterWrapper(s1.end()), + BidirectionalIterWrapper(s2.begin()), BidirectionalIterWrapper(s2.end()), score_cutoff); + rapidfuzz::experimental::CachedDamerauLevenshtein scorer(s1); + double res4 = scorer.normalized_similarity(s2, score_cutoff); + double res5 = scorer.normalized_similarity(s2.begin(), s2.end(), score_cutoff); + REQUIRE_THAT(res1, WithinAbs(res2, 0.0001)); + REQUIRE_THAT(res1, WithinAbs(res3, 0.0001)); + REQUIRE_THAT(res1, WithinAbs(res4, 0.0001)); + REQUIRE_THAT(res1, WithinAbs(res5, 0.0001)); + return res1; +} + +TEST_CASE("Levenshtein") +{ + std::string test = "aaaa"; + std::wstring no_suffix = L"aaa"; + std::string no_suffix2 = "aaab"; + std::string swapped1 = "abaa"; + std::string swapped2 = "baaa"; + std::string replace_all = "bbbb"; + + SECTION("damerau levenshtein calculates correct distances") + { + REQUIRE(damerau_levenshtein_distance(test, test) == 0); + REQUIRE(damerau_levenshtein_distance(test, no_suffix) == 1); + REQUIRE(damerau_levenshtein_distance(swapped1, swapped2) == 1); + REQUIRE(damerau_levenshtein_distance(test, no_suffix2) == 1); + REQUIRE(damerau_levenshtein_distance(test, replace_all) == 4); + + { + std::string s1 = "CA"; + std::string s2 = "ABC"; + REQUIRE(damerau_levenshtein_distance(s1, s2) == 2); + } + } + + SECTION("weighted levenshtein calculates correct ratios") + { + REQUIRE(damerau_levenshtein_normalized_similarity(test, test) == 1.0); + REQUIRE_THAT(damerau_levenshtein_normalized_similarity(test, no_suffix), WithinAbs(0.75, 0.0001)); + REQUIRE_THAT(damerau_levenshtein_normalized_similarity(swapped1, swapped2), WithinAbs(0.75, 0.0001)); + REQUIRE_THAT(damerau_levenshtein_normalized_similarity(test, no_suffix2), WithinAbs(0.75, 0.0001)); + REQUIRE(damerau_levenshtein_normalized_similarity(test, replace_all) == 0.0); + + { + std::string s1 = "CA"; + std::string s2 = "ABC"; + REQUIRE_THAT(damerau_levenshtein_normalized_similarity(s1, s2), WithinAbs(0.33333, 0.0001)); + } + } +} diff --git a/src/external/rapidfuzz-cpp/test/distance/tests-Hamming.cpp b/src/external/rapidfuzz-cpp/test/distance/tests-Hamming.cpp new file mode 100644 index 00000000..45feeb4e --- /dev/null +++ b/src/external/rapidfuzz-cpp/test/distance/tests-Hamming.cpp @@ -0,0 +1,124 @@ +#include +#include +#include +#include + +#include "../common.hpp" + +using Catch::Matchers::WithinAbs; + +template +size_t hamming_distance(const Sentence1& s1, const Sentence2& s2, + size_t max = std::numeric_limits::max()) +{ + size_t res1 = rapidfuzz::hamming_distance(s1, s2, max); + size_t res2 = rapidfuzz::hamming_distance(s1.begin(), s1.end(), s2.begin(), s2.end(), max); + size_t res3 = rapidfuzz::hamming_distance( + BidirectionalIterWrapper(s1.begin()), BidirectionalIterWrapper(s1.end()), + BidirectionalIterWrapper(s2.begin()), BidirectionalIterWrapper(s2.end()), max); + rapidfuzz::CachedHamming scorer(s1); + size_t res4 = scorer.distance(s2, max); + size_t res5 = scorer.distance(s2.begin(), s2.end(), max); + REQUIRE(res1 == res2); + REQUIRE(res1 == res3); + REQUIRE(res1 == res4); + REQUIRE(res1 == res5); + return res1; +} + +template +size_t hamming_similarity(const Sentence1& s1, const Sentence2& s2, size_t max = 0) +{ + size_t res1 = rapidfuzz::hamming_similarity(s1, s2, max); + size_t res2 = rapidfuzz::hamming_similarity(s1.begin(), s1.end(), s2.begin(), s2.end(), max); + size_t res3 = rapidfuzz::hamming_similarity( + BidirectionalIterWrapper(s1.begin()), BidirectionalIterWrapper(s1.end()), + BidirectionalIterWrapper(s2.begin()), BidirectionalIterWrapper(s2.end()), max); + rapidfuzz::CachedHamming scorer(s1); + size_t res4 = scorer.similarity(s2, max); + size_t res5 = scorer.similarity(s2.begin(), s2.end(), max); + REQUIRE(res1 == res2); + REQUIRE(res1 == res3); + REQUIRE(res1 == res4); + REQUIRE(res1 == res5); + return res1; +} + +template +double hamming_normalized_distance(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 1.0) +{ + double res1 = rapidfuzz::hamming_normalized_distance(s1, s2, score_cutoff); + double res2 = + rapidfuzz::hamming_normalized_distance(s1.begin(), s1.end(), s2.begin(), s2.end(), score_cutoff); + double res3 = rapidfuzz::hamming_normalized_distance( + BidirectionalIterWrapper(s1.begin()), BidirectionalIterWrapper(s1.end()), + BidirectionalIterWrapper(s2.begin()), BidirectionalIterWrapper(s2.end()), score_cutoff); + rapidfuzz::CachedHamming scorer(s1); + double res4 = scorer.normalized_distance(s2, score_cutoff); + double res5 = scorer.normalized_distance(s2.begin(), s2.end(), score_cutoff); + REQUIRE_THAT(res1, WithinAbs(res2, 0.0001)); + REQUIRE_THAT(res1, WithinAbs(res3, 0.0001)); + REQUIRE_THAT(res1, WithinAbs(res4, 0.0001)); + REQUIRE_THAT(res1, WithinAbs(res5, 0.0001)); + return res1; +} + +template +double hamming_normalized_similarity(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 0.0) +{ + double res1 = rapidfuzz::hamming_normalized_similarity(s1, s2, score_cutoff); + double res2 = + rapidfuzz::hamming_normalized_similarity(s1.begin(), s1.end(), s2.begin(), s2.end(), score_cutoff); + double res3 = rapidfuzz::hamming_normalized_similarity( + BidirectionalIterWrapper(s1.begin()), BidirectionalIterWrapper(s1.end()), + BidirectionalIterWrapper(s2.begin()), BidirectionalIterWrapper(s2.end()), score_cutoff); + rapidfuzz::CachedHamming scorer(s1); + double res4 = scorer.normalized_similarity(s2, score_cutoff); + double res5 = scorer.normalized_similarity(s2.begin(), s2.end(), score_cutoff); + REQUIRE_THAT(res1, WithinAbs(res2, 0.0001)); + REQUIRE_THAT(res1, WithinAbs(res3, 0.0001)); + REQUIRE_THAT(res1, WithinAbs(res4, 0.0001)); + REQUIRE_THAT(res1, WithinAbs(res5, 0.0001)); + return res1; +} + +TEST_CASE("Hamming") +{ + std::string test = "aaaa"; + std::string diff_a = "abaa"; + std::string diff_b = "aaba"; + std::string diff_len = "aaaaa"; + + SECTION("hamming calculates correct distances") + { + REQUIRE(hamming_distance(test, test) == 0); + REQUIRE(hamming_distance(test, diff_a) == 1); + REQUIRE(hamming_distance(test, diff_b) == 1); + REQUIRE(hamming_distance(diff_a, diff_b) == 2); + } + + SECTION("hamming handles different string lengths as insertions / deletions") + { + REQUIRE(hamming_distance(test, diff_len) == 1); + REQUIRE(hamming_distance(diff_len, test) == 1); + } +} + +TEST_CASE("Hamming_editops") +{ + std::string s = "Lorem ipsum."; + std::string d = "XYZLorem ABC iPsum"; + + { + rapidfuzz::Editops ops = rapidfuzz::hamming_editops(s, d); + REQUIRE(d == rapidfuzz::editops_apply_str(ops, s, d)); + REQUIRE(ops.get_src_len() == s.size()); + REQUIRE(ops.get_dest_len() == d.size()); + } + { + rapidfuzz::Editops ops = rapidfuzz::hamming_editops(d, s); + REQUIRE(s == rapidfuzz::editops_apply_str(ops, d, s)); + REQUIRE(ops.get_src_len() == d.size()); + REQUIRE(ops.get_dest_len() == s.size()); + } +} \ No newline at end of file diff --git a/src/external/rapidfuzz-cpp/test/distance/tests-Indel.cpp b/src/external/rapidfuzz-cpp/test/distance/tests-Indel.cpp new file mode 100644 index 00000000..4d787a4e --- /dev/null +++ b/src/external/rapidfuzz-cpp/test/distance/tests-Indel.cpp @@ -0,0 +1,283 @@ +#include +#include + +#include +#include + +#include "../common.hpp" + +using Catch::Matchers::WithinAbs; + +template +size_t indel_distance(const Sentence1& s1, const Sentence2& s2, + size_t max = std::numeric_limits::max()) +{ + size_t res1 = rapidfuzz::indel_distance(s1, s2, max); + size_t res2 = rapidfuzz::indel_distance(s1.begin(), s1.end(), s2.begin(), s2.end(), max); + size_t res3 = rapidfuzz::indel_distance( + BidirectionalIterWrapper(s1.begin()), BidirectionalIterWrapper(s1.end()), + BidirectionalIterWrapper(s2.begin()), BidirectionalIterWrapper(s2.end()), max); + rapidfuzz::CachedIndel scorer(s1); + size_t res4 = scorer.distance(s2, max); + size_t res5 = scorer.distance(s2.begin(), s2.end(), max); +#ifdef RAPIDFUZZ_SIMD + if (s1.size() <= 64) { + std::vector results(256 / 8); + + if (s1.size() <= 8) { + rapidfuzz::experimental::MultiIndel<8> simd_scorer(1); + simd_scorer.insert(s1); + simd_scorer.distance(&results[0], results.size(), s2, max); + REQUIRE(res1 == results[0]); + } + if (s1.size() <= 16) { + rapidfuzz::experimental::MultiIndel<16> simd_scorer(1); + simd_scorer.insert(s1); + simd_scorer.distance(&results[0], results.size(), s2, max); + REQUIRE(res1 == results[0]); + } + if (s1.size() <= 32) { + rapidfuzz::experimental::MultiIndel<32> simd_scorer(1); + simd_scorer.insert(s1); + simd_scorer.distance(&results[0], results.size(), s2, max); + REQUIRE(res1 == results[0]); + } + if (s1.size() <= 64) { + rapidfuzz::experimental::MultiIndel<64> simd_scorer(1); + simd_scorer.insert(s1); + simd_scorer.distance(&results[0], results.size(), s2, max); + REQUIRE(res1 == results[0]); + } + } +#endif + REQUIRE(res1 == res2); + REQUIRE(res1 == res3); + REQUIRE(res1 == res4); + REQUIRE(res1 == res5); + return res1; +} + +template +size_t indel_similarity(const Sentence1& s1, const Sentence2& s2, size_t max = 0) +{ + size_t res1 = rapidfuzz::indel_similarity(s1, s2, max); + size_t res2 = rapidfuzz::indel_similarity(s1.begin(), s1.end(), s2.begin(), s2.end(), max); + size_t res3 = rapidfuzz::indel_similarity( + BidirectionalIterWrapper(s1.begin()), BidirectionalIterWrapper(s1.end()), + BidirectionalIterWrapper(s2.begin()), BidirectionalIterWrapper(s2.end()), max); + rapidfuzz::CachedIndel scorer(s1); + size_t res4 = scorer.similarity(s2, max); + size_t res5 = scorer.similarity(s2.begin(), s2.end(), max); +#ifdef RAPIDFUZZ_SIMD + if (s1.size() <= 64) { + std::vector results(256 / 8); + + if (s1.size() <= 8) { + rapidfuzz::experimental::MultiIndel<8> simd_scorer(1); + simd_scorer.insert(s1); + simd_scorer.similarity(&results[0], results.size(), s2, max); + } + else if (s1.size() <= 16) { + rapidfuzz::experimental::MultiIndel<16> simd_scorer(1); + simd_scorer.insert(s1); + simd_scorer.similarity(&results[0], results.size(), s2, max); + } + else if (s1.size() <= 32) { + rapidfuzz::experimental::MultiIndel<32> simd_scorer(1); + simd_scorer.insert(s1); + simd_scorer.similarity(&results[0], results.size(), s2, max); + } + else { + rapidfuzz::experimental::MultiIndel<64> simd_scorer(1); + simd_scorer.insert(s1); + simd_scorer.similarity(&results[0], results.size(), s2, max); + } + + REQUIRE(res1 == results[0]); + } +#endif + REQUIRE(res1 == res2); + REQUIRE(res1 == res3); + REQUIRE(res1 == res4); + REQUIRE(res1 == res5); + return res1; +} + +template +double indel_normalized_distance(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 1.0) +{ + double res1 = rapidfuzz::indel_normalized_distance(s1, s2, score_cutoff); + double res2 = + rapidfuzz::indel_normalized_distance(s1.begin(), s1.end(), s2.begin(), s2.end(), score_cutoff); + double res3 = rapidfuzz::indel_normalized_distance( + BidirectionalIterWrapper(s1.begin()), BidirectionalIterWrapper(s1.end()), + BidirectionalIterWrapper(s2.begin()), BidirectionalIterWrapper(s2.end()), score_cutoff); + rapidfuzz::CachedIndel scorer(s1); + double res4 = scorer.normalized_distance(s2, score_cutoff); + double res5 = scorer.normalized_distance(s2.begin(), s2.end(), score_cutoff); +#ifdef RAPIDFUZZ_SIMD + if (s1.size() <= 64) { + std::vector results(256 / 8); + + if (s1.size() <= 8) { + rapidfuzz::experimental::MultiIndel<8> simd_scorer(1); + simd_scorer.insert(s1); + simd_scorer.normalized_distance(&results[0], results.size(), s2, score_cutoff); + } + else if (s1.size() <= 16) { + rapidfuzz::experimental::MultiIndel<16> simd_scorer(1); + simd_scorer.insert(s1); + simd_scorer.normalized_distance(&results[0], results.size(), s2, score_cutoff); + } + else if (s1.size() <= 32) { + rapidfuzz::experimental::MultiIndel<32> simd_scorer(1); + simd_scorer.insert(s1); + simd_scorer.normalized_distance(&results[0], results.size(), s2, score_cutoff); + } + else { + rapidfuzz::experimental::MultiIndel<64> simd_scorer(1); + simd_scorer.insert(s1); + simd_scorer.normalized_distance(&results[0], results.size(), s2, score_cutoff); + } + + REQUIRE_THAT(res1, WithinAbs(results[0], 0.0001)); + } +#endif + REQUIRE_THAT(res1, WithinAbs(res2, 0.0001)); + REQUIRE_THAT(res1, WithinAbs(res3, 0.0001)); + REQUIRE_THAT(res1, WithinAbs(res4, 0.0001)); + REQUIRE_THAT(res1, WithinAbs(res5, 0.0001)); + return res1; +} + +template +double indel_normalized_similarity(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 0.0) +{ + double res1 = rapidfuzz::indel_normalized_similarity(s1, s2, score_cutoff); + double res2 = + rapidfuzz::indel_normalized_similarity(s1.begin(), s1.end(), s2.begin(), s2.end(), score_cutoff); + double res3 = rapidfuzz::indel_normalized_similarity( + BidirectionalIterWrapper(s1.begin()), BidirectionalIterWrapper(s1.end()), + BidirectionalIterWrapper(s2.begin()), BidirectionalIterWrapper(s2.end()), score_cutoff); + rapidfuzz::CachedIndel scorer(s1); + double res4 = scorer.normalized_similarity(s2, score_cutoff); + double res5 = scorer.normalized_similarity(s2.begin(), s2.end(), score_cutoff); +#ifdef RAPIDFUZZ_SIMD + if (s1.size() <= 64) { + std::vector results(256 / 8); + + if (s1.size() <= 8) { + rapidfuzz::experimental::MultiIndel<8> simd_scorer(1); + simd_scorer.insert(s1); + simd_scorer.normalized_similarity(&results[0], results.size(), s2, score_cutoff); + } + else if (s1.size() <= 16) { + rapidfuzz::experimental::MultiIndel<16> simd_scorer(1); + simd_scorer.insert(s1); + simd_scorer.normalized_similarity(&results[0], results.size(), s2, score_cutoff); + } + else if (s1.size() <= 32) { + rapidfuzz::experimental::MultiIndel<32> simd_scorer(1); + simd_scorer.insert(s1); + simd_scorer.normalized_similarity(&results[0], results.size(), s2, score_cutoff); + } + else { + rapidfuzz::experimental::MultiIndel<64> simd_scorer(1); + simd_scorer.insert(s1); + simd_scorer.normalized_similarity(&results[0], results.size(), s2, score_cutoff); + } + + REQUIRE_THAT(res1, WithinAbs(results[0], 0.0001)); + } +#endif + REQUIRE_THAT(res1, WithinAbs(res2, 0.0001)); + REQUIRE_THAT(res1, WithinAbs(res3, 0.0001)); + REQUIRE_THAT(res1, WithinAbs(res4, 0.0001)); + REQUIRE_THAT(res1, WithinAbs(res5, 0.0001)); + return res1; +} + +TEST_CASE("Indel") +{ + std::string test = "aaaa"; + std::string replace_all = "bbbb"; + + SECTION("similar strings") + { + REQUIRE(indel_distance(test, test) == 0); + REQUIRE(indel_similarity(test, test) == 8); + REQUIRE(indel_normalized_distance(test, test) == 0.0); + REQUIRE(indel_normalized_similarity(test, test) == 1.0); + } + + SECTION("completly different strings") + { + REQUIRE(indel_distance(test, replace_all) == 8); + REQUIRE(indel_similarity(test, replace_all) == 0); + REQUIRE(indel_normalized_distance(test, replace_all) == 1.0); + REQUIRE(indel_normalized_similarity(test, replace_all) == 0.0); + } + + SECTION("some tests for mbleven") + { + std::string a = "South Korea"; + std::string b = "North Korea"; + REQUIRE(indel_distance(a, b) == 4); + REQUIRE(indel_distance(a, b, 5) == 4); + REQUIRE(indel_distance(a, b, 4) == 4); + REQUIRE(indel_distance(a, b, 3) == 4); + REQUIRE(indel_distance(a, b, 2) == 3); + REQUIRE(indel_distance(a, b, 1) == 2); + REQUIRE(indel_distance(a, b, 0) == 1); + + a = "aabc"; + b = "cccd"; + REQUIRE(indel_distance(a, b) == 6); + REQUIRE(indel_distance(a, b, 6) == 6); + REQUIRE(indel_distance(a, b, 5) == 6); + REQUIRE(indel_distance(a, b, 4) == 5); + REQUIRE(indel_distance(a, b, 3) == 4); + REQUIRE(indel_distance(a, b, 2) == 3); + REQUIRE(indel_distance(a, b, 1) == 2); + REQUIRE(indel_distance(a, b, 0) == 1); + } + + SECTION("testCachedImplementation") + { + std::string a = "001"; + std::string b = "220"; + REQUIRE_THAT(rapidfuzz::indel_normalized_similarity(a, b), WithinAbs(0.3333333, 0.000001)); + REQUIRE_THAT(rapidfuzz::CachedIndel(a).normalized_similarity(b), + WithinAbs(0.3333333, 0.000001)); + } + + SECTION("test banded implementation") + { + { + std::string s1 = "ddccbccc"; + std::string s2 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaccacccaccaaaaaaaa" + "daaaaaaaaccccaccccccaaaaaaaccccaaacccaccccadddaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaccccccccacccaaaaaacccaaaaaacccacccaaaaaacccdccc" + "cccacccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccddddddaaaaaaaa" + "aaaaaaaaaaaaaaaaaacacccaaaaaacccddddaaaaaaaaaaaaaaaaaaaaaaaaaaaaaccccaaaaaaaaaa" + "ccccccaadddaaaaaaaaaaaaaaaaaaaaaacaaaaaa"; + REQUIRE(indel_distance(s1, s2) == 508); + REQUIRE(indel_distance(s1, s2, 508) == 508); + REQUIRE(indel_distance(s1, s2, 507) == 508); + REQUIRE(indel_distance(s1, s2, std::numeric_limits::max()) == 508); + } + + { + std::string s1 = "bbbdbbmbbbbbbbbbBbfbbbbbbbbbbbbbbbbbbbrbbbbbrbbbbbdbnbbbjbhbbbbbbbbbhbbbbbCbobb" + "bxbbbbbkbbbAbxbbwbbbtbcbbbbebbiblbbbbqbbbbbbpbbbbbbubbbkbbDbbbhbkbCbbgbbrbbbbbb" + "bbbbbkbyvbbsbAbbbbz"; + std::string s2 = "jaaagaaqyaaaanrCfwaaxaeahtaaaCzaaaspaaBkvaaaaqDaacndaaeolwiaaauaaaaaaamA"; + + REQUIRE(indel_distance(s1, s2) == 231); + + rapidfuzz::Editops ops = rapidfuzz::indel_editops(s1, s2); + REQUIRE(s2 == rapidfuzz::editops_apply_str(ops, s1, s2)); + } + } +} diff --git a/src/external/rapidfuzz-cpp/test/distance/tests-Jaro.cpp b/src/external/rapidfuzz-cpp/test/distance/tests-Jaro.cpp new file mode 100644 index 00000000..fb3d0823 --- /dev/null +++ b/src/external/rapidfuzz-cpp/test/distance/tests-Jaro.cpp @@ -0,0 +1,251 @@ +#include "../../rapidfuzz_reference/Jaro.hpp" +#include +#include + +#include "../common.hpp" + +using Catch::Matchers::WithinAbs; + +template +double jaro_similarity(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 0.0) +{ + double res1 = rapidfuzz::jaro_similarity(s1, s2, score_cutoff); + double res2 = rapidfuzz::jaro_similarity(s1.begin(), s1.end(), s2.begin(), s2.end(), score_cutoff); + double res3 = rapidfuzz::jaro_normalized_similarity(s1, s2, score_cutoff); + double res4 = + rapidfuzz::jaro_normalized_similarity(s1.begin(), s1.end(), s2.begin(), s2.end(), score_cutoff); +#if 0 // todo + double res5 = rapidfuzz::jaro_similarity( + BidirectionalIterWrapper(s1.begin()), BidirectionalIterWrapper(s1.end()), + BidirectionalIterWrapper(s2.begin()), BidirectionalIterWrapper(s2.end()), score_cutoff); +#endif + rapidfuzz::CachedJaro scorer(s1); + double res6 = scorer.similarity(s2, score_cutoff); + double res7 = scorer.similarity(s2.begin(), s2.end(), score_cutoff); + double res8 = scorer.normalized_similarity(s2, score_cutoff); + double res9 = scorer.normalized_similarity(s2.begin(), s2.end(), score_cutoff); + +#ifdef RAPIDFUZZ_SIMD + std::vector results(256 / 8); + if (s1.size() <= 8) { + rapidfuzz::experimental::MultiJaro<8> simd_scorer(32); + for (size_t i = 0; i < 32; ++i) + simd_scorer.insert(s1); + + simd_scorer.similarity(&results[0], results.size(), s2, score_cutoff); + for (size_t i = 0; i < 32; ++i) + REQUIRE_THAT(res1, WithinAbs(results[i], 0.000001)); + } + if (s1.size() <= 16) { + rapidfuzz::experimental::MultiJaro<16> simd_scorer(16); + for (size_t i = 0; i < 16; ++i) + simd_scorer.insert(s1); + + simd_scorer.similarity(&results[0], results.size(), s2, score_cutoff); + for (size_t i = 0; i < 16; ++i) + REQUIRE_THAT(res1, WithinAbs(results[i], 0.000001)); + } + if (s1.size() <= 32) { + rapidfuzz::experimental::MultiJaro<32> simd_scorer(8); + for (size_t i = 0; i < 8; ++i) + simd_scorer.insert(s1); + + simd_scorer.similarity(&results[0], results.size(), s2, score_cutoff); + for (size_t i = 0; i < 8; ++i) + REQUIRE_THAT(res1, WithinAbs(results[i], 0.000001)); + } + if (s1.size() <= 64) { + rapidfuzz::experimental::MultiJaro<64> simd_scorer(4); + for (size_t i = 0; i < 4; ++i) + simd_scorer.insert(s1); + + simd_scorer.similarity(&results[0], results.size(), s2, score_cutoff); + for (size_t i = 0; i < 4; ++i) + REQUIRE_THAT(res1, WithinAbs(results[i], 0.000001)); + } +#endif + + REQUIRE_THAT(res1, WithinAbs(res2, 0.000001)); + REQUIRE_THAT(res1, WithinAbs(res3, 0.000001)); + REQUIRE_THAT(res1, WithinAbs(res4, 0.000001)); + // REQUIRE_THAT(res1, WithinAbs(res5, 0.000001)); + REQUIRE_THAT(res1, WithinAbs(res6, 0.000001)); + REQUIRE_THAT(res1, WithinAbs(res7, 0.000001)); + REQUIRE_THAT(res1, WithinAbs(res8, 0.000001)); + REQUIRE_THAT(res1, WithinAbs(res9, 0.000001)); + return res1; +} + +template +double jaro_distance(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 1.0) +{ + double res1 = rapidfuzz::jaro_distance(s1, s2, score_cutoff); + double res2 = rapidfuzz::jaro_distance(s1.begin(), s1.end(), s2.begin(), s2.end(), score_cutoff); + double res3 = rapidfuzz::jaro_normalized_distance(s1, s2, score_cutoff); + double res4 = + rapidfuzz::jaro_normalized_distance(s1.begin(), s1.end(), s2.begin(), s2.end(), score_cutoff); +#if 0 // todo + double res5 = rapidfuzz::jaro_distance( + BidirectionalIterWrapper(s1.begin()), BidirectionalIterWrapper(s1.end()), + BidirectionalIterWrapper(s2.begin()), BidirectionalIterWrapper(s2.end()), score_cutoff); +#endif + rapidfuzz::CachedJaro scorer(s1); + double res6 = scorer.distance(s2, score_cutoff); + double res7 = scorer.distance(s2.begin(), s2.end(), score_cutoff); + double res8 = scorer.normalized_distance(s2, score_cutoff); + double res9 = scorer.normalized_distance(s2.begin(), s2.end(), score_cutoff); + +#ifdef RAPIDFUZZ_SIMD + std::vector results(256 / 8); + if (s1.size() <= 8) { + rapidfuzz::experimental::MultiJaro<8> simd_scorer(32); + for (size_t i = 0; i < 32; ++i) + simd_scorer.insert(s1); + + simd_scorer.distance(&results[0], results.size(), s2, score_cutoff); + for (size_t i = 0; i < 32; ++i) + REQUIRE_THAT(res1, WithinAbs(results[i], 0.000001)); + } + if (s1.size() <= 16) { + rapidfuzz::experimental::MultiJaro<16> simd_scorer(16); + for (size_t i = 0; i < 16; ++i) + simd_scorer.insert(s1); + + simd_scorer.distance(&results[0], results.size(), s2, score_cutoff); + for (size_t i = 0; i < 16; ++i) + REQUIRE_THAT(res1, WithinAbs(results[i], 0.000001)); + } + if (s1.size() <= 32) { + rapidfuzz::experimental::MultiJaro<32> simd_scorer(8); + for (size_t i = 0; i < 8; ++i) + simd_scorer.insert(s1); + + simd_scorer.distance(&results[0], results.size(), s2, score_cutoff); + for (size_t i = 0; i < 8; ++i) + REQUIRE_THAT(res1, WithinAbs(results[i], 0.000001)); + } + if (s1.size() <= 64) { + rapidfuzz::experimental::MultiJaro<64> simd_scorer(4); + for (size_t i = 0; i < 4; ++i) + simd_scorer.insert(s1); + + simd_scorer.distance(&results[0], results.size(), s2, score_cutoff); + for (size_t i = 0; i < 4; ++i) + REQUIRE_THAT(res1, WithinAbs(results[i], 0.000001)); + } +#endif + + REQUIRE_THAT(res1, WithinAbs(res2, 0.000001)); + REQUIRE_THAT(res1, WithinAbs(res3, 0.000001)); + REQUIRE_THAT(res1, WithinAbs(res4, 0.000001)); + // REQUIRE_THAT(res1, WithinAbs(res5, 0.000001)); + REQUIRE_THAT(res1, WithinAbs(res6, 0.000001)); + REQUIRE_THAT(res1, WithinAbs(res7, 0.000001)); + REQUIRE_THAT(res1, WithinAbs(res8, 0.000001)); + REQUIRE_THAT(res1, WithinAbs(res9, 0.000001)); + return res1; +} + +template +double jaro_sim_test(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 0.0) +{ + INFO("name1: " << s1 << ", name2: " << s2 << ", score_cutoff: " << score_cutoff); + double Sim_original = rapidfuzz_reference::jaro_similarity(s1, s2, score_cutoff); + double Sim_bitparallel = jaro_similarity(s1, s2, score_cutoff); + double Dist_bitparallel = jaro_distance(s1, s2, 1.0 - score_cutoff); + double Sim_bitparallel2 = jaro_similarity(s2, s1, score_cutoff); + double Dist_bitparallel2 = jaro_distance(s2, s1, 1.0 - score_cutoff); + + REQUIRE_THAT(Sim_original, WithinAbs(Sim_bitparallel, 0.000001)); + REQUIRE_THAT((1.0 - Sim_original), WithinAbs(Dist_bitparallel, 0.000001)); + REQUIRE_THAT(Sim_original, WithinAbs(Sim_bitparallel2, 0.000001)); + REQUIRE_THAT((1.0 - Sim_original), WithinAbs(Dist_bitparallel2, 0.000001)); + return Sim_original; +} + +TEST_CASE("JaroTest") +{ + std::array names = {"james", "robert", "john", "michael", "william", + "david", "joseph", "thomas", "charles", "mary", + "patricia", "jennifer", "linda", "elizabeth", "barbara", + "susan", "jessica", "sarah", "karen", ""}; + + SECTION("testFullResultWithScoreCutoff") + { + auto score_cutoffs = {0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1}; + + for (double score_cutoff : score_cutoffs) + for (const auto& name1 : names) + for (const auto& name2 : names) + jaro_sim_test(name1, name2, score_cutoff); + } + + SECTION("testEdgeCaseLengths") + { + REQUIRE_THAT(jaro_sim_test(std::string(""), std::string("")), WithinAbs(1, 0.000001)); + REQUIRE_THAT(jaro_sim_test(std::string("0"), std::string("0")), WithinAbs(1, 0.000001)); + REQUIRE_THAT(jaro_sim_test(std::string("00"), std::string("00")), WithinAbs(1, 0.000001)); + REQUIRE_THAT(jaro_sim_test(std::string("0"), std::string("00")), WithinAbs(0.833333, 0.000001)); + + REQUIRE_THAT(jaro_sim_test(str_multiply(std::string("0"), 65), str_multiply(std::string("0"), 65)), + WithinAbs(1, 0.000001)); + REQUIRE_THAT(jaro_sim_test(str_multiply(std::string("0"), 64), str_multiply(std::string("0"), 65)), + WithinAbs(0.994872, 0.000001)); + REQUIRE_THAT(jaro_sim_test(str_multiply(std::string("0"), 63), str_multiply(std::string("0"), 65)), + WithinAbs(0.989744, 0.000001)); + + REQUIRE_THAT(jaro_sim_test(std::string("000000001"), std::string("0000010")), + WithinAbs(0.8783068783, 0.000001)); + { + std::string s1("01234567"); + std::string s2 = str_multiply(std::string("0"), 170) + std::string("7654321") + + str_multiply(std::string("0"), 200); + + REQUIRE_THAT(jaro_sim_test(s1, s2), WithinAbs(0.5487400531, 0.000001)); + } + + REQUIRE_THAT(jaro_sim_test(std::string("01"), std::string("1111100000")), + WithinAbs(0.53333333, 0.000001)); + + REQUIRE_THAT( + jaro_sim_test(std::string("10000000000000000000000000000000000000000000000000000000000000020"), + std::string("00000000000000000000000000000000000000000000000000000000000000000")), + WithinAbs(0.979487, 0.000001)); + REQUIRE_THAT( + jaro_sim_test( + std::string("00000000000000100000000000000000000000010000000000000000000000000"), + std::string( + "0000000000000000000000000000000000000000000000000000000000000000000000000000001")), + WithinAbs(0.922233, 0.000001)); + REQUIRE_THAT( + jaro_sim_test(std::string("00000000000000000000000000000000000000000000000000000000000000000"), + std::string("0100000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000")), + WithinAbs(0.8359375, 0.000001)); + } + + SECTION("testFuzzingRegressions") + { +#ifdef RAPIDFUZZ_SIMD + { + std::string s2 = "010101010101010101010101010101010101010101010101010101010101010101" + "010101010101010101010101010101010101010101010101010101010101010101" + "010101010101010101010101010101010101010101010101010101010101010101" + "0101010101010101010101010101010101010101010101010101010101"; + + std::vector results(512 / 8); + rapidfuzz::experimental::MultiJaro<8> simd_scorer(64); + for (size_t i = 0; i < 32; ++i) + simd_scorer.insert("10010010"); + + simd_scorer.insert("00100100"); + simd_scorer.similarity(&results[0], results.size(), s2); + + for (size_t i = 0; i < 32; ++i) + REQUIRE_THAT(results[i], WithinAbs(0.593750, 0.000001)); + + REQUIRE_THAT(results[32], WithinAbs(0.593750, 0.000001)); + } +#endif + } +} diff --git a/src/external/rapidfuzz-cpp/test/distance/tests-JaroWinkler.cpp b/src/external/rapidfuzz-cpp/test/distance/tests-JaroWinkler.cpp new file mode 100644 index 00000000..bccf3915 --- /dev/null +++ b/src/external/rapidfuzz-cpp/test/distance/tests-JaroWinkler.cpp @@ -0,0 +1,211 @@ +#include "../../rapidfuzz_reference/JaroWinkler.hpp" +#include +#include + +#include "../common.hpp" + +using Catch::Matchers::WithinAbs; + +template +double jaro_winkler_similarity(const Sentence1& s1, const Sentence2& s2, double prefix_weight = 0.1, + double score_cutoff = 0.0) +{ + double res1 = rapidfuzz::jaro_winkler_similarity(s1, s2, prefix_weight, score_cutoff); + double res2 = rapidfuzz::jaro_winkler_similarity(s1.begin(), s1.end(), s2.begin(), s2.end(), + prefix_weight, score_cutoff); + double res3 = rapidfuzz::jaro_winkler_normalized_similarity(s1, s2, prefix_weight, score_cutoff); + double res4 = rapidfuzz::jaro_winkler_normalized_similarity(s1.begin(), s1.end(), s2.begin(), s2.end(), + prefix_weight, score_cutoff); + rapidfuzz::CachedJaroWinkler scorer(s1, prefix_weight); + double res5 = scorer.similarity(s2, score_cutoff); + double res6 = scorer.similarity(s2.begin(), s2.end(), score_cutoff); + double res7 = scorer.normalized_similarity(s2, score_cutoff); + double res8 = scorer.normalized_similarity(s2.begin(), s2.end(), score_cutoff); + +#ifdef RAPIDFUZZ_SIMD + std::vector results(256 / 8); + if (s1.size() <= 8) { + rapidfuzz::experimental::MultiJaroWinkler<8> simd_scorer(32, prefix_weight); + for (unsigned int i = 0; i < 32; ++i) + simd_scorer.insert(s1); + simd_scorer.similarity(&results[0], results.size(), s2, score_cutoff); + for (unsigned int i = 0; i < 32; ++i) + REQUIRE_THAT(res1, WithinAbs(results[i], 0.000001)); + } + if (s1.size() <= 16) { + rapidfuzz::experimental::MultiJaroWinkler<16> simd_scorer(1, prefix_weight); + simd_scorer.insert(s1); + simd_scorer.similarity(&results[0], results.size(), s2, score_cutoff); + REQUIRE_THAT(res1, WithinAbs(results[0], 0.000001)); + } + if (s1.size() <= 32) { + rapidfuzz::experimental::MultiJaroWinkler<32> simd_scorer(1, prefix_weight); + simd_scorer.insert(s1); + simd_scorer.similarity(&results[0], results.size(), s2, score_cutoff); + REQUIRE_THAT(res1, WithinAbs(results[0], 0.000001)); + } + if (s1.size() <= 64) { + rapidfuzz::experimental::MultiJaroWinkler<64> simd_scorer(1, prefix_weight); + simd_scorer.insert(s1); + simd_scorer.similarity(&results[0], results.size(), s2, score_cutoff); + REQUIRE_THAT(res1, WithinAbs(results[0], 0.000001)); + } +#endif + + REQUIRE_THAT(res1, WithinAbs(res2, 0.000001)); + REQUIRE_THAT(res1, WithinAbs(res3, 0.000001)); + REQUIRE_THAT(res1, WithinAbs(res4, 0.000001)); + REQUIRE_THAT(res1, WithinAbs(res5, 0.000001)); + REQUIRE_THAT(res1, WithinAbs(res6, 0.000001)); + REQUIRE_THAT(res1, WithinAbs(res7, 0.000001)); + REQUIRE_THAT(res1, WithinAbs(res8, 0.000001)); + return res1; +} + +template +double jaro_winkler_distance(const Sentence1& s1, const Sentence2& s2, double prefix_weight = 0.1, + double score_cutoff = 1.0) +{ + double res1 = rapidfuzz::jaro_winkler_distance(s1, s2, prefix_weight, score_cutoff); + double res2 = rapidfuzz::jaro_winkler_distance(s1.begin(), s1.end(), s2.begin(), s2.end(), prefix_weight, + score_cutoff); + double res3 = rapidfuzz::jaro_winkler_normalized_distance(s1, s2, prefix_weight, score_cutoff); + double res4 = rapidfuzz::jaro_winkler_normalized_distance(s1.begin(), s1.end(), s2.begin(), s2.end(), + prefix_weight, score_cutoff); + rapidfuzz::CachedJaroWinkler scorer(s1, prefix_weight); + double res5 = scorer.distance(s2, score_cutoff); + double res6 = scorer.distance(s2.begin(), s2.end(), score_cutoff); + double res7 = scorer.normalized_distance(s2, score_cutoff); + double res8 = scorer.normalized_distance(s2.begin(), s2.end(), score_cutoff); + +#ifdef RAPIDFUZZ_SIMD + std::vector results(256 / 8); + if (s1.size() <= 8) { + rapidfuzz::experimental::MultiJaroWinkler<8> simd_scorer(1, prefix_weight); + simd_scorer.insert(s1); + simd_scorer.distance(&results[0], results.size(), s2, score_cutoff); + REQUIRE_THAT(res1, WithinAbs(results[0], 0.000001)); + } + if (s1.size() <= 16) { + rapidfuzz::experimental::MultiJaroWinkler<16> simd_scorer(1, prefix_weight); + simd_scorer.insert(s1); + simd_scorer.distance(&results[0], results.size(), s2, score_cutoff); + REQUIRE_THAT(res1, WithinAbs(results[0], 0.000001)); + } + if (s1.size() <= 32) { + rapidfuzz::experimental::MultiJaroWinkler<32> simd_scorer(1, prefix_weight); + simd_scorer.insert(s1); + simd_scorer.distance(&results[0], results.size(), s2, score_cutoff); + REQUIRE_THAT(res1, WithinAbs(results[0], 0.000001)); + } + if (s1.size() <= 64) { + rapidfuzz::experimental::MultiJaroWinkler<64> simd_scorer(1, prefix_weight); + simd_scorer.insert(s1); + simd_scorer.distance(&results[0], results.size(), s2, score_cutoff); + REQUIRE_THAT(res1, WithinAbs(results[0], 0.000001)); + } +#endif + + REQUIRE_THAT(res1, WithinAbs(res2, 0.000001)); + REQUIRE_THAT(res1, WithinAbs(res3, 0.000001)); + REQUIRE_THAT(res1, WithinAbs(res4, 0.000001)); + REQUIRE_THAT(res1, WithinAbs(res5, 0.000001)); + REQUIRE_THAT(res1, WithinAbs(res6, 0.000001)); + REQUIRE_THAT(res1, WithinAbs(res7, 0.000001)); + REQUIRE_THAT(res1, WithinAbs(res8, 0.000001)); + return res1; +} + +template +double jaro_winkler_sim_test(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 0.0) +{ + INFO("name1: " << s1 << ", name2: " << s2 << ", score_cutoff: " << score_cutoff); + double Sim_original = rapidfuzz_reference::jaro_winkler_similarity(s1, s2, 0.1, score_cutoff); + double Sim_bitparallel = jaro_winkler_similarity(s1, s2, 0.1, score_cutoff); + double Dist_bitparallel = jaro_winkler_distance(s1, s2, 0.1, 1.0 - score_cutoff); + double Sim_bitparallel2 = jaro_winkler_similarity(s2, s1, 0.1, score_cutoff); + double Dist_bitparallel2 = jaro_winkler_distance(s2, s1, 0.1, 1.0 - score_cutoff); + + REQUIRE_THAT(Sim_original, WithinAbs(Sim_bitparallel, 0.000001)); + REQUIRE_THAT((1.0 - Sim_original), WithinAbs(Dist_bitparallel, 0.000001)); + REQUIRE_THAT(Sim_original, WithinAbs(Sim_bitparallel2, 0.000001)); + REQUIRE_THAT((1.0 - Sim_original), WithinAbs(Dist_bitparallel2, 0.000001)); + return Sim_original; +} + +TEST_CASE("JaroWinklerTest") +{ + std::array names = {"james", + "robert", + "john", + "michael", + "william", + "david", + "joseph", + "thomas", + "charles", + "mary", + "patricia", + "jennifer", + "linda", + "elizabeth", + "barbara", + "susan", + "jessica", + "sarah", + "karen", + "" + "aaaaaaaa", + "aabaaab"}; + + SECTION("testFullResultWithScoreCutoff") + { + auto score_cutoffs = {0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1}; + + for (double score_cutoff : score_cutoffs) + for (const auto& name1 : names) + for (const auto& name2 : names) + jaro_winkler_sim_test(name1, name2, score_cutoff); + } + + SECTION("testEdgeCaseLengths") + { + REQUIRE_THAT(jaro_winkler_sim_test(std::string(""), std::string("")), WithinAbs(1, 0.000001)); + REQUIRE_THAT(jaro_winkler_sim_test(std::string("0"), std::string("0")), WithinAbs(1, 0.000001)); + REQUIRE_THAT(jaro_winkler_sim_test(std::string("00"), std::string("00")), WithinAbs(1, 0.000001)); + REQUIRE_THAT(jaro_winkler_sim_test(std::string("0"), std::string("00")), WithinAbs(0.85, 0.000001)); + + REQUIRE_THAT( + jaro_winkler_sim_test(str_multiply(std::string("0"), 65), str_multiply(std::string("0"), 65)), + WithinAbs(1, 0.000001)); + REQUIRE_THAT( + jaro_winkler_sim_test(str_multiply(std::string("0"), 64), str_multiply(std::string("0"), 65)), + WithinAbs(0.996923, 0.000001)); + REQUIRE_THAT( + jaro_winkler_sim_test(str_multiply(std::string("0"), 63), str_multiply(std::string("0"), 65)), + WithinAbs(0.993846, 0.000001)); + + REQUIRE_THAT(jaro_winkler_sim_test(std::string("000000001"), std::string("0000010")), + WithinAbs(0.926984127, 0.000001)); + + REQUIRE_THAT(jaro_winkler_sim_test(std::string("01"), std::string("1111100000")), + WithinAbs(0.53333333, 0.000001)); + + REQUIRE_THAT(jaro_winkler_sim_test( + std::string("10000000000000000000000000000000000000000000000000000000000000020"), + std::string("00000000000000000000000000000000000000000000000000000000000000000")), + WithinAbs(0.979487, 0.000001)); + REQUIRE_THAT( + jaro_winkler_sim_test( + std::string("00000000000000100000000000000000000000010000000000000000000000000"), + std::string( + "0000000000000000000000000000000000000000000000000000000000000000000000000000001")), + WithinAbs(0.95334, 0.000001)); + REQUIRE_THAT( + jaro_winkler_sim_test( + std::string("00000000000000000000000000000000000000000000000000000000000000000"), + std::string("0100000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000")), + WithinAbs(0.852344, 0.000001)); + } +} \ No newline at end of file diff --git a/src/external/rapidfuzz-cpp/test/distance/tests-LCSseq.cpp b/src/external/rapidfuzz-cpp/test/distance/tests-LCSseq.cpp new file mode 100644 index 00000000..f8cdeab1 --- /dev/null +++ b/src/external/rapidfuzz-cpp/test/distance/tests-LCSseq.cpp @@ -0,0 +1,240 @@ +#include +#include +#include + +#include + +#include "../common.hpp" + +using Catch::Matchers::WithinAbs; + +template +size_t lcs_seq_distance(const Sentence1& s1, const Sentence2& s2, + size_t max = std::numeric_limits::max()) +{ + size_t res1 = rapidfuzz::lcs_seq_distance(s1, s2, max); + size_t res2 = rapidfuzz::lcs_seq_distance(s1.begin(), s1.end(), s2.begin(), s2.end(), max); + size_t res3 = rapidfuzz::lcs_seq_distance( + BidirectionalIterWrapper(s1.begin()), BidirectionalIterWrapper(s1.end()), + BidirectionalIterWrapper(s2.begin()), BidirectionalIterWrapper(s2.end()), max); + rapidfuzz::CachedLCSseq scorer(s1); + size_t res4 = scorer.distance(s2, max); + size_t res5 = scorer.distance(s2.begin(), s2.end(), max); +#ifdef RAPIDFUZZ_SIMD + if (s1.size() <= 64) { + std::vector results(256 / 8); + + if (s1.size() <= 8) { + rapidfuzz::experimental::MultiLCSseq<8> simd_scorer(1); + simd_scorer.insert(s1); + simd_scorer.distance(&results[0], results.size(), s2, max); + REQUIRE(res1 == results[0]); + } + if (s1.size() <= 16) { + rapidfuzz::experimental::MultiLCSseq<16> simd_scorer(1); + simd_scorer.insert(s1); + simd_scorer.distance(&results[0], results.size(), s2, max); + REQUIRE(res1 == results[0]); + } + if (s1.size() <= 32) { + rapidfuzz::experimental::MultiLCSseq<32> simd_scorer(1); + simd_scorer.insert(s1); + simd_scorer.distance(&results[0], results.size(), s2, max); + REQUIRE(res1 == results[0]); + } + if (s1.size() <= 64) { + rapidfuzz::experimental::MultiLCSseq<64> simd_scorer(1); + simd_scorer.insert(s1); + simd_scorer.distance(&results[0], results.size(), s2, max); + REQUIRE(res1 == results[0]); + } + } +#endif + REQUIRE(res1 == res2); + REQUIRE(res1 == res3); + REQUIRE(res1 == res4); + REQUIRE(res1 == res5); + return res1; +} + +template +size_t lcs_seq_similarity(const Sentence1& s1, const Sentence2& s2, size_t max = 0) +{ + size_t res1 = rapidfuzz::lcs_seq_similarity(s1, s2, max); + size_t res2 = rapidfuzz::lcs_seq_similarity(s1.begin(), s1.end(), s2.begin(), s2.end(), max); + size_t res3 = rapidfuzz::lcs_seq_similarity( + BidirectionalIterWrapper(s1.begin()), BidirectionalIterWrapper(s1.end()), + BidirectionalIterWrapper(s2.begin()), BidirectionalIterWrapper(s2.end()), max); + rapidfuzz::CachedLCSseq scorer(s1); + size_t res4 = scorer.similarity(s2, max); + size_t res5 = scorer.similarity(s2.begin(), s2.end(), max); +#ifdef RAPIDFUZZ_SIMD + if (s1.size() <= 64) { + std::vector results(256 / 8); + + if (s1.size() <= 8) { + rapidfuzz::experimental::MultiLCSseq<8> simd_scorer(1); + simd_scorer.insert(s1); + simd_scorer.similarity(&results[0], results.size(), s2, max); + } + else if (s1.size() <= 16) { + rapidfuzz::experimental::MultiLCSseq<16> simd_scorer(1); + simd_scorer.insert(s1); + simd_scorer.similarity(&results[0], results.size(), s2, max); + } + else if (s1.size() <= 32) { + rapidfuzz::experimental::MultiLCSseq<32> simd_scorer(1); + simd_scorer.insert(s1); + simd_scorer.similarity(&results[0], results.size(), s2, max); + } + else { + rapidfuzz::experimental::MultiLCSseq<64> simd_scorer(1); + simd_scorer.insert(s1); + simd_scorer.similarity(&results[0], results.size(), s2, max); + } + + REQUIRE(res1 == results[0]); + } +#endif + REQUIRE(res1 == res2); + REQUIRE(res1 == res3); + REQUIRE(res1 == res4); + REQUIRE(res1 == res5); + return res1; +} + +template +double lcs_seq_normalized_distance(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 1.0) +{ + double res1 = rapidfuzz::lcs_seq_normalized_distance(s1, s2, score_cutoff); + double res2 = + rapidfuzz::lcs_seq_normalized_distance(s1.begin(), s1.end(), s2.begin(), s2.end(), score_cutoff); + double res3 = rapidfuzz::lcs_seq_normalized_distance( + BidirectionalIterWrapper(s1.begin()), BidirectionalIterWrapper(s1.end()), + BidirectionalIterWrapper(s2.begin()), BidirectionalIterWrapper(s2.end()), score_cutoff); + rapidfuzz::CachedLCSseq scorer(s1); + double res4 = scorer.normalized_distance(s2, score_cutoff); + double res5 = scorer.normalized_distance(s2.begin(), s2.end(), score_cutoff); + REQUIRE_THAT(res1, WithinAbs(res2, 0.0001)); + REQUIRE_THAT(res1, WithinAbs(res3, 0.0001)); + REQUIRE_THAT(res1, WithinAbs(res4, 0.0001)); + REQUIRE_THAT(res1, WithinAbs(res5, 0.0001)); + return res1; +} + +template +double lcs_seq_normalized_similarity(const Sentence1& s1, const Sentence2& s2, double score_cutoff = 0.0) +{ + double res1 = rapidfuzz::lcs_seq_normalized_similarity(s1, s2, score_cutoff); + double res2 = + rapidfuzz::lcs_seq_normalized_similarity(s1.begin(), s1.end(), s2.begin(), s2.end(), score_cutoff); + double res3 = rapidfuzz::lcs_seq_normalized_similarity( + BidirectionalIterWrapper(s1.begin()), BidirectionalIterWrapper(s1.end()), + BidirectionalIterWrapper(s2.begin()), BidirectionalIterWrapper(s2.end()), score_cutoff); + rapidfuzz::CachedLCSseq scorer(s1); + double res4 = scorer.normalized_similarity(s2, score_cutoff); + double res5 = scorer.normalized_similarity(s2.begin(), s2.end(), score_cutoff); + REQUIRE_THAT(res1, WithinAbs(res2, 0.0001)); + REQUIRE_THAT(res1, WithinAbs(res3, 0.0001)); + REQUIRE_THAT(res1, WithinAbs(res4, 0.0001)); + REQUIRE_THAT(res1, WithinAbs(res5, 0.0001)); + return res1; +} + +TEST_CASE("LCSseq") +{ + std::string test = "aaaa"; + std::string replace_all = "bbbb"; + + SECTION("similar strings") + { + REQUIRE(lcs_seq_distance(test, test) == 0); + REQUIRE(lcs_seq_similarity(test, test) == 4); + REQUIRE(lcs_seq_normalized_distance(test, test) == 0.0); + REQUIRE(lcs_seq_normalized_similarity(test, test) == 1.0); + } + + SECTION("completly different strings") + { + REQUIRE(rapidfuzz::lcs_seq_distance(test, replace_all) == 4); + REQUIRE(rapidfuzz::lcs_seq_similarity(test, replace_all) == 0); + REQUIRE(rapidfuzz::lcs_seq_normalized_distance(test, replace_all) == 1.0); + REQUIRE(rapidfuzz::lcs_seq_normalized_similarity(test, replace_all) == 0.0); + } + + SECTION("some tests for mbleven") + { + std::string a = "South Korea"; + std::string b = "North Korea"; + REQUIRE(lcs_seq_similarity(a, b) == 9); + REQUIRE(lcs_seq_similarity(a, b, 9) == 9); + REQUIRE(lcs_seq_similarity(a, b, 10) == 0); + + REQUIRE(lcs_seq_distance(a, b) == 2); + REQUIRE(lcs_seq_distance(a, b, 4) == 2); + REQUIRE(lcs_seq_distance(a, b, 3) == 2); + REQUIRE(lcs_seq_distance(a, b, 2) == 2); + REQUIRE(lcs_seq_distance(a, b, 1) == 2); + REQUIRE(lcs_seq_distance(a, b, 0) == 1); + + a = "aabc"; + b = "cccd"; + REQUIRE(lcs_seq_similarity(a, b) == 1); + REQUIRE(lcs_seq_similarity(a, b, 1) == 1); + REQUIRE(lcs_seq_similarity(a, b, 2) == 0); + + REQUIRE(lcs_seq_distance(a, b) == 3); + REQUIRE(lcs_seq_distance(a, b, 4) == 3); + REQUIRE(lcs_seq_distance(a, b, 3) == 3); + REQUIRE(lcs_seq_distance(a, b, 2) == 3); + REQUIRE(lcs_seq_distance(a, b, 1) == 2); + REQUIRE(lcs_seq_distance(a, b, 0) == 1); + } + + SECTION("testCachedImplementation") + { + std::string a = "001"; + std::string b = "220"; + REQUIRE(1 == rapidfuzz::lcs_seq_similarity(a, b)); + REQUIRE(1 == rapidfuzz::CachedLCSseq(a).similarity(b)); + } +} + +#ifdef RAPIDFUZZ_SIMD +TEST_CASE("SIMD wraparound") +{ + rapidfuzz::experimental::MultiLCSseq<8> scorer(4); + scorer.insert(std::string("a")); + scorer.insert(std::string("b")); + scorer.insert(std::string("aa")); + scorer.insert(std::string("bb")); + std::vector results(scorer.result_count()); + + { + std::string s2 = str_multiply(std::string("b"), 256); + scorer.distance(&results[0], results.size(), s2); + REQUIRE(results[0] == 256); + REQUIRE(results[1] == 255); + REQUIRE(results[2] == 256); + REQUIRE(results[3] == 254); + } + + { + std::string s2 = str_multiply(std::string("b"), 300); + scorer.distance(&results[0], results.size(), s2); + REQUIRE(results[0] == 300); + REQUIRE(results[1] == 299); + REQUIRE(results[2] == 300); + REQUIRE(results[3] == 298); + } + + { + std::string s2 = str_multiply(std::string("b"), 512); + scorer.distance(&results[0], results.size(), s2); + REQUIRE(results[0] == 512); + REQUIRE(results[1] == 511); + REQUIRE(results[2] == 512); + REQUIRE(results[3] == 510); + } +} +#endif diff --git a/src/external/rapidfuzz-cpp/test/distance/tests-Levenshtein.cpp b/src/external/rapidfuzz-cpp/test/distance/tests-Levenshtein.cpp new file mode 100644 index 00000000..69841ce3 --- /dev/null +++ b/src/external/rapidfuzz-cpp/test/distance/tests-Levenshtein.cpp @@ -0,0 +1,540 @@ +#include +#include +#include +#include +#include + +#include "examples/ocr.hpp" +#include "examples/pythonLevenshteinIssue9.hpp" +#include + +#include "../common.hpp" + +using Catch::Matchers::WithinAbs; + +template +size_t levenshtein_distance(const Sentence1& s1, const Sentence2& s2, + rapidfuzz::LevenshteinWeightTable weights = {1, 1, 1}, + size_t max = std::numeric_limits::max()) +{ + size_t res1 = rapidfuzz::levenshtein_distance(s1, s2, weights, max); + size_t res2 = rapidfuzz::levenshtein_distance(s1.begin(), s1.end(), s2.begin(), s2.end(), weights, max); + size_t res3 = rapidfuzz::levenshtein_distance( + BidirectionalIterWrapper(s1.begin()), BidirectionalIterWrapper(s1.end()), + BidirectionalIterWrapper(s2.begin()), BidirectionalIterWrapper(s2.end()), weights, max); + rapidfuzz::CachedLevenshtein scorer(s1, weights); + size_t res4 = scorer.distance(s2, max); + size_t res5 = scorer.distance(s2.begin(), s2.end(), max); +#ifdef RAPIDFUZZ_SIMD + if (weights.delete_cost == 1 && weights.insert_cost == 1 && weights.replace_cost == 1 && s1.size() <= 64) + { + std::vector results(256 / 8); + + if (s1.size() <= 8) { + rapidfuzz::experimental::MultiLevenshtein<8> simd_scorer(1); + simd_scorer.insert(s1); + simd_scorer.distance(&results[0], results.size(), s2, max); + REQUIRE(res1 == results[0]); + } + if (s1.size() <= 16) { + rapidfuzz::experimental::MultiLevenshtein<16> simd_scorer(1); + simd_scorer.insert(s1); + simd_scorer.distance(&results[0], results.size(), s2, max); + REQUIRE(res1 == results[0]); + } + if (s1.size() <= 32) { + rapidfuzz::experimental::MultiLevenshtein<32> simd_scorer(1); + simd_scorer.insert(s1); + simd_scorer.distance(&results[0], results.size(), s2, max); + REQUIRE(res1 == results[0]); + } + if (s1.size() <= 64) { + rapidfuzz::experimental::MultiLevenshtein<64> simd_scorer(1); + simd_scorer.insert(s1); + simd_scorer.distance(&results[0], results.size(), s2, max); + REQUIRE(res1 == results[0]); + } + } +#endif + REQUIRE(res1 == res2); + REQUIRE(res1 == res3); + REQUIRE(res1 == res4); + REQUIRE(res1 == res5); + return res1; +} + +template +std::vector get_subsequence(const std::vector& s, ptrdiff_t pos, ptrdiff_t len) +{ + return std::vector(std::begin(s) + pos, std::begin(s) + pos + len); +} + +template +double levenshtein_normalized_similarity(const Sentence1& s1, const Sentence2& s2, + rapidfuzz::LevenshteinWeightTable weights = {1, 1, 1}, + double score_cutoff = 0.0) +{ + double res1 = rapidfuzz::levenshtein_normalized_similarity(s1, s2, weights, score_cutoff); + double res2 = rapidfuzz::levenshtein_normalized_similarity(s1.begin(), s1.end(), s2.begin(), s2.end(), + weights, score_cutoff); + double res3 = rapidfuzz::levenshtein_normalized_similarity( + BidirectionalIterWrapper(s1.begin()), BidirectionalIterWrapper(s1.end()), + BidirectionalIterWrapper(s2.begin()), BidirectionalIterWrapper(s2.end()), weights, score_cutoff); + rapidfuzz::CachedLevenshtein scorer(s1, weights); + double res4 = scorer.normalized_similarity(s2, score_cutoff); + double res5 = scorer.normalized_similarity(s2.begin(), s2.end(), score_cutoff); + REQUIRE_THAT(res1, WithinAbs(res2, 0.0001)); + REQUIRE_THAT(res1, WithinAbs(res3, 0.0001)); + REQUIRE_THAT(res1, WithinAbs(res4, 0.0001)); + REQUIRE_THAT(res1, WithinAbs(res5, 0.0001)); + return res1; +} + +TEST_CASE("Levenshtein") +{ + std::string empty = ""; + std::string test = "aaaa"; + std::wstring no_suffix = L"aaa"; + std::string no_suffix2 = "aaab"; + std::string swapped1 = "abaa"; + std::string swapped2 = "baaa"; + std::string replace_all = "bbbb"; + + SECTION("levenshtein calculates empty sequence") + { + REQUIRE(levenshtein_distance(empty, empty) == 0); + REQUIRE(levenshtein_distance(test, empty) == 4); + REQUIRE(levenshtein_distance(empty, test) == 4); + } + + SECTION("levenshtein calculates correct distances") + { + REQUIRE(levenshtein_distance(test, test) == 0); + REQUIRE(levenshtein_distance(test, no_suffix) == 1); + REQUIRE(levenshtein_distance(swapped1, swapped2) == 2); + REQUIRE(levenshtein_distance(test, no_suffix2) == 1); + REQUIRE(levenshtein_distance(test, replace_all) == 4); + } + + SECTION("weighted levenshtein calculates correct distances") + { + REQUIRE(levenshtein_distance(test, test, {1, 1, 2}) == 0); + REQUIRE(levenshtein_distance(test, no_suffix, {1, 1, 2}) == 1); + REQUIRE(levenshtein_distance(swapped1, swapped2, {1, 1, 2}) == 2); + REQUIRE(levenshtein_distance(test, no_suffix2, {1, 1, 2}) == 2); + REQUIRE(levenshtein_distance(test, replace_all, {1, 1, 2}) == 8); + } + + SECTION("weighted levenshtein calculates correct ratios") + { + REQUIRE(levenshtein_normalized_similarity(test, test, {1, 1, 2}) == 1.0); + REQUIRE_THAT(levenshtein_normalized_similarity(test, no_suffix, {1, 1, 2}), + WithinAbs(0.8571, 0.0001)); + REQUIRE_THAT(levenshtein_normalized_similarity(swapped1, swapped2, {1, 1, 2}), + WithinAbs(0.75, 0.0001)); + REQUIRE_THAT(levenshtein_normalized_similarity(test, no_suffix2, {1, 1, 2}), WithinAbs(0.75, 0.0001)); + REQUIRE(levenshtein_normalized_similarity(test, replace_all, {1, 1, 2}) == 0.0); + } + + SECTION("test mbleven implementation") + { + std::string a = "South Korea"; + std::string b = "North Korea"; + REQUIRE(levenshtein_distance(a, b, {1, 1, 1}) == 2); + REQUIRE(levenshtein_distance(a, b, {1, 1, 1}, 4) == 2); + REQUIRE(levenshtein_distance(a, b, {1, 1, 1}, 3) == 2); + REQUIRE(levenshtein_distance(a, b, {1, 1, 1}, 2) == 2); + REQUIRE(levenshtein_distance(a, b, {1, 1, 1}, 1) == 2); + REQUIRE(levenshtein_distance(a, b, {1, 1, 1}, 0) == 1); + + REQUIRE(levenshtein_distance(a, b, {1, 1, 2}) == 4); + REQUIRE(levenshtein_distance(a, b, {1, 1, 2}, 4) == 4); + REQUIRE(levenshtein_distance(a, b, {1, 1, 2}, 3) == 4); + REQUIRE(levenshtein_distance(a, b, {1, 1, 2}, 2) == 3); + REQUIRE(levenshtein_distance(a, b, {1, 1, 2}, 1) == 2); + REQUIRE(levenshtein_distance(a, b, {1, 1, 2}, 0) == 1); + + a = "aabc"; + b = "cccd"; + REQUIRE(levenshtein_distance(a, b, {1, 1, 1}) == 4); + REQUIRE(levenshtein_distance(a, b, {1, 1, 1}, 4) == 4); + REQUIRE(levenshtein_distance(a, b, {1, 1, 1}, 3) == 4); + REQUIRE(levenshtein_distance(a, b, {1, 1, 1}, 2) == 3); + REQUIRE(levenshtein_distance(a, b, {1, 1, 1}, 1) == 2); + REQUIRE(levenshtein_distance(a, b, {1, 1, 1}, 0) == 1); + + REQUIRE(levenshtein_distance(a, b, {1, 1, 2}) == 6); + REQUIRE(levenshtein_distance(a, b, {1, 1, 2}, 6) == 6); + REQUIRE(levenshtein_distance(a, b, {1, 1, 2}, 5) == 6); + REQUIRE(levenshtein_distance(a, b, {1, 1, 2}, 4) == 5); + REQUIRE(levenshtein_distance(a, b, {1, 1, 2}, 3) == 4); + REQUIRE(levenshtein_distance(a, b, {1, 1, 2}, 2) == 3); + REQUIRE(levenshtein_distance(a, b, {1, 1, 2}, 1) == 2); + REQUIRE(levenshtein_distance(a, b, {1, 1, 2}, 0) == 1); + } + + SECTION("test banded implementation") + { + { + std::string s1 = "kkkkbbbbfkkkkkkibfkkkafakkfekgkkkkkkkkkkbdbbddddddddddafkkkekkkhkk"; + std::string s2 = "khddddddddkkkkdgkdikkccccckcckkkekkkkdddddddddddafkkhckkkkkdckkkcc"; + REQUIRE(levenshtein_distance(s1, s2, {1, 1, 1}) == 36); + REQUIRE(levenshtein_distance(s1, s2, {1, 1, 1}, 31) == 32); + } + + { + std::string s1 = "ccddcddddddddddddddddddddddddddddddddddddddddddddddddddddaaaaaaaaaaa"; + std::string s2 = "aaaaaaaaaaaaaadddddddddbddddddddddddddddddddddddddddddddddbddddddddd"; + REQUIRE(levenshtein_distance(s1, s2, {1, 1, 1}) == 26); + REQUIRE(levenshtein_distance(s1, s2, {1, 1, 1}, 31) == 26); + } + + { + std::string s1 = "accccccccccaaaaaaaccccccccccccccccccccccccccccccacccccccccccccccccccccccccccccc" + "ccccccccccccccccccccaaaaaaaaaaaaacccccccccccccccccccccc"; + std::string s2 = "ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" + "ccccccccccccccccccccccccccccccccccccbcccb"; + REQUIRE(levenshtein_distance(s1, s2, {1, 1, 1}) == 24); + REQUIRE(levenshtein_distance(s1, s2, {1, 1, 1}, 25) == 24); + } + + { + std::string s1 = "miiiiiiiiiiliiiiiiibghiiaaaaaaaaaaaaaaacccfccccedddaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaa"; + std::string s2 = + "aaaaaaajaaaaaaaabghiiaaaaaaaaaaaaaaacccfccccedddaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaajjdim"; + REQUIRE(levenshtein_distance(s1, s2, {1, 1, 1}) == 27); + REQUIRE(levenshtein_distance(s1, s2, {1, 1, 1}, 27) == 27); + } + + { + std::string s1 = "lllllfllllllllllllllllllllllllllllllllllllllllllllllllglllllilldcaaaaaaaaaaaaaa" + "aaaaadbbllllllllllhllllllllllllllllllllllllllgl"; + std::string s2 = "aaaaaaaaaaaaaadbbllllllllllllllelllllllllllllllllllllllllllllllglllllilldcaaaaa" + "aaaaaaaaaaaaaadbbllllllllllllllellllllllllllllhlllllllllill"; + REQUIRE(levenshtein_distance(s1, s2, {1, 1, 1}) == 23); + REQUIRE(levenshtein_distance(s1, s2, {1, 1, 1}, 27) == 23); + REQUIRE(levenshtein_distance(s1, s2, {1, 1, 1}, 28) == 23); + } + + { + std::string s1 = "llccacaaaaaaaaaccccccccccccccccddffaccccaccecccggggclallhcccccljif"; + std::string s2 = "bddcbllllllbcccccccccccccccccddffccccccccebcccggggclbllhcccccljifbddcccccc"; + REQUIRE(levenshtein_distance(s1, s2, {1, 1, 1}) == 27); + REQUIRE(levenshtein_distance(s1, s2, {1, 1, 1}, 27) == 27); + REQUIRE(levenshtein_distance(s1, s2, {1, 1, 1}, 28) == 27); + } + } +} + +TEST_CASE("Levenshtein_editops") +{ + std::string s = "Lorem ipsum."; + std::string d = "XYZLorem ABC iPsum"; + + rapidfuzz::Editops ops = rapidfuzz::levenshtein_editops(s, d); + REQUIRE(d == rapidfuzz::editops_apply_str(ops, s, d)); + REQUIRE(ops.get_src_len() == s.size()); + REQUIRE(ops.get_dest_len() == d.size()); +} + +TEST_CASE("Levenshtein_find_hirschberg_pos") +{ + { + std::string s1 = str_multiply(std::string("abb"), 2); + std::string s2 = str_multiply(std::string("ccccca"), 2); + + auto hpos = rapidfuzz::detail::find_hirschberg_pos(rapidfuzz::detail::Range(s1), + rapidfuzz::detail::Range(s2)); + REQUIRE(hpos.left_score == 5); + REQUIRE(hpos.right_score == 6); + REQUIRE(hpos.s2_mid == 6); + REQUIRE(hpos.s1_mid == 1); + } + + { + std::string s1 = str_multiply(std::string("abb"), 8 * 64); + std::string s2 = str_multiply(std::string("ccccca"), 8 * 64); + + auto hpos = rapidfuzz::detail::find_hirschberg_pos(rapidfuzz::detail::Range(s1), + rapidfuzz::detail::Range(s2)); + REQUIRE(hpos.left_score == 1280); + REQUIRE(hpos.right_score == 1281); + REQUIRE(hpos.s2_mid == 1536); + REQUIRE(hpos.s1_mid == 766); + } + + { + std::string s1 = "aaaa"; + std::string s2 = "bbbbbbaaaa"; + + auto hpos = rapidfuzz::detail::find_hirschberg_pos(rapidfuzz::detail::Range(s1), + rapidfuzz::detail::Range(s2)); + REQUIRE(hpos.left_score == 5); + REQUIRE(hpos.right_score == 1); + REQUIRE(hpos.s2_mid == 5); + REQUIRE(hpos.s1_mid == 0); + } +} + +TEST_CASE("Levenshtein_blockwise") +{ + { + std::string s1 = str_multiply(std::string("a"), 128); + std::string s2 = str_multiply(std::string("b"), 128); + REQUIRE(levenshtein_distance(s1, s2, {1, 1, 1}) == 128); + } +} + +TEST_CASE("Levenshtein_editops[fuzzing_regressions]") +{ + /* Test regressions of bugs found through fuzzing */ + { + std::string s1 = "b"; + std::string s2 = "aaaaaaaaaaaaaaaabbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + rapidfuzz::Editops ops = rapidfuzz::levenshtein_editops(s1, s2); + REQUIRE(s2 == rapidfuzz::editops_apply_str(ops, s1, s2)); + } + + { + std::string s1 = "aa"; + std::string s2 = "abb"; + rapidfuzz::Editops ops = rapidfuzz::levenshtein_editops(s1, s2); + REQUIRE(s2 == rapidfuzz::editops_apply_str(ops, s1, s2)); + } + + { + std::string s1 = str_multiply(std::string("abb"), 8 * 64); + std::string s2 = str_multiply(std::string("ccccca"), 8 * 64); + rapidfuzz::Editops ops = rapidfuzz::levenshtein_editops(s1, s2); + REQUIRE(s2 == rapidfuzz::editops_apply_str(ops, s1, s2)); + } +} + +TEST_CASE("Levenshtein small band") +{ + { + std::string s1 = + "kevZLemllleyOT1UNTKWeSOYYRKWKWeBGNWKXHK05RQKWZTMeHK2UMKTie3YKRRYKe3OINeJOKcc1OKJKWeKWNKHROINkevZ" + "LemllleyOT1UNTKWeSOYYRKWKWeBGNWKXHK05RQKWZTMeHK2UMKTie3YKRRYKe3OINeJOKccFGNReJKWeyNK3INROK4KTJKT" + "emumqdmumteGZLemqirniemumqdmunleGZLemuitleMKMKTemuinleOTeJKTccFGNReJKWeyNK3INROK4KTJKTemumqdmumt" + "eGZLemqirniemumqdmunleGZLemuitleMKMKTemuinleOTeJKTccJWKOeRKY2YKTezWOKJKTXPGNWKTkexZWINeWOINYKWRO" + "INKTeEVWZINe1ZWJKTemumqdmlmtersoeyNKTeMKjccJWKOeRKY2YKTezWOKJKTXPGNWKTkexZWINeWOINYKWROINKTeEVWZ" + "INe1ZWJKTemumqdmlmtersoeyNKTeMKjcc3INOKJKTieJkeOkesinleGZLePKemllieOTeJKWeMRKOINKTeFKOYeTKZeKOTM" + "KMGTMKTKeyNKTiemumueGRRKOTcc3INOKJKTieJkeOkesinleGZLePKemllieOTeJKWeMRKOINKTeFKOYeTKZeKOTMKMGTMK" + "TKeyNKTiemumueGRRKOTccoqueaetinfeMKMKTesinfegmummdmumohkccoqueaetinfeMKMKTesinfegmummdmumohkccyO" + "TKTeWKINYeKWNKHROINKTeD6IQMGTMeKWLZNWeJOKeFGNReJKWeAKHUWKTKTkewKYWZMe3OKccyOTKTeWKINYeKWNKHROINK" + "TeD6IQMGTMeKWLZNWeJOKeFGNReJKWeAKHUWKTKTkewKYWZMe3OKccmumqeTUINeqsoueZTJeHROKHe3OKe3USOYeTZWeZSe" + "mspeNOTYKWeJKWemumoeMKSKRJKYKTeFGNReJKWeAKjccmumqeTUINeqsoueZTJeHROKHe3OKe3USOYeTZWeZSemspeNOTYK" + "WeJKWemumoeMKSKRJKYKTeFGNReJKWeAKjccHUWKTKTe2ZW6IQie3Ue3GTQe3OKegmumqheGZLeprsqiegmumrheGZLeosnm" + "eZTJegmumsheMGWeGZLeomuoieZSeJGTTccHUWKTKTe2ZW6IQie3Ue3GTQe3OKegmumqheGZLeprsqiegmumrheGZLeosnme" + "ZTJegmumsheMGWeGZLeomuoieZSeJGTTccxOKeCKYNUJKe2ZWeyWWKINTZTMeJOK3KWeFOLLKWeZTJeJOKeFO33KWTe3KRH3" + "YeLOTJKTie3OINeOTeJKWeOSeCGOjccxOKeCKYNUJKe2ZWeyWWKINTZTMeJOK3KWeFOLLKWeZTJeJOKeFO33KWTe3KRH3YeL" + "OTJKTie3OINeOTeJKWeOSeCGOjccNKLYemunmeJKW"; + std::string s2 = + "ievZLemllleyOT1UNTKWeSOYYRKWKWeBGNWKXHK05RQKWZTMeHK2UMKTie3YKRRYKe3OINeJOKcc1OKJKWeKWNKHROINkev" + "LemllleyOT1UNTKWeSOYYRKWKWeBGNWKXHK05RQKWZTMeHK2UMKTie3YKRRYKe3OINeJOKccNReJKWeyNK3INROK4KTJKTem" + "umqdjmumteGZLemqirniemumqdjmunleGZLemuitleMKMKTemuinleOTeJKTccFGNReJKWeyNK3INROK4KTJKTemumqmumte" + "GZLemqirniemumqdmunleGZLemuitleMKMKTemuinleOTeJKTccJWKOeRKY2YKTkzWOKJKTXPGNWKTkexZWINeWOINYKWROI" + "NKTeEVWZINe1ZWJKTemumqjmlmtersoeyNKTeMKjccJWKOeRKY2YKTezWOKJKTXPGNWKTkexZWINeWOINYKWROINKTeEVWZI" + "Ne1ZWJKTemumqmlmtersoeyNKTeMKdccINOKJKTieJkeOkesinleGZLePKemllieOTeJKWeMRKOINKTeFKOYeTKZeKOTMKMG" + "TMKTKeyNKTiemumueGRRKOTcc3INOKJKTieJkeOkesinleGZLePKemllieOTeJKWeMRKOINKTeFKOYeTKZeKOTMKMGTMKTKe" + "yNKTiemumueGRRKOTccoqueEetinefeMKMKTesinbegmummdmumohkccoqueEetineseMKMKTesinfegmummdjemumohkccy" + "OTKTeWKINYebWNKHROINKTeD6IQMGTMeKWLZNWeJOKeFGNReJKWeAKHUWKTKTkewKYWZMe3OKccyOTKTeWKINYeKWNKHROIN" + "KTeD6IQMGTMeKWLZNWeJOKeFGNReJKWeAKHUWKTKTkewKYWZMe3OKccumqeTUINeqsoueZTJeVROKHe3OKe3USOYeTZWeZSe" + "mspeNOTYKWeJKWemumoeMKSKRJKYKTeFGNReJKWeAKdccmumqeTUINeqsoueZTJeHROKHe3OKe3USOYeTZWeZSemspeNOTYK" + "WeJKWemumoeMKSKRJKYKTeFGNReJKWeAKdccHUWKTKTe2ZW6IQie3Ue3GTQe3OKegmuhmqheGZLeprsqiegmumrheGZLeosn" + "meZTJegmumsheMGWeGZLeqmuoieZSeJGTTccHUWKTKTe2ZW6IQie3Ue3GTQe3OKegmumqheGZLeprsqiegmumrheGZLeosnm" + "eZTJegmumsheMGWeGZLeomuoieZSeJGTTccxOKeCKYNUJKe2ZWeyWWKINTZTMeJOK3KWeFOLLKWeZTJeJOKeFO33KWTe3KRH" + "3YeLOTJKTie3OINeOTeJKWeOSeCGOjccxOKeCKYNUJKe2ZWeyWWKINTZTMeJOK3KWeFOLLKWeZTJeJOKeFO33KWTe3KRH3Ye" + "LOTJKTie3OINeOTeJKWeOSeCGOdccNKLYemunmeJKWk"; + + rapidfuzz::Editops ops1; + rapidfuzz::detail::levenshtein_align(ops1, rapidfuzz::detail::Range(s1), + rapidfuzz::detail::Range(s2)); + REQUIRE(s2 == rapidfuzz::editops_apply_str(ops1, s1, s2)); + rapidfuzz::Editops ops2; + rapidfuzz::detail::levenshtein_align(ops2, rapidfuzz::detail::Range(s1), rapidfuzz::detail::Range(s2), + ops1.size()); + REQUIRE(ops1 == ops2); + } + + { + std::string s1 = + "GdFGRdyKGTGRfdVPNdkmhdwUMKdjpjnccXUdGRTGKMGOhdsUREJdFKGdrUOFGSRCTSVGRPRFOUOIdeXUNdzEJUTXGdFGRdyK" + "GTGRfdVPNdkmhdwUMKdjpjnccKOdAGRDKOFUOIdNKTdFGNdtRMCZdFGSdyKOKYTGRSdFGSdvOOGROdVPNdlihdqUIUYTdjpj" + "ndUOFdFGRdyKGTGRaccKOdAGRDKOFUOIdNKTdFGNdtRMCZdFGSdyKOKYTGRSdFGSdvOOGROdVPNdlihdqUIUYTdjpjndUOFd" + "FGRdyKGTGRaccYEJUTXVGRPRFOUOIdeVPNdklhdzGQTGNDGRdjpjofdWURFGdFCSdtKOKIUOISCNTdHGROGRdGRN0EJTKITg" + "dCUHccYEJUTXVGRPRFOUOIdeVPNdklhdzGQTGNDGRdjpjofdWURFGdFCSdtKOKIUOISCNTdHGROGRdGRN0EJTKITgdCUHccq" + "ORUHGOdGKOGSdyKGTGRSd2DGRdFKGdBKRLYCNLGKTdGKOGRdx2OFKIUOIdFGSdAGRNKGTGRSgd2DGRdFKGccqORUHGOdGKOG" + "SdyKGTGRSd2DGRdFKGdBKRLYCNLGKTdGKOGRdx2OFKIUOIdFGSdAGRNKGTGRSgd2DGRdFKGccuPRTYGTXUOIdFGSdyKGTVGR" + "J0MTOKYYGSgdCUEJdWGOOdLGKOGdx2OFKIUOIdVPRMKGITgdDKSdXURdsCUGRdGKOGSccuPRTYGTXUOIdFGSdyKGTVGRJ0MT" + "OKYYGSgdCUEJdWGOOdLGKOGdx2OFKIUOIdVPRMKGITgdDKSdXURdsCUGRdGKOGSccwCJRGSdYPWKGd2DGRdGKOGdtRJ1JUOI" + "dFGSdyKGTXKOYGSdeKNduCMMGdFGRduPRTYGTXUOIfdXUdDGSTKNNGOgccwCJRGSdYPWKGd2DGRdGKOGdtRJ1JUOIdFGSdyK" + "GTXKOYGSdeKNduCMMGdFGRduPRTYGTXUOIfdXUdDGSTKNNGOgccCUHdqORUHGOdGKOGSdAGRNKGTGRSdGKOGOdNKTdGKOGNd" + "OGUGOdyKGTGRdCDIGYEJMPYYGOGOdyKGTVGRTRCIgccCUHdqORUHGOdGKOGSdAGRNKGTGRSdGKOGOdNKTdGKOGNdOGUGOdyK" + "GTGRdCDIGYEJMPYYGOGOdyKGTVGRTRCIgccFGYYGOdtRH2MMUOIdVPOdGKOGRdtOTYEJGKFUOId2DGRdFKGdFRGKdGDGOdIG" + "OCOOTGOdu0MMGdPFGRdVPRdGKOGNccFGYYGOdtRH2MMUOIdVPOdGKOGRdtOTYEJGKFUOId2DGRdFKGdFRGKdGDGOdIGOCOOT" + "GOdu0MMGdPFGRdVPRdGKOGNccAGRIMGKEJdVPRdFGNdyKGTGKOKIUOISCNTdIGTRPHHGOdWKRFgdNKTdR2ELWKRLGOFGRdxR" + "CHTdCUHXUJGDGOhccAGRIMGKEJdVPRdFGNdyKGTGKOKIUOISCNTdIGTRPHHGOdWKRFgdNKTdR2ELWKRLGOFGRdxRCHTdCUHX" + "UJGDGOhccu"; + std::string s2 = + "SdFGRdyKGTGRfdFPNdkmhdwUMKdjpjndVccXUdGRTGKMGOhdsUREJdFKGdrUOFGSRCTSVGRPRFOUOIdeXUNdzEJUTXGdFGRd" + "yKGTGRfdVPNdkmhdwUMKdjpjnccbzGRDKOFUOIdNKTdFGNdtRMCZdFGSdyKOKYTGRSdFGSdvOOGROdVPNdlihdqUIUYTdjpj" + "ndUOFdFGRdyKGTGRbccKOdAGRDKOFUOIdNKTdFGNdtRMCZdFGSdyKOKYTGRSdFGSdvOOGROdVPNdlihdqUIUYTdjpjndUOFd" + "FGRdyKGTGRbccYEJUTXVGRPRFOUOIdeVPNdklhdzGQTGNDGRdjpjofdWURFGdFCSdtKOKIUOISCNTdHGROGRdGRN0EJTKITg" + "dCUHccYEJUTXVGRPRFOUOIdeVPNdklhdzGQTGNDGRdjpjofdWURFGdFCSdtKOKIUOISCNTdHGROGRdGRN0EJTKITgdCUHccq" + "ORUHGOhdGKOGSdyKGTGRSd2DGRdFKGdBKRLYCNLGKTdGKOGRdx2OFKIUOIdFGSdAGRNKGTGRSgd2DGRdFKGccqORUHGOdGKO" + "GSdyKGTGRSd2DGRdFKGdBKRLYCNLGKTdGKOGRdx2OFKIUOIdFGSdAGRNKGTGRSgd2DGRdFKGccVPRTYGTXUOIdFGSdyKGTVG" + "RJ0MTOKYYGSgdCUEJdWGOOdLGKOGdx2OFKIUOIdVPRMKGITgdDKSdXURdsCUGRdGKOGSccuPRTYGTXUOIdFGSdyKGTVGRJ0M" + "TOKYYGSgdCUEJdWGOOdLGKOGdx2OFKIUOIdVPRMKGITgdDKSdXURdsCUGRdGKOGSccwCJRGSdYPWKGd2DGRdGKOGdtRJ1JUO" + "IdFGSdyKGTXKOYGSdeKNduCMMGdFGRduPRTYGTXUOIfdXUDDGSTKNNGOgccwCJRGSdYPWKGd2DGRdGKOGdtRJ1JUOIdFGSdy" + "KGTXKOYGSdeKNduCMMGdFGRduPRTYGTXUOIfdXUdDGSTKNNGOgccCUHdqORUHGOdGKOGSdAGRNKGTGRSdGKOGOdNKTdGKOGN" + "dOGUGOdyKGTGRdCDIGYEJMPYYGOGOdyKGTVGRTRCIgccCUHdqORUHGOdGKOGSdAGRNKGTGRSdGKOGOdNKTdGKOGNdOGUGOdy" + "KGTGRdCDIGYEJMPYYGOGOdyKGTVGRTRCIgccbFGYYGOdtRH2MMUOIdVPOdGKOGRdtOTYEJGKFUOId2DGTdFKGdFRGKdGDGOd" + "IGOCOOTGOdu0MMGdPFGRdVPRdGKOGNccFGYYGOdtRH2MMUOIdVPOdGKOGRdtOTYEJGKFUOId2DGRdFKGdFRGKdGDGOdIGOCO" + "OTGOdu0MMGdPFGRdVPRdGKOGNccAGRIMGKEJdVPRdFGNdyKGTGKOKIUOISCNTdIGTRPHHGOdWKRFgdNKTdR2ELWKRLGOFGRd" + "xRCHTdCUHXUJGDGOhccAGRIMGKEJdVPRdFGNdyKGTGKOKIUOISCNTdIGTRPHHGOdWKRFgdNKTdR2ELWKRLGOFGRdxRCHTdCU" + "HXUJGDGOhccZ"; + + rapidfuzz::Editops ops1; + rapidfuzz::detail::levenshtein_align(ops1, rapidfuzz::detail::Range(s1), + rapidfuzz::detail::Range(s2)); + REQUIRE(s2 == rapidfuzz::editops_apply_str(ops1, s1, s2)); + rapidfuzz::Editops ops2; + rapidfuzz::detail::levenshtein_align(ops2, rapidfuzz::detail::Range(s1), rapidfuzz::detail::Range(s2), + ops1.size()); + REQUIRE(ops1 == ops2); + } +} + +TEST_CASE("Levenshtein large band (python-Levenshtein issue 9)") +{ + using namespace pythonLevenshteinIssue9; + + REQUIRE(example1.size() == 5227); + REQUIRE(example2.size() == 5569); + + { + std::vector s1 = get_subsequence(example1, 3718, 1509); + std::vector s2 = get_subsequence(example2, 2784, 2785); + + REQUIRE(rapidfuzz::levenshtein_distance(s1, s2) == 1587); + + rapidfuzz::Editops ops1 = rapidfuzz::levenshtein_editops(s1, s2); + REQUIRE(ops1.size() == 1587); + REQUIRE(s2 == rapidfuzz::editops_apply_vec(ops1, s1, s2)); + } + + { + REQUIRE(rapidfuzz::levenshtein_distance(example1, example2) == 2590); + rapidfuzz::Editops ops1 = rapidfuzz::levenshtein_editops(example1, example2); + REQUIRE(ops1.size() == 2590); + REQUIRE(example2 == rapidfuzz::editops_apply_vec(ops1, example1, example2)); + } +} + +TEST_CASE("Levenshtein large band (ocr example)") +{ + REQUIRE(ocr_example1.size() == 106514); + REQUIRE(ocr_example2.size() == 107244); + + { + std::vector s1 = get_subsequence(ocr_example1, 51, 6541); + std::vector s2 = get_subsequence(ocr_example2, 51, 6516); + + rapidfuzz::Editops ops1; + rapidfuzz::detail::levenshtein_align(ops1, rapidfuzz::detail::Range(s1), + rapidfuzz::detail::Range(s2)); + REQUIRE(s2 == rapidfuzz::editops_apply_vec(ops1, s1, s2)); + rapidfuzz::Editops ops2; + rapidfuzz::detail::levenshtein_align(ops2, rapidfuzz::detail::Range(s1), rapidfuzz::detail::Range(s2), + ops1.size()); + REQUIRE(ops1 == ops2); + } + + { + auto dist = rapidfuzz::levenshtein_distance(ocr_example1, ocr_example2, {1, 1, 1}); + REQUIRE(dist == 5278); + } + { + auto dist = rapidfuzz::levenshtein_distance(ocr_example1, ocr_example2, {1, 1, 1}, 2500); + REQUIRE(dist == 2501); + } + { + rapidfuzz::Editops ops1 = rapidfuzz::levenshtein_editops(ocr_example1, ocr_example2); + REQUIRE(ops1.size() == 5278); + REQUIRE(ocr_example2 == rapidfuzz::editops_apply_vec(ops1, ocr_example1, ocr_example2)); + } + { + rapidfuzz::Editops ops1 = rapidfuzz::levenshtein_editops(ocr_example1, ocr_example2, 5278); + REQUIRE(ops1.size() == 5278); + REQUIRE(ocr_example2 == rapidfuzz::editops_apply_vec(ops1, ocr_example1, ocr_example2)); + } + { + rapidfuzz::Editops ops1 = rapidfuzz::levenshtein_editops(ocr_example1, ocr_example2, 2000); + REQUIRE(ops1.size() == 5278); + REQUIRE(ocr_example2 == rapidfuzz::editops_apply_vec(ops1, ocr_example1, ocr_example2)); + } +} + +#ifdef RAPIDFUZZ_SIMD +TEST_CASE("SIMD wraparound") +{ + rapidfuzz::experimental::MultiLevenshtein<8> scorer(4); + scorer.insert(std::string("a")); + scorer.insert(std::string("b")); + scorer.insert(std::string("aa")); + scorer.insert(std::string("bb")); + std::vector results(scorer.result_count()); + + { + std::string s2 = str_multiply(std::string("b"), 256); + scorer.distance(&results[0], results.size(), s2); + REQUIRE(results[0] == 256); + REQUIRE(results[1] == 255); + REQUIRE(results[2] == 256); + REQUIRE(results[3] == 254); + } + + { + std::string s2 = str_multiply(std::string("b"), 300); + scorer.distance(&results[0], results.size(), s2); + REQUIRE(results[0] == 300); + REQUIRE(results[1] == 299); + REQUIRE(results[2] == 300); + REQUIRE(results[3] == 298); + } + + { + std::string s2 = str_multiply(std::string("b"), 512); + scorer.distance(&results[0], results.size(), s2); + REQUIRE(results[0] == 512); + REQUIRE(results[1] == 511); + REQUIRE(results[2] == 512); + REQUIRE(results[3] == 510); + } +} + +TEST_CASE("SIMD") +{ + SECTION("multiple sequences") + { + std::string s2 = "0"; + size_t count = 256 / 32 + 1; + rapidfuzz::experimental::MultiLevenshtein<32> scorer(count); + for (size_t i = 0; i < count - 1; ++i) + scorer.insert(std::string("")); + + scorer.insert(std::string("00000000000000000")); + + std::vector results(scorer.result_count()); + scorer.distance(&results[0], results.size(), s2); + + for (size_t i = 0; i < count - 1; ++i) + REQUIRE(results[i] == 1); + + REQUIRE(results[count - 1] == 16); + } +} +#endif diff --git a/src/external/rapidfuzz-cpp/test/distance/tests-OSA.cpp b/src/external/rapidfuzz-cpp/test/distance/tests-OSA.cpp new file mode 100644 index 00000000..a5f7147b --- /dev/null +++ b/src/external/rapidfuzz-cpp/test/distance/tests-OSA.cpp @@ -0,0 +1,92 @@ +#include +#include +#include +#include + +#include "../common.hpp" + +template +size_t osa_distance(const Sentence1& s1, const Sentence2& s2, size_t max = std::numeric_limits::max()) +{ + size_t res1 = rapidfuzz::osa_distance(s1, s2, max); + size_t res2 = rapidfuzz::osa_distance(s1.begin(), s1.end(), s2.begin(), s2.end(), max); + size_t res3 = rapidfuzz::osa_distance( + BidirectionalIterWrapper(s1.begin()), BidirectionalIterWrapper(s1.end()), + BidirectionalIterWrapper(s2.begin()), BidirectionalIterWrapper(s2.end()), max); + rapidfuzz::CachedOSA scorer(s1); + size_t res4 = scorer.distance(s2, max); + size_t res5 = scorer.distance(s2.begin(), s2.end(), max); +#ifdef RAPIDFUZZ_SIMD + if (s1.size() <= 64) { + std::vector results(256 / 8); + + if (s1.size() <= 8) { + rapidfuzz::experimental::MultiOSA<8> simd_scorer(1); + simd_scorer.insert(s1); + simd_scorer.distance(&results[0], results.size(), s2, max); + REQUIRE(res1 == results[0]); + } + if (s1.size() <= 16) { + rapidfuzz::experimental::MultiOSA<16> simd_scorer(1); + simd_scorer.insert(s1); + simd_scorer.distance(&results[0], results.size(), s2, max); + REQUIRE(res1 == results[0]); + } + if (s1.size() <= 32) { + rapidfuzz::experimental::MultiOSA<32> simd_scorer(1); + simd_scorer.insert(s1); + simd_scorer.distance(&results[0], results.size(), s2, max); + REQUIRE(res1 == results[0]); + } + if (s1.size() <= 64) { + rapidfuzz::experimental::MultiOSA<64> simd_scorer(1); + simd_scorer.insert(s1); + simd_scorer.distance(&results[0], results.size(), s2, max); + REQUIRE(res1 == results[0]); + } + } +#endif + REQUIRE(res1 == res2); + REQUIRE(res1 == res3); + REQUIRE(res1 == res4); + REQUIRE(res1 == res5); + return res1; +} + +/* test some very simple cases of the osa distance */ +TEST_CASE("osa[simple]") +{ + { + std::string s1 = ""; + std::string s2 = ""; + REQUIRE(osa_distance(s1, s2) == 0); + } + + { + std::string s1 = "aaaa"; + std::string s2 = ""; + REQUIRE(osa_distance(s1, s2) == 4); + REQUIRE(osa_distance(s2, s1) == 4); + REQUIRE(osa_distance(s1, s2, 1) == 2); + REQUIRE(osa_distance(s2, s1, 1) == 2); + } + + { + std::string s1 = "CA"; + std::string s2 = "ABC"; + REQUIRE(osa_distance(s1, s2) == 3); + } + + { + std::string s1 = "CA"; + std::string s2 = "AC"; + REQUIRE(osa_distance(s1, s2) == 1); + } + + { + std::string filler = str_multiply(std::string("a"), 64); + std::string s1 = std::string("a") + filler + "CA" + filler + std::string("a"); + std::string s2 = std::string("b") + filler + "AC" + filler + std::string("b"); + REQUIRE(osa_distance(s1, s2) == 3); + } +} diff --git a/src/external/rapidfuzz-cpp/test/tests-common.cpp b/src/external/rapidfuzz-cpp/test/tests-common.cpp new file mode 100644 index 00000000..0e66fe94 --- /dev/null +++ b/src/external/rapidfuzz-cpp/test/tests-common.cpp @@ -0,0 +1,35 @@ +#include + +#include + +TEST_CASE("remove affix") +{ + std::string s1 = "aabbbbaaaa"; + std::string s2 = "aaabbbbaaaaa"; + + { + rapidfuzz::detail::Range s1_(s1); + rapidfuzz::detail::Range s2_(s2); + REQUIRE(rapidfuzz::detail::remove_common_prefix(s1_, s2_) == 2); + REQUIRE(s1_ == rapidfuzz::detail::Range("bbbbaaaa")); + REQUIRE(s2_ == rapidfuzz::detail::Range("abbbbaaaaa")); + } + + { + rapidfuzz::detail::Range s1_(s1); + rapidfuzz::detail::Range s2_(s2); + REQUIRE(rapidfuzz::detail::remove_common_suffix(s1_, s2_) == 4); + REQUIRE(s1_ == rapidfuzz::detail::Range("aabbbb")); + REQUIRE(s2_ == rapidfuzz::detail::Range("aaabbbba")); + } + + { + rapidfuzz::detail::Range s1_(s1); + rapidfuzz::detail::Range s2_(s2); + auto affix = rapidfuzz::detail::remove_common_affix(s1_, s2_); + REQUIRE(affix.prefix_len == 2); + REQUIRE(affix.suffix_len == 4); + REQUIRE(s1_ == rapidfuzz::detail::Range("bbbb")); + REQUIRE(s2_ == rapidfuzz::detail::Range("abbbba")); + } +} diff --git a/src/external/rapidfuzz-cpp/test/tests-fuzz.cpp b/src/external/rapidfuzz-cpp/test/tests-fuzz.cpp new file mode 100644 index 00000000..5a2f111c --- /dev/null +++ b/src/external/rapidfuzz-cpp/test/tests-fuzz.cpp @@ -0,0 +1,245 @@ +#include + +#include + +#include "common.hpp" + +namespace fuzz = rapidfuzz::fuzz; + +using MetricPtr = double (*)(const char*, const char*, double); +struct Metric { + MetricPtr call; + const char* name; + bool symmetric; +}; + +#define LIST_OF_METRICS(FUNC) \ + /* func symmetric */ \ + FUNC(fuzz::ratio, true) \ + FUNC(fuzz::partial_ratio, false) \ + FUNC(fuzz::token_set_ratio, true) \ + FUNC(fuzz::token_sort_ratio, true) \ + FUNC(fuzz::token_ratio, true) \ + FUNC(fuzz::partial_token_set_ratio, false) \ + FUNC(fuzz::partial_token_sort_ratio, false) \ + FUNC(fuzz::partial_token_ratio, false) \ + FUNC(fuzz::WRatio, false) \ + FUNC(fuzz::QRatio, true) + +#define CREATE_METRIC(func, symmetric) \ + Metric{[](const char* s1, const char* s2, double score_cutoff) { return func(s1, s2, score_cutoff); }, \ + #func, symmetric}, + +std::vector metrics = {LIST_OF_METRICS(CREATE_METRIC)}; + +void score_test(double expected, double input) +{ + REQUIRE(input <= 100); + REQUIRE(input >= 0); + REQUIRE_THAT(input, Catch::Matchers::WithinAbs(expected, 0.000001)); +} + +/** + * @name RatioTest + * + * @todo Enable 'testPartialTokenSortRatio' once the function is implemented + */ +TEST_CASE("RatioTest") +{ + const std::string s1 = "new york mets"; + const std::string s2 = "new YORK mets"; + const std::string s3 = "the wonderful new york mets"; + const std::string s4 = "new york mets vs atlanta braves"; + const std::string s5 = "atlanta braves vs new york mets"; + const std::string s6 = "new york mets - atlanta braves"; + const std::string s7 = "new york city mets - atlanta braves"; + // test silly corner cases + const std::string s8 = "{"; + const std::string s9 = "{a"; + const std::string s10 = "a{"; + const std::string s10a = "{b"; + + SECTION("testEqual") + { + score_test(100, fuzz::ratio(s1, s1)); + score_test(100, fuzz::ratio("test", "test")); + score_test(100, fuzz::ratio(s8, s8)); + score_test(100, fuzz::ratio(s9, s9)); + } + + SECTION("testPartialRatio") + { + score_test(100, fuzz::partial_ratio(s1, s1)); + score_test(65, fuzz::ratio(s1, s3)); + score_test(100, fuzz::partial_ratio(s1, s3)); + } + + SECTION("testTokenSortRatio") + { + score_test(100, fuzz::token_sort_ratio(s1, s1)); + score_test(100, fuzz::token_sort_ratio("metss new york hello", "metss new york hello")); + } + + SECTION("testTokenSetRatio") + { + score_test(100, fuzz::token_set_ratio(s4, s5)); + score_test(100, fuzz::token_set_ratio(s8, s8)); + score_test(100, fuzz::token_set_ratio(s9, s9)); + score_test(50, fuzz::token_set_ratio(s10, s10a)); + } + + SECTION("testPartialTokenSetRatio") + { + score_test(100, fuzz::partial_token_set_ratio(s4, s7)); + } + + SECTION("testWRatioEqual") + { + score_test(100, fuzz::WRatio(s1, s1)); + } + + SECTION("testWRatioPartialMatch") + { + // a partial match is scaled by .9 + score_test(90, fuzz::WRatio(s1, s3)); + } + + SECTION("testWRatioMisorderedMatch") + { + // misordered full matches are scaled by .95 + score_test(95, fuzz::WRatio(s4, s5)); + } + + SECTION("testTwoEmptyStrings") + { + score_test(100, fuzz::ratio("", "")); + score_test(100, fuzz::partial_ratio("", "")); + score_test(100, fuzz::token_sort_ratio("", "")); + score_test(0, fuzz::token_set_ratio("", "")); + score_test(100, fuzz::partial_token_sort_ratio("", "")); + score_test(0, fuzz::partial_token_set_ratio("", "")); + score_test(100, fuzz::token_ratio("", "")); + score_test(100, fuzz::partial_token_ratio("", "")); + score_test(0, fuzz::WRatio("", "")); + score_test(0, fuzz::QRatio("", "")); + } + + SECTION("testFirstStringEmpty") + { + for (auto& metric : metrics) { + INFO("Score not 0 for " << metric.name); + score_test(0, metric.call("test", "", 0)); + } + } + + SECTION("testSecondStringEmpty") + { + for (auto& metric : metrics) { + INFO("Score not 0 for " << metric.name); + score_test(0, metric.call("", "test", 0)); + } + } + + SECTION("testPartialRatioShortNeedle") + { + score_test(33.3333333, fuzz::partial_ratio("001", "220222")); + score_test(100, fuzz::partial_ratio("physics 2 vid", "study physics physics 2 video")); + } + + SECTION("testIssue206") /* test for https://github.com/rapidfuzz/RapidFuzz/issues/206 */ + { + const char* str1 = "South Korea"; + const char* str2 = "North Korea"; + + for (auto& metric : metrics) { + double score = metric.call(str1, str2, 0); + INFO("score_cutoff does not work correctly for " << metric.name); + score_test(0, metric.call(str1, str2, score + 0.0001)); + score_test(score, metric.call(str1, str2, score - 0.0001)); + } + } + + SECTION("testIssue210") /* test for https://github.com/rapidfuzz/RapidFuzz/issues/210 */ + { + const char* str1 = "bc"; + const char* str2 = "bca"; + + for (auto& metric : metrics) { + double score = metric.call(str1, str2, 0); + INFO("score_cutoff does not work correctly for " << metric.name); + score_test(0, metric.call(str1, str2, score + 0.0001)); + score_test(score, metric.call(str1, str2, score - 0.0001)); + } + } + + SECTION("testIssue231") /* test for https://github.com/rapidfuzz/RapidFuzz/issues/231 */ + { + const char* str1 = "er merkantilismus f/rderte handel und verkehr mit teils marktkonformen, teils " + "dirigistischen ma_nahmen."; + const char* str2 = "ils marktkonformen, teils dirigistischen ma_nahmen. an der schwelle zum 19. " + "jahrhundert entstand ein neu"; + + auto alignment = fuzz::partial_ratio_alignment(str1, str2); + score_test(66.2337662, alignment.score); + REQUIRE(alignment.src_start == 0); + REQUIRE(alignment.src_end == 103); + REQUIRE(alignment.dest_start == 0); + REQUIRE(alignment.dest_end == 51); + } + + SECTION("testIssue257") /* test for https://github.com/rapidfuzz/RapidFuzz/issues/257 */ + { + const char* str1 = "aaaaaaaaaaaaaaaaaaaaaaaabacaaaaaaaabaaabaaaaaaaababbbbbbbbbbabbcb"; + const char* str2 = "aaaaaaaaaaaaaaaaaaaaaaaababaaaaaaaabaaabaaaaaaaababbbbbbbbbbabbcb"; + + score_test(98.4615385, fuzz::partial_ratio(str1, str2)); + score_test(98.4615385, fuzz::partial_ratio(str2, str1)); + } + + SECTION("testIssue257") /* test for https://github.com/rapidfuzz/RapidFuzz/issues/219 */ + { + const char* str1 = + "TTAGCGCTACCGGTCGCCACCATGGTTTTCTAAGGGGAGGCCGTCATCAAAAGAGTTCATGTAGCACGAAGTCCACCTTTGAAGGATCGATGAATG" + "GCCATGAATTCGAAATCGAGGGGAGGGCGAGAGAGGGCCGGCCTTACGAGGGCACACCCAAACTGCCAAACTGAAAGTGACCAAAGGCGGCCCGTT" + "ACCATTCTCCTGGGACATACTGTAAGTGCATGGCACCACGCTCTATTTCTTAAAAAAAGTGTAGGGTCTGGCGCCCTCGGGGGCGGCTTAGGAAAA" + "GAGGCCTGACCAATTTTTGTCTCTTATAGGTCACCACAGTTCATGTACGGAAGCAGAGCGTTCACGAAGCACCCAGCTGACATCCCGGACTACTAT" + "GACAGAGCTTCCCGGAAGGACTCAAGTGGGAGCGGGTCATGAACTTCGAGGACGGTGGGGCAGTGACTGTGACACAGGACACCAGCCTGAAGATGG" + "AACTCTTATCTACAAAGTAAAGCTAAGAGGAACCAACTTCCCGCCAGATGGGCCCGTTATGCAAAAGAAAACGATGGGGTGGGAAGCTTCTGCAGA" + "GCGCCTTTACCCCGAGGATGGCGTCCTTAAGGGGGATATCAAAATGGCGCTACGCCTTAAGGATGGAGGCAGATATTTGGCAGACTTCAAAACAAC" + "ATTACAAGGCGAAGAAGCCAGTCCAGATGCCTGGAGCTTGCAATGGTAAGCACCTCTGCCTGCCCCGCTAGTTGGGTGTGAGTGGCCCAGGCAGCC" + "GCCTGCATTTAGCTCTAGCCGGGGTACGGGTGCCCCTTGATGCCTGAGGCCTCTCCTGTGGCTGAGGCGACTGGCCCAGAGTCTGGGTCTCCTCGA" + "GGGTGGCCATCTGGCGTCACCTGTCATCTGCCACCTCTGACCCCTGCCTCTCTCCTCACAGTTGACCGGAAGCTCGACATAACGAGTCACAACGAG" + "GACTACACAGTTGTCGAGCAGTACGAACGTTCCGAGGGTCGACACTCAACTGGCAGGATGGATGAGCTTTTACAAAGGGCGGGGGCGGAGGAAGCG" + "GAGGAGGAGGAAGTGGTGGAGGAGGCTCGAAAGGTAAGTATCAGGGTTGCAGCGTTTCTCTGACCTCATATTCCAATGGATGTGTGAGAAGCATAG" + "TGAGATCCGTTTACCCCTTTTGCTCAATTCTCACGTGGCTGTAGTCGTGTTTATAAGTCTGATCGTAATGGCAGCTTGGTCTGCGTGCCTTGAAAT" + "TGTGGCCCCCACATGCATAATAAACGATCCTCTAGCACTACTTTCTGTCGAGCCACCTCAGCGCCCGTACAGTAATGTCTACAGCGCGTCTAACCC" + "GACAAATGCGTTTCTTTCTCTCCTAGAACGAAAGATTACGGATCACAGAAACGTCTCGGAAAGTCCAAATAGAAAGAACGAGAAAGAAGAAAGTGA" + "AGGATCACAAGAGCAACTCGAAAGAAAGAGACATAAGAAGGAACTCAGAAAAGGATGACAAGTATAAAAACAAAGTGAAGAAAAGAGCGAAGAGCA" + "GAGTAGAAGCAAGAGTAAAGAGAAGAAGAGCAAATCGAAGGAAAGGTAAGTGGCTTTCAAGAACATTGGTAAAACGTCATGTGTATTGCGGTTCCA" + "TGCTTACACAAATTCGTTCGCTTGTTTTCAGGGACTCGAAACACAACAGAAACGAAGAGAAGAGAATGAGAAGCAGAAGCAAAGGAAGAGACCATG" + "AAAATGTCAAGGAAAAAGAAAAAACAGTCCGATAGCAAAGGCAAAGACCAGGAGCGGTCTCGGTCGAAGGAAAAATCTAAACAACTTGAATCAAAA" + "TCTAACGAGCATGGTAAGTTCGCGAGACACTAAGTTGATTCTTAGTGTTTAGACGTGAAACTCCCTTGGAAGGTTTAACGAATACTGTTAATATTT" + "TCAGATCACTCAAAATCCAAAAGAACCGACGGGCACAATCCCGGAGCCGTGAATGTGATATAACCAAGGAAGCACAGTTGCAATTCGAGAACAAGA" + "GAAAGAAGCAGAAGTAGAGAGATCGCTCGAGAAGAGTGAGAAGCAGAACACATGATAGAGACAGAAGCCGGTCGAAAGAATACCACCGCTACAGAG" + "AACAAGGTAAGCATGACTACTTGAGTGTAAATACGTTGTGATAGAGATGAAAAACAAAACCGAACATTACTTTGGGTAATAATTAACTTTTTTTTA" + "ATAGAATATCGGGAGAAAGGAAGGTCGAGAAGCAGAGAAAGAAGGACGCCTCAGGAAGAAGCCGTTCGAAAGACAGAAGGAGAAGGAGAAGAGATT" + "CGAAAGTTCAGAGCGTGAAGAGTCTCAATCGCGTAATAAAGACAAGTACGGGAACCAAGAAAGTAAAAGTTCCCACAGGAAGAACTCTGAAGAGCG" + "AGAAAAGTAAAAAAGGGTTTCCTGTTTTTTGCCTATTTTGGGTAAAGGGGTTGATGGAGAAACAGGTGTGTGGACTGCTGAGGAGTGAGTTAGAAT" + "AAATGGTGGTATCACTTCTTCAATGCTACTACAATGGAACAACAGTCGTTACCTGTTTTAAGTTCGTGGCGTCTTATGCTCCGGACAGGGACAGAT" + "AGGCGGTTGACAGAGAGTTAAGATCTAGTACACTGGGTTTCCTAAATGTAAGAATTGGCCCGAATCCGGCCTAATATGCGAACTTTGTGCTACCAA" + "GCGAGCGGGAAGCTAAGGGTGGGGAATTGCGGGTTTAATGGACCATCTCATGAGTCTAGCAGTTAATGTATCCTATCTTCCAAACAGGAATGTATT" + "CGAAAGAGTAGAGACCATAATTCGTCTAACAACTCAAGGAAAAGAAGGCGGAGTAGAGCCGATTCCGAACCCTTTGCTAGGACTAGATAGCACGTG" + "AACCTAGACTGTCTCTGAGACTGCGCCATTACGTCTCGATCAGTAACGATTGCATCGCGAGGCTGTGGATGTAAAACCTCTGCTGACCTTGACTGA" + "CTGAGATACAATGCCTTCAGCAATGCGTGGCAG"; + const char* str2 = + "GTAAGGGTTTCCTGTTTTTTGCCTATTTTGGGTAAAGGGGGGTTGATGGAGAAACAGGTGTGTGGACTGCTGAGGAGTGAGTTAGAATAAATGGTG" + "GTATCACTTCTTCAATGCTACAATGGAACAACAGTCGTTACCTGTTTTAAGTTCGTGGCGTCTTATGCTCCGGACAGGGACAGATAGGCGGTTAGA" + "CAGAGAGTTAAGATCTAGTACACTGGGTTTCCTAAATGTAAAAATTGGCCCGAATCCGGCCTAATATGCGAACTTTGTGCTACCAAGCGAGCGGGA" + "AGCTAAGGGTGGGGAGTGCGGGTTTAATGGACCATCTCGCAGGTCTAGCAGTTAATGTATCCTATCTTCCAAACAG"; + + score_test(97.5274725, fuzz::partial_ratio(str1, str2)); + score_test(97.5274725, fuzz::partial_ratio(str2, str1)); + score_test(97.5274725, fuzz::partial_ratio(str1, str2, 97.5)); + score_test(97.5274725, fuzz::partial_ratio(str2, str1, 97.5)); + } +} diff --git a/src/external/rapidfuzz-cpp/test/tests-main.cpp b/src/external/rapidfuzz-cpp/test/tests-main.cpp new file mode 100644 index 00000000..a45c7d61 --- /dev/null +++ b/src/external/rapidfuzz-cpp/test/tests-main.cpp @@ -0,0 +1,3 @@ +// test main file so catch2 does not has to be recompiled +#define CATCH_CONFIG_MAIN +#include "catch2/catch.hpp" \ No newline at end of file diff --git a/src/external/rapidfuzz-cpp/tools/amalgamation.py b/src/external/rapidfuzz-cpp/tools/amalgamation.py new file mode 100644 index 00000000..c10ab954 --- /dev/null +++ b/src/external/rapidfuzz-cpp/tools/amalgamation.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python3 +# disclaimer: this file is mostly copied from Catch2 + +import os +import re +import datetime +import sys +import subprocess + +root_path = os.path.dirname(os.path.realpath( os.path.dirname(sys.argv[0]))) +version_string = "1.0.2" + +starting_header = os.path.join(root_path, 'rapidfuzz', 'rapidfuzz_all.hpp') +output_header = os.path.join(root_path, 'extras', 'rapidfuzz_amalgamated.hpp') +output_cpp = os.path.join(root_path, 'extras', 'rapidfuzz_amalgamated.cpp') + +# These are the copyright comments in each file, we want to ignore them +def is_copyright_line(line): + copyright_lines = [ + '/* SPDX-License-Identifier: MIT', + '/* Copyright ' + ] + + for copyright_line in copyright_lines: + if line.startswith(copyright_line): + return True + return False + + +# The header of the amalgamated file: copyright information + explanation +# what this file is. +file_header = '''\ +// Licensed under the MIT License . +// SPDX-License-Identifier: MIT +// RapidFuzz v{version_string} +// Generated: {generation_time} +// ---------------------------------------------------------- +// This file is an amalgamation of multiple different files. +// You probably shouldn't edit it directly. +// ---------------------------------------------------------- +''' + +# Returns file header with proper version string and generation time +def formatted_file_header(): + return file_header.format(version_string=version_string, + generation_time=datetime.datetime.now()) + +# Which headers were already concatenated (and thus should not be +# processed again) +concatenated_headers = set() + +internal_include_parser = re.compile(r'\s*# *include [<"](rapidfuzz/.*)[>"].*') + +def concatenate_file(out, filename: str) -> int: + # Gathers statistics on how many headers were expanded + concatenated = 1 + with open(filename, mode='r', encoding='utf-8') as input: + for line in input: + if is_copyright_line(line): + continue + + if line.startswith('#pragma once'): + continue + + m = internal_include_parser.match(line) + # anything that isn't a RapidFuzz header can just be copied to + # the resulting file + if not m: + out.write(line) + continue + + next_header = m.group(1) + # We have to avoid re-expanding the same header over and + # over again + if next_header in concatenated_headers: + continue + concatenated_headers.add(next_header) + out.write("\n") + concatenated += concatenate_file(out, os.path.join(root_path, next_header)) + out.write("\n") + + return concatenated + + +def generate_header(): + with open(output_header, mode='w', encoding='utf-8') as header: + header.write(formatted_file_header()) + header.write('#ifndef RAPIDFUZZ_AMALGAMATED_HPP_INCLUDED\n') + header.write('#define RAPIDFUZZ_AMALGAMATED_HPP_INCLUDED\n') + print('Concatenated {} headers'.format(concatenate_file(header, starting_header))) + header.write('#endif // RAPIDFUZZ_AMALGAMATED_HPP_INCLUDED\n') + + # format output properly + subprocess.run(["clang-format", "-i", output_header]) + +generate_header() + + +# Notes: +# * For .cpp files, internal includes have to be stripped and rewritten +# * for .hpp files, internal includes have to be resolved and included +# * The .cpp file needs to start with `#include "catch_amalgamated.hpp" +# * include guards can be left/stripped, doesn't matter +# * *.cpp files should be included sorted, to minimize diffs between versions +# * *.hpp files should also be somehow sorted -> use catch_all.hpp as the +# * entrypoint +# * allow disabling main in the .cpp amalgamation \ No newline at end of file diff --git a/src/math/coordinates.hpp b/src/math/coordinates.hpp index 2ad227a7..88f63d62 100644 --- a/src/math/coordinates.hpp +++ b/src/math/coordinates.hpp @@ -22,6 +22,11 @@ namespace math vector[2] = -x + ZEROPOINT; } + inline glm::vec3 to_client(float x, float y, float z) + { + return { -y + ZEROPOINT, z, -x + ZEROPOINT }; + } + inline void to_server(glm::vec3& vector) { float x = vector.x; @@ -37,4 +42,9 @@ namespace math vector[2] = vector[1]; vector[1] = ZEROPOINT - x; } + + inline glm::vec3 to_server(float x, float y, float z) + { + return { ZEROPOINT - z, ZEROPOINT - x, y, }; + } } diff --git a/src/math/frustum.cpp b/src/math/frustum.cpp index e6c256e0..c8d154fc 100755 --- a/src/math/frustum.cpp +++ b/src/math/frustum.cpp @@ -1,5 +1,6 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). +#include #include #include @@ -116,7 +117,7 @@ namespace math { for (auto const& plane : _planes) { - const float distance = glm::dot(plane.normal(), position+ plane.distance()); + const float distance = glm::dot(position, plane.normal()) + plane.distance(); if (distance < -radius) { return false; @@ -128,4 +129,9 @@ namespace math } return true; } + + bool frustum::intersectsSphere(sphere const& sphere) const + { + return intersectsSphere(sphere.position, sphere.radius); + } } diff --git a/src/math/frustum.hpp b/src/math/frustum.hpp index 7c466c11..c925d1fe 100755 --- a/src/math/frustum.hpp +++ b/src/math/frustum.hpp @@ -6,6 +6,8 @@ namespace math { + struct sphere; + class frustum { enum SIDES @@ -32,7 +34,7 @@ namespace math void normalize() { - const float recip (1.0f / _normal.length()); + constexpr float recip (1.0f / glm::vec3::length()); _normal *= recip; _distance *= recip; } @@ -64,5 +66,7 @@ namespace math bool intersectsSphere ( const glm::vec3& position , const float& radius ) const; + + bool intersectsSphere(sphere const& sphere) const; }; } diff --git a/src/math/ray.cpp b/src/math/ray.cpp index dbd6199d..9f3dcc56 100755 --- a/src/math/ray.cpp +++ b/src/math/ray.cpp @@ -1,26 +1,36 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). #include +#include + +#include +#include #include #include namespace math { + float magnitutde_sq(glm::vec2 const& v) + { + return glm::dot(v, v); + } + + void calculate_tmin_tmax(float origin, float direction, float min, float max, float& tmin, float& tmax) + { + if (direction != 0.0f) { + float t1 = (min - origin) / direction; + float t2 = (max - origin) / direction; + tmin = std::max(tmin, std::min(t1, t2)); + tmax = std::min(tmax, std::max(t1, t2)); + } + } + std::optional ray::intersect_bounds (glm::vec3 const& min, glm::vec3 const& max) const noexcept { - float tmin (std::numeric_limits::lowest()); - float tmax (std::numeric_limits::max()); - - auto calculate_tmin_tmax = [](float origin, float direction, float min, float max, float& tmin, float& tmax) { - if (direction != 0.0f) { - float t1 = (min - origin) / direction; - float t2 = (max - origin) / direction; - tmin = std::max(tmin, std::min(t1, t2)); - tmax = std::min(tmax, std::max(t1, t2)); - } - }; + float tmin(std::numeric_limits::lowest()); + float tmax(std::numeric_limits::max()); calculate_tmin_tmax(_origin.x, _direction.x, min.x, max.x, tmin, tmax); calculate_tmin_tmax(_origin.y, _direction.y, min.y, max.y, tmin, tmax); @@ -37,10 +47,10 @@ namespace math std::optional ray::intersect_triangle (glm::vec3 const& v0, glm::vec3 const& v1, glm::vec3 const& v2) const noexcept { - glm::vec3 e1 (v1 - v0); - glm::vec3 e2 (v2 - v0); + glm::vec3 e1(v1 - v0); + glm::vec3 e2(v2 - v0); - glm::vec3 P = glm::cross(_direction,e2); + glm::vec3 P = glm::cross(_direction, e2); float const det = glm::dot(e1, P); @@ -50,23 +60,23 @@ namespace math return std::nullopt; } - glm::vec3 const T (_origin - v0); - float const dotu = glm::dot(T , P) / det; + glm::vec3 const T(_origin - v0); + float const dotu = glm::dot(T, P) / det; if (dotu < 0.0f || dotu > 1.0f) { return std::nullopt; } - glm::vec3 const Q (glm::cross(T, e1)); - float const dotv = glm::dot(_direction , Q) / det; + glm::vec3 const Q(glm::cross(T, e1)); + float const dotv = glm::dot(_direction, Q) / det; if (dotv < 0.0f || dotu + dotv > 1.0f) { return std::nullopt; } - float const dott = glm::dot(e2 , Q) / det; + float const dott = glm::dot(e2, Q) / det; if (dott > epsilon) // if (dott > std::numeric_limits::min()) { @@ -75,4 +85,66 @@ namespace math return std::nullopt; } + + std::optional ray::intersect_box + (glm::vec3 const& position, glm::vec3 const& box_min, glm::vec3 const& box_max, glm::vec3 const& rotation) const noexcept + { + auto inverse_rotation = glm::inverse(glm::yawPitchRoll(rotation.y, rotation.z, rotation.x)); + + glm::vec3 origin = inverse_rotation * glm::vec4{ _origin - position, 1.f }; + glm::vec3 direction = inverse_rotation * glm::vec4{ _direction, 0.0f }; + + float tmin(std::numeric_limits::lowest()); + float tmax(std::numeric_limits::max()); + + calculate_tmin_tmax(origin.x, direction.x, box_min.x, box_max.x, tmin, tmax); + calculate_tmin_tmax(origin.y, direction.y, box_min.y, box_max.y, tmin, tmax); + calculate_tmin_tmax(origin.z, direction.z, box_min.z, box_max.z, tmin, tmax); + + if (tmax >= tmin) + { + return tmin; + } + + return std::nullopt; + } + + hit_result ray::intersects_sphere(sphere const& sphere) const + { + // Taken from https://gamedev.stackexchange.com/a/96469 + + // Calculate ray start's offset from the sphere center + glm::vec3 p = _origin - sphere.position; + + float rSquared = sphere.radius * sphere.radius; + float p_d = dot(p, _direction); + + // The sphere is behind or surrounding the start point. + if (p_d > 0 || dot(p, p) < rSquared) + return { sphere.position, sphere.radius, false }; + + // Flatten p into the plane passing through sphere.position perpendicular to the ray. + // This gives the closest approach of the ray to the center. + glm::vec3 a = p - p_d * _direction; + + float aSquared = dot(a, a); + + // Closest approach is outside the sphere. + if (aSquared > rSquared) + return { sphere.position, sphere.radius, false }; + + // Calculate distance from plane where ray enters/exits the sphere. + float h = sqrt(rSquared - aSquared); + + // Calculate intersection point relative to sphere center. + glm::vec3 i = a - h * _direction; + + glm::vec3 intersection = sphere.position + i; + glm::vec3 normal = i / sphere.radius; + // We've taken a shortcut here to avoid a second square root. + // Note numerical errors can make the normal have length slightly different from 1. + // If you need higher precision, you may need to perform a conventional normalization. + + return { intersection, h, true }; + } } diff --git a/src/math/ray.hpp b/src/math/ray.hpp index 92919987..277aa273 100755 --- a/src/math/ray.hpp +++ b/src/math/ray.hpp @@ -7,6 +7,15 @@ namespace math { + struct sphere; + + struct hit_result + { + glm::vec3 position; + float t = 0; + bool hit = false; + }; + struct ray { ray (glm::vec3 origin, glm::vec3 const& direction) @@ -30,6 +39,11 @@ namespace math std::optional intersect_triangle (glm::vec3 const& _v0, glm::vec3 const& _v1, glm::vec3 const& _v2) const noexcept; + std::optional intersect_box + (glm::vec3 const& position, glm::vec3 const& box_min, glm::vec3 const& box_max, glm::vec3 const& rotation) const noexcept; + + hit_result intersects_sphere(sphere const& sphere) const; + glm::vec3 position (float distance) const { return _origin + _direction * distance; diff --git a/src/math/sphere.cpp b/src/math/sphere.cpp new file mode 100644 index 00000000..1f7b12ec --- /dev/null +++ b/src/math/sphere.cpp @@ -0,0 +1,7 @@ +// This file is part of Noggit3, licensed under GNU General Public License (version 3). + +#include "sphere.hpp" + +namespace math +{ +} diff --git a/src/math/sphere.hpp b/src/math/sphere.hpp new file mode 100644 index 00000000..17c12155 --- /dev/null +++ b/src/math/sphere.hpp @@ -0,0 +1,14 @@ +// This file is part of Noggit3, licensed under GNU General Public License (version 3). + +#pragma once + +#include + +namespace math +{ + struct sphere + { + glm::vec3 position; + float radius = 0; + }; +} diff --git a/src/noggit/Action.cpp b/src/noggit/Action.cpp index 1c43f86d..e20f2345 100644 --- a/src/noggit/Action.cpp +++ b/src/noggit/Action.cpp @@ -7,10 +7,12 @@ #include #include #include +#include Noggit::Action::Action(MapView* map_view) : QObject() +, _flags{0} , _map_view(map_view) { } @@ -230,6 +232,21 @@ void Noggit::Action::undo(bool redo) pair.first->registerChunkUpdate(ChunkUpdateFlags::GROUND_EFFECT); } } + if (_flags & ActionFlags::eAREA_TRIGGER_TRANSFORMED) + { + for (auto& pair : redo ? _transformed_area_trigger_post : _transformed_area_trigger_pre) + { + for (auto&& record : gAreaTriggerDB) + { + area_trigger trigger{ record }; + if (trigger.id == pair.first) + { + trigger = pair.second; + trigger.write_to_dbc(); + } + } + } + } } @@ -543,6 +560,26 @@ void Noggit::Action::finish() std::memcpy(post.second.data(), &post.first->_shadow_map, 64 * 64 * sizeof(std::uint8_t)); } } + if (_flags & ActionFlags::eAREA_TRIGGER_TRANSFORMED) + { + _transformed_area_trigger_post.resize(_transformed_area_trigger_pre.size()); + + for (int i = 0; i < _transformed_area_trigger_pre.size(); ++i) + { + auto& post = _transformed_area_trigger_post.at(i); + auto& pre = _transformed_area_trigger_pre.at(i); + post.first = pre.first; + + for (auto&& record : gAreaTriggerDB) + { + area_trigger trigger{ record }; + if (trigger.id == pre.first) + { + post.second = trigger; + } + } + } + } if (_post) _post(); @@ -847,6 +884,18 @@ void Noggit::Action::registerAllChunkChanges(MapChunk* chunk) registerChunkDetailDoodadExclusionChange(chunk); } +void Noggit::Action::registerAreaTriggerTransformed(area_trigger* trigger) +{ + _flags |= ActionFlags::eAREA_TRIGGER_TRANSFORMED; + for (auto& pair : _transformed_area_trigger_pre) + { + if (pair.first == trigger->id) + return; + } + + _transformed_area_trigger_pre.emplace_back(trigger->id, *trigger); +} + Noggit::Action::~Action() { -} \ No newline at end of file +} diff --git a/src/noggit/Action.hpp b/src/noggit/Action.hpp index 4a8b295e..98ad2517 100755 --- a/src/noggit/Action.hpp +++ b/src/noggit/Action.hpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -25,25 +26,25 @@ class MapChunk; namespace Noggit { - enum ActionFlags { - eNO_FLAG = 0, - eCHUNKS_TERRAIN = 0x1, - eCHUNKS_AREAID = 0x2, - eCHUNKS_HOLES = 0x4, - eCHUNKS_VERTEX_COLOR = 0x8, - eCHUNKS_WATER = 0x10, - eCHUNKS_TEXTURE = 0x20, - eOBJECTS_REMOVED = 0x40, - eOBJECTS_ADDED = 0x80, - eOBJECTS_TRANSFORMED = 0x100, - eCHUNKS_FLAGS = 0x200, - eVERTEX_SELECTION = 0x400, - eCHUNK_SHADOWS = 0x800, - eDO_NOT_WRITE_HISTORY= 0x1000, - eCHUNK_DOODADS_EXCLUSION = 0x2000, // ground effects exclusion mapping - eCHUNKS_LAYERINFO = 0x4000 // ground effect id and texture flags + eNO_FLAG = 0, + eCHUNKS_TERRAIN = 0x1, + eCHUNKS_AREAID = 0x2, + eCHUNKS_HOLES = 0x4, + eCHUNKS_VERTEX_COLOR = 0x8, + eCHUNKS_WATER = 0x10, + eCHUNKS_TEXTURE = 0x20, + eOBJECTS_REMOVED = 0x40, + eOBJECTS_ADDED = 0x80, + eOBJECTS_TRANSFORMED = 0x100, + eCHUNKS_FLAGS = 0x200, + eVERTEX_SELECTION = 0x400, + eCHUNK_SHADOWS = 0x800, + eDO_NOT_WRITE_HISTORY = 0x1000, + eCHUNK_DOODADS_EXCLUSION = 0x2000, // ground effects exclusion mapping + eCHUNKS_LAYERINFO = 0x4000, // ground effect id and texture flags + eAREA_TRIGGER_TRANSFORMED = 0x8000, }; enum ActionModalityControllers @@ -142,6 +143,7 @@ namespace Noggit void registerChunkLayerInfoChange(MapChunk* chunk); void registerChunkDetailDoodadExclusionChange(MapChunk* chunk); void registerAllChunkChanges(MapChunk* chunk); + void registerAreaTriggerTransformed(area_trigger* trigger); private: @@ -175,6 +177,8 @@ namespace Noggit std::vector> _chunk_flags_post; std::vector>> _chunk_liquid_pre; std::vector>> _chunk_liquid_post; + std::vector> _transformed_area_trigger_pre; + std::vector> _transformed_area_trigger_post; VertexSelectionCache _vertex_selection_pre; VertexSelectionCache _vertex_selection_post; diff --git a/src/noggit/DBC.cpp b/src/noggit/DBC.cpp index de2ad1e6..e035671a 100755 --- a/src/noggit/DBC.cpp +++ b/src/noggit/DBC.cpp @@ -8,6 +8,7 @@ #include AreaDB gAreaDB; +AreaTriggerDB gAreaTriggerDB; MapDB gMapDB; LoadingScreensDB gLoadingScreensDB; LightDB gLightDB; @@ -33,6 +34,7 @@ void OpenDBs(std::shared_ptr clientData) try { gAreaDB.open(clientData); + gAreaTriggerDB.open(clientData); gMapDB.open(clientData); gLoadingScreensDB.open(clientData); gLightDB.open(clientData); diff --git a/src/noggit/DBC.h b/src/noggit/DBC.h index 27bd8115..d2bf22f5 100755 --- a/src/noggit/DBC.h +++ b/src/noggit/DBC.h @@ -37,6 +37,28 @@ public: static std::uint32_t get_new_areabit(); }; +class AreaTriggerDB : public DBCFile +{ +public: + AreaTriggerDB() : + DBCFile{ "DBFilesClient\\AreaTrigger.dbc" } + { + } + + static const size_t Id = 0; // uint + static const size_t MapId = 1; // uint + static const size_t X = 2; // float + static const size_t Y = 3; // float + static const size_t Z = 4; // float + static const size_t Radius = 5; // float + static const size_t Length = 6; // float + static const size_t Width = 7; // float + static const size_t Height = 8; // float + static const size_t Orientation = 9; // float + + static const uint32_t FieldCount = 10; +}; + class MapDB : public DBCFile { public: @@ -360,6 +382,7 @@ void OpenDBs(std::shared_ptr clientData); const char * getGroundEffectDoodad(unsigned int effectID, int DoodadNum); extern AreaDB gAreaDB; +extern AreaTriggerDB gAreaTriggerDB; extern MapDB gMapDB; extern LoadingScreensDB gLoadingScreensDB; extern LightDB gLightDB; diff --git a/src/noggit/DBCFile.cpp b/src/noggit/DBCFile.cpp index 9f1845f3..2f3d4ea4 100755 --- a/src/noggit/DBCFile.cpp +++ b/src/noggit/DBCFile.cpp @@ -23,11 +23,12 @@ auto write(std::ostream& stream, T const& val) -> void DBCFile::DBCFile(const std::string& _filename) : filename(_filename) -{} +{ +} void DBCFile::open(std::shared_ptr clientData) { - BlizzardArchive::ClientFile f (filename, clientData.get()); + BlizzardArchive::ClientFile f(filename, clientData.get()); if (f.isEof()) { @@ -52,14 +53,14 @@ void DBCFile::open(std::shared_ptr clientData) if (fieldCount * 4 != recordSize) { - throw std::logic_error ("non four-byte-columns not supported : " + filename); + throw std::logic_error("non four-byte-columns not supported : " + filename); } - data.resize (recordSize * recordCount); - f.read (data.data(), data.size()); + data.resize(recordSize * recordCount); + f.read(data.data(), data.size()); - stringTable.resize (stringSize); - f.read (stringTable.data(), stringTable.size()); + stringTable.resize(stringSize); + f.read(stringTable.data(), stringTable.size()); f.close(); } @@ -92,6 +93,26 @@ void DBCFile::save() stream.close(); } +void DBCFile::overwriteWith(DBCFile const& file) +{ + filename = file.filename; + recordSize = file.recordSize; + recordCount = file.recordCount; + fieldCount = file.fieldCount; + stringSize = file.stringSize; + data = file.data; + stringTable = file.stringTable; +} + +DBCFile DBCFile::createNew(std::string filename, std::uint32_t fieldCount, std::uint32_t recordSize) +{ + DBCFile file{}; + file.filename = std::move(filename); + file.recordSize = recordSize; + file.fieldCount = fieldCount; + return file; +} + DBCFile::Record DBCFile::addRecord(size_t id, size_t id_field) { assert(recordSize > 0); diff --git a/src/noggit/DBCFile.h b/src/noggit/DBCFile.h index cf425f27..31392164 100755 --- a/src/noggit/DBCFile.h +++ b/src/noggit/DBCFile.h @@ -22,6 +22,10 @@ public: void open(std::shared_ptr clientData); void save(); + void overwriteWith(DBCFile const& file); + + static DBCFile createNew(std::string filename, std::uint32_t fieldCount, std::uint32_t recordSize); + class NotFound : public std::runtime_error { public: @@ -173,8 +177,10 @@ public: return Iterator(*this, data.data() + data.size()); } - inline size_t getRecordCount() { return recordCount; } - inline size_t getFieldCount() { return fieldCount; } + inline size_t getRecordCount() const { return recordCount; } + inline size_t getFieldCount() const { return fieldCount; } + inline size_t getRecordSize() const { return recordSize; } + inline Record getByID(unsigned int id, size_t field = 0) { for (Iterator i = begin(); i != end(); ++i) @@ -214,11 +220,13 @@ public: int getEmptyRecordID(size_t id_field = 0); private: + DBCFile() = default; + std::string filename; - std::uint32_t recordSize; - std::uint32_t recordCount; - std::uint32_t fieldCount; - std::uint32_t stringSize; + std::uint32_t recordSize = 0; + std::uint32_t recordCount = 0; + std::uint32_t fieldCount = 0; + std::uint32_t stringSize = 0; std::vector data; std::vector stringTable; }; diff --git a/src/noggit/MapView.cpp b/src/noggit/MapView.cpp index f14174fb..6ddc2157 100644 --- a/src/noggit/MapView.cpp +++ b/src/noggit/MapView.cpp @@ -68,6 +68,7 @@ #include #include #include +#include #include #include @@ -942,12 +943,30 @@ void MapView::setupEditMenu() edit_menu->addSeparator(); edit_menu->addAction(createTextSeparator("Selected object")); edit_menu->addSeparator(); - ADD_ACTION (edit_menu, "Delete", Qt::Key_Delete, [this] - { - NOGGIT_ACTION_MGR->beginAction(this, Noggit::ActionFlags::eOBJECTS_REMOVED); - DeleteSelectedObjects(); - NOGGIT_ACTION_MGR->endAction(); - }); + ADD_ACTION(edit_menu, "Delete", Qt::Key_Delete, [this] + { + if (get_editing_mode() == editing_mode::object) + { + NOGGIT_ACTION_MGR->beginAction(this, Noggit::ActionFlags::eOBJECTS_REMOVED); + DeleteSelectedObjects(); + NOGGIT_ACTION_MGR->endAction(); + } + else + { + for (auto&& hotkey : hotkeys) + { + if (Qt::Key_Delete == hotkey.key && hotkey.condition()) + { + makeCurrent(); + OpenGL::context::scoped_setter const _(::gl, context()); + + hotkey.onPress(); + return; + } + } + } + } + ); ADD_ACTION (edit_menu, "Reset rotation", "Ctrl+R", [this] @@ -2304,6 +2323,8 @@ void MapView::setupHotkeys() addHotkey(Qt::Key_Minus, MOD_num, "decreaseSelectedScale"_hash); addHotkey(Qt::Key_F, MOD_none, "setAreaId"_hash); + + addHotkey(Qt::Key_Delete, MOD_none, "deleteSelection"_hash); } void MapView::setupMinimap() @@ -2399,8 +2420,9 @@ void MapView::createGUI() _tools.emplace_back(std::make_unique(this))->setupUi(_tool_panel_dock); _tools.emplace_back(std::make_unique(this))->setupUi(_tool_panel_dock); _tools.emplace_back(std::make_unique(this))->setupUi(_tool_panel_dock); - // _tools.emplace_back(std::make_unique(this))->setupUi(_tool_panel_dock); - // _tools.emplace_back(std::make_unique(this))->setupUi(_tool_panel_dock); + _tools.emplace_back(std::make_unique(this))->setupUi(_tool_panel_dock); + _tools.emplace_back(std::make_unique(this))->setupUi(_tool_panel_dock); + _tools.emplace_back(std::make_unique(this))->setupUi(_tool_panel_dock); // End combined dock @@ -2780,16 +2802,13 @@ void MapView::paintGL() { lock = true; draw_map(); + activeTool()->postRender(); lock = false; tick (now - _last_update); } _last_update = now; - lock = true; - activeTool()->postRender(); - lock = false; - if (_gizmo_on.get() && _world->has_selection()) { ImGui::SetCurrentContext(_imgui_context); @@ -2816,6 +2835,7 @@ void MapView::paintGL() _transform_gizmo.handleTransformGizmo(this, _world->current_selection(), _model_view, _projection); // _world->update_selection_pivot(); + activeTool()->renderImGui(_gizmo_mode, _gizmo_operation); ImGui::End(); @@ -4167,11 +4187,14 @@ void MapView::save(save_mode mode) // write wdl, we update wdl data prior in the mapIndex saving fucntions above _world->horizon.save_wdl(_world.get()); + for (auto&& dbc : _dirty_dbcs) + { + dbc->save(); + } NOGGIT_ACTION_MGR->purge(); AsyncLoader::instance->reset_object_fail(); - _main_window->statusBar()->showMessage("Map saved", 2000); } @@ -4248,6 +4271,29 @@ void MapView::cursorPosition(glm::vec3 position) _cursor_pos = position; } +void MapView::enableGizmoBar() +{ + _viewport_overlay_ui->gizmoBar->show(); +} + +void MapView::disableGizmoBar() +{ + _viewport_overlay_ui->gizmoBar->hide(); +} + +void MapView::setDbcDirty(DBCFile* dbc) +{ + for (auto&& dirty_dbc : _dirty_dbcs) + { + if (dirty_dbc == dbc) + { + return; + } + } + + _dirty_dbcs.emplace_back(dbc); +} + // also called when loading world/viewport in MapView::initializeGL() void MapView::onSettingsSave() { diff --git a/src/noggit/MapView.h b/src/noggit/MapView.h index df5e2fe4..2c3c8a91 100755 --- a/src/noggit/MapView.h +++ b/src/noggit/MapView.h @@ -148,12 +148,6 @@ private: display_mode _display_mode; - [[nodiscard]] - glm::mat4x4 model_view(bool use_debug_cam = false) const; - - [[nodiscard]] - glm::mat4x4 projection() const; - void draw_map(); void createGUI(); @@ -278,6 +272,11 @@ public: glm::vec3 cursorPosition() const; void cursorPosition(glm::vec3 position); + void enableGizmoBar(); + void disableGizmoBar(); + + void setDbcDirty(DBCFile* dbc); + private: enum Modifier { @@ -355,8 +354,6 @@ private: Noggit::Ui::minimap_widget* _minimap; QDockWidget* _minimap_dock; - void move_camera_with_auto_height (glm::vec3 const&); - void setToolPropertyWidgetVisibility(editing_mode mode); void unloadOpenglData() override; @@ -392,6 +389,8 @@ private: glm::mat4x4 _model_view; glm::mat4x4 _projection; + std::vector _dirty_dbcs; + public: private: @@ -464,4 +463,12 @@ private: [[nodiscard]] float timeSpeed() const; + + [[nodiscard]] + glm::mat4x4 model_view(bool use_debug_cam = false) const; + + [[nodiscard]] + glm::mat4x4 projection() const; + + void move_camera_with_auto_height(glm::vec3 const&); }; diff --git a/src/noggit/Tool.cpp b/src/noggit/Tool.cpp index 8d33719a..853f96b1 100644 --- a/src/noggit/Tool.cpp +++ b/src/noggit/Tool.cpp @@ -102,6 +102,10 @@ namespace Noggit { } + void Tool::renderImGui(ImGuizmo::MODE mode, ImGuizmo::OPERATION operation) + { + } + void Tool::onMousePress(MousePressParameters const& params) { } @@ -244,4 +248,4 @@ namespace Noggit { menu->addSeparator(); } -} \ No newline at end of file +} diff --git a/src/noggit/Tool.hpp b/src/noggit/Tool.hpp index 92d81e42..268230c4 100644 --- a/src/noggit/Tool.hpp +++ b/src/noggit/Tool.hpp @@ -8,6 +8,9 @@ #include +#include +#include + #include #include @@ -169,6 +172,9 @@ namespace Noggit // will be called after the map got drawn virtual void postRender(); + // Imgui specific draw-calls (e.g. gizmo related stuff) can be made here + virtual void renderImGui(ImGuizmo::MODE mode, ImGuizmo::OPERATION operation); + // will be called whenever a mouse button is pressed virtual void onMousePress(MousePressParameters const& params); @@ -206,4 +212,4 @@ namespace Noggit MapView* _mapView = nullptr; std::unordered_map _hotkeys; }; -} \ No newline at end of file +} diff --git a/src/noggit/area_trigger.cpp b/src/noggit/area_trigger.cpp new file mode 100644 index 00000000..1715dc95 --- /dev/null +++ b/src/noggit/area_trigger.cpp @@ -0,0 +1,155 @@ +// This file is part of Noggit3, licensed under GNU General Public License (version 3). + +#include "area_trigger.hpp" + +#include + +#include + +namespace Noggit +{ + area_trigger::area_trigger(uint32_t id) + { + for (auto&& record : gAreaTriggerDB) + { + if (record.getUInt(AreaTriggerDB::Id) == id) + { + this->id = record.getUInt(AreaTriggerDB::Id); + map_id = record.getUInt(AreaTriggerDB::MapId); + position = math::to_client(record.getFloat(AreaTriggerDB::X), record.getFloat(AreaTriggerDB::Y), record.getFloat(AreaTriggerDB::Z)); + from_record(record); + return; + } + } + + throw std::exception{ std::format("There is no area trigger with id {}", id).c_str() }; + } + + area_trigger::area_trigger(DBCFile::Record& record) + : id{ record.getUInt(AreaTriggerDB::Id) } + , map_id{ record.getUInt(AreaTriggerDB::MapId) } + , position{ math::to_client(record.getFloat(AreaTriggerDB::X), record.getFloat(AreaTriggerDB::Y), record.getFloat(AreaTriggerDB::Z)) } + { + from_record(record); + } + + void area_trigger::from_record(DBCFile::Record& record) + { + auto const radius = record.getFloat(AreaTriggerDB::Radius); + if (radius) + { + trigger = sphere_trigger{ .radius = radius, }; + } + else + { + auto const length = record.getFloat(AreaTriggerDB::Length); + auto const width = record.getFloat(AreaTriggerDB::Width); + auto const height = record.getFloat(AreaTriggerDB::Height); + auto const orientation = record.getFloat(AreaTriggerDB::Orientation); + + trigger = box_trigger + { + .extents_min = glm::vec3{ -width / 2, -height / 2, -length / 2 }, + .extents_max = glm::vec3{ width / 2, height / 2,length / 2 }, + .orientation = orientation, + }; + } + } + + bool area_trigger::intersects(math::frustum const& frustum) const + { + return std::visit([=](auto&& trigger) { + using T = std::remove_cvref_t; + if constexpr (std::is_same_v) + { + math::sphere const sphere{ .position = position, .radius = trigger.radius, }; + return frustum.intersectsSphere(sphere); + } + else if constexpr (std::is_same_v < std::remove_cvref_t, box_trigger>) + { + return frustum.intersects(position - trigger.extents_min, position - trigger.extents_max); + } + else + { + throw std::exception("Unknown area trigger type encountered!"); + } + }, trigger); + } + + bool area_trigger::intersects(math::ray const& ray) const + { + return std::visit([&](auto&& trigger) { + using T = std::remove_cvref_t; + if constexpr (std::is_same_v) + { + math::sphere sphere{ .position = position, .radius = trigger.radius }; + return ray.intersects_sphere(sphere).hit; + } + if constexpr (std::is_same_v) + { + return ray.intersect_box(position, trigger.extents_min, trigger.extents_max, glm::vec3{0, trigger.orientation , 0}).value_or(0) > 0; + } + else + { + throw std::exception("Unknown area trigger type encountered!"); + } + }, trigger); + } + + void area_trigger::draw(glm::mat4x4 const& projection, glm::mat4x4 const& model_view, WireBoxRenderer& wirebox_renderer, SphereRenderer& sphere_renderer, glm::vec4& color) + { + std::visit([&](auto&& trigger) { + using T = std::remove_cvref_t; + if constexpr (std::is_same_v) + { + sphere_renderer.draw(projection * model_view, position, color, trigger.radius, 32, 17, 1.f, true, false); + } + else if constexpr (std::is_same_v < std::remove_cvref_t, box_trigger>) + { + auto transform = glm::rotate(glm::translate(glm::mat4x4{ 1 }, position), trigger.orientation, glm::vec3{ 0,1,0 }); + wirebox_renderer.draw(model_view, projection, transform, color, trigger.extents_min, trigger.extents_max); + } + else + { + throw std::exception("Unknown area trigger type encountered!"); + } + }, trigger); + } + + void area_trigger::write_to_dbc() const + { + for (auto&& record : gAreaTriggerDB) + { + auto const trigger_id = record.getUInt(AreaTriggerDB::Id); + auto const trigger_map_id = record.getUInt(AreaTriggerDB::MapId); + if (trigger_id != id || trigger_map_id != map_id) + { + continue; + } + + record.write(AreaTriggerDB::Id, id); + record.write(AreaTriggerDB::MapId, map_id); + + auto pos = math::to_server(position.x, position.y, position.z); + + record.write(AreaTriggerDB::X, pos.x); + record.write(AreaTriggerDB::Y, pos.y); + record.write(AreaTriggerDB::Z, pos.z); + + std::visit([&](auto t) { + if constexpr (std::is_same_v) + { + auto diff = t.extents_max - t.extents_min; + record.write(AreaTriggerDB::Length, diff.z); + record.write(AreaTriggerDB::Width, diff.x); + record.write(AreaTriggerDB::Height, diff.y); + record.write(AreaTriggerDB::Orientation, t.orientation); + } + else if constexpr (std::is_same_v) + { + record.write(AreaTriggerDB::Radius, t.radius); + } + }, trigger); + } + } +} diff --git a/src/noggit/area_trigger.hpp b/src/noggit/area_trigger.hpp new file mode 100644 index 00000000..e51327b6 --- /dev/null +++ b/src/noggit/area_trigger.hpp @@ -0,0 +1,61 @@ +// This file is part of Noggit3, licensed under GNU General Public License (version 3). + +#pragma once + +#include +#include +#include +#include + +#include + +#include +#include "rendering/Primitives.hpp" + +namespace Noggit::Rendering::Primitives +{ + class WireBox; + class Sphere; +} + +namespace Noggit +{ + struct sphere_trigger + { + float radius = 0; + }; + + struct box_trigger + { + glm::vec3 extents_min; + glm::vec3 extents_max; + float orientation = 0; + }; + + struct area_trigger + { + using WireBoxRenderer = Noggit::Rendering::Primitives::WireBox; + using SphereRenderer = Noggit::Rendering::Primitives::Sphere; + + uint32_t id = 0; + uint32_t map_id = 0; + glm::vec3 position = {}; + std::variant trigger; + + area_trigger() = default; + explicit area_trigger(uint32_t id); // May throw on invalid id + explicit area_trigger(DBCFile::Record& record); + + bool intersects(math::frustum const& frustum) const; + + bool intersects(math::ray const& ray) const; + + void draw(glm::mat4x4 const& projection, glm::mat4x4 const& model_view, WireBoxRenderer& wirebox_renderer, SphereRenderer& sphere_renderer, glm::vec4& color); + + // This only writes to memory! Make sure to also save the DBC! + void write_to_dbc() const; + + private: + void from_record(DBCFile::Record& record); + }; +} diff --git a/src/noggit/tool_enums.hpp b/src/noggit/tool_enums.hpp index 06ef9897..db0de693 100755 --- a/src/noggit/tool_enums.hpp +++ b/src/noggit/tool_enums.hpp @@ -75,7 +75,8 @@ enum class editing_mode stamp = 10, light = 11, scripting = 12, - chunk = 13 + chunk = 13, + area_trigger = 14, }; enum water_opacity diff --git a/src/noggit/tools/AreaTriggerTool.cpp b/src/noggit/tools/AreaTriggerTool.cpp new file mode 100644 index 00000000..85e8c4c5 --- /dev/null +++ b/src/noggit/tools/AreaTriggerTool.cpp @@ -0,0 +1,470 @@ +// This file is part of Noggit3, licensed under GNU General Public License (version 3). + +#include "AreaTriggerTool.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +size_t get_closest_hit(MapView* map_view, std::vector const& hits) +{ + size_t closest_index = 0; + float closest_distance = std::numeric_limits::max(); + auto camera_position = map_view->getCamera()->position; + + for (size_t i = 0; i < hits.size(); ++i) + { + auto&& hit = hits[i]; + + auto distance = glm::distance(hit.position, camera_position); + if (distance > closest_distance) + { + continue; + } + + closest_distance = distance; + closest_index = i; + } + + return closest_index; +} + +namespace Noggit +{ + AreaTriggerTool::AreaTriggerTool(MapView* mapView) + : Tool{ mapView } + { + addHotkey("deleteSelection"_hash, { + .onPress = [=] { _editor->deleteSelectedTrigger(); }, + .condition = [=] { return mapView->get_editing_mode() == editing_mode::area_trigger && !NOGGIT_CUR_ACTION; } + }); + } + + AreaTriggerTool::~AreaTriggerTool() + { + } + + char const* AreaTriggerTool::name() const + { + return "Area Trigger"; + } + + editing_mode AreaTriggerTool::editingMode() const + { + return editing_mode::area_trigger; + } + + Ui::FontNoggit::Icons AreaTriggerTool::icon() const + { + return Ui::FontNoggit::AREA_TRIGGER; + } + + void AreaTriggerTool::setupUi(Ui::Tools::ToolPanel* toolPanel) + { + using Ui::Tools::AreaTriggerEditor; + _editor = new AreaTriggerEditor{ mapView() }; + toolPanel->registerTool(this, _editor); + + QObject::connect(_editor, &AreaTriggerEditor::selectionChanged, [=](uint32_t current) { _selected_area_trigger = current; }); + } + + ToolDrawParameters AreaTriggerTool::drawParameters() const + { + return {}; + } + + void AreaTriggerTool::onSelected() + { + mapView()->enableGizmoBar(); + } + + void AreaTriggerTool::onDeselected() + { + mapView()->disableGizmoBar(); + } + + void AreaTriggerTool::onMouseRelease(MouseReleaseParameters const& params) + { + if (params.button == Qt::MouseButton::MiddleButton) + { + jump_to_area_trigger(params); + return; + } + + if (params.button == Qt::MouseButton::LeftButton && (!ImGuizmo::IsUsing() && !ImGuizmo::IsOver())) + { + select_area_trigger(); + return; + } + } + + void AreaTriggerTool::postRender() + { + auto const modelView = mapView()->model_view(); + auto const projection = mapView()->projection(); + math::frustum const frustum(projection * modelView); + + for (auto&& record : gAreaTriggerDB) + { + area_trigger areaTrigger{ record }; + if (areaTrigger.map_id != mapView()->getWorld()->getMapID()) + { + continue; + } + + if (areaTrigger.intersects(frustum)) + { + glm::vec4 color{ 1.f, 1.f, 1.f, 1.f }; + + if (areaTrigger.id == _selected_area_trigger) + { + color = glm::vec4{ 1.f, 0.f, 0.f, 1.f }; + } + + areaTrigger.draw(projection, modelView, _boxRenderer, _sphereRenderer, color); + } + } + } + + void AreaTriggerTool::renderImGui(ImGuizmo::MODE mode, ImGuizmo::OPERATION operation) + { + auto trigger = get_selected_trigger(); + + if (!trigger) + { + return; + } + + glm::mat4 delta_matrix; + if (!manipulate_gizmo(mode, operation, *trigger, delta_matrix)) + { + return; + } + + apply_changes(mode, operation, delta_matrix, *trigger); + } + + void AreaTriggerTool::saveSettings() + { + _editor->save(); + + std::vector area_triggers{ gAreaTriggerDB.getRecordCount() }; + + int i = 0; + for (auto&& record : gAreaTriggerDB) + { + area_triggers[i++] = area_trigger{ record }; + } + + std::sort(area_triggers.begin(), area_triggers.end(), [](auto&& a, auto&& b) { + if (a.map_id != b.map_id) + { + return a.map_id < b.map_id; + } + + return a.id < b.id; + }); + + auto dbc = DBCFile::createNew("DBFilesClient\\AreaTrigger.dbc", AreaTriggerDB::FieldCount, static_cast(gAreaTriggerDB.getRecordSize())); + for (auto&& trigger : area_triggers) + { + auto&& record = dbc.addRecord(trigger.id); + record.write(AreaTriggerDB::MapId, trigger.map_id); + auto pos = math::to_server(trigger.position.x, trigger.position.y, trigger.position.z); + record.write(AreaTriggerDB::X, pos.x); + record.write(AreaTriggerDB::Y, pos.y); + record.write(AreaTriggerDB::Z, pos.z); + + std::visit([&](auto&& trigger) { + if constexpr (std::is_same_v, sphere_trigger>) + { + record.write(AreaTriggerDB::Radius, trigger.radius); + } + else if constexpr (std::is_same_v, box_trigger>) + { + auto size = trigger.extents_max - trigger.extents_min; + record.write(AreaTriggerDB::Length, size.z); + record.write(AreaTriggerDB::Width, size.x); + record.write(AreaTriggerDB::Height, size.y); + record.write(AreaTriggerDB::Orientation, trigger.orientation); + } + }, trigger.trigger); + } + + dbc.save(); + gAreaTriggerDB.overwriteWith(dbc); + } + + void AreaTriggerTool::jump_to_area_trigger(const Noggit::MouseReleaseParameters& params) + { + for (auto&& record : gAreaTriggerDB) + { + area_trigger areaTrigger{ record }; + if (areaTrigger.map_id != mapView()->getWorld()->getMapID()) + { + continue; + } + + if (_selected_area_trigger == std::numeric_limits::max() + && params.mod_ctrl_down) + { + mapView()->getCamera()->position = areaTrigger.position; + return; + } + + if (areaTrigger.id != _selected_area_trigger) + { + continue; + } + + mapView()->getCamera()->position = areaTrigger.position; + return; + } + } + + void AreaTriggerTool::select_area_trigger() + { + auto const ray = mapView()->intersect_ray(); + std::vector hits; + + for (auto&& record : gAreaTriggerDB) + { + area_trigger areaTrigger{ record }; + if (areaTrigger.map_id != mapView()->getWorld()->getMapID()) + { + continue; + } + + if (!areaTrigger.intersects(math::frustum{ mapView()->projection() * mapView()->model_view() }) + || !areaTrigger.intersects(ray)) + { + continue; + } + + hits.emplace_back(areaTrigger); + } + + if (hits.empty()) + { + _selected_area_trigger = std::numeric_limits::max(); + _editor->clearSelection(); + return; + } + + if (hits.size() == 1) + { + _editor->set_selected(hits[0]); + _selected_area_trigger = hits[0].id; + return; + } + + auto const closest_index = get_closest_hit(mapView(), hits); + _editor->set_selected(hits[closest_index]); + _selected_area_trigger = hits[closest_index].id; + } + + std::optional AreaTriggerTool::get_selected_trigger() + { + if (_selected_area_trigger == std::numeric_limits::max()) + { + return {}; + } + + for (auto&& record : gAreaTriggerDB) + { + area_trigger areaTrigger{ record }; + if (areaTrigger.id == _selected_area_trigger) + { + return areaTrigger; + } + } + + return {}; + } + + bool AreaTriggerTool::manipulate_gizmo(ImGuizmo::MODE mode, ImGuizmo::OPERATION& operation, area_trigger const& trigger, glm::mat4& out_delta_matrix) + { + auto const modelView = mapView()->model_view(); + auto const projection = mapView()->projection(); + auto model_view_trs = modelView; + auto projection_trs = projection; + + ImGuizmo::SetDrawlist(); + + ImGuizmo::SetOrthographic(false); + ImGuizmo::SetScaleGizmoAxisLock(true); + ImGuizmo::BeginFrame(); + + ImGuiIO& io = ImGui::GetIO(); + ImGuizmo::SetRect(0, 0, io.DisplaySize.x, io.DisplaySize.y); + + glm::mat4x4 delta_matrix = glm::mat4x4(1.0f); + glm::mat4x4 object_matrix = glm::translate(glm::mat4x4(1.0f), trigger.position); + float bounds[6] = {}; + + if (operation == ImGuizmo::SCALE && trigger.trigger.index() == 0) + { + ImGuizmo::SetScaleGizmoAxisLock(true); + auto radius = std::get<0>(trigger.trigger).radius; + object_matrix = glm::scale(object_matrix, { radius, radius, radius }); + } + else if (operation == ImGuizmo::SCALE) + { + operation = ImGuizmo::BOUNDS; + + auto box = std::get<1>(trigger.trigger); + auto ext_max = box.extents_max; + auto ext_min = box.extents_min; + auto size = ext_max - ext_min; + + object_matrix = glm::rotate(object_matrix, box.orientation, { 0, 1, 0 }); + object_matrix = glm::scale(object_matrix, size); + bounds[0] = ext_min.x / size.x; + bounds[1] = ext_min.y / size.y; + bounds[2] = ext_min.z / size.z; + bounds[3] = ext_max.x / size.x; + bounds[4] = ext_max.y / size.y; + bounds[5] = ext_max.z / size.z; + } + + if (!ImGuizmo::Manipulate(glm::value_ptr(model_view_trs) + , glm::value_ptr(projection_trs) + , operation, mode + , glm::value_ptr(object_matrix) + , glm::value_ptr(delta_matrix) + , nullptr + , operation == ImGuizmo::BOUNDS ? bounds : nullptr) && operation != ImGuizmo::BOUNDS) + { + return false; + } + + if (!ImGuizmo::IsUsing()) + { + return false; + } + + out_delta_matrix = operation == ImGuizmo::BOUNDS ? object_matrix : delta_matrix; + + return true; + } + + bool has_changed(const ImGuizmo::OPERATION operation, glm::vec3& new_translation, glm::quat& new_orientation, glm::vec3& new_scale) + { + switch (operation) + { + case ImGuizmo::TRANSLATE: + { + if (new_translation.x == 0.0f && new_translation.y == 0.0f && new_translation.z == 0.0f) + return false; + break; + } + case ImGuizmo::ROTATE: + { + if (new_orientation.x == -0.0f && new_orientation.y == -0.0f && new_orientation.z == -0.0f) + return false; + break; + } + case ImGuizmo::SCALE: + { + if (new_scale.x == 1.0f && new_scale.y == 1.0f && new_scale.z == 1.0f) + return false; + break; + } + case ImGuizmo::BOUNDS: + { + if (new_scale.x == 1.0f && new_scale.y == 1.0f && new_scale.z == 1.0f) + return false; + break; + } + } + return true; + } + + void AreaTriggerTool::apply_changes(ImGuizmo::MODE mode, ImGuizmo::OPERATION operation, glm::mat4 const& delta_matrix, area_trigger& trigger) + { + glm::vec3 new_scale; + glm::quat new_orientation; + glm::vec3 new_translation; + glm::vec3 new_skew_; + glm::vec4 new_perspective_; + + glm::decompose(delta_matrix, + new_scale, + new_orientation, + new_translation, + new_skew_, + new_perspective_ + ); + + if (!has_changed(operation, new_translation, new_orientation, new_scale)) + { + return; + } + + new_scale = glm::clamp(new_scale, 0.01f, 500.f); // These values are arbitrary. Feel free to change them + + NOGGIT_ACTION_MGR->beginAction(mapView(), Noggit::ActionFlags::eAREA_TRIGGER_TRANSFORMED, Noggit::ActionModalityControllers::eLMB); + + NOGGIT_CUR_ACTION->registerAreaTriggerTransformed(&trigger); + + if (operation == ImGuizmo::TRANSLATE) + { + trigger.position += new_translation; + _editor->selection_translated(trigger.position); + } + if (operation == ImGuizmo::ROTATE) + { + std::visit([&](auto&& t) + { + if constexpr (std::is_same_v, box_trigger>) + { + t.orientation += new_orientation.y; + _editor->selection_rotated(t.orientation); + } + }, trigger.trigger); + } + if (operation == ImGuizmo::SCALE) + { + std::visit([&](auto&& t) + { + if constexpr (std::is_same_v, sphere_trigger>) + { + t.radius = new_scale.z; + _editor->selection_scaled(new_scale.z); + } + else if constexpr (std::is_same_v, box_trigger>) + { + t.extents_min = glm::vec3{ -new_scale.x / 2, -new_scale.y / 2, -new_scale.z / 2 }; + t.extents_max = glm::vec3{ new_scale.x / 2, new_scale.y / 2, new_scale.z / 2 }; + _editor->selection_scaled(new_scale); + } + }, trigger.trigger); + } + if (operation == ImGuizmo::BOUNDS) + { + std::visit([&](auto&& t) + { + if constexpr (std::is_same_v, box_trigger>) + { + trigger.position = new_translation; + t.extents_min = glm::vec3{ -new_scale.x / 2, -new_scale.y / 2, -new_scale.z / 2 }; + t.extents_max = glm::vec3{ new_scale.x / 2, new_scale.y / 2, new_scale.z / 2 }; + _editor->selection_translated(new_translation); + _editor->selection_scaled(new_scale); + } + }, trigger.trigger); + } + + trigger.write_to_dbc(); + } +} diff --git a/src/noggit/tools/AreaTriggerTool.hpp b/src/noggit/tools/AreaTriggerTool.hpp new file mode 100644 index 00000000..22c76baf --- /dev/null +++ b/src/noggit/tools/AreaTriggerTool.hpp @@ -0,0 +1,73 @@ +// This file is part of Noggit3, licensed under GNU General Public License (version 3). + +#pragma once + +#include +#include +#include +#include + +#include + +namespace Noggit +{ + namespace Ui + { + class hole_tool; + + namespace Tools + { + class AreaTriggerEditor; + } + } + + class AreaTriggerTool final : public Tool + { + public: + AreaTriggerTool(MapView* mapView); + ~AreaTriggerTool(); + + [[nodiscard]] + virtual char const* name() const override; + + [[nodiscard]] + virtual editing_mode editingMode() const override; + + [[nodiscard]] + virtual Ui::FontNoggit::Icons icon() const override; + + void setupUi(Ui::Tools::ToolPanel* toolPanel) override; + + [[nodiscard]] + ToolDrawParameters drawParameters() const override; + + void onSelected() override; + + void onDeselected() override; + + void onMouseRelease(MouseReleaseParameters const& params) override; + + void postRender() override; + + void renderImGui(ImGuizmo::MODE mode, ImGuizmo::OPERATION operation) override; + + void saveSettings() override; + + private: + Ui::Tools::AreaTriggerEditor* _editor = nullptr; + Noggit::Rendering::Primitives::WireBox _boxRenderer; + Noggit::Rendering::Primitives::Sphere _sphereRenderer; + + uint32_t _selected_area_trigger = std::numeric_limits::max(); + + void jump_to_area_trigger(const Noggit::MouseReleaseParameters& params); + + void select_area_trigger(); + + std::optional get_selected_trigger(); + + bool manipulate_gizmo(ImGuizmo::MODE mode, ImGuizmo::OPERATION& operation, area_trigger const& trigger, glm::mat4& out_delta_matrix); + + void apply_changes(ImGuizmo::MODE mode, ImGuizmo::OPERATION operation, glm::mat4 const& delta_matrix, area_trigger& trigger); + }; +} diff --git a/src/noggit/ui/FontNoggit.hpp b/src/noggit/ui/FontNoggit.hpp index 7005e45d..09f1f8d3 100755 --- a/src/noggit/ui/FontNoggit.hpp +++ b/src/noggit/ui/FontNoggit.hpp @@ -139,6 +139,8 @@ namespace Noggit CAMERA = 0xf8dd, WINDOW = 0xf8de, VISIBILITY_FLIGHT_BOUNDS = 0xf8df, + AREA_TRIGGER = 0xf8e0, + AREA_TRIGGER_SPHERE = 0xf8e1, }; }; diff --git a/src/noggit/ui/tools/AreaTriggerEditor/AreaTriggerEditor.cpp b/src/noggit/ui/tools/AreaTriggerEditor/AreaTriggerEditor.cpp new file mode 100644 index 00000000..dfa190c0 --- /dev/null +++ b/src/noggit/ui/tools/AreaTriggerEditor/AreaTriggerEditor.cpp @@ -0,0 +1,687 @@ +// This file is part of Noggit3, licensed under GNU General Public License (version 3). + +#include "AreaTriggerEditor.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Noggit::Ui::Tools +{ + constexpr int TRIGGER_ID_USERDATA = Qt::UserRole + 1; + + AreaTriggerEditor::areatrigger_description::areatrigger_description( + uint32_t id, + std::string zone_name, + std::string sub_category, + std::string trigger_name, + bool is_builtin) + : id{ id } + , zone_name{ std::move(zone_name) } + , sub_category{ std::move(sub_category) } + , trigger_name{ std::move(trigger_name) } + , is_builtin{ is_builtin } + { + } + + AreaTriggerEditor::AreaTriggerEditor(MapView* map_view, QWidget* parent) + : QWidget{ parent } + , _map_view{ map_view } + { + parseDescriptions(); + + setMinimumWidth(250); + + auto layout = new QVBoxLayout(this); + layout->setAlignment(Qt::AlignTop); + + createListView(layout); + createSelectionWidgets(layout); + createInfoWidgets(layout); + + for (auto&& record : gAreaTriggerDB) + { + auto const id = record.getUInt(AreaTriggerDB::Id); + _max_dbc_entry = std::max(_max_dbc_entry, id); + } + } + + void AreaTriggerEditor::clearSelection() + { + _selected_trigger_id = std::numeric_limits::max(); + + _list_widget->clearSelection(); + + _selection_widgets.zone_name_widget->clear(); + _selection_widgets.category_name_widget->clear(); + _selection_widgets.trigger_name_widget->clear(); + + _selection_widgets.entry_spinbox->clear(); + _selection_widgets.map_spinbox->clear(); + _selection_widgets.position_widget->clear(); + _selection_widgets.size_widget->clear(); + _selection_widgets.rotation_widget->clear(); + _selection_widgets.radius_widget->clear(); + } + + void AreaTriggerEditor::set_selected(area_trigger& trigger) + { + _selected_trigger_id = trigger.id; + + if (auto itr = _list_items.find(trigger.id); itr != _list_items.end()) + { + QSignalBlocker const _a(_list_widget); + itr->second->setSelected(true); + _list_widget->scrollToItem(itr->second); + } + + if (auto description = _trigger_descriptions.At(_selected_trigger_id); description) + { + _selection_widgets.zone_name_widget->setText(description->zone_name.c_str()); + _selection_widgets.category_name_widget->setText(description->sub_category.c_str()); + _selection_widgets.trigger_name_widget->setText(description->trigger_name.c_str()); + } + + QSignalBlocker const _a(_selection_widgets.entry_spinbox); + QSignalBlocker const _b(_selection_widgets.map_spinbox); + QSignalBlocker const _c(_selection_widgets.position_widget); + QSignalBlocker const _d(_selection_widgets.radius_widget); + QSignalBlocker const _e(_selection_widgets.size_widget); + QSignalBlocker const _f(_selection_widgets.rotation_widget); + + _selection_widgets.entry_spinbox->setValue(trigger.id); + _selection_widgets.map_spinbox->setValue(trigger.map_id); + _selection_widgets.position_widget->setValue(&trigger.position.x); + + std::visit([&](auto&& t) { + if constexpr (std::is_same_v, sphere_trigger>) + { + _selection_widgets.radius_widget->setValue(t.radius); + + _selection_widgets.radius_widget->setDisabled(false); + _selection_widgets.size_widget->setDisabled(true); + _selection_widgets.rotation_widget->setDisabled(true); + _selection_widgets.size_widget->clear(); + _selection_widgets.rotation_widget->clear(); + } + else if constexpr (std::is_same_v, box_trigger>) + { + auto size = t.extents_max - t.extents_min; + _selection_widgets.size_widget->setValue(&size.x); + _selection_widgets.rotation_widget->setValue(t.orientation); + + _selection_widgets.radius_widget->setDisabled(true); + _selection_widgets.radius_widget->clear(); + _selection_widgets.size_widget->setDisabled(false); + _selection_widgets.rotation_widget->setDisabled(false); + } + }, trigger.trigger); + + emit selectionChanged(trigger.id); + } + + void AreaTriggerEditor::selection_translated(glm::vec3& new_position) + { + QSignalBlocker const _(_selection_widgets.position_widget); + _selection_widgets.position_widget->setValue(&new_position.x); + } + + void AreaTriggerEditor::selection_rotated(float new_rotation) + { + QSignalBlocker const _(_selection_widgets.rotation_widget); + _selection_widgets.rotation_widget->setValue(new_rotation); + } + + void AreaTriggerEditor::selection_scaled(glm::vec3& new_scale) + { + QSignalBlocker const _(_selection_widgets.size_widget); + _selection_widgets.size_widget->setValue(&new_scale.x); + } + + void AreaTriggerEditor::selection_scaled(float new_scale) + { + QSignalBlocker const _(_selection_widgets.radius_widget); + _selection_widgets.radius_widget->setValue(new_scale); + } + + void AreaTriggerEditor::save() + { + auto const file_path = Noggit::Application::NoggitApplication::instance()->getConfiguration()->ApplicationNoggitDefinitionsPath + + "\\AreatriggerDescriptions.csv"; + + std::ofstream file{ file_path, std::ios_base::out }; + if (!file) + { + throw std::exception{ std::format("Could not open file {}!", file_path).c_str() }; + } + + file << "ID,Zone Name,Sub Category,Trigger Name,IsBuiltIn,\n"; + + for (auto&& [_, desc] : _trigger_descriptions) + { + file << std::format("{0},{1},{2},{3},{4},\n", desc.id, desc.zone_name, desc.sub_category, desc.trigger_name, desc.is_builtin ? '1' : '0'); + } + } + + void AreaTriggerEditor::parseDescriptions() + { + constexpr int expected_num_tokens = 6; + char const* expeceted_header = "ID,Zone Name,Sub Category,Trigger Name,IsBuiltIn,"; + + auto const file_path = Noggit::Application::NoggitApplication::instance()->getConfiguration()->ApplicationNoggitDefinitionsPath + + "\\AreatriggerDescriptions.csv"; + + QFile file{ QString::fromStdString(file_path) }; + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) + { + throw std::exception{ std::format("Could not open file {}!", file_path).c_str() }; + } + + QTextStream stream{ &file }; + if (auto header = stream.readLine(); header != expeceted_header) + { + auto foo = header.toStdString(); + throw std::exception{ std::format("File {} uses invalid header `{}`!", file_path, header.toStdString()).c_str() }; + } + + int line = 1; + std::vector> descriptions; + while (!stream.atEnd()) + { + ++line; + auto tokens = stream.readLine().split(','); + if (tokens.size() != expected_num_tokens) + { + LogError << std::format("Encountered wrong number of tokens (expected: {}, encountered: {}) in file {}!", expected_num_tokens, tokens.size(), file_path); + continue; + } + + descriptions.emplace_back(std::make_pair(tokens[0].toUInt() + , areatrigger_description{ + tokens[0].toUInt(), + tokens[1].trimmed().toStdString(), + tokens[2].trimmed().toStdString(), + tokens[3].trimmed().toStdString(), + tokens[4].toUInt() == 1 + })); + } + + _trigger_descriptions = util::FlatMap{ std::move(descriptions) }; + } + + std::string AreaTriggerEditor::formatTriggerName(uint32_t trigger_id) + { + auto info = _trigger_descriptions.At(trigger_id); + if (!info) + { + return std::format("{}: UNKNOWN", trigger_id); + } + + return std::format("{}: {} - {} - {}", trigger_id, info->zone_name, info->sub_category, info->trigger_name); + } + + void AreaTriggerEditor::createListView(QVBoxLayout* layout) + { + auto search_layout = new QHBoxLayout(this); + layout->addLayout(search_layout); + + auto search_bar = new QLineEdit{ this }; + search_layout->addWidget(search_bar); + + auto search_btn = new QPushButton{ this }; + search_btn->setMaximumWidth(20); + search_btn->setHidden(true); + search_btn->setIcon(Noggit::Ui::FontAwesomeIcon(Noggit::Ui::FontAwesome::times)); + search_layout->addWidget(search_btn, 0, Qt::AlignLeft); + + connect(search_btn, &QPushButton::pressed, [search_bar, search_btn] { + search_bar->clear(); + search_btn->setHidden(true); + }); + + connect(search_bar, &QLineEdit::textChanged, [&, search_btn](QString const& new_text) { + constexpr double score_cutoff = 50.0; + + if (new_text.isEmpty()) + { + search_btn->setHidden(true); + for (auto&& [_, item] : _list_items) + { + item->setHidden(false); + } + } + else + { + search_btn->setHidden(false); + for (auto&& [_, item] : _list_items) + { + bool hide = rapidfuzz::fuzz::token_set_ratio(item->text().toStdString(), new_text.toStdString()) <= score_cutoff; + item->setHidden(hide); + } + } + }); + + _list_widget = new QListWidget(this); + auto list_layout = new QVBoxLayout(_list_widget); + list_layout->setContentsMargins(0, 0, 0, 0); + + _list_widget->setViewMode(QListView::ListMode); + _list_widget->setSelectionMode(QAbstractItemView::SingleSelection); + _list_widget->setSelectionBehavior(QAbstractItemView::SelectItems); + _list_widget->setUniformItemSizes(true); + + for (auto&& record : gAreaTriggerDB) + { + area_trigger trigger{ record }; + if (trigger.map_id != _map_view->getWorld()->getMapID()) + { + continue; + } + + auto* item = new QListWidgetItem(); + item->setData(TRIGGER_ID_USERDATA, QVariant{ trigger.id }); + + if (trigger.trigger.index() == 0) + { + item->setIcon(Noggit::Ui::FontNoggitIcon(Noggit::Ui::FontNoggit::AREA_TRIGGER_SPHERE)); + } + else + { + item->setIcon(Noggit::Ui::FontNoggitIcon(Noggit::Ui::FontNoggit::AREA_TRIGGER)); + } + item->setText(formatTriggerName(trigger.id).c_str()); + _list_widget->addItem(item); + + _list_items[trigger.id] = item; + } + + connect(_list_widget, &QListWidget::currentItemChanged, [=](QListWidgetItem* current, QListWidgetItem* previous) { + uint32_t previous_id = std::numeric_limits::max(); + if (previous) + { + previous_id = previous->data(TRIGGER_ID_USERDATA).toUInt(); + } + QSignalBlocker _{ _list_widget }; + area_trigger trigger{ current->data(TRIGGER_ID_USERDATA).toUInt() }; + set_selected(trigger); + }); + + layout->addWidget(_list_widget); + + auto btn_layout = new QHBoxLayout(this); + layout->addLayout(btn_layout); + + auto add_btn = new QPushButton("Add New", this); + add_btn->setIcon(Noggit::Ui::FontAwesomeIcon(Noggit::Ui::FontAwesome::plus)); + connect(add_btn, &QPushButton::pressed, [=] { + auto dialog = new QDialog{ this }; + dialog->setWindowFlag(Qt::Dialog); + dialog->setWindowTitle("Add new Area Trigger..."); + dialog->setFixedWidth(250); + auto layout = new QVBoxLayout{ dialog }; + + auto radio_layout = new QHBoxLayout{ dialog }; + layout->addLayout(radio_layout); + + auto radio1 = new QRadioButton{ "Sphere" }; + radio1->setIcon(Noggit::Ui::FontNoggitIcon(Noggit::Ui::FontNoggit::AREA_TRIGGER_SPHERE)); + radio1->setChecked(true); + radio_layout->addWidget(radio1); + + auto radio2 = new QRadioButton{ "Box" }; + radio2->setIcon(Noggit::Ui::FontNoggitIcon(Noggit::Ui::FontNoggit::AREA_TRIGGER)); + radio_layout->addWidget(radio2); + + auto btn_layout = new QHBoxLayout{ dialog }; + layout->addLayout(btn_layout); + + auto add_btn = new QPushButton{ "Add", dialog }; + connect(add_btn, &QPushButton::pressed, [=] { + addNewTrigger(radio1->isChecked() ? TriggerKind::Sphere : TriggerKind::Box); + dialog->close(); + }); + btn_layout->addWidget(add_btn); + + auto cancel_btn = new QPushButton{ "Cancel", dialog }; + connect(cancel_btn, &QPushButton::pressed, [=] { dialog->close(); }); + btn_layout->addWidget(cancel_btn); + dialog->show(); + }); + btn_layout->addWidget(add_btn); + + auto remove_btn = new QPushButton("Remove Selected", this); + remove_btn->setIcon(Noggit::Ui::FontAwesomeIcon(Noggit::Ui::FontAwesome::times)); + connect(remove_btn, &QPushButton::pressed, [=] { + deleteSelectedTrigger(); + }); + btn_layout->addWidget(remove_btn); + } + + void AreaTriggerEditor::createSelectionWidgets(QVBoxLayout* layout) + { + auto group_box = new QGroupBox("Selected"); + auto radius_layout = new QFormLayout(group_box); + + auto update_text = [&]() + { + if (_selected_trigger_id == std::numeric_limits::max()) + { + return; + } + + _trigger_descriptions.Transform(_selected_trigger_id, [&](uint32_t key, areatrigger_description& description) + { + description.zone_name = _selection_widgets.zone_name_widget->text().toStdString(); + description.sub_category = _selection_widgets.category_name_widget->text().toStdString(); + description.trigger_name = _selection_widgets.trigger_name_widget->text().toStdString(); + }); + + _list_items[_selected_trigger_id]->setText(QString("%1: %2 - %3 - %4").arg(_selected_trigger_id) + .arg(_selection_widgets.zone_name_widget->text() + , _selection_widgets.category_name_widget->text() + , _selection_widgets.trigger_name_widget->text())); + }; + + _selection_widgets.zone_name_widget = new QLineEdit{ group_box }; + connect(_selection_widgets.zone_name_widget, &QLineEdit::textEdited, update_text); + radius_layout->addRow("Zone", _selection_widgets.zone_name_widget); + + _selection_widgets.category_name_widget = new QLineEdit{ group_box }; + connect(_selection_widgets.category_name_widget, &QLineEdit::textEdited, update_text); + radius_layout->addRow("Category", _selection_widgets.category_name_widget); + + _selection_widgets.trigger_name_widget = new QLineEdit{ group_box }; + connect(_selection_widgets.trigger_name_widget, &QLineEdit::textEdited, update_text); + radius_layout->addRow("Name", _selection_widgets.trigger_name_widget); + + _selection_widgets.entry_spinbox = new QSpinBox{ group_box }; + _selection_widgets.entry_spinbox->setReadOnly(true); + _selection_widgets.entry_spinbox->setMinimum(0); + _selection_widgets.entry_spinbox->setMaximum(std::numeric_limits::max()); + radius_layout->addRow("ID", _selection_widgets.entry_spinbox); + + _selection_widgets.map_spinbox = new QSpinBox{ group_box }; + _selection_widgets.map_spinbox->setReadOnly(true); + _selection_widgets.map_spinbox->setMinimum(0); + _selection_widgets.map_spinbox->setMaximum(std::numeric_limits::max()); + radius_layout->addRow("Map-ID", _selection_widgets.map_spinbox); + + _selection_widgets.position_widget = new Vector3fWidget{ group_box }; + connect(_selection_widgets.position_widget, &Vector3fWidget::valueChanged, [=](glm::vec3 const& value) { + if (_selected_trigger_id == std::numeric_limits::max()) + { + return; + } + + area_trigger trigger{ _selected_trigger_id }; + + NOGGIT_ACTION_MGR->beginAction(_map_view, Noggit::ActionFlags::eAREA_TRIGGER_TRANSFORMED, Noggit::ActionModalityControllers::eLMB); + NOGGIT_CUR_ACTION->registerAreaTriggerTransformed(&trigger); + + trigger.position = value; + trigger.write_to_dbc(); + }); + radius_layout->addRow("Position", _selection_widgets.position_widget); + + _selection_widgets.size_widget = new Vector3fWidget{ group_box }; + connect(_selection_widgets.size_widget, &Vector3fWidget::valueChanged, [=](glm::vec3 const& value) { + if (_selected_trigger_id == std::numeric_limits::max()) + { + return; + } + + area_trigger trigger{ _selected_trigger_id }; + + NOGGIT_ACTION_MGR->beginAction(_map_view, Noggit::ActionFlags::eAREA_TRIGGER_TRANSFORMED, Noggit::ActionModalityControllers::eLMB); + NOGGIT_CUR_ACTION->registerAreaTriggerTransformed(&trigger); + + if (trigger.trigger.index() != 1) + { + return; + } + + auto&& box = std::get<1>(trigger.trigger); + box.extents_min = glm::vec3{ -value.x / 2, -value.y / 2, -value.z / 2 }; + box.extents_max = glm::vec3{ value.x / 2, value.y / 2, value.z / 2 }; + trigger.write_to_dbc(); + }); + radius_layout->addRow("Size", _selection_widgets.size_widget); + + _selection_widgets.rotation_widget = new QDoubleSpinBox{ group_box }; + connect(_selection_widgets.rotation_widget, static_cast(&QDoubleSpinBox::valueChanged), [=](double value) { + if (_selected_trigger_id == std::numeric_limits::max()) + { + return; + } + + area_trigger trigger{ _selected_trigger_id }; + + NOGGIT_ACTION_MGR->beginAction(_map_view, Noggit::ActionFlags::eAREA_TRIGGER_TRANSFORMED, Noggit::ActionModalityControllers::eLMB); + NOGGIT_CUR_ACTION->registerAreaTriggerTransformed(&trigger); + + if (trigger.trigger.index() != 1) + { + return; + } + + std::get<1>(trigger.trigger).orientation = static_cast(value); + trigger.write_to_dbc(); + }); + radius_layout->addRow("Rotation", _selection_widgets.rotation_widget); + + _selection_widgets.radius_widget = new QDoubleSpinBox{ group_box }; + _selection_widgets.radius_widget->setMinimum(0.001); + _selection_widgets.radius_widget->setMaximum(500); + connect(_selection_widgets.radius_widget, static_cast(&QDoubleSpinBox::valueChanged), [=](double value) { + if (_selected_trigger_id == std::numeric_limits::max()) + { + return; + } + + area_trigger trigger{ _selected_trigger_id }; + + NOGGIT_ACTION_MGR->beginAction(_map_view, Noggit::ActionFlags::eAREA_TRIGGER_TRANSFORMED, Noggit::ActionModalityControllers::eLMB); + NOGGIT_CUR_ACTION->registerAreaTriggerTransformed(&trigger); + + if (trigger.trigger.index() != 0) + { + return; + } + + std::get<0>(trigger.trigger).radius = static_cast(value); + trigger.write_to_dbc(); + }); + radius_layout->addRow("Radius", _selection_widgets.radius_widget); + + layout->addWidget(group_box); + } + + void generate_hotkey_row(std::initializer_list&& hotkeys, const char* description, QWidget* parent, QFormLayout* layout) + { + auto row_layout = new QHBoxLayout(parent); + + const char* from = nullptr; + auto icon = hotkeys.begin(); + + while (*description) + { + if (*description == '\a') + { + if (from) + { + auto label = new QLabel(::std::string(from, description - from).c_str()); + row_layout->addWidget(label); + } + + auto label = new QLabel(parent); + QIcon hotkey_icon = FontNoggitIcon(*icon++); + label->setPixmap(hotkey_icon.pixmap(22, 22)); + row_layout->addWidget(label); + + from = ++description; + } + else + { + if (!from) + { + from = description; + } + ++description; + } + } + + if (from && *from) + { + auto label = new QLabel(from); + row_layout->addWidget(label); + } + row_layout->setAlignment(Qt::AlignLeft); + layout->addRow(row_layout); + } + + void AreaTriggerEditor::createInfoWidgets(QVBoxLayout* layout) + { + auto group_box = new QGroupBox("Info"); + auto radius_layout = new QFormLayout(group_box); + layout->addWidget(group_box); + + generate_hotkey_row({ FontNoggit::mmb }, "\aJump to selected Trigger", group_box, radius_layout); + } + + void AreaTriggerEditor::addNewTrigger(TriggerKind kind) + { + auto&& record = gAreaTriggerDB.addRecord(++_max_dbc_entry); + area_trigger trigger{ record }; + trigger.id = _max_dbc_entry; + trigger.map_id = _map_view->getWorld()->getMapID(); + trigger.position = _map_view->getCamera()->position; + + auto selection = _map_view->intersect_result(false); + if (!selection.empty()) + { + std::visit([&](auto&& selection) { + if constexpr (std::is_same_v, selected_object_type>) + { + // TODO: get the hit vertex and create the area trigger there + } + else if constexpr (std::is_same_v, selected_chunk_type>) + { + trigger.position = selection.position; + } + + }, selection.front().second); + } + + QIcon icon = Noggit::Ui::FontNoggitIcon(Noggit::Ui::FontNoggit::AREA_TRIGGER_SPHERE); + if (kind == TriggerKind::Sphere) + { + trigger.trigger = sphere_trigger{ .radius = 1.f }; + } + else + { + trigger.trigger = box_trigger + { + .extents_min = { -0.5f, 0.0f, -0.5f }, + .extents_max = { 0.5f, 1.0f, 0.5f }, + .orientation = 0.f, + }; + icon = Noggit::Ui::FontNoggitIcon(Noggit::Ui::FontNoggit::AREA_TRIGGER); + } + trigger.write_to_dbc(); + emit areaTriggerCreated(trigger.id, record); + + areatrigger_description desc{ trigger.id, "ZoneName", "SubCategory", "TriggerName", false }; + _trigger_descriptions.Insert(trigger.id, desc); + + auto* item = new QListWidgetItem(); + item->setData(TRIGGER_ID_USERDATA, QVariant{ trigger.id }); + item->setIcon(icon); + item->setText(formatTriggerName(trigger.id).c_str()); + _list_widget->addItem(item); + _list_items[trigger.id] = item; + + set_selected(trigger); + } + + void AreaTriggerEditor::deleteSelectedTrigger() + { + if (_selected_trigger_id == std::numeric_limits::max()) + { + return; + } + + if (_selected_trigger_id == std::numeric_limits::max()) + { + return; + } + + auto dialog = new QDialog{ this }; + dialog->setWindowFlag(Qt::Dialog); + dialog->setWindowTitle("Remove Area Trigger..."); + dialog->setFixedWidth(250); + auto layout = new QVBoxLayout{ dialog }; + + auto label = new QLabel{ "Are you sure you want to delete the selected\nArea Trigger?", dialog }; + layout->addWidget(label); + + if (auto desc = _trigger_descriptions.At(_selected_trigger_id); desc) + { + if (desc->is_builtin) + { + auto label2 = new QLabel{ "THIS WILL DELETE A BUILT-IN TRIGGER!\n" + "THIS MAY BREAK THE GAME!\n\n" + "Don't do this if you don't know what you're\n" + "doing!", dialog }; + label2->setStyleSheet("QLabel { color: RED }"); + layout->addWidget(label2); + } + } + + auto btn_layout = new QHBoxLayout{ dialog }; + layout->addLayout(btn_layout); + + auto yes_btn = new QPushButton{ "Yes", dialog }; + connect(yes_btn, &QPushButton::pressed, [=] { + _list_items.erase(_selected_trigger_id); + + _trigger_descriptions.Erase(_selected_trigger_id); + + gAreaTriggerDB.removeRecord(_selected_trigger_id); + + clearSelection(); + emit selectionChanged(_selected_trigger_id); + + dialog->close(); + }); + btn_layout->addWidget(yes_btn); + + auto no_btn = new QPushButton{ "No", dialog }; + connect(no_btn, &QPushButton::pressed, [=] { + dialog->close(); + }); + btn_layout->addWidget(no_btn); + + dialog->show(); + } +} diff --git a/src/noggit/ui/tools/AreaTriggerEditor/AreaTriggerEditor.hpp b/src/noggit/ui/tools/AreaTriggerEditor/AreaTriggerEditor.hpp new file mode 100644 index 00000000..54a75ee9 --- /dev/null +++ b/src/noggit/ui/tools/AreaTriggerEditor/AreaTriggerEditor.hpp @@ -0,0 +1,106 @@ +// This file is part of Noggit3, licensed under GNU General Public License (version 3). + +#pragma once + +#include +#include + +#include + +#include + +#include +#include + +class MapView; +class QLineEdit; +class QListWidget; +class QListWidgetItem; +class QTabWidget; +class QVBoxLayout; +class QSpinBox; +class QDoubleSpinBox; + +namespace Noggit +{ + struct area_trigger; + + namespace Ui + { + class Vector3fWidget; + } +} + +namespace Noggit::Ui::Tools +{ + class AreaTriggerEditor : public QWidget + { + Q_OBJECT + public: + AreaTriggerEditor(MapView* map_view, QWidget* parent = nullptr); + + void clearSelection(); + void set_selected(area_trigger& trigger); + + void selection_translated(glm::vec3& new_position); + void selection_rotated(float new_rotation); + void selection_scaled(glm::vec3& new_scale); + void selection_scaled(float new_scale); + + void save(); + + void deleteSelectedTrigger(); + + Q_SIGNALS: + void selectionChanged(uint32_t current_id); + void areaTriggerCreated(uint32_t id, DBCFile::Record& record); + + private: + struct areatrigger_description + { + uint32_t id = 0; + std::string zone_name; + std::string sub_category; + std::string trigger_name; + bool is_builtin = false; + + areatrigger_description() = default; + areatrigger_description(uint32_t id, std::string zone_name, std::string sub_category, std::string trigger_name, bool is_builtin); + }; + + util::FlatMap _trigger_descriptions; + + MapView* _map_view = nullptr; + QListWidget* _list_widget = nullptr; + + uint32_t _max_dbc_entry = 0; + + struct + { + QSpinBox* entry_spinbox = nullptr; + QSpinBox* map_spinbox = nullptr; + QLineEdit* zone_name_widget = nullptr; + QLineEdit* category_name_widget = nullptr; + QLineEdit* trigger_name_widget = nullptr; + Vector3fWidget* position_widget = nullptr; + Vector3fWidget* size_widget = nullptr; + QDoubleSpinBox* rotation_widget = nullptr; + QDoubleSpinBox* radius_widget = nullptr; + } _selection_widgets; + + std::unordered_map _list_items; + + uint32_t _selected_trigger_id = std::numeric_limits::max(); + + void parseDescriptions(); + + std::string formatTriggerName(uint32_t trigger_id); + + void createListView(QVBoxLayout* layout); + void createSelectionWidgets(QVBoxLayout* layout); + void createInfoWidgets(QVBoxLayout* layout); + + enum class TriggerKind { Sphere, Box }; + void addNewTrigger(TriggerKind kind); + }; +} diff --git a/src/noggit/ui/tools/LightEditor/LightEditor.cpp b/src/noggit/ui/tools/LightEditor/LightEditor.cpp index 60573719..7ccaccda 100644 --- a/src/noggit/ui/tools/LightEditor/LightEditor.cpp +++ b/src/noggit/ui/tools/LightEditor/LightEditor.cpp @@ -2,6 +2,7 @@ #include "LightEditor.hpp" #include +#include #include #include #include diff --git a/src/noggit/ui/windows/noggitWindow/NoggitWindow.cpp b/src/noggit/ui/windows/noggitWindow/NoggitWindow.cpp index 40485f1c..ae2942b9 100755 --- a/src/noggit/ui/windows/noggitWindow/NoggitWindow.cpp +++ b/src/noggit/ui/windows/noggitWindow/NoggitWindow.cpp @@ -302,7 +302,7 @@ namespace Noggit::Ui::Windows _minimap->world(getWorld()); - _project->ClientDatabase->UnloadTable("Map"); + //_project->ClientDatabase->UnloadTable("Map"); } void NoggitWindow::buildMenu() diff --git a/src/noggit/ui/windows/noggitWindow/NoggitWindow.hpp b/src/noggit/ui/windows/noggitWindow/NoggitWindow.hpp index fc6c82c5..aa5ce268 100755 --- a/src/noggit/ui/windows/noggitWindow/NoggitWindow.hpp +++ b/src/noggit/ui/windows/noggitWindow/NoggitWindow.hpp @@ -27,6 +27,11 @@ namespace Noggit::Ui class minimap_widget; class settings; class about; + + namespace Tools::MapCreationWizard::Ui + { + class MapCreationWizard; + } } namespace Noggit::Ui::Windows diff --git a/src/util/FlatMap.cpp b/src/util/FlatMap.cpp new file mode 100644 index 00000000..e69de29b diff --git a/src/util/FlatMap.hpp b/src/util/FlatMap.hpp new file mode 100644 index 00000000..f91b79cb --- /dev/null +++ b/src/util/FlatMap.hpp @@ -0,0 +1,57 @@ +// This file is part of Noggit3, licensed under GNU General Public License (version 3). + +#pragma once + +#include +#include +#include + +namespace util +{ + template + struct FlatMap + { + std::vector> data; + + FlatMap() = default; + FlatMap(std::vector>&& data) : + data{ std::forward>>(data) } + { + } + + constexpr void Insert(KEY key, VALUE value) + { + data.emplace_back(std::move(key), std::move(value)); + } + + constexpr void Erase(KEY key) + { + data.erase(std::remove_if(data.begin(), data.end(), [key](auto const& pair) { + return pair.first == key; + }), data.end()); + } + + [[nodiscard]] constexpr std::optional At(KEY const& key) const + { + auto const itr = std::find_if(std::begin(data), std::end(data), [&key](auto const& v) { return v.first == key; }); + if (itr != std::end(data)) + { + return itr->second; + } + return {}; + } + + template + [[nodiscard]] constexpr void Transform(KEY const& key, FUNC&& func) + { + auto itr = std::find_if(std::begin(data), std::end(data), [&key](auto const& v) { return v.first == key; }); + if (itr != std::end(data)) + { + func(itr->first, itr->second); + } + } + + auto begin() { return data.begin(); } + auto end() { return data.end(); } + }; +} From 50692e52a9363435a39c95a75763eb46ddbfdde4 Mon Sep 17 00:00:00 2001 From: dwg Date: Sat, 4 Jan 2025 13:08:38 +0100 Subject: [PATCH 2/4] bugfix: You can now select areas using the AreaTool again (CTRL+Left Click) --- src/noggit/MapView.cpp | 4 +++ src/noggit/Tool.hpp | 4 +++ src/noggit/tools/AreaTool.cpp | 57 ++++++++++++++++++----------------- src/noggit/tools/AreaTool.hpp | 6 ++-- 4 files changed, 41 insertions(+), 30 deletions(-) diff --git a/src/noggit/MapView.cpp b/src/noggit/MapView.cpp index 6ddc2157..d2c45984 100644 --- a/src/noggit/MapView.cpp +++ b/src/noggit/MapView.cpp @@ -3972,7 +3972,11 @@ void MapView::mousePressEvent(QMouseEvent* event) activeTool()->onMousePress({ .button = event->button(), .mouse_position = event->pos(), + .mod_shift_down = _mod_shift_down, .mod_ctrl_down = _mod_ctrl_down, + .mod_alt_down = _mod_alt_down, + .mod_num_down = _mod_num_down, + .mod_space_down = _mod_space_down, }); switch (event->button()) diff --git a/src/noggit/Tool.hpp b/src/noggit/Tool.hpp index 268230c4..6ad49667 100644 --- a/src/noggit/Tool.hpp +++ b/src/noggit/Tool.hpp @@ -56,7 +56,11 @@ namespace Noggit { Qt::MouseButton button = Qt::MouseButton::NoButton; QPoint mouse_position; + bool mod_shift_down = false; bool mod_ctrl_down = false; + bool mod_alt_down = false; + bool mod_num_down = false; + bool mod_space_down = false; }; struct MouseReleaseParameters diff --git a/src/noggit/tools/AreaTool.cpp b/src/noggit/tools/AreaTool.cpp index 939c0337..13480ec1 100644 --- a/src/noggit/tools/AreaTool.cpp +++ b/src/noggit/tools/AreaTool.cpp @@ -85,33 +85,6 @@ namespace Noggit mapView()->getWorld()->renderer()->getTerrainParamsUniformBlock()->draw_areaid_overlay = false; } - void AreaTool::onTick(float deltaTime, TickParameters const& params) - { - if (!mapView()->getWorld()->has_selection() || params.underMap || !params.left_mouse) - { - return; - } - - if (params.mod_shift_down) - { - NOGGIT_ACTION_MGR->beginAction(mapView(), Noggit::ActionFlags::eCHUNKS_AREAID, - Noggit::ActionModalityControllers::eSHIFT - | Noggit::ActionModalityControllers::eLMB); - // draw the selected AreaId on current selected chunk - mapView()->getWorld()->setAreaID(mapView()->cursorPosition(), _selectedAreaId, false, _areaTool->brushRadius()); - } - else if (params.mod_ctrl_down) - { - for (auto&& selection : mapView()->getWorld()->current_selection()) - { - MapChunk* chnk(std::get(selection).chunk); - int newID = chnk->getAreaID(); - _selectedAreaId = newID; - _areaTool->setZoneID(newID); - } - } - } - void AreaTool::onMouseMove(MouseMoveParameters const& params) { if (params.left_mouse && params.mod_alt_down && !params.mod_shift_down && !params.mod_ctrl_down) @@ -119,4 +92,34 @@ namespace Noggit _areaTool->changeRadius(params.relative_movement.dx() / XSENS); } } + + void AreaTool::onMousePress(MousePressParameters const& params) + { + if (params.button != Qt::LeftButton) + { + return; + } + + if (params.mod_shift_down) + { + NOGGIT_ACTION_MGR->beginAction(mapView(), Noggit::ActionFlags::eCHUNKS_AREAID, + Noggit::ActionModalityControllers::eSHIFT + | Noggit::ActionModalityControllers::eLMB); + // draw the selected AreaId on current selected chunk + mapView()->getWorld()->setAreaID(mapView()->cursorPosition(), _selectedAreaId, false, _areaTool->brushRadius()); + } + else if(params.mod_ctrl_down) + { + mapView()->doSelection(true); + + for (auto&& selection : mapView()->getWorld()->current_selection()) + { + MapChunk* chnk(std::get(selection).chunk); + int newID = chnk->getAreaID(); + _selectedAreaId = newID; + _areaTool->setZoneID(newID); + } + return; + } + } } diff --git a/src/noggit/tools/AreaTool.hpp b/src/noggit/tools/AreaTool.hpp index 64dedd3b..f9f2c4a2 100644 --- a/src/noggit/tools/AreaTool.hpp +++ b/src/noggit/tools/AreaTool.hpp @@ -37,12 +37,12 @@ namespace Noggit virtual void onDeselected(); - void onTick(float deltaTime, TickParameters const& params) override; - void onMouseMove(MouseMoveParameters const& params) override; + void onMousePress(MousePressParameters const& params) override; + private: Ui::zone_id_browser* _areaTool = nullptr; int _selectedAreaId = -1; }; -} \ No newline at end of file +} From 5c66b1d5e905aa5ddc48ecf4bbcdb6f064d4e683 Mon Sep 17 00:00:00 2001 From: dwg Date: Sat, 4 Jan 2025 23:03:24 +0100 Subject: [PATCH 3/4] Fix cursor color not updating in vertex painter tool https://discord.com/channels/947571309175504926/1306407414244835469 --- src/noggit/MapView.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/noggit/MapView.cpp b/src/noggit/MapView.cpp index d2c45984..c50423fa 100644 --- a/src/noggit/MapView.cpp +++ b/src/noggit/MapView.cpp @@ -3619,7 +3619,7 @@ void MapView::draw_map() _model_view , _projection , _cursor_pos - , cursor_color + , cursorColor , ref_pos , _camera.position , &minimapRenderSettings From 11cdee946fd413b8eec0db8b702c40d18c90d46a Mon Sep 17 00:00:00 2001 From: DennisWG <13482820-DennisWG@users.noreply.gitlab.com> Date: Fri, 17 Jan 2025 21:25:24 +0000 Subject: [PATCH 4/4] Compile-speedup --- src/math/bounding_box.cpp | 1 - src/math/interpolation.hpp | 2 - src/math/ray.cpp | 12 +- src/math/ray.hpp | 11 +- src/math/trig.cpp | 4 +- src/math/trig.hpp | 9 +- src/noggit/Action.cpp | 28 +- src/noggit/Action.hpp | 17 +- src/noggit/ActionManager.cpp | 2 +- src/noggit/Alphamap.cpp | 1 + src/noggit/Alphamap.hpp | 4 +- src/noggit/AsyncLoader.cpp | 17 +- src/noggit/AsyncLoader.h | 8 +- src/noggit/AsyncObject.cpp | 64 ++++ src/noggit/AsyncObject.h | 63 +--- src/noggit/AsyncObjectMultimap.hpp | 6 +- src/noggit/Brush.cpp | 4 + src/noggit/Camera.hpp | 4 +- src/noggit/ChunkWater.cpp | 40 +++ src/noggit/ChunkWater.hpp | 23 +- src/noggit/DBCFile.cpp | 153 ++++++++- src/noggit/DBCFile.h | 143 ++------ src/noggit/MapChunk.cpp | 86 ++++- src/noggit/MapChunk.h | 57 ++-- src/noggit/MapTile.cpp | 116 ++++++- src/noggit/MapTile.h | 76 ++--- src/noggit/MapView.cpp | 61 +++- src/noggit/MapView.h | 74 +++-- src/noggit/Misc.cpp | 46 ++- src/noggit/Misc.h | 57 +--- src/noggit/Model.cpp | 77 ++++- src/noggit/Model.h | 51 ++- src/noggit/ModelInstance.cpp | 140 +++++++- src/noggit/ModelInstance.h | 113 ++----- src/noggit/ModelManager.cpp | 67 +++- src/noggit/ModelManager.h | 68 +--- src/noggit/Particle.cpp | 1 + src/noggit/Particle.h | 8 +- src/noggit/SceneObject.cpp | 47 ++- src/noggit/SceneObject.hpp | 18 +- src/noggit/Selection.cpp | 28 +- src/noggit/Selection.h | 12 +- src/noggit/Sky.cpp | 161 ++++++++- src/noggit/Sky.h | 68 ++-- src/noggit/TextureManager.cpp | 114 +++++-- src/noggit/TextureManager.h | 71 +--- src/noggit/TileIndex.cpp | 41 +++ src/noggit/TileIndex.hpp | 28 +- src/noggit/TileWater.cpp | 42 ++- src/noggit/TileWater.hpp | 44 +-- src/noggit/Tool.cpp | 9 +- src/noggit/Tool.hpp | 1 - src/noggit/WMO.cpp | 101 +++++- src/noggit/WMO.h | 62 ++-- src/noggit/WMOInstance.cpp | 97 +++++- src/noggit/WMOInstance.h | 64 +--- src/noggit/World.cpp | 116 +++++-- src/noggit/World.h | 64 ++-- src/noggit/World.inl | 1 + src/noggit/application/ApplicationEntry.cpp | 21 +- .../NoggitApplicationConfigurationReader.cpp | 3 +- .../NoggitApplicationConfigurationReader.hpp | 5 +- .../NoggitApplicationConfigurationWriter.cpp | 6 +- .../NoggitApplicationConfigurationWriter.hpp | 11 +- src/noggit/application/NoggitApplication.cpp | 39 ++- src/noggit/application/NoggitApplication.hpp | 76 ++--- src/noggit/application/Utils.hpp | 2 + src/noggit/area_trigger.cpp | 2 + src/noggit/async_priority.hpp | 11 + src/noggit/liquid_layer.cpp | 59 ++++ src/noggit/liquid_layer.hpp | 31 +- src/noggit/map_horizon.cpp | 6 +- src/noggit/map_index.cpp | 103 +++++- src/noggit/map_index.hpp | 86 +---- src/noggit/project/ApplicationProject.cpp | 259 ++++++++++++++- src/noggit/project/ApplicationProject.h | 289 ++-------------- .../project/ApplicationProjectReader.cpp | 4 +- .../project/ApplicationProjectWriter.cpp | 2 - src/noggit/rendering/CursorRender.cpp | 2 + src/noggit/rendering/CursorRender.hpp | 11 +- src/noggit/rendering/FlightBoundsRender.cpp | 4 +- src/noggit/rendering/FlightBoundsRender.hpp | 6 +- src/noggit/rendering/LiquidRender.cpp | 4 + src/noggit/rendering/LiquidRender.hpp | 12 +- src/noggit/rendering/LiquidTextureManager.cpp | 1 + src/noggit/rendering/LiquidTextureManager.hpp | 6 +- src/noggit/rendering/ModelRender.cpp | 33 +- src/noggit/rendering/ModelRender.hpp | 30 +- src/noggit/rendering/Primitives.cpp | 29 +- src/noggit/rendering/Primitives.hpp | 20 +- src/noggit/rendering/TileRender.cpp | 80 ++++- src/noggit/rendering/TileRender.hpp | 32 +- src/noggit/rendering/WMOGroupRender.cpp | 9 + src/noggit/rendering/WMOGroupRender.hpp | 12 +- src/noggit/rendering/WMORender.cpp | 8 + src/noggit/rendering/WMORender.hpp | 13 +- src/noggit/rendering/WorldRender.cpp | 49 ++- src/noggit/rendering/WorldRender.hpp | 17 +- src/noggit/scoped_blp_texture_reference.cpp | 35 ++ src/noggit/scoped_blp_texture_reference.hpp | 35 ++ src/noggit/scripting/script_chunk.cpp | 1 + src/noggit/scripting/script_chunk.hpp | 2 +- src/noggit/scripting/script_global.cpp | 3 +- src/noggit/scripting/script_model.cpp | 2 + src/noggit/scripting/script_tex.cpp | 3 +- src/noggit/scripting/script_vert.cpp | 6 +- src/noggit/texture_set.cpp | 92 ++++- src/noggit/texture_set.hpp | 46 +-- src/noggit/tools/AreaTool.cpp | 2 + src/noggit/tools/AreaTriggerTool.cpp | 10 +- src/noggit/tools/AreaTriggerTool.hpp | 1 - src/noggit/tools/FlattenBlurTool.cpp | 3 + src/noggit/tools/HoleTool.cpp | 1 + src/noggit/tools/ImpassTool.cpp | 1 + src/noggit/tools/LightTool.cpp | 1 + src/noggit/tools/MinimapTool.cpp | 8 +- src/noggit/tools/ObjectTool.cpp | 7 + src/noggit/tools/RaiseLowerTool.cpp | 2 + src/noggit/tools/ScriptingTool.cpp | 2 +- src/noggit/tools/StampTool.cpp | 4 +- src/noggit/tools/TexturingTool.cpp | 16 +- src/noggit/tools/VertexPainterTool.cpp | 4 +- src/noggit/tools/WaterTool.cpp | 3 + src/noggit/ui/CurrentTexture.cpp | 7 +- src/noggit/ui/CurrentTexture.h | 2 - src/noggit/ui/DetailInfos.cpp | 1 + src/noggit/ui/DetailInfos.h | 4 +- src/noggit/ui/FlattenTool.cpp | 53 ++- src/noggit/ui/FlattenTool.hpp | 43 ++- src/noggit/ui/FontAwesome.cpp | 4 - src/noggit/ui/FontNoggit.cpp | 4 - src/noggit/ui/FramelessWindow.hpp | 1 + src/noggit/ui/GroundEffectsTool.cpp | 113 ++++++- src/noggit/ui/GroundEffectsTool.hpp | 313 ++++++++---------- src/noggit/ui/Help.cpp | 2 - src/noggit/ui/Help.h | 2 +- src/noggit/ui/HelperModels.cpp | 4 +- src/noggit/ui/MinimapCreator.cpp | 94 +++++- src/noggit/ui/MinimapCreator.hpp | 50 ++- src/noggit/ui/ModelImport.cpp | 13 +- src/noggit/ui/ModelImport.h | 5 +- src/noggit/ui/ObjectEditor.cpp | 60 +++- src/noggit/ui/ObjectEditor.h | 25 +- src/noggit/ui/RotationEditor.cpp | 14 +- src/noggit/ui/RotationEditor.h | 5 +- src/noggit/ui/ShaderTool.cpp | 54 ++- src/noggit/ui/ShaderTool.hpp | 48 ++- src/noggit/ui/TerrainTool.cpp | 78 ++++- src/noggit/ui/TerrainTool.hpp | 47 +-- src/noggit/ui/TextureList.cpp | 9 +- src/noggit/ui/TextureList.hpp | 1 - src/noggit/ui/TexturePicker.cpp | 7 +- src/noggit/ui/TexturePicker.h | 3 +- src/noggit/ui/TexturingGUI.cpp | 23 +- src/noggit/ui/Toolbar.cpp | 3 +- src/noggit/ui/Toolbar.h | 7 +- src/noggit/ui/UidFixWindow.cpp | 1 - src/noggit/ui/UidFixWindow.hpp | 11 +- src/noggit/ui/Water.cpp | 43 ++- src/noggit/ui/Water.h | 16 +- src/noggit/ui/WeightListWidgetItem.cpp | 9 +- src/noggit/ui/WeightListWidgetItem.hpp | 17 +- src/noggit/ui/ZoneIDBrowser.cpp | 49 ++- src/noggit/ui/ZoneIDBrowser.h | 31 +- src/noggit/ui/hole_tool.cpp | 11 +- src/noggit/ui/hole_tool.hpp | 9 +- src/noggit/ui/minimap_widget.cpp | 79 ++++- src/noggit/ui/minimap_widget.hpp | 25 +- src/noggit/ui/object_palette.cpp | 29 +- src/noggit/ui/object_palette.hpp | 124 +++---- src/noggit/ui/texture_palette_small.hpp | 13 +- src/noggit/ui/texture_swapper.cpp | 49 ++- src/noggit/ui/texture_swapper.hpp | 44 +-- src/noggit/ui/texturing_tool.cpp | 189 +++++++++-- src/noggit/ui/texturing_tool.hpp | 143 ++------ .../ActionHistoryNavigator.cpp | 4 + .../ActionHistoryNavigator.hpp | 6 +- .../AreaTriggerEditor/AreaTriggerEditor.cpp | 19 +- .../tools/AssetBrowser/BrowserModelView.cpp | 42 ++- .../tools/AssetBrowser/BrowserModelView.hpp | 26 +- .../ui/tools/AssetBrowser/Ui/AssetBrowser.cpp | 76 ++++- .../ui/tools/AssetBrowser/Ui/AssetBrowser.hpp | 63 ++-- .../AssetBrowser/Ui/Model/TreeManager.cpp | 11 + .../AssetBrowser/Ui/Model/TreeManager.hpp | 14 +- src/noggit/ui/tools/BrushStack/BrushStack.cpp | 24 +- src/noggit/ui/tools/BrushStack/BrushStack.hpp | 16 +- .../ui/tools/BrushStack/BrushStackItem.cpp | 46 ++- .../ui/tools/BrushStack/BrushStackItem.hpp | 150 +++++---- .../tools/ChunkManipulator/ChunkClipboard.cpp | 24 +- .../tools/ChunkManipulator/ChunkClipboard.hpp | 28 +- .../ui/tools/LightEditor/LightEditor.cpp | 31 +- .../ui/tools/LightEditor/LightEditor.hpp | 15 +- .../Ui/MapCreationWizard.cpp | 55 ++- .../Ui/MapCreationWizard.hpp | 59 ++-- .../ui/tools/NodeEditor/Nodes/BaseNode.cpp | 102 +++++- .../ui/tools/NodeEditor/Nodes/BaseNode.hpp | 50 ++- .../Nodes/Containers/JSON/CreateJSONArray.cpp | 1 + .../Containers/JSON/CreateJSONObject.cpp | 1 + .../Nodes/Containers/JSON/GetJSONValue.cpp | 3 +- .../Containers/JSON/JSONArrayGetValue.cpp | 1 + .../Nodes/Containers/JSON/JSONArrayInfo.cpp | 1 + .../Containers/JSON/JSONArrayInsertValue.cpp | 1 + .../Nodes/Containers/JSON/JSONArrayPush.cpp | 4 + .../Nodes/Containers/JSON/JSONArrayPush.hpp | 4 +- .../Nodes/Containers/JSON/JSONObjectInfo.cpp | 3 +- .../Nodes/Containers/JSON/LoadJSONObject.cpp | 1 + .../Nodes/Containers/JSON/SaveJSONObject.cpp | 1 + .../Nodes/Containers/JSON/SetJSONValue.cpp | 1 + .../Nodes/Containers/List/DataListNode.cpp | 4 +- .../Nodes/Containers/List/DataListNode.hpp | 5 +- .../Nodes/Containers/List/ListAddNode.cpp | 2 + .../Nodes/Containers/List/ListAddNode.hpp | 5 +- .../Nodes/Containers/List/ListClearNode.cpp | 1 + .../Nodes/Containers/List/ListDeclareNode.cpp | 4 + .../Nodes/Containers/List/ListDeclareNode.hpp | 5 +- .../Nodes/Containers/List/ListEraseNode.cpp | 1 + .../Nodes/Containers/List/ListGetNode.cpp | 2 + .../Nodes/Containers/List/ListGetNode.hpp | 5 +- .../Nodes/Containers/List/ListReserveNode.cpp | 1 + .../Nodes/Containers/List/ListSizeNode.cpp | 2 + .../NodeEditor/Nodes/ContextLogicNodeBase.cpp | 51 +++ .../NodeEditor/Nodes/ContextLogicNodeBase.hpp | 40 +-- .../NodeEditor/Nodes/ContextNodeBase.cpp | 26 ++ .../NodeEditor/Nodes/ContextNodeBase.hpp | 14 +- .../Nodes/Data/DataConstantNode.cpp | 4 + .../Nodes/Data/DataConstantNode.hpp | 5 +- .../Nodes/Data/Image/ImageBlendOpenGLNode.cpp | 4 + .../Nodes/Data/Image/ImageBlendOpenGLNode.hpp | 4 +- .../Nodes/Data/Image/ImageCreateNode.cpp | 4 + .../Nodes/Data/Image/ImageCreateNode.hpp | 5 +- .../Nodes/Data/Image/ImageFillNode.cpp | 1 + .../Data/Image/ImageGaussianBlurNode.cpp | 1 + .../Nodes/Data/Image/ImageGetPixelNode.cpp | 1 + .../Nodes/Data/Image/ImageGetRegionNode.cpp | 1 + .../Nodes/Data/Image/ImageInfoNode.cpp | 1 + .../Nodes/Data/Image/ImageInvertNode.cpp | 1 + .../Data/Image/ImageMaskRandomPointsNode.cpp | 2 + .../Nodes/Data/Image/ImageMirrorNode.cpp | 1 + .../Nodes/Data/Image/ImageResizeNode.cpp | 4 + .../Nodes/Data/Image/ImageResizeNode.hpp | 5 +- .../Nodes/Data/Image/ImageRotateNode.cpp | 4 + .../Nodes/Data/Image/ImageRotateNode.hpp | 5 +- .../Nodes/Data/Image/ImageSaveNode.cpp | 1 + .../Nodes/Data/Image/ImageScaleNode.cpp | 4 + .../Nodes/Data/Image/ImageScaleNode.hpp | 6 +- .../Nodes/Data/Image/ImageSetPixelNode.cpp | 1 + .../Nodes/Data/Image/ImageSetRegionNode.cpp | 1 + .../Nodes/Data/Image/ImageToGrayscaleNode.cpp | 1 + .../Nodes/Data/Image/ImageTranslateNode.cpp | 4 + .../Nodes/Data/Image/ImageTranslateNode.hpp | 5 +- .../Nodes/Data/Image/LoadImageNode.cpp | 1 + .../Nodes/Data/Image/LoadImageNode.hpp | 2 + .../Nodes/Data/Image/gaussianblur.cpp | 4 +- .../Nodes/Data/Image/gaussianblur.h | 1 - .../Nodes/Data/Noise/NoiseAbsNode.cpp | 1 + .../Nodes/Data/Noise/NoiseBillowNode.cpp | 4 + .../Nodes/Data/Noise/NoiseBillowNode.hpp | 4 +- .../Nodes/Data/Noise/NoiseBlendNode.cpp | 1 + .../Nodes/Data/Noise/NoiseCacheNode.cpp | 1 + .../Data/Noise/NoiseCheckerboardNode.cpp | 1 + .../Nodes/Data/Noise/NoiseClampNode.cpp | 1 + .../Nodes/Data/Noise/NoiseConstValueNode.cpp | 1 + .../Nodes/Data/Noise/NoiseCurveNode.cpp | 1 + .../Nodes/Data/Noise/NoiseCylindersNode.cpp | 1 + .../Nodes/Data/Noise/NoiseDisplaceNode.cpp | 1 + .../Nodes/Data/Noise/NoiseExponentNode.cpp | 1 + .../Nodes/Data/Noise/NoiseInvertNode.cpp | 1 + .../Nodes/Data/Noise/NoiseMathNode.cpp | 4 + .../Nodes/Data/Noise/NoiseMathNode.hpp | 2 +- .../Nodes/Data/Noise/NoisePerlinNode.cpp | 5 + .../Nodes/Data/Noise/NoisePerlinNode.hpp | 2 +- .../Nodes/Data/Noise/NoiseRidgedMultiNode.cpp | 4 + .../Nodes/Data/Noise/NoiseRidgedMultiNode.hpp | 2 +- .../Nodes/Data/Noise/NoiseScaleBiasNode.cpp | 1 + .../Nodes/Data/Noise/NoiseSelectNode.cpp | 1 + .../Nodes/Data/Noise/NoiseSpheresNode.cpp | 1 + .../Nodes/Data/Noise/NoiseTerraceNode.cpp | 1 + .../Nodes/Data/Noise/NoiseToImageNode.cpp | 1 + .../Data/Noise/NoiseTransformPointNode.cpp | 4 + .../Data/Noise/NoiseTransformPointNode.hpp | 4 +- .../Nodes/Data/Noise/NoiseTurbulenceNode.cpp | 1 + .../Nodes/Data/Noise/NoiseViewerNode.cpp | 6 + .../Nodes/Data/Noise/NoiseViewerNode.hpp | 6 +- .../Nodes/Data/Noise/NoiseVoronoiNode.cpp | 1 + .../Nodes/Data/Random/RandomDecimalNode.cpp | 1 + .../Data/Random/RandomDecimalRangeNode.cpp | 2 + .../Nodes/Data/Random/RandomIntegerNode.cpp | 1 + .../Data/Random/RandomIntegerRangeNode.cpp | 2 + .../Nodes/Data/Random/RandomSeedNode.cpp | 2 + .../Data/String/StringConcatenateNode.cpp | 1 + .../Nodes/Data/String/StringEndsWithNode.cpp | 1 + .../Nodes/Data/String/StringEqual.cpp | 1 + .../Nodes/Data/String/StringSizeNode.cpp | 1 + .../Nodes/Data/TypeParameterNode.cpp | 1 + .../NodeEditor/Nodes/Functions/PrintNode.cpp | 4 + .../NodeEditor/Nodes/Functions/PrintNode.hpp | 5 +- .../NodeEditor/Nodes/Logic/ConditionNode.cpp | 1 + .../NodeEditor/Nodes/Logic/ConditionNode.hpp | 5 +- .../NodeEditor/Nodes/Logic/LogicBeginNode.cpp | 5 +- .../NodeEditor/Nodes/Logic/LogicBeginNode.hpp | 4 - .../NodeEditor/Nodes/Logic/LogicChainNode.cpp | 1 + .../Nodes/Logic/LogicForLoopNode.cpp | 1 + .../NodeEditor/Nodes/Logic/LogicIfNode.cpp | 1 + .../NodeEditor/Nodes/Logic/LogicIfNode.hpp | 8 - .../Nodes/Logic/LogicProcedureNode.cpp | 5 +- .../Nodes/Logic/LogicProcedureNode.hpp | 14 +- .../Nodes/Logic/LogicReturnNode.cpp | 2 + .../Nodes/Logic/LogicReturnNode.hpp | 1 - .../Nodes/Logic/LogicWhileLoopNode.cpp | 1 + .../tools/NodeEditor/Nodes/LogicNodeBase.cpp | 74 +++++ .../tools/NodeEditor/Nodes/LogicNodeBase.hpp | 41 +-- .../Nodes/Math/Color/ColorMathNode.cpp | 5 + .../Nodes/Math/Color/ColorMathNode.hpp | 4 +- .../Nodes/Math/Color/ColorToRGBANode.cpp | 1 + .../Nodes/Math/Color/RGBAtoColorNode.cpp | 1 + .../tools/NodeEditor/Nodes/Math/MathNode.cpp | 5 + .../tools/NodeEditor/Nodes/Math/MathNode.hpp | 6 +- .../NodeEditor/Nodes/Math/MathUnaryNode.cpp | 4 + .../NodeEditor/Nodes/Math/MathUnaryNode.hpp | 4 +- .../Nodes/Math/Matrix/MatrixDecomposeNode.cpp | 3 + .../Nodes/Math/Matrix/MatrixMathNode.cpp | 4 + .../Nodes/Math/Matrix/MatrixMathNode.hpp | 5 +- .../Nodes/Math/Matrix/MatrixNode.cpp | 4 + .../Nodes/Math/Matrix/MatrixNode.hpp | 5 +- .../Matrix/MatrixRotateQuaternionNode.cpp | 2 + .../Nodes/Math/Matrix/MatrixTransformNode.cpp | 5 + .../Nodes/Math/Matrix/MatrixTransformNode.hpp | 4 +- .../Nodes/Math/Matrix/MatrixUnaryMathNode.cpp | 4 + .../Nodes/Math/Matrix/MatrixUnaryMathNode.hpp | 4 +- .../Nodes/Math/Vector/Vector2DToXYNode.cpp | 2 + .../Nodes/Math/Vector/Vector3DToXYZNode.cpp | 2 + .../Nodes/Math/Vector/Vector4DToXYZWNode.cpp | 2 + .../Nodes/Math/Vector/VectorMathNode.hpp | 3 + .../Math/Vector/VectorScalarMathNode.hpp | 2 + .../Nodes/Math/Vector/XYZWtoVector4DNode.cpp | 2 + .../Nodes/Math/Vector/XYZtoVector3DNode.cpp | 2 + .../Nodes/Math/Vector/XYtoVector2D.cpp | 2 + .../NodeEditor/Nodes/Scene/LogicBranch.cpp | 17 + .../NodeEditor/Nodes/Scene/LogicBranch.hpp | 22 +- .../NodeEditor/Nodes/Scene/NodeScene.cpp | 20 ++ .../NodeEditor/Nodes/Scene/NodeScene.hpp | 17 +- .../NodeEditor/Nodes/Scene/NodesContext.cpp | 33 ++ .../NodeEditor/Nodes/Scene/NodesContext.hpp | 31 +- .../Nodes/Widgets/QUnsignedSpinBox.cpp | 124 +++++++ .../Nodes/Widgets/QUnsignedSpinBox.hpp | 116 +------ .../World/Chunk/ChunkAddDetailDoodads.cpp | 14 +- .../Nodes/World/Chunk/ChunkAddTextureNode.cpp | 4 + .../World/Chunk/ChunkCanPaintTexture.cpp | 3 + .../Nodes/World/Chunk/ChunkClearHeight.cpp | 3 + .../Nodes/World/Chunk/ChunkClearShadows.cpp | 5 +- .../Nodes/World/Chunk/ChunkEraseTextures.cpp | 5 +- .../World/Chunk/ChunkEraseUnusedTextures.cpp | 6 +- .../World/Chunk/ChunkFindTextureNode.cpp | 6 +- .../Nodes/World/Chunk/ChunkGetAlphaLayer.cpp | 6 +- .../Nodes/World/Chunk/ChunkGetHeightmap.cpp | 5 +- .../World/Chunk/ChunkGetHeightmapImage.cpp | 6 +- .../World/Chunk/ChunkGetTextureByLayer.cpp | 6 +- .../World/Chunk/ChunkGetVertexColors.cpp | 3 + .../World/Chunk/ChunkGetVertexColorsImage.cpp | 3 + .../Nodes/World/Chunk/ChunkInfoNode.cpp | 6 +- .../World/Chunk/ChunkRecalculateNormals.cpp | 4 + .../Nodes/World/Chunk/ChunkSetAlphaLayer.cpp | 6 +- .../Nodes/World/Chunk/ChunkSetAreaID.cpp | 5 +- .../Nodes/World/Chunk/ChunkSetHeightmap.cpp | 3 + .../World/Chunk/ChunkSetHeightmapImage.cpp | 7 +- .../World/Chunk/ChunkSetHeightmapImage.hpp | 3 +- .../World/Chunk/ChunkSetVertexColors.cpp | 3 + .../World/Chunk/ChunkSetVertexColorsImage.cpp | 3 + .../Nodes/World/Chunk/ChunkSwapTexture.cpp | 3 + .../Nodes/World/Coordinates/GetChunk.cpp | 5 +- .../World/Coordinates/GetChunkFromPos.cpp | 5 + .../World/Coordinates/GetChunksInRange.cpp | 4 + .../Nodes/World/Coordinates/GetTile.cpp | 5 + .../Nodes/World/Coordinates/GetTileChunks.cpp | 3 + .../World/Coordinates/GetTileFromPos.cpp | 5 + .../World/Coordinates/GetTilesInRange.cpp | 4 + .../Nodes/World/Coordinates/HasTileAt.cpp | 4 + .../Nodes/World/Coordinates/HasTileAtPos.cpp | 4 + .../NodeEditor/Nodes/World/Holes/SetHole.cpp | 4 + .../Nodes/World/Holes/SetHoleADTAtPos.cpp | 4 + .../Nodes/World/Liquid/CropWaterAdtAtPos.cpp | 4 + .../Nodes/World/Liquid/GetWaterType.cpp | 4 + .../Nodes/World/Liquid/PaintLiquid.cpp | 6 + .../Nodes/World/Liquid/PaintLiquid.hpp | 3 +- .../Nodes/World/Liquid/SetWaterType.cpp | 6 + .../Nodes/World/Liquid/SetWaterType.hpp | 3 +- .../World/LoadedTiles/FixAllGapsNode.cpp | 4 + .../Nodes/World/Misc/WorldConstantsNode.cpp | 3 + .../Nodes/World/Object/AddObjectInstance.cpp | 11 +- .../World/Object/GetObjectInstanceByUID.cpp | 4 + .../Nodes/World/Object/ObjectInstanceInfo.cpp | 4 + .../Object/ObjectInstanceSetPosition.cpp | 3 + .../Object/ObjectInstanceSetRotation.cpp | 3 + .../World/Object/ObjectInstanceSetScale.cpp | 3 + .../AddObjectInstanceToSelection.cpp | 4 + .../AddObjectInstancesToSelectionRange.cpp | 4 + .../DeleteSelectedObjectInstances.cpp | 4 + .../Selection/DeselectObjectInstance.cpp | 4 + .../Selection/DeselectObjectInstanceByUID.cpp | 4 + .../GetLastSelectedObjectInstance.cpp | 4 + .../Selection/GetSelectedObjectInstances.cpp | 4 + .../Selection/IsObjectInstanceSelected.cpp | 4 + .../Selection/IsObjectInstanceSelectedUID.cpp | 4 + .../Selection/MoveSelectedObjectInstances.cpp | 4 + .../Nodes/World/Selection/ResetSelection.cpp | 4 + .../RotateSelectedObjectInstances.cpp | 4 + .../ScaleSelectedObjectInstances.cpp | 6 + .../ScaleSelectedObjectInstances.hpp | 3 +- .../Nodes/World/Selection/SelectionInfo.cpp | 4 + .../World/Selection/SetCurrentSelection.cpp | 4 + .../SetSelectedObjectInstancesPosition.cpp | 4 + .../SetSelectedObjectInstancesRotation.cpp | 4 + .../SnapSelectedObjectInstancesToGround.cpp | 4 + .../World/Shading/ShadingPaintColorNode.cpp | 4 + .../World/Shading/ShadingPickColorNode.cpp | 4 + .../Nodes/World/Terrain/TerrainBlurNode.cpp | 6 + .../Nodes/World/Terrain/TerrainBlurNode.hpp | 3 +- .../World/Terrain/TerrainClearHeight.cpp | 4 + .../Terrain/TerrainClearVertexSelection.cpp | 4 + .../World/Terrain/TerrainDeselectVertices.cpp | 4 + .../World/Terrain/TerrainFlattenNode.cpp | 6 + .../World/Terrain/TerrainFlattenNode.hpp | 3 +- .../World/Terrain/TerrainFlattenVertices.cpp | 4 + .../Terrain/TerrainMoveSelectedVertices.cpp | 4 + .../Terrain/TerrainOrientVerticesNode.cpp | 4 + .../World/Terrain/TerrainRaiseLowerNode.cpp | 6 + .../World/Terrain/TerrainRaiseLowerNode.hpp | 2 +- .../World/Terrain/TerrainSelectVertices.cpp | 4 + .../TexturingClearTexturesAdtAtPosNode.cpp | 4 + .../Texturing/TexturingPaintTextureNode.cpp | 4 + .../TexturingRemoveTexDuplisAdtAtPosNode.cpp | 4 + .../TexturingSetAdtBaseTextureAtPosNode.cpp | 3 + .../Texturing/TexturingSprayTextureNode.cpp | 4 + .../TexturingSwapTextureAtPosNode.cpp | 4 + .../TexturingSwapTextureAtPosRadiusNode.cpp | 4 + .../World/Texturing/TexturingTilesetNode.cpp | 5 + .../World/Texturing/TexturingTilesetNode.hpp | 4 +- .../Nodes/World/Tile/ReloadTileNode.cpp | 4 + .../Nodes/World/Tile/TileGetAlphaLayer.cpp | 3 + .../World/Tile/TileGetAlphaLayerTexture.cpp | 3 + .../World/Tile/TileGetHeightMapImage.cpp | 3 + .../Nodes/World/Tile/TileGetMinMaxHeight.cpp | 3 + .../Nodes/World/Tile/TileGetObjectsUIDs.cpp | 3 + .../World/Tile/TileGetVertexColorsImage.cpp | 3 + .../Nodes/World/Tile/TileGetVertexNode.cpp | 3 + .../World/Tile/TileRecalculateNormals.cpp | 4 + .../Nodes/World/Tile/TileSetAlphaLayer.cpp | 3 + .../World/Tile/TileSetHeightmapImage.cpp | 5 + .../World/Tile/TileSetHeightmapImage.hpp | 3 +- .../World/Tile/TileSetVertexColorsImage.cpp | 5 + .../World/Tile/TileSetVertexColorsImage.hpp | 3 +- .../ui/tools/NodeEditor/Ui/NodeEditor.cpp | 26 +- .../ui/tools/NodeEditor/Ui/NodeEditor.hpp | 48 +-- .../ui/tools/PresetEditor/ModelView.cpp | 25 +- .../ui/tools/PresetEditor/ModelView.hpp | 14 +- .../ui/tools/PresetEditor/Ui/PresetEditor.cpp | 18 +- .../ui/tools/PresetEditor/Ui/PresetEditor.hpp | 31 +- .../tools/PreviewRenderer/PreviewRenderer.cpp | 25 +- .../tools/PreviewRenderer/PreviewRenderer.hpp | 11 +- src/noggit/ui/tools/ToolPanel/ToolPanel.cpp | 1 - .../ui/tools/UiCommon/ExtendedSlider.cpp | 8 +- .../ui/tools/UiCommon/ExtendedSlider.hpp | 59 ++-- src/noggit/ui/tools/UiCommon/ImageBrowser.cpp | 8 +- src/noggit/ui/tools/UiCommon/ImageBrowser.hpp | 3 +- .../ui/tools/UiCommon/ImageMaskSelector.cpp | 72 +++- .../ui/tools/UiCommon/ImageMaskSelector.hpp | 27 +- .../tools/UiCommon/ReorderableVerticalBox.cpp | 10 + .../tools/UiCommon/ReorderableVerticalBox.hpp | 6 +- .../ui/tools/UiCommon/StackedWidget.cpp | 11 +- src/noggit/ui/tools/UiCommon/expanderwidget.h | 86 +++-- .../ui/tools/ViewToolbar/Ui/ViewToolbar.cpp | 192 ++++++++++- .../ui/tools/ViewToolbar/Ui/ViewToolbar.hpp | 174 ++-------- .../ui/tools/ViewportGizmo/ViewportGizmo.cpp | 65 +++- .../ui/tools/ViewportGizmo/ViewportGizmo.hpp | 18 +- .../tools/ViewportManager/ViewportManager.cpp | 22 ++ .../tools/ViewportManager/ViewportManager.hpp | 20 +- src/noggit/ui/uid_fix_mode.hpp | 10 + src/noggit/ui/widgets/DynamicMouseWidget.cpp | 6 +- src/noggit/ui/widgets/DynamicMouseWidget.h | 6 +- src/noggit/ui/widgets/LightViewWidget.cpp | 25 +- src/noggit/ui/widgets/LightViewWidget.h | 26 +- .../EditorWindows/SoundEntryPickerWindow.cpp | 22 +- .../EditorWindows/SoundEntryPickerWindow.h | 21 +- .../ZoneIntroMusicPickerWindow.cpp | 18 +- .../ZoneIntroMusicPickerWindow.h | 20 +- .../EditorWindows/ZoneMusicPickerWindow.cpp | 29 +- .../EditorWindows/ZoneMusicPickerWindow.h | 19 +- .../windows/SoundPlayer/SoundEntryPlayer.cpp | 25 +- .../ui/windows/SoundPlayer/SoundEntryPlayer.h | 18 +- src/noggit/ui/windows/changelog/Changelog.cpp | 8 +- src/noggit/ui/windows/changelog/Changelog.hpp | 6 +- .../downloadFileDialog/DownloadFileDialog.cpp | 19 +- .../downloadFileDialog/DownloadFileDialog.h | 13 +- .../ui/windows/noggitWindow/NoggitWindow.cpp | 56 ++-- .../ui/windows/noggitWindow/NoggitWindow.hpp | 43 ++- .../components/BuildMapListComponent.cpp | 15 +- .../components/BuildMapListComponent.hpp | 8 +- .../widgets/MapBookmarkListItem.cpp | 10 +- .../widgets/MapBookmarkListItem.hpp | 12 +- .../noggitWindow/widgets/MapListItem.cpp | 33 +- .../noggitWindow/widgets/MapListItem.hpp | 18 +- .../NoggitProjectSelectionWindow.cpp | 24 +- .../NoggitProjectSelectionWindow.hpp | 25 +- .../components/CreateProjectComponent.cpp | 9 +- .../components/CreateProjectComponent.hpp | 10 +- .../components/LoadProjectComponent.cpp | 181 ++++++++++ .../components/LoadProjectComponent.hpp | 194 +---------- .../components/RecentProjectsComponent.cpp | 17 +- .../components/RecentProjectsComponent.hpp | 13 +- .../widgets/ProjectListItem.cpp | 9 +- .../widgets/ProjectListItem.hpp | 9 +- .../windows/settingsPanel/SettingsPanel.cpp | 24 +- .../ui/windows/settingsPanel/SettingsPanel.h | 10 +- src/noggit/ui/windows/updater/Updater.cpp | 11 +- src/noggit/ui/windows/updater/Updater.h | 33 +- src/noggit/wmo_liquid.cpp | 8 +- src/noggit/wmo_liquid.hpp | 7 +- src/noggit/world_model_instances_storage.cpp | 11 + src/noggit/world_model_instances_storage.hpp | 13 +- src/noggit/world_tile_update_queue.cpp | 2 - src/opengl/context.cpp | 1 - src/opengl/scoped.hpp | 1 - src/opengl/shader.cpp | 3 +- src/opengl/shader.hpp | 28 +- 524 files changed, 7123 insertions(+), 3865 deletions(-) create mode 100644 src/noggit/AsyncObject.cpp create mode 100644 src/noggit/TileIndex.cpp create mode 100644 src/noggit/async_priority.hpp create mode 100644 src/noggit/scoped_blp_texture_reference.cpp create mode 100644 src/noggit/scoped_blp_texture_reference.hpp create mode 100644 src/noggit/ui/tools/NodeEditor/Nodes/ContextLogicNodeBase.cpp create mode 100644 src/noggit/ui/tools/NodeEditor/Nodes/ContextNodeBase.cpp create mode 100644 src/noggit/ui/tools/NodeEditor/Nodes/LogicNodeBase.cpp create mode 100644 src/noggit/ui/tools/NodeEditor/Nodes/Widgets/QUnsignedSpinBox.cpp create mode 100644 src/noggit/ui/uid_fix_mode.hpp create mode 100644 src/noggit/ui/windows/projectSelection/components/LoadProjectComponent.cpp diff --git a/src/math/bounding_box.cpp b/src/math/bounding_box.cpp index 650b434a..986478dc 100755 --- a/src/math/bounding_box.cpp +++ b/src/math/bounding_box.cpp @@ -1,6 +1,5 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). -#include #include #include diff --git a/src/math/interpolation.hpp b/src/math/interpolation.hpp index ef073a49..d8c72be0 100755 --- a/src/math/interpolation.hpp +++ b/src/math/interpolation.hpp @@ -2,9 +2,7 @@ #pragma once -#include #include -#include namespace math { diff --git a/src/math/ray.cpp b/src/math/ray.cpp index 9f3dcc56..d5922773 100755 --- a/src/math/ray.cpp +++ b/src/math/ray.cpp @@ -4,10 +4,9 @@ #include #include -#include +#include #include -#include namespace math { @@ -26,6 +25,15 @@ namespace math } } + ray::ray(glm::vec3 origin, glm::vec3 const& direction) + : _origin(std::move(origin)), _direction(glm::normalize(direction)) + { + if (std::isnan(_direction.x) || std::isnan(_direction.y) || std::isnan(_direction.z)) + { + assert(false); + } + } + std::optional ray::intersect_bounds (glm::vec3 const& min, glm::vec3 const& max) const noexcept { diff --git a/src/math/ray.hpp b/src/math/ray.hpp index 277aa273..7ac631e5 100755 --- a/src/math/ray.hpp +++ b/src/math/ray.hpp @@ -2,8 +2,6 @@ #pragma once #include #include -#include -#include namespace math { @@ -18,14 +16,7 @@ namespace math struct ray { - ray (glm::vec3 origin, glm::vec3 const& direction) - : _origin (std::move (origin)), _direction (glm::normalize(direction)) - { - if (std::isnan(_direction.x) || std::isnan(_direction.y) || std::isnan(_direction.z)) - { - assert(false); - } - } + ray (glm::vec3 origin, glm::vec3 const& direction); ray (glm::mat4x4 const& transform, ray const& other) : ray ( diff --git a/src/math/trig.cpp b/src/math/trig.cpp index 880d7569..05073fa5 100644 --- a/src/math/trig.cpp +++ b/src/math/trig.cpp @@ -1,7 +1,7 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). #include -#include + #include bool math::is_inside_of_polygon(const glm::vec2& pos, const std::vector& polygon) { @@ -24,4 +24,4 @@ bool math::is_inside_of_polygon(const glm::vec2& pos, const std::vector -#include -#include -#include #include #include #include #include #include +#include +#include +#include +#include + namespace math { namespace diff --git a/src/noggit/Action.cpp b/src/noggit/Action.cpp index e20f2345..05211173 100644 --- a/src/noggit/Action.cpp +++ b/src/noggit/Action.cpp @@ -1,13 +1,15 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). #include "Action.hpp" +#include +#include #include #include +#include #include -#include -#include +#include + #include -#include Noggit::Action::Action(MapView* map_view) @@ -621,6 +623,26 @@ void Noggit::Action::setPostCallback(std::function function) _post = function; } + bool Noggit::Action::getTag() +{ + return _tag; +} + + void Noggit::Action::setTag(bool tag) +{ + _tag = tag; +} + + bool Noggit::Action::checkAdressTag(std::uintptr_t address) +{ + return std::find(_address_tag.begin(), _address_tag.end(), address) != _address_tag.end(); +} + + void Noggit::Action::tagAdress(std::uintptr_t address) +{ + _address_tag.push_back(address); +} + /* ============ */ /* Registrators */ diff --git a/src/noggit/Action.hpp b/src/noggit/Action.hpp index 98ad2517..95b565fe 100755 --- a/src/noggit/Action.hpp +++ b/src/noggit/Action.hpp @@ -6,23 +6,18 @@ #include #include #include -#include -#include #include -#include #include -#include #include -#include #include -#include #include -#include #include +#include class MapView; class MapChunk; +class SceneObject; namespace Noggit { @@ -119,11 +114,11 @@ namespace Noggit void setBlockCursor(bool state); bool getBlockCursor() const; void setPostCallback(std::function function); - bool getTag() { return _tag; }; - void setTag(bool tag) { _tag = tag; }; + bool getTag(); + void setTag(bool tag); - bool checkAdressTag(std::uintptr_t address) { return std::find(_address_tag.begin(), _address_tag.end(), address) != _address_tag.end(); }; - void tagAdress(std::uintptr_t address) { _address_tag.push_back(address); } + bool checkAdressTag(std::uintptr_t address); + void tagAdress(std::uintptr_t address); float* getChunkTerrainOriginalData(MapChunk* chunk); diff --git a/src/noggit/ActionManager.cpp b/src/noggit/ActionManager.cpp index cbaa6047..d36ca44b 100755 --- a/src/noggit/ActionManager.cpp +++ b/src/noggit/ActionManager.cpp @@ -190,4 +190,4 @@ ActionManager::~ActionManager() } _action_stack.clear(); -} \ No newline at end of file +} diff --git a/src/noggit/Alphamap.cpp b/src/noggit/Alphamap.cpp index abf07773..e99bd5c8 100755 --- a/src/noggit/Alphamap.cpp +++ b/src/noggit/Alphamap.cpp @@ -1,6 +1,7 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). #include +#include #include #include #include diff --git a/src/noggit/Alphamap.hpp b/src/noggit/Alphamap.hpp index 24d3dcbb..3e24c493 100755 --- a/src/noggit/Alphamap.hpp +++ b/src/noggit/Alphamap.hpp @@ -2,8 +2,8 @@ #pragma once -#include -#include +#include +#include namespace BlizzardArchive { diff --git a/src/noggit/AsyncLoader.cpp b/src/noggit/AsyncLoader.cpp index 80a5691e..46b08dc4 100755 --- a/src/noggit/AsyncLoader.cpp +++ b/src/noggit/AsyncLoader.cpp @@ -1,8 +1,11 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). -#include -#include +#include #include +#include +#include +#include +#include #include @@ -176,3 +179,13 @@ AsyncLoader::~AsyncLoader() thread.join(); } } + +bool AsyncLoader::important_object_failed_loading() const +{ + return _important_object_failed_loading; +} + +void AsyncLoader::reset_object_fail() +{ + _important_object_failed_loading = false; +} diff --git a/src/noggit/AsyncLoader.h b/src/noggit/AsyncLoader.h index 1e2d8120..28da6149 100755 --- a/src/noggit/AsyncLoader.h +++ b/src/noggit/AsyncLoader.h @@ -2,7 +2,7 @@ #pragma once -#include +#include #include #include @@ -11,6 +11,8 @@ #include #include +class AsyncObject; + class AsyncLoader { public: @@ -37,8 +39,8 @@ public: AsyncLoader(int numThreads); ~AsyncLoader(); - bool important_object_failed_loading() const { return _important_object_failed_loading; } - void reset_object_fail() { _important_object_failed_loading = false; } + bool important_object_failed_loading() const; + void reset_object_fail(); private: void process(); diff --git a/src/noggit/AsyncObject.cpp b/src/noggit/AsyncObject.cpp new file mode 100644 index 00000000..f64dc181 --- /dev/null +++ b/src/noggit/AsyncObject.cpp @@ -0,0 +1,64 @@ +// This file is part of Noggit3, licensed under GNU General Public License (version 3). + +#include +#include + + AsyncObject::AsyncObject(BlizzardArchive::Listfile::FileKey file_key) : _file_key(std::move(file_key)) {} + +[[nodiscard]] + BlizzardArchive::Listfile::FileKey const& AsyncObject::file_key() const +{ + return _file_key; +} + +[[nodiscard]] + bool AsyncObject::finishedLoading() const +{ + return finished.load(); +} + +[[nodiscard]] + bool AsyncObject::loading_failed() const +{ + return _loading_failed; +} + + void AsyncObject::wait_until_loaded() +{ + if (finished.load()) + { + return; + } + + std::unique_lock lock(_mutex); + + _state_changed.wait + (lock + , [&] + { + return finished.load(); + } + ); +} + + void AsyncObject::error_on_loading() +{ + LogError << "File " << (_file_key.hasFilepath() ? _file_key.filepath() : std::to_string(_file_key.fileDataID())) + << " could not be loaded" << std::endl; + + _loading_failed = true; + finished = true; + _state_changed.notify_all(); +} + +[[nodiscard]] + bool AsyncObject::is_required_when_saving() const +{ + return false; +} + +[[nodiscard]] + async_priority AsyncObject::loading_priority() const +{ + return async_priority::medium; +} diff --git a/src/noggit/AsyncObject.h b/src/noggit/AsyncObject.h index 82fba2b0..abaa41b1 100755 --- a/src/noggit/AsyncObject.h +++ b/src/noggit/AsyncObject.h @@ -2,21 +2,12 @@ #pragma once +#include #include -#include #include #include #include -#include - -enum class async_priority : int -{ - high, - medium, - low, - count -}; class AsyncObject { @@ -30,67 +21,31 @@ protected: BlizzardArchive::Listfile::FileKey _file_key; - AsyncObject(BlizzardArchive::Listfile::FileKey file_key) : _file_key(std::move(file_key)) {} + AsyncObject(BlizzardArchive::Listfile::FileKey file_key); public: [[nodiscard]] - BlizzardArchive::Listfile::FileKey const& file_key() const { return _file_key; }; + BlizzardArchive::Listfile::FileKey const& file_key() const; AsyncObject() = delete; virtual ~AsyncObject() = default; [[nodiscard]] - virtual bool finishedLoading() const - { - return finished.load(); - } + virtual bool finishedLoading() const; [[nodiscard]] - bool loading_failed() const - { - return _loading_failed; - } + bool loading_failed() const; - void wait_until_loaded() - { - if (finished.load()) - { - return; - } + void wait_until_loaded(); - std::unique_lock lock (_mutex); - - _state_changed.wait - ( lock - , [&] - { - return finished.load(); - } - ); - } - - void error_on_loading() - { - LogError << "File " << (_file_key.hasFilepath() ? _file_key.filepath() : std::to_string(_file_key.fileDataID())) - << " could not be loaded" << std::endl; - - _loading_failed = true; - finished = true; - _state_changed.notify_all(); - } + void error_on_loading(); [[nodiscard]] - virtual bool is_required_when_saving() const - { - return false; - } + virtual bool is_required_when_saving() const; [[nodiscard]] - virtual async_priority loading_priority() const - { - return async_priority::medium; - } + virtual async_priority loading_priority() const; virtual void finishLoading() = 0; virtual void waitForChildrenLoaded() = 0; diff --git a/src/noggit/AsyncObjectMultimap.hpp b/src/noggit/AsyncObjectMultimap.hpp index 78f2a4e6..7cc11fc3 100755 --- a/src/noggit/AsyncObjectMultimap.hpp +++ b/src/noggit/AsyncObjectMultimap.hpp @@ -4,17 +4,13 @@ #include #include #include -#include +#include -#include #include -#include #include -#include #include #include -#include struct pair_hash { diff --git a/src/noggit/Brush.cpp b/src/noggit/Brush.cpp index a83d7c01..1d38d623 100755 --- a/src/noggit/Brush.cpp +++ b/src/noggit/Brush.cpp @@ -16,20 +16,24 @@ void Brush::setHardness(float H) iradius = hardness * radius; oradius = radius - iradius; } + void Brush::setRadius(float R) { radius = R; iradius = hardness * radius; oradius = radius - iradius; } + float Brush::getHardness() const { return hardness; } + float Brush::getRadius() const { return radius; } + float Brush::getValue(float dist) const { if (dist > radius) diff --git a/src/noggit/Camera.hpp b/src/noggit/Camera.hpp index ee1ed7c9..863db8c0 100755 --- a/src/noggit/Camera.hpp +++ b/src/noggit/Camera.hpp @@ -1,9 +1,11 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). #pragma once -#include #include "math/trig.hpp" +#include +#include + namespace Noggit { //! \todo there should be a seperate class for tile mode diff --git a/src/noggit/ChunkWater.cpp b/src/noggit/ChunkWater.cpp index 032dc346..85711fd8 100755 --- a/src/noggit/ChunkWater.cpp +++ b/src/noggit/ChunkWater.cpp @@ -353,6 +353,16 @@ void ChunkWater::update_layers() _layer_count = _layers.size(); } +float ChunkWater::getMinHeight() const +{ + return vmin.y; +} + +float ChunkWater::getMaxHeight() const +{ + return vmax.y; +} + bool ChunkWater::hasData(size_t layer) const { return _layer_count > layer; @@ -438,6 +448,31 @@ void ChunkWater::paintLiquid( glm::vec3 const& pos update_layers(); } +MapChunk* ChunkWater::getChunk() +{ + return _chunk; +} + +TileWater* ChunkWater::getWaterTile() +{ + return _water_tile; +} + +MH2O_Attributes const& ChunkWater::getAttributes() const +{ + return attributes; +} + +MH2O_Attributes& ChunkWater::getAttributes() +{ + return attributes; +} + +int ChunkWater::layer_count() const +{ + return _layers.size(); +} + void ChunkWater::cleanup() { for (int i = static_cast(_layer_count - 1); i >= 0; --i) @@ -492,3 +527,8 @@ void ChunkWater::tagUpdate() _water_tile->tagUpdate(); } +std::vector* ChunkWater::getLayers() +{ + return &_layers; +} + diff --git a/src/noggit/ChunkWater.hpp b/src/noggit/ChunkWater.hpp index e6696e1e..f52da64d 100755 --- a/src/noggit/ChunkWater.hpp +++ b/src/noggit/ChunkWater.hpp @@ -4,11 +4,8 @@ #include #include #include -#include #include -#include -#include class MapChunk; class TileWater; @@ -18,6 +15,11 @@ namespace BlizzardArchive class ClientFile; } +namespace util +{ + class sExtendableArray; +} + class ChunkWater { public: @@ -49,12 +51,12 @@ public: bool hasData(size_t layer) const; void tagUpdate(); - std::vector* getLayers() { return &_layers; }; + std::vector* getLayers(); // update every layer's render void update_layers(); - float getMinHeight() { return vmin.y; }; - float getMaxHeight() { return vmax.y; } + float getMinHeight() const; + float getMaxHeight() const; void paintLiquid( glm::vec3 const& pos , float radius @@ -70,14 +72,15 @@ public: , float opacity_factor ); - MapChunk* getChunk() { return _chunk; }; - TileWater* getWaterTile() { return _water_tile; }; + MapChunk* getChunk(); + TileWater* getWaterTile(); - MH2O_Attributes& const getAttributes() { return attributes; }; + MH2O_Attributes const& getAttributes() const; + MH2O_Attributes& getAttributes(); float xbase, zbase; - int layer_count() const { return _layers.size(); } + int layer_count() const; private: MH2O_Attributes attributes; diff --git a/src/noggit/DBCFile.cpp b/src/noggit/DBCFile.cpp index 2f3d4ea4..1d2f5f53 100755 --- a/src/noggit/DBCFile.cpp +++ b/src/noggit/DBCFile.cpp @@ -2,17 +2,15 @@ #include #include -#include #include + #include -#include -#include #include -#include -#include -#include + #include +#include +#include template inline @@ -113,6 +111,71 @@ DBCFile DBCFile::createNew(std::string filename, std::uint32_t fieldCount, std:: return file; } +DBCFile::Record DBCFile::getRecord(size_t id) +{ + return Record(*this, data.data() + id * recordSize); +} + +DBCFile::Iterator DBCFile::begin() +{ + return Iterator(*this, data.data()); +} + +DBCFile::Iterator DBCFile::end() +{ + return Iterator(*this, data.data() + data.size()); +} + +size_t DBCFile::getRecordCount() const +{ + return recordCount; +} + +size_t DBCFile::getFieldCount() const +{ + return fieldCount; +} + +size_t DBCFile::getRecordSize() const +{ + return recordSize; +} + +DBCFile::Record DBCFile::getByID(unsigned int id, size_t field) +{ + for (Iterator i = begin(); i != end(); ++i) + { + if (i->getUInt(field) == id) + return (*i); + } + LogDebug << "Tried to get a not existing row in " << filename << " (ID = " << id << ")!" << std::endl; + throw NotFound(); +} + +bool DBCFile::CheckIfIdExists(unsigned int id, size_t field) +{ + for (Iterator i = begin(); i != end(); ++i) + { + if (i->getUInt(field) == id) + return (true); + } + return (false); +} + +int DBCFile::getRecordRowId(unsigned int id, size_t field) +{ + int row_id = 0; + for (Iterator i = begin(); i != end(); ++i) + { + if (i->getUInt(field) == id) + return row_id; + + row_id++; + } + LogError << "Tried to get a not existing row in " << filename << " (ID = " << id << ")!" << std::endl; + throw NotFound(); +} + DBCFile::Record DBCFile::addRecord(size_t id, size_t id_field) { assert(recordSize > 0); @@ -234,5 +297,83 @@ int DBCFile::getEmptyRecordID(size_t id_field) return static_cast(++id); } +const float& DBCFile::Record::getFloat(size_t field) const +{ + assert(field < file.fieldCount); + return *reinterpret_cast(offset + field * 4); +} +const unsigned int& DBCFile::Record::getUInt(size_t field) const +{ + assert(field < file.fieldCount); + return *reinterpret_cast(offset + field * 4); +} +const int& DBCFile::Record::getInt(size_t field) const +{ + assert(field < file.fieldCount); + return *reinterpret_cast(offset + field * 4); +} + +const char* DBCFile::Record::getString(size_t field) const +{ + assert(field < file.fieldCount); + size_t stringOffset = getUInt(field); + assert(stringOffset < file.stringSize); + return file.stringTable.data() + stringOffset; +} + +const char* DBCFile::Record::getLocalizedString(size_t field, int locale) const +{ + int loc = locale; + if (locale == -1) + { + assert(field < file.fieldCount - 8); + for (loc = 0; loc < 15; loc++) + { + size_t stringOffset = getUInt(field + loc); + if (stringOffset != 0) + break; + } + } + + assert(field + loc < file.fieldCount); + size_t stringOffset = getUInt(field + loc); + assert(stringOffset < file.stringSize); + return file.stringTable.data() + stringOffset; +} + +void DBCFile::Record::writeString(size_t field, const std::string& val) +{ + assert(field < file.fieldCount); + + if (!val.size()) + { + *reinterpret_cast(offset + field * 4) = 0; + return; + } + + size_t old_size = file.stringTable.size(); + *reinterpret_cast(offset + field * 4) = static_cast(file.stringTable.size()); + file.stringTable.resize(old_size + val.size() + 1); + std::copy(val.c_str(), val.c_str() + val.size() + 1, file.stringTable.data() + old_size); + file.stringSize += static_cast(val.size() + 1); +} + +void DBCFile::Record::writeLocalizedString(size_t field, const std::string& val, unsigned int locale) +{ + assert(field < file.fieldCount); + assert(locale < 16); + + if (!val.size()) + { + *reinterpret_cast(offset + ((field + locale) * 4)) = 0; + return; + } + + size_t old_size = file.stringTable.size(); + *reinterpret_cast(offset + ((field + locale) * 4)) = static_cast(file.stringTable.size()); + file.stringTable.resize(old_size + val.size() + 1); + std::copy(val.c_str(), val.c_str() + val.size() + 1, file.stringTable.data() + old_size); + file.stringSize += static_cast(val.size() + 1); +} diff --git a/src/noggit/DBCFile.h b/src/noggit/DBCFile.h index 31392164..7bd02d4c 100755 --- a/src/noggit/DBCFile.h +++ b/src/noggit/DBCFile.h @@ -6,12 +6,8 @@ #include #include #include -#include -#include #include -#include #include -#include class DBCFile { @@ -44,47 +40,15 @@ public: class Record { public: - const float& getFloat(size_t field) const - { - assert(field < file.fieldCount); - return *reinterpret_cast(offset + field * 4); - } - const unsigned int& getUInt(size_t field) const - { - assert(field < file.fieldCount); - return *reinterpret_cast(offset + field * 4); - } - const int& getInt(size_t field) const - { - assert(field < file.fieldCount); - return *reinterpret_cast(offset + field * 4); - } - const char *getString(size_t field) const - { - assert(field < file.fieldCount); - size_t stringOffset = getUInt(field); - assert(stringOffset < file.stringSize); - return file.stringTable.data() + stringOffset; - } - const char *getLocalizedString(size_t field, int locale = -1) const - { - int loc = locale; - if (locale == -1) - { - assert(field < file.fieldCount - 8); - for (loc = 0; loc < 15; loc++) - { - size_t stringOffset = getUInt(field + loc); - if (stringOffset != 0) - break; - } - } + const float& getFloat(size_t field) const; - assert(field + loc < file.fieldCount); - size_t stringOffset = getUInt(field + loc); - assert(stringOffset < file.stringSize); - return file.stringTable.data() + stringOffset; - } + const unsigned int& getUInt(size_t field) const; + + const int& getInt(size_t field) const; + + const char *getString(size_t field) const; + + const char *getLocalizedString(size_t field, int locale = -1) const; template inline void write(size_t field, T val) @@ -94,40 +58,9 @@ public: *reinterpret_cast(offset + field * 4) = val; } - void writeString(size_t field, const std::string& val) - { - assert(field < file.fieldCount); + void writeString(size_t field, const std::string& val); - if (!val.size()) - { - *reinterpret_cast(offset + field * 4) = 0; - return; - } - - size_t old_size = file.stringTable.size(); - *reinterpret_cast(offset + field * 4) = static_cast(file.stringTable.size()); - file.stringTable.resize(old_size + val.size() + 1); - std::copy(val.c_str(), val.c_str() + val.size() + 1, file.stringTable.data() + old_size); - file.stringSize += static_cast(val.size() + 1); - } - - void writeLocalizedString(size_t field, const std::string& val, unsigned int locale) - { - assert(field < file.fieldCount); - assert(locale < 16); - - if (!val.size()) - { - *reinterpret_cast(offset + ((field + locale) * 4)) = 0; - return; - } - - size_t old_size = file.stringTable.size(); - *reinterpret_cast(offset + ((field + locale) * 4)) = static_cast(file.stringTable.size()); - file.stringTable.resize(old_size + val.size() + 1); - std::copy(val.c_str(), val.c_str() + val.size() + 1, file.stringTable.data() + old_size); - file.stringSize += static_cast(val.size() + 1); - } + void writeLocalizedString(size_t field, const std::string& val, unsigned int locale); private: Record(DBCFile &pfile, unsigned char *poffset) : file(pfile), offset(poffset) {} @@ -163,56 +96,18 @@ public: Record record; }; - inline Record getRecord(size_t id) - { - return Record(*this, data.data() + id*recordSize); - } + Record getRecord(size_t id); - inline Iterator begin() - { - return Iterator(*this, data.data()); - } - inline Iterator end() - { - return Iterator(*this, data.data() + data.size()); - } + Iterator begin(); + Iterator end(); - inline size_t getRecordCount() const { return recordCount; } - inline size_t getFieldCount() const { return fieldCount; } - inline size_t getRecordSize() const { return recordSize; } + size_t getRecordCount() const; + size_t getFieldCount() const; + size_t getRecordSize() const; - inline Record getByID(unsigned int id, size_t field = 0) - { - for (Iterator i = begin(); i != end(); ++i) - { - if (i->getUInt(field) == id) - return (*i); - } - LogDebug << "Tried to get a not existing row in " << filename << " (ID = " << id << ")!" << std::endl; - throw NotFound(); - } - inline bool CheckIfIdExists(unsigned int id, size_t field = 0) - { - for (Iterator i = begin(); i != end(); ++i) - { - if (i->getUInt(field) == id) - return (true); - } - return (false); - } - inline int getRecordRowId(unsigned int id, size_t field = 0) - { - int row_id = 0; - for (Iterator i = begin(); i != end(); ++i) - { - if (i->getUInt(field) == id) - return row_id; - - row_id++; - } - LogError << "Tried to get a not existing row in " << filename << " (ID = " << id << ")!" << std::endl; - throw NotFound(); - } + Record getByID(unsigned int id, size_t field = 0); + bool CheckIfIdExists(unsigned int id, size_t field = 0); + int getRecordRowId(unsigned int id, size_t field = 0); Record addRecord(size_t id, size_t id_field = 0); Record addRecordCopy(size_t id, size_t id_from, size_t id_field = 0); diff --git a/src/noggit/MapChunk.cpp b/src/noggit/MapChunk.cpp index 71e2a489..7894e2d0 100644 --- a/src/noggit/MapChunk.cpp +++ b/src/noggit/MapChunk.cpp @@ -1,28 +1,28 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). +#include +#include #include +#include +#include +#include #include -#include +#include #include #include #include +#include // MapTile #include -#include -#include +#include #include +#include #include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include -#include -#include +#include #include #include -#include MapChunk::MapChunk(MapTile* maintile, BlizzardArchive::ClientFile* f, bool bigAlpha,tile_mode mode , Noggit::NoggitRenderContext context, bool init_empty, int chunk_idx, bool load_textures) @@ -343,6 +343,11 @@ MapChunk::MapChunk(MapTile* maintile, BlizzardArchive::ClientFile* f, bool bigAl } } +auto MapChunk::getHoleMask(void) const -> unsigned +{ + return static_cast(holes); +} + int MapChunk::indexLoD(int x, int y) { return (x + 1) * 9 + x * 8 + y; @@ -423,6 +428,21 @@ float MapChunk::getHeight(int x, int z) return mVertices[indexNoLoD(x, z)].y; } +float MapChunk::getMinHeight() const +{ + return vmin.y; +} + +float MapChunk::getMaxHeight() const +{ + return vmax.y; +} + +glm::vec3 MapChunk::getCenter() const +{ + return vcenter; +} + void MapChunk::clearHeight() { for (int i = 0; i < mapbufsize; ++i) @@ -439,6 +459,11 @@ void MapChunk::clearHeight() } +TextureSet* MapChunk::getTextureSet() const +{ + return texture_set.get(); +} + void MapChunk::draw ( math::frustum const& frustum , OpenGL::Scoped::use_program& mcnk_shader , const float& cull_distance @@ -1859,6 +1884,21 @@ bool MapChunk::fixGapAbove(const MapChunk* chunk) return changed; } +glm::vec3* MapChunk::getHeightmap() +{ + return &mVertices[0]; +} + +glm::vec3 const* MapChunk::getNormals() const +{ + return &mNormals[0]; +} + +glm::vec3* MapChunk::getVertexColors() +{ + return &mccv[0]; +} + void MapChunk::selectVertex(glm::vec3 const& pos, float radius, std::unordered_set& vertices) { @@ -2008,11 +2048,21 @@ void MapChunk::setHeightmapImage(QImage const& image, float multiplier, int mode registerChunkUpdate(ChunkUpdateFlags::VERTEX); } -ChunkWater* MapChunk::liquid_chunk() const +ChunkWater const* MapChunk::liquid_chunk() const { return mt->Water.getChunk(px, py); } +ChunkWater* MapChunk::liquid_chunk() +{ + return mt->Water.getChunk(px, py); +} + +bool MapChunk::hasColors() const +{ + return hasMCCV; +} + void MapChunk::unload() { _chunk_update_flags = ChunkUpdateFlags::VERTEX | ChunkUpdateFlags::ALPHAMAP @@ -2115,4 +2165,12 @@ void MapChunk::registerChunkUpdate(unsigned flags) mt->registerChunkUpdate(flags); } +void MapChunk::endChunkUpdates() +{ + _chunk_update_flags = 0; +} +unsigned MapChunk::getUpdateFlags() const +{ + return _chunk_update_flags; +} diff --git a/src/noggit/MapChunk.h b/src/noggit/MapChunk.h index 49e156b7..e9cdb03b 100755 --- a/src/noggit/MapChunk.h +++ b/src/noggit/MapChunk.h @@ -1,26 +1,20 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). #pragma once +#include #include -#include // MapTile +#include #include -#include #include #include -#include -#include -#include #include -#include -#include -#include -#include -#include +#include + +#include #include #include -#include -#include +#include namespace BlizzardArchive { @@ -30,10 +24,22 @@ namespace BlizzardArchive namespace math { class frustum; + struct ray; struct vector_4d; } + +namespace util +{ + class sExtendableArray; +} + class Brush; class ChunkWater; +class MapTile; +class ModelInstance; +class TextureSet; +class WMOInstance; + class QPixmap; using StripType = uint16_t; @@ -70,7 +76,7 @@ public: MapChunk(MapTile* mt, BlizzardArchive::ClientFile* f, bool bigAlpha, tile_mode mode, Noggit::NoggitRenderContext context , bool init_empty = false, int chunk_idx = 0, bool load_textures = true); - auto getHoleMask(void) const -> unsigned { return static_cast(holes); } + auto getHoleMask(void) const -> unsigned; MapTile *mt; glm::vec3 vmin, vmax, vcenter; int px, py; @@ -113,7 +119,7 @@ private: public: - TextureSet* getTextureSet() const { return texture_set.get(); }; + TextureSet* getTextureSet() const; void draw ( math::frustum const& frustum , OpenGL::Scoped::use_program& mcnk_shader @@ -136,9 +142,10 @@ public: bool stampMCCV(glm::vec3 const& pos, glm::vec4 const& color, float change, float radius, bool editMode, QImage* img, bool paint, bool use_image_colors); glm::vec3 pickMCCV(glm::vec3 const& pos); - ChunkWater* liquid_chunk() const; + ChunkWater const* liquid_chunk() const; + ChunkWater* liquid_chunk(); - bool hasColors() const { return hasMCCV; }; + bool hasColors() const;; void updateVerticesData(); void recalcExtents(); @@ -189,9 +196,9 @@ public: bool GetVertex(float x, float z, glm::vec3 *V); void getVertexInternal(float x, float z, glm::vec3 * v); float getHeight(int x, int z); - float getMinHeight() { return vmin.y; }; - float getMaxHeight() { return vmax.y; }; - glm::vec3 getCenter() { return vcenter; }; + float getMinHeight() const;; + float getMaxHeight() const;; + glm::vec3 getCenter() const;; void clearHeight(); @@ -210,9 +217,9 @@ public: // fix the gaps with the chunk above bool fixGapAbove(const MapChunk* chunk); - glm::vec3* getHeightmap() { return &mVertices[0]; }; - glm::vec3 const* getNormals() const { return &mNormals[0]; } - glm::vec3* getVertexColors() { return &mccv[0]; }; + glm::vec3* getHeightmap();; + glm::vec3 const* getNormals() const; + glm::vec3* getVertexColors();; void update_vertex_colors(); @@ -220,11 +227,11 @@ public: QImage getAlphamapImage(unsigned layer); QImage getVertexColorImage(); void setHeightmapImage(QImage const& image, float multiplier, int mode); - void setAlphamapImage(QImage const& image, unsigned layer); + void setAlphamapImage(QImage const& image, unsigned int layer); void setVertexColorImage(QImage const& image); void initMCCV(); void registerChunkUpdate(unsigned flags); - void endChunkUpdates() { _chunk_update_flags = 0; } - unsigned getUpdateFlags() { return _chunk_update_flags; } + void endChunkUpdates(); + unsigned getUpdateFlags() const; }; diff --git a/src/noggit/MapTile.cpp b/src/noggit/MapTile.cpp index 031c0e5e..0680e2f3 100644 --- a/src/noggit/MapTile.cpp +++ b/src/noggit/MapTile.cpp @@ -1,36 +1,35 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). +#include +#include #include #include #include #include +#include #include // ModelInstance #include // ModelManager +#include +#include #include #include // WMOInstance #include -#include -#include -#include -#include -#include +#include + +#include + #include -#include -#include -#include -#include + #include -#include #include #include -#include +#include #include #include #include #include -#include MapTile::MapTile( int pX @@ -399,6 +398,21 @@ bool MapTile::isTile(int pX, int pZ) return pX == index.x && pZ == index.z; } +bool MapTile::hasFlightBounds() const +{ + return mFlags & 1; +} + +async_priority MapTile::loading_priority() const +{ + return async_priority::high; +} + +bool MapTile::has_model(uint32_t uid) const +{ + return std::find(uids.begin(), uids.end(), uid) != uids.end(); +} + float MapTile::getMaxHeight() { return getExtents()[1].y; @@ -1092,6 +1106,16 @@ void MapTile::add_model(SceneObject* instance) } } +bool MapTile::tile_is_being_reloaded() const +{ + return _tile_is_being_reloaded; +} + +std::vector* MapTile::get_uids() +{ + return &uids; +} + void MapTile::initEmptyChunks() { for (int nextChunk = 0; nextChunk < 256; ++nextChunk) @@ -1694,6 +1718,26 @@ void MapTile::setVertexColorImage(QImage const& baseimage, int mode, bool tiledE } } +void MapTile::registerChunkUpdate(unsigned flags) +{ + _chunk_update_flags |= flags; +} + +void MapTile::endChunkUpdates() +{ + _chunk_update_flags = 0; +} + +std::array& MapTile::getChunkHeightmapBuffer() +{ + return _chunk_heightmap_buffer; +} + +unsigned MapTile::getChunkUpdateFlags() const +{ + return _chunk_update_flags; +} + void MapTile::recalcExtents() { if (!_extents_dirty) @@ -1775,11 +1819,36 @@ void MapTile::recalcObjectInstanceExtents() } +float MapTile::camDist() const +{ + return _cam_dist; +} + void MapTile::calcCamDist(glm::vec3 const& camera) { _cam_dist = glm::distance(camera, _center); } +void MapTile::markExtentsDirty() +{ + _extents_dirty = true; +} + +void MapTile::tagCombinedExtents(bool state) +{ + _combined_extents_dirty = state; +} + +Noggit::Rendering::TileRender* MapTile::renderer() +{ + return &_renderer; +} + +Noggit::Rendering::FlightBoundsRender* MapTile::flightBoundsRenderer() +{ + return &_fl_bounds_render; +} + const texture_heightmapping_data& MapTile::GetTextureHeightMappingData(const std::string& name) const { return Noggit::Project::CurrentProject::get()->ExtraMapData.GetTextureHeightDataForADT(_world->mapIndex._map_id, index,name); @@ -1885,4 +1954,25 @@ void MapTile::recalcCombinedExtents() } _combined_extents_dirty = false; -} \ No newline at end of file +} + +std::array& MapTile::getExtents() +{ + recalcExtents(); return _extents; +} + +std::array& MapTile::getCombinedExtents() +{ + recalcCombinedExtents(); return _combined_extents; +} + +World* MapTile::getWorld() +{ + return _world; +} + +[[nodiscard]] +tsl::robin_map> const& MapTile::getObjectInstances() const +{ + return object_instances; +} diff --git a/src/noggit/MapTile.h b/src/noggit/MapTile.h index c577a45e..4b5a0b8b 100644 --- a/src/noggit/MapTile.h +++ b/src/noggit/MapTile.h @@ -2,38 +2,32 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include #include -#include -#include -#include +#include +#include #include +#include +#include +#include +#include +#include + +#include #include #include #include -#include namespace math { class frustum; struct vector_3d; + struct ray; } -namespace Noggit::Rendering -{ - class TileRender; - class FlightBoundsRender; -} - +class MapChunk; +struct texture_heightmapping_data; class World; @@ -118,17 +112,11 @@ public: bool isTile(int pX, int pZ); - bool hasFlightBounds() const { return mFlags & 1; }; + bool hasFlightBounds() const;; - async_priority loading_priority() const override - { - return async_priority::high; - } + async_priority loading_priority() const override; - bool has_model(uint32_t uid) const - { - return std::find(uids.begin(), uids.end(), uid) != uids.end(); - } + bool has_model(uint32_t uid) const; void remove_model(uint32_t uid); void remove_model(SceneObject* instance); @@ -137,9 +125,9 @@ public: TileWater Water; - bool tile_is_being_reloaded() const { return _tile_is_being_reloaded; } + bool tile_is_being_reloaded() const; - std::vector* get_uids() { return &uids; } + std::vector* get_uids(); void initEmptyChunks(); @@ -151,31 +139,31 @@ public: QImage getVertexColorsImage(); QImage getNormalmapImage(); void setHeightmapImage(QImage const& baseimage, float min_height, float max_height, int mode, bool tiledEdges); - void setWatermapImage(QImage const& baseimage, float multiplier, int mode, bool tiledEdges); + // void setWatermapImage(QImage const& baseimage, float multiplier, int mode, bool tiledEdges); void setAlphaImage(QImage const& image, unsigned layer, bool cleanup); void setVertexColorImage(QImage const& image, int mode, bool tiledEdges); - void registerChunkUpdate(unsigned flags) { _chunk_update_flags |= flags; }; - void endChunkUpdates() { _chunk_update_flags = 0; }; - std::array& getChunkHeightmapBuffer() { return _chunk_heightmap_buffer; }; - unsigned getChunkUpdateFlags() { return _chunk_update_flags; } + void registerChunkUpdate(unsigned flags);; + void endChunkUpdates();; + std::array& getChunkHeightmapBuffer();; + unsigned getChunkUpdateFlags() const; void recalcExtents(); void recalcObjectInstanceExtents(); void recalcCombinedExtents(); - std::array& getExtents() { recalcExtents(); return _extents; }; - std::array& getCombinedExtents() { recalcCombinedExtents(); return _combined_extents; }; + std::array& getExtents();; + std::array& getCombinedExtents();; - World* getWorld() { return _world; }; + World* getWorld();; [[nodiscard]] - tsl::robin_map> const& getObjectInstances() const { return object_instances; }; + tsl::robin_map> const& getObjectInstances() const;; - float camDist() { return _cam_dist; } + float camDist() const; void calcCamDist(glm::vec3 const& camera); - void markExtentsDirty() { _extents_dirty = true; } - void tagCombinedExtents(bool state) { _combined_extents_dirty = state; }; + void markExtentsDirty(); + void tagCombinedExtents(bool state);; - Noggit::Rendering::TileRender* renderer() { return &_renderer; }; - Noggit::Rendering::FlightBoundsRender* flightBoundsRenderer() { return &_fl_bounds_render; }; + Noggit::Rendering::TileRender* renderer();; + Noggit::Rendering::FlightBoundsRender* flightBoundsRenderer();; const texture_heightmapping_data& GetTextureHeightMappingData(const std::string& name) const; diff --git a/src/noggit/MapView.cpp b/src/noggit/MapView.cpp index c50423fa..1f379df9 100644 --- a/src/noggit/MapView.cpp +++ b/src/noggit/MapView.cpp @@ -7,7 +7,11 @@ #include // TextureManager, Texture #include // WMOInstance #include +#include #include +#include +#include +#include #include #include #include // detailInfos @@ -46,6 +50,7 @@ #include #include #include +#include #include #include #include @@ -75,8 +80,8 @@ #ifdef USE_MYSQL_UID_STORAGE #include -#include #endif +#include #include #include @@ -86,12 +91,17 @@ #include +#include + #include "revision.h" #include #include #include +#include +#include #include +#include #include #include #include @@ -104,14 +114,16 @@ #include #include #include +#include #include +#include #include #include #include #include #include - +#include #include #include #include @@ -298,6 +310,11 @@ void MapView::set_editing_mode(editing_mode mode) _world->renderer()->markTerrainParamsUniformBlockDirty(); } +editing_mode MapView::get_editing_mode() const +{ + return terrainMode; +} + void MapView::setToolPropertyWidgetVisibility(editing_mode mode) { _tool_panel_dock->setCurrentTool(mode); @@ -2651,6 +2668,11 @@ auto MapView::setBrushTexture(QImage const* img) -> void gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); } +Noggit::Camera* MapView::getCamera() +{ + return &_camera; +} + void MapView::move_camera_with_auto_height (glm::vec3 const& pos) { makeCurrent(); @@ -4265,6 +4287,30 @@ QWidget* MapView::getLeftSecondaryToolbar() return _viewport_overlay_ui->leftSecondaryToolbarHolder; } +[[nodiscard]] +Noggit::NoggitRenderContext MapView::getRenderContext() +{ + return _context; +} + +[[nodiscard]] +World* MapView::getWorld() const +{ + return _world.get(); +} + +[[nodiscard]] +QDockWidget* MapView::getAssetBrowser() +{ + return _asset_browser_dock; +} + +[[nodiscard]] +Noggit::Ui::Tools::AssetBrowser::Ui::AssetBrowserWidget* MapView::getAssetBrowserWidget() +{ + return _asset_browser; +} + glm::vec3 MapView::cursorPosition() const { return _cursor_pos; @@ -4356,6 +4402,17 @@ void MapView::onSettingsSave() } +void MapView::setCameraDirty() +{ + _camera_moved_since_last_draw = true; +} + +[[nodiscard]] +Noggit::Ui::minimap_widget* MapView::getMinimapWidget() const +{ + return _minimap; +} + void MapView::ShowContextMenu(QPoint pos) { // QApplication::startDragDistance() is 10 diff --git a/src/noggit/MapView.h b/src/noggit/MapView.h index 2c3c8a91..38f392ed 100755 --- a/src/noggit/MapView.h +++ b/src/noggit/MapView.h @@ -3,41 +3,32 @@ #pragma once #include -#include -#include +#include #include +#include +#include #include -#include -#include #include #include -#include -#include -#include +#include #include -#include #include -#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include -#include - -#include +#include +class DBCFile; class World; +struct ImGuiContext; + +class QSettings; +class QDockWidget; +class QLabel; +class QWidgetAction; +class QOpenGLContext; namespace Noggit::Ui::Windows { @@ -47,6 +38,12 @@ namespace Noggit::Ui::Windows namespace Noggit { class Tool; + class TabletManager; + + namespace Project + { + class NoggitProject; + } namespace Ui::Tools::ViewToolbar::Ui { @@ -56,10 +53,12 @@ namespace Noggit namespace Ui::Tools { class ToolPanel; + + namespace AssetBrowser::Ui + { + class AssetBrowserWidget; + } } - - class Camera; - namespace Ui { @@ -70,6 +69,15 @@ namespace Noggit } } +namespace OpenGL +{ + class texture; +} + +namespace Ui { + class MapViewOverlay; +} + enum class save_mode { current, @@ -241,15 +249,15 @@ public: void change_selected_wmo_nameset(int set); void change_selected_wmo_doodadset(int set); auto setBrushTexture(QImage const* img) -> void; - Noggit::Camera* getCamera() { return &_camera; }; + Noggit::Camera* getCamera();; void onSettingsSave(); - void setCameraDirty() { _camera_moved_since_last_draw = true; }; + void setCameraDirty();; [[nodiscard]] - Noggit::Ui::minimap_widget* getMinimapWidget() const { return _minimap; } + Noggit::Ui::minimap_widget* getMinimapWidget() const; void set_editing_mode (editing_mode); - editing_mode get_editing_mode() { return terrainMode; }; + editing_mode get_editing_mode() const;; [[nodiscard]] QWidget *getSecondaryToolBar(); @@ -258,16 +266,16 @@ public: QWidget *getLeftSecondaryToolbar(); [[nodiscard]] - Noggit::NoggitRenderContext getRenderContext() { return _context; }; + Noggit::NoggitRenderContext getRenderContext();; [[nodiscard]] - World* getWorld() { return _world.get(); }; + World* getWorld() const;; [[nodiscard]] - QDockWidget* getAssetBrowser() {return _asset_browser_dock; }; + QDockWidget* getAssetBrowser();; [[nodiscard]] - Noggit::Ui::Tools::AssetBrowser::Ui::AssetBrowserWidget* getAssetBrowserWidget() { return _asset_browser; }; + Noggit::Ui::Tools::AssetBrowser::Ui::AssetBrowserWidget* getAssetBrowserWidget();; glm::vec3 cursorPosition() const; void cursorPosition(glm::vec3 position); diff --git a/src/noggit/Misc.cpp b/src/noggit/Misc.cpp index 2f7cd5d1..8d91eaeb 100755 --- a/src/noggit/Misc.cpp +++ b/src/noggit/Misc.cpp @@ -1,15 +1,10 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). #include -#include -#include -#include -#include #include +#include -#include -#include -#include +#include #include #include @@ -28,6 +23,11 @@ namespace misc point.x <= extents[1].x && point.y <= extents[1].y; } + glm::vec4 normalized_device_coords(int x, int y, int screen_width, int screen_height) + { + return { 2.0f * x / screen_width - 1.0f, 1.0f - 2.0f * y / screen_height, 0.0f, 1.0f }; + } + // project 3D point to 2d screen space. // Note : When working with noggit 3D coords, need to swap Y and Z ! glm::vec4 projectPointToScreen(const glm::vec3& point, const glm::mat4& VPmatrix, float viewport_width, float viewport_height, bool& valid) @@ -132,6 +132,31 @@ namespace misc } } + int rounded_int_div(int value, int div) + { + return value / div + (value % div <= (div >> 1) ? 0 : 1); + } + + int rounded_255_int_div(int value) + { + return value / 255 + (value % 255 <= 127 ? 0 : 1); + } + + // treat the value as an 8x8 array of bit + void set_bit(std::uint64_t& value, int x, int y, bool on) + { + std::uint64_t bit = std::uint64_t(1) << (y * 8 + x); + value = on ? (value | bit) : (value & ~bit); + } + + void bit_or(std::uint64_t& value, int x, int y, bool on) + { + if (on) + { + value |= (std::uint64_t(1) << (y * 8 + x)); + } + } + void find_and_replace(std::string& source, const std::string& find, const std::string& replace) { size_t found = source.rfind(find); @@ -286,6 +311,13 @@ namespace misc return filename; } + + // see http://realtimecollisiondetection.net/blog/?p=89 for more info + bool float_equals(float const& a, float const& b) + { + return std::abs(a - b) < (std::max(1.f, std::max(a, b)) * std::numeric_limits::epsilon()); + } + bool vec3d_equals(glm::vec3 const& v1, glm::vec3 const& v2) { return float_equals(v1.x, v2.x) && float_equals(v1.y, v2.y) && float_equals(v1.z, v2.z); diff --git a/src/noggit/Misc.h b/src/noggit/Misc.h index 7dd5a5cf..e507183e 100755 --- a/src/noggit/Misc.h +++ b/src/noggit/Misc.h @@ -3,19 +3,20 @@ #pragma once #include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include -#include -#include +#include +#include +#include +#include +#include +#include + + +namespace util +{ + class sExtendableArray; +} // namespace for static helper functions. @@ -49,10 +50,7 @@ namespace misc std::string normalize_adt_filename(std::string filename); // see http://realtimecollisiondetection.net/blog/?p=89 for more info - inline bool float_equals(float const& a, float const& b) - { - return std::abs(a - b) < (std::max(1.f, std::max(a, b)) * std::numeric_limits::epsilon()); - } + bool float_equals(float const& a, float const& b); bool vec3d_equals(glm::vec3 const& v1, glm::vec3 const& v2); bool deg_vec3d_equals(math::degrees::vec3 const& v1, math::degrees::vec3 const& v2); @@ -70,35 +68,16 @@ namespace misc bool pointInside(glm::vec3 point, std::array const& extents); bool pointInside(glm::vec2 point, std::array const& extents); - inline glm::vec4 normalized_device_coords(int x, int y, int screen_width, int screen_height) - { - return { 2.0f * x / screen_width - 1.0f, 1.0f - 2.0f * y / screen_height, 0.0f, 1.0f }; - } + glm::vec4 normalized_device_coords(int x, int y, int screen_width, int screen_height); void minmax(glm::vec3* a, glm::vec3* b); - inline int rounded_int_div(int value, int div) - { - return value / div + (value % div <= (div >> 1) ? 0 : 1); - } - inline int rounded_255_int_div(int value) - { - return value / 255 + (value % 255 <= 127 ? 0 : 1); - } + int rounded_int_div(int value, int div); + int rounded_255_int_div(int value); // treat the value as an 8x8 array of bit - inline void set_bit(std::uint64_t& value, int x, int y, bool on) - { - std::uint64_t bit = std::uint64_t(1) << (y * 8 + x); - value = on ? (value | bit) : (value & ~bit); - } - inline void bit_or(std::uint64_t& value, int x, int y, bool on) - { - if (on) - { - value |= (std::uint64_t(1) << (y * 8 + x)); - } - } + void set_bit(std::uint64_t& value, int x, int y, bool on); + void bit_or(std::uint64_t& value, int x, int y, bool on); struct random_color : glm::vec4 { diff --git a/src/noggit/Model.cpp b/src/noggit/Model.cpp index 2deaf2b9..d84d21c0 100755 --- a/src/noggit/Model.cpp +++ b/src/noggit/Model.cpp @@ -1,27 +1,20 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). #include -#include +#include +#include #include #include -#include -#include // TextureManager, Texture -#include -#include -#include -#include -#include +#include #include -#include -#include +#include +#include // TextureManager, Texture -#include #include -#include -#include #include #include -#include +#include +#include Model::Model(const std::string& filename, Noggit::NoggitRenderContext context) : AsyncObject(filename) @@ -142,6 +135,62 @@ void Model::waitForChildrenLoaded() } } +[[nodiscard]] +bool Model::is_hidden() const +{ + return _hidden; +} + +void Model::toggle_visibility() +{ + _hidden = !_hidden; +} + +void Model::show() +{ + _hidden = false; +} + +void Model::hide() +{ + _hidden = true; +} + +[[nodiscard]] +bool Model::use_fake_geometry() const +{ + return !!_fake_geometry; +} + +[[nodiscard]] +bool Model::animated_mesh() const +{ + return (animGeometry || animBones); +} + +[[nodiscard]] +bool Model::particles_only() const +{ // some particle emitters like wisps in ashenvale have a few vertices but no collision, using that to detect + return !_particles.empty() + && (_renderer.renderPasses().empty() || _vertices.empty() || !nBoundingTriangles); +} + +[[nodiscard]] +bool Model::is_required_when_saving() const +{ + return true; +} + +[[nodiscard]] +Noggit::Rendering::ModelRender* Model::renderer() +{ + return &_renderer; +} + +uint32_t Model::get_anim_lenght(int16_t anim_id) +{ + return _animation_length[anim_id]; +} bool Model::isAnimated(const BlizzardArchive::ClientFile& f, ModelHeader& header) { diff --git a/src/noggit/Model.h b/src/noggit/Model.h index 9f8844eb..509a4952 100755 --- a/src/noggit/Model.h +++ b/src/noggit/Model.h @@ -1,23 +1,23 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). #pragma once -#include -#include -#include #include // Animation::M2Value #include // AsyncObject +#include #include #include -#include -#include -#include #include -#include -#include +#include + +#include + #include + +#include + +#include #include #include -#include class Bone; class Model; @@ -27,10 +27,14 @@ class RibbonEmitter; namespace Noggit::Rendering { - class ModelRender; struct ModelRenderPass; } +namespace math +{ + struct ray; +} + enum M2Versions { m2_version_pre_release = 256, // < 257 @@ -167,35 +171,28 @@ public: void waitForChildrenLoaded() override; [[nodiscard]] - bool is_hidden() const { return _hidden; } + bool is_hidden() const; - void toggle_visibility() { _hidden = !_hidden; } - void show() { _hidden = false ; } - void hide() { _hidden = true; } + void toggle_visibility(); + void show(); + void hide(); [[nodiscard]] - bool use_fake_geometry() const { return !!_fake_geometry; } + bool use_fake_geometry() const; [[nodiscard]] - bool animated_mesh() const { return (animGeometry || animBones); } + bool animated_mesh() const; [[nodiscard]] - bool particles_only() const - { // some particle emitters like wisps in ashenvale have a few vertices but no collision, using that to detect - return !_particles.empty() - && (_renderer.renderPasses().empty() || _vertices.empty() || !nBoundingTriangles); - } + bool particles_only() const; [[nodiscard]] - bool is_required_when_saving() const override - { - return true; - } + bool is_required_when_saving() const override; [[nodiscard]] - Noggit::Rendering::ModelRender* renderer() { return &_renderer; } + Noggit::Rendering::ModelRender* renderer(); - uint32_t get_anim_lenght(int16_t anim_id) { return _animation_length[anim_id]; } + uint32_t get_anim_lenght(int16_t anim_id); // only useful if model has multiple anims with varying bound sizes // probably never happens with world objects, but this should be more accurate than global bounds diff --git a/src/noggit/ModelInstance.cpp b/src/noggit/ModelInstance.cpp index 833c4fdb..2488cb32 100755 --- a/src/noggit/ModelInstance.cpp +++ b/src/noggit/ModelInstance.cpp @@ -1,21 +1,22 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). -#include -#include +#include +#include // ENTRY_MDDF #include // checkinside #include // Model, etc. -#include -#include +#include #include -#include -#include +#include +#include -#include +#include +#include #include +#include +#include #include #include -#include -#include +#include #include @@ -39,6 +40,39 @@ ModelInstance::ModelInstance(BlizzardArchive::Listfile::FileKey const& file_key _need_recalc_extents = true; } +ModelInstance::ModelInstance(ModelInstance&& other) noexcept + : SceneObject(other._type, other._context) + , model(std::move(other.model)) + , light_color(other.light_color) + , size_cat(other.size_cat) + , _need_recalc_extents(other._need_recalc_extents) +{ + pos = other.pos; + dir = other.dir; + scale = other.scale; + extents[0] = other.extents[0]; + extents[1] = other.extents[1]; + _transform_mat_inverted = other._transform_mat_inverted; + _context = other._context; + uid = other.uid; +} + +ModelInstance& ModelInstance::operator= (ModelInstance&& other) noexcept +{ + std::swap(model, other.model); + std::swap(pos, other.pos); + std::swap(dir, other.dir); + std::swap(light_color, other.light_color); + std::swap(uid, other.uid); + std::swap(scale, other.scale); + std::swap(size_cat, other.size_cat); + std::swap(_need_recalc_extents, other._need_recalc_extents); + std::swap(extents, other.extents); + std::swap(_transform_mat_inverted, other._transform_mat_inverted); + std::swap(_context, other._context); + return *this; +} + void ModelInstance::draw_box (glm::mat4x4 const& model_view , glm::mat4x4 const& projection @@ -222,6 +256,17 @@ bool ModelInstance::isInRenderDist(const float cull_distance, const glm::vec3& c return true; } +bool ModelInstance::extentsDirty() const +{ + return _need_recalc_extents || !model->finishedLoading(); +} + +[[nodiscard]] +glm::vec3 const& ModelInstance::get_pos() const +{ + return pos; +} + void ModelInstance::recalcExtents() { if (!model->finishedLoading()) @@ -304,6 +349,11 @@ void ModelInstance::ensureExtents() } } +bool ModelInstance::finishedLoading() +{ + return model->finishedLoading(); +} + std::array const& ModelInstance::getExtents() { ensureExtents(); @@ -331,6 +381,18 @@ std::array ModelInstance::getBoundingBox() return relative_to_model.rotated_corners(_transform_mat, true); } +[[nodiscard]] +bool ModelInstance::isWMODoodad() const +{ + return false; +} + +[[nodiscard]] +AsyncObject* ModelInstance::instance_model() const +{ + return model.get(); +} + void ModelInstance::updateDetails(Noggit::Ui::detail_infos* detail_widget) { std::stringstream select_info; @@ -372,6 +434,12 @@ void ModelInstance::updateDetails(Noggit::Ui::detail_infos* detail_widget) detail_widget->setText(select_info.str()); } +[[nodiscard]] +std::uint32_t ModelInstance::gpuTransformUid() const +{ + return _gpu_transform_uid; +} + wmo_doodad_instance::wmo_doodad_instance(BlizzardArchive::Listfile::FileKey const& file_key , BlizzardArchive::ClientFile* f , Noggit::NoggitRenderContext context) @@ -405,6 +473,44 @@ wmo_doodad_instance::wmo_doodad_instance(BlizzardArchive::Listfile::FileKey cons light_color = glm::vec3(color.bgra.r / 255.f, color.bgra.g / 255.f, color.bgra.b / 255.f); } +// titi : issue with those constructors: ModelInstance data is lost (scale, pos...) +/**/ +wmo_doodad_instance::wmo_doodad_instance(wmo_doodad_instance const& other) +// : ModelInstance(other.model->file_key(), other._context) + : ModelInstance(other) // titi : Use the copy constructor of ModelInstance instead + , doodad_orientation(other.doodad_orientation) + , world_pos(other.world_pos) + , _need_matrix_update(other._need_matrix_update) +{ + // titi: added those. + // pos = other.pos; + // scale = other.scale; + // frame = other.frame; +} + +wmo_doodad_instance::wmo_doodad_instance(wmo_doodad_instance&& other) noexcept + : ModelInstance(reinterpret_cast(other)) + , doodad_orientation(other.doodad_orientation) + , world_pos(other.world_pos) + , _need_matrix_update(other._need_matrix_update) +{ +} + +wmo_doodad_instance& wmo_doodad_instance::operator= (wmo_doodad_instance&& other) noexcept +{ + ModelInstance::operator= (reinterpret_cast(other)); + std::swap(doodad_orientation, other.doodad_orientation); + std::swap(world_pos, other.world_pos); + std::swap(_need_matrix_update, other._need_matrix_update); + return *this; +} + +[[nodiscard]] +bool wmo_doodad_instance::need_matrix_update() const +{ + return _need_matrix_update; +} + void wmo_doodad_instance::update_transform_matrix_wmo(WMOInstance* wmo) { if (!model->finishedLoading() || !wmo->finishedLoading()) @@ -468,3 +574,19 @@ bool wmo_doodad_instance::isInRenderDist(const float cull_distance, const glm::v return true; } +[[nodiscard]] +glm::vec3 const& wmo_doodad_instance::get_pos() const +{ + return world_pos; +} + +[[nodiscard]] +bool wmo_doodad_instance::isWMODoodad() const +{ + return true; +} + +// to avoid redefining recalcExtents +void wmo_doodad_instance::updateTransformMatrix() +{ +} diff --git a/src/noggit/ModelInstance.h b/src/noggit/ModelInstance.h index d9b2266b..17eba889 100755 --- a/src/noggit/ModelInstance.h +++ b/src/noggit/ModelInstance.h @@ -2,20 +2,33 @@ #pragma once -#include -#include -#include // ENTRY_MDDF #include #include #include -#include -#include #include -#include -#include + +#include +#include + #include -namespace math { class frustum; } +namespace math +{ + class frustum; + struct ray; +} + +namespace BlizzardArchive +{ + class ClientFile; + + namespace Listfile + { + class FileKey; + } +} + +struct ENTRY_MDDF; class Model; class WMOInstance; @@ -42,38 +55,9 @@ public: ModelInstance(ModelInstance const& other) = default; ModelInstance& operator= (ModelInstance const& other) = default; - ModelInstance (ModelInstance&& other) noexcept - : SceneObject(other._type, other._context) - , model (std::move (other.model)) - , light_color (other.light_color) - , size_cat (other.size_cat) - , _need_recalc_extents(other._need_recalc_extents) - { - pos = other.pos; - dir = other.dir; - scale = other.scale; - extents[0] = other.extents[0]; - extents[1] = other.extents[1]; - _transform_mat_inverted = other._transform_mat_inverted; - _context = other._context; - uid = other.uid; - } + ModelInstance (ModelInstance&& other) noexcept; - ModelInstance& operator= (ModelInstance&& other) noexcept - { - std::swap (model, other.model); - std::swap (pos, other.pos); - std::swap (dir, other.dir); - std::swap (light_color, other.light_color); - std::swap (uid, other.uid); - std::swap (scale, other.scale); - std::swap (size_cat, other.size_cat); - std::swap (_need_recalc_extents, other._need_recalc_extents); - std::swap (extents, other.extents); - std::swap(_transform_mat_inverted, other._transform_mat_inverted); - std::swap(_context, other._context); - return *this; - } + ModelInstance& operator= (ModelInstance&& other) noexcept; void draw_box (glm::mat4x4 const& model_view , glm::mat4x4 const& projection @@ -94,29 +78,29 @@ public: bool isInFrustum(math::frustum const& frustum); bool isInRenderDist(const float cull_distance, const glm::vec3& camera, display_mode display); - bool extentsDirty() { return _need_recalc_extents || !model->finishedLoading(); }; + bool extentsDirty() const;; [[nodiscard]] - virtual glm::vec3 const& get_pos() const { return pos; } + virtual glm::vec3 const& get_pos() const; void recalcExtents() override; void ensureExtents() override; - bool finishedLoading() override { return model->finishedLoading(); }; + bool finishedLoading() override;; std::array const& getExtents() override; // axis aligned std::array const& getLocalExtents() const; std::array getBoundingBox() override; // not axis aligned [[nodiscard]] - virtual bool isWMODoodad() const { return false; }; + virtual bool isWMODoodad() const;; [[nodiscard]] - AsyncObject* instance_model() const override { return model.get(); }; + AsyncObject* instance_model() const override;; void updateDetails(Noggit::Ui::detail_infos* detail_widget) override; [[nodiscard]] - std::uint32_t gpuTransformUid() const { return _gpu_transform_uid; } + std::uint32_t gpuTransformUid() const; protected: bool _need_recalc_extents = true; @@ -139,55 +123,30 @@ public: // titi : issue with those constructors: ModelInstance data is lost (scale, pos...) /**/ - wmo_doodad_instance(wmo_doodad_instance const& other) - // : ModelInstance(other.model->file_key(), other._context) - : ModelInstance(other) // titi : Use the copy constructor of ModelInstance instead - , doodad_orientation(other.doodad_orientation) - , world_pos(other.world_pos) - , _need_matrix_update(other._need_matrix_update) - { - // titi: added those. - // pos = other.pos; - // scale = other.scale; - // frame = other.frame; - }; + wmo_doodad_instance(wmo_doodad_instance const& other);; wmo_doodad_instance& operator= (wmo_doodad_instance const& other) = delete; - wmo_doodad_instance(wmo_doodad_instance&& other) noexcept - : ModelInstance(reinterpret_cast(other)) - , doodad_orientation(other.doodad_orientation) - , world_pos(other.world_pos) - , _need_matrix_update(other._need_matrix_update) - { - } + wmo_doodad_instance(wmo_doodad_instance&& other) noexcept; - wmo_doodad_instance& operator= (wmo_doodad_instance&& other) noexcept - { - ModelInstance::operator= (reinterpret_cast(other)); - std::swap (doodad_orientation, other.doodad_orientation); - std::swap (world_pos, other.world_pos); - std::swap (_need_matrix_update, other._need_matrix_update); - return *this; - } - + wmo_doodad_instance& operator= (wmo_doodad_instance&& other) noexcept; [[nodiscard]] - bool need_matrix_update() const { return _need_matrix_update; } + bool need_matrix_update() const; void update_transform_matrix_wmo(WMOInstance* wmo); bool isInRenderDist(const float cull_distance, const glm::vec3& camera, display_mode display); [[nodiscard]] - glm::vec3 const& get_pos() const override { return world_pos; }; + glm::vec3 const& get_pos() const override;; [[nodiscard]] - bool isWMODoodad() const override { return true; }; + bool isWMODoodad() const override;; protected: // to avoid redefining recalcExtents - void updateTransformMatrix() override { } + void updateTransformMatrix() override; private: bool _need_matrix_update = true; diff --git a/src/noggit/ModelManager.cpp b/src/noggit/ModelManager.cpp index aadfae8e..932f2ece 100755 --- a/src/noggit/ModelManager.cpp +++ b/src/noggit/ModelManager.cpp @@ -1,11 +1,9 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). -#include // AsyncLoader #include // LogDebug #include // Model #include // ModelManager -#include namespace { @@ -77,3 +75,68 @@ void ModelManager::unload_all(Noggit::NoggitRenderContext context) , context ); } + +scoped_model_reference::scoped_model_reference(BlizzardArchive::Listfile::FileKey const& file_key, Noggit::NoggitRenderContext context) + + : _valid(true) + , _file_key(file_key) + , _model(ModelManager::_.emplace(_file_key, context)) + , _context(context) + +{ +} + +scoped_model_reference::scoped_model_reference(scoped_model_reference const& other) + : _valid(other._valid) + , _file_key(other._file_key) + , _model(ModelManager::_.emplace(_file_key, other._context)) + , _context(other._context) +{ +} + +scoped_model_reference& scoped_model_reference::operator=(scoped_model_reference const& other) +{ + _valid = other._valid; + _file_key = other._file_key; + _model = ModelManager::_.emplace(_file_key, other._context); + _context = other._context; + return *this; +} + +scoped_model_reference::scoped_model_reference(scoped_model_reference&& other) + : _valid(other._valid) + , _file_key(other._file_key) + , _model(other._model) + , _context(other._context) +{ + other._valid = false; +} + +scoped_model_reference& scoped_model_reference::operator=(scoped_model_reference&& other) +{ + std::swap(_valid, other._valid); + std::swap(_file_key, other._file_key); + std::swap(_model, other._model); + std::swap(_context, other._context); + other._valid = false; + return *this; +} + +scoped_model_reference::~scoped_model_reference() +{ + if (_valid) + { + ModelManager::_.erase(_file_key, _context); + } +} + +Model* scoped_model_reference::operator->() const +{ + return _model; +} + +[[nodiscard]] +Model* scoped_model_reference::get() const +{ + return _model; +} diff --git a/src/noggit/ModelManager.h b/src/noggit/ModelManager.h index 37d03cc6..160ddc34 100755 --- a/src/noggit/ModelManager.h +++ b/src/noggit/ModelManager.h @@ -2,14 +2,8 @@ #pragma once -#include #include #include -#include - -#include -#include -#include class Model; @@ -30,66 +24,20 @@ private: struct scoped_model_reference { - scoped_model_reference(BlizzardArchive::Listfile::FileKey const& file_key, Noggit::NoggitRenderContext context) + scoped_model_reference(BlizzardArchive::Listfile::FileKey const& file_key, Noggit::NoggitRenderContext context); - : _valid(true) - , _file_key(file_key) - , _model(ModelManager::_.emplace (_file_key, context)) - , _context(context) + scoped_model_reference(scoped_model_reference const& other); + scoped_model_reference& operator=(scoped_model_reference const& other); - {} + scoped_model_reference(scoped_model_reference&& other); + scoped_model_reference& operator=(scoped_model_reference&& other); - scoped_model_reference(scoped_model_reference const& other) - : _valid (other._valid) - , _file_key (other._file_key) - , _model (ModelManager::_.emplace(_file_key, other._context)) - , _context(other._context) - {} - scoped_model_reference& operator=(scoped_model_reference const& other) - { - _valid = other._valid; - _file_key = other._file_key; - _model = ModelManager::_.emplace (_file_key, other._context); - _context = other._context; - return *this; - } + ~scoped_model_reference(); - scoped_model_reference(scoped_model_reference&& other) - : _valid(other._valid) - , _file_key(other._file_key) - , _model(other._model) - , _context(other._context) - { - other._valid = false; - } - scoped_model_reference& operator=(scoped_model_reference&& other) - { - std::swap(_valid, other._valid); - std::swap(_file_key, other._file_key); - std::swap(_model, other._model); - std::swap(_context, other._context); - other._valid = false; - return *this; - } - - ~scoped_model_reference() - { - if (_valid) - { - ModelManager::_.erase(_file_key, _context); - } - } - - Model* operator->() const - { - return _model; - } + Model* operator->() const; [[nodiscard]] - Model* get() const - { - return _model; - } + Model* get() const; private: bool _valid; diff --git a/src/noggit/Particle.cpp b/src/noggit/Particle.cpp index 4ca6faee..65038a33 100755 --- a/src/noggit/Particle.cpp +++ b/src/noggit/Particle.cpp @@ -1,6 +1,7 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). #include +#include #include #include #include diff --git a/src/noggit/Particle.h b/src/noggit/Particle.h index 8550e336..c3bc46ae 100755 --- a/src/noggit/Particle.h +++ b/src/noggit/Particle.h @@ -3,10 +3,7 @@ #pragma once #include // Animation::M2Value -#include -#include #include -#include #include #include @@ -17,6 +14,11 @@ class Model; class ParticleSystem; class RibbonEmitter; +namespace OpenGL::Scoped +{ + struct use_program; +} + namespace BlizzardArchive { class ClientFile; diff --git a/src/noggit/SceneObject.cpp b/src/noggit/SceneObject.cpp index 372c8461..67b92a66 100755 --- a/src/noggit/SceneObject.cpp +++ b/src/noggit/SceneObject.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -17,9 +18,10 @@ SceneObject::SceneObject(SceneObjectTypes type, Noggit::NoggitRenderContext cont , dir(0.f, 0.f, 0.f) , uid(0) , frame(0) +, bounding_radius{0} { // min and max initialized to their opposites - extents[0] = glm::vec3(std::numeric_limits::max()); + extents[0] = glm::vec3(std::numeric_limits::max()); extents[1] = glm::vec3(std::numeric_limits::lowest()); } @@ -110,6 +112,26 @@ void SceneObject::normalizeDirection() dir.z += 360.0f; } +[[nodiscard]] +glm::mat4x4 SceneObject::transformMatrix() const +{ + /*ensureExtents();*/ + return _transform_mat; +} + +[[nodiscard]] +glm::mat4x4 SceneObject::transformMatrixInverted() const +{ + /*ensureExtents();*/ + return _transform_mat_inverted; +} + +[[nodiscard]] +SceneObjectTypes SceneObject::which() const +{ + return _type; +} + void SceneObject::refTile(MapTile* tile) { assert(tile); @@ -130,3 +152,26 @@ void SceneObject::derefTile(MapTile* tile) if (it != _tiles.end()) _tiles.erase(it); } + +[[nodiscard]] +std::vector const& SceneObject::getTiles() const +{ + return _tiles; +} + +[[nodiscard]] +std::array const& SceneObject::getExtents() +{ + ensureExtents(); return extents; +} + +[[nodiscard]] +float SceneObject::getBoundingRadius() +{ + ensureExtents(); return bounding_radius; +} + +glm::vec3 const SceneObject::getServerPos() const +{ + return glm::vec3(ZEROPOINT - pos.z, ZEROPOINT - pos.x, pos.y); +} diff --git a/src/noggit/SceneObject.hpp b/src/noggit/SceneObject.hpp index d7a08908..a433ada6 100755 --- a/src/noggit/SceneObject.hpp +++ b/src/noggit/SceneObject.hpp @@ -4,12 +4,8 @@ #define NOGGIT_3DOBJECT_HPP #include -#include #include #include -#include -#include -#include #include namespace BlizzardArchive::Listfile @@ -51,33 +47,33 @@ public: void normalizeDirection(); [[nodiscard]] - inline glm::mat4x4 transformMatrix() { /*ensureExtents();*/ return _transform_mat; }; // can't recalc extent directly there because recalc extents functions call this and it causes an infinite loop + glm::mat4x4 transformMatrix() const;; // can't recalc extent directly there because recalc extents functions call this and it causes an infinite loop [[nodiscard]] - inline glm::mat4x4 transformMatrixInverted() { /*ensureExtents();*/ return _transform_mat_inverted; }; + glm::mat4x4 transformMatrixInverted() const;; [[nodiscard]] - inline SceneObjectTypes which() const { return _type; }; + SceneObjectTypes which() const;; void refTile(MapTile* tile); void derefTile(MapTile* tile); [[nodiscard]] - std::vector const& getTiles() const { return _tiles; }; + std::vector const& getTiles() const;; [[nodiscard]] virtual AsyncObject* instance_model() const = 0; [[nodiscard]] - virtual std::array const& getExtents() { ensureExtents(); return extents; } // axis aligned + virtual std::array const& getExtents(); // axis aligned [[nodiscard]] virtual std::array getBoundingBox() = 0; // non axis aligned [[nodiscard]] - inline float const getBoundingRadius() { ensureExtents(); return bounding_radius; } + float getBoundingRadius(); - glm::vec3 const getServerPos() { return glm::vec3(ZEROPOINT - pos.z, ZEROPOINT - pos.x, pos.y); } + glm::vec3 const getServerPos() const; bool _grouped = false; diff --git a/src/noggit/Selection.cpp b/src/noggit/Selection.cpp index 6fd8bb78..35fb9908 100755 --- a/src/noggit/Selection.cpp +++ b/src/noggit/Selection.cpp @@ -1,6 +1,9 @@ -#include -#include +#include #include +#include +#include +#include +#include #include #include @@ -398,3 +401,24 @@ void selection_group::recalcExtents() _group_extents[1].z = instance->getExtents()[1].z; } } + +std::vector const& selection_group::getMembers() const +{ + return _members_uid; +} + +[[nodiscard]] +std::array const& selection_group::getExtents() const +{ + return _group_extents; +} + +bool selection_group::isSelected() const +{ + return _is_selected; +} + +void selection_group::setUnselected() +{ + _is_selected = false; +} diff --git a/src/noggit/Selection.h b/src/noggit/Selection.h index c56d45f3..239e9d80 100755 --- a/src/noggit/Selection.h +++ b/src/noggit/Selection.h @@ -4,9 +4,7 @@ #include #include #include -#include #include -#include #include // #include @@ -86,13 +84,13 @@ public: // void scale_group(); // void rotate_group(); - std::vector const& getMembers() const { return _members_uid; } + std::vector const& getMembers() const; [[nodiscard]] - std::array const& getExtents() const { return _group_extents; } // ensureExtents(); + std::array const& getExtents() const; // ensureExtents(); - bool isSelected() const { return _is_selected; } - void setUnselected() { _is_selected = false; } + bool isSelected() const; + void setUnselected(); bool _is_selected = false; @@ -111,4 +109,4 @@ private: }; using selection_entry = std::pair; // float = hit distance -using selection_result = std::vector; \ No newline at end of file +using selection_result = std::vector; diff --git a/src/noggit/Sky.cpp b/src/noggit/Sky.cpp index 66f02909..266709f0 100755 --- a/src/noggit/Sky.cpp +++ b/src/noggit/Sky.cpp @@ -2,11 +2,12 @@ #include #include +#include #include // Model #include // ModelManager #include -#include #include +#include #include #include #include @@ -210,6 +211,66 @@ SkyParam::SkyParam(int paramId, Noggit::NoggitRenderContext context) } +bool SkyParam::highlight_sky() const +{ + return _highlight_sky; +} + +float SkyParam::river_shallow_alpha() const +{ + return _river_shallow_alpha; +} + +float SkyParam::river_deep_alpha() const +{ + return _river_deep_alpha; +} + +float SkyParam::ocean_shallow_alpha() const +{ + return _ocean_shallow_alpha; +} + +float SkyParam::ocean_deep_alpha() const +{ + return _ocean_deep_alpha; +} + +float SkyParam::glow() const +{ + return _glow; +} + +void SkyParam::set_glow(float glow) +{ + _glow = glow; +} + +void SkyParam::set_highlight_sky(bool state) +{ + _highlight_sky = state; +} + +void SkyParam::set_river_shallow_alpha(float alpha) +{ + _river_shallow_alpha = alpha; +} + +void SkyParam::set_river_deep_alpha(float alpha) +{ + _river_deep_alpha = alpha; +} + + void SkyParam::set_ocean_shallow_alpha(float alpha) +{ + _ocean_shallow_alpha = alpha; +} + +void SkyParam::set_ocean_deep_alpha(float alpha) +{ + _ocean_deep_alpha = alpha; +} + Sky::Sky(DBCFile::Iterator data, Noggit::NoggitRenderContext context) : _context(context) @@ -237,6 +298,11 @@ Sky::Sky(DBCFile::Iterator data, Noggit::NoggitRenderContext context) } } +int Sky::getId() const +{ + return Id; +} + std::optional Sky::getParam(int param_index) const { unsigned int param_id = skyParams[param_index]; @@ -261,6 +327,11 @@ std::optional Sky::getParam(int param_index) const } } +std::optional Sky::getCurrentParam() const +{ + return getParam(curr_sky_param); +} + float Sky::floatParamFor(int r, int t) const { auto param_opt = getCurrentParam(); @@ -1124,6 +1195,75 @@ void Skies::drawLightingSphereHandles (glm::mat4x4 const& model_view } } +bool Skies::hasSkies() const +{ + return numSkies > 0; +} + +float Skies::river_shallow_alpha() const +{ + return _river_shallow_alpha; +} + +float Skies::river_deep_alpha() const +{ + return _river_deep_alpha; +} + +float Skies::ocean_shallow_alpha() const +{ + return _ocean_shallow_alpha; +} + +float Skies::ocean_deep_alpha() const +{ + return _ocean_deep_alpha; +} + +float Skies::fog_distance_end() const +{ + return _fog_distance / 36.f; +} + +float Skies::fog_distance_start() const +{ + return (_fog_distance / 36.f) * _fog_multiplier; +} + +float Skies::fog_distance_multiplier() const +{ + return _fog_multiplier; +} + +float Skies::celestial_glow() const +{ + return _celestial_glow; +} + +float Skies::cloud_density() const +{ + return _cloud_density; +} + +float Skies::unknown_float_param4() const +{ + return _unknown_float_param4; +} + +float Skies::unknown_float_param5() const +{ + return _unknown_float_param5; +} + +float Skies::glow() const +{ + return _glow; +} + +float Skies::fogRate() const +{ + return _fog_rate; +} void Skies::unload() { @@ -1137,6 +1277,11 @@ void Skies::unload() } +void Skies::force_update() +{ + _force_update = true; +} + void Skies::upload() { _program.reset(new OpenGL::program( @@ -1358,6 +1503,18 @@ OutdoorLightStats OutdoorLighting::getLightStats(int time) return out; } +bool Sky::operator<(const Sky& s) const +{ + if (global) return false; + else if (s.global) return true; + else return r2 < s.r2; +} + +bool Sky::selected() const +{ + return _selected; +} + void Sky::save_to_dbc() { // Save Light.dbc record @@ -1578,4 +1735,4 @@ void Sky::save_to_dbc() assert(false); } -} \ No newline at end of file +} diff --git a/src/noggit/Sky.h b/src/noggit/Sky.h index 7a044d7e..65c4a1e6 100755 --- a/src/noggit/Sky.h +++ b/src/noggit/Sky.h @@ -5,7 +5,6 @@ #include #include #include -#include #include #include @@ -168,19 +167,19 @@ public: // potential structure rework, more similar to retail/classic LightData.db // std::vector lightData; - bool highlight_sky() const { return _highlight_sky; } - float river_shallow_alpha() const { return _river_shallow_alpha; } - float river_deep_alpha() const { return _river_deep_alpha; } - float ocean_shallow_alpha() const { return _ocean_shallow_alpha; } - float ocean_deep_alpha() const { return _ocean_deep_alpha; } - float glow() const { return _glow; } + bool highlight_sky() const; + float river_shallow_alpha() const; + float river_deep_alpha() const; + float ocean_shallow_alpha() const; + float ocean_deep_alpha() const; + float glow() const; - void set_glow(float glow) { _glow = glow; } - void set_highlight_sky(bool state) { _highlight_sky = state; } - void set_river_shallow_alpha(float alpha) { _river_shallow_alpha = alpha; } - void set_river_deep_alpha(float alpha) { _river_deep_alpha = alpha; } - void set_ocean_shallow_alpha(float alpha) { _ocean_shallow_alpha = alpha; } - void set_ocean_deep_alpha(float alpha) { _ocean_deep_alpha = alpha; } + void set_glow(float glow); + void set_highlight_sky(bool state); + void set_river_shallow_alpha(float alpha); + void set_river_deep_alpha(float alpha); + void set_ocean_shallow_alpha(float alpha); + void set_ocean_deep_alpha(float alpha); // always save them for now // later we can have a system to only save modified dbcs @@ -216,7 +215,7 @@ public: explicit Sky(DBCFile::Iterator data, Noggit::NoggitRenderContext context); - int getId() const { return Id; }; + int getId() const;; // std::unique_ptr skyParams[NUM_SkyParamsNames]; unsigned int skyParams[NUM_SkyParamsNames]; @@ -224,7 +223,7 @@ public: std::optional getParam(int param_index) const; - std::optional getCurrentParam() const { return getParam(curr_sky_param);}; + std::optional getCurrentParam() const;; glm::vec3 colorFor(int r, int t) const; @@ -236,14 +235,9 @@ public: bool is_new_record = false; - bool operator<(const Sky& s) const - { - if (global) return false; - else if (s.global) return true; - else return r2 < s.r2; - } + bool operator<(const Sky& s) const; - bool selected() const { return _selected; } + bool selected() const; void save_to_dbc(); @@ -345,29 +339,29 @@ public: ); - bool hasSkies() { return numSkies > 0; } + bool hasSkies() const; - float river_shallow_alpha() const { return _river_shallow_alpha; } - float river_deep_alpha() const { return _river_deep_alpha; } - float ocean_shallow_alpha() const { return _ocean_shallow_alpha; } - float ocean_deep_alpha() const { return _ocean_deep_alpha; } + float river_shallow_alpha() const; + float river_deep_alpha() const; + float ocean_shallow_alpha() const; + float ocean_deep_alpha() const; - float fog_distance_end() const { return _fog_distance / 36.f; }; - float fog_distance_start() const { return (_fog_distance / 36.f) * _fog_multiplier; }; - float fog_distance_multiplier() const { return _fog_multiplier; }; + float fog_distance_end() const;; + float fog_distance_start() const;; + float fog_distance_multiplier() const;; - float celestial_glow() const { return _celestial_glow; }; - float cloud_density() const { return _cloud_density; }; - float unknown_float_param4() const { return _unknown_float_param4; }; - float unknown_float_param5() const { return _unknown_float_param5; }; + float celestial_glow() const;; + float cloud_density() const;; + float unknown_float_param4() const;; + float unknown_float_param5() const;; - float glow() const { return _glow; }; + float glow() const;; - float fogRate() const { return _fog_rate; } + float fogRate() const; void unload(); - void force_update() { _force_update = true; } + void force_update(); private: bool _uploaded = false; diff --git a/src/noggit/TextureManager.cpp b/src/noggit/TextureManager.cpp index e94d3809..6f0ababd 100755 --- a/src/noggit/TextureManager.cpp +++ b/src/noggit/TextureManager.cpp @@ -2,10 +2,11 @@ #include #include // LogDebug #include +#include #include -#include -#include +#include +#include #include #include @@ -284,6 +285,68 @@ void blp_texture::unload() finishLoading(); } +bool blp_texture::is_uploaded() const +{ + return _uploaded; +} + +GLuint blp_texture::texture_array() const +{ + return _texture_array; +} + +int blp_texture::array_index() const +{ + return _array_index; +} + +bool blp_texture::is_specular() const +{ + return _is_specular; +} + +unsigned blp_texture::mip_level() const +{ + return static_cast(!_compression_format ? _data.size() : _compressed_data.size()); +} + +std::map>& blp_texture::data() +{ + return _data; +} + +std::map>& blp_texture::compressed_data() +{ + return _compressed_data; +} + +std::optional const& blp_texture::compression_format() const +{ + return _compression_format; +} + +Noggit::NoggitRenderContext blp_texture::getContext() const +{ + return _context; +} + +[[nodiscard]] +async_priority blp_texture::loading_priority() const +{ + return async_priority::high; +} + +// Mists HeightMapping +bool blp_texture::hasHeightMap() const +{ + return _has_heightmap; +} + +blp_texture* blp_texture::getHeightMap() +{ + return heightMap.get(); +} + void blp_texture::loadFromUncompressedData(BLPHeader const* lHeader, char const* lData) { unsigned int const* pal = reinterpret_cast(lData + sizeof(BLPHeader)); @@ -398,6 +461,16 @@ void blp_texture::loadFromCompressedData(BLPHeader const* lHeader, char const* l } } +int blp_texture::width() const +{ + return _width; +} + +int blp_texture::height() const +{ + return _height; +} + blp_texture::blp_texture(BlizzardArchive::Listfile::FileKey const& file_key, Noggit::NoggitRenderContext context) : AsyncObject(file_key) , _context(context) @@ -500,8 +573,13 @@ void blp_texture::finishLoading() namespace Noggit { + BLPRenderer& BLPRenderer::getInstance() + { + static BLPRenderer instance; + return instance; + } - QPixmap* BLPRenderer::render_blp_to_pixmap ( std::string const& blp_filename + QPixmap* BLPRenderer::render_blp_to_pixmap ( std::string const& blp_filename , int width , int height ) @@ -696,33 +774,3 @@ namespace Noggit } } - -scoped_blp_texture_reference::scoped_blp_texture_reference (std::string const& filename, Noggit::NoggitRenderContext context) - : _blp_texture(TextureManager::_.emplace(filename, context)) - , _context(context) -{} - -scoped_blp_texture_reference::scoped_blp_texture_reference (scoped_blp_texture_reference const& other) - : _blp_texture(other._blp_texture ? TextureManager::_.emplace(other._blp_texture->file_key().filepath(), other._context) : nullptr) - , _context(other._context) -{} - -void scoped_blp_texture_reference::Deleter::operator() (blp_texture* texture) const -{ - TextureManager::_.erase(texture->file_key().filepath(), texture->getContext()); -} - -blp_texture* scoped_blp_texture_reference::operator->() const -{ - return _blp_texture.get(); -} - -blp_texture* scoped_blp_texture_reference::get() const -{ - return _blp_texture.get(); -} - -bool scoped_blp_texture_reference::operator== (scoped_blp_texture_reference const& other) const -{ - return std::tie(_blp_texture) == std::tie(other._blp_texture); -} diff --git a/src/noggit/TextureManager.h b/src/noggit/TextureManager.h index e6b3f929..0f4923cf 100644 --- a/src/noggit/TextureManager.h +++ b/src/noggit/TextureManager.h @@ -5,15 +5,9 @@ #include #include #include -#include -#include -#include #include #include -#include -#include -#include #include #include #include @@ -23,6 +17,8 @@ #include #include +class QOffscreenSurface; +class QOpenGLFramebufferObjectFormat; struct texture_heightmapping_data { @@ -53,7 +49,6 @@ struct tuple_hash struct BLPHeader; -struct scoped_blp_texture_reference; struct blp_texture : public AsyncObject { blp_texture (BlizzardArchive::Listfile::FileKey const& filename, Noggit::NoggitRenderContext context); @@ -63,34 +58,31 @@ struct blp_texture : public AsyncObject void loadFromUncompressedData(BLPHeader const* lHeader, char const* lData); void loadFromCompressedData(BLPHeader const* lHeader, char const* lData); - int width() const { return _width; } - int height() const { return _height; } + int width() const; + int height() const; void bind(); void upload(); void uploadToArray(unsigned layer); void unload(); - bool is_uploaded() { return _uploaded; }; - GLuint texture_array() { return _texture_array; }; - int array_index() { return _array_index; }; - bool is_specular() { return _is_specular; }; - unsigned mip_level() { return static_cast(!_compression_format ? _data.size() : _compressed_data.size()); }; + bool is_uploaded() const;; + GLuint texture_array() const;; + int array_index() const;; + bool is_specular() const;; + unsigned mip_level() const;; - std::map>& data() { return _data;}; - std::map>& compressed_data() { return _compressed_data; }; - std::optional const& compression_format() { return _compression_format; }; + std::map>& data();; + std::map>& compressed_data();; + std::optional const& compression_format() const;; - Noggit::NoggitRenderContext getContext() { return _context; }; + Noggit::NoggitRenderContext getContext() const;; [[nodiscard]] - async_priority loading_priority() const override - { - return async_priority::high; - } + async_priority loading_priority() const override; // Mists HeightMapping - bool hasHeightMap() {return _has_heightmap; }; + bool hasHeightMap() const;; - blp_texture* getHeightMap() { return heightMap.get(); }; + blp_texture* getHeightMap();; private: bool _uploaded = false; @@ -134,31 +126,6 @@ private: }; -struct scoped_blp_texture_reference -{ - scoped_blp_texture_reference() = delete; - scoped_blp_texture_reference (std::string const& filename, Noggit::NoggitRenderContext context); - scoped_blp_texture_reference (scoped_blp_texture_reference const& other); - scoped_blp_texture_reference (scoped_blp_texture_reference&&) = default; - scoped_blp_texture_reference& operator= (scoped_blp_texture_reference const&) = delete; - scoped_blp_texture_reference& operator= (scoped_blp_texture_reference&&) = default; - ~scoped_blp_texture_reference() = default; - - blp_texture* operator->() const; - blp_texture* get() const; - - bool operator== (scoped_blp_texture_reference const& other) const; - - bool use_cubemap = false; -private: - struct Deleter - { - void operator() (blp_texture*) const; - }; - std::unique_ptr _blp_texture; - Noggit::NoggitRenderContext _context; -}; - namespace Noggit { @@ -183,11 +150,7 @@ namespace Noggit bool _uploaded = false; public: - static BLPRenderer& getInstance() - { - static BLPRenderer instance; - return instance; - } + static BLPRenderer& getInstance(); QPixmap* render_blp_to_pixmap ( std::string const& blp_filename, int width = -1, int height = -1); void unload(); diff --git a/src/noggit/TileIndex.cpp b/src/noggit/TileIndex.cpp new file mode 100644 index 00000000..c62265d3 --- /dev/null +++ b/src/noggit/TileIndex.cpp @@ -0,0 +1,41 @@ +#include "TileIndex.hpp" + +#include + +#include + +#include + + +TileIndex::TileIndex(const glm::vec3& pos) + : TileIndex(std::floor(pos.x / TILESIZE) + , std::floor(pos.z / TILESIZE)) +{ +} + +TileIndex::TileIndex(std::size_t tileX, std::size_t tileZ) + : x(tileX) + , z(tileZ) +{ +} + +bool operator==(TileIndex const& lhs, TileIndex const& rhs) +{ + return std::tie(lhs.x, lhs.z) == std::tie(rhs.x, rhs.z); +} + +bool operator<(TileIndex const& lhs, TileIndex const& rhs) +{ + return std::tie(lhs.x, lhs.z) < std::tie(rhs.x, rhs.z); +} + +bool TileIndex::is_valid() const +{ + // x and z are unsigned so negative signed int value are positive and > 63 + return x < 64 && z < 64; +} + +float TileIndex::dist(TileIndex const& other) const +{ + return glm::distance(glm::vec3(x, 0.f, z), glm::vec3(other.x, 0.f, other.z)); +} diff --git a/src/noggit/TileIndex.hpp b/src/noggit/TileIndex.hpp index fdb15552..10e00bb8 100755 --- a/src/noggit/TileIndex.hpp +++ b/src/noggit/TileIndex.hpp @@ -1,33 +1,21 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). #pragma once -#include + +#include struct TileIndex { - TileIndex(const glm::vec3& pos) : TileIndex(std::floor(pos.x / TILESIZE), std::floor(pos.z / TILESIZE)) { } - TileIndex(std::size_t tileX, std::size_t tileZ) : x(tileX), z(tileZ) { } + TileIndex(const glm::vec3& pos); + TileIndex(std::size_t tileX, std::size_t tileZ); - friend bool operator== (TileIndex const& lhs, TileIndex const& rhs) - { - return std::tie (lhs.x, lhs.z) == std::tie (rhs.x, rhs.z); - } + friend bool operator== (TileIndex const& lhs, TileIndex const& rhs); - friend bool operator< (TileIndex const& lhs, TileIndex const& rhs) - { - return std::tie (lhs.x, lhs.z) < std::tie (rhs.x, rhs.z); - } + friend bool operator< (TileIndex const& lhs, TileIndex const& rhs); - bool is_valid() const - { - // x and z are unsigned so negative signed int value are positive and > 63 - return x < 64 && z < 64; - } + bool is_valid() const; - float dist(TileIndex const& other) const - { - return glm::distance(glm::vec3(x, 0.f, z), glm::vec3(other.x, 0.f, other.z)); - } + float dist(TileIndex const& other) const; std::size_t x; std::size_t z; diff --git a/src/noggit/TileWater.cpp b/src/noggit/TileWater.cpp index c0d6ae5f..44b1d6c9 100755 --- a/src/noggit/TileWater.cpp +++ b/src/noggit/TileWater.cpp @@ -1,16 +1,13 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). #include -#include +#include +#include #include #include #include -#include -#include -#include #include -#include #include TileWater::TileWater(MapTile *pTile, float pXbase, float pZbase, bool use_mclq_green_lava) @@ -118,6 +115,11 @@ bool TileWater::hasData(size_t layer) return false; } +bool TileWater::hasData() const +{ + return _has_data; +} + void TileWater::CropMiniChunk(int x, int z, MapChunk* chunkTerrain) { chunks[z][x]->CropWater(chunkTerrain); @@ -149,6 +151,15 @@ int TileWater::getType(size_t layer) return 0; } +std::array& TileWater::getExtents() +{ + if (needsUpdate()) + { + recalcExtents(); + } + return _extents; +} + void TileWater::recalcExtents() { _extents = {glm::vec3{xbase, std::numeric_limits::max(), zbase}, @@ -356,3 +367,24 @@ void TileWater::setWatermapImage(QImage const& baseimage, float min_height, floa // } // } } + +void TileWater::tagExtents(bool state) +{ + _extents_changed = state; +} + +void TileWater::tagUpdate() +{ + _renderer.tagUpdate(); +} + +Noggit::Rendering::LiquidRender* TileWater::renderer() +{ + return &_renderer; +} + +[[nodiscard]] +bool TileWater::needsUpdate() +{ + return _renderer.needsUpdate() || _extents_changed; +} diff --git a/src/noggit/TileWater.hpp b/src/noggit/TileWater.hpp index 19f4939f..02639d78 100755 --- a/src/noggit/TileWater.hpp +++ b/src/noggit/TileWater.hpp @@ -1,19 +1,13 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). #pragma once #include -#include -#include -#include -#include -#include #include #include #include -#include #include -#include +class MapChunk; class MapTile; class liquid_layer; enum LiquidLayerUpdateFlags; @@ -23,12 +17,6 @@ namespace BlizzardArchive class ClientFile; } -namespace Noggit::Rendering -{ - class LiquidRender; -} - - class TileWater { friend class Noggit::Rendering::LiquidRender; @@ -41,17 +29,17 @@ public: void readFromFile(BlizzardArchive::ClientFile& theFile, size_t basePos); void saveToFile(util::sExtendableArray& lADTFile, int& lMHDR_Position, int& lCurrentPosition); - void draw ( math::frustum const& frustum - , const glm::vec3& camera - , bool camera_moved - , OpenGL::Scoped::use_program& water_shader - , int animtime - , int layer - , display_mode display - , Noggit::Rendering::LiquidTextureManager* tex_manager - ); + // void draw ( math::frustum const& frustum + // , const glm::vec3& camera + // , bool camera_moved + // , OpenGL::Scoped::use_program& water_shader + // , int animtime + // , int layer + // , display_mode display + // , Noggit::Rendering::LiquidTextureManager* tex_manager + // ); bool hasData(size_t layer); - bool hasData() { return _has_data; }; + bool hasData() const;; void CropMiniChunk(int x, int z, MapChunk* chunkTerrain); @@ -62,20 +50,20 @@ public: void setType(int type, size_t layer); int getType(size_t layer); - std::array& getExtents() { if (needsUpdate()){ recalcExtents(); } return _extents; }; + std::array& getExtents();; [[nodiscard]] bool isVisible(const math::frustum& frustum) const; void setWatermapImage(QImage const& baseimage, float min_height, float max_height, int mode, bool tiledEdges); - void tagExtents(bool state) { _extents_changed = state; }; - void tagUpdate() { _renderer.tagUpdate(); }; + void tagExtents(bool state);; + void tagUpdate();; - Noggit::Rendering::LiquidRender* renderer() { return &_renderer; }; + Noggit::Rendering::LiquidRender* renderer();; [[nodiscard]] - bool needsUpdate() { return _renderer.needsUpdate() || _extents_changed; }; + bool needsUpdate();; void recalcExtents(); diff --git a/src/noggit/Tool.cpp b/src/noggit/Tool.cpp index 853f96b1..02292668 100644 --- a/src/noggit/Tool.cpp +++ b/src/noggit/Tool.cpp @@ -1,14 +1,15 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). #include "Tool.hpp" -#include -#include #include -#include +#include +#include #include -#include +#include #include +#include +#include namespace Noggit { diff --git a/src/noggit/Tool.hpp b/src/noggit/Tool.hpp index 6ad49667..def9f4fe 100644 --- a/src/noggit/Tool.hpp +++ b/src/noggit/Tool.hpp @@ -16,7 +16,6 @@ #include #include -#include #include #include diff --git a/src/noggit/WMO.cpp b/src/noggit/WMO.cpp index 46af4ba7..7558b5fe 100755 --- a/src/noggit/WMO.cpp +++ b/src/noggit/WMO.cpp @@ -1,19 +1,19 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). +#include #include -#include +#include +#include #include // LogDebug +#include +#include #include // ModelManager #include // TextureManager, Texture #include -#include -#include -#include -#include +#include #include #include -#include #include #include #include @@ -27,6 +27,10 @@ WMO::WMO(BlizzardArchive::Listfile::FileKey const& file_key, Noggit::NoggitRende { } +WMO::~WMO() +{ +} + void WMO::finishLoading () { BlizzardArchive::ClientFile f(_file_key.filepath(), Noggit::Application::NoggitApplication::instance()->clientData()); @@ -390,8 +394,6 @@ std::vector WMO::intersect (math::ray const& ray, bool do_exterior) const return results; } - - std::map> WMO::doodads_per_group(uint16_t doodadset) const { std::map> doodads; @@ -419,6 +421,39 @@ std::map> WMO::doodads_per_group(uint return doodads; } +[[nodiscard]] +bool WMO::is_hidden() const +{ + return _hidden; +} + +void WMO::toggle_visibility() +{ + _hidden = !_hidden; +} + +void WMO::show() +{ + _hidden = false; +} + +void WMO::hide() +{ + _hidden = true; +} + +[[nodiscard]] +bool WMO::is_required_when_saving() const +{ + return true; +} + +[[nodiscard]] +Noggit::Rendering::WMORender* WMO::renderer() +{ + return &_renderer; +} + void WMOLight::init(BlizzardArchive::ClientFile* f) { char type[4]; @@ -1115,6 +1150,29 @@ bool WMOGroup::is_visible( glm::mat4x4 const& transform return true; } +[[nodiscard]] +std::vector WMOGroup::doodad_ref() const +{ + return _doodad_ref; +} + +[[nodiscard]] +bool WMOGroup::has_skybox() const +{ + return header.flags.skybox; +} + +[[nodiscard]] +bool WMOGroup::is_indoor() const +{ + return header.flags.indoor; +} + +[[nodiscard]] +Noggit::Rendering::WMOGroupRender* WMOGroup::renderer() +{ + return &_renderer; +} void WMOGroup::intersect (math::ray const& ray, std::vector* results) const { @@ -1223,4 +1281,29 @@ void WMOManager::unload_all(Noggit::NoggitRenderContext context) } , context ); -} \ No newline at end of file +} + +bool wmo_triangle_material_info::isTransFace() const +{ + return flags.flag_0x01 && (flags.detail || flags.render); +} + +bool wmo_triangle_material_info::isColor() const +{ + return !flags.collision; +} + +bool wmo_triangle_material_info::isRenderFace() const +{ + return flags.render && !flags.detail; +} + +bool wmo_triangle_material_info::isCollidable() const +{ + return flags.collision || isRenderFace(); +} + +bool wmo_triangle_material_info::isCollision() const +{ + return texture == 0xff; +} diff --git a/src/noggit/WMO.h b/src/noggit/WMO.h index eb72a003..e3c2b03c 100755 --- a/src/noggit/WMO.h +++ b/src/noggit/WMO.h @@ -1,40 +1,38 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). #pragma once -#include -#include // ModelInstance -#include #include -#include -#include -#include #include +#include #include #include -#include -#include -#include +#include +#include +#include #include -#include +#include #include #include #include -#include +struct scoped_blp_texture_reference; class WMO; +class wmo_doodad_instance; class WMOGroup; class WMOInstance; class WMOManager; -class wmo_liquid; class Model; -namespace Noggit::Rendering +namespace math { - class WMOGroupRender; - class WMORender; + struct ray; } +namespace BlizzardArchive +{ + class ClientFile; +} struct wmo_batch { @@ -74,12 +72,12 @@ struct wmo_triangle_material_info wmo_mopy_flags flags; uint8_t texture; - bool isTransFace() { return flags.flag_0x01 && (flags.detail || flags.render); } - bool isColor() { return !flags.collision; } - bool isRenderFace() { return flags.render && !flags.detail; } - bool isCollidable() { return flags.collision || isRenderFace(); } + bool isTransFace() const; + bool isColor() const; + bool isRenderFace() const; + bool isCollidable() const; - bool isCollision() { return texture == 0xff; } + bool isCollision() const; }; enum wmo_mobn_flags @@ -194,7 +192,7 @@ public: ) const; [[nodiscard]] - std::vector doodad_ref() const { return _doodad_ref; } + std::vector doodad_ref() const; glm::vec3 BoundingBoxMin; glm::vec3 BoundingBoxMax; @@ -205,13 +203,13 @@ public: std::string name; [[nodiscard]] - bool has_skybox() const { return header.flags.skybox; } + bool has_skybox() const; [[nodiscard]] - bool is_indoor() const { return header.flags.indoor; } + bool is_indoor() const; [[nodiscard]] - Noggit::Rendering::WMOGroupRender* renderer() { return &_renderer; }; + Noggit::Rendering::WMOGroupRender* renderer();; ::glm::vec3 center; private: @@ -308,6 +306,7 @@ class WMO : public AsyncObject public: explicit WMO(BlizzardArchive::Listfile::FileKey const& file_key, Noggit::NoggitRenderContext context ); + ~WMO(); [[nodiscard]] std::vector intersect (math::ray const&, bool do_exterior = true) const; @@ -343,21 +342,18 @@ public: Noggit::NoggitRenderContext _context; [[nodiscard]] - bool is_hidden() const { return _hidden; } + bool is_hidden() const; - void toggle_visibility() { _hidden = !_hidden; } - void show() { _hidden = false ; } - void hide() { _hidden = true; } + void toggle_visibility(); + void show(); + void hide(); [[nodiscard]] - bool is_required_when_saving() const override - { - return true; - } + bool is_required_when_saving() const override; [[nodiscard]] - Noggit::Rendering::WMORender* renderer() { return &_renderer; } + Noggit::Rendering::WMORender* renderer(); private: bool _hidden = false; diff --git a/src/noggit/WMOInstance.cpp b/src/noggit/WMOInstance.cpp index b9aa11c2..b1ed62d2 100755 --- a/src/noggit/WMOInstance.cpp +++ b/src/noggit/WMOInstance.cpp @@ -1,16 +1,21 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). -#include -#include -#include -#include // checkinside -#include -#include // WMO -#include -#include -#include -#include +#include #include +#include +#include +#include +#include +#include +#include +#include // WMO +#include + +#include + +#include +#include +#include #include @@ -62,6 +67,48 @@ WMOInstance::WMOInstance(BlizzardArchive::Listfile::FileKey const& file_key, Nog updateTransformMatrix(); } +WMOInstance::WMOInstance(WMOInstance&& other) noexcept + : SceneObject(other._type, other._context) + , wmo(std::move(other.wmo)) + , group_extents(other.group_extents) + , mFlags(other.mFlags) + , mNameset(other.mNameset) + , _doodadset(other._doodadset) + , _doodads_per_group(other._doodads_per_group) + , _need_doodadset_update(other._need_doodadset_update) + , _need_recalc_extents(other._need_recalc_extents) +{ + std::swap(extents, other.extents); + pos = other.pos; + scale = other.scale; + dir = other.dir; + _context = other._context; + uid = other.uid; + + _transform_mat = other._transform_mat; + _transform_mat_inverted = other._transform_mat_inverted; +} + +WMOInstance& WMOInstance::operator= (WMOInstance&& other) noexcept +{ + std::swap(wmo, other.wmo); + std::swap(pos, other.pos); + std::swap(extents, other.extents); + std::swap(group_extents, other.group_extents); + std::swap(dir, other.dir); + std::swap(uid, other.uid); + std::swap(scale, other.scale); + std::swap(mFlags, other.mFlags); + std::swap(mNameset, other.mNameset); + std::swap(_doodadset, other._doodadset); + std::swap(_doodads_per_group, other._doodads_per_group); + std::swap(_need_doodadset_update, other._need_doodadset_update); + std::swap(_transform_mat, other._transform_mat); + std::swap(_transform_mat_inverted, other._transform_mat_inverted); + std::swap(_context, other._context); + std::swap(_need_recalc_extents, other._need_recalc_extents); + return *this; +} void WMOInstance::draw ( OpenGL::Scoped::use_program& wmo_shader , const glm::mat4x4 const& model_view @@ -196,6 +243,12 @@ std::array WMOInstance::getBoundingBox() return relative_to_model.rotated_corners(_transform_mat, true); } +// not axis aligned +bool WMOInstance::extentsDirty() const +{ + return _need_recalc_extents || !wmo->finishedLoading(); +} + void WMOInstance::ensureExtents() { if ( (_need_recalc_extents || _update_group_extents) && wmo->finishedLoading()) @@ -204,6 +257,11 @@ void WMOInstance::ensureExtents() } } +bool WMOInstance::finishedLoading() +{ + return wmo->finishedLoading(); +} + void WMOInstance::updateDetails(Noggit::Ui::detail_infos* detail_widget) { std::stringstream select_info; @@ -247,6 +305,12 @@ void WMOInstance::updateDetails(Noggit::Ui::detail_infos* detail_widget) detail_widget->setText(select_info.str()); } +[[nodiscard]] +AsyncObject* WMOInstance::instance_model() const +{ + return wmo.get(); +} + void WMOInstance::recalcExtents() { // keep the old extents since they are saved in the adt @@ -311,6 +375,11 @@ void WMOInstance::change_nameset(uint16_t name_set) mNameset = name_set; } +uint16_t WMOInstance::doodadset() const +{ + return _doodadset; +} + void WMOInstance::change_doodadset(uint16_t doodad_set) { if (!wmo->finishedLoading()) @@ -333,6 +402,14 @@ void WMOInstance::change_doodadset(uint16_t doodad_set) update_doodads(); } +[[nodiscard]] +std::map> const& WMOInstance::getGroupExtents() +{ + _update_group_extents = true; + ensureExtents(); + return group_extents; +} + void WMOInstance::update_doodads() { for (auto& group_doodads : _doodads_per_group) diff --git a/src/noggit/WMOInstance.h b/src/noggit/WMOInstance.h index ab32d9fe..55991ea3 100755 --- a/src/noggit/WMOInstance.h +++ b/src/noggit/WMOInstance.h @@ -1,12 +1,15 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). #pragma once #include -#include #include #include #include -#include + +namespace math +{ + struct ray; +} struct ENTRY_MODF; @@ -18,11 +21,11 @@ public: uint16_t mFlags; uint16_t mNameset; - uint16_t doodadset() const { return _doodadset; } + uint16_t doodadset() const; void change_doodadset(uint16_t doodad_set); [[nodiscard]] - std::map> const& getGroupExtents() { _update_group_extents = true; ensureExtents(); return group_extents; } + std::map> const& getGroupExtents(); private: void update_doodads(); @@ -42,52 +45,13 @@ public: WMOInstance(WMOInstance const& other) = default; WMOInstance& operator=(WMOInstance const& other) = default; - WMOInstance (WMOInstance&& other) - : SceneObject(other._type, other._context) - , wmo (std::move (other.wmo)) - , group_extents(other.group_extents) - , mFlags (other.mFlags) - , mNameset (other.mNameset) - , _doodadset (other._doodadset) - , _doodads_per_group(other._doodads_per_group) - , _need_doodadset_update(other._need_doodadset_update) - , _need_recalc_extents(other._need_recalc_extents) - { - std::swap (extents, other.extents); - pos = other.pos; - scale = other.scale; - dir = other.dir; - _context = other._context; - uid = other.uid; + WMOInstance (WMOInstance&& other) noexcept; - _transform_mat = other._transform_mat; - _transform_mat_inverted = other._transform_mat_inverted; - } - - WMOInstance& operator= (WMOInstance&& other) - { - std::swap(wmo, other.wmo); - std::swap(pos, other.pos); - std::swap(extents, other.extents); - std::swap(group_extents, other.group_extents); - std::swap(dir, other.dir); - std::swap(uid, other.uid); - std::swap(scale, other.scale); - std::swap(mFlags, other.mFlags); - std::swap(mNameset, other.mNameset); - std::swap(_doodadset, other._doodadset); - std::swap(_doodads_per_group, other._doodads_per_group); - std::swap(_need_doodadset_update, other._need_doodadset_update); - std::swap(_transform_mat, other._transform_mat); - std::swap(_transform_mat_inverted, other._transform_mat_inverted); - std::swap(_context, other._context); - std::swap(_need_recalc_extents, other._need_recalc_extents); - return *this; - } + WMOInstance& operator= (WMOInstance&& other) noexcept; void draw ( OpenGL::Scoped::use_program& wmo_shader - , const glm::mat4x4 const& model_view - , const glm::mat4x4 const& projection + , glm::mat4x4 const& model_view + , glm::mat4x4 const& projection , math::frustum const& frustum , const float& cull_distance , const glm::vec3& camera @@ -109,15 +73,15 @@ public: std::array const& getExtents() override; // axis aligned std::array const& getLocalExtents() const; std::array getBoundingBox() override; // not axis aligned - inline bool extentsDirty() const { return _need_recalc_extents || !wmo->finishedLoading(); }; + bool extentsDirty() const;; void recalcExtents() override; void change_nameset(uint16_t name_set); void ensureExtents() override; - bool finishedLoading() override { return wmo->finishedLoading(); }; + bool finishedLoading() override;; virtual void updateDetails(Noggit::Ui::detail_infos* detail_widget) override; [[nodiscard]] - AsyncObject* instance_model() const override { return wmo.get(); }; + AsyncObject* instance_model() const override;; std::vector get_visible_doodads( math::frustum const& frustum , float const& cull_distance diff --git a/src/noggit/World.cpp b/src/noggit/World.cpp index 9b0dee2c..ed838609 100644 --- a/src/noggit/World.cpp +++ b/src/noggit/World.cpp @@ -1,46 +1,51 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). -#include -#include - #include -#include +#include +#include #include // brush -#include +#include #include #include #include #include +#include +#include #include // ModelManager -#include -#include // WMOInstance -#include -#include -#include -#include -#include +#include #include -#include +#include +#include +#include +#include +#include +#include // WMOInstance +#include +#include + #include +#include + #include -#include + +#include +#include #include +#include +#include +#include + +#include + #include +#include #include -#include -#include +#include #include #include #include #include -#include -#include -#include -#include - -#include -#include bool World::IsEditableWorld(BlizzardDatabaseLib::Structures::BlizzardDatabaseRow& record) @@ -153,6 +158,11 @@ void World::saveSelectionGroups() Noggit::Project::CurrentProject::get()->saveObjectSelectionGroups(proj_selection_map_group); } +Noggit::Rendering::WorldRender* World::renderer() +{ + return &_renderer; +} + void World::update_selection_pivot() { ZoneScoped; @@ -178,6 +188,11 @@ void World::update_selection_pivot() } } +std::optional const& World::multi_select_pivot() const +{ + return _multi_select_pivot; +} + bool World::is_selected(selection_type selection) { ZoneScoped; @@ -254,6 +269,11 @@ bool World::is_selected(std::uint32_t uid) const return selected_uids.contains(uid); } +std::vector const& World::current_selection() const +{ + return _current_selection; +} + std::optional World::get_last_selected_model() const { ZoneScoped; @@ -274,6 +294,21 @@ std::optional World::get_last_selected_model() const ? std::optional() : std::optional (*it); } +bool World::has_selection() const +{ + return !_current_selection.empty(); +} + +bool World::has_multiple_model_selected() const +{ + return _selected_model_count > 1; +} + +int World::get_selected_model_count() const +{ + return _selected_model_count; +} + std::vector const World::get_selected_objects() const { // std::vector objects(_selected_model_count); @@ -915,6 +950,16 @@ void World::move_model(selection_type entry, float dx, float dy, float dz) } +void World::move_selected_models(glm::vec3 const& delta) +{ + move_selected_models(delta.x, delta.y, delta.z); +} + +void World::set_selected_models_pos(float x, float y, float z, bool change_height) +{ + return set_selected_models_pos({ x,y,z }, change_height); +} + void World::set_selected_models_pos(glm::vec3 const& pos, bool change_height) { ZoneScoped; @@ -1287,6 +1332,11 @@ void World::setAreaID(glm::vec3 const& pos, int id, bool adt, float radius) } } +Noggit::NoggitRenderContext World::getRenderContext() const +{ + return _context; +} + bool World::GetVertex(float x, float z, glm::vec3 *V) const { ZoneScoped; @@ -1851,6 +1901,21 @@ void World::loadAllTiles(glm::vec3& camera_pos) } } +unsigned World::getNumLoadedTiles() const +{ + return _n_loaded_tiles; +} + +unsigned World::getNumRenderedTiles() const +{ + return _n_rendered_tiles; +} + +unsigned World::getNumRenderedObjects() const +{ + return _n_rendered_objects; +} + void World::convert_alphamap(QProgressDialog* progress_dialog, bool to_big_alpha) { ZoneScoped; @@ -3181,6 +3246,11 @@ void World::range_add_to_selection(glm::vec3 const& pos, float radius, bool remo update_selection_pivot(); } +Noggit::world_model_instances_storage& World::getModelInstanceStorage() +{ + return _model_instance_storage; +} + float World::getMaxTileHeight(const TileIndex& tile) { ZoneScoped; diff --git a/src/noggit/World.h b/src/noggit/World.h index 2804ba55..c3e82ca2 100644 --- a/src/noggit/World.h +++ b/src/noggit/World.h @@ -2,51 +2,39 @@ #pragma once -#include #include -#include -#include -#include // ModelManager #include -#include // Skies, OutdoorLighting, OutdoorLightStats -#include // WMOManager #include #include -#include -#include #include #include -#include #include -#include -#include -#include -#include #include -#include -#include #include #include #include #include -#include #include -#include namespace Noggit { struct object_paste_params; struct VertexSelectionCache; - - namespace Rendering - { - class WorldRender; - } } +namespace BlizzardDatabaseLib::Structures +{ + struct BlizzardDatabaseRow; +} + +struct TileIndex; +struct flatten_mode; + class Brush; class MapTile; class QPixmap; +class QProgressDialog; +class QSettings; static const float detail_size = 8.0f; @@ -97,7 +85,7 @@ public: unsigned int getAreaID (glm::vec3 const&); void setAreaID(glm::vec3 const& pos, int id, bool adt, float radius = -1.0f); - Noggit::NoggitRenderContext getRenderContext() { return _context; }; + Noggit::NoggitRenderContext getRenderContext() const;; selection_result intersect (glm::mat4x4 const& model_view , math::ray const& @@ -121,20 +109,20 @@ protected: std::optional _multi_select_pivot; public: - Noggit::Rendering::WorldRender* renderer() { return &_renderer; } + Noggit::Rendering::WorldRender* renderer(); void update_selection_pivot(); - std::optional const& multi_select_pivot() const { return _multi_select_pivot; } + std::optional const& multi_select_pivot() const; // Selection related methods. bool is_selected(selection_type selection); bool is_selected(std::uint32_t uid) const; - std::vector const& current_selection() const { return _current_selection; } + std::vector const& current_selection() const; std::vector const get_selected_objects() const; std::optional get_last_selected_model() const; - bool has_selection() const { return !_current_selection.empty(); } - bool has_multiple_model_selected() const { return _selected_model_count > 1; } - int get_selected_model_count() const { return _selected_model_count; } + bool has_selection() const; + bool has_multiple_model_selected() const; + int get_selected_model_count() const; // Unused in Red, models are now iterated by adt because of the occlusion check // std::unordered_map> get_models_by_filename() const& { return _models_by_filename; } void set_current_selection(selection_type entry); @@ -145,7 +133,7 @@ public: void delete_selected_models(); glm::vec3 get_ground_height(glm::vec3 pos); void range_add_to_selection(glm::vec3 const& pos, float radius, bool remove); - Noggit::world_model_instances_storage& getModelInstanceStorage() { return _model_instance_storage; }; + Noggit::world_model_instances_storage& getModelInstanceStorage();; enum class object_scaling_type { @@ -158,14 +146,8 @@ public: void scale_selected_models(float v, object_scaling_type type); void move_selected_models(float dx, float dy, float dz); void move_model(selection_type entry, float dx, float dy, float dz); - void move_selected_models(glm::vec3 const& delta) - { - move_selected_models(delta.x, delta.y, delta.z); - } - void set_selected_models_pos(float x, float y, float z, bool change_height = true) - { - return set_selected_models_pos({x,y,z}, change_height); - } + void move_selected_models(glm::vec3 const& delta); + void set_selected_models_pos(float x, float y, float z, bool change_height = true); void set_selected_models_pos(glm::vec3 const& pos, bool change_height = true); void set_model_pos(selection_type entry, glm::vec3 const& pos, bool change_height = true); void rotate_selected_models(math::degrees rx, math::degrees ry, math::degrees rz, bool use_pivot); @@ -395,9 +377,9 @@ public: bool need_model_updates = false; void loadAllTiles(glm::vec3& camera_pos); - unsigned getNumLoadedTiles() const { return _n_loaded_tiles; }; - unsigned getNumRenderedTiles() const { return _n_rendered_tiles; }; - unsigned getNumRenderedObjects() const { return _n_rendered_objects; }; + unsigned getNumLoadedTiles() const;; + unsigned getNumRenderedTiles() const;; + unsigned getNumRenderedObjects() const;; void select_objects_in_area( const std::array& selection_box, diff --git a/src/noggit/World.inl b/src/noggit/World.inl index aa258ee1..ade721cf 100755 --- a/src/noggit/World.inl +++ b/src/noggit/World.inl @@ -4,6 +4,7 @@ #define NOGGIT_WORLD_INL #include "World.h" +#include "MapTile.h" #include template diff --git a/src/noggit/application/ApplicationEntry.cpp b/src/noggit/application/ApplicationEntry.cpp index c310fa8e..a6a93562 100755 --- a/src/noggit/application/ApplicationEntry.cpp +++ b/src/noggit/application/ApplicationEntry.cpp @@ -1,23 +1,14 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). +#include #include -#include #include -#include -#include -#include -#include -#include -#include -#include -#include +#include + #include +#include +#include #include #include -#include -#include -#include -#include -#include QCommandLineParser* ProcessCommandLine() { @@ -64,4 +55,4 @@ int main(int argc, char *argv[]) // project_selection->show(); return q_application.exec(); -} \ No newline at end of file +} diff --git a/src/noggit/application/Configuration/NoggitApplicationConfigurationReader.cpp b/src/noggit/application/Configuration/NoggitApplicationConfigurationReader.cpp index 30704fa1..f8da94fb 100755 --- a/src/noggit/application/Configuration/NoggitApplicationConfigurationReader.cpp +++ b/src/noggit/application/Configuration/NoggitApplicationConfigurationReader.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -83,4 +84,4 @@ namespace Noggit::Application { return noggitApplicationConfiguration; } -} \ No newline at end of file +} diff --git a/src/noggit/application/Configuration/NoggitApplicationConfigurationReader.hpp b/src/noggit/application/Configuration/NoggitApplicationConfigurationReader.hpp index 4950c827..01cf0cd6 100755 --- a/src/noggit/application/Configuration/NoggitApplicationConfigurationReader.hpp +++ b/src/noggit/application/Configuration/NoggitApplicationConfigurationReader.hpp @@ -2,7 +2,8 @@ #define NOGGIT_APPLICATION_CONFIGURATION_READER_HPP #include -#include + +class QFile; namespace Noggit::Application { @@ -12,4 +13,4 @@ namespace Noggit::Application { NoggitApplicationConfiguration ReadConfigurationState(QFile& inputFile); }; } -#endif //NOGGIT_APPLICATION_CONFIGURATION_READER_HPP \ No newline at end of file +#endif //NOGGIT_APPLICATION_CONFIGURATION_READER_HPP diff --git a/src/noggit/application/Configuration/NoggitApplicationConfigurationWriter.cpp b/src/noggit/application/Configuration/NoggitApplicationConfigurationWriter.cpp index b753171d..7a598604 100755 --- a/src/noggit/application/Configuration/NoggitApplicationConfigurationWriter.cpp +++ b/src/noggit/application/Configuration/NoggitApplicationConfigurationWriter.cpp @@ -1,4 +1,6 @@ #include +#include +#include #include #include @@ -6,7 +8,7 @@ namespace Noggit::Application { void NoggitApplicationConfigurationWriter::PersistDefaultConfigurationState(QFile& outputFile) { - auto noggitApplicationConfiguration = NoggitApplicationConfiguration(); + NoggitApplicationConfiguration noggitApplicationConfiguration; noggitApplicationConfiguration.ApplicationProjectPath = std::string("projects"); noggitApplicationConfiguration.ApplicationThemePath = std::string("themes"); noggitApplicationConfiguration.ApplicationDatabaseDefinitionsPath = std::string("definitions"); @@ -67,4 +69,4 @@ namespace Noggit::Application { outputFile.write(document.toJson(QJsonDocument::Indented)); }; -} \ No newline at end of file +} diff --git a/src/noggit/application/Configuration/NoggitApplicationConfigurationWriter.hpp b/src/noggit/application/Configuration/NoggitApplicationConfigurationWriter.hpp index e273c52c..c293a8f8 100755 --- a/src/noggit/application/Configuration/NoggitApplicationConfigurationWriter.hpp +++ b/src/noggit/application/Configuration/NoggitApplicationConfigurationWriter.hpp @@ -1,10 +1,10 @@ -#ifndef NOGGIT_APPLICATION_CONFIGURATION_WRITER_HPP -#define NOGGIT_APPLICATION_CONFIGURATION_WRITER_HPP +#pragma once -#include -#include +class QFile; -namespace Noggit::Application { +namespace Noggit::Application +{ + struct NoggitApplicationConfiguration; class NoggitApplicationConfigurationWriter { @@ -13,4 +13,3 @@ namespace Noggit::Application { void PersistConfigurationState(QFile& outputFile, const NoggitApplicationConfiguration& configuration); }; } -#endif // NOGGIT_APPLICATION_CONFIGURATION_WRITER_HPP \ No newline at end of file diff --git a/src/noggit/application/NoggitApplication.cpp b/src/noggit/application/NoggitApplication.cpp index 53222463..002f7bba 100755 --- a/src/noggit/application/NoggitApplication.cpp +++ b/src/noggit/application/NoggitApplication.cpp @@ -1,14 +1,28 @@ #include -#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include #include -#include #include +#include +#include #include +#include +#include #include #include +#include +#include +#include namespace { @@ -33,6 +47,27 @@ namespace namespace Noggit::Application { + NoggitApplication* NoggitApplication::instance() + { + static NoggitApplication inst{}; + return &inst; + } + + BlizzardArchive::ClientData* NoggitApplication::clientData() + { + return _client_data.get(); + } + + bool NoggitApplication::hasClientData() const + { + return _client_data != nullptr; + } + + void NoggitApplication::setClientData(std::shared_ptr data) + { + _client_data = data; + } + bool NoggitApplication::initalize(int argc, char* argv[], std::vector Parser) { InitLogging(); diff --git a/src/noggit/application/NoggitApplication.hpp b/src/noggit/application/NoggitApplication.hpp index bd4463be..f9313806 100755 --- a/src/noggit/application/NoggitApplication.hpp +++ b/src/noggit/application/NoggitApplication.hpp @@ -5,68 +5,44 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include #include +#include + #include -#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -namespace Noggit::Ui::Windows +namespace BlizzardArchive { - class NoggitProjectSelectionWindow; + class ClientData; } -namespace Noggit::Application { +namespace Noggit::Application +{ + class NoggitApplication + { + public: + static NoggitApplication* instance(); - class NoggitApplication - { - public: - static NoggitApplication* instance() - { - static NoggitApplication inst{}; - return &inst; - } + BlizzardArchive::ClientData* clientData(); + bool hasClientData() const; + void setClientData(std::shared_ptr data); - BlizzardArchive::ClientData* clientData() { return _client_data.get(); } - bool hasClientData() const { return _client_data != nullptr; } - void setClientData(std::shared_ptr data) { _client_data = data; } + bool initalize(int argc, char* argv[], std::vector Parser); + std::shared_ptr getConfiguration(); + static void terminationHandler(); + bool GetCommand(int index); - bool initalize(int argc, char* argv[], std::vector Parser); - std::shared_ptr getConfiguration(); - static void terminationHandler(); - bool GetCommand(int index); + protected: + std::vector Command; - protected: - std::vector Command; + private: + NoggitApplication() = default; - private: - NoggitApplication() = default; - - std::shared_ptr _application_configuration; - std::unique_ptr _project_selection_page; - std::shared_ptr _client_data; - - }; + std::shared_ptr _application_configuration; + std::unique_ptr _project_selection_page; + std::shared_ptr _client_data; + }; } -#endif //NOGGIT_APPLICATION_HPP \ No newline at end of file +#endif //NOGGIT_APPLICATION_HPP diff --git a/src/noggit/application/Utils.hpp b/src/noggit/application/Utils.hpp index 50e778da..b953d2f3 100755 --- a/src/noggit/application/Utils.hpp +++ b/src/noggit/application/Utils.hpp @@ -4,6 +4,8 @@ #define NOGGIT_UTILS_HPP #include + +#include #include diff --git a/src/noggit/area_trigger.cpp b/src/noggit/area_trigger.cpp index 1715dc95..745d0af7 100644 --- a/src/noggit/area_trigger.cpp +++ b/src/noggit/area_trigger.cpp @@ -4,6 +4,8 @@ #include +#include + #include namespace Noggit diff --git a/src/noggit/async_priority.hpp b/src/noggit/async_priority.hpp new file mode 100644 index 00000000..07a86b28 --- /dev/null +++ b/src/noggit/async_priority.hpp @@ -0,0 +1,11 @@ +// This file is part of Noggit3, licensed under GNU General Public License (version 3). + +#pragma once + +enum class async_priority : int +{ + high, + medium, + low, + count +}; diff --git a/src/noggit/liquid_layer.cpp b/src/noggit/liquid_layer.cpp index fb199476..d51b74bc 100755 --- a/src/noggit/liquid_layer.cpp +++ b/src/noggit/liquid_layer.cpp @@ -5,8 +5,11 @@ #include #include #include +#include #include +#include + #include #include @@ -492,6 +495,31 @@ void liquid_layer::update_underground_vertices_depth(MapChunk* chunk) } } +std::array& liquid_layer::getVertices() +{ + return _vertices; +} + +float liquid_layer::min() const +{ + return _minimum; +} + +float liquid_layer::max() const +{ + return _maximum; +} + +int liquid_layer::liquidID() const +{ + return _liquid_id; +} + +int liquid_layer::mclq_liquid_type() const +{ + return _mclq_liquid_type; +} + bool liquid_layer::hasSubchunk(int x, int z, int size) const { for (int pz = z; pz < z + size; ++pz) @@ -512,6 +540,26 @@ void liquid_layer::setSubchunk(int x, int z, bool water) misc::set_bit(_subchunks, x, z, water); } +std::uint64_t liquid_layer::getSubchunks() +{ + return _subchunks; +} + +bool liquid_layer::empty() const +{ + return !_subchunks; +} + +bool liquid_layer::full() const +{ + return _subchunks == std::uint64_t(-1); +} + +void liquid_layer::clear() +{ + _subchunks = std::uint64_t(0); +} + void liquid_layer::paintLiquid( glm::vec3 const& cursor_pos , float radius , bool add @@ -615,6 +663,16 @@ void liquid_layer::copy_subchunk_height(int x, int z, liquid_layer const& from) setSubchunk(x, z, true); } +ChunkWater* liquid_layer::getChunk() +{ + return _chunk; +} + +bool liquid_layer::has_fatigue() const +{ + return _fatigue_enabled; +} + void liquid_layer::update_vertex_opacity(int x, int z, MapChunk* chunk, float factor) { const int index = z * 9 + x; @@ -763,3 +821,4 @@ bool liquid_layer::subchunk_at_max_depth(int x, int z) const return true; } +liquid_layer::liquid_vertex::liquid_vertex(glm::vec3 const& pos, glm::vec2 const& uv, float depth) : position(pos), uv(uv), depth(depth) {} diff --git a/src/noggit/liquid_layer.hpp b/src/noggit/liquid_layer.hpp index 9d2c8334..514b0dd8 100755 --- a/src/noggit/liquid_layer.hpp +++ b/src/noggit/liquid_layer.hpp @@ -4,14 +4,17 @@ #include #include -#include #include #include -#include class MapChunk; class ChunkWater; +namespace util +{ + class sExtendableArray; +} + enum LiquidLayerUpdateFlags { ll_HEIGHT = 0x1, @@ -44,7 +47,7 @@ struct liquid_vertex float depth; liquid_vertex() = default; - liquid_vertex(glm::vec3 const& pos, glm::vec2 const& uv, float depth) : position(pos), uv(uv), depth(depth) {} + liquid_vertex(glm::vec3 const& pos, glm::vec2 const& uv, float depth); }; public: @@ -69,14 +72,14 @@ public: void update_opacity(MapChunk* chunk, float factor); void update_underground_vertices_depth(MapChunk* chunk); - std::array& getVertices() { return _vertices; }; + std::array& getVertices(); // std::array& getDepth() { return _depth; }; // std::array& getTexCoords() { return _tex_coords; }; - float min() const { return _minimum; } - float max() const { return _maximum; } - int liquidID() const { return _liquid_id; } - int mclq_liquid_type() const { return _mclq_liquid_type; } + float min() const; + float max() const; + int liquidID() const; + int mclq_liquid_type() const; // order of the flag corresponding to the liquid type in the mcnk header int mclq_flag_ordering() const; @@ -86,11 +89,11 @@ public: bool hasSubchunk(int x, int z, int size = 1) const; void setSubchunk(int x, int z, bool water); - std::uint64_t getSubchunks() { return _subchunks; }; + std::uint64_t getSubchunks(); - bool empty() const { return !_subchunks; } - bool full() const { return _subchunks == std::uint64_t(-1); } - void clear() { _subchunks = std::uint64_t(0); } + bool empty() const; + bool full() const; + void clear(); void paintLiquid(glm::vec3 const& pos , float radius @@ -106,9 +109,9 @@ public: void copy_subchunk_height(int x, int z, liquid_layer const& from); - ChunkWater* getChunk() { return _chunk; }; + ChunkWater* getChunk(); - bool has_fatigue() const { return _fatigue_enabled; } + bool has_fatigue() const; private: void create_vertices(float height); diff --git a/src/noggit/map_horizon.cpp b/src/noggit/map_horizon.cpp index 4a085687..988e83fa 100755 --- a/src/noggit/map_horizon.cpp +++ b/src/noggit/map_horizon.cpp @@ -2,16 +2,18 @@ #include "map_horizon.h" -#include #include +#include #include +#include #include #include + #include #include -#include #include +#include struct color { diff --git a/src/noggit/map_index.cpp b/src/noggit/map_index.cpp index 6137f269..ee390f88 100755 --- a/src/noggit/map_index.cpp +++ b/src/noggit/map_index.cpp @@ -24,7 +24,43 @@ #include #include -#include +#include + +MapIndex::TileRange MapIndex::loaded_tiles() +{ + return tiles + ([](TileIndex const&, MapTile* tile) { return !!tile && tile->finishedLoading(); }); +} + +MapIndex::TileRange MapIndex::tiles_in_range(glm::vec3 const& pos, float radius) +{ + return tiles + ([this, pos, radius](TileIndex const& index, MapTile*) + { + return hasTile(index) && misc::getShortestDist + (pos.x, pos.z, index.x * TILESIZE, index.z * TILESIZE, TILESIZE) <= radius; + } + ); +} + +MapIndex::TileRange MapIndex::tiles_in_rect(glm::vec3 const& pos, float radius) +{ + glm::vec2 l_chunk{ pos.x - radius, pos.z - radius }; + glm::vec2 r_chunk{ pos.x + radius, pos.z + radius }; + + return tiles + ([this, pos, radius, l_chunk, r_chunk](TileIndex const& index, MapTile*) + { + if (!hasTile(index) || radius == 0.f) + return false; + + glm::vec2 l_tile{ index.x * TILESIZE, index.z * TILESIZE }; + glm::vec2 r_tile{ index.x * TILESIZE + TILESIZE, index.z * TILESIZE + TILESIZE }; + + return ((l_chunk.x < r_tile.x) && (r_chunk.x >= l_tile.x) && (l_chunk.y < r_tile.y) && (r_chunk.y >= l_tile.y)); + } + ); +} MapIndex::MapIndex (const std::string &pBasename, int map_id, World* world, Noggit::NoggitRenderContext context, bool create_empty) @@ -626,6 +662,31 @@ void MapIndex::convert_alphamap(bool to_big_alpha) } } +bool MapIndex::hasBigAlpha() const +{ + return mBigAlpha; +} + +void MapIndex::setBigAlpha(bool state) +{ + mBigAlpha = state; +} + +unsigned MapIndex::getNLoadedTiles() const +{ + return _n_loaded_tiles; +} + +bool MapIndex::sort_models_by_size_class() const +{ + return _sort_models_by_size_class; +} + +void MapIndex::set_sort_models_by_size_class(bool state) +{ + _sort_models_by_size_class = state; +} + uint32_t MapIndex::getHighestGUIDFromFile(const std::string& pFilename) const { @@ -693,6 +754,24 @@ uint32_t MapIndex::getHighestGUIDFromFile(const std::string& pFilename) const return highGUID; } +// reloadable settings +void MapIndex::setLoadingRadius(int value) +{ + if (value < _unload_dist) + _loading_radius = value; +} + +void MapIndex::setUnloadDistance(int value) +{ + if (value > _loading_radius) + _unload_dist = value; +} + +void MapIndex::setUnloadInterval(int value) +{ + _unload_interval = value; +} + uint32_t MapIndex::newGUID() { std::unique_lock lock (_mutex); @@ -1248,6 +1327,17 @@ unsigned MapIndex::getNumExistingTiles() return _n_existing_tiles; } +// todo: find out how wow choose to use the green lava in outland +bool MapIndex::use_mclq_green_lava() const +{ + return _map_id == 530; +} + +bool MapIndex::uid_fix_all_in_progress() const +{ + return _uid_fix_all_in_progress; +} + void MapIndex::set_basename(const std::string &pBasename) { basename = pBasename; @@ -1380,3 +1470,14 @@ void MapIndex::create_empty_wdl() const f.save(); f.close(); } + +MapTileEntry::~MapTileEntry() +{ +} + +MapTileEntry::MapTileEntry() + : flags(0) + , tile(nullptr) + , onDisc(false) +{ +} diff --git a/src/noggit/map_index.hpp b/src/noggit/map_index.hpp index 216c091c..089daabb 100755 --- a/src/noggit/map_index.hpp +++ b/src/noggit/map_index.hpp @@ -4,21 +4,13 @@ #include #include -#include -#include +#include #include #include -#include -#include #include -#include -#include #include -#include #include -#include -#include enum class uid_fix_status @@ -28,18 +20,20 @@ enum class uid_fix_status failed }; +class MapTile; + /*! \brief This class is only a holder to have easier access to MapTiles and their flags for easier WDT parsing. This is private and for the class World only. */ class MapTileEntry { + ~MapTileEntry(); private: uint32_t flags; std::unique_ptr tile; bool onDisc; - - MapTileEntry() : flags(0), tile(nullptr), onDisc(false) {} + MapTileEntry(); friend class MapIndex; }; @@ -151,41 +145,11 @@ public: return TileRange(tile_iterator {this, { 0, 0 }, pred}, tile_iterator{}); } - auto loaded_tiles() - { - return tiles - ([] (TileIndex const&, MapTile* tile) { return !!tile && tile->finishedLoading(); }); - } + TileRange loaded_tiles(); - auto tiles_in_range (glm::vec3 const& pos, float radius) - { - return tiles - ( [this, pos, radius] (TileIndex const& index, MapTile*) - { - return hasTile(index) && misc::getShortestDist - (pos.x, pos.z, index.x * TILESIZE, index.z * TILESIZE, TILESIZE) <= radius; - } - ); - } + TileRange tiles_in_range (glm::vec3 const& pos, float radius); - auto tiles_in_rect (glm::vec3 const& pos, float radius) - { - glm::vec2 l_chunk{pos.x - radius, pos.z - radius}; - glm::vec2 r_chunk{pos.x + radius, pos.z + radius}; - - return tiles - ( [this, pos, radius, l_chunk, r_chunk] (TileIndex const& index, MapTile*) - { - if (!hasTile(index) || radius == 0.f) - return false; - - glm::vec2 l_tile{index.x * TILESIZE, index.z * TILESIZE}; - glm::vec2 r_tile{index.x * TILESIZE + TILESIZE, index.z * TILESIZE + TILESIZE}; - - return ((l_chunk.x < r_tile.x) && (r_chunk.x >= l_tile.x) && (l_chunk.y < r_tile.y) && (r_chunk.y >= l_tile.y)); - } - ); - } + TileRange tiles_in_rect (glm::vec3 const& pos, float radius); MapIndex(const std::string& pBasename, int map_id, World*, Noggit::NoggitRenderContext context, bool create_empty = false); @@ -227,12 +191,12 @@ public: uint32_t getFlag(const TileIndex& tile) const; void convert_alphamap(bool to_big_alpha); - bool hasBigAlpha() const { return mBigAlpha; } - void setBigAlpha(bool state) { mBigAlpha = state; }; - unsigned getNLoadedTiles() { return _n_loaded_tiles; } + bool hasBigAlpha() const; + void setBigAlpha(bool state);; + unsigned getNLoadedTiles() const; - bool sort_models_by_size_class() const { return _sort_models_by_size_class; } - void set_sort_models_by_size_class(bool state) { _sort_models_by_size_class = state; } + bool sort_models_by_size_class() const; + void set_sort_models_by_size_class(bool state); uint32_t newGUID(); @@ -250,15 +214,9 @@ public: unsigned getNumExistingTiles(); // todo: find out how wow choose to use the green lava in outland - inline bool use_mclq_green_lava() const - { - return _map_id == 530; - } + bool use_mclq_green_lava() const; - bool uid_fix_all_in_progress() const - { - return _uid_fix_all_in_progress; - } + bool uid_fix_all_in_progress() const; void loadMinimapMD5translate(); void saveMinimapMD5translate(); @@ -277,19 +235,11 @@ public: ENTRY_MODF wmoEntry; public: // reloadable settings - void setLoadingRadius(int value) - { - if (value < _unload_dist) - _loading_radius = value; - } + void setLoadingRadius(int value); - void setUnloadDistance(int value) - { - if (value > _loading_radius) - _unload_dist = value; - } + void setUnloadDistance(int value); - void setUnloadInterval(int value) { _unload_interval = value; }; + void setUnloadInterval(int value);; private: int _last_unload_time; int _unload_interval; diff --git a/src/noggit/project/ApplicationProject.cpp b/src/noggit/project/ApplicationProject.cpp index 085056fa..76d1e090 100755 --- a/src/noggit/project/ApplicationProject.cpp +++ b/src/noggit/project/ApplicationProject.cpp @@ -1,11 +1,154 @@ #include "ApplicationProject.h" +#include "ApplicationProjectReader.h" +#include "ApplicationProjectWriter.h" + +#include #include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include namespace Noggit::Project { + ApplicationProject::ApplicationProject(std::shared_ptr configuration) + { + _active_project = nullptr; + _configuration = configuration; + } - - void ApplicationProject::loadExtraData(NoggitProject& project) + void ApplicationProject::createProject(std::filesystem::path const& project_path, std::filesystem::path const& client_path, std::string const& client_version, std::string const& project_name) + { + if (!std::filesystem::exists(project_path)) + std::filesystem::create_directory(project_path); + + auto project = NoggitProject(); + project.ProjectName = project_name; + project.projectVersion = ClientVersionFactory::mapToEnumVersion(client_version); + project.ClientPath = client_path.generic_string(); + project.ProjectPath = project_path.generic_string(); + + auto project_writer = ApplicationProjectWriter(); + project_writer.saveProject(&project, project_path); + } + + std::shared_ptr ApplicationProject::loadProject(std::filesystem::path const& project_path) + { + ApplicationProjectReader project_reader{}; + auto project = project_reader.readProject(project_path); + + if (!project.has_value()) + { + LogError << "loadProject() failed, Project is null" << std::endl; + return {}; + } + else + { + Log << "loadProject(): Loading Project Data" << std::endl; + } + + + project_reader.readPalettes(&project.value()); + project_reader.readObjectSelectionGroups(&project.value()); + + std::string dbd_file_directory = _configuration->ApplicationDatabaseDefinitionsPath; + + BlizzardDatabaseLib::Structures::Build client_build("3.3.5.12340"); + auto client_archive_version = BlizzardArchive::ClientVersion::WOTLK; + auto client_archive_locale = BlizzardArchive::Locale::AUTO; + if (project->projectVersion == ProjectVersion::SL) + { + client_archive_version = BlizzardArchive::ClientVersion::SL; + client_build = BlizzardDatabaseLib::Structures::Build("9.1.0.39584"); + client_archive_locale = BlizzardArchive::Locale::enUS; + } + + else if (project->projectVersion == ProjectVersion::WOTLK) + { + client_archive_version = BlizzardArchive::ClientVersion::WOTLK; + client_build = BlizzardDatabaseLib::Structures::Build("3.3.5.12340"); + client_archive_locale = BlizzardArchive::Locale::AUTO; + } + + else + { + LogError << "Unsupported project version" << std::endl; + return {}; + } + + project->ClientDatabase = std::make_shared(dbd_file_directory, client_build); + + Log << "Loading Client Path : " << project->ClientPath << std::endl; + + try + { + project->ClientData = std::make_shared( + project->ClientPath, client_archive_version, client_archive_locale, project_path.generic_string()); + } + catch (BlizzardArchive::Exceptions::Locale::LocaleNotFoundError& e) + { + LogError << e.what() << std::endl; + QMessageBox::critical(nullptr, "Error", e.what()); + return {}; + } + catch (BlizzardArchive::Exceptions::Locale::IncorrectLocaleModeError& e) + { + LogError << e.what() << std::endl; + QMessageBox::critical(nullptr, "Error", e.what()); + return {}; + } + catch (BlizzardArchive::Exceptions::Archive::ArchiveOpenError& e) + { + LogError << e.what() << std::endl; + QMessageBox::critical(nullptr, "Error", e.what()); + return {}; + } + catch (...) + { + LogError << "Failed loading Client data. Unhandled exception." << std::endl; + return {}; + } + + if (!project->ClientData) + { + LogError << "Failed loading Client data." << std::endl; + return {}; + } + + // Log << "Client Version: " << static_cast(project->ClientData->version()) << std::endl; + + Log << "Client Locale: " << project->ClientData->locale_name() << std::endl; + + for (auto const loaded_achive : *project->ClientData->loadedArchives()) + { + Log << "Loaded client Archive: " << loaded_achive->path() << std::endl; + } + + // QSettings settings; + // bool modern_features = settings.value("modern_features", false).toBool(); + bool modern_features = _configuration->modern_features; + if (modern_features) + { + Log << "Modern Features Enabled" << std::endl; + loadExtraData(project.value()); + } + else + { + Log << "Modern Features Disabled" << std::endl; + } + return std::make_shared(project.value()); + } + + void ApplicationProject::loadExtraData(NoggitProject& project) { std::filesystem::path extraDataFolder = (project.ProjectPath); extraDataFolder /= "extraData"; @@ -90,4 +233,114 @@ namespace Noggit::Project } return defaultValue; } -}; \ No newline at end of file + + ProjectVersion ClientVersionFactory::mapToEnumVersion(std::string const& projectVersion) + { + if (projectVersion == "Wrath Of The Lich King") + return ProjectVersion::WOTLK; + if (projectVersion == "Shadowlands") + return ProjectVersion::SL; + + assert(false); + } + + std::string ClientVersionFactory::MapToStringVersion(ProjectVersion const& projectVersion) + { + if (projectVersion == ProjectVersion::WOTLK) + return std::string("Wrath Of The Lich King"); + if (projectVersion == ProjectVersion::SL) + return std::string("Shadowlands"); + + assert(false); + } + + NoggitProject::NoggitProject() + { + _projectWriter = std::make_shared(); + } + + void NoggitProject::createBookmark(const NoggitProjectBookmarkMap& bookmark) + { + Bookmarks.push_back(bookmark); + + _projectWriter->saveProject(this, std::filesystem::path(ProjectPath)); + } + + void NoggitProject::deleteBookmark() + { + } + + void NoggitProject::pinMap(int map_id, const std::string& map_name) + { + auto pinnedMap = NoggitProjectPinnedMap(); + pinnedMap.MapName = map_name; + pinnedMap.MapId = map_id; + + auto pinnedMapFound = std::find_if(std::begin(PinnedMaps), std::end(PinnedMaps), + [&](Project::NoggitProjectPinnedMap pinnedMap) + { + return pinnedMap.MapId == map_id; + }); + + if (pinnedMapFound != std::end(PinnedMaps)) + return; + + PinnedMaps.push_back(pinnedMap); + + _projectWriter->saveProject(this, std::filesystem::path(ProjectPath)); + } + + void NoggitProject::unpinMap(int mapId) + { + PinnedMaps.erase(std::remove_if(PinnedMaps.begin(), PinnedMaps.end(), + [=](NoggitProjectPinnedMap pinnedMap) + { + return pinnedMap.MapId == mapId; + }), + PinnedMaps.end()); + + _projectWriter->saveProject(this, std::filesystem::path(ProjectPath)); + } + + void NoggitProject::saveTexturePalette(const NoggitProjectTexturePalette& new_texture_palette) + { + TexturePalettes.erase(std::remove_if(TexturePalettes.begin(), TexturePalettes.end(), + [=](NoggitProjectTexturePalette texture_palette) + { + return texture_palette.MapId == new_texture_palette.MapId; + }), + TexturePalettes.end()); + + TexturePalettes.push_back(new_texture_palette); + + _projectWriter->savePalettes(this, std::filesystem::path(ProjectPath)); + } + + void NoggitProject::saveObjectPalette(const NoggitProjectObjectPalette& new_object_palette) + { + ObjectPalettes.erase(std::remove_if(ObjectPalettes.begin(), ObjectPalettes.end(), + [=](NoggitProjectObjectPalette obj_palette) + { + return obj_palette.MapId == new_object_palette.MapId; + }), + ObjectPalettes.end()); + + ObjectPalettes.push_back(new_object_palette); + + _projectWriter->savePalettes(this, std::filesystem::path(ProjectPath)); + } + + void NoggitProject::saveObjectSelectionGroups(const NoggitProjectSelectionGroups& new_selection_groups) + { + ObjectSelectionGroups.erase(std::remove_if(ObjectSelectionGroups.begin(), ObjectSelectionGroups.end(), + [=](NoggitProjectSelectionGroups proj_selection_group) + { + return proj_selection_group.MapId == new_selection_groups.MapId; + }), + ObjectSelectionGroups.end()); + + ObjectSelectionGroups.push_back(new_selection_groups); + + _projectWriter->saveObjectSelectionGroups(this, std::filesystem::path(ProjectPath)); + } +}; diff --git a/src/noggit/project/ApplicationProject.h b/src/noggit/project/ApplicationProject.h index d5e6aa1c..33086cb7 100755 --- a/src/noggit/project/ApplicationProject.h +++ b/src/noggit/project/ApplicationProject.h @@ -1,43 +1,33 @@ //Folder to contain all of the project related files #pragma once -#include #include -#include -#include -#include -#include // #include -#include -#include #include #include -#include -#include -#include -#include -#include -#include + #include -#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include -#include "ApplicationProjectReader.h" -#include "ApplicationProjectWriter.h" + +namespace BlizzardDatabaseLib +{ + class BlizzardDatabase; +} + +namespace Noggit::Application +{ + struct NoggitApplicationConfiguration; +} + +struct TileIndex; +class World; namespace Noggit::Project { + class ApplicationProjectWriter; + enum class ProjectVersion { VANILLA, @@ -53,25 +43,9 @@ namespace Noggit::Project struct ClientVersionFactory { - static ProjectVersion mapToEnumVersion(std::string const& projectVersion) - { - if (projectVersion == "Wrath Of The Lich King") - return ProjectVersion::WOTLK; - if (projectVersion == "Shadowlands") - return ProjectVersion::SL; + static ProjectVersion mapToEnumVersion(std::string const& projectVersion); - assert(false); - } - - static std::string MapToStringVersion(ProjectVersion const& projectVersion) - { - if (projectVersion == ProjectVersion::WOTLK) - return std::string("Wrath Of The Lich King"); - if (projectVersion == ProjectVersion::SL) - return std::string("Shadowlands"); - - assert(false); - } + static std::string MapToStringVersion(ProjectVersion const& projectVersion); }; struct NoggitProjectBookmarkMap @@ -141,102 +115,21 @@ namespace Noggit::Project std::vector ObjectSelectionGroups; NoggitExtraMapData ExtraMapData; - NoggitProject() - { - PinnedMaps = std::vector(); - Bookmarks = std::vector(); - ObjectPalettes = std::vector(); - TexturePalettes = std::vector(); - ObjectSelectionGroups = std::vector(); + NoggitProject(); - _projectWriter = std::make_shared(); - } + void createBookmark(const NoggitProjectBookmarkMap& bookmark); - void createBookmark(const NoggitProjectBookmarkMap& bookmark) - { - Bookmarks.push_back(bookmark); + void deleteBookmark(); - _projectWriter->saveProject(this, std::filesystem::path(ProjectPath)); - } + void pinMap(int map_id, const std::string& map_name); - void deleteBookmark() - { + void unpinMap(int mapId); - } + void saveTexturePalette(const NoggitProjectTexturePalette& new_texture_palette); - void pinMap(int map_id, const std::string& map_name) - { - auto pinnedMap = NoggitProjectPinnedMap(); - pinnedMap.MapName = map_name; - pinnedMap.MapId = map_id; + void saveObjectPalette(const NoggitProjectObjectPalette& new_object_palette); - auto pinnedMapFound = std::find_if(std::begin(PinnedMaps), std::end(PinnedMaps), - [&](Project::NoggitProjectPinnedMap pinnedMap) - { - return pinnedMap.MapId == map_id; - }); - - if (pinnedMapFound != std::end(PinnedMaps)) - return; - - PinnedMaps.push_back(pinnedMap); - - _projectWriter->saveProject(this, std::filesystem::path(ProjectPath)); - } - - void unpinMap(int mapId) - { - PinnedMaps.erase(std::remove_if(PinnedMaps.begin(), PinnedMaps.end(), - [=](NoggitProjectPinnedMap pinnedMap) - { - return pinnedMap.MapId == mapId; - }), - PinnedMaps.end()); - - _projectWriter->saveProject(this, std::filesystem::path(ProjectPath)); - } - - void saveTexturePalette(const NoggitProjectTexturePalette& new_texture_palette) - { - TexturePalettes.erase(std::remove_if(TexturePalettes.begin(), TexturePalettes.end(), - [=](NoggitProjectTexturePalette texture_palette) - { - return texture_palette.MapId == new_texture_palette.MapId; - }), - TexturePalettes.end()); - - TexturePalettes.push_back(new_texture_palette); - - _projectWriter->savePalettes(this, std::filesystem::path(ProjectPath)); - } - - void saveObjectPalette(const NoggitProjectObjectPalette& new_object_palette) - { - ObjectPalettes.erase(std::remove_if(ObjectPalettes.begin(), ObjectPalettes.end(), - [=](NoggitProjectObjectPalette obj_palette) - { - return obj_palette.MapId == new_object_palette.MapId; - }), - ObjectPalettes.end()); - - ObjectPalettes.push_back(new_object_palette); - - _projectWriter->savePalettes(this, std::filesystem::path(ProjectPath)); - } - - void saveObjectSelectionGroups(const NoggitProjectSelectionGroups& new_selection_groups) - { - ObjectSelectionGroups.erase(std::remove_if(ObjectSelectionGroups.begin(), ObjectSelectionGroups.end(), - [=](NoggitProjectSelectionGroups proj_selection_group) - { - return proj_selection_group.MapId == new_selection_groups.MapId; - }), - ObjectSelectionGroups.end()); - - ObjectSelectionGroups.push_back(new_selection_groups); - - _projectWriter->saveObjectSelectionGroups(this, std::filesystem::path(ProjectPath)); - } + void saveObjectSelectionGroups(const NoggitProjectSelectionGroups& new_selection_groups); }; class ApplicationProject @@ -244,136 +137,12 @@ namespace Noggit::Project std::shared_ptr _active_project; std::shared_ptr _configuration; public: - ApplicationProject(std::shared_ptr configuration) - { - _active_project = nullptr; - _configuration = configuration; - } + ApplicationProject(std::shared_ptr configuration); void createProject(std::filesystem::path const& project_path, std::filesystem::path const& client_path, - std::string const& client_version, std::string const& project_name) - { - if (!std::filesystem::exists(project_path)) - std::filesystem::create_directory(project_path); + std::string const& client_version, std::string const& project_name); - auto project = NoggitProject(); - project.ProjectName = project_name; - project.projectVersion = ClientVersionFactory::mapToEnumVersion(client_version); - project.ClientPath = client_path.generic_string(); - project.ProjectPath = project_path.generic_string(); - - auto project_writer = ApplicationProjectWriter(); - project_writer.saveProject(&project, project_path); - - - } - - std::shared_ptr loadProject(std::filesystem::path const& project_path) - { - ApplicationProjectReader project_reader{}; - auto project = project_reader.readProject(project_path); - - if (!project.has_value()) - { - LogError << "loadProject() failed, Project is null" << std::endl; - return {}; - } - else - { - Log << "loadProject(): Loading Project Data" << std::endl; - } - - - project_reader.readPalettes(&project.value()); - project_reader.readObjectSelectionGroups(&project.value()); - - std::string dbd_file_directory = _configuration->ApplicationDatabaseDefinitionsPath; - - BlizzardDatabaseLib::Structures::Build client_build("3.3.5.12340"); - auto client_archive_version = BlizzardArchive::ClientVersion::WOTLK; - auto client_archive_locale = BlizzardArchive::Locale::AUTO; - if (project->projectVersion == ProjectVersion::SL) - { - client_archive_version = BlizzardArchive::ClientVersion::SL; - client_build = BlizzardDatabaseLib::Structures::Build("9.1.0.39584"); - client_archive_locale = BlizzardArchive::Locale::enUS; - } - - else if (project->projectVersion == ProjectVersion::WOTLK) - { - client_archive_version = BlizzardArchive::ClientVersion::WOTLK; - client_build = BlizzardDatabaseLib::Structures::Build("3.3.5.12340"); - client_archive_locale = BlizzardArchive::Locale::AUTO; - } - - else - { - LogError << "Unsupported project version" << std::endl; - return {}; - } - - project->ClientDatabase = std::make_shared(dbd_file_directory, client_build); - - Log << "Loading Client Path : " << project->ClientPath << std::endl; - - try - { - project->ClientData = std::make_shared( - project->ClientPath, client_archive_version, client_archive_locale, project_path.generic_string()); - } - catch (BlizzardArchive::Exceptions::Locale::LocaleNotFoundError& e) - { - LogError << e.what() << std::endl; - QMessageBox::critical(nullptr, "Error", e.what()); - return {}; - } - catch (BlizzardArchive::Exceptions::Locale::IncorrectLocaleModeError& e) - { - LogError << e.what() << std::endl; - QMessageBox::critical(nullptr, "Error", e.what()); - return {}; - } - catch (BlizzardArchive::Exceptions::Archive::ArchiveOpenError& e) - { - LogError << e.what() << std::endl; - QMessageBox::critical(nullptr, "Error", e.what()); - return {}; - } - catch (...) - { - LogError << "Failed loading Client data. Unhandled exception." << std::endl; - return {}; - } - - if (!project->ClientData) - { - LogError << "Failed loading Client data." << std::endl; - return {}; - } - - // Log << "Client Version: " << static_cast(project->ClientData->version()) << std::endl; - - Log << "Client Locale: " << project->ClientData->locale_name() << std::endl; - - for (auto const loaded_achive : *project->ClientData->loadedArchives()) - { - Log << "Loaded client Archive: " << loaded_achive->path() << std::endl; - } - - // QSettings settings; - // bool modern_features = settings.value("modern_features", false).toBool(); - bool modern_features = _configuration->modern_features; - if (modern_features) - { - Log << "Modern Features Enabled" << std::endl; - loadExtraData(project.value()); - } - else - { - Log << "Modern Features Disabled" << std::endl; - } - return std::make_shared(project.value()); - } + std::shared_ptr loadProject(std::filesystem::path const& project_path); void loadExtraData(NoggitProject& project); }; diff --git a/src/noggit/project/ApplicationProjectReader.cpp b/src/noggit/project/ApplicationProjectReader.cpp index e608a95b..14299ec5 100755 --- a/src/noggit/project/ApplicationProjectReader.cpp +++ b/src/noggit/project/ApplicationProjectReader.cpp @@ -5,9 +5,7 @@ #include #include #include -#include #include -#include #include namespace Noggit::Project @@ -224,4 +222,4 @@ namespace Noggit::Project } json_file.close(); } -} \ No newline at end of file +} diff --git a/src/noggit/project/ApplicationProjectWriter.cpp b/src/noggit/project/ApplicationProjectWriter.cpp index 701209a4..2706d0a9 100755 --- a/src/noggit/project/ApplicationProjectWriter.cpp +++ b/src/noggit/project/ApplicationProjectWriter.cpp @@ -5,9 +5,7 @@ #include #include #include -#include #include -#include #include namespace Noggit::Project diff --git a/src/noggit/rendering/CursorRender.cpp b/src/noggit/rendering/CursorRender.cpp index 33e4f49a..ec72ad24 100755 --- a/src/noggit/rendering/CursorRender.cpp +++ b/src/noggit/rendering/CursorRender.cpp @@ -4,6 +4,8 @@ #include "math/trig.hpp" #include "opengl/shader.hpp" +#include + namespace Noggit { void CursorRender::draw(Mode cursor_mode, glm::mat4x4 const& mvp, glm::vec4 color, glm::vec3 const& pos, float radius, float inner_radius_ratio) diff --git a/src/noggit/rendering/CursorRender.hpp b/src/noggit/rendering/CursorRender.hpp index dc88e09d..32c20130 100755 --- a/src/noggit/rendering/CursorRender.hpp +++ b/src/noggit/rendering/CursorRender.hpp @@ -2,10 +2,19 @@ #pragma once #include "opengl/scoped.hpp" -#include "opengl/shader.fwd.hpp" #include +namespace OpenGL +{ + struct program; + + namespace Scoped + { + struct use_program; + } +} + namespace Noggit { class CursorRender diff --git a/src/noggit/rendering/FlightBoundsRender.cpp b/src/noggit/rendering/FlightBoundsRender.cpp index 94476737..507d3af2 100755 --- a/src/noggit/rendering/FlightBoundsRender.cpp +++ b/src/noggit/rendering/FlightBoundsRender.cpp @@ -3,6 +3,8 @@ #include "FlightBoundsRender.hpp" #include +#include + #include using namespace Noggit::Rendering; @@ -84,4 +86,4 @@ void FlightBoundsRender::unload() _mfbo_vaos.unload(); _uploaded = false; -} \ No newline at end of file +} diff --git a/src/noggit/rendering/FlightBoundsRender.hpp b/src/noggit/rendering/FlightBoundsRender.hpp index 8074ca47..ff852df8 100755 --- a/src/noggit/rendering/FlightBoundsRender.hpp +++ b/src/noggit/rendering/FlightBoundsRender.hpp @@ -5,10 +5,14 @@ #include #include -#include class MapTile; +namespace OpenGL::Scoped +{ + struct use_program; +} + namespace Noggit::Rendering { class FlightBoundsRender : public BaseRender diff --git a/src/noggit/rendering/LiquidRender.cpp b/src/noggit/rendering/LiquidRender.cpp index 507368c0..c38975e7 100755 --- a/src/noggit/rendering/LiquidRender.cpp +++ b/src/noggit/rendering/LiquidRender.cpp @@ -1,7 +1,11 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). #include "LiquidRender.hpp" +#include #include +#include + +#include using namespace Noggit::Rendering; diff --git a/src/noggit/rendering/LiquidRender.hpp b/src/noggit/rendering/LiquidRender.hpp index 0447ff7b..1b254c98 100755 --- a/src/noggit/rendering/LiquidRender.hpp +++ b/src/noggit/rendering/LiquidRender.hpp @@ -4,16 +4,22 @@ #define NOGGIT_LIQUIDRENDER_HPP #include -#include -#include -#include #include +#include + +#include + namespace math { class frustum; } +namespace OpenGL::Scoped +{ + struct use_program; +} + class MapTile; diff --git a/src/noggit/rendering/LiquidTextureManager.cpp b/src/noggit/rendering/LiquidTextureManager.cpp index 6be20467..3c97fb3a 100755 --- a/src/noggit/rendering/LiquidTextureManager.cpp +++ b/src/noggit/rendering/LiquidTextureManager.cpp @@ -4,6 +4,7 @@ #include "opengl/context.inl" #include "noggit/DBC.h" #include "noggit/application/NoggitApplication.hpp" +#include using namespace Noggit::Rendering; diff --git a/src/noggit/rendering/LiquidTextureManager.hpp b/src/noggit/rendering/LiquidTextureManager.hpp index 260c49cc..44920c28 100755 --- a/src/noggit/rendering/LiquidTextureManager.hpp +++ b/src/noggit/rendering/LiquidTextureManager.hpp @@ -3,12 +3,14 @@ #ifndef NOGGIT_LIQUIDTEXTUREMANAGER_HPP #define NOGGIT_LIQUIDTEXTUREMANAGER_HPP -#include #include + +#include + #include +#include #include -#include /* template diff --git a/src/noggit/rendering/ModelRender.cpp b/src/noggit/rendering/ModelRender.cpp index 28e67079..09445179 100755 --- a/src/noggit/rendering/ModelRender.cpp +++ b/src/noggit/rendering/ModelRender.cpp @@ -3,9 +3,16 @@ #include "ModelRender.hpp" #include #include -#include -#include #include +#include +#include + +#include +#include + +#include + +#include using namespace Noggit::Rendering; @@ -296,6 +303,12 @@ void ModelRender::drawBox(OpenGL::Scoped::use_program& m2_box_shader, std::size_ gl.drawElementsInstanced (GL_LINE_STRIP, static_cast(_box_indices.size()), GL_UNSIGNED_SHORT, nullptr, static_cast(box_count)); } +[[nodiscard]] +std::vector const& Noggit::Rendering::ModelRender::renderPasses() const +{ + return _render_passes; +} + void ModelRender::setupVAO(OpenGL::Scoped::use_program& m2_shader) { OpenGL::Scoped::vao_binder const _(_vao); @@ -1076,3 +1089,19 @@ void ModelRenderPass::initUVTypes(Model* m) } } } + +bool ModelRenderPass::operator< (const ModelRenderPass& m) const +{ + if (priority_plane < m.priority_plane) + { + return true; + } + else if (priority_plane > m.priority_plane) + { + return false; + } + else + { + return blend_mode == m.blend_mode ? (ordering_thingy < m.ordering_thingy) : blend_mode < m.blend_mode; + } +} diff --git a/src/noggit/rendering/ModelRender.hpp b/src/noggit/rendering/ModelRender.hpp index cfe464d6..a2727491 100755 --- a/src/noggit/rendering/ModelRender.hpp +++ b/src/noggit/rendering/ModelRender.hpp @@ -7,8 +7,16 @@ #include #include #include -#include -#include + +namespace math +{ + class frustum; +} + +namespace OpenGL::Scoped +{ + struct use_program; +} class Model; class ModelInstance; @@ -81,21 +89,7 @@ namespace Noggit::Rendering void bindTexture(size_t index, Model* m, OpenGL::M2RenderState& model_render_state, OpenGL::Scoped::use_program& m2_shader); void initUVTypes(Model* m); - bool operator< (const ModelRenderPass &m) const - { - if (priority_plane < m.priority_plane) - { - return true; - } - else if (priority_plane > m.priority_plane) - { - return false; - } - else - { - return blend_mode == m.blend_mode ? (ordering_thingy < m.ordering_thingy) : blend_mode < m.blend_mode; - } - } + bool operator< (const ModelRenderPass& m) const; }; class ModelRender : public BaseRender @@ -150,7 +144,7 @@ namespace Noggit::Rendering void drawBox(OpenGL::Scoped::use_program& m2_box_shader, std::size_t box_count); [[nodiscard]] - std::vector const& renderPasses() const { return _render_passes; }; + std::vector const& renderPasses() const;; void updateBoneMatrices(); diff --git a/src/noggit/rendering/Primitives.cpp b/src/noggit/rendering/Primitives.cpp index 0f89ae71..567c4e05 100755 --- a/src/noggit/rendering/Primitives.cpp +++ b/src/noggit/rendering/Primitives.cpp @@ -3,20 +3,35 @@ #include #include -#include #include #include -#include -#include +#include + +#include -#include #include #include -#include -#include using namespace Noggit::Rendering::Primitives; +WireBox& WireBox::operator=(WireBox& box) +{ + return *this; +} + +WireBox& Noggit::Rendering::Primitives::WireBox::getInstance(Noggit::NoggitRenderContext context) +{ + static std::unordered_map instances; + + if (instances.find(context) == instances.end()) + { + WireBox instance; + instances[context] = instance; + } + + return instances.at(context); +} + void WireBox::draw ( glm::mat4x4 const& model_view , glm::mat4x4 const& projection , glm::mat4x4 const& transform @@ -654,4 +669,4 @@ void Square::setup_buffers() _buffers_are_setup = false; - } \ No newline at end of file + } diff --git a/src/noggit/rendering/Primitives.hpp b/src/noggit/rendering/Primitives.hpp index f57a8c76..af88f4b2 100755 --- a/src/noggit/rendering/Primitives.hpp +++ b/src/noggit/rendering/Primitives.hpp @@ -8,7 +8,6 @@ #include #include -#include namespace math { @@ -22,23 +21,12 @@ namespace Noggit::Rendering::Primitives class WireBox { public: - WireBox() {} - WireBox(const WireBox&); - WireBox& operator=(WireBox& box ) { return *this; }; + WireBox() = default; + WireBox(const WireBox&) = delete; + WireBox& operator=(WireBox& box ); public: - static WireBox& getInstance(Noggit::NoggitRenderContext context) - { - static std::unordered_map instances; - - if (instances.find(context) == instances.end()) - { - WireBox instance; - instances[context] = instance; - } - - return instances.at(context); - } + static WireBox& getInstance(Noggit::NoggitRenderContext context); void draw ( glm::mat4x4 const& model_view , glm::mat4x4 const& projection diff --git a/src/noggit/rendering/TileRender.cpp b/src/noggit/rendering/TileRender.cpp index 0a7c7493..24af58bf 100755 --- a/src/noggit/rendering/TileRender.cpp +++ b/src/noggit/rendering/TileRender.cpp @@ -3,9 +3,14 @@ #include #include #include +#include #include -#include #include +#include + +#include + +#include using namespace Noggit::Rendering; @@ -512,8 +517,18 @@ bool TileRender::getTileOcclusionQueryResult(glm::vec3 const& camera) return static_cast(result); } +void Noggit::Rendering::TileRender::discardTileOcclusionQuery() +{ + _tile_occlusion_query_in_use = false; +} -bool TileRender::fillSamplers(MapChunk* chunk, unsigned chunk_index, unsigned int draw_call_index) +void Noggit::Rendering::TileRender::notifyTileRendererOnSelectedTextureChange() +{ + _requires_paintability_recalc = true; +} + + +bool TileRender::fillSamplers(MapChunk* chunk, unsigned chunk_index, unsigned draw_call_index) { MapTileDrawCall& draw_call = _draw_calls[draw_call_index]; @@ -752,3 +767,64 @@ void Noggit::Rendering::TileRender::setActiveRenderGEffectTexture(std::string ac _require_geffect_active_texture_update = true; } + +[[nodiscard]] +unsigned Noggit::Rendering::TileRender::objectsFrustumCullTest() const +{ + return _objects_frustum_cull_test; +} + +void Noggit::Rendering::TileRender::setObjectsFrustumCullTest(unsigned state) +{ + _objects_frustum_cull_test = state; +} + +[[nodiscard]] +bool Noggit::Rendering::TileRender::isOccluded() const +{ + return _tile_occluded; +} + +void Noggit::Rendering::TileRender::setOccluded(bool state) +{ + _tile_occluded = state; +} + +[[nodiscard]] +bool Noggit::Rendering::TileRender::isFrustumCulled() const +{ + return _tile_frustum_culled; +} + +void Noggit::Rendering::TileRender::setFrustumCulled(bool state) +{ + _tile_frustum_culled = state; +} + +[[nodiscard]] +bool Noggit::Rendering::TileRender::isOverridingOcclusionCulling() const +{ + return _tile_occlusion_cull_override; +} + +void Noggit::Rendering::TileRender::setOverrideOcclusionCulling(bool state) +{ + _tile_frustum_culled = state; +} + +[[nodiscard]] +bool Noggit::Rendering::TileRender::isUploaded() const +{ + return _uploaded; +} + +[[nodiscard]] +bool Noggit::Rendering::TileRender::alphamapUploadedLastFrame() const +{ + return _uploaded_alphamap_last_frame; +} + +int Noggit::Rendering::TileRender::numUploadedChunkAlphamaps() const +{ + return _num_uploaded_chunk_alphamaps; +} diff --git a/src/noggit/rendering/TileRender.hpp b/src/noggit/rendering/TileRender.hpp index bf82dcc6..052cd985 100755 --- a/src/noggit/rendering/TileRender.hpp +++ b/src/noggit/rendering/TileRender.hpp @@ -5,9 +5,13 @@ #include #include -#include #include +namespace OpenGL::Scoped +{ + struct use_program; +} + class MapTile; class MapChunk; @@ -38,8 +42,8 @@ namespace Noggit::Rendering void doTileOcclusionQuery(OpenGL::Scoped::use_program& occlusion_shader); bool getTileOcclusionQueryResult(glm::vec3 const& camera); - void discardTileOcclusionQuery() { _tile_occlusion_query_in_use = false; } - void notifyTileRendererOnSelectedTextureChange() { _requires_paintability_recalc = true; }; + void discardTileOcclusionQuery(); + void notifyTileRendererOnSelectedTextureChange();; void setChunkGroundEffectColor(unsigned int chunkid, glm::vec3 color); void initChunkData(MapChunk* chunk); @@ -49,26 +53,26 @@ namespace Noggit::Rendering void setActiveRenderGEffectTexture(std::string active_texture); [[nodiscard]] - unsigned objectsFrustumCullTest() const { return _objects_frustum_cull_test; }; - void setObjectsFrustumCullTest(unsigned state) { _objects_frustum_cull_test = state; }; + unsigned objectsFrustumCullTest() const;; + void setObjectsFrustumCullTest(unsigned state);; [[nodiscard]] - bool isOccluded() const { return _tile_occluded; } ; - void setOccluded(bool state) { _tile_occluded = state; }; + bool isOccluded() const; ; + void setOccluded(bool state);; [[nodiscard]] - bool isFrustumCulled() const{ return _tile_frustum_culled; }; - void setFrustumCulled(bool state) {_tile_frustum_culled = state; }; + bool isFrustumCulled() const;; + void setFrustumCulled(bool state);; [[nodiscard]] - bool isOverridingOcclusionCulling() const { return _tile_occlusion_cull_override; }; - void setOverrideOcclusionCulling(bool state) { _tile_frustum_culled = state; }; + bool isOverridingOcclusionCulling() const;; + void setOverrideOcclusionCulling(bool state);; [[nodiscard]] - bool isUploaded() const { return _uploaded; }; + bool isUploaded() const;; [[nodiscard]] - bool alphamapUploadedLastFrame() const { return _uploaded_alphamap_last_frame; }; - int numUploadedChunkAlphamaps() const { return _num_uploaded_chunk_alphamaps; }; + bool alphamapUploadedLastFrame() const;; + int numUploadedChunkAlphamaps() const;; private: diff --git a/src/noggit/rendering/WMOGroupRender.cpp b/src/noggit/rendering/WMOGroupRender.cpp index 22001ca2..dc260295 100755 --- a/src/noggit/rendering/WMOGroupRender.cpp +++ b/src/noggit/rendering/WMOGroupRender.cpp @@ -2,8 +2,17 @@ #include "WMOGroupRender.hpp" #include +#include #include // LogDebug +#include #include +#include + +#include + +#include + +#include using namespace Noggit::Rendering; diff --git a/src/noggit/rendering/WMOGroupRender.hpp b/src/noggit/rendering/WMOGroupRender.hpp index a1d5745b..13c3fe33 100755 --- a/src/noggit/rendering/WMOGroupRender.hpp +++ b/src/noggit/rendering/WMOGroupRender.hpp @@ -4,9 +4,17 @@ #define NOGGIT_WMOGROUPRENDER_HPP #include -#include #include -#include + +namespace OpenGL::Scoped +{ + struct use_program; +} + +namespace math +{ + class frustum; +} class WMOGroup; diff --git a/src/noggit/rendering/WMORender.cpp b/src/noggit/rendering/WMORender.cpp index ca29c8be..2b08513d 100755 --- a/src/noggit/rendering/WMORender.cpp +++ b/src/noggit/rendering/WMORender.cpp @@ -1,7 +1,15 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). #include "WMORender.hpp" +#include +#include #include +#include + +#include + +#include + using namespace Noggit::Rendering; diff --git a/src/noggit/rendering/WMORender.hpp b/src/noggit/rendering/WMORender.hpp index 1413bc12..1e3670c1 100755 --- a/src/noggit/rendering/WMORender.hpp +++ b/src/noggit/rendering/WMORender.hpp @@ -5,13 +5,20 @@ #include #include -#include -#include #include -#include #include +namespace OpenGL::Scoped +{ + struct use_program; +} + +namespace math +{ + class frustum; +} + class WMO; namespace Noggit::Rendering diff --git a/src/noggit/rendering/WorldRender.cpp b/src/noggit/rendering/WorldRender.cpp index 46c82ebc..7b28de7b 100755 --- a/src/noggit/rendering/WorldRender.cpp +++ b/src/noggit/rendering/WorldRender.cpp @@ -1,17 +1,34 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). #include "WorldRender.hpp" +#include #include #include -#include -#include -#include -#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include -#include #include #include +#include +#include +#include +#include + +#include using namespace Noggit::Rendering; @@ -1619,6 +1636,22 @@ void WorldRender::updateTerrainParamsUniformBlock() _need_terrain_params_ubo_update = false; } +void Noggit::Rendering::WorldRender::markTerrainParamsUniformBlockDirty() +{ + _need_terrain_params_ubo_update = true; +} + +[[nodiscard]] +std::unique_ptr& Noggit::Rendering::WorldRender::skies() +{ + return _skies; +} + +float Noggit::Rendering::WorldRender::cullDistance() const +{ + return _cull_distance; +} + void WorldRender::setupChunkVAO(OpenGL::Scoped::use_program& mcnk_shader) { ZoneScoped; @@ -2122,3 +2155,9 @@ bool WorldRender::saveMinimap(TileIndex const& tile_idx, MinimapRenderSettings* return true; } + +[[nodiscard]] +OpenGL::TerrainParamsUniformBlock* Noggit::Rendering::WorldRender::getTerrainParamsUniformBlock() +{ + return &_terrain_params_ubo_data; +} diff --git a/src/noggit/rendering/WorldRender.hpp b/src/noggit/rendering/WorldRender.hpp index 2f110e7e..9828e4f9 100755 --- a/src/noggit/rendering/WorldRender.hpp +++ b/src/noggit/rendering/WorldRender.hpp @@ -6,7 +6,6 @@ #include #include -#include #include #include @@ -14,11 +13,16 @@ #include #include -#include #include #include +namespace OpenGL +{ + struct program; +} + +struct TileIndex; class World; struct MinimapRenderSettings; @@ -90,15 +94,15 @@ namespace Noggit::Rendering , std::optional& combined_image); [[nodiscard]] - OpenGL::TerrainParamsUniformBlock* getTerrainParamsUniformBlock() { return &_terrain_params_ubo_data; }; + OpenGL::TerrainParamsUniformBlock* getTerrainParamsUniformBlock();; void updateTerrainParamsUniformBlock(); - void markTerrainParamsUniformBlockDirty() { _need_terrain_params_ubo_update = true; }; + void markTerrainParamsUniformBlockDirty();; - [[nodiscard]] std::unique_ptr& skies() { return _skies; }; + [[nodiscard]] std::unique_ptr& skies();; float _view_distance; - inline float cullDistance() const { return _cull_distance; } + float cullDistance() const; unsigned int _frame_max_chunk_updates = 256; @@ -168,7 +172,6 @@ namespace Noggit::Rendering OpenGL::LightingUniformBlock _lighting_ubo_data; OpenGL::TerrainParamsUniformBlock _terrain_params_ubo_data; - // VAOs OpenGL::Scoped::deferred_upload_vertex_arrays<3> _vertex_arrays; GLuint const& _mapchunk_vao = _vertex_arrays[0]; diff --git a/src/noggit/scoped_blp_texture_reference.cpp b/src/noggit/scoped_blp_texture_reference.cpp new file mode 100644 index 00000000..51a212db --- /dev/null +++ b/src/noggit/scoped_blp_texture_reference.cpp @@ -0,0 +1,35 @@ +#include "scoped_blp_texture_reference.hpp" + +#include + +scoped_blp_texture_reference::scoped_blp_texture_reference(std::string const& filename, Noggit::NoggitRenderContext context) + : _blp_texture(TextureManager::_.emplace(filename, context)) + , _context(context) +{ +} + +scoped_blp_texture_reference::scoped_blp_texture_reference(scoped_blp_texture_reference const& other) + : _blp_texture(other._blp_texture ? TextureManager::_.emplace(other._blp_texture->file_key().filepath(), other._context) : nullptr) + , _context(other._context) +{ +} + +void scoped_blp_texture_reference::Deleter::operator() (blp_texture* texture) const +{ + TextureManager::_.erase(texture->file_key().filepath(), texture->getContext()); +} + +blp_texture* scoped_blp_texture_reference::operator->() const +{ + return _blp_texture.get(); +} + +blp_texture* scoped_blp_texture_reference::get() const +{ + return _blp_texture.get(); +} + +bool scoped_blp_texture_reference::operator== (scoped_blp_texture_reference const& other) const +{ + return std::tie(_blp_texture) == std::tie(other._blp_texture); +} diff --git a/src/noggit/scoped_blp_texture_reference.hpp b/src/noggit/scoped_blp_texture_reference.hpp new file mode 100644 index 00000000..9c2d029c --- /dev/null +++ b/src/noggit/scoped_blp_texture_reference.hpp @@ -0,0 +1,35 @@ +// This file is part of Noggit3, licensed under GNU General Public License (version 3). + +#pragma once + +#include + +#include +#include + +struct blp_texture; + +struct scoped_blp_texture_reference +{ + scoped_blp_texture_reference() = delete; + scoped_blp_texture_reference(std::string const& filename, Noggit::NoggitRenderContext context); + scoped_blp_texture_reference(scoped_blp_texture_reference const& other); + scoped_blp_texture_reference(scoped_blp_texture_reference&&) = default; + scoped_blp_texture_reference& operator= (scoped_blp_texture_reference const&) = delete; + scoped_blp_texture_reference& operator= (scoped_blp_texture_reference&&) = default; + ~scoped_blp_texture_reference() = default; + + blp_texture* operator->() const; + blp_texture* get() const; + + bool operator== (scoped_blp_texture_reference const& other) const; + + bool use_cubemap = false; +private: + struct Deleter + { + void operator() (blp_texture*) const; + }; + std::unique_ptr _blp_texture; + Noggit::NoggitRenderContext _context; +}; diff --git a/src/noggit/scripting/script_chunk.cpp b/src/noggit/scripting/script_chunk.cpp index e40cdb36..02b2960e 100755 --- a/src/noggit/scripting/script_chunk.cpp +++ b/src/noggit/scripting/script_chunk.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include diff --git a/src/noggit/scripting/script_chunk.hpp b/src/noggit/scripting/script_chunk.hpp index 9741aeeb..0f0fe40e 100755 --- a/src/noggit/scripting/script_chunk.hpp +++ b/src/noggit/scripting/script_chunk.hpp @@ -58,4 +58,4 @@ namespace Noggit void register_chunk(script_context * state); } // namespace Scripting -} // namespace Noggit \ No newline at end of file +} // namespace Noggit diff --git a/src/noggit/scripting/script_global.cpp b/src/noggit/scripting/script_global.cpp index 82a49ec2..b7f38d9c 100644 --- a/src/noggit/scripting/script_global.cpp +++ b/src/noggit/scripting/script_global.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include @@ -135,4 +136,4 @@ namespace Noggit { }); } } -} \ No newline at end of file +} diff --git a/src/noggit/scripting/script_model.cpp b/src/noggit/scripting/script_model.cpp index 654fdfe2..8b9bbe2c 100644 --- a/src/noggit/scripting/script_model.cpp +++ b/src/noggit/scripting/script_model.cpp @@ -5,8 +5,10 @@ #include #include #include +#include #include #include + #include namespace Noggit diff --git a/src/noggit/scripting/script_tex.cpp b/src/noggit/scripting/script_tex.cpp index da1bbfc5..ea54b221 100755 --- a/src/noggit/scripting/script_tex.cpp +++ b/src/noggit/scripting/script_tex.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include @@ -87,4 +88,4 @@ namespace Noggit { ); } } -} \ No newline at end of file +} diff --git a/src/noggit/scripting/script_vert.cpp b/src/noggit/scripting/script_vert.cpp index 28f592d0..0a001a1f 100755 --- a/src/noggit/scripting/script_vert.cpp +++ b/src/noggit/scripting/script_vert.cpp @@ -1,8 +1,10 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). -#include -#include +#include #include +#include +#include #include +#include #include diff --git a/src/noggit/texture_set.cpp b/src/noggit/texture_set.cpp index a20abb7c..b1a84f73 100644 --- a/src/noggit/texture_set.cpp +++ b/src/noggit/texture_set.cpp @@ -1,15 +1,18 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). #include -#include +#include +#include #include #include -#include // TextureManager, Texture -#include #include +#include // TextureManager, Texture + #include + +#include + #include // std::min -#include #include TextureSet::TextureSet (MapChunk* chunk, BlizzardArchive::ClientFile* f, size_t base @@ -398,6 +401,16 @@ int TextureSet::get_texture_index_or_add (scoped_blp_texture_reference texture, return addTexture (std::move (texture)); } +auto TextureSet::getDoodadMappingBase(void) -> std::uint16_t* +{ + return _doodadMapping.data(); +} + +std::array const& TextureSet::getDoodadMapping() +{ + return _doodadMapping; +} + std::array, 8> const TextureSet::getDoodadMappingReadable() { std::array, 8> doodad_mapping{}; @@ -427,6 +440,13 @@ uint8_t const TextureSet::getDoodadActiveLayerIdAt(unsigned int x, unsigned int return layer_id; } + +// this is actually uint1_t[8][8] (8*8 -> 1 bit each) +auto TextureSet::getDoodadStencilBase(void) -> std::uint8_t* +{ + return _doodadStencil.data(); +} + bool const TextureSet::getDoodadDisabledAt(int x, int y) { if (x >= 8 || y >= 8) @@ -468,6 +488,26 @@ void TextureSet::setDetailDoodadsExclusion(float xbase, float zbase, glm::vec3 c _chunk->registerChunkUpdate(ChunkUpdateFlags::DETAILDOODADS_EXCLUSION); } +auto TextureSet::getEffectForLayer(std::size_t idx) const -> unsigned +{ + return _layers_info[idx].effectID; +} + +layer_info* TextureSet::getMCLYEntries() +{ + return &_layers_info[0]; +} + +void TextureSet::setNTextures(size_t n) +{ + nTextures = n; +} + +std::vector* TextureSet::getTextures() +{ + return &textures; +} + bool TextureSet::stampTexture(float xbase, float zbase, float x, float z, Brush* brush, float strength, float pressure, scoped_blp_texture_reference texture, QImage* image, bool paint) { @@ -1032,6 +1072,11 @@ bool TextureSet::replace_texture( float xbase return changed; } + size_t const& TextureSet::num() const +{ + return nTextures; +} + unsigned int TextureSet::flag(size_t id) { return _layers_info[id].flags; @@ -1396,6 +1441,18 @@ void TextureSet::update_lod_texture_map() _need_lod_texture_map_update = false; } + constexpr std::array TextureSet::make_alpha_lookup_array() +{ + std::array array{}; + + for (int i = 0; i < 256 * 256; ++i) + { + array[i] = i / 255 + (i % 255 <= 127 ? 0 : 1); + } + + return array; +} + std::array TextureSet::get_textures_weight_for_unit(unsigned int unit_x, unsigned int unit_y) @@ -1578,7 +1635,7 @@ uint8_t TextureSet::sum_alpha(size_t offset) const namespace { - inline std::uint8_t float_alpha_to_uint8(float a) + std::uint8_t float_alpha_to_uint8(float a) { return static_cast(std::max(0.f, std::min(255.f, std::round(a)))); } @@ -1676,4 +1733,27 @@ std::array TextureSet::lod_texture_map() return _doodadMapping; } -std::array TextureSet::alpha_convertion_lookup = TextureSet::make_alpha_lookup_array(); \ No newline at end of file + std::array, MAX_ALPHAMAPS>* TextureSet::getAlphamaps() +{ + return &alphamaps; +} + + std::unique_ptr& TextureSet::getTempAlphamaps() +{ + return tmp_edit_values; +} + + void TextureSet::setAlphamaps(const std::array, MAX_ALPHAMAPS>& newAlphamaps) +{ + for (int i = 0; i < MAX_ALPHAMAPS; ++i) + { + if (newAlphamaps[i]) + { + alphamaps[i] = std::make_unique(*newAlphamaps[i]); + } + else + alphamaps[i].reset(); + } +} + +std::array TextureSet::alpha_convertion_lookup = TextureSet::make_alpha_lookup_array(); diff --git a/src/noggit/texture_set.hpp b/src/noggit/texture_set.hpp index 9fb615e1..b45f19ea 100644 --- a/src/noggit/texture_set.hpp +++ b/src/noggit/texture_set.hpp @@ -3,7 +3,6 @@ #pragma once #include -#include #include #include @@ -13,6 +12,7 @@ class Brush; class MapTile; class MapChunk; +struct MapChunkHeader; namespace BlizzardArchive { @@ -69,7 +69,7 @@ public: const std::string& filename(size_t id); - size_t const& num() const { return nTextures; } + size_t const& num() const; unsigned int flag(size_t id); unsigned int effect(size_t id); bool is_animated(std::size_t id) const; @@ -99,38 +99,28 @@ public: std::array lod_texture_map(); - std::array, MAX_ALPHAMAPS>* getAlphamaps() { return &alphamaps; }; - std::unique_ptr& getTempAlphamaps() { return tmp_edit_values; }; + std::array, MAX_ALPHAMAPS>* getAlphamaps();; + std::unique_ptr& getTempAlphamaps();; - void setAlphamaps(const std::array, MAX_ALPHAMAPS>& newAlphamaps) { - for (int i = 0; i < MAX_ALPHAMAPS; ++i) - { - if (newAlphamaps[i]) - { - alphamaps[i] = std::make_unique(*newAlphamaps[i]); - } - else - alphamaps[i].reset(); - } - } + void setAlphamaps(const std::array, MAX_ALPHAMAPS>& newAlphamaps); int get_texture_index_or_add (scoped_blp_texture_reference texture, float target); - auto getDoodadMappingBase(void) -> std::uint16_t* { return _doodadMapping.data(); } - std::array const& getDoodadMapping() { return _doodadMapping; } + auto getDoodadMappingBase(void) -> std::uint16_t*; + std::array const& getDoodadMapping(); std::array, 8> const getDoodadMappingReadable(); // get array of readable values uint8_t const getDoodadActiveLayerIdAt(unsigned int unit_x, unsigned int unit_y); std::array _doodadStencil; // doodads disabled if 1; WoD: may be an explicit MCDD chunk // this is actually uint1_t[8][8] (8*8 -> 1 bit each) - auto getDoodadStencilBase(void) -> std::uint8_t* { return _doodadStencil.data(); } + auto getDoodadStencilBase(void) -> std::uint8_t*; bool const getDoodadDisabledAt(int x, int y); // max is 8 void setDetailDoodadsExclusion(float xbase, float zbase, glm::vec3 const& pos, float radius, bool big, bool add); - auto getEffectForLayer(std::size_t idx) const -> unsigned { return _layers_info[idx].effectID; } - layer_info* getMCLYEntries() { return &_layers_info[0]; }; - void setNTextures(size_t n) { nTextures = n; }; - std::vector* getTextures() { return &textures; }; + auto getEffectForLayer(std::size_t idx) const -> unsigned; + layer_info* getMCLYEntries();; + void setNTextures(size_t n);; + std::vector* getTextures();; // get the weight of each texture in a chunk unit std::array get_textures_weight_for_unit(unsigned int unit_x, unsigned int unit_y); @@ -171,17 +161,7 @@ private: bool _do_not_convert_alphamaps; - static constexpr std::array make_alpha_lookup_array() - { - std::array array{}; - - for (int i = 0; i < 256 * 256; ++i) - { - array[i] = i / 255 + (i % 255 <= 127 ? 0 : 1); - } - - return array; - } + static constexpr std::array make_alpha_lookup_array(); static std::array alpha_convertion_lookup; diff --git a/src/noggit/tools/AreaTool.cpp b/src/noggit/tools/AreaTool.cpp index 13480ec1..957857d2 100644 --- a/src/noggit/tools/AreaTool.cpp +++ b/src/noggit/tools/AreaTool.cpp @@ -3,10 +3,12 @@ #include "AreaTool.hpp" #include +#include #include #include #include #include +#include namespace Noggit { diff --git a/src/noggit/tools/AreaTriggerTool.cpp b/src/noggit/tools/AreaTriggerTool.cpp index 85e8c4c5..9bb53d1c 100644 --- a/src/noggit/tools/AreaTriggerTool.cpp +++ b/src/noggit/tools/AreaTriggerTool.cpp @@ -5,16 +5,18 @@ #include #include #include -#include +#include #include -#include +#include #include +#include +#include -#include #include +#include -#include #include +#include size_t get_closest_hit(MapView* map_view, std::vector const& hits) { diff --git a/src/noggit/tools/AreaTriggerTool.hpp b/src/noggit/tools/AreaTriggerTool.hpp index 22c76baf..314ec84d 100644 --- a/src/noggit/tools/AreaTriggerTool.hpp +++ b/src/noggit/tools/AreaTriggerTool.hpp @@ -3,7 +3,6 @@ #pragma once #include -#include #include #include diff --git a/src/noggit/tools/FlattenBlurTool.cpp b/src/noggit/tools/FlattenBlurTool.cpp index 19c1be11..5ef3440b 100644 --- a/src/noggit/tools/FlattenBlurTool.cpp +++ b/src/noggit/tools/FlattenBlurTool.cpp @@ -8,6 +8,9 @@ #include #include #include +#include + +#include namespace Noggit { diff --git a/src/noggit/tools/HoleTool.cpp b/src/noggit/tools/HoleTool.cpp index e0b4c822..5601bd4c 100644 --- a/src/noggit/tools/HoleTool.cpp +++ b/src/noggit/tools/HoleTool.cpp @@ -7,6 +7,7 @@ #include #include #include +#include namespace Noggit { diff --git a/src/noggit/tools/ImpassTool.cpp b/src/noggit/tools/ImpassTool.cpp index 0b037b27..e8366a19 100644 --- a/src/noggit/tools/ImpassTool.cpp +++ b/src/noggit/tools/ImpassTool.cpp @@ -6,6 +6,7 @@ #include #include #include +#include namespace Noggit { diff --git a/src/noggit/tools/LightTool.cpp b/src/noggit/tools/LightTool.cpp index 8944872d..9cf95b9c 100644 --- a/src/noggit/tools/LightTool.cpp +++ b/src/noggit/tools/LightTool.cpp @@ -7,6 +7,7 @@ #include #include #include +#include namespace Noggit { diff --git a/src/noggit/tools/MinimapTool.cpp b/src/noggit/tools/MinimapTool.cpp index b53a5df0..9cb1e174 100644 --- a/src/noggit/tools/MinimapTool.cpp +++ b/src/noggit/tools/MinimapTool.cpp @@ -3,15 +3,19 @@ #include "MinimapTool.hpp" #include -#include +#include #include +#include #include +#include #include #include #include -#include +#include #include +#include +#include #include namespace Noggit diff --git a/src/noggit/tools/ObjectTool.cpp b/src/noggit/tools/ObjectTool.cpp index c023aee1..aadfa61c 100644 --- a/src/noggit/tools/ObjectTool.cpp +++ b/src/noggit/tools/ObjectTool.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -13,6 +14,12 @@ #include #include #include +#include + +#include +#include + +#include namespace Noggit { diff --git a/src/noggit/tools/RaiseLowerTool.cpp b/src/noggit/tools/RaiseLowerTool.cpp index f77dd7b4..76f3e96d 100644 --- a/src/noggit/tools/RaiseLowerTool.cpp +++ b/src/noggit/tools/RaiseLowerTool.cpp @@ -7,6 +7,8 @@ #include #include #include +#include +#include #include diff --git a/src/noggit/tools/ScriptingTool.cpp b/src/noggit/tools/ScriptingTool.cpp index f30e74fd..e8958364 100644 --- a/src/noggit/tools/ScriptingTool.cpp +++ b/src/noggit/tools/ScriptingTool.cpp @@ -2,12 +2,12 @@ #include "ScriptingTool.hpp" -#include #include #include #include #include #include +#include namespace Noggit { diff --git a/src/noggit/tools/StampTool.cpp b/src/noggit/tools/StampTool.cpp index e271fc07..55a2f548 100644 --- a/src/noggit/tools/StampTool.cpp +++ b/src/noggit/tools/StampTool.cpp @@ -3,10 +3,12 @@ #include "StampTool.hpp" #include -#include #include +#include #include +#include #include +#include #include diff --git a/src/noggit/tools/TexturingTool.cpp b/src/noggit/tools/TexturingTool.cpp index a2543ef5..9e29b478 100644 --- a/src/noggit/tools/TexturingTool.cpp +++ b/src/noggit/tools/TexturingTool.cpp @@ -2,25 +2,27 @@ #include "FlattenBlurTool.hpp" +#include "TexturingTool.hpp" #include #include #include #include -#include -#include +#include +#include +#include #include #include -#include -#include +#include #include #include -#include -#include -#include "TexturingTool.hpp" +#include #include +#include +#include #include #include +#include #include diff --git a/src/noggit/tools/VertexPainterTool.cpp b/src/noggit/tools/VertexPainterTool.cpp index e4329988..fdeff173 100644 --- a/src/noggit/tools/VertexPainterTool.cpp +++ b/src/noggit/tools/VertexPainterTool.cpp @@ -3,10 +3,12 @@ #include "VertexPainterTool.hpp" #include -#include #include +#include #include #include +#include +#include #include diff --git a/src/noggit/tools/WaterTool.cpp b/src/noggit/tools/WaterTool.cpp index ef5ead47..db439b4c 100644 --- a/src/noggit/tools/WaterTool.cpp +++ b/src/noggit/tools/WaterTool.cpp @@ -7,6 +7,9 @@ #include #include #include +#include + +#include namespace Noggit { diff --git a/src/noggit/ui/CurrentTexture.cpp b/src/noggit/ui/CurrentTexture.cpp index 1f33c6f4..a7e77be1 100755 --- a/src/noggit/ui/CurrentTexture.cpp +++ b/src/noggit/ui/CurrentTexture.cpp @@ -1,18 +1,13 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). #include -#include -#include +#include -#include - -#include #include #include #include #include #include -#include namespace Noggit { diff --git a/src/noggit/ui/CurrentTexture.h b/src/noggit/ui/CurrentTexture.h index 89b00184..8a9534b8 100755 --- a/src/noggit/ui/CurrentTexture.h +++ b/src/noggit/ui/CurrentTexture.h @@ -2,9 +2,7 @@ #pragma once -#include #include -#include #include diff --git a/src/noggit/ui/DetailInfos.cpp b/src/noggit/ui/DetailInfos.cpp index 093f5b1e..c997709f 100755 --- a/src/noggit/ui/DetailInfos.cpp +++ b/src/noggit/ui/DetailInfos.cpp @@ -3,6 +3,7 @@ #include #include +#include #include namespace Noggit diff --git a/src/noggit/ui/DetailInfos.h b/src/noggit/ui/DetailInfos.h index f09e3785..93c02b02 100755 --- a/src/noggit/ui/DetailInfos.h +++ b/src/noggit/ui/DetailInfos.h @@ -4,10 +4,10 @@ #include -#include - #include +class QLabel; + namespace Noggit { namespace Ui diff --git a/src/noggit/ui/FlattenTool.cpp b/src/noggit/ui/FlattenTool.cpp index ab9c42fc..8a6b8c1f 100755 --- a/src/noggit/ui/FlattenTool.cpp +++ b/src/noggit/ui/FlattenTool.cpp @@ -2,15 +2,22 @@ #include #include - +#include #include -#include -#include +#include + +#include +#include +#include +#include +#include #include #include +#include #include #include +#include namespace Noggit { @@ -394,6 +401,46 @@ namespace Noggit _orientation_info->setText(QString::number(_orientation_dial->value())); } + float flatten_blur_tool::brushRadius() const + { + return _radius_slider->value(); + } + + float flatten_blur_tool::angle() const + { + return _angle; + } + + float flatten_blur_tool::orientation() const + { + return _orientation; + } + + bool flatten_blur_tool::angled_mode() const + { + return _angle_group->isChecked(); + } + + bool flatten_blur_tool::use_ref_pos() const + { + return _lock_group->isChecked(); + } + + glm::vec3 flatten_blur_tool::ref_pos() const + { + return _lock_pos; + } + + Noggit::Ui::Tools::UiCommon::ExtendedSlider* flatten_blur_tool::getRadiusSlider() + { + return _radius_slider; + } + + Noggit::Ui::Tools::UiCommon::ExtendedSlider* flatten_blur_tool::getSpeedSlider() + { + return _speed_slider; + } + void flatten_blur_tool::changeAngle(float change) { _angle = std::min(89.0f, std::max(0.0f, _angle + change)); diff --git a/src/noggit/ui/FlattenTool.hpp b/src/noggit/ui/FlattenTool.hpp index 8c5be0f6..3ffcef94 100755 --- a/src/noggit/ui/FlattenTool.hpp +++ b/src/noggit/ui/FlattenTool.hpp @@ -2,18 +2,27 @@ #pragma once #include -#include -#include -#include -#include -#include -#include -#include -#include -#include + #include +#include +#include + +namespace Noggit::Ui::Tools::UiCommon +{ + class ExtendedSlider; +} + class World; + +class QButtonGroup; +class QCheckBox; +class QLabel; +class QDial; +class QDoubleSpinBox; +class QGroupBox; +class QSlider; + namespace Noggit { namespace Ui @@ -42,15 +51,15 @@ namespace Noggit void setSpeed(float speed); void setOrientation(float orientation); - float brushRadius() const { return _radius_slider->value(); } - float angle() const { return _angle; } - float orientation() const { return _orientation; } - bool angled_mode() const { return _angle_group->isChecked(); } - bool use_ref_pos() const { return _lock_group->isChecked(); } - glm::vec3 ref_pos() const { return _lock_pos; } + float brushRadius() const; + float angle() const; + float orientation() const; + bool angled_mode() const; + bool use_ref_pos() const; + glm::vec3 ref_pos() const; - Noggit::Ui::Tools::UiCommon::ExtendedSlider* getRadiusSlider() { return _radius_slider; }; - Noggit::Ui::Tools::UiCommon::ExtendedSlider* getSpeedSlider() { return _speed_slider; }; + Noggit::Ui::Tools::UiCommon::ExtendedSlider* getRadiusSlider();; + Noggit::Ui::Tools::UiCommon::ExtendedSlider* getSpeedSlider();; QSize sizeHint() const override; flatten_mode _flatten_mode; diff --git a/src/noggit/ui/FontAwesome.cpp b/src/noggit/ui/FontAwesome.cpp index 4d8a13e0..1245964d 100755 --- a/src/noggit/ui/FontAwesome.cpp +++ b/src/noggit/ui/FontAwesome.cpp @@ -1,14 +1,10 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). #include -#include #include #include -#include -#include #include -#include namespace Noggit { diff --git a/src/noggit/ui/FontNoggit.cpp b/src/noggit/ui/FontNoggit.cpp index 8e16124f..ff6f4b0e 100755 --- a/src/noggit/ui/FontNoggit.cpp +++ b/src/noggit/ui/FontNoggit.cpp @@ -1,14 +1,10 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). #include -#include #include #include -#include #include -#include -#include namespace Noggit { diff --git a/src/noggit/ui/FramelessWindow.hpp b/src/noggit/ui/FramelessWindow.hpp index 3d7e8f44..481d0b8c 100755 --- a/src/noggit/ui/FramelessWindow.hpp +++ b/src/noggit/ui/FramelessWindow.hpp @@ -5,6 +5,7 @@ #include #include +#include #include diff --git a/src/noggit/ui/GroundEffectsTool.cpp b/src/noggit/ui/GroundEffectsTool.cpp index 84bd958f..5758df9a 100644 --- a/src/noggit/ui/GroundEffectsTool.cpp +++ b/src/noggit/ui/GroundEffectsTool.cpp @@ -1,22 +1,31 @@ -#include -#include -#include -#include +#include +#include +#include +#include +#include #include -#include +#include +#include +#include #include -#include -#include +#include +#include #include -#include -#include +#include +#include #include #include #include +#include +#include +#include +#include +#include +#include +#include #include -#include namespace Noggit { @@ -627,6 +636,42 @@ namespace Noggit } } + bool GroundEffectsTool::render_active_sets_overlay() const + { + return isVisible() && !render_exclusion_map_overlay() && _render_active_sets->isChecked() && render_mode(); + } + + bool GroundEffectsTool::render_placement_map_overlay() const + { + return isVisible() && !render_exclusion_map_overlay() && _render_placement_map->isChecked() && render_mode(); + } + + bool GroundEffectsTool::render_exclusion_map_overlay() const + { + // return isVisible() && _render_exclusion_map->isChecked() && render_mode(); + return isVisible() && brush_mode() == ground_effect_brush_mode::exclusion; + } + + void GroundEffectsTool::change_radius(float change) + { + _effect_radius_slider->setValue(static_cast(_effect_radius_slider->value()) + change); + } + + + //Close event triggers, hide event. + void GroundEffectsTool::hideEvent(QHideEvent* event) + { + if (_map_view->_world) + { + _map_view->getWorld()->renderer()->getTerrainParamsUniformBlock()->draw_groundeffectid_overlay = false; + _map_view->getWorld()->renderer()->getTerrainParamsUniformBlock()->draw_groundeffect_layerid_overlay = false; + _map_view->getWorld()->renderer()->getTerrainParamsUniformBlock()->draw_noeffectdoodad_overlay = false; + _map_view->getWorld()->renderer()->markTerrainParamsUniformBlockDirty(); + } + + QWidget::hideEvent(event); + } + void GroundEffectsTool::setDoodadSlotFromBrowser(QString doodad_path) { const QFileInfo info(doodad_path); @@ -667,6 +712,11 @@ namespace Noggit delete _preview_renderer; } + float GroundEffectsTool::radius() const + { + return _effect_radius_slider->value(); + } + ground_effect_brush_mode GroundEffectsTool::brush_mode() const { if (!_brush_grup_box->isChecked()) @@ -684,6 +734,22 @@ namespace Noggit return ground_effect_brush_mode::none; } + bool GroundEffectsTool::render_mode() const + { + return _render_group_box->isChecked(); + } + + void GroundEffectsTool::delete_renderer() + { + delete _preview_renderer; + } + + void GroundEffectsTool::showEvent(QShowEvent* event) + { + QWidget::showEvent(event); + updateTerrainUniformParams(); + } + std::optional GroundEffectsTool::getSelectedGroundEffect() { int index = _effect_sets_list->currentIndex().row(); @@ -787,5 +853,30 @@ namespace Noggit LogError << "Couldn't find ground effect Id : " << effect_id << "in GroundEffectTexture.dbc" << std::endl; } } - } + + bool ground_effect_set::empty() const + { + return !ID; + } + + bool ground_effect_set::operator== (ground_effect_set* effect2) + { + return (TerrainType == effect2->TerrainType && Amount == effect2->Amount + && Doodads[0] == &effect2->Doodads[0] && Doodads[1] == &effect2->Doodads[1] + && Doodads[2] == &effect2->Doodads[2] && Doodads[3] == &effect2->Doodads[3] + && Weights[0] == effect2->Weights[0] && Weights[1] == effect2->Weights[1] + && Weights[2] == effect2->Weights[2] && Weights[3] == effect2->Weights[3] + ); + } + + bool ground_effect_doodad::empty() const + { + return filename.empty(); + } + + bool ground_effect_doodad::operator== (ground_effect_doodad* doodad2) + { + return filename == doodad2->filename; + } +} } diff --git a/src/noggit/ui/GroundEffectsTool.hpp b/src/noggit/ui/GroundEffectsTool.hpp index 45add575..2f1d67b8 100644 --- a/src/noggit/ui/GroundEffectsTool.hpp +++ b/src/noggit/ui/GroundEffectsTool.hpp @@ -2,188 +2,151 @@ #pragma once -#include -#include -#include +#include +#include #include -#include -#include -#include -#include -#include class World; class MapView; -class DBCFile::Record; -namespace Noggit +class QButtonGroup; +class QCheckBox; +class QComboBox; +class QGroupBox; +class QListWidget; +class QRadioButton; +class QSpinBox; + +namespace Noggit { - namespace Ui + namespace Ui + { + namespace Tools { - class texturing_tool; + class PreviewRenderer; - struct ground_effect_doodad - { - unsigned int ID = 0; - std::string filename = ""; - // Flag (useless in 3.3.5). - - bool empty() { return filename.empty(); }; - - bool operator== (ground_effect_doodad* doodad2) - { - return filename == doodad2->filename; - } - }; - - struct ground_effect_set - { - public: - void load_from_id(unsigned int effect_id); - - bool empty() { return !ID; }; - - // only ignores id and name (use filename to compare doodads) - bool operator== (ground_effect_set* effect2) - { - return (TerrainType == effect2->TerrainType && Amount == effect2->Amount - && Doodads[0] == &effect2->Doodads[0] && Doodads[1] == &effect2->Doodads[1] - && Doodads[2] == &effect2->Doodads[2] && Doodads[3] == &effect2->Doodads[3] - && Weights[0] == effect2->Weights[0] && Weights[1] == effect2->Weights[1] - && Weights[2] == effect2->Weights[2] && Weights[3] == effect2->Weights[3] - ); - } - - // Created by the user or auto generated. - std::string Name = ""; - - unsigned int ID = 0; - // TODO: can pack doodad and weight in a struct - ground_effect_doodad Doodads[4]; - unsigned int Weights[4]{ 1, 1, 1, 1 }; - unsigned int Amount = 0; - unsigned int TerrainType = 0; - }; - - enum class ground_effect_brush_mode - { - none, - exclusion, - effect - }; - - class GroundEffectsTool : public QWidget - { - Q_OBJECT - - public: - GroundEffectsTool(texturing_tool* texturing_tool, MapView* map_view, QWidget* parent = nullptr); - void updateTerrainUniformParams(); - // Delete renderer. - ~GroundEffectsTool(); - float radius() const - { - return _effect_radius_slider->value(); - } - ground_effect_brush_mode brush_mode() const; - bool render_mode() const - { - return _render_group_box->isChecked(); - } - void delete_renderer() - { - delete _preview_renderer; - } - - protected: - void showEvent(QShowEvent* event) override - { - QWidget::showEvent(event); - updateTerrainUniformParams(); - } - - //Close event triggers, hide event. - void hideEvent(QHideEvent* event) override - { - if (_map_view->_world) - { - _map_view->getWorld()->renderer()->getTerrainParamsUniformBlock()->draw_groundeffectid_overlay = false; - _map_view->getWorld()->renderer()->getTerrainParamsUniformBlock()->draw_groundeffect_layerid_overlay = false; - _map_view->getWorld()->renderer()->getTerrainParamsUniformBlock()->draw_noeffectdoodad_overlay = false; - _map_view->getWorld()->renderer()->markTerrainParamsUniformBlockDirty(); - } - - QWidget::hideEvent(event); - }; - - public: - void setDoodadSlotFromBrowser(QString doodad_path); - // Selected texture was changed. - void TextureChanged(); - - inline bool render_active_sets_overlay() const - { - return isVisible() && !render_exclusion_map_overlay() && _render_active_sets->isChecked() && render_mode(); - }; - - inline bool render_placement_map_overlay() const - { - return isVisible() && !render_exclusion_map_overlay() && _render_placement_map->isChecked() && render_mode(); - }; - - inline bool render_exclusion_map_overlay() const - { - // return isVisible() && _render_exclusion_map->isChecked() && render_mode(); - return isVisible() && brush_mode() == ground_effect_brush_mode::exclusion; - }; - - void change_radius(float change) - { - _effect_radius_slider->setValue(static_cast(_effect_radius_slider->value()) + change); - }; - - private: - std::optional getSelectedGroundEffect(); - std::optional getSelectedEffectColor(); - void setActiveGroundEffect(ground_effect_set const& effect); - void updateDoodadPreviewRender(int slot_index); - void scanTileForEffects(TileIndex tile_index); - void updateSetsList(); - void genEffectColors(); - - std::vector _loaded_effects; - // Store them for faster iteration on duplicates. - std::unordered_map _ground_effect_cache; - std::vector _effects_colors; - MapView* _map_view; - texturing_tool* _texturing_tool; - Tools::PreviewRenderer* _preview_renderer; - QGroupBox* _render_group_box; - QButtonGroup* _render_type_group; - // Render all the loaded effect sets for this texture in various colors. - QRadioButton* _render_active_sets; - // Only for the active/selected set of the current texture: - // - Render as red if set is present in the chunk and NOT the current active layer. - // - Render as green if set is present in the chunk and is the current active layer. - // - Render as black is set is not present. - QRadioButton* _render_placement_map; - // Render chunk units where effect doodads are disabled as white, rest as black. - // QRadioButton* _render_exclusion_map; - QCheckBox* _chkbox_merge_duplicates; - QListWidget* _effect_sets_list; - // For render previews. - QListWidget* _object_list; - // Weight and percentage customization. - QListWidget* _weight_list; - QSpinBox* _spinbox_doodads_amount; - QComboBox* _cbbox_terrain_type; - QCheckBox* _apply_override_cb; - QGroupBox* _brush_grup_box; - QButtonGroup* _brush_type_group; - QRadioButton* _paint_effect; - QRadioButton* _paint_exclusion; - Noggit::Ui::Tools::UiCommon::ExtendedSlider* _effect_radius_slider; - }; + namespace UiCommon + { + class ExtendedSlider; + } } -} \ No newline at end of file + + class texturing_tool; + + struct ground_effect_doodad + { + unsigned int ID = 0; + std::string filename = ""; + // Flag (useless in 3.3.5). + + bool empty() const;; + + bool operator== (ground_effect_doodad* doodad2); + }; + + struct ground_effect_set + { + public: + void load_from_id(unsigned int effect_id); + + bool empty() const;; + + // only ignores id and name (use filename to compare doodads) + bool operator== (ground_effect_set* effect2); + + // Created by the user or auto generated. + std::string Name = ""; + + unsigned int ID = 0; + // TODO: can pack doodad and weight in a struct + ground_effect_doodad Doodads[4]; + unsigned int Weights[4]{ 1, 1, 1, 1 }; + unsigned int Amount = 0; + unsigned int TerrainType = 0; + }; + + enum class ground_effect_brush_mode + { + none, + exclusion, + effect + }; + + class GroundEffectsTool : public QWidget + { + Q_OBJECT + + public: + GroundEffectsTool(texturing_tool* texturing_tool, MapView* map_view, QWidget* parent = nullptr); + void updateTerrainUniformParams(); + // Delete renderer. + ~GroundEffectsTool(); + float radius() const; + ground_effect_brush_mode brush_mode() const; + bool render_mode() const; + void delete_renderer(); + + protected: + void showEvent(QShowEvent* event) override; + + //Close event triggers, hide event. + void hideEvent(QHideEvent* event) override;; + + public: + void setDoodadSlotFromBrowser(QString doodad_path); + // Selected texture was changed. + void TextureChanged(); + + bool render_active_sets_overlay() const;; + + bool render_placement_map_overlay() const;; + + bool render_exclusion_map_overlay() const;; + + void change_radius(float change);; + + private: + std::optional getSelectedGroundEffect(); + std::optional getSelectedEffectColor(); + void setActiveGroundEffect(ground_effect_set const& effect); + void updateDoodadPreviewRender(int slot_index); + void scanTileForEffects(TileIndex tile_index); + void updateSetsList(); + void genEffectColors(); + + std::vector _loaded_effects; + // Store them for faster iteration on duplicates. + std::unordered_map _ground_effect_cache; + std::vector _effects_colors; + MapView* _map_view; + texturing_tool* _texturing_tool; + Tools::PreviewRenderer* _preview_renderer; + QGroupBox* _render_group_box; + QButtonGroup* _render_type_group; + // Render all the loaded effect sets for this texture in various colors. + QRadioButton* _render_active_sets; + // Only for the active/selected set of the current texture: + // - Render as red if set is present in the chunk and NOT the current active layer. + // - Render as green if set is present in the chunk and is the current active layer. + // - Render as black is set is not present. + QRadioButton* _render_placement_map; + // Render chunk units where effect doodads are disabled as white, rest as black. + // QRadioButton* _render_exclusion_map; + QCheckBox* _chkbox_merge_duplicates; + QListWidget* _effect_sets_list; + // For render previews. + QListWidget* _object_list; + // Weight and percentage customization. + QListWidget* _weight_list; + QSpinBox* _spinbox_doodads_amount; + QComboBox* _cbbox_terrain_type; + QCheckBox* _apply_override_cb; + QGroupBox* _brush_grup_box; + QButtonGroup* _brush_type_group; + QRadioButton* _paint_effect; + QRadioButton* _paint_exclusion; + Noggit::Ui::Tools::UiCommon::ExtendedSlider* _effect_radius_slider; + }; + } +} diff --git a/src/noggit/ui/Help.cpp b/src/noggit/ui/Help.cpp index b0622539..348384a5 100755 --- a/src/noggit/ui/Help.cpp +++ b/src/noggit/ui/Help.cpp @@ -1,12 +1,10 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). #include -#include #include #include #include -#include #include #include diff --git a/src/noggit/ui/Help.h b/src/noggit/ui/Help.h index 72edabc7..7b8bfec8 100755 --- a/src/noggit/ui/Help.h +++ b/src/noggit/ui/Help.h @@ -17,7 +17,7 @@ namespace Noggit help(QWidget* parent = nullptr); private: - inline void generate_hotkey_row(std::initializer_list&& hotkeys, const char* description, QFormLayout* layout); + void generate_hotkey_row(std::initializer_list&& hotkeys, const char* description, QFormLayout* layout); }; } } diff --git a/src/noggit/ui/HelperModels.cpp b/src/noggit/ui/HelperModels.cpp index 235ca3e0..fd0a6f93 100755 --- a/src/noggit/ui/HelperModels.cpp +++ b/src/noggit/ui/HelperModels.cpp @@ -1,9 +1,7 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). -#include - -#include #include +#include #include #include diff --git a/src/noggit/ui/MinimapCreator.cpp b/src/noggit/ui/MinimapCreator.cpp index 66936375..855e449a 100755 --- a/src/noggit/ui/MinimapCreator.cpp +++ b/src/noggit/ui/MinimapCreator.cpp @@ -1,32 +1,36 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). -#include "MinimapCreator.hpp" #include "FontAwesome.hpp" +#include "MinimapCreator.hpp" -#include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include -#include +#include +#include +#include +#include +#include +#include #include #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include #include -#include +#include +#include +#include +#include #include +#include +#include +#include namespace Noggit { @@ -872,6 +876,22 @@ namespace Noggit _radius_spin->setValue (_radius + change); } + float MinimapCreator::brushRadius() const + { + return _radius; + } + + //std::array* getSelectedTiles() { return &_render_settings.selected_tiles; }; + std::vector* MinimapCreator::getSelectedTiles() + { + return &_render_settings.selected_tiles; + } + + MinimapRenderSettings* MinimapCreator::getMinimapRenderSettings() + { + return &_render_settings; + } + QSize MinimapCreator::sizeHint() const { return QSize(width(), height()); @@ -1207,7 +1227,27 @@ namespace Noggit } - MinimapWMOModelFilterEntry::MinimapWMOModelFilterEntry(QWidget* parent) : QWidget(parent) + QString MinimapM2ModelFilterEntry::getFileName() const + { + return _filename->text(); + } + + void MinimapM2ModelFilterEntry::setFileName(const std::string& filename) + { + _filename->setText(QString(filename.c_str())); + } + + void MinimapM2ModelFilterEntry::setSizeCategory(float size_cat) + { + _size_category_spin->setValue(size_cat); + } + + float MinimapM2ModelFilterEntry::getSizeCategory() const + { + return static_cast(_size_category_spin->value()); + } + + MinimapWMOModelFilterEntry::MinimapWMOModelFilterEntry(QWidget* parent) : QWidget(parent) { setAttribute(Qt::WA_TranslucentBackground); auto layout = new QHBoxLayout(this); @@ -1217,6 +1257,16 @@ namespace Noggit _filename->setEnabled(false); } + QString MinimapWMOModelFilterEntry::getFileName() const + { + return _filename->text(); + } + + void MinimapWMOModelFilterEntry::setFileName(const std::string& filename) + { + _filename->setText(QString(filename.c_str())); + } + MinimapInstanceFilterEntry::MinimapInstanceFilterEntry(QWidget* parent) : QWidget(parent) { setAttribute(Qt::WA_TranslucentBackground); @@ -1226,5 +1276,15 @@ namespace Noggit layout->setContentsMargins(5, 2, 5, 2); } + uint32_t MinimapInstanceFilterEntry::getUid() const + { + return _uid; + } + + void MinimapInstanceFilterEntry::setUid(uint32_t uid) + { + _uid = uid; + _uid_label->setText(QString::fromStdString(std::to_string(uid))); + } } } diff --git a/src/noggit/ui/MinimapCreator.hpp b/src/noggit/ui/MinimapCreator.hpp index 7c20d37e..1864d05e 100644 --- a/src/noggit/ui/MinimapCreator.hpp +++ b/src/noggit/ui/MinimapCreator.hpp @@ -1,34 +1,26 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). #pragma once -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - #include -#include +#include + +#include +#include class MapView; class World; - +class QDoubleSpinBox; +class QLabel; +class QLineEdit; +class QListWidget; +class QSlider; namespace Noggit { - namespace Ui { + class minimap_widget; class MinimapCreator : public QWidget { @@ -38,12 +30,12 @@ namespace Noggit void changeRadius(float change); - float brushRadius() const { return _radius; } + float brushRadius() const; //std::array* getSelectedTiles() { return &_render_settings.selected_tiles; }; - std::vector* getSelectedTiles() { return &_render_settings.selected_tiles; }; + std::vector* getSelectedTiles();; - MinimapRenderSettings* getMinimapRenderSettings() { return &_render_settings; }; + MinimapRenderSettings* getMinimapRenderSettings();; QSize sizeHint() const override; @@ -81,10 +73,10 @@ namespace Noggit public: MinimapM2ModelFilterEntry(QWidget* parent = nullptr); - QString getFileName() { return _filename->text(); }; - void setFileName(const std::string& filename) { _filename->setText(QString(filename.c_str())); }; - void setSizeCategory(float size_cat) {_size_category_spin->setValue(size_cat); }; - float getSizeCategory() { return static_cast(_size_category_spin->value()); }; + QString getFileName() const;; + void setFileName(const std::string& filename);; + void setSizeCategory(float size_cat);; + float getSizeCategory() const;; private: QLineEdit* _filename; @@ -96,8 +88,8 @@ namespace Noggit public: MinimapWMOModelFilterEntry(QWidget* parent = nullptr); - QString getFileName() { return _filename->text(); }; - void setFileName(const std::string& filename) { _filename->setText(QString(filename.c_str())); }; + QString getFileName() const;; + void setFileName(const std::string& filename);; private: QLineEdit* _filename; @@ -108,8 +100,8 @@ namespace Noggit public: MinimapInstanceFilterEntry(QWidget* parent = nullptr); - uint32_t getUid() { return _uid; }; - void setUid(uint32_t uid) { _uid = uid; _uid_label->setText(QString::fromStdString(std::to_string(uid))); }; + uint32_t getUid() const;; + void setUid(uint32_t uid);; private: uint32_t _uid; diff --git a/src/noggit/ui/ModelImport.cpp b/src/noggit/ui/ModelImport.cpp index 41ee75f2..a4b61273 100755 --- a/src/noggit/ui/ModelImport.cpp +++ b/src/noggit/ui/ModelImport.cpp @@ -1,19 +1,24 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). -#include - #include #include #include -#include +#include #include +#include + +#include #include #include #include -#include +#include #include +#include +#include +#include +#include namespace Noggit { diff --git a/src/noggit/ui/ModelImport.h b/src/noggit/ui/ModelImport.h index 5ca7766a..eb0b9ad1 100755 --- a/src/noggit/ui/ModelImport.h +++ b/src/noggit/ui/ModelImport.h @@ -2,12 +2,13 @@ #pragma once -#include -#include #include class MapView; +class QLineEdit; +class QListWidget; + namespace Noggit { namespace Ui diff --git a/src/noggit/ui/ObjectEditor.cpp b/src/noggit/ui/ObjectEditor.cpp index 17b54442..7f60c348 100644 --- a/src/noggit/ui/ObjectEditor.cpp +++ b/src/noggit/ui/ObjectEditor.cpp @@ -1,38 +1,46 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). -#include +#include "noggit/Action.hpp" +#include "noggit/ActionManager.hpp" #include -#include +#include +#include +#include +#include +#include #include -#include // WMOInstance -#include +#include +#include +#include #include #include #include #include -#include +#include #include -#include -#include -#include "noggit/ActionManager.hpp" -#include "noggit/Action.hpp" -#include +#include // WMOInstance +#include +#include +#include +#include #include #include #include -#include -#include -#include -#include +#include #include +#include +#include +#include +#include +#include #include +#include #include -#include #include -#include #include +#include namespace Noggit { @@ -597,6 +605,26 @@ namespace Noggit _radius_spin->setValue (_radius + change); } + float object_editor::brushRadius() const + { + return _radius; + } + + float object_editor::drag_selection_depth() const + { + return _drag_selection_depth; + } + + int object_editor::clipboardSize() const + { + return _model_instance_created.size(); + } + + std::vector object_editor::getClipboard() const& + { + return _model_instance_created; + } + void object_editor::showImportModels() { modelImport->show(); diff --git a/src/noggit/ui/ObjectEditor.h b/src/noggit/ui/ObjectEditor.h index 8e594001..9bc112f3 100755 --- a/src/noggit/ui/ObjectEditor.h +++ b/src/noggit/ui/ObjectEditor.h @@ -2,19 +2,17 @@ #pragma once #include -#include -#include -#include #include -#include -#include -#include -#include -#include class MapView; class QButtonGroup; +class QComboBox; +class QDoubleSpinBox; +class QGroupBox; +class QLabel; +class QSettings; +class QSlider; class World; namespace Noggit @@ -37,6 +35,9 @@ enum ModelPasteMode namespace Noggit { + struct BoolToggleProperty; + struct object_paste_params; + namespace Ui { class object_editor : public QWidget @@ -70,13 +71,13 @@ namespace Noggit void changeRadius(float change); - float brushRadius() const { return _radius; } + float brushRadius() const; - float drag_selection_depth() const { return _drag_selection_depth; } + float drag_selection_depth() const; - int clipboardSize() const { return _model_instance_created.size(); } + int clipboardSize() const; - std::vector getClipboard() const& { return _model_instance_created; } + std::vector getClipboard() const&; model_import *modelImport; rotation_editor* rotationEditor; diff --git a/src/noggit/ui/RotationEditor.cpp b/src/noggit/ui/RotationEditor.cpp index b42f171a..3e220602 100755 --- a/src/noggit/ui/RotationEditor.cpp +++ b/src/noggit/ui/RotationEditor.cpp @@ -2,18 +2,16 @@ #include -#include +#include +#include +#include +#include +#include #include #include -#include #include -#include -#include -#include -#include -#include -#include +#include #include #include diff --git a/src/noggit/ui/RotationEditor.h b/src/noggit/ui/RotationEditor.h index 45b0a8e6..bee004f9 100755 --- a/src/noggit/ui/RotationEditor.h +++ b/src/noggit/ui/RotationEditor.h @@ -1,13 +1,12 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). #pragma once -#include -#include #include -#include class World; +class QDoubleSpinBox; + namespace Noggit { namespace Ui diff --git a/src/noggit/ui/ShaderTool.cpp b/src/noggit/ui/ShaderTool.cpp index 38fc2f23..4ee6d0b9 100755 --- a/src/noggit/ui/ShaderTool.cpp +++ b/src/noggit/ui/ShaderTool.cpp @@ -1,30 +1,27 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). -#include #include #include #include -#include -#include +#include +#include +#include +#include #include #include -#include #include -#include -#include +#include +#include +#include #include #include -#include -#include -#include +#include #define _USE_MATH_DEFINES #include -#include - namespace Noggit { namespace Ui @@ -202,6 +199,11 @@ namespace Noggit _speed_slider->setValue(speed); } + float ShaderTool::brushRadius() const + { + return _radius_slider->value(); + } + void ShaderTool::pickColor(World* world, glm::vec3 const& pos) { glm::vec3 color = world->pickShaderColor(pos); @@ -265,6 +267,16 @@ namespace Noggit return QSize(215, height()); } + Noggit::Ui::Tools::ImageMaskSelector* ShaderTool::getImageMaskSelector() + { + return _image_mask_group; + } + + QImage* ShaderTool::getMaskImage() + { + return &_mask_image; + } + void ShaderTool::updateMaskImage() { @@ -276,6 +288,26 @@ namespace Noggit emit _map_view->trySetBrushTexture(&_mask_image, this); } + glm::vec4& ShaderTool::shaderColor() + { + return _color; + } + + Noggit::Ui::Tools::UiCommon::ExtendedSlider* ShaderTool::getRadiusSlider() + { + return _radius_slider; + } + + Noggit::Ui::Tools::UiCommon::ExtendedSlider* ShaderTool::getSpeedSlider() + { + return _speed_slider; + } + + QDial* ShaderTool::getMaskOrientationDial() + { + return _image_mask_group->getMaskOrientationDial(); + } + QJsonObject ShaderTool::toJSON() { QJsonObject json; diff --git a/src/noggit/ui/ShaderTool.hpp b/src/noggit/ui/ShaderTool.hpp index 4ff891dd..25a6bc07 100755 --- a/src/noggit/ui/ShaderTool.hpp +++ b/src/noggit/ui/ShaderTool.hpp @@ -1,29 +1,41 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). #pragma once -#include -#include -#include -#include #include -#include - -#include -#include -#include -#include -#include #include +namespace color_widgets +{ + class ColorListWidget; + class ColorSelector; + class ColorWheel; + class GradientSlider; + class HueSlider; +} + class World; class MapView; +class QCheckBox; +class QDial; +class QSpinBox; + namespace Noggit { namespace Ui { + namespace Tools + { + namespace UiCommon + { + class ExtendedSlider; + } + + class ImageMaskSelector; + } + class ShaderTool : public QWidget { Q_OBJECT @@ -39,19 +51,19 @@ namespace Noggit void changeSpeed(float change); void setSpeed(float speed); - float brushRadius() const { return _radius_slider->value(); } + float brushRadius() const; QSize sizeHint() const override; - Noggit::Ui::Tools::ImageMaskSelector* getImageMaskSelector() { return _image_mask_group; }; - QImage* getMaskImage() { return &_mask_image; } + Noggit::Ui::Tools::ImageMaskSelector* getImageMaskSelector();; + QImage* getMaskImage(); void updateMaskImage(); - glm::vec4& shaderColor() { return _color; }; + glm::vec4& shaderColor();; - Noggit::Ui::Tools::UiCommon::ExtendedSlider* getRadiusSlider() { return _radius_slider; }; - Noggit::Ui::Tools::UiCommon::ExtendedSlider* getSpeedSlider() { return _speed_slider; }; - QDial* getMaskOrientationDial() { return _image_mask_group->getMaskOrientationDial(); }; + Noggit::Ui::Tools::UiCommon::ExtendedSlider* getRadiusSlider();; + Noggit::Ui::Tools::UiCommon::ExtendedSlider* getSpeedSlider();; + QDial* getMaskOrientationDial();; QJsonObject toJSON(); void fromJSON(QJsonObject const& json); diff --git a/src/noggit/ui/TerrainTool.cpp b/src/noggit/ui/TerrainTool.cpp index 5ad7f43b..71a27650 100755 --- a/src/noggit/ui/TerrainTool.cpp +++ b/src/noggit/ui/TerrainTool.cpp @@ -2,20 +2,22 @@ #include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - #include -#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include #define _USE_MATH_DEFINES #include @@ -345,6 +347,31 @@ namespace Noggit _speed_slider->setValue(speed); } + float TerrainTool::getSpeed() const + { + return _speed_slider->value(); + } + + Noggit::Ui::Tools::UiCommon::ExtendedSlider* TerrainTool::getRadiusSlider() + { + return _radius_slider; + } + + Noggit::Ui::Tools::UiCommon::ExtendedSlider* TerrainTool::getInnerRadiusSlider() + { + return _inner_radius_slider; + } + + Noggit::Ui::Tools::UiCommon::ExtendedSlider* TerrainTool::getSpeedSlider() + { + return _speed_slider; + } + + QDial* TerrainTool::getMaskOrientationDial() + { + return _image_mask_group->getMaskOrientationDial(); + } + void TerrainTool::changeOrientation (float change) { setOrientation (_vertex_orientation._ + change); @@ -382,6 +409,31 @@ namespace Noggit } } + float TerrainTool::brushRadius() const + { + return static_cast(_radius_slider->value()); + } + + float TerrainTool::innerRadius() const + { + return static_cast(_inner_radius_slider->value()); + } + + void TerrainTool::storeCursorPos(glm::vec3* cursor_pos) + { + _cursor_pos = cursor_pos; + } + + Noggit::Ui::Tools::ImageMaskSelector* TerrainTool::getImageMaskSelector() + { + return _image_mask_group; + } + + QImage* TerrainTool::getMaskImage() + { + return &_mask_image; + } + void TerrainTool::changeAngle (float change) { setAngle (_vertex_angle._ + change); diff --git a/src/noggit/ui/TerrainTool.hpp b/src/noggit/ui/TerrainTool.hpp index a98ce169..55c66f47 100755 --- a/src/noggit/ui/TerrainTool.hpp +++ b/src/noggit/ui/TerrainTool.hpp @@ -3,26 +3,35 @@ #pragma once #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include + #include #include +#include class World; class MapView; +class QButtonGroup; +class QCheckBox; +class QDial; +class QGroupBox; +class QSlider; + namespace Noggit { namespace Ui { + namespace Tools + { + namespace UiCommon + { + class ExtendedSlider; + } + + class ImageMaskSelector; + } + class TerrainTool : public QWidget { Q_OBJECT @@ -42,12 +51,12 @@ namespace Noggit void setOrientation (float orientation); void setAngle (float angle); void setSpeed (float speed); - float getSpeed () { return _speed_slider->value(); }; + float getSpeed () const;; - Noggit::Ui::Tools::UiCommon::ExtendedSlider* getRadiusSlider() { return _radius_slider; }; - Noggit::Ui::Tools::UiCommon::ExtendedSlider* getInnerRadiusSlider() { return _inner_radius_slider; }; - Noggit::Ui::Tools::UiCommon::ExtendedSlider* getSpeedSlider() { return _speed_slider; }; - QDial* getMaskOrientationDial() { return _image_mask_group->getMaskOrientationDial(); }; + Noggit::Ui::Tools::UiCommon::ExtendedSlider* getRadiusSlider();; + Noggit::Ui::Tools::UiCommon::ExtendedSlider* getInnerRadiusSlider();; + Noggit::Ui::Tools::UiCommon::ExtendedSlider* getSpeedSlider();; + QDial* getMaskOrientationDial();; // vertex edit only functions void moveVertices (World*, float dt); @@ -57,14 +66,14 @@ namespace Noggit void changeAngle (float change); void setOrientRelativeTo (World*, glm::vec3 const& pos); - float brushRadius() const { return static_cast(_radius_slider->value()); } - float innerRadius() const { return static_cast(_inner_radius_slider->value()); } + float brushRadius() const; + float innerRadius() const; - void storeCursorPos (glm::vec3* cursor_pos) { _cursor_pos = cursor_pos; } + void storeCursorPos (glm::vec3* cursor_pos); - Noggit::Ui::Tools::ImageMaskSelector* getImageMaskSelector() { return _image_mask_group; }; + Noggit::Ui::Tools::ImageMaskSelector* getImageMaskSelector();; - QImage* getMaskImage() { return &_mask_image; }; + QImage* getMaskImage();; QSize sizeHint() const override; diff --git a/src/noggit/ui/TextureList.cpp b/src/noggit/ui/TextureList.cpp index 25b431e7..465f149b 100755 --- a/src/noggit/ui/TextureList.cpp +++ b/src/noggit/ui/TextureList.cpp @@ -2,14 +2,11 @@ #include -#include - -#include +#include +#include #include #include -#include -#include - +#include namespace Noggit { diff --git a/src/noggit/ui/TextureList.hpp b/src/noggit/ui/TextureList.hpp index ef44d9d3..b2c18103 100755 --- a/src/noggit/ui/TextureList.hpp +++ b/src/noggit/ui/TextureList.hpp @@ -1,6 +1,5 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). -#include #include class QMouseEvent; diff --git a/src/noggit/ui/TexturePicker.cpp b/src/noggit/ui/TexturePicker.cpp index 1a2b1f0e..edd02a12 100644 --- a/src/noggit/ui/TexturePicker.cpp +++ b/src/noggit/ui/TexturePicker.cpp @@ -2,14 +2,13 @@ #include -#include -#include +#include +#include #include #include #include +#include #include -#include -#include #include #include diff --git a/src/noggit/ui/TexturePicker.h b/src/noggit/ui/TexturePicker.h index 6615537c..230f1406 100755 --- a/src/noggit/ui/TexturePicker.h +++ b/src/noggit/ui/TexturePicker.h @@ -2,12 +2,13 @@ #pragma once -#include +#include #include #include #include +class MapChunk; class QGridLayout; namespace Noggit diff --git a/src/noggit/ui/TexturingGUI.cpp b/src/noggit/ui/TexturingGUI.cpp index bfe5c7f7..bcd0a677 100644 --- a/src/noggit/ui/TexturingGUI.cpp +++ b/src/noggit/ui/TexturingGUI.cpp @@ -1,31 +1,24 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). #include - -#include -#include -#include -#include -#include -#include - -#include -#include - +#include +#include +#include #include // TextureManager, Texture #include -#include -#include -#include +#include -#include #include #include #include #include #include +#include +#include +#include + namespace Noggit { namespace Ui diff --git a/src/noggit/ui/Toolbar.cpp b/src/noggit/ui/Toolbar.cpp index e20a7611..af14e7d3 100755 --- a/src/noggit/ui/Toolbar.cpp +++ b/src/noggit/ui/Toolbar.cpp @@ -1,7 +1,8 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). -#include #include +#include +#include namespace Noggit { diff --git a/src/noggit/ui/Toolbar.h b/src/noggit/ui/Toolbar.h index dea4fd68..84b3f354 100755 --- a/src/noggit/ui/Toolbar.h +++ b/src/noggit/ui/Toolbar.h @@ -2,15 +2,14 @@ #pragma once -#include +#include +#include #include #include -#include -#include - #include +#include namespace Noggit { diff --git a/src/noggit/ui/UidFixWindow.cpp b/src/noggit/ui/UidFixWindow.cpp index 55f96a39..021f8585 100755 --- a/src/noggit/ui/UidFixWindow.cpp +++ b/src/noggit/ui/UidFixWindow.cpp @@ -2,7 +2,6 @@ #include -#include #include #include #include diff --git a/src/noggit/ui/UidFixWindow.hpp b/src/noggit/ui/UidFixWindow.hpp index 824a0989..b23cdddb 100755 --- a/src/noggit/ui/UidFixWindow.hpp +++ b/src/noggit/ui/UidFixWindow.hpp @@ -1,21 +1,12 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). #pragma once +#include #include #include -#include - class World; -enum class uid_fix_mode -{ - none, - max_uid, - fix_all_fail_on_model_loading_error, - fix_all_fuckporting_edition -}; - namespace Noggit { namespace Ui diff --git a/src/noggit/ui/Water.cpp b/src/noggit/ui/Water.cpp index 71e02a5b..07aaa0d9 100755 --- a/src/noggit/ui/Water.cpp +++ b/src/noggit/ui/Water.cpp @@ -1,22 +1,19 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). #include -#include -#include -#include +#include +#include #include #include -#include -#include +#include +#include #include -#include +#include #include #include #include -#include #include -#include #include namespace Noggit @@ -367,6 +364,36 @@ namespace Noggit _angled_mode.toggle(); } + float water::brushRadius() const + { + return _radius; + } + + float water::angle() const + { + return _angle; + } + + float water::orientation() const + { + return _orientation; + } + + bool water::angled_mode() const + { + return _angled_mode.get(); + } + + bool water::use_ref_pos() const + { + return _locked.get(); + } + + glm::vec3 water::ref_pos() const + { + return _lock_pos; + } + float water::get_opacity_factor() const { switch (_opacity_mode) diff --git a/src/noggit/ui/Water.h b/src/noggit/ui/Water.h index 53a7c5a4..ebe3976c 100755 --- a/src/noggit/ui/Water.h +++ b/src/noggit/ui/Water.h @@ -4,8 +4,6 @@ #include #include -#include -#include class QDoubleSpinBox; class QGroupBox; @@ -19,6 +17,8 @@ class QButtonGroup; namespace Noggit { + struct unsigned_int_property; + namespace Ui { class water : public QWidget @@ -48,12 +48,12 @@ namespace Noggit void toggle_lock(); void toggle_angled_mode(); - float brushRadius() const { return _radius; } - float angle() const { return _angle; } - float orientation() const { return _orientation; } - bool angled_mode() const { return _angled_mode.get(); } - bool use_ref_pos() const { return _locked.get(); } - glm::vec3 ref_pos() const { return _lock_pos; } + float brushRadius() const; + float angle() const; + float orientation() const; + bool angled_mode() const; + bool use_ref_pos() const; + glm::vec3 ref_pos() const; QSize sizeHint() const override; diff --git a/src/noggit/ui/WeightListWidgetItem.cpp b/src/noggit/ui/WeightListWidgetItem.cpp index d717f65e..89c42ad6 100644 --- a/src/noggit/ui/WeightListWidgetItem.cpp +++ b/src/noggit/ui/WeightListWidgetItem.cpp @@ -2,6 +2,13 @@ #include +#include +#include +#include +#include +#include +#include + namespace noggit { namespace Ui @@ -76,4 +83,4 @@ namespace noggit setLayout(wrapper_layout); } } -} \ No newline at end of file +} diff --git a/src/noggit/ui/WeightListWidgetItem.hpp b/src/noggit/ui/WeightListWidgetItem.hpp index a9a27936..dab2fd21 100644 --- a/src/noggit/ui/WeightListWidgetItem.hpp +++ b/src/noggit/ui/WeightListWidgetItem.hpp @@ -1,15 +1,14 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include -#include #include +class QHBoxLayout; +class QFrame; +class QLabel; +class QPushButton; +class QSpinBox; +class QVBoxLayout; + namespace noggit { namespace Ui @@ -36,4 +35,4 @@ namespace noggit QFrame* line; }; } -} \ No newline at end of file +} diff --git a/src/noggit/ui/ZoneIDBrowser.cpp b/src/noggit/ui/ZoneIDBrowser.cpp index 050fa5ce..b2c355bb 100755 --- a/src/noggit/ui/ZoneIDBrowser.cpp +++ b/src/noggit/ui/ZoneIDBrowser.cpp @@ -1,39 +1,34 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include #include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include -#include namespace Noggit { diff --git a/src/noggit/ui/ZoneIDBrowser.h b/src/noggit/ui/ZoneIDBrowser.h index 9e12ff14..1d3c0a64 100755 --- a/src/noggit/ui/ZoneIDBrowser.h +++ b/src/noggit/ui/ZoneIDBrowser.h @@ -2,25 +2,26 @@ #pragma once -#include -#include - #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include +class QCheckBox; +class QComboBox; +class QDoubleSpinBox; +class QLabel; +class QLineEdit; +class QPushButton; +class QSlider; +class QSpinBox; +class QTreeWidget; +class QTreeWidgetItem; + +namespace Noggit::Ui::Tools::MapCreationWizard::Ui +{ + class LocaleDBCEntry; +} + namespace Noggit { namespace Ui diff --git a/src/noggit/ui/hole_tool.cpp b/src/noggit/ui/hole_tool.cpp index 19e8ae84..b166a71e 100755 --- a/src/noggit/ui/hole_tool.cpp +++ b/src/noggit/ui/hole_tool.cpp @@ -1,9 +1,11 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). #include "hole_tool.hpp" -#include -#include +#include #include +#include + +#include namespace Noggit { @@ -53,6 +55,11 @@ namespace Noggit _radius_spin->setValue (_radius + change); } + float hole_tool::brushRadius() const + { + return _radius; + } + void hole_tool::setRadius(float radius) { _radius_spin->setValue(radius); diff --git a/src/noggit/ui/hole_tool.hpp b/src/noggit/ui/hole_tool.hpp index f9e35d29..75e31165 100755 --- a/src/noggit/ui/hole_tool.hpp +++ b/src/noggit/ui/hole_tool.hpp @@ -3,12 +3,9 @@ #pragma once #include -#include -#include -#include -#include -#include +class QDoubleSpinBox; +class QSlider; namespace Noggit { @@ -23,7 +20,7 @@ namespace Noggit void changeRadius(float change); - float brushRadius() const { return _radius; } + float brushRadius() const; void setRadius(float radius); diff --git a/src/noggit/ui/minimap_widget.cpp b/src/noggit/ui/minimap_widget.cpp index 1628c1cc..1ef02a72 100755 --- a/src/noggit/ui/minimap_widget.cpp +++ b/src/noggit/ui/minimap_widget.cpp @@ -1,18 +1,14 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). -#include - - -#include -#include -#include -#include -#include - -#include -#include #include -#include +#include +#include +#include + +#include +#include +#include +#include namespace Noggit { @@ -115,6 +111,65 @@ namespace Noggit return QSize (512, 512); } + const World* minimap_widget::world(World* const world_) + { + _world = world_; + update(); + return _world; + } + + const World* minimap_widget::world() const + { + return _world; + } + + const bool& minimap_widget::draw_skies(const bool& draw_skies_) + { + _draw_skies = draw_skies_; + update(); + return _draw_skies; + } + + const bool& minimap_widget::draw_skies() const + { + return _draw_skies; + } + + const bool& minimap_widget::draw_boundaries(const bool& draw_boundaries_) + { + _draw_boundaries = draw_boundaries_; + update(); + return _draw_boundaries; + } + + const bool& minimap_widget::draw_boundaries() const + { + return _draw_boundaries; + } + + const std::vector* minimap_widget::use_selection(std::vector* selection_) + { + _use_selection = selection_; + _selected_tiles = selection_; + update(); + return _selected_tiles; + } + + const std::vector* minimap_widget::selection() const + { + return _selected_tiles; + } + + void minimap_widget::camera(Noggit::Camera* camera) + { + _camera = camera; + } + + void minimap_widget::set_resizeable(bool state) + { + _resizeable = state; + } + //! \todo Only redraw stuff as told in event. // called by _minimap->update() // \todo : massive performance drop after clicking the minimap once until moving cursor out of frame, paintEvent gets called repeatidly diff --git a/src/noggit/ui/minimap_widget.hpp b/src/noggit/ui/minimap_widget.hpp index 80bbd22b..7e9985b8 100755 --- a/src/noggit/ui/minimap_widget.hpp +++ b/src/noggit/ui/minimap_widget.hpp @@ -3,7 +3,6 @@ #pragma once #include -#include #include namespace math @@ -29,24 +28,20 @@ namespace Noggit virtual QSize sizeHint() const override; - inline const World* world (World* const world_) - { _world = world_; update(); return _world; } - inline const World* world() const { return _world; } + const World* world (World* const world_); + const World* world() const; - inline const bool& draw_skies (const bool& draw_skies_) - { _draw_skies = draw_skies_; update(); return _draw_skies; } - inline const bool& draw_skies() const { return _draw_skies; } + const bool& draw_skies (const bool& draw_skies_); + const bool& draw_skies() const; - inline const bool& draw_boundaries (const bool& draw_boundaries_) - { _draw_boundaries = draw_boundaries_; update(); return _draw_boundaries; } - inline const bool& draw_boundaries() const { return _draw_boundaries; } + const bool& draw_boundaries (const bool& draw_boundaries_); + const bool& draw_boundaries() const; - inline const std::vector* use_selection (std::vector* selection_) - { _use_selection = selection_; _selected_tiles = selection_; update(); return _selected_tiles; } - inline const std::vector* selection() const { return _selected_tiles; } + const std::vector* use_selection (std::vector* selection_); + const std::vector* selection() const; - inline void camera (Noggit::Camera* camera) { _camera = camera; } - void set_resizeable(bool state) { _resizeable = state; }; + void camera (Noggit::Camera* camera); + void set_resizeable(bool state);; protected: virtual void paintEvent (QPaintEvent* paint_event) override; diff --git a/src/noggit/ui/object_palette.cpp b/src/noggit/ui/object_palette.cpp index 6f8eaf02..f33a0383 100755 --- a/src/noggit/ui/object_palette.cpp +++ b/src/noggit/ui/object_palette.cpp @@ -2,26 +2,27 @@ #include "object_palette.hpp" -#include -#include -#include -#include #include +#include +#include +#include +#include +#include -#include -#include -#include -#include -#include +#include +#include +#include +#include #include #include -#include -#include -#include +#include +#include +#include +#include +#include -#include #include -#include +#include namespace Noggit diff --git a/src/noggit/ui/object_palette.hpp b/src/noggit/ui/object_palette.hpp index a2e0c23f..56ab1947 100755 --- a/src/noggit/ui/object_palette.hpp +++ b/src/noggit/ui/object_palette.hpp @@ -4,14 +4,11 @@ #define NOGGIT_OBJECT_PALETTE_HPP #include -#include -#include + #include -#include + #include - - - +#include class QGridLayout; class QPushButton; @@ -22,64 +19,73 @@ class QListWidget; class QPoint; class MapView; +namespace Noggit::Project +{ + class NoggitProject; +} namespace Noggit { - namespace Ui + namespace Ui + { + namespace Tools { - class current_texture; - - class ObjectList : public QListWidget - { - public: - ObjectList(QWidget* parent); - void mouseMoveEvent(QMouseEvent* event) override; - void mousePressEvent(QMouseEvent* event) override; - - private: - QPoint _start_pos; - - }; - - class ObjectPalette : public widget - { - Q_OBJECT - - public: - ObjectPalette(MapView* map_view, std::shared_ptr Project, QWidget* parent); - - ~ObjectPalette(); - - void addObjectFromAssetBrowser(); - void addObjectByFilename(QString const& filename, bool save_palette = true); - void LoadSavedPalette(); - - void SavePalette(); - - void removeObject(QString filename); - - void removeSelectedTexture(); - - void dragEnterEvent(QDragEnterEvent* event) override; - void dropEvent(QDropEvent* event) override; - - signals: - void selected(std::string); - - private: - - QGridLayout* layout; - - ObjectList* _object_list; - QPushButton* _add_button; - QPushButton* _remove_button; - std::unordered_set _object_paths; - MapView* _map_view; - Noggit::Ui::Tools::PreviewRenderer* _preview_renderer; - std::shared_ptr _project; - - }; + class PreviewRenderer; } + + class current_texture; + + class ObjectList : public QListWidget + { + public: + ObjectList(QWidget* parent); + void mouseMoveEvent(QMouseEvent* event) override; + void mousePressEvent(QMouseEvent* event) override; + + private: + QPoint _start_pos; + + }; + + class ObjectPalette : public widget + { + Q_OBJECT + + public: + ObjectPalette(MapView* map_view, std::shared_ptr Project, QWidget* parent); + + ~ObjectPalette(); + + void addObjectFromAssetBrowser(); + void addObjectByFilename(QString const& filename, bool save_palette = true); + void LoadSavedPalette(); + + void SavePalette(); + + void removeObject(QString filename); + + void removeSelectedTexture(); + + void dragEnterEvent(QDragEnterEvent* event) override; + void dropEvent(QDropEvent* event) override; + + signals: + void selected(std::string); + + private: + + QGridLayout* layout; + + ObjectList* _object_list; + QPushButton* _add_button; + QPushButton* _remove_button; + std::unordered_set _object_paths; + MapView* _map_view; + Noggit::Ui::Tools::PreviewRenderer* _preview_renderer; + std::shared_ptr _project; + + }; + } } #endif //NOGGIT_OBJECT_PALETTE_HPP diff --git a/src/noggit/ui/texture_palette_small.hpp b/src/noggit/ui/texture_palette_small.hpp index d31feae5..fc7ff161 100755 --- a/src/noggit/ui/texture_palette_small.hpp +++ b/src/noggit/ui/texture_palette_small.hpp @@ -3,12 +3,11 @@ #pragma once #include -#include + #include -#include + #include - - +#include class QGridLayout; class QPushButton; @@ -18,9 +17,13 @@ class QMouseEvent; class QListWidget; class QPoint; - namespace Noggit { + namespace Project + { + class NoggitProject; + } + namespace Ui { class current_texture; diff --git a/src/noggit/ui/texture_swapper.cpp b/src/noggit/ui/texture_swapper.cpp index cc3b15ec..50a95df8 100755 --- a/src/noggit/ui/texture_swapper.cpp +++ b/src/noggit/ui/texture_swapper.cpp @@ -1,18 +1,20 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). +#include +#include +#include +#include #include #include #include -#include -#include -#include -#include - -#include +#include +#include #include +#include #include #include +#include namespace Noggit { @@ -136,14 +138,49 @@ namespace Noggit ); } + std::optional const& texture_swapper::texture_to_swap() const + { + return _texture_to_swap; + } + + float texture_swapper::radius() const + { + return _radius; + } + + bool texture_swapper::entireChunk() const + { + return _swap_entire_chunk->isChecked(); + } + + bool texture_swapper::entireTile() const + { + return _swap_entire_tile->isChecked(); + } + void texture_swapper::change_radius(float change) { _radius_spin->setValue(_radius + change); } + bool texture_swapper::brush_mode() const + { + return _brush_mode_group->isChecked(); + } + + void texture_swapper::toggle_brush_mode() + { + _brush_mode_group->setChecked(!_brush_mode_group->isChecked()); + } + void texture_swapper::set_texture(std::string const& filename) { _texture_to_swap = std::move(scoped_blp_texture_reference(filename, _world->getRenderContext())); } + + current_texture* const texture_swapper::texture_display() + { + return _texture_to_swap_display; + } } } diff --git a/src/noggit/ui/texture_swapper.hpp b/src/noggit/ui/texture_swapper.hpp index 1a495fa9..b67b4a48 100755 --- a/src/noggit/ui/texture_swapper.hpp +++ b/src/noggit/ui/texture_swapper.hpp @@ -1,23 +1,25 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). #pragma once -#include #include -#include -#include -#include -#include #include #include class World; class MapView; +class QCheckBox; +class QDoubleSpinBox; +class QGroupBox; +class QSlider; + namespace Noggit { namespace Ui { + class current_texture; + class texture_swapper : public QWidget { public: @@ -26,41 +28,23 @@ namespace Noggit , MapView* map_view ); - std::optional const& texture_to_swap() const - { - return _texture_to_swap; - } + std::optional const& texture_to_swap() const; - float radius() const - { - return _radius; - } + float radius() const; - bool entireChunk() const - { - return _swap_entire_chunk->isChecked(); - } + bool entireChunk() const; - bool entireTile() const - { - return _swap_entire_tile->isChecked(); - } + bool entireTile() const; void change_radius(float change); - bool brush_mode() const - { - return _brush_mode_group->isChecked(); - } + bool brush_mode() const; - void toggle_brush_mode() - { - _brush_mode_group->setChecked(!_brush_mode_group->isChecked()); - } + void toggle_brush_mode(); void set_texture(std::string const& filename); - current_texture* const texture_display() { return _texture_to_swap_display; } + current_texture* const texture_display(); private: std::optional _texture_to_swap; diff --git a/src/noggit/ui/texturing_tool.cpp b/src/noggit/ui/texturing_tool.cpp index f1baf5ee..3e6ac573 100644 --- a/src/noggit/ui/texturing_tool.cpp +++ b/src/noggit/ui/texturing_tool.cpp @@ -1,33 +1,37 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). -#include -#include - -#include -#include +#include +#include #include -#include +#include +#include +#include #include #include +#include #include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include +#include #include -#include -#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #define _USE_MATH_DEFINES #include -#include namespace Noggit { @@ -576,6 +580,11 @@ namespace Noggit } } + GroundEffectsTool* texturing_tool::getGroundEffectsTool() + { + return _ground_effect_tool; + } + void texturing_tool::setRadius(float radius) { _radius_slider->setValue(radius); @@ -659,6 +668,26 @@ namespace Noggit } } + Noggit::Ui::Tools::UiCommon::ExtendedSlider* texturing_tool::getRadiusSlider() + { + return _radius_slider; + } + + Noggit::Ui::Tools::UiCommon::ExtendedSlider* texturing_tool::getInnerRadiusSlider() + { + return _hardness_slider; + } + + Noggit::Ui::Tools::UiCommon::ExtendedSlider* texturing_tool::getSpeedSlider() + { + return _pressure_slider; + } + + QDial* texturing_tool::getMaskOrientationDial() + { + return _image_mask_group->getMaskOrientationDial(); + } + void texturing_tool::set_pressure(float pressure) { if (_texturing_mode == texturing_mode::paint) @@ -777,6 +806,16 @@ namespace Noggit } } + Brush const& texturing_tool::texture_brush() const + { + return _texture_brush; + } + + float texturing_tool::alpha_target() const + { + return static_cast(_brush_level); + } + void texturing_tool::change_tex_flag(World* world, glm::vec3 const& pos, bool add, scoped_blp_texture_reference texture) { std::uint32_t flag = 0; @@ -802,11 +841,34 @@ namespace Noggit world->change_texture_flag(pos, texture, flag, add); } + texture_swapper* const texturing_tool::texture_swap_tool() + { + return _texture_switcher; + } + QSize texturing_tool::sizeHint() const { return QSize(215, height()); } + Noggit::Ui::Tools::ImageMaskSelector* texturing_tool::getImageMaskSelector() + { + return _image_mask_group; + } + + QImage* texturing_tool::getMaskImage() + { + return &_mask_image; + } + + texturing_mode texturing_tool::getTexturingMode() const + { + if (_ground_effect_tool->isVisible()) + return texturing_mode::ground_effect; + else + return _texturing_mode; + } + QJsonObject texturing_tool::toJSON() { QJsonObject json; @@ -878,5 +940,90 @@ namespace Noggit _texture_switcher->set_texture(tex_to_swap_path.toStdString()); } - } + + QPushButton* const texturing_tool::heightmappingApplyGlobalButton() + { + return _heightmapping_apply_global_btn; + } + + QPushButton* const texturing_tool::heightmappingApplyAdtButton() + { + return _heightmapping_apply_adt_btn; + } + + texture_heightmapping_data& texturing_tool::getCurrentHeightMappingSetting() + { + return textureHeightmappingData; + } + + OpacitySlider::OpacitySlider(Qt::Orientation orientation, QWidget* parent) + : QSlider(orientation, parent) + { + setFixedWidth(35); + } + + void OpacitySlider::paintEvent(QPaintEvent* event) + { + // QSlider::paintEvent(event); + + // chat-gpt code, can probably be improved... + + QPainter p(this); + QStyleOptionSlider opt; + initStyleOption(&opt); + opt.subControls = QStyle::SC_SliderGroove | QStyle::SC_SliderHandle; + if (tickPosition() != NoTicks) + opt.subControls |= QStyle::SC_SliderTickmarks; + if (isSliderDown()) { + opt.activeSubControls = QStyle::SC_SliderHandle; + opt.state |= QStyle::State_Sunken; + } + else { + opt.activeSubControls = QStyle::SC_None; + } + + // Draw the groove with a linear gradient + QRect grooveRect = style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderGroove, this); + grooveRect.setLeft((width() - 35) / 2); + grooveRect.setRight((width() + 35) / 2); + QLinearGradient gradient(grooveRect.topLeft(), grooveRect.bottomLeft()); + gradient.setColorAt(0, Qt::black); + gradient.setColorAt(1, Qt::white); + p.fillRect(grooveRect.adjusted(0, 0, -1, -1), gradient); + + // Draw the handle with red color + QRect handleRect = style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderHandle, this); + handleRect.setLeft((width() - 35) / 2); + handleRect.setRight((width() + 35) / 2); + handleRect.setHeight(5); // Set handle height to 5 + p.setBrush(Qt::red); + p.setPen(Qt::NoPen); + p.drawRect(handleRect); + + // Draw the ticks if needed + if (tickPosition() != NoTicks) { + opt.subControls = QStyle::SC_SliderTickmarks; + style()->drawComplexControl(QStyle::CC_Slider, &opt, &p, this); + } + + // QSlider::paintEvent() source code : + /* + Q_D(QSlider); + QPainter p(this); + QStyleOptionSlider opt; + initStyleOption(&opt); + opt.subControls = QStyle::SC_SliderGroove | QStyle::SC_SliderHandle; + if (d->tickPosition != NoTicks) + opt.subControls |= QStyle::SC_SliderTickmarks; + if (d->pressedControl) { + opt.activeSubControls = d->pressedControl; + opt.state |= QStyle::State_Sunken; + } + else { + opt.activeSubControls = d->hoverControl; + } + style()->drawComplexControl(QStyle::CC_Slider, &opt, &p, this); + */ + } +} } diff --git a/src/noggit/ui/texturing_tool.hpp b/src/noggit/ui/texturing_tool.hpp index 483db5a9..cc6f40e7 100644 --- a/src/noggit/ui/texturing_tool.hpp +++ b/src/noggit/ui/texturing_tool.hpp @@ -5,27 +5,21 @@ #include #include #include -#include -#include -#include -#include -#include -#include -#include -#include +#include #include -#include -#include #include #include -#include -#include -#include class World; class MapView; -class DBCFile::Record; + +class QCheckBox; +class QDoubleSpinBox; +class QGroupBox; +class QPushButton; +class QSpinBox; +class QTabWidget; inline constexpr const char* STRING_EMPTY_DISPLAY = "-NONE-"; @@ -35,79 +29,25 @@ namespace Noggit { class CheckBox; class current_texture; + class GroundEffectsTool; class texture_swapper; + namespace Tools + { + namespace UiCommon + { + class ExtendedSlider; + } + + class ImageMaskSelector; + } class OpacitySlider : public QSlider{ public: - OpacitySlider(Qt::Orientation orientation, QWidget * parent = nullptr) - : QSlider(orientation, parent) { - setFixedWidth(35); - } + OpacitySlider(Qt::Orientation orientation, QWidget * parent = nullptr); protected: - void paintEvent(QPaintEvent * event) override { - // QSlider::paintEvent(event); - - // chat-gpt code, can probably be improved... - - QPainter p(this); - QStyleOptionSlider opt; - initStyleOption(&opt); - opt.subControls = QStyle::SC_SliderGroove | QStyle::SC_SliderHandle; - if (tickPosition() != NoTicks) - opt.subControls |= QStyle::SC_SliderTickmarks; - if (isSliderDown()) { - opt.activeSubControls = QStyle::SC_SliderHandle; - opt.state |= QStyle::State_Sunken; - } - else { - opt.activeSubControls = QStyle::SC_None; - } - - // Draw the groove with a linear gradient - QRect grooveRect = style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderGroove, this); - grooveRect.setLeft((width() - 35) / 2); - grooveRect.setRight((width() + 35) / 2); - QLinearGradient gradient(grooveRect.topLeft(), grooveRect.bottomLeft()); - gradient.setColorAt(0, Qt::black); - gradient.setColorAt(1, Qt::white); - p.fillRect(grooveRect.adjusted(0, 0, -1, -1), gradient); - - // Draw the handle with red color - QRect handleRect = style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderHandle, this); - handleRect.setLeft((width() - 35) / 2); - handleRect.setRight((width() + 35) / 2); - handleRect.setHeight(5); // Set handle height to 5 - p.setBrush(Qt::red); - p.setPen(Qt::NoPen); - p.drawRect(handleRect); - - // Draw the ticks if needed - if (tickPosition() != NoTicks) { - opt.subControls = QStyle::SC_SliderTickmarks; - style()->drawComplexControl(QStyle::CC_Slider, &opt, &p, this); - } - - // QSlider::paintEvent() source code : - /* - Q_D(QSlider); - QPainter p(this); - QStyleOptionSlider opt; - initStyleOption(&opt); - opt.subControls = QStyle::SC_SliderGroove | QStyle::SC_SliderHandle; - if (d->tickPosition != NoTicks) - opt.subControls |= QStyle::SC_SliderTickmarks; - if (d->pressedControl) { - opt.activeSubControls = d->pressedControl; - opt.state |= QStyle::State_Sunken; - } - else { - opt.activeSubControls = d->hoverControl; - } - style()->drawComplexControl(QStyle::CC_Slider, &opt, &p, this); - */ - } + void paintEvent(QPaintEvent * event) override; }; enum class texturing_mode @@ -146,10 +86,7 @@ namespace Noggit void toggle_tool(); - GroundEffectsTool* getGroundEffectsTool() - { - return _ground_effect_tool; - }; + GroundEffectsTool* getGroundEffectsTool();; void change_radius (float change); void setRadius(float radius); @@ -162,46 +99,34 @@ namespace Noggit void change_spray_size (float change); void change_spray_pressure (float change); - Noggit::Ui::Tools::UiCommon::ExtendedSlider* getRadiusSlider() { return _radius_slider; }; - Noggit::Ui::Tools::UiCommon::ExtendedSlider* getInnerRadiusSlider() { return _hardness_slider; }; - Noggit::Ui::Tools::UiCommon::ExtendedSlider* getSpeedSlider() { return _pressure_slider; }; - QDial* getMaskOrientationDial() { return _image_mask_group->getMaskOrientationDial(); }; + Noggit::Ui::Tools::UiCommon::ExtendedSlider* getRadiusSlider();; + Noggit::Ui::Tools::UiCommon::ExtendedSlider* getInnerRadiusSlider();; + Noggit::Ui::Tools::UiCommon::ExtendedSlider* getSpeedSlider();; + QDial* getMaskOrientationDial();; void paint (World* world, glm::vec3 const& pos, float dt, scoped_blp_texture_reference texture); - Brush const& texture_brush() const - { - return _texture_brush; - } + Brush const& texture_brush() const; - float alpha_target() const - { - return static_cast(_brush_level); - } + float alpha_target() const; current_texture* _current_texture; - texture_swapper* const texture_swap_tool() { return _texture_switcher; } + texture_swapper* const texture_swap_tool(); QSize sizeHint() const override; - Noggit::Ui::Tools::ImageMaskSelector* getImageMaskSelector() { return _image_mask_group; }; - QImage* getMaskImage() { return &_mask_image; } - inline texturing_mode getTexturingMode() const - { - if (_ground_effect_tool->isVisible()) - return texturing_mode::ground_effect; - else - return _texturing_mode; - }; + Noggit::Ui::Tools::ImageMaskSelector* getImageMaskSelector();; + QImage* getMaskImage(); + texturing_mode getTexturingMode() const;; void updateMaskImage(); QJsonObject toJSON(); void fromJSON(QJsonObject const& json); - QPushButton* const heightmappingApplyGlobalButton() { return _heightmapping_apply_global_btn; } - QPushButton* const heightmappingApplyAdtButton() { return _heightmapping_apply_adt_btn; } - texture_heightmapping_data& getCurrentHeightMappingSetting() {return textureHeightmappingData; } + QPushButton* const heightmappingApplyGlobalButton(); + QPushButton* const heightmappingApplyAdtButton(); + texture_heightmapping_data& getCurrentHeightMappingSetting(); signals: void texturePaletteToggled(); diff --git a/src/noggit/ui/tools/ActionHistoryNavigator/ActionHistoryNavigator.cpp b/src/noggit/ui/tools/ActionHistoryNavigator/ActionHistoryNavigator.cpp index 479424cb..d7372712 100755 --- a/src/noggit/ui/tools/ActionHistoryNavigator/ActionHistoryNavigator.cpp +++ b/src/noggit/ui/tools/ActionHistoryNavigator/ActionHistoryNavigator.cpp @@ -4,6 +4,10 @@ #include #include #include + +#include +#include +#include #include #include #include diff --git a/src/noggit/ui/tools/ActionHistoryNavigator/ActionHistoryNavigator.hpp b/src/noggit/ui/tools/ActionHistoryNavigator/ActionHistoryNavigator.hpp index 6d7fe5ac..f37c6ef7 100755 --- a/src/noggit/ui/tools/ActionHistoryNavigator/ActionHistoryNavigator.hpp +++ b/src/noggit/ui/tools/ActionHistoryNavigator/ActionHistoryNavigator.hpp @@ -4,10 +4,10 @@ #define NOGGIT_ACTIONHISTORYNAVIGATOR_HPP #include -#include -#include -#include +class QListWidget; +class QButtonGroup; +class QLabel; namespace Noggit { diff --git a/src/noggit/ui/tools/AreaTriggerEditor/AreaTriggerEditor.cpp b/src/noggit/ui/tools/AreaTriggerEditor/AreaTriggerEditor.cpp index dfa190c0..5e2dd641 100644 --- a/src/noggit/ui/tools/AreaTriggerEditor/AreaTriggerEditor.cpp +++ b/src/noggit/ui/tools/AreaTriggerEditor/AreaTriggerEditor.cpp @@ -2,29 +2,32 @@ #include "AreaTriggerEditor.hpp" -#include #include +#include #include -#include #include +#include #include #include +#include #include +#include +#include #include #include +#include +#include #include +#include +#include +#include +#include #include -#include -#include #include #include -#include -#include -#include -#include #include namespace Noggit::Ui::Tools diff --git a/src/noggit/ui/tools/AssetBrowser/BrowserModelView.cpp b/src/noggit/ui/tools/AssetBrowser/BrowserModelView.cpp index e3a24642..f2279fe4 100644 --- a/src/noggit/ui/tools/AssetBrowser/BrowserModelView.cpp +++ b/src/noggit/ui/tools/AssetBrowser/BrowserModelView.cpp @@ -1,19 +1,16 @@ #include "BrowserModelView.hpp" -#include -#include -#include + #include +#include -#include -#include -#include -#include -#include - +#include +#include +#include +#include +#include using namespace Noggit::Ui::Tools::AssetBrowser; - ModelViewer::ModelViewer(QWidget* parent, Noggit::NoggitRenderContext context) : PreviewRenderer(0, 0, context, parent) , look(false) @@ -115,6 +112,16 @@ void ModelViewer::setModel(std::string const& filename) _last_selected_model = filename; } +void Noggit::Ui::Tools::AssetBrowser::ModelViewer::setMoveSensitivity(float s) +{ + _move_sensitivity = s / 30.0f; +} + +float Noggit::Ui::Tools::AssetBrowser::ModelViewer::getMoveSensitivity() const +{ + return _move_sensitivity; +} + float ModelViewer::aspect_ratio() const { return float (width()) / float (height()); @@ -293,6 +300,21 @@ void ModelViewer::setActiveDoodadSet(const std::string& filename, const std::str } } +std::string& Noggit::Ui::Tools::AssetBrowser::ModelViewer::getLastSelectedModel() +{ + return _last_selected_model; +} + +bool Noggit::Ui::Tools::AssetBrowser::ModelViewer::hasHeightForWidth() const +{ + return true; +} + +int Noggit::Ui::Tools::AssetBrowser::ModelViewer::heightForWidth(int w) const +{ + return w; +} + ModelViewer::~ModelViewer() { disconnect(_gl_guard_connection); diff --git a/src/noggit/ui/tools/AssetBrowser/BrowserModelView.hpp b/src/noggit/ui/tools/AssetBrowser/BrowserModelView.hpp index f5dc9445..024f20b7 100644 --- a/src/noggit/ui/tools/AssetBrowser/BrowserModelView.hpp +++ b/src/noggit/ui/tools/AssetBrowser/BrowserModelView.hpp @@ -2,23 +2,17 @@ #define NOGGIT_ModelView_HPP #include -#include -#include -#include -#include -#include -#include #include #include #include #include -#include -#include -#include -#include -#include #include +class QWheelEvent; +class QMouseEvent; +class QFocusEvent; +class QKeyEvent; + namespace Noggit { namespace Ui::Tools::AssetBrowser @@ -33,14 +27,14 @@ namespace Noggit , Noggit::NoggitRenderContext context = Noggit::NoggitRenderContext::ASSET_BROWSER); void setModel(std::string const& filename) override; - void setMoveSensitivity(float s) { _move_sensitivity = s / 30.0f; }; - float getMoveSensitivity() { return _move_sensitivity; }; + void setMoveSensitivity(float s);; + float getMoveSensitivity() const;; QStringList getDoodadSetNames(std::string const& filename); void setActiveDoodadSet(std::string const& filename, std::string const& doodadset_name); - std::string& getLastSelectedModel() { return _last_selected_model; }; + std::string& getLastSelectedModel();; - bool hasHeightForWidth() const override { return true; }; - int heightForWidth(int w) const override { return w; }; + bool hasHeightForWidth() const override;; + int heightForWidth(int w) const override;; ~ModelViewer() override; diff --git a/src/noggit/ui/tools/AssetBrowser/Ui/AssetBrowser.cpp b/src/noggit/ui/tools/AssetBrowser/Ui/AssetBrowser.cpp index 6571e389..92e3d308 100755 --- a/src/noggit/ui/tools/AssetBrowser/Ui/AssetBrowser.cpp +++ b/src/noggit/ui/tools/AssetBrowser/Ui/AssetBrowser.cpp @@ -1,24 +1,30 @@ #include "AssetBrowser.hpp" -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include -#include -#include -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include +#include #include +#include using namespace Noggit::Ui::Tools::AssetBrowser::Ui; using namespace Noggit::Ui; @@ -466,6 +472,11 @@ AssetBrowserWidget::~AssetBrowserWidget() delete _preview_renderer; } +std::string const& Noggit::Ui::Tools::AssetBrowser::Ui::AssetBrowserWidget::getFilename() const +{ + return _selected_path; +} + void AssetBrowserWidget::set_browse_mode(asset_browse_mode browse_mode) { if (_browse_mode == browse_mode) @@ -529,3 +540,36 @@ void AssetBrowserWidget::keyPressEvent(QKeyEvent *event) } } + +QList NoggitExpendableFilterProxyModel::findIndices() const +{ + QList ret; + for (auto iter = 0; iter < rowCount(); iter++) + { + auto childIndex = index(iter, 0, QModelIndex()); + ret << recursivelyFindIndices(childIndex); + } + return ret; +} + +bool NoggitExpendableFilterProxyModel::rowAccepted(int source_row, const QModelIndex& source_parent) const +{ + return filterAcceptsRow(source_row, source_parent); +} + +QList NoggitExpendableFilterProxyModel::recursivelyFindIndices(const QModelIndex& ind) const +{ + QList ret; + if (rowAccepted(ind.row(), ind.parent())) + { + ret << ind; + } + else + { + for (auto iter = 0; iter < rowCount(ind); iter++) + { + ret << recursivelyFindIndices(index(iter, 0, ind)); + } + } + return ret; +} diff --git a/src/noggit/ui/tools/AssetBrowser/Ui/AssetBrowser.hpp b/src/noggit/ui/tools/AssetBrowser/Ui/AssetBrowser.hpp index 1aa5b16a..9b6c4733 100755 --- a/src/noggit/ui/tools/AssetBrowser/Ui/AssetBrowser.hpp +++ b/src/noggit/ui/tools/AssetBrowser/Ui/AssetBrowser.hpp @@ -1,58 +1,32 @@ #ifndef NOGGIT_ASSETBROWSER_HPP #define NOGGIT_ASSETBROWSER_HPP -#include -#include -#include -#include - #include -#include #include #include #include #include +namespace Ui +{ + class AssetBrowser; + class AssetBrowserOverlay; +} + class MapView; +class QStandardItemModel; + // custom model that makes the searched children expend, credit to https://stackoverflow.com/questions/56781145/expand-specific-items-in-a-treeview-during-filtering class NoggitExpendableFilterProxyModel : public QSortFilterProxyModel { Q_OBJECT public: - QList findIndices() const - { - QList ret; - for (auto iter = 0; iter < rowCount(); iter++) - { - auto childIndex = index(iter, 0, QModelIndex()); - ret << recursivelyFindIndices(childIndex); - } - return ret; - } + QList findIndices() const; - - bool rowAccepted(int source_row, const QModelIndex& source_parent) const - { - return filterAcceptsRow(source_row, source_parent); - } + bool rowAccepted(int source_row, const QModelIndex& source_parent) const; private: - QList recursivelyFindIndices(const QModelIndex& ind) const - { - QList ret; - if (rowAccepted(ind.row(), ind.parent())) - { - ret << ind; - } - else - { - for (auto iter = 0; iter < rowCount(ind); iter++) - { - ret << recursivelyFindIndices(index(iter, 0, ind)); - } - } - return ret; - } + QList recursivelyFindIndices(const QModelIndex& ind) const; }; namespace Noggit::Ui::Tools::AssetBrowser @@ -74,9 +48,15 @@ namespace Noggit::Ui::Tools::AssetBrowser namespace Noggit { - namespace Ui::Tools::AssetBrowser::Ui + namespace Ui::Tools { - + class PreviewRenderer; + namespace AssetBrowser::Ui + { + namespace Model + { + class TreeManager; + } class AssetBrowserWidget : public QMainWindow { @@ -84,7 +64,7 @@ namespace Noggit public: AssetBrowserWidget(MapView* map_view, QWidget* parent = nullptr); ~AssetBrowserWidget(); - std::string const& getFilename() { return _selected_path; }; + std::string const& getFilename() const;; void set_browse_mode(asset_browse_mode browse_mode); @@ -105,7 +85,7 @@ namespace Noggit void updateModelData(); void recurseDirectory(Model::TreeManager& tree_mgr, const QString& s_dir, const QString& project_dir); - inline bool validateBrowseMode(const QString& wow_file_path); + bool validateBrowseMode(const QString& wow_file_path); // commented objects that shouldn't be placed on the map, still accessible through Show all const QMap brosweModeLabels = { @@ -128,6 +108,7 @@ namespace Noggit }; } + } } diff --git a/src/noggit/ui/tools/AssetBrowser/Ui/Model/TreeManager.cpp b/src/noggit/ui/tools/AssetBrowser/Ui/Model/TreeManager.cpp index 4897f3ab..8c30b682 100755 --- a/src/noggit/ui/tools/AssetBrowser/Ui/Model/TreeManager.cpp +++ b/src/noggit/ui/tools/AssetBrowser/Ui/Model/TreeManager.cpp @@ -1,9 +1,20 @@ #include "TreeManager.hpp" +#include using namespace Noggit::Ui::Tools::AssetBrowser::Ui::Model; +Noggit::Ui::Tools::AssetBrowser::Ui::Model::TreeManager::TreeManager(QStandardItem* root) + : root(root), sep(QLatin1Char('/')) +{ +} + +Noggit::Ui::Tools::AssetBrowser::Ui::Model::TreeManager::TreeManager(QStandardItemModel* model) + : root(model->invisibleRootItem()), sep(QLatin1Char('/')) +{ +} + QStandardItem* TreeManager::addItem(QString path) { QStandardItem * item = root; diff --git a/src/noggit/ui/tools/AssetBrowser/Ui/Model/TreeManager.hpp b/src/noggit/ui/tools/AssetBrowser/Ui/Model/TreeManager.hpp index 52a70429..cd433c6b 100755 --- a/src/noggit/ui/tools/AssetBrowser/Ui/Model/TreeManager.hpp +++ b/src/noggit/ui/tools/AssetBrowser/Ui/Model/TreeManager.hpp @@ -3,10 +3,11 @@ #include -#include - +#include #include -#include + +class QStandardItemModel; +class QStandardItem; namespace Noggit { @@ -17,8 +18,8 @@ namespace Noggit Q_DISABLE_COPY(TreeManager) public: - explicit TreeManager(QStandardItem* root) : root(root), sep(QLatin1Char('/')) {} - explicit TreeManager(QStandardItemModel* model) : root(model->invisibleRootItem()), sep(QLatin1Char('/')) {} + explicit TreeManager(QStandardItem* root); + explicit TreeManager(QStandardItemModel* model); QStandardItem* addItem(QString path); @@ -32,9 +33,6 @@ namespace Noggit QStandardItem* find(QString path); }; } - } - - #endif //NOGGIT_TREEMANAGER_HPP diff --git a/src/noggit/ui/tools/BrushStack/BrushStack.cpp b/src/noggit/ui/tools/BrushStack/BrushStack.cpp index e5b8d14c..5d3a7639 100755 --- a/src/noggit/ui/tools/BrushStack/BrushStack.cpp +++ b/src/noggit/ui/tools/BrushStack/BrushStack.cpp @@ -1,10 +1,17 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). #include "BrushStack.hpp" +#include "BrushStackItem.hpp" #include +#include +#include +#include +#include -#include +#include +#include #include +#include using namespace Noggit::Ui::Tools; @@ -272,6 +279,21 @@ float BrushStack::getSpeed() return _ui.speedSlider->value(); } +bool Noggit::Ui::Tools::BrushStack::getBrushMode() const +{ + return _ui.sculptRadio->isChecked(); +} + +bool Noggit::Ui::Tools::BrushStack::getRandomizeRotation() const +{ + return _ui.randomizeRotation->isChecked(); +} + +BrushStackItem* Noggit::Ui::Tools::BrushStack::getActiveBrushItem() +{ + return _active_item; +} + void BrushStack::changeRotation(int change) { int orientation = _ui.brushRotation->value() + change; diff --git a/src/noggit/ui/tools/BrushStack/BrushStack.hpp b/src/noggit/ui/tools/BrushStack/BrushStack.hpp index 5c0f21a8..b7771aa2 100755 --- a/src/noggit/ui/tools/BrushStack/BrushStack.hpp +++ b/src/noggit/ui/tools/BrushStack/BrushStack.hpp @@ -4,20 +4,22 @@ #define NOGGIT_BRUSHSTACK_HPP #include -#include "BrushStackItem.hpp" #include -#include -#include - #include +#include class MapView; class World; +class QButtonGroup; +class QComboBox; + namespace Noggit::Ui::Tools { + class BrushStackItem; + class BrushStack : public QWidget { public: @@ -33,9 +35,9 @@ namespace Noggit::Ui::Tools float getRadius(); float getInnerRadius(); float getSpeed(); - bool getBrushMode() { return _ui.sculptRadio->isChecked(); }; - bool getRandomizeRotation() { return _ui.randomizeRotation->isChecked(); }; - BrushStackItem* getActiveBrushItem() { return _active_item; }; + bool getBrushMode() const;; + bool getRandomizeRotation() const;; + BrushStackItem* getActiveBrushItem();; QJsonObject toJSON(); void fromJSON(QJsonObject const& json); diff --git a/src/noggit/ui/tools/BrushStack/BrushStackItem.cpp b/src/noggit/ui/tools/BrushStack/BrushStackItem.cpp index d951e462..bc2eee09 100755 --- a/src/noggit/ui/tools/BrushStack/BrushStackItem.cpp +++ b/src/noggit/ui/tools/BrushStack/BrushStackItem.cpp @@ -1,12 +1,22 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). #include "BrushStackItem.hpp" +#include +#include +#include +#include #include #include +#include +#include +#include +#include +#include +#include #include -#include -#include -#include + +#include +#include using namespace Noggit::Ui::Tools; @@ -321,6 +331,36 @@ void BrushStackItem::syncSliders(double radius, double inner_radius, double spee } } +bool Noggit::Ui::Tools::BrushStackItem::isRadiusAffecting() const +{ + return _is_radius_affecting->isChecked(); +} + +bool Noggit::Ui::Tools::BrushStackItem::isInnerRadiusAffecting() const +{ + return _is_inner_radius_affecting->isChecked(); +} + +bool Noggit::Ui::Tools::BrushStackItem::isMaskRotationAffecting() const +{ + return _is_mask_rotation_affecting->isChecked(); +} + +bool Noggit::Ui::Tools::BrushStackItem::isSpeedAffecting() const +{ + return _is_speed_affecting->isChecked(); +} + +QToolButton* Noggit::Ui::Tools::BrushStackItem::getActiveButton() +{ + return _ui.brushNameLabel; +} + +bool Noggit::Ui::Tools::BrushStackItem::isAffecting() const +{ + return _ui.contentWidget->isEnabled(); +} + void BrushStackItem::setRadius(float radius) { switch(_tool_widget.index()) diff --git a/src/noggit/ui/tools/BrushStack/BrushStackItem.hpp b/src/noggit/ui/tools/BrushStack/BrushStackItem.hpp index 01ec05bb..abdc4191 100755 --- a/src/noggit/ui/tools/BrushStack/BrushStackItem.hpp +++ b/src/noggit/ui/tools/BrushStack/BrushStackItem.hpp @@ -3,102 +3,108 @@ #ifndef NOGGIT_BRUSHSTACKITEM_HPP #define NOGGIT_BRUSHSTACKITEM_HPP +#include +#include + +#include + +#include + #include #include -#include -#include #include -#include + #include #include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - class World; -namespace Noggit::Ui::Tools +class QCheckBox; +class QToolButton; + +namespace Noggit::Ui { - using operation_type = std::variant - < Noggit::Ui::TerrainTool* - , Noggit::Ui::flatten_blur_tool* - , Noggit::Ui::texturing_tool* - , Noggit::Ui::ShaderTool* - >; + class TerrainTool; + struct tileset_chooser; + class flatten_blur_tool; + class texturing_tool; + class ShaderTool; - //! \note Keep in same order as variant! - enum eTools + namespace Tools { - eRaiseLower, - eFlattenBlur, - eTexturing, - eShader - }; + using operation_type = std::variant + < Noggit::Ui::TerrainTool* + , Noggit::Ui::flatten_blur_tool* + , Noggit::Ui::texturing_tool* + , Noggit::Ui::ShaderTool* + >; - class BrushStackItem : public ReorderableVerticalBox - { - Q_OBJECT - public: - BrushStackItem(QWidget* parent = nullptr); + //! \note Keep in same order as variant! + enum eTools + { + eRaiseLower, + eFlattenBlur, + eTexturing, + eShader + }; - void setTool(operation_type tool); - QWidget* getTool(); - void setRadius(float radius); - void setInnerRadius(float inner_radius); - void setSpeed(float speed); - void setMaskRotation(int rot); - void setBrushMode(bool sculpt); - void execute(glm::vec3 const& cursor_pos, World* world, float dt, bool mod_shift_down, bool mod_alt_down, bool mod_ctrl_down, bool is_under_map); - void syncSliders(double radius, double inner_radius, double speed, int rot, int brushMode); + class BrushStackItem : public ReorderableVerticalBox + { + Q_OBJECT + public: + BrushStackItem(QWidget* parent = nullptr); - bool isRadiusAffecting() { return _is_radius_affecting->isChecked(); }; - bool isInnerRadiusAffecting() { return _is_inner_radius_affecting->isChecked(); }; - bool isMaskRotationAffecting() { return _is_mask_rotation_affecting->isChecked(); }; - bool isSpeedAffecting() { return _is_speed_affecting->isChecked(); }; - QToolButton* getActiveButton() { return _ui.brushNameLabel; }; + void setTool(operation_type tool); + QWidget* getTool(); + void setRadius(float radius); + void setInnerRadius(float inner_radius); + void setSpeed(float speed); + void setMaskRotation(int rot); + void setBrushMode(bool sculpt); + void execute(glm::vec3 const& cursor_pos, World* world, float dt, bool mod_shift_down, bool mod_alt_down, bool mod_ctrl_down, bool is_under_map); + void syncSliders(double radius, double inner_radius, double speed, int rot, int brushMode); - bool isAffecting() const { return _ui.contentWidget->isEnabled(); }; + bool isRadiusAffecting() const;; + bool isInnerRadiusAffecting() const;; + bool isMaskRotationAffecting() const;; + bool isSpeedAffecting() const;; + QToolButton* getActiveButton();; - bool isMaskEnabled(); - void updateMask(); + bool isAffecting() const;; + + bool isMaskEnabled(); + void updateMask(); - QJsonObject toJSON(); - void fromJSON(QJsonObject const& json); + QJsonObject toJSON(); + void fromJSON(QJsonObject const& json); - signals: - void requestDelete(BrushStackItem* item); - void activated(BrushStackItem* item); - void settingsChanged(BrushStackItem* item); + signals: + void requestDelete(BrushStackItem* item); + void activated(BrushStackItem* item); + void settingsChanged(BrushStackItem* item); - private: - ::Ui::brushStackItem _ui; - QIcon _expanded_icon; - QIcon _collapsed_icon; - QIcon _enabled_icon; - QIcon _disabled_icon; + private: + ::Ui::brushStackItem _ui; + QIcon _expanded_icon; + QIcon _collapsed_icon; + QIcon _enabled_icon; + QIcon _disabled_icon; - operation_type _tool_widget; - QWidget* _settings_popup; - QCheckBox* _is_radius_affecting; - QCheckBox* _is_inner_radius_affecting; - QCheckBox* _is_mask_rotation_affecting; - QCheckBox* _is_speed_affecting; + operation_type _tool_widget; + QWidget* _settings_popup; + QCheckBox* _is_radius_affecting; + QCheckBox* _is_inner_radius_affecting; + QCheckBox* _is_mask_rotation_affecting; + QCheckBox* _is_speed_affecting; - std::optional _selected_texture; - Noggit::Ui::tileset_chooser* _texture_palette = nullptr; - bool _is_texture_dirty = false; + std::optional _selected_texture; + Noggit::Ui::tileset_chooser* _texture_palette = nullptr; + bool _is_texture_dirty = false; - }; + }; + } } #endif //NOGGIT_BRUSHSTACKITEM_HPP diff --git a/src/noggit/ui/tools/ChunkManipulator/ChunkClipboard.cpp b/src/noggit/ui/tools/ChunkManipulator/ChunkClipboard.cpp index f1862f26..2c5c4b6c 100755 --- a/src/noggit/ui/tools/ChunkManipulator/ChunkClipboard.cpp +++ b/src/noggit/ui/tools/ChunkManipulator/ChunkClipboard.cpp @@ -2,7 +2,13 @@ #include "ChunkClipboard.hpp" +#include +#include +#include +#include +#include #include +#include #include #include @@ -19,7 +25,6 @@ ChunkClipboard::ChunkClipboard(World* world, QObject* parent) void ChunkClipboard::selectRange(glm::vec3 const& cursor_pos, float radius, ChunkSelectionMode mode) { - switch (mode) { case ChunkSelectionMode::SELECT: @@ -205,3 +210,20 @@ void ChunkClipboard::pasteSelection(glm::vec3 const& pos, ChunkPasteFlags flags) { } + +[[nodiscard]] +ChunkCopyFlags Noggit::Ui::Tools::ChunkManipulator::ChunkClipboard::copyParams() const +{ + return _copy_flags; +} + +void Noggit::Ui::Tools::ChunkManipulator::ChunkClipboard::setCopyParams(ChunkCopyFlags flags) +{ + _copy_flags = flags; +} + +[[nodiscard]] +std::set const& Noggit::Ui::Tools::ChunkManipulator::ChunkClipboard::selectedChunks() const +{ + return _selected_chunks; +} diff --git a/src/noggit/ui/tools/ChunkManipulator/ChunkClipboard.hpp b/src/noggit/ui/tools/ChunkManipulator/ChunkClipboard.hpp index 0dc53e36..37eafd0e 100755 --- a/src/noggit/ui/tools/ChunkManipulator/ChunkClipboard.hpp +++ b/src/noggit/ui/tools/ChunkManipulator/ChunkClipboard.hpp @@ -3,29 +3,29 @@ #ifndef NOGGIT_CHUNKCLIPBOARD_HPP #define NOGGIT_CHUNKCLIPBOARD_HPP +#include +#include + +#include + #include + #include -#include #include -#include #include #include +#include #include +#include -#include -#include -#include -#include -#include -#include - - +class Alphamap; +class liquid_layer; +struct tmp_edit_alpha_values; class World; namespace Noggit::Ui::Tools::ChunkManipulator { - enum class ChunkCopyFlags { TERRAIN = 0x1, @@ -127,11 +127,11 @@ namespace Noggit::Ui::Tools::ChunkManipulator void pasteSelection(glm::vec3 const& pos, ChunkPasteFlags flags); [[nodiscard]] - ChunkCopyFlags copyParams() const { return _copy_flags; }; - void setCopyParams(ChunkCopyFlags flags) { _copy_flags = flags; }; + ChunkCopyFlags copyParams() const;; + void setCopyParams(ChunkCopyFlags flags);; [[nodiscard]] - std::set const& selectedChunks() const { return _selected_chunks; }; + std::set const& selectedChunks() const;; signals: void selectionChanged(std::set const& selected_chunks); diff --git a/src/noggit/ui/tools/LightEditor/LightEditor.cpp b/src/noggit/ui/tools/LightEditor/LightEditor.cpp index 7ccaccda..ac3d68d9 100644 --- a/src/noggit/ui/tools/LightEditor/LightEditor.cpp +++ b/src/noggit/ui/tools/LightEditor/LightEditor.cpp @@ -1,24 +1,39 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). #include "LightEditor.hpp" -#include +#include +#include #include #include +#include #include -#include +#include +#include #include -#include #include +#include -#include -#include -#include -#include +#include #include +#include +#include +#include #include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include using namespace Noggit::Ui::Tools; diff --git a/src/noggit/ui/tools/LightEditor/LightEditor.hpp b/src/noggit/ui/tools/LightEditor/LightEditor.hpp index b2e745e9..c8da9dd2 100644 --- a/src/noggit/ui/tools/LightEditor/LightEditor.hpp +++ b/src/noggit/ui/tools/LightEditor/LightEditor.hpp @@ -6,8 +6,19 @@ #include #include #include -#include -#include + +class LightViewPreview; +class LightViewEditor; +class Sky; + +class QPushButton; +class QDoubleSpinBox; +class QDial; +class QSpinBox; +class QListWidget; +class QComboBox; +class QTabWidget; +class QCheckBox; namespace Noggit::Ui::Tools { diff --git a/src/noggit/ui/tools/MapCreationWizard/Ui/MapCreationWizard.cpp b/src/noggit/ui/tools/MapCreationWizard/Ui/MapCreationWizard.cpp index a2baee08..4f23c527 100755 --- a/src/noggit/ui/tools/MapCreationWizard/Ui/MapCreationWizard.cpp +++ b/src/noggit/ui/tools/MapCreationWizard/Ui/MapCreationWizard.cpp @@ -2,32 +2,37 @@ #include "MapCreationWizard.hpp" -#include -#include -#include -#include -#include -#include #include +#include #include +#include +#include +#include +#include +#include #include +#include -#include +#include +#include +#include +#include +#include +#include +#include #include #include #include -#include -#include +#include +#include +#include +#include #include #include -#include -#include -#include -#include +#include #include -#include -#include +#include #include @@ -1035,6 +1040,11 @@ void MapCreationWizard::addNewMap() _max_players->setValue(0); } +World* Noggit::Ui::Tools::MapCreationWizard::Ui::MapCreationWizard::getWorld() const +{ + return _world.get(); +} + void MapCreationWizard::removeMap() { QMessageBox prompt; @@ -1158,6 +1168,21 @@ void LocaleDBCEntry::setCurrentLocale(const std::string& locale) _show_entry->setCurrentWidget(_widget_map.at(locale)); } +void Noggit::Ui::Tools::MapCreationWizard::Ui::LocaleDBCEntry::setValue(const std::string& val, int locale) +{ + _widget_map.at(_locale_names[locale])->setText(QString::fromStdString(val)); + + if (!val.empty() && _flags->value() == 0) + { + _flags->setValue(16712190); // default flags when there is text + } +} + +std::string Noggit::Ui::Tools::MapCreationWizard::Ui::LocaleDBCEntry::getValue(int locale) +{ + return _widget_map.at(_locale_names[locale])->text().toStdString(); +} + void LocaleDBCEntry::fill(DBCFile::Record& record, size_t field) { for (int loc = 0; loc < 16; ++loc) diff --git a/src/noggit/ui/tools/MapCreationWizard/Ui/MapCreationWizard.hpp b/src/noggit/ui/tools/MapCreationWizard/Ui/MapCreationWizard.hpp index 3dc65211..c700a465 100755 --- a/src/noggit/ui/tools/MapCreationWizard/Ui/MapCreationWizard.hpp +++ b/src/noggit/ui/tools/MapCreationWizard/Ui/MapCreationWizard.hpp @@ -2,33 +2,44 @@ #pragma once -#include -#include -#include +#include +#include +#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include -#include -#include -#include -#include +namespace BlizzardDatabaseLib::Structures +{ + struct BlizzardDatabaseRow; +} + +class WMOInstance; +class World; + +class QLineEdit; +class QDoubleSpinBox; +class QComboBox; +class QGroupBox; +class QSpinBox; +class QCheckBox; +class QStackedWidget; +class QTabWidget; namespace Noggit { + namespace Project + { + class NoggitProject; + } + namespace Ui { class Vector3fWidget; + class minimap_widget; } namespace Ui::Tools::MapCreationWizard::Ui @@ -41,17 +52,9 @@ namespace Noggit void setCurrentLocale(const std::string& locale); - void setValue(const std::string& val, int locale) - { - _widget_map.at(_locale_names[locale])->setText(QString::fromStdString(val)); + void setValue(const std::string& val, int locale); - if (!val.empty() && _flags->value() == 0) - { - _flags->setValue(16712190); // default flags when there is text - } - } - - std::string getValue(int locale) { return _widget_map.at(_locale_names[locale])->text().toStdString(); }; + std::string getValue(int locale);; void fill(DBCFile::Record& record, size_t field); void fill(BlizzardDatabaseLib::Structures::BlizzardDatabaseRow& record, std::string columnName); @@ -99,7 +102,7 @@ namespace Noggit // void destroyFakeWorld() { if(_world) _world.reset(); _world = nullptr; _minimap_widget->world (nullptr); }; void addNewMap(); - World* getWorld() { return _world.get(); }; + World* getWorld() const;; std::unique_ptr _world; signals: diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/BaseNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/BaseNode.cpp index 89fb71f2..4f61c29d 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/BaseNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/BaseNode.cpp @@ -1,10 +1,16 @@ #include "BaseNode.hpp" -#include +#include #include +#include #include #include +#include +#include + + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; using QtNodes::Node; @@ -12,12 +18,14 @@ using QtNodes::Node; InNodePort::InNodePort(QString const& caption_, bool caption_visible_) : caption(caption_) , caption_visible(caption_visible_) -{} +{ +} OutNodePort::OutNodePort(QString const& caption_, bool caption_visible_) : caption(caption_) , caption_visible(caption_visible_) -{} +{ +} BaseNode::BaseNode() : NodeDataModel() @@ -43,6 +51,10 @@ BaseNode::BaseNode() _embedded_widget_layout_bottom->setContentsMargins(5, 5, 5, 5); } +BaseNode::~BaseNode() +{ +} + std::shared_ptr BaseNode::outData(PortIndex port_index) { return std::static_pointer_cast(_out_ports[port_index].out_value); @@ -64,6 +76,21 @@ std::unique_ptr& BaseNode::dataModel(PortType port_type, PortIndex por } } +QWidget* BaseNode::embeddedWidget() +{ + return &_embedded_widget; +} + +NodeValidationState BaseNode::validationState() const +{ + return _validation_state; +} + +QString BaseNode::validationMessage() const +{ + return _validation_error; +} + void BaseNode::setInData(std::shared_ptr data, PortIndex port_index) { _in_ports[port_index].in_value = data; @@ -123,6 +150,21 @@ QString BaseNode::portCaption(PortType port_type, PortIndex port_index) const } } +QString BaseNode::name() const +{ + return _name; +} + +QString BaseNode::caption() const +{ + return _caption; +} + +void BaseNode::setCaption(QString const& caption) +{ + _caption = caption; +} + NodeDataType BaseNode::dataType(PortType port_type, PortIndex port_index) const { if (port_type == PortType::In) @@ -137,6 +179,16 @@ NodeDataType BaseNode::dataType(PortType port_type, PortIndex port_index) const return NodeDataType {"invalid", "Invalid"}; } +NodeInterpreterTokens BaseNode::getInterpreterToken() const +{ + return _token; +} + +void BaseNode::setInterpreterToken(NodeInterpreterTokens token) +{ + _token = token; +} + unsigned int BaseNode::nPorts(PortType port_type) const { if (port_type == PortType::In) @@ -227,6 +279,37 @@ void BaseNode::restore(const QJsonObject& json_obj) setCaption(json_obj["caption"].toString()); } +bool BaseNode::isLogicNode() +{ + return false; +} + +void BaseNode::setValidationMessage(QString const& message) +{ + _validation_error = message; + Q_EMIT visualsNeedUpdate(); +} + +void BaseNode::setValidationState(NodeValidationState state) +{ + _validation_state = state; +} + +NodeValidationState BaseNode::validate() +{ + return _validation_state; +} + +bool BaseNode::isComputed() const +{ + return _is_computed; +} + +void BaseNode::setComputed(bool state) +{ + _is_computed = state; +} + void BaseNode::deletePort(PortType port_type, PortIndex port_index) { deleteDefaultWidget(port_type, port_index); @@ -275,6 +358,11 @@ void BaseNode::deleteDefaultWidget(PortType port_type, PortIndex port_index) _embedded_widget.adjustSize(); } +void BaseNode::setName(QString const& name) +{ + _name = name; +} + void BaseNode::addWidgetTop(QWidget* widget) { _embedded_widget_layout_top->addWidget(widget); @@ -322,11 +410,3 @@ void BaseNode::captionDoubleClicked() } } - - - - - - - - diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/BaseNode.hpp b/src/noggit/ui/tools/NodeEditor/Nodes/BaseNode.hpp index fb69d3cb..8ce983e5 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/BaseNode.hpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/BaseNode.hpp @@ -2,23 +2,17 @@ #define NOGGIT_BASENODE_HPP #include -#include -#include -#include #include #include -#include -#include -#include -#include -#include -#include -#include -#include +namespace QtNodes +{ + class Connection; + class NodeData; +} -#include +class QVBoxLayout; using QtNodes::PortType; using QtNodes::PortIndex; @@ -77,12 +71,12 @@ namespace Noggit public: BaseNode(); - virtual ~BaseNode() {} + virtual ~BaseNode(); public: - NodeInterpreterTokens getInterpreterToken() { return _token; }; - void setInterpreterToken(NodeInterpreterTokens token) { _token = token; }; + NodeInterpreterTokens getInterpreterToken() const; + void setInterpreterToken(NodeInterpreterTokens token); unsigned int nPorts(PortType port_type) const override; @@ -94,19 +88,19 @@ namespace Noggit std::unique_ptr& dataModel(PortType port_type, PortIndex port_index); - QWidget* embeddedWidget() override { return &_embedded_widget; } + QWidget* embeddedWidget() override; - NodeValidationState validationState() const override { return _validation_state; }; + NodeValidationState validationState() const override; - QString validationMessage() const override { return _validation_error; }; + QString validationMessage() const override; QString portCaption(PortType port_type, PortIndex port_index) const override; - QString name() const override { return _name; } + QString name() const override; - QString caption() const override { return _caption; }; + QString caption() const override; - void setCaption(QString const& caption){_caption = caption;}; + void setCaption(QString const& caption); bool portCaptionVisible(PortType port_type, PortIndex port_index) const override; @@ -117,16 +111,16 @@ namespace Noggit QJsonObject save() const override; void restore(QJsonObject const& json_obj) override; - virtual bool isLogicNode() { return false; }; + virtual bool isLogicNode(); - void setValidationMessage(QString const& message){_validation_error = message; Q_EMIT visualsNeedUpdate();}; - void setValidationState(NodeValidationState state){_validation_state = state;}; + void setValidationMessage(QString const& message); + void setValidationState(NodeValidationState state); virtual void compute() = 0; - virtual NodeValidationState validate() { return _validation_state; }; + virtual NodeValidationState validate(); - bool isComputed() { return _is_computed; }; - void setComputed(bool state) { _is_computed = state; }; + bool isComputed() const; + void setComputed(bool state); public Q_SLOTS: @@ -139,7 +133,7 @@ namespace Noggit protected: - void setName(QString const& name) {_name = name;}; + void setName(QString const& name); void addWidgetTop(QWidget* widget); void addWidgetBottom(QWidget* widget); diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Containers/JSON/CreateJSONArray.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Containers/JSON/CreateJSONArray.cpp index db5a341f..7730e602 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Containers/JSON/CreateJSONArray.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Containers/JSON/CreateJSONArray.cpp @@ -4,6 +4,7 @@ #include #include +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Containers/JSON/CreateJSONObject.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Containers/JSON/CreateJSONObject.cpp index b87ef713..fc34fe0b 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Containers/JSON/CreateJSONObject.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Containers/JSON/CreateJSONObject.cpp @@ -4,6 +4,7 @@ #include #include +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Containers/JSON/GetJSONValue.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Containers/JSON/GetJSONValue.cpp index 08a3e75e..b2a50725 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Containers/JSON/GetJSONValue.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Containers/JSON/GetJSONValue.cpp @@ -4,6 +4,7 @@ #include #include +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; @@ -131,4 +132,4 @@ NodeValidationState GetJSONValueNode::validate() } return _validation_state; -} \ No newline at end of file +} diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Containers/JSON/JSONArrayGetValue.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Containers/JSON/JSONArrayGetValue.cpp index ecb8c7c1..48172412 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Containers/JSON/JSONArrayGetValue.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Containers/JSON/JSONArrayGetValue.cpp @@ -4,6 +4,7 @@ #include #include +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Containers/JSON/JSONArrayInfo.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Containers/JSON/JSONArrayInfo.cpp index 3f69c6ec..f5572c50 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Containers/JSON/JSONArrayInfo.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Containers/JSON/JSONArrayInfo.cpp @@ -4,6 +4,7 @@ #include #include +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Containers/JSON/JSONArrayInsertValue.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Containers/JSON/JSONArrayInsertValue.cpp index 0029626e..d2aa3bd5 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Containers/JSON/JSONArrayInsertValue.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Containers/JSON/JSONArrayInsertValue.cpp @@ -4,6 +4,7 @@ #include #include +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Containers/JSON/JSONArrayPush.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Containers/JSON/JSONArrayPush.cpp index ef4f56c2..02a641cd 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Containers/JSON/JSONArrayPush.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Containers/JSON/JSONArrayPush.cpp @@ -5,6 +5,10 @@ #include #include +#include + +#include + using namespace Noggit::Ui::Tools::NodeEditor::Nodes; JSONArrayPushNode::JSONArrayPushNode() diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Containers/JSON/JSONArrayPush.hpp b/src/noggit/ui/tools/NodeEditor/Nodes/Containers/JSON/JSONArrayPush.hpp index 3739a9e3..ddfaca3e 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Containers/JSON/JSONArrayPush.hpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Containers/JSON/JSONArrayPush.hpp @@ -4,7 +4,6 @@ #define NOGGIT_JSONARRAYPUSH_HPP #include -#include using QtNodes::PortType; using QtNodes::PortIndex; @@ -13,6 +12,7 @@ using QtNodes::NodeDataType; using QtNodes::NodeDataModel; using QtNodes::NodeValidationState; +class QComboBox; namespace Noggit { @@ -32,9 +32,7 @@ namespace Noggit private: QComboBox* _operation; }; - } - } #endif //NOGGIT_JSONARRAYPUSH_HPP diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Containers/JSON/JSONObjectInfo.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Containers/JSON/JSONObjectInfo.cpp index 8c346db5..230d678b 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Containers/JSON/JSONObjectInfo.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Containers/JSON/JSONObjectInfo.cpp @@ -4,6 +4,7 @@ #include #include +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; @@ -47,4 +48,4 @@ NodeValidationState JSONObjectInfoNode::validate() } return _validation_state; -} \ No newline at end of file +} diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Containers/JSON/LoadJSONObject.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Containers/JSON/LoadJSONObject.cpp index c91a0be3..902e81fd 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Containers/JSON/LoadJSONObject.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Containers/JSON/LoadJSONObject.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Containers/JSON/SaveJSONObject.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Containers/JSON/SaveJSONObject.cpp index 694ca3e8..a265dd53 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Containers/JSON/SaveJSONObject.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Containers/JSON/SaveJSONObject.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Containers/JSON/SetJSONValue.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Containers/JSON/SetJSONValue.cpp index 4ae32274..7d84e026 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Containers/JSON/SetJSONValue.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Containers/JSON/SetJSONValue.cpp @@ -4,6 +4,7 @@ #include #include +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Containers/List/DataListNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Containers/List/DataListNode.cpp index f9a92792..f6470bfe 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Containers/List/DataListNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Containers/List/DataListNode.cpp @@ -5,7 +5,9 @@ #include #include -#include +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Containers/List/DataListNode.hpp b/src/noggit/ui/tools/NodeEditor/Nodes/Containers/List/DataListNode.hpp index 59e185d7..d2c550e9 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Containers/List/DataListNode.hpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Containers/List/DataListNode.hpp @@ -5,9 +5,7 @@ #include "noggit/ui/tools/NodeEditor/Nodes/Containers/List/ListNodeBase.hpp" -#include #include -#include using QtNodes::PortType; using QtNodes::PortIndex; @@ -16,6 +14,7 @@ using QtNodes::NodeDataType; using QtNodes::NodeDataModel; using QtNodes::NodeValidationState; +class QComboBox; namespace Noggit { @@ -36,9 +35,7 @@ namespace Noggit std::vector> _data; }; - } - } #endif //NOGGIT_DATALISTNODE_HPP diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Containers/List/ListAddNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Containers/List/ListAddNode.cpp index b99fd1f8..f20be7bf 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Containers/List/ListAddNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Containers/List/ListAddNode.cpp @@ -8,6 +8,8 @@ #include +#include + using QtNodes::Node; using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Containers/List/ListAddNode.hpp b/src/noggit/ui/tools/NodeEditor/Nodes/Containers/List/ListAddNode.hpp index fe963f2c..53598799 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Containers/List/ListAddNode.hpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Containers/List/ListAddNode.hpp @@ -5,8 +5,6 @@ #include "noggit/ui/tools/NodeEditor/Nodes/LogicNodeBase.hpp" -#include - using QtNodes::PortType; using QtNodes::PortIndex; using QtNodes::NodeData; @@ -14,6 +12,7 @@ using QtNodes::NodeDataType; using QtNodes::NodeDataModel; using QtNodes::NodeValidationState; +class QComboBox; namespace Noggit { @@ -37,9 +36,7 @@ namespace Noggit private: QComboBox* _operation; }; - } - } #endif //NOGGIT_LISTADDNODE_HPP diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Containers/List/ListClearNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Containers/List/ListClearNode.cpp index a17448f5..ea030683 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Containers/List/ListClearNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Containers/List/ListClearNode.cpp @@ -4,6 +4,7 @@ #include #include +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Containers/List/ListDeclareNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Containers/List/ListDeclareNode.cpp index b4b84b4f..ac2078e6 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Containers/List/ListDeclareNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Containers/List/ListDeclareNode.cpp @@ -5,6 +5,10 @@ #include #include +#include + +#include + using namespace Noggit::Ui::Tools::NodeEditor::Nodes; ListDeclareNode::ListDeclareNode() diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Containers/List/ListDeclareNode.hpp b/src/noggit/ui/tools/NodeEditor/Nodes/Containers/List/ListDeclareNode.hpp index 0b9714b1..8681e1d2 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Containers/List/ListDeclareNode.hpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Containers/List/ListDeclareNode.hpp @@ -4,7 +4,6 @@ #define NOGGIT_LISTDECLARENODE_HPP #include "ListNodeBase.hpp" -#include using QtNodes::PortType; using QtNodes::PortIndex; @@ -13,6 +12,7 @@ using QtNodes::NodeDataType; using QtNodes::NodeDataModel; using QtNodes::NodeValidationState; +class QComboBox; namespace Noggit { @@ -32,11 +32,8 @@ namespace Noggit private: QComboBox* _type; - }; - } - } #endif //NOGGIT_LISTDECLARENODE_HPP diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Containers/List/ListEraseNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Containers/List/ListEraseNode.cpp index 2255eb52..2e098c15 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Containers/List/ListEraseNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Containers/List/ListEraseNode.cpp @@ -4,6 +4,7 @@ #include #include +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Containers/List/ListGetNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Containers/List/ListGetNode.cpp index fcf84eb6..bc874274 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Containers/List/ListGetNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Containers/List/ListGetNode.cpp @@ -8,6 +8,8 @@ #include +#include + using QtNodes::Node; using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Containers/List/ListGetNode.hpp b/src/noggit/ui/tools/NodeEditor/Nodes/Containers/List/ListGetNode.hpp index 95c2fe97..cf069585 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Containers/List/ListGetNode.hpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Containers/List/ListGetNode.hpp @@ -5,8 +5,6 @@ #include "noggit/ui/tools/NodeEditor/Nodes/LogicNodeBase.hpp" -#include - using QtNodes::PortType; using QtNodes::PortIndex; using QtNodes::NodeData; @@ -14,6 +12,7 @@ using QtNodes::NodeDataType; using QtNodes::NodeDataModel; using QtNodes::NodeValidationState; +class QComboBox; namespace Noggit { @@ -37,9 +36,7 @@ namespace Noggit private: QComboBox* _operation; }; - } - } #endif //NOGGIT_LISTGETNODE_HPP diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Containers/List/ListReserveNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Containers/List/ListReserveNode.cpp index e50ea21e..54cbab36 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Containers/List/ListReserveNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Containers/List/ListReserveNode.cpp @@ -4,6 +4,7 @@ #include #include +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Containers/List/ListSizeNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Containers/List/ListSizeNode.cpp index 4670f6a0..f36bdc93 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Containers/List/ListSizeNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Containers/List/ListSizeNode.cpp @@ -5,6 +5,8 @@ #include #include +#include + using namespace Noggit::Ui::Tools::NodeEditor::Nodes; ListSizeNode::ListSizeNode() diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/ContextLogicNodeBase.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/ContextLogicNodeBase.cpp new file mode 100644 index 00000000..e97751f1 --- /dev/null +++ b/src/noggit/ui/tools/NodeEditor/Nodes/ContextLogicNodeBase.cpp @@ -0,0 +1,51 @@ +// This file is part of Noggit3, licensed under GNU General Public License (version 3). + +#include "ContextLogicNodeBase.hpp" + +#include + +namespace Noggit +{ + namespace Ui::Tools::NodeEditor::Nodes + { + Noggit::Ui::Tools::NodeEditor::Nodes::ContextLogicNodeBase::ContextLogicNodeBase() + { + } + + NodeValidationState ContextLogicNodeBase::validate() + { + if (!gCurrentContext->getWorld() || !gCurrentContext->getViewport()) + { + setValidationState(NodeValidationState::Error); + setValidationMessage("Error: this node requires a world context to run."); + return _validation_state; + } + + return LogicNodeBase::validate(); + } + + QJsonObject ContextLogicNodeBase::save() const + { + QJsonObject json_obj = BaseNode::save(); + + for (int i = 0; i < _in_ports.size(); ++i) + { + defaultWidgetToJson(PortType::In, i, json_obj, _in_ports[i].caption); + } + + return json_obj; + } + + void ContextLogicNodeBase::restore(QJsonObject const& json_obj) + { + BaseNode::restore(json_obj); + + for (int i = 0; i < _in_ports.size(); ++i) + { + defaultWidgetFromJson(PortType::In, i, json_obj, _in_ports[i].caption); + } + + } + + } +} diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/ContextLogicNodeBase.hpp b/src/noggit/ui/tools/NodeEditor/Nodes/ContextLogicNodeBase.hpp index 30695002..de6c8ffe 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/ContextLogicNodeBase.hpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/ContextLogicNodeBase.hpp @@ -4,7 +4,6 @@ #define NOGGIT_CONTEXTLOGICNODEBASE_HPP #include -#include using QtNodes::PortType; using QtNodes::PortIndex; @@ -23,46 +22,15 @@ namespace Noggit Q_OBJECT public: - ContextLogicNodeBase() {}; - NodeValidationState validate() override - { - if (!gCurrentContext->getWorld() || !gCurrentContext->getViewport()) - { - setValidationState(NodeValidationState::Error); - setValidationMessage("Error: this node requires a world context to run."); - return _validation_state; - } + ContextLogicNodeBase(); - return LogicNodeBase::validate(); - }; + NodeValidationState validate() override; - QJsonObject save() const override - { - QJsonObject json_obj = BaseNode::save(); - - for (int i = 0; i < _in_ports.size(); ++i) - { - defaultWidgetToJson(PortType::In, i, json_obj, _in_ports[i].caption); - } - - return json_obj; - }; - - void restore(QJsonObject const& json_obj) override - { - BaseNode::restore(json_obj); - - for (int i = 0; i < _in_ports.size(); ++i) - { - defaultWidgetFromJson(PortType::In, i, json_obj, _in_ports[i].caption); - } - - }; + QJsonObject save() const override; + void restore(QJsonObject const& json_obj) override; }; - } - } #endif //NOGGIT_CONTEXTLOGICNODEBASE_HPP diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/ContextNodeBase.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/ContextNodeBase.cpp new file mode 100644 index 00000000..a491f419 --- /dev/null +++ b/src/noggit/ui/tools/NodeEditor/Nodes/ContextNodeBase.cpp @@ -0,0 +1,26 @@ +// This file is part of Noggit3, licensed under GNU General Public License (version 3). + +#include "ContextNodeBase.hpp" + +#include + +namespace Noggit +{ + namespace Ui::Tools::NodeEditor::Nodes + { + ContextNodeBase::ContextNodeBase() + { + } + + NodeValidationState ContextNodeBase::validate() + { + if (!gCurrentContext->getWorld() || !gCurrentContext->getViewport()) + { + setValidationState(NodeValidationState::Error); + setValidationMessage("Error: this node requires a world context to run."); + } + + return _validation_state; + } + } +} diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/ContextNodeBase.hpp b/src/noggit/ui/tools/NodeEditor/Nodes/ContextNodeBase.hpp index c8e65542..23c4018a 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/ContextNodeBase.hpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/ContextNodeBase.hpp @@ -4,7 +4,6 @@ #define NOGGIT_CONTEXTNODEBASE_HPP #include -#include using QtNodes::PortType; using QtNodes::PortIndex; @@ -23,18 +22,9 @@ namespace Noggit Q_OBJECT public: - ContextNodeBase() {}; + ContextNodeBase(); - NodeValidationState validate() override - { - if (!gCurrentContext->getWorld() || !gCurrentContext->getViewport()) - { - setValidationState(NodeValidationState::Error); - setValidationMessage("Error: this node requires a world context to run."); - } - - return _validation_state; - }; + NodeValidationState validate() override;; }; } diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/DataConstantNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/DataConstantNode.cpp index 04025fdc..13345ca7 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/DataConstantNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/DataConstantNode.cpp @@ -5,6 +5,10 @@ #include #include +#include + +#include + using namespace Noggit::Ui::Tools::NodeEditor::Nodes; DataConstantNode::DataConstantNode() diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/DataConstantNode.hpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/DataConstantNode.hpp index 70d8da15..b39c95de 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/DataConstantNode.hpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/DataConstantNode.hpp @@ -6,8 +6,6 @@ #include "noggit/ui/tools/NodeEditor/Nodes/BaseNode.hpp" #include -#include - using QtNodes::PortType; using QtNodes::PortIndex; using QtNodes::NodeData; @@ -15,6 +13,7 @@ using QtNodes::NodeDataType; using QtNodes::NodeDataModel; using QtNodes::NodeValidationState; +class QComboBox; namespace Noggit { @@ -42,9 +41,7 @@ namespace Noggit {"Vector4D", "vec4"}, {"Color", "color"}}; }; - } - } #endif //NOGGIT_DATACONSTANTNODE_HPP diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageBlendOpenGLNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageBlendOpenGLNode.cpp index 51e08b35..3d26a5cb 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageBlendOpenGLNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageBlendOpenGLNode.cpp @@ -5,6 +5,10 @@ #include #include +#include + +#include + using namespace Noggit::Ui::Tools::NodeEditor::Nodes; ImageBlendOpenGLNode::ImageBlendOpenGLNode() diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageBlendOpenGLNode.hpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageBlendOpenGLNode.hpp index f4b30b70..18c6a4a8 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageBlendOpenGLNode.hpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageBlendOpenGLNode.hpp @@ -4,7 +4,6 @@ #define NOGGIT_IMAGEBLENDOPENGLNODE_HPP #include -#include using QtNodes::PortType; using QtNodes::PortIndex; @@ -13,6 +12,7 @@ using QtNodes::NodeDataType; using QtNodes::NodeDataModel; using QtNodes::NodeValidationState; +class QComboBox; namespace Noggit { @@ -37,9 +37,7 @@ namespace Noggit QComboBox* _sfactor; QComboBox* _dfactor; }; - } - } #endif //NOGGIT_IMAGEBLENDOPENGLNODE_HPP diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageCreateNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageCreateNode.cpp index bfe68c4f..5e782c95 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageCreateNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageCreateNode.cpp @@ -5,6 +5,10 @@ #include #include +#include + +#include + using namespace Noggit::Ui::Tools::NodeEditor::Nodes; ImageCreateNode::ImageCreateNode() diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageCreateNode.hpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageCreateNode.hpp index 0acf4e1e..48c11692 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageCreateNode.hpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageCreateNode.hpp @@ -4,7 +4,6 @@ #define NOGGIT_IMAGECREATENODE_HPP #include -#include using QtNodes::PortType; using QtNodes::PortIndex; @@ -13,6 +12,7 @@ using QtNodes::NodeDataType; using QtNodes::NodeDataModel; using QtNodes::NodeValidationState; +class QComboBox; namespace Noggit { @@ -30,11 +30,8 @@ namespace Noggit private: QComboBox* _image_format; - }; - } - } #endif //NOGGIT_IMAGECREATENODE_HPP diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageFillNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageFillNode.cpp index 5d1c0992..1503db59 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageFillNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageFillNode.cpp @@ -4,6 +4,7 @@ #include #include +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageGaussianBlurNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageGaussianBlurNode.cpp index 018ac372..05bb485f 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageGaussianBlurNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageGaussianBlurNode.cpp @@ -5,6 +5,7 @@ #include #include +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageGetPixelNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageGetPixelNode.cpp index dd875112..e70266aa 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageGetPixelNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageGetPixelNode.cpp @@ -4,6 +4,7 @@ #include #include +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageGetRegionNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageGetRegionNode.cpp index 606a485e..c9f5d2c6 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageGetRegionNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageGetRegionNode.cpp @@ -4,6 +4,7 @@ #include #include +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageInfoNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageInfoNode.cpp index f81126f9..2f8f3e39 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageInfoNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageInfoNode.cpp @@ -4,6 +4,7 @@ #include #include +#include #include diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageInvertNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageInvertNode.cpp index dd1a28b7..9f713189 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageInvertNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageInvertNode.cpp @@ -4,6 +4,7 @@ #include #include +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageMaskRandomPointsNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageMaskRandomPointsNode.cpp index fd2d8140..8d1bdfab 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageMaskRandomPointsNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageMaskRandomPointsNode.cpp @@ -4,6 +4,8 @@ #include #include +#include + #include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageMirrorNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageMirrorNode.cpp index e0c4cefd..3e7dcd6e 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageMirrorNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageMirrorNode.cpp @@ -4,6 +4,7 @@ #include #include +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageResizeNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageResizeNode.cpp index 869ef4cd..461ff9c9 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageResizeNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageResizeNode.cpp @@ -5,6 +5,10 @@ #include #include +#include + +#include + using namespace Noggit::Ui::Tools::NodeEditor::Nodes; ImageResizeNode::ImageResizeNode() diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageResizeNode.hpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageResizeNode.hpp index 5f49f845..52783485 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageResizeNode.hpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageResizeNode.hpp @@ -5,8 +5,6 @@ #include -#include - using QtNodes::PortType; using QtNodes::PortIndex; using QtNodes::NodeData; @@ -14,6 +12,7 @@ using QtNodes::NodeDataType; using QtNodes::NodeDataModel; using QtNodes::NodeValidationState; +class QComboBox; namespace Noggit { @@ -34,9 +33,7 @@ namespace Noggit QComboBox* _aspect_ratio_mode; QComboBox* _mode; }; - } - } #endif //NOGGIT_IMAGERESIZENODE_HPP diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageRotateNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageRotateNode.cpp index e3d1eaa9..e1f36191 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageRotateNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageRotateNode.cpp @@ -5,6 +5,10 @@ #include #include +#include + +#include + using namespace Noggit::Ui::Tools::NodeEditor::Nodes; ImageRotateNode::ImageRotateNode() diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageRotateNode.hpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageRotateNode.hpp index 5ddb2d48..6ae4f9ee 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageRotateNode.hpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageRotateNode.hpp @@ -5,8 +5,6 @@ #include -#include - using QtNodes::PortType; using QtNodes::PortIndex; using QtNodes::NodeData; @@ -14,6 +12,7 @@ using QtNodes::NodeDataType; using QtNodes::NodeDataModel; using QtNodes::NodeValidationState; +class QComboBox; namespace Noggit { @@ -33,9 +32,7 @@ namespace Noggit private: QComboBox* _mode; }; - } - } #endif //NOGGIT_IMAGEROTATENODE_HPP diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageSaveNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageSaveNode.cpp index 6a467078..ef9b1c06 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageSaveNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageSaveNode.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageScaleNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageScaleNode.cpp index b92705ef..981874af 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageScaleNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageScaleNode.cpp @@ -5,6 +5,10 @@ #include #include +#include + +#include + using namespace Noggit::Ui::Tools::NodeEditor::Nodes; ImageScaleNode::ImageScaleNode() diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageScaleNode.hpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageScaleNode.hpp index d664f7a2..cb760c26 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageScaleNode.hpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageScaleNode.hpp @@ -5,8 +5,6 @@ #include -#include - using QtNodes::PortType; using QtNodes::PortIndex; using QtNodes::NodeData; @@ -14,6 +12,7 @@ using QtNodes::NodeDataType; using QtNodes::NodeDataModel; using QtNodes::NodeValidationState; +class QComboBox; namespace Noggit { @@ -32,11 +31,8 @@ namespace Noggit private: QComboBox* _mode; - }; - } - } #endif //NOGGIT_IMAGESCALENODE_HPP diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageSetPixelNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageSetPixelNode.cpp index 3c666380..12c5811e 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageSetPixelNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageSetPixelNode.cpp @@ -4,6 +4,7 @@ #include #include +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageSetRegionNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageSetRegionNode.cpp index 6480d078..94ed9e79 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageSetRegionNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageSetRegionNode.cpp @@ -4,6 +4,7 @@ #include #include +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageToGrayscaleNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageToGrayscaleNode.cpp index d69bf080..f6df068c 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageToGrayscaleNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageToGrayscaleNode.cpp @@ -4,6 +4,7 @@ #include #include +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageTranslateNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageTranslateNode.cpp index ea84f82d..3c183401 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageTranslateNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageTranslateNode.cpp @@ -5,6 +5,10 @@ #include #include +#include + +#include + using namespace Noggit::Ui::Tools::NodeEditor::Nodes; ImageTranslateNode::ImageTranslateNode() diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageTranslateNode.hpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageTranslateNode.hpp index 2fce6e08..9e546161 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageTranslateNode.hpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/ImageTranslateNode.hpp @@ -5,8 +5,6 @@ #include -#include - using QtNodes::PortType; using QtNodes::PortIndex; using QtNodes::NodeData; @@ -14,6 +12,7 @@ using QtNodes::NodeDataType; using QtNodes::NodeDataModel; using QtNodes::NodeValidationState; +class QComboBox; namespace Noggit { @@ -33,9 +32,7 @@ namespace Noggit private: QComboBox* _mode; }; - } - } #endif //NOGGIT_IMAGETRANSLATENODE_HPP diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/LoadImageNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/LoadImageNode.cpp index c9c06c07..580cb9d2 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/LoadImageNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/LoadImageNode.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/LoadImageNode.hpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/LoadImageNode.hpp index b947eb65..42ab2be3 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/LoadImageNode.hpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/LoadImageNode.hpp @@ -12,6 +12,8 @@ using QtNodes::NodeDataType; using QtNodes::NodeDataModel; using QtNodes::NodeValidationState; +class QPushButton; + namespace Noggit { diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/gaussianblur.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/gaussianblur.cpp index 2024db71..f6d7e7fc 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/gaussianblur.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/gaussianblur.cpp @@ -1,5 +1,7 @@ #include "gaussianblur.h" +#include + double GaussianBlur::GetNumberOnNormalDistribution(int i, int j, const int center, double sigma) const { return (1.0 / (2 * M_PI * pow(sigma, 2)) * exp ( - (pow(i - center, 2) + pow(j - center, 2)) / (2 * pow(sigma, 2)))); @@ -61,7 +63,7 @@ GaussianBlur::GaussianBlur(int radius, double sigma) : diviation_ (sigma), values_is_set_ (false) { - if (true == (radius_ % 2)) + if (1 == (radius_ % 2)) { size_ = 2 * radius_ + 1; matrix_ = new double* [size_]; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/gaussianblur.h b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/gaussianblur.h index bf2432f5..980740eb 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/gaussianblur.h +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Image/gaussianblur.h @@ -3,7 +3,6 @@ #include #include -#include class GaussianBlur { diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseAbsNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseAbsNode.cpp index 5e1e949b..1abcda75 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseAbsNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseAbsNode.cpp @@ -4,6 +4,7 @@ #include #include +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseBillowNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseBillowNode.cpp index 51506f6f..8726e8e7 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseBillowNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseBillowNode.cpp @@ -5,6 +5,10 @@ #include #include +#include + +#include + using namespace Noggit::Ui::Tools::NodeEditor::Nodes; NoiseBillowNode::NoiseBillowNode() diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseBillowNode.hpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseBillowNode.hpp index df459970..ef178dac 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseBillowNode.hpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseBillowNode.hpp @@ -5,7 +5,6 @@ #include "NoiseGeneratorBase.hpp" #include -#include using QtNodes::PortType; using QtNodes::PortIndex; @@ -14,6 +13,7 @@ using QtNodes::NodeDataType; using QtNodes::NodeDataModel; using QtNodes::NodeValidationState; +class QComboBox; namespace Noggit { @@ -33,9 +33,7 @@ namespace Noggit QComboBox* _quality; noise::module::Billow _module; }; - } - } #endif //NOGGIT_NOISEBILLOWNODE_HPP diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseBlendNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseBlendNode.cpp index 3eecc5d2..c28e2e8f 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseBlendNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseBlendNode.cpp @@ -4,6 +4,7 @@ #include #include +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseCacheNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseCacheNode.cpp index cf9d9d7b..f765b74c 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseCacheNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseCacheNode.cpp @@ -4,6 +4,7 @@ #include #include +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseCheckerboardNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseCheckerboardNode.cpp index 0d367382..0f0382ee 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseCheckerboardNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseCheckerboardNode.cpp @@ -4,6 +4,7 @@ #include #include +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseClampNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseClampNode.cpp index f954f4a5..b446aca4 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseClampNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseClampNode.cpp @@ -4,6 +4,7 @@ #include #include +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseConstValueNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseConstValueNode.cpp index 19085f3c..bd9bd9b1 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseConstValueNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseConstValueNode.cpp @@ -4,6 +4,7 @@ #include #include +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseCurveNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseCurveNode.cpp index d358e6e7..953f93b7 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseCurveNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseCurveNode.cpp @@ -4,6 +4,7 @@ #include #include +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseCylindersNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseCylindersNode.cpp index b3c7042f..d72e5d9b 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseCylindersNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseCylindersNode.cpp @@ -4,6 +4,7 @@ #include #include +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseDisplaceNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseDisplaceNode.cpp index d0145dcc..37cad45f 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseDisplaceNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseDisplaceNode.cpp @@ -4,6 +4,7 @@ #include #include +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseExponentNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseExponentNode.cpp index 7efcc1d4..42ba14a5 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseExponentNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseExponentNode.cpp @@ -4,6 +4,7 @@ #include #include +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseInvertNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseInvertNode.cpp index 08464546..49e1d4a5 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseInvertNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseInvertNode.cpp @@ -4,6 +4,7 @@ #include #include +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseMathNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseMathNode.cpp index 8ee640aa..8bb95a97 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseMathNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseMathNode.cpp @@ -5,6 +5,10 @@ #include #include +#include + +#include + using namespace Noggit::Ui::Tools::NodeEditor::Nodes; NoiseMathNode::NoiseMathNode() diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseMathNode.hpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseMathNode.hpp index 9ea7a1fd..253b60d6 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseMathNode.hpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseMathNode.hpp @@ -5,7 +5,6 @@ #include #include -#include using QtNodes::PortType; using QtNodes::PortIndex; @@ -14,6 +13,7 @@ using QtNodes::NodeDataType; using QtNodes::NodeDataModel; using QtNodes::NodeValidationState; +class QComboBox; namespace Noggit { diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoisePerlinNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoisePerlinNode.cpp index 59faa0ba..7b3f5484 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoisePerlinNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoisePerlinNode.cpp @@ -4,6 +4,11 @@ #include #include + +#include + +#include + #include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoisePerlinNode.hpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoisePerlinNode.hpp index 11b37575..a4104438 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoisePerlinNode.hpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoisePerlinNode.hpp @@ -5,7 +5,6 @@ #include "NoiseGeneratorBase.hpp" #include -#include using QtNodes::PortType; using QtNodes::PortIndex; @@ -14,6 +13,7 @@ using QtNodes::NodeDataType; using QtNodes::NodeDataModel; using QtNodes::NodeValidationState; +class QComboBox; namespace Noggit { diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseRidgedMultiNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseRidgedMultiNode.cpp index 4f8790e6..195db140 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseRidgedMultiNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseRidgedMultiNode.cpp @@ -5,6 +5,10 @@ #include #include +#include + +#include + using namespace Noggit::Ui::Tools::NodeEditor::Nodes; NoiseRidgedMultiNode::NoiseRidgedMultiNode() diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseRidgedMultiNode.hpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseRidgedMultiNode.hpp index 9ac11472..c272c5e0 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseRidgedMultiNode.hpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseRidgedMultiNode.hpp @@ -5,7 +5,6 @@ #include "NoiseGeneratorBase.hpp" #include -#include using QtNodes::PortType; using QtNodes::PortIndex; @@ -14,6 +13,7 @@ using QtNodes::NodeDataType; using QtNodes::NodeDataModel; using QtNodes::NodeValidationState; +class QComboBox; namespace Noggit { diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseScaleBiasNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseScaleBiasNode.cpp index bbb315cb..53b2ff9e 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseScaleBiasNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseScaleBiasNode.cpp @@ -4,6 +4,7 @@ #include #include +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseSelectNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseSelectNode.cpp index eb239a71..b605a3cb 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseSelectNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseSelectNode.cpp @@ -4,6 +4,7 @@ #include #include +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseSpheresNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseSpheresNode.cpp index 25310271..d4b81751 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseSpheresNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseSpheresNode.cpp @@ -4,6 +4,7 @@ #include #include +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseTerraceNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseTerraceNode.cpp index dc39a42e..68127d50 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseTerraceNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseTerraceNode.cpp @@ -4,6 +4,7 @@ #include #include +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseToImageNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseToImageNode.cpp index fe1057c7..30fbb8ac 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseToImageNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseToImageNode.cpp @@ -4,6 +4,7 @@ #include #include +#include #include diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseTransformPointNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseTransformPointNode.cpp index 8a8e85da..3cbf919b 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseTransformPointNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseTransformPointNode.cpp @@ -5,6 +5,10 @@ #include #include +#include + +#include + using namespace Noggit::Ui::Tools::NodeEditor::Nodes; NoiseTransformPointNode::NoiseTransformPointNode() diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseTransformPointNode.hpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseTransformPointNode.hpp index e93e7b74..39296d89 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseTransformPointNode.hpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseTransformPointNode.hpp @@ -5,7 +5,6 @@ #include #include -#include using QtNodes::PortType; using QtNodes::PortIndex; @@ -14,6 +13,7 @@ using QtNodes::NodeDataType; using QtNodes::NodeDataModel; using QtNodes::NodeValidationState; +class QComboBox; namespace Noggit { @@ -35,9 +35,7 @@ namespace Noggit int _last_module = -1; std::unique_ptr _module; }; - } - } #endif //NOGGIT_NOISEROTATEPOINTNODE_HPP diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseTurbulenceNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseTurbulenceNode.cpp index 7745e429..61db0597 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseTurbulenceNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseTurbulenceNode.cpp @@ -4,6 +4,7 @@ #include #include +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseViewerNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseViewerNode.cpp index 41958ac9..36b46a25 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseViewerNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseViewerNode.cpp @@ -6,7 +6,13 @@ #include #include +#include + #include + +#include +#include + #include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseViewerNode.hpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseViewerNode.hpp index 990fe92a..eebaf5e9 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseViewerNode.hpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseViewerNode.hpp @@ -4,8 +4,6 @@ #define NOGGIT_NOISEVIEWERNODE_HPP #include -#include -#include using QtNodes::PortType; using QtNodes::PortIndex; @@ -14,6 +12,8 @@ using QtNodes::NodeDataType; using QtNodes::NodeDataModel; using QtNodes::NodeValidationState; +class QLabel; +class QPushButton; namespace Noggit { @@ -32,9 +32,7 @@ namespace Noggit QPushButton* _update_btn; QLabel* _image; }; - } - } #endif //NOGGIT_NOISEVIEWERNODE_HPP diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseVoronoiNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseVoronoiNode.cpp index 4656b91e..91539970 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseVoronoiNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Noise/NoiseVoronoiNode.cpp @@ -4,6 +4,7 @@ #include #include +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Random/RandomDecimalNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Random/RandomDecimalNode.cpp index 8f16218f..e776ef8f 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Random/RandomDecimalNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Random/RandomDecimalNode.cpp @@ -4,6 +4,7 @@ #include #include +#include #include diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Random/RandomDecimalRangeNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Random/RandomDecimalRangeNode.cpp index 7958c2ae..e7332ede 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Random/RandomDecimalRangeNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Random/RandomDecimalRangeNode.cpp @@ -4,6 +4,8 @@ #include #include +#include + #include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Random/RandomIntegerNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Random/RandomIntegerNode.cpp index 6aaae0c3..fe000cb6 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Random/RandomIntegerNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Random/RandomIntegerNode.cpp @@ -4,6 +4,7 @@ #include #include +#include #include diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Random/RandomIntegerRangeNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Random/RandomIntegerRangeNode.cpp index 20ff7d41..81895aa9 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Random/RandomIntegerRangeNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Random/RandomIntegerRangeNode.cpp @@ -4,6 +4,8 @@ #include #include +#include + #include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Random/RandomSeedNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Random/RandomSeedNode.cpp index 1c4b2fa1..529e06cc 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/Random/RandomSeedNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/Random/RandomSeedNode.cpp @@ -4,6 +4,8 @@ #include #include +#include + #include #include diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/String/StringConcatenateNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/String/StringConcatenateNode.cpp index c0d64b28..6859cf7e 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/String/StringConcatenateNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/String/StringConcatenateNode.cpp @@ -4,6 +4,7 @@ #include #include +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/String/StringEndsWithNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/String/StringEndsWithNode.cpp index 31ac74b8..56feb152 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/String/StringEndsWithNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/String/StringEndsWithNode.cpp @@ -4,6 +4,7 @@ #include #include +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/String/StringEqual.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/String/StringEqual.cpp index e4f6fda7..916a31d8 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/String/StringEqual.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/String/StringEqual.cpp @@ -4,6 +4,7 @@ #include #include +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/String/StringSizeNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/String/StringSizeNode.cpp index 5afa580b..22defe39 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/String/StringSizeNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/String/StringSizeNode.cpp @@ -4,6 +4,7 @@ #include #include +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Data/TypeParameterNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Data/TypeParameterNode.cpp index 8266bd4c..1afa4f9e 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Data/TypeParameterNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Data/TypeParameterNode.cpp @@ -4,6 +4,7 @@ #include #include +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Functions/PrintNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Functions/PrintNode.cpp index 3ce4955c..fc31d58a 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Functions/PrintNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Functions/PrintNode.cpp @@ -4,6 +4,10 @@ #include #include +#include + +#include + using namespace Noggit::Ui::Tools::NodeEditor::Nodes; PrintNode::PrintNode() diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Functions/PrintNode.hpp b/src/noggit/ui/tools/NodeEditor/Nodes/Functions/PrintNode.hpp index be549c1a..b46102c7 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Functions/PrintNode.hpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Functions/PrintNode.hpp @@ -2,7 +2,6 @@ #define NOGGIT_PRINTNODE_HPP #include "noggit/ui/tools/NodeEditor/Nodes/LogicNodeBase.hpp" -#include using QtNodes::PortType; using QtNodes::PortIndex; @@ -11,6 +10,7 @@ using QtNodes::NodeDataType; using QtNodes::NodeDataModel; using QtNodes::NodeValidationState; +class QLineEdit; namespace Noggit { @@ -29,11 +29,8 @@ namespace Noggit private: QLineEdit* _text; - }; - } - } #endif //NOGGIT_PRINTNODE_HPP diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Logic/ConditionNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Logic/ConditionNode.cpp index e4735e95..fab61832 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Logic/ConditionNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Logic/ConditionNode.cpp @@ -1,6 +1,7 @@ #include "ConditionNode.hpp" #include #include +#include #include #include diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Logic/ConditionNode.hpp b/src/noggit/ui/tools/NodeEditor/Nodes/Logic/ConditionNode.hpp index e2362253..d948c356 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Logic/ConditionNode.hpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Logic/ConditionNode.hpp @@ -1,7 +1,8 @@ #ifndef NOGGIT_CONDITIONNODE_HPP #define NOGGIT_CONDITIONNODE_HPP -#include "noggit/ui/tools/NodeEditor/Nodes/BaseNode.hpp" +#include +#include #include @@ -12,6 +13,8 @@ using QtNodes::NodeDataType; using QtNodes::NodeDataModel; using QtNodes::NodeValidationState; +class QDoubleSpinBox; +class QComboBox; namespace Noggit { diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Logic/LogicBeginNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Logic/LogicBeginNode.cpp index 523eced4..07a15771 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Logic/LogicBeginNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Logic/LogicBeginNode.cpp @@ -2,10 +2,13 @@ #include #include -#include + +#include #include +#include + using namespace Noggit::Ui::Tools::NodeEditor::Nodes; LogicBeginNode::LogicBeginNode() diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Logic/LogicBeginNode.hpp b/src/noggit/ui/tools/NodeEditor/Nodes/Logic/LogicBeginNode.hpp index 5a70d88e..298285bd 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Logic/LogicBeginNode.hpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Logic/LogicBeginNode.hpp @@ -2,7 +2,6 @@ #define NOGGIT_LOGICBEGINNODE_HPP #include "noggit/ui/tools/NodeEditor/Nodes/LogicNodeBase.hpp" -#include #include using QtNodes::PortType; @@ -39,11 +38,8 @@ namespace Noggit private: std::vector _default_widgets; - }; - } - } #endif //NOGGIT_LOGICBEGINNODE_HPP diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Logic/LogicChainNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Logic/LogicChainNode.cpp index 805fe5c2..a293ab7b 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Logic/LogicChainNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Logic/LogicChainNode.cpp @@ -4,6 +4,7 @@ #include #include +#include #include diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Logic/LogicForLoopNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Logic/LogicForLoopNode.cpp index 796b46c6..df3120b1 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Logic/LogicForLoopNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Logic/LogicForLoopNode.cpp @@ -4,6 +4,7 @@ #include #include +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Logic/LogicIfNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Logic/LogicIfNode.cpp index a78ea9eb..748adb84 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Logic/LogicIfNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Logic/LogicIfNode.cpp @@ -2,6 +2,7 @@ #include #include +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Logic/LogicIfNode.hpp b/src/noggit/ui/tools/NodeEditor/Nodes/Logic/LogicIfNode.hpp index 5f88fa1a..70b220ba 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Logic/LogicIfNode.hpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Logic/LogicIfNode.hpp @@ -3,14 +3,8 @@ #include "noggit/ui/tools/NodeEditor/Nodes/LogicNodeBase.hpp" -using QtNodes::PortType; -using QtNodes::PortIndex; -using QtNodes::NodeData; -using QtNodes::NodeDataType; -using QtNodes::NodeDataModel; using QtNodes::NodeValidationState; - namespace Noggit { namespace Ui::Tools::NodeEditor::Nodes @@ -24,9 +18,7 @@ namespace Noggit void compute() override; NodeValidationState validate() override; }; - } - } #endif //NOGGIT_LOGICIFNODE_HPP diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Logic/LogicProcedureNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Logic/LogicProcedureNode.cpp index 3818b3a1..22a763c1 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Logic/LogicProcedureNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Logic/LogicProcedureNode.cpp @@ -7,13 +7,14 @@ #include "LogicReturnNode.hpp" #include #include "noggit/ui/tools/NodeEditor/Nodes/Widgets/ProcedureSelector.hpp" - +#include #include -#include #include #include +#include + using namespace Noggit::Ui::Tools::NodeEditor::Nodes; LogicProcedureNode::LogicProcedureNode() diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Logic/LogicProcedureNode.hpp b/src/noggit/ui/tools/NodeEditor/Nodes/Logic/LogicProcedureNode.hpp index 430474cf..7a55f02b 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Logic/LogicProcedureNode.hpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Logic/LogicProcedureNode.hpp @@ -4,23 +4,15 @@ #define NOGGIT_LOGICPROCEDURENODE_HPP #include "noggit/ui/tools/NodeEditor/Nodes/LogicNodeBase.hpp" -#include "noggit/ui/tools/NodeEditor/Nodes/Scene/NodesContext.hpp" -#include -#include - -using QtNodes::PortType; -using QtNodes::PortIndex; -using QtNodes::NodeData; -using QtNodes::NodeDataType; -using QtNodes::NodeDataModel; using QtNodes::NodeValidationState; - namespace Noggit { namespace Ui::Tools::NodeEditor::Nodes { + class NodeScene; + class LogicProcedureNode : public LogicNodeBase { Q_OBJECT @@ -41,9 +33,7 @@ namespace Noggit NodeScene* _scene; QString _scene_path; }; - } - } #endif //NOGGIT_LOGICPROCEDURENODE_HPP diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Logic/LogicReturnNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Logic/LogicReturnNode.cpp index b03cb86e..5ac5ab3a 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Logic/LogicReturnNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Logic/LogicReturnNode.cpp @@ -5,6 +5,8 @@ #include #include +#include + #include #include diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Logic/LogicReturnNode.hpp b/src/noggit/ui/tools/NodeEditor/Nodes/Logic/LogicReturnNode.hpp index 380af935..27e952c5 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Logic/LogicReturnNode.hpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Logic/LogicReturnNode.hpp @@ -4,7 +4,6 @@ #define NOGGIT_LOGICRETURNNODE_HPP #include "noggit/ui/tools/NodeEditor/Nodes/LogicNodeBase.hpp" -#include using QtNodes::PortType; using QtNodes::Node; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Logic/LogicWhileLoopNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Logic/LogicWhileLoopNode.cpp index 322f3dc9..ca7f27e0 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Logic/LogicWhileLoopNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Logic/LogicWhileLoopNode.cpp @@ -4,6 +4,7 @@ #include #include +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/LogicNodeBase.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/LogicNodeBase.cpp new file mode 100644 index 00000000..6d49151d --- /dev/null +++ b/src/noggit/ui/tools/NodeEditor/Nodes/LogicNodeBase.cpp @@ -0,0 +1,74 @@ +// This file is part of Noggit3, licensed under GNU General Public License (version 3). + +#include "LogicNodeBase.hpp" + +#include +#include + + +namespace Noggit +{ + namespace Ui::Tools::NodeEditor::Nodes + { + LogicNodeBase::LogicNodeBase() + { + } + + void LogicNodeBase::resetInterationIndex() + { + _iteration_index = 0; + } + + unsigned LogicNodeBase::getIterationindex() const + { + return _iteration_index; + } + + bool LogicNodeBase::isLogicNode() + { + return true; + } + + bool LogicNodeBase::isIterable() const + { + return _is_iterable; + } + + void LogicNodeBase::setNIterations(unsigned n_iterations) + { + _n_iterations = n_iterations; + } + + unsigned LogicNodeBase::getNIteraitons() const + { + return _n_iterations; + } + + void LogicNodeBase::setIterationIndex(unsigned index) + { + _iteration_index = index; + } + + NodeValidationState LogicNodeBase::validate() + { + setValidationState(NodeValidationState::Valid); + auto logic = static_cast(_in_ports[0].in_value.lock().get()); + + if (!logic) + { + setValidationState(NodeValidationState::Error); + setValidationMessage("Error: Failed to evaluate logic input"); + + _out_ports[0].out_value = std::make_shared(false); + _node->onDataUpdated(0); + } + + return _validation_state; + } + + void LogicNodeBase::setIsIterable(bool is_iterable) + { + _is_iterable = is_iterable; + } + } +} diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/LogicNodeBase.hpp b/src/noggit/ui/tools/NodeEditor/Nodes/LogicNodeBase.hpp index 1c1dd2d8..81fdc4f8 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/LogicNodeBase.hpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/LogicNodeBase.hpp @@ -4,8 +4,6 @@ #define NOGGIT_LOGICNODEBASE_HPP #include "BaseNode.hpp" -#include -#include using QtNodes::PortType; using QtNodes::PortIndex; @@ -25,46 +23,27 @@ namespace Noggit Q_OBJECT public: - LogicNodeBase() {}; - void resetInterationIndex() { _iteration_index = 0; }; - unsigned getIterationindex() { return _iteration_index; }; + LogicNodeBase(); + void resetInterationIndex(); + unsigned getIterationindex() const; - virtual bool isLogicNode() override { return true; }; + virtual bool isLogicNode() override; - bool isIterable() { return _is_iterable; }; + bool isIterable() const; - void setNIterations(unsigned n_iterations) { _n_iterations = n_iterations; }; - unsigned getNIteraitons() { return _n_iterations; }; - void setIterationIndex(unsigned index) { _iteration_index = index; }; - - NodeValidationState validate() override - { - setValidationState(NodeValidationState::Valid); - auto logic = static_cast(_in_ports[0].in_value.lock().get()); - - if (!logic) - { - setValidationState(NodeValidationState::Error); - setValidationMessage("Error: Failed to evaluate logic input"); - - _out_ports[0].out_value = std::make_shared(false); - _node->onDataUpdated(0); - } - - return _validation_state; - }; + void setNIterations(unsigned n_iterations); + unsigned getNIteraitons() const; + void setIterationIndex(unsigned index); + NodeValidationState validate() override; protected: - - void setIsIterable(bool is_iterable) { _is_iterable = is_iterable; }; + void setIsIterable(bool is_iterable); unsigned _iteration_index = 0; unsigned _n_iterations = 0; bool _is_iterable = false; - - }; } diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Math/Color/ColorMathNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Math/Color/ColorMathNode.cpp index a813357c..35f7c622 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Math/Color/ColorMathNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Math/Color/ColorMathNode.cpp @@ -4,8 +4,13 @@ #include #include + #include +#include + +#include + using namespace Noggit::Ui::Tools::NodeEditor::Nodes; ColorMathNode::ColorMathNode() diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Math/Color/ColorMathNode.hpp b/src/noggit/ui/tools/NodeEditor/Nodes/Math/Color/ColorMathNode.hpp index 5a4ede16..d1c822ec 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Math/Color/ColorMathNode.hpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Math/Color/ColorMathNode.hpp @@ -4,7 +4,6 @@ #define NOGGIT_COLORMATHNODE_HPP #include "noggit/ui/tools/NodeEditor/Nodes/BaseNode.hpp" -#include using QtNodes::PortType; using QtNodes::PortIndex; @@ -13,6 +12,7 @@ using QtNodes::NodeDataType; using QtNodes::NodeDataModel; using QtNodes::NodeValidationState; +class QComboBox; namespace Noggit { @@ -31,9 +31,7 @@ namespace Noggit private: QComboBox* _operation; }; - } - } #endif //NOGGIT_COLORMATHNODE_HPP diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Math/Color/ColorToRGBANode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Math/Color/ColorToRGBANode.cpp index bb9365a4..66b1450b 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Math/Color/ColorToRGBANode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Math/Color/ColorToRGBANode.cpp @@ -4,6 +4,7 @@ #include #include +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Math/Color/RGBAtoColorNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Math/Color/RGBAtoColorNode.cpp index 205f6eef..e264760a 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Math/Color/RGBAtoColorNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Math/Color/RGBAtoColorNode.cpp @@ -4,6 +4,7 @@ #include #include +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Math/MathNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Math/MathNode.cpp index 55154725..b11c11a7 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Math/MathNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Math/MathNode.cpp @@ -1,8 +1,13 @@ #include "MathNode.hpp" + #include #include #include + #include + +#include + #include #include diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Math/MathNode.hpp b/src/noggit/ui/tools/NodeEditor/Nodes/Math/MathNode.hpp index 08fee2c1..c0f82492 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Math/MathNode.hpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Math/MathNode.hpp @@ -3,11 +3,9 @@ #include "noggit/ui/tools/NodeEditor/Nodes/BaseNode.hpp" -#include -#include #include -#include -#include + +class QComboBox; using QtNodes::PortType; using QtNodes::PortIndex; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Math/MathUnaryNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Math/MathUnaryNode.cpp index 4cb34797..4c206cdd 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Math/MathUnaryNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Math/MathUnaryNode.cpp @@ -5,6 +5,10 @@ #include #include +#include + +#include + #include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Math/MathUnaryNode.hpp b/src/noggit/ui/tools/NodeEditor/Nodes/Math/MathUnaryNode.hpp index 770e59bf..7f0083ad 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Math/MathUnaryNode.hpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Math/MathUnaryNode.hpp @@ -4,7 +4,6 @@ #define NOGGIT_MATHUNARYNODE_HPP #include "noggit/ui/tools/NodeEditor/Nodes/BaseNode.hpp" -#include using QtNodes::PortType; using QtNodes::PortIndex; @@ -13,6 +12,7 @@ using QtNodes::NodeDataType; using QtNodes::NodeDataModel; using QtNodes::NodeValidationState; +class QComboBox; namespace Noggit { @@ -32,9 +32,7 @@ namespace Noggit private: QComboBox* _operation; }; - } - } #endif //NOGGIT_MATHUNARYNODE_HPP diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Math/Matrix/MatrixDecomposeNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Math/Matrix/MatrixDecomposeNode.cpp index d1b7d046..9da1fc3c 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Math/Matrix/MatrixDecomposeNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Math/Matrix/MatrixDecomposeNode.cpp @@ -4,6 +4,9 @@ #include #include + +#include + #include #include #include diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Math/Matrix/MatrixMathNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Math/Matrix/MatrixMathNode.cpp index d46e5b03..9359de9a 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Math/Matrix/MatrixMathNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Math/Matrix/MatrixMathNode.cpp @@ -5,6 +5,10 @@ #include #include +#include + +#include + using namespace Noggit::Ui::Tools::NodeEditor::Nodes; MatrixMathNode::MatrixMathNode() diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Math/Matrix/MatrixMathNode.hpp b/src/noggit/ui/tools/NodeEditor/Nodes/Math/Matrix/MatrixMathNode.hpp index c2302c36..d6356fad 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Math/Matrix/MatrixMathNode.hpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Math/Matrix/MatrixMathNode.hpp @@ -4,7 +4,6 @@ #define NOGGIT_MATRIXMATHNODE_HPP #include "noggit/ui/tools/NodeEditor/Nodes/BaseNode.hpp" -#include using QtNodes::PortType; using QtNodes::PortIndex; @@ -13,6 +12,7 @@ using QtNodes::NodeDataType; using QtNodes::NodeDataModel; using QtNodes::NodeValidationState; +class QComboBox; namespace Noggit { @@ -31,11 +31,8 @@ namespace Noggit private: QComboBox* _operation; - }; - } - } #endif //NOGGIT_MATRIXMATHNODE_HPP diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Math/Matrix/MatrixNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Math/Matrix/MatrixNode.cpp index d0eed996..342b71e5 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Math/Matrix/MatrixNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Math/Matrix/MatrixNode.cpp @@ -5,6 +5,10 @@ #include #include +#include + +#include + using namespace Noggit::Ui::Tools::NodeEditor::Nodes; MatrixNode::MatrixNode() diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Math/Matrix/MatrixNode.hpp b/src/noggit/ui/tools/NodeEditor/Nodes/Math/Matrix/MatrixNode.hpp index 2888ff29..53235c5e 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Math/Matrix/MatrixNode.hpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Math/Matrix/MatrixNode.hpp @@ -4,7 +4,6 @@ #define NOGGIT_MATRIXNODE_HPP #include "noggit/ui/tools/NodeEditor/Nodes/BaseNode.hpp" -#include using QtNodes::PortType; using QtNodes::PortIndex; @@ -13,6 +12,7 @@ using QtNodes::NodeDataType; using QtNodes::NodeDataModel; using QtNodes::NodeValidationState; +class QComboBox; namespace Noggit { @@ -30,11 +30,8 @@ namespace Noggit private: QComboBox* _operation; - }; - } - } #endif //NOGGIT_MATRIXNODE_HPP diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Math/Matrix/MatrixRotateQuaternionNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Math/Matrix/MatrixRotateQuaternionNode.cpp index c6c2f9cf..e0296bad 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Math/Matrix/MatrixRotateQuaternionNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Math/Matrix/MatrixRotateQuaternionNode.cpp @@ -5,6 +5,8 @@ #include #include +#include + #include diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Math/Matrix/MatrixTransformNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Math/Matrix/MatrixTransformNode.cpp index ba7df867..e5467c43 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Math/Matrix/MatrixTransformNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Math/Matrix/MatrixTransformNode.cpp @@ -4,6 +4,11 @@ #include #include + +#include + +#include + #include #include diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Math/Matrix/MatrixTransformNode.hpp b/src/noggit/ui/tools/NodeEditor/Nodes/Math/Matrix/MatrixTransformNode.hpp index d327090f..3dad5bf5 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Math/Matrix/MatrixTransformNode.hpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Math/Matrix/MatrixTransformNode.hpp @@ -4,7 +4,6 @@ #define NOGGIT_MATRIXTRANSFORMNODE_HPP #include "noggit/ui/tools/NodeEditor/Nodes/BaseNode.hpp" -#include using QtNodes::PortType; using QtNodes::PortIndex; @@ -13,6 +12,7 @@ using QtNodes::NodeDataType; using QtNodes::NodeDataModel; using QtNodes::NodeValidationState; +class QComboBox; namespace Noggit { @@ -32,9 +32,7 @@ namespace Noggit private: QComboBox* _operation; }; - } - } #endif //NOGGIT_MATRIXTRANSFORMNODE_HPP diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Math/Matrix/MatrixUnaryMathNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Math/Matrix/MatrixUnaryMathNode.cpp index ded2fa1c..c5affc04 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Math/Matrix/MatrixUnaryMathNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Math/Matrix/MatrixUnaryMathNode.cpp @@ -5,6 +5,10 @@ #include #include +#include + +#include + using namespace Noggit::Ui::Tools::NodeEditor::Nodes; MatrixUnaryMathNode::MatrixUnaryMathNode() diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Math/Matrix/MatrixUnaryMathNode.hpp b/src/noggit/ui/tools/NodeEditor/Nodes/Math/Matrix/MatrixUnaryMathNode.hpp index 60882a1e..735b6ef0 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Math/Matrix/MatrixUnaryMathNode.hpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Math/Matrix/MatrixUnaryMathNode.hpp @@ -4,7 +4,6 @@ #define NOGGIT_MATRIXUNARYMATHNODE_HPP #include "noggit/ui/tools/NodeEditor/Nodes/BaseNode.hpp" -#include using QtNodes::PortType; using QtNodes::PortIndex; @@ -13,6 +12,7 @@ using QtNodes::NodeDataType; using QtNodes::NodeDataModel; using QtNodes::NodeValidationState; +class QComboBox; namespace Noggit { @@ -32,9 +32,7 @@ namespace Noggit private: QComboBox* _operation; }; - } - } #endif //NOGGIT_MATRIXUNARYMATHNODE_HPP diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Math/Vector/Vector2DToXYNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Math/Vector/Vector2DToXYNode.cpp index b93efec9..4de8dbf3 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Math/Vector/Vector2DToXYNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Math/Vector/Vector2DToXYNode.cpp @@ -6,6 +6,8 @@ #include #include +#include + using namespace Noggit::Ui::Tools::NodeEditor::Nodes; Vector2DToXYNode::Vector2DToXYNode() diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Math/Vector/Vector3DToXYZNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Math/Vector/Vector3DToXYZNode.cpp index 643d272f..c54ad092 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Math/Vector/Vector3DToXYZNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Math/Vector/Vector3DToXYZNode.cpp @@ -5,6 +5,8 @@ #include #include +#include + using namespace Noggit::Ui::Tools::NodeEditor::Nodes; Vector3DToXYZNode::Vector3DToXYZNode() diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Math/Vector/Vector4DToXYZWNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Math/Vector/Vector4DToXYZWNode.cpp index c746bd17..1c6e0327 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Math/Vector/Vector4DToXYZWNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Math/Vector/Vector4DToXYZWNode.cpp @@ -5,6 +5,8 @@ #include #include +#include + using namespace Noggit::Ui::Tools::NodeEditor::Nodes; Vector4DToXYZWNode::Vector4DToXYZWNode() diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Math/Vector/VectorMathNode.hpp b/src/noggit/ui/tools/NodeEditor/Nodes/Math/Vector/VectorMathNode.hpp index 99c25a41..e6459f95 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Math/Vector/VectorMathNode.hpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Math/Vector/VectorMathNode.hpp @@ -5,6 +5,9 @@ #include "noggit/ui/tools/NodeEditor/Nodes/BaseNode.hpp" #include + +#include + #include using QtNodes::PortType; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Math/Vector/VectorScalarMathNode.hpp b/src/noggit/ui/tools/NodeEditor/Nodes/Math/Vector/VectorScalarMathNode.hpp index edb607f2..d913778f 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Math/Vector/VectorScalarMathNode.hpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Math/Vector/VectorScalarMathNode.hpp @@ -8,6 +8,8 @@ #include +#include + using QtNodes::PortType; using QtNodes::PortIndex; using QtNodes::NodeData; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Math/Vector/XYZWtoVector4DNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Math/Vector/XYZWtoVector4DNode.cpp index b8711091..c138f3b0 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Math/Vector/XYZWtoVector4DNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Math/Vector/XYZWtoVector4DNode.cpp @@ -5,6 +5,8 @@ #include #include +#include + using namespace Noggit::Ui::Tools::NodeEditor::Nodes; XYZWtoVector4DNode::XYZWtoVector4DNode() diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Math/Vector/XYZtoVector3DNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Math/Vector/XYZtoVector3DNode.cpp index b6470445..21c3e59d 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Math/Vector/XYZtoVector3DNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Math/Vector/XYZtoVector3DNode.cpp @@ -6,6 +6,8 @@ #include #include +#include + using namespace Noggit::Ui::Tools::NodeEditor::Nodes; XYZtoVector3DNode::XYZtoVector3DNode() diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Math/Vector/XYtoVector2D.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Math/Vector/XYtoVector2D.cpp index 94fe156f..89cc1130 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Math/Vector/XYtoVector2D.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Math/Vector/XYtoVector2D.cpp @@ -5,6 +5,8 @@ #include #include +#include + using namespace Noggit::Ui::Tools::NodeEditor::Nodes; XYtoVector2DNode::XYtoVector2DNode() diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Scene/LogicBranch.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Scene/LogicBranch.cpp index aa9860bd..131a3855 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Scene/LogicBranch.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Scene/LogicBranch.cpp @@ -6,6 +6,8 @@ #include "noggit/ui/tools/NodeEditor/Nodes/Logic/LogicContinueNode.hpp" #include +#include + #include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; @@ -264,3 +266,18 @@ void LogicBranch::markNodeLeavesComputed(Node* start_node, Node* source_node, bo } } } + +void LogicBranch::setCurrentLoop(Node* node) +{ + _loop_stack.push(node); +} + +void LogicBranch::unsetCurrentLoop() +{ + _loop_stack.pop(); +} + +Node* LogicBranch::getCurrentLoop() +{ + return _loop_stack.empty() ? nullptr : _loop_stack.top(); +} diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Scene/LogicBranch.hpp b/src/noggit/ui/tools/NodeEditor/Nodes/Scene/LogicBranch.hpp index 76c98fd9..758f2666 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Scene/LogicBranch.hpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Scene/LogicBranch.hpp @@ -1,18 +1,14 @@ #ifndef NOGGIT_LOGICBRANCH_HPP #define NOGGIT_LOGICBRANCH_HPP -#include -#include #include -using QtNodes::PortType; -using QtNodes::PortIndex; -using QtNodes::NodeData; +namespace QtNodes +{ + class Node; +} + using QtNodes::Node; -using QtNodes::NodeDataType; -using QtNodes::NodeDataModel; -using QtNodes::NodeValidationState; -using QtNodes::Connection; namespace Noggit @@ -27,9 +23,9 @@ namespace Noggit static bool executeNodeLeaves(Node* node, Node* source_node); void markNodesComputed(Node* start_node, bool state); void markNodeLeavesComputed(Node* start_node, Node* source_node, bool state); - void setCurrentLoop(Node* node) { _loop_stack.push(node); }; - void unsetCurrentLoop() { _loop_stack.pop(); }; - Node* getCurrentLoop() { return _loop_stack.empty() ? nullptr : _loop_stack.top(); }; + void setCurrentLoop(Node* node); + void unsetCurrentLoop(); + Node* getCurrentLoop(); bool execute(); private: @@ -37,9 +33,7 @@ namespace Noggit std::stack _loop_stack; bool _return = false; }; - } - } #endif //NOGGIT_LOGICBRANCH_HPP diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Scene/NodeScene.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Scene/NodeScene.cpp index 3494e9ff..554613b7 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Scene/NodeScene.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Scene/NodeScene.cpp @@ -12,6 +12,11 @@ using namespace Noggit::Ui::Tools::NodeEditor::Nodes; +NodeScene::NodeScene(std::shared_ptr registry, QObject* parent) + : FlowScene(std::move(registry), parent) +{ +} + bool NodeScene::execute() { if (!validate()) @@ -74,3 +79,18 @@ bool NodeScene::validate() return true; } + +Node* NodeScene::getBeginNode() +{ + return _begin_node; +} + +Node* NodeScene::getReturnNode() +{ + return _return_node; +} + +VariableMap* NodeScene::getVariableMap() +{ + return &_variables; +} diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Scene/NodeScene.hpp b/src/noggit/ui/tools/NodeEditor/Nodes/Scene/NodeScene.hpp index 24fc8add..0602a77b 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Scene/NodeScene.hpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Scene/NodeScene.hpp @@ -4,7 +4,12 @@ #include #include -#include "../DataTypes/GenericData.hpp" +namespace QtNodes +{ + class DataModelRegistry; + class Node; + class NodeData; +} using QtNodes::DataModelRegistry; using QtNodes::FlowScene; @@ -14,18 +19,18 @@ namespace Noggit { namespace Ui::Tools::NodeEditor::Nodes { - using VariableMap = tsl::robin_map>>; + using VariableMap = tsl::robin_map>>; class NodeScene : public FlowScene { public: NodeScene(std::shared_ptr registry, - QObject* parent = Q_NULLPTR) : FlowScene(std::move(registry), parent) {}; + QObject* parent = Q_NULLPTR); bool execute(); bool validate(); - Node* getBeginNode() { return _begin_node; }; - Node* getReturnNode() { return _return_node; }; - VariableMap* getVariableMap() { return &_variables; }; + Node* getBeginNode(); + Node* getReturnNode(); + VariableMap* getVariableMap(); private: diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Scene/NodesContext.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Scene/NodesContext.cpp index a2d4dfab..5ac1cf44 100644 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Scene/NodesContext.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Scene/NodesContext.cpp @@ -1,8 +1,11 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). #include "NodesContext.hpp" +#include "NodeScene.hpp" #include "../../NodeRegistry.hpp" +#include + #include #include @@ -54,8 +57,38 @@ NodeScene* Context::getScene(const QString& path, QObject* parent) } +NodeExecutionContext Context::getContextType() const +{ + return _context_type; +} + void Context::makeCurrent() { gCurrentContext = this; } +Noggit::Ui::Tools::NodeEditor::Nodes::VariableMap* Context::getVariableMap() +{ + return &_variable_map; +} + +World* Context::getWorld() +{ + return _world; +} + +Noggit::Ui::Tools::ViewportManager::Viewport* Context::getViewport() +{ + return _viewport; +} + +void Context::setWorld(World* world) +{ + _world = world; +} + +void Context::setViewport(ViewportManager::Viewport* viewport) +{ + _viewport = viewport; +} + diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Scene/NodesContext.hpp b/src/noggit/ui/tools/NodeEditor/Nodes/Scene/NodesContext.hpp index 516316c5..ebba4945 100644 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Scene/NodesContext.hpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Scene/NodesContext.hpp @@ -3,19 +3,27 @@ #ifndef NOGGIT_CONTEXT_HPP #define NOGGIT_CONTEXT_HPP -#include #include -#include "NodeScene.hpp" #include -#include #include +namespace QtNodes +{ + class DataModelRegistry; + class NodeData; +} + +class World; + namespace Noggit { namespace Ui::Tools::NodeEditor::Nodes { - using VariableMap = tsl::robin_map>>; + class Context; + class NodeScene; + + using VariableMap = tsl::robin_map>>; enum NodeExecutionContext { @@ -32,14 +40,14 @@ namespace Noggit Context(NodeExecutionContext context_type, QObject* parent = nullptr); NodeScene* getScene(QString const& path, QObject* parent = nullptr); - NodeExecutionContext getContextType() { return _context_type; }; + NodeExecutionContext getContextType() const; void makeCurrent(); - VariableMap* getVariableMap() { return &_variable_map; }; - World* getWorld() { return _world; }; - ViewportManager::Viewport* getViewport() { return _viewport; }; + Nodes::VariableMap* getVariableMap(); + World* getWorld(); + ViewportManager::Viewport* getViewport(); - void setWorld(World* world) { _world = world; }; - void setViewport(ViewportManager::Viewport* viewport) { _viewport = viewport; }; + void setWorld(World* world); + void setViewport(ViewportManager::Viewport* viewport); private: World* _world; @@ -47,11 +55,10 @@ namespace Noggit tsl::robin_map _scene_cache; NodeExecutionContext _context_type; VariableMap _variable_map; - }; extern Context* gCurrentContext; - extern std::shared_ptr gDataModelRegistry; + extern std::shared_ptr gDataModelRegistry; } } diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Widgets/QUnsignedSpinBox.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/Widgets/QUnsignedSpinBox.cpp new file mode 100644 index 00000000..ebabb1c6 --- /dev/null +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Widgets/QUnsignedSpinBox.cpp @@ -0,0 +1,124 @@ +#include "QUnsignedSpinBox.hpp" + +#include + +#include + +QUnsignedSpinBox::QUnsignedSpinBox(QWidget* parent) + : m_minimum{std::numeric_limits::min()} + , m_maximum{std::numeric_limits::max()} +{ + setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); + lineEdit()->setText("0"); + lineEdit()->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); + connect(lineEdit(), SIGNAL(textEdited(QString)), this, SLOT(onEditFinished())); +} + + QUnsignedSpinBox::~QUnsignedSpinBox() +{ +} + + quint32 QUnsignedSpinBox::value() const +{ + return m_value; +} + + quint32 QUnsignedSpinBox::minimum() const +{ + return m_minimum; +} + + void QUnsignedSpinBox::setMinimum(quint32 min) +{ + m_minimum = min; +} + + quint32 QUnsignedSpinBox::maximum() const +{ + return m_maximum; +} + + void QUnsignedSpinBox::setMaximum(quint32 max) +{ + m_maximum = max; +} + + void QUnsignedSpinBox::setRange(quint32 min, quint32 max) +{ + setMinimum(min); + setMaximum(max); +} + + void QUnsignedSpinBox::stepBy(int steps) +{ + auto new_value = m_value; + if (steps < 0 && new_value + steps > new_value) { + new_value = std::numeric_limits::min(); + } + else if (steps > 0 && new_value + steps < new_value) { + new_value = std::numeric_limits::max(); + } + else { + new_value += steps; + } + + lineEdit()->setText(textFromValue(new_value)); + setValue(new_value); +} + +//bool event(QEvent *event); + QValidator::State QUnsignedSpinBox::validate(QString& input, int& pos) const +{ + if (input.isEmpty()) + return QValidator::Acceptable; + + bool ok; + quint32 val = input.toUInt(&ok); + if (!ok) + return QValidator::Invalid; + + if (val < m_minimum || val > m_maximum) + return QValidator::Invalid; + + return QValidator::Acceptable; +} + + quint32 QUnsignedSpinBox::valueFromText(const QString& text) const +{ + + if (text.isEmpty()) + return 0; + + return text.toUInt(); +} + + QString QUnsignedSpinBox::textFromValue(quint32 val) const +{ + return QString::number(val); +} + + QAbstractSpinBox::StepEnabled QUnsignedSpinBox::stepEnabled() const +{ + return StepUpEnabled | StepDownEnabled; +} + +void QUnsignedSpinBox::setValue(quint32 val) +{ + if (m_value != val) { + lineEdit()->setText(textFromValue(val)); + m_value = val; + } +} + + void QUnsignedSpinBox::onEditFinished() +{ + QString input = lineEdit()->text(); + int pos = 0; + if (QValidator::Acceptable == validate(input, pos)) + setValue(input.isEmpty() ? valueFromText(input) : 0); + else + lineEdit()->setText(textFromValue(m_value)); + + if (input.isEmpty()) + lineEdit()->setText("0"); +} diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/Widgets/QUnsignedSpinBox.hpp b/src/noggit/ui/tools/NodeEditor/Nodes/Widgets/QUnsignedSpinBox.hpp index 76587acf..1fb56242 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/Widgets/QUnsignedSpinBox.hpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/Widgets/QUnsignedSpinBox.hpp @@ -3,9 +3,7 @@ #ifndef NOGGIT_QUNSIGNEDSPINBOX_HPP #define NOGGIT_QUNSIGNEDSPINBOX_HPP -#include #include -#include class QUnsignedSpinBoxPrivate; class QUnsignedSpinBox : public QAbstractSpinBox @@ -23,123 +21,39 @@ Q_OBJECT quint32 m_value = 0; public: - explicit QUnsignedSpinBox(QWidget *parent = nullptr) - { - setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); - lineEdit()->setText("0"); - lineEdit()->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); - connect(lineEdit(), SIGNAL(textEdited(QString)), this, SLOT(onEditFinished())); - }; - ~QUnsignedSpinBox() {}; + explicit QUnsignedSpinBox(QWidget *parent = nullptr); + ~QUnsignedSpinBox(); - quint32 value() const - { - return m_value; - }; + quint32 value() const; - quint32 minimum() const - { - return m_minimum; - }; + quint32 minimum() const; - void setMinimum(quint32 min) - { - m_minimum = min; - } + void setMinimum(quint32 min); - quint32 maximum() const - { - return m_maximum; - }; + quint32 maximum() const; - void setMaximum(quint32 max) - { - m_maximum = max; - } + void setMaximum(quint32 max); - void setRange(quint32 min, quint32 max) - { - setMinimum(min); - setMaximum(max); - } + void setRange(quint32 min, quint32 max); - virtual void stepBy(int steps) - { - auto new_value = m_value; - if (steps < 0 && new_value + steps > new_value) { - new_value = std::numeric_limits::min(); - } - else if (steps > 0 && new_value + steps < new_value) { - new_value = std::numeric_limits::max(); - } - else { - new_value += steps; - } - - lineEdit()->setText(textFromValue(new_value)); - setValue(new_value); - } + virtual void stepBy(int steps); protected: //bool event(QEvent *event); - virtual QValidator::State validate(QString &input, int &pos) const - { - if (input.isEmpty()) - return QValidator::Acceptable; + virtual QValidator::State validate(QString &input, int &pos) const; - bool ok; - quint32 val = input.toUInt(&ok); - if (!ok) - return QValidator::Invalid; + virtual quint32 valueFromText(const QString &text) const; - if (val < m_minimum || val > m_maximum) - return QValidator::Invalid; - - return QValidator::Acceptable; - } - - virtual quint32 valueFromText(const QString &text) const - { - - if (text.isEmpty()) - return 0; - - return text.toUInt(); - } - - virtual QString textFromValue(quint32 val) const - { - return QString::number(val); - } + virtual QString textFromValue(quint32 val) const; //virtual void fixup(QString &str) const; - virtual QAbstractSpinBox::StepEnabled stepEnabled() const - { - return StepUpEnabled | StepDownEnabled; - } + virtual QAbstractSpinBox::StepEnabled stepEnabled() const; public Q_SLOTS: - void setValue(quint32 val) - { - if (m_value != val) { - lineEdit()->setText(textFromValue(val)); - m_value = val; - } - } + void setValue(quint32 val); - void onEditFinished() - { - QString input = lineEdit()->text(); - int pos = 0; - if (QValidator::Acceptable == validate(input, pos)) - setValue(input.isEmpty() ? valueFromText(input) : 0); - else - lineEdit()->setText(textFromValue(m_value)); - - if (input.isEmpty()) - lineEdit()->setText("0"); - } + void onEditFinished(); Q_SIGNALS: void valueChanged(quint32 v); diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkAddDetailDoodads.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkAddDetailDoodads.cpp index 04733e8c..718fc7a6 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkAddDetailDoodads.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkAddDetailDoodads.cpp @@ -1,11 +1,17 @@ +#include "ChunkAddDetailDoodads.hpp" +#include +#include +#include +#include +#include +#include + +#include + #include #include #include #include -#include -#include -#include -#include "ChunkAddDetailDoodads.hpp" using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkAddTextureNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkAddTextureNode.cpp index d44aea40..a4df5009 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkAddTextureNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkAddTextureNode.cpp @@ -2,8 +2,12 @@ #include "ChunkAddTextureNode.hpp" +#include #include #include +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkCanPaintTexture.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkCanPaintTexture.cpp index 727109d8..a4ee3077 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkCanPaintTexture.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkCanPaintTexture.cpp @@ -4,8 +4,11 @@ #include #include +#include #include +#include + using namespace Noggit::Ui::Tools::NodeEditor::Nodes; ChunkCanPaintTextureNode::ChunkCanPaintTextureNode() diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkClearHeight.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkClearHeight.cpp index d24912ad..401ad291 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkClearHeight.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkClearHeight.cpp @@ -4,8 +4,11 @@ #include #include +#include #include +#include + using namespace Noggit::Ui::Tools::NodeEditor::Nodes; ChunkClearHeightNode::ChunkClearHeightNode() diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkClearShadows.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkClearShadows.cpp index b776613c..af969b80 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkClearShadows.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkClearShadows.cpp @@ -4,8 +4,11 @@ #include #include +#include #include +#include + using namespace Noggit::Ui::Tools::NodeEditor::Nodes; ChunkClearShadowsNode::ChunkClearShadowsNode() @@ -46,4 +49,4 @@ NodeValidationState ChunkClearShadowsNode::validate() } return ContextLogicNodeBase::validate(); -} \ No newline at end of file +} diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkEraseTextures.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkEraseTextures.cpp index fb818676..2c1d175a 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkEraseTextures.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkEraseTextures.cpp @@ -4,8 +4,11 @@ #include #include +#include #include +#include + using namespace Noggit::Ui::Tools::NodeEditor::Nodes; ChunkEraseTexturesNode::ChunkEraseTexturesNode() @@ -45,4 +48,4 @@ NodeValidationState ChunkEraseTexturesNode::validate() } return ContextLogicNodeBase::validate(); -} \ No newline at end of file +} diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkEraseUnusedTextures.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkEraseUnusedTextures.cpp index 83fc0d93..3099fccb 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkEraseUnusedTextures.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkEraseUnusedTextures.cpp @@ -2,9 +2,13 @@ #include "ChunkEraseUnusedTextures.hpp" +#include +#include #include #include -#include +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkFindTextureNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkFindTextureNode.cpp index 4f8029a9..dc2d2640 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkFindTextureNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkFindTextureNode.cpp @@ -2,9 +2,13 @@ #include "ChunkFindTextureNode.hpp" +#include +#include #include #include -#include +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkGetAlphaLayer.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkGetAlphaLayer.cpp index ad6d4423..ff8bb132 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkGetAlphaLayer.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkGetAlphaLayer.cpp @@ -2,9 +2,13 @@ #include "ChunkGetAlphaLayer.hpp" +#include +#include #include #include -#include +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkGetHeightmap.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkGetHeightmap.cpp index e4a04177..c42cf9e8 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkGetHeightmap.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkGetHeightmap.cpp @@ -4,8 +4,11 @@ #include #include +#include #include +#include + using namespace Noggit::Ui::Tools::NodeEditor::Nodes; ChunkGetHeightmapNode::ChunkGetHeightmapNode() @@ -63,4 +66,4 @@ NodeValidationState ChunkGetHeightmapNode::validate() } return ContextLogicNodeBase::validate(); -} \ No newline at end of file +} diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkGetHeightmapImage.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkGetHeightmapImage.cpp index 34f705fa..5acb8ce5 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkGetHeightmapImage.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkGetHeightmapImage.cpp @@ -4,6 +4,10 @@ #include #include +#include + +#include + #include #include @@ -62,4 +66,4 @@ NodeValidationState ChunkGetHeightmapImageNode::validate() } return ContextLogicNodeBase::validate(); -} \ No newline at end of file +} diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkGetTextureByLayer.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkGetTextureByLayer.cpp index ccea9c23..ccfe3c80 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkGetTextureByLayer.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkGetTextureByLayer.cpp @@ -2,9 +2,13 @@ #include "ChunkGetTextureByLayer.hpp" +#include +#include #include #include -#include +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkGetVertexColors.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkGetVertexColors.cpp index f96186a8..f1b9f06e 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkGetVertexColors.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkGetVertexColors.cpp @@ -4,8 +4,11 @@ #include #include +#include #include +#include + using namespace Noggit::Ui::Tools::NodeEditor::Nodes; ChunkGetVertexColorsNode::ChunkGetVertexColorsNode() diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkGetVertexColorsImage.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkGetVertexColorsImage.cpp index 2c6975d4..4fb1be25 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkGetVertexColorsImage.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkGetVertexColorsImage.cpp @@ -4,8 +4,11 @@ #include #include +#include #include +#include + using namespace Noggit::Ui::Tools::NodeEditor::Nodes; ChunkGetVertexColorsImageNode::ChunkGetVertexColorsImageNode() diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkInfoNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkInfoNode.cpp index 81f72993..28d2ad43 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkInfoNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkInfoNode.cpp @@ -2,9 +2,13 @@ #include "ChunkInfoNode.hpp" +#include +#include #include #include -#include +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkRecalculateNormals.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkRecalculateNormals.cpp index 7102cec6..1ffda49c 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkRecalculateNormals.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkRecalculateNormals.cpp @@ -4,6 +4,10 @@ #include #include +#include +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkSetAlphaLayer.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkSetAlphaLayer.cpp index 70b957d0..dd8880dd 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkSetAlphaLayer.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkSetAlphaLayer.cpp @@ -2,9 +2,13 @@ #include "ChunkSetAlphaLayer.hpp" +#include +#include #include #include -#include +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkSetAreaID.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkSetAreaID.cpp index ef33b101..eb19f8a3 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkSetAreaID.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkSetAreaID.cpp @@ -4,8 +4,11 @@ #include #include +#include #include +#include + using namespace Noggit::Ui::Tools::NodeEditor::Nodes; ChunkSetAreaIDNode::ChunkSetAreaIDNode() @@ -48,4 +51,4 @@ NodeValidationState ChunkSetAreaIDNode::validate() } return ContextLogicNodeBase::validate(); -} \ No newline at end of file +} diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkSetHeightmap.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkSetHeightmap.cpp index 10226f0b..a3c0f074 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkSetHeightmap.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkSetHeightmap.cpp @@ -4,8 +4,11 @@ #include #include +#include #include +#include + using namespace Noggit::Ui::Tools::NodeEditor::Nodes; ChunkSetHeightmapNode::ChunkSetHeightmapNode() diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkSetHeightmapImage.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkSetHeightmapImage.cpp index f66850bf..5eae01e6 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkSetHeightmapImage.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkSetHeightmapImage.cpp @@ -4,8 +4,13 @@ #include #include +#include #include +#include + +#include + using namespace Noggit::Ui::Tools::NodeEditor::Nodes; ChunkSetHeightmapImageNode::ChunkSetHeightmapImageNode() @@ -93,4 +98,4 @@ void ChunkSetHeightmapImageNode::restore(QJsonObject const& json_obj) _operation->setCurrentIndex(json_obj["operation"].toInt()); ContextLogicNodeBase::restore(json_obj); -} \ No newline at end of file +} diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkSetHeightmapImage.hpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkSetHeightmapImage.hpp index 0dca3150..cb07ef56 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkSetHeightmapImage.hpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkSetHeightmapImage.hpp @@ -4,7 +4,6 @@ #define NOGGIT_CHUNKSETHEIGHTMAPIMAGE_HPP #include -#include using QtNodes::PortType; using QtNodes::PortIndex; @@ -13,6 +12,8 @@ using QtNodes::NodeDataType; using QtNodes::NodeDataModel; using QtNodes::NodeValidationState; +class QComboBox; + namespace Noggit { namespace Ui::Tools::NodeEditor::Nodes diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkSetVertexColors.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkSetVertexColors.cpp index 44a76198..65e9291c 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkSetVertexColors.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkSetVertexColors.cpp @@ -4,8 +4,11 @@ #include #include +#include #include +#include + using namespace Noggit::Ui::Tools::NodeEditor::Nodes; ChunkSetVertexColorsNode::ChunkSetVertexColorsNode() diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkSetVertexColorsImage.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkSetVertexColorsImage.cpp index 6ed1f598..33c7d626 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkSetVertexColorsImage.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkSetVertexColorsImage.cpp @@ -4,8 +4,11 @@ #include #include +#include #include +#include + using namespace Noggit::Ui::Tools::NodeEditor::Nodes; ChunkSetVertexColorsImageNode::ChunkSetVertexColorsImageNode() diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkSwapTexture.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkSwapTexture.cpp index eb10d276..27eac474 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkSwapTexture.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Chunk/ChunkSwapTexture.cpp @@ -4,8 +4,11 @@ #include #include +#include #include +#include + using namespace Noggit::Ui::Tools::NodeEditor::Nodes; ChunkSwapTextureNode::ChunkSwapTextureNode() diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Coordinates/GetChunk.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Coordinates/GetChunk.cpp index 044a78e0..dbaa004e 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Coordinates/GetChunk.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Coordinates/GetChunk.cpp @@ -4,9 +4,12 @@ #include #include +#include #include #include +#include + using namespace Noggit::Ui::Tools::NodeEditor::Nodes; GetChunkNode::GetChunkNode() @@ -63,4 +66,4 @@ NodeValidationState GetChunkNode::validate() } return ContextLogicNodeBase::validate(); -} \ No newline at end of file +} diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Coordinates/GetChunkFromPos.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Coordinates/GetChunkFromPos.cpp index f2982a09..69913c9f 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Coordinates/GetChunkFromPos.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Coordinates/GetChunkFromPos.cpp @@ -4,8 +4,13 @@ #include #include +#include #include #include +#include + +#include + #include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Coordinates/GetChunksInRange.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Coordinates/GetChunksInRange.cpp index 15c013db..8483b6e8 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Coordinates/GetChunksInRange.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Coordinates/GetChunksInRange.cpp @@ -4,8 +4,12 @@ #include #include +#include #include #include +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Coordinates/GetTile.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Coordinates/GetTile.cpp index 16e59606..69178bcb 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Coordinates/GetTile.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Coordinates/GetTile.cpp @@ -4,8 +4,13 @@ #include #include +#include #include #include +#include + +#include + #include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Coordinates/GetTileChunks.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Coordinates/GetTileChunks.cpp index b8153cc9..2b054f0a 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Coordinates/GetTileChunks.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Coordinates/GetTileChunks.cpp @@ -4,9 +4,12 @@ #include #include +#include #include #include +#include + using namespace Noggit::Ui::Tools::NodeEditor::Nodes; GetTileChunksNode::GetTileChunksNode() diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Coordinates/GetTileFromPos.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Coordinates/GetTileFromPos.cpp index 3536c492..bb7c7008 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Coordinates/GetTileFromPos.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Coordinates/GetTileFromPos.cpp @@ -4,8 +4,13 @@ #include #include +#include #include #include +#include + +#include + #include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Coordinates/GetTilesInRange.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Coordinates/GetTilesInRange.cpp index d722e519..9fd50c78 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Coordinates/GetTilesInRange.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Coordinates/GetTilesInRange.cpp @@ -4,8 +4,12 @@ #include #include +#include #include #include +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Coordinates/HasTileAt.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Coordinates/HasTileAt.cpp index ee054277..e1b12f49 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Coordinates/HasTileAt.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Coordinates/HasTileAt.cpp @@ -4,7 +4,11 @@ #include #include +#include #include +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Coordinates/HasTileAtPos.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Coordinates/HasTileAtPos.cpp index 63927092..f1c1cea1 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Coordinates/HasTileAtPos.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Coordinates/HasTileAtPos.cpp @@ -4,7 +4,11 @@ #include #include +#include #include +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Holes/SetHole.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Holes/SetHole.cpp index 0533d565..a7ce8caa 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Holes/SetHole.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Holes/SetHole.cpp @@ -4,6 +4,10 @@ #include #include +#include +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Holes/SetHoleADTAtPos.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Holes/SetHoleADTAtPos.cpp index 77ac362d..8a3b25e9 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Holes/SetHoleADTAtPos.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Holes/SetHoleADTAtPos.cpp @@ -4,6 +4,10 @@ #include #include +#include +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Liquid/CropWaterAdtAtPos.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Liquid/CropWaterAdtAtPos.cpp index 7ff20815..2394668e 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Liquid/CropWaterAdtAtPos.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Liquid/CropWaterAdtAtPos.cpp @@ -4,6 +4,10 @@ #include #include +#include +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Liquid/GetWaterType.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Liquid/GetWaterType.cpp index cb0dc64a..5ad02f92 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Liquid/GetWaterType.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Liquid/GetWaterType.cpp @@ -4,6 +4,10 @@ #include #include +#include +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Liquid/PaintLiquid.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Liquid/PaintLiquid.cpp index b1a7a870..d7a910ab 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Liquid/PaintLiquid.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Liquid/PaintLiquid.cpp @@ -4,7 +4,13 @@ #include #include +#include #include +#include + +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Liquid/PaintLiquid.hpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Liquid/PaintLiquid.hpp index 2de91e37..700428f7 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Liquid/PaintLiquid.hpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Liquid/PaintLiquid.hpp @@ -4,7 +4,6 @@ #define NOGGIT_PAINTLIQUID_HPP #include -#include using QtNodes::PortType; using QtNodes::PortIndex; @@ -13,6 +12,8 @@ using QtNodes::NodeDataType; using QtNodes::NodeDataModel; using QtNodes::NodeValidationState; +class QComboBox; + namespace Noggit { namespace Ui::Tools::NodeEditor::Nodes diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Liquid/SetWaterType.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Liquid/SetWaterType.cpp index bbd81d6e..8c03618e 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Liquid/SetWaterType.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Liquid/SetWaterType.cpp @@ -4,7 +4,13 @@ #include #include +#include #include +#include + +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Liquid/SetWaterType.hpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Liquid/SetWaterType.hpp index df9cdd62..5b8b3bda 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Liquid/SetWaterType.hpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Liquid/SetWaterType.hpp @@ -4,7 +4,6 @@ #define NOGGIT_SETWATERTYPE_HPP #include -#include using QtNodes::PortType; using QtNodes::PortIndex; @@ -13,6 +12,8 @@ using QtNodes::NodeDataType; using QtNodes::NodeDataModel; using QtNodes::NodeValidationState; +class QComboBox; + namespace Noggit { namespace Ui::Tools::NodeEditor::Nodes diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/LoadedTiles/FixAllGapsNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/LoadedTiles/FixAllGapsNode.cpp index 8f247fec..7854b8d9 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/LoadedTiles/FixAllGapsNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/LoadedTiles/FixAllGapsNode.cpp @@ -4,7 +4,11 @@ #include #include +#include #include +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Misc/WorldConstantsNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Misc/WorldConstantsNode.cpp index 41840f08..7b4d428c 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Misc/WorldConstantsNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Misc/WorldConstantsNode.cpp @@ -5,6 +5,9 @@ #include #include #include + +#include + #include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Object/AddObjectInstance.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Object/AddObjectInstance.cpp index 401f8280..51c0fa79 100644 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Object/AddObjectInstance.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Object/AddObjectInstance.cpp @@ -2,11 +2,16 @@ #include "AddObjectInstance.hpp" +#include +#include +#include +#include #include #include -#include -#include -#include +#include +#include + +#include #include diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Object/GetObjectInstanceByUID.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Object/GetObjectInstanceByUID.cpp index 487506d1..627602b6 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Object/GetObjectInstanceByUID.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Object/GetObjectInstanceByUID.cpp @@ -4,8 +4,12 @@ #include #include +#include #include #include +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Object/ObjectInstanceInfo.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Object/ObjectInstanceInfo.cpp index 1f8e225e..731dc7e9 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Object/ObjectInstanceInfo.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Object/ObjectInstanceInfo.cpp @@ -4,6 +4,10 @@ #include #include +#include +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Object/ObjectInstanceSetPosition.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Object/ObjectInstanceSetPosition.cpp index a2d5030f..67d93ed6 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Object/ObjectInstanceSetPosition.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Object/ObjectInstanceSetPosition.cpp @@ -4,9 +4,12 @@ #include #include +#include #include #include +#include + using namespace Noggit::Ui::Tools::NodeEditor::Nodes; ObjectInstanceSetPositionNode::ObjectInstanceSetPositionNode() diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Object/ObjectInstanceSetRotation.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Object/ObjectInstanceSetRotation.cpp index 39657433..9a222ee9 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Object/ObjectInstanceSetRotation.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Object/ObjectInstanceSetRotation.cpp @@ -4,9 +4,12 @@ #include #include +#include #include #include +#include + using namespace Noggit::Ui::Tools::NodeEditor::Nodes; ObjectInstanceSetRotationNode::ObjectInstanceSetRotationNode() diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Object/ObjectInstanceSetScale.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Object/ObjectInstanceSetScale.cpp index f3620498..5f0a4c8c 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Object/ObjectInstanceSetScale.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Object/ObjectInstanceSetScale.cpp @@ -4,10 +4,13 @@ #include #include +#include #include #include #include +#include + using namespace Noggit::Ui::Tools::NodeEditor::Nodes; ObjectInstanceSetScaleNode::ObjectInstanceSetScaleNode() diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/AddObjectInstanceToSelection.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/AddObjectInstanceToSelection.cpp index fbe37323..bcbf27c5 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/AddObjectInstanceToSelection.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/AddObjectInstanceToSelection.cpp @@ -4,6 +4,10 @@ #include #include +#include +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/AddObjectInstancesToSelectionRange.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/AddObjectInstancesToSelectionRange.cpp index 454d46d3..8782da82 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/AddObjectInstancesToSelectionRange.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/AddObjectInstancesToSelectionRange.cpp @@ -6,6 +6,10 @@ #include #include +#include +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/DeleteSelectedObjectInstances.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/DeleteSelectedObjectInstances.cpp index 53fe70e1..da8ffdd1 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/DeleteSelectedObjectInstances.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/DeleteSelectedObjectInstances.cpp @@ -4,6 +4,10 @@ #include #include +#include +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/DeselectObjectInstance.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/DeselectObjectInstance.cpp index 10cfc5a8..c68f2dd0 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/DeselectObjectInstance.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/DeselectObjectInstance.cpp @@ -4,6 +4,10 @@ #include #include +#include +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/DeselectObjectInstanceByUID.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/DeselectObjectInstanceByUID.cpp index 88469758..031e8afc 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/DeselectObjectInstanceByUID.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/DeselectObjectInstanceByUID.cpp @@ -4,6 +4,10 @@ #include #include +#include +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/GetLastSelectedObjectInstance.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/GetLastSelectedObjectInstance.cpp index 0d618ab9..caba8d30 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/GetLastSelectedObjectInstance.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/GetLastSelectedObjectInstance.cpp @@ -4,6 +4,10 @@ #include #include +#include +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/GetSelectedObjectInstances.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/GetSelectedObjectInstances.cpp index 7916bed2..23f19034 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/GetSelectedObjectInstances.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/GetSelectedObjectInstances.cpp @@ -4,6 +4,10 @@ #include #include +#include +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/IsObjectInstanceSelected.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/IsObjectInstanceSelected.cpp index c599c3f4..9fb7e585 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/IsObjectInstanceSelected.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/IsObjectInstanceSelected.cpp @@ -4,6 +4,10 @@ #include #include +#include +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/IsObjectInstanceSelectedUID.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/IsObjectInstanceSelectedUID.cpp index 98a70e65..27c0b398 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/IsObjectInstanceSelectedUID.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/IsObjectInstanceSelectedUID.cpp @@ -4,6 +4,10 @@ #include #include +#include +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/MoveSelectedObjectInstances.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/MoveSelectedObjectInstances.cpp index 22c3b3d4..c9f592d6 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/MoveSelectedObjectInstances.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/MoveSelectedObjectInstances.cpp @@ -4,6 +4,10 @@ #include #include +#include +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/ResetSelection.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/ResetSelection.cpp index 69b0bf60..a0747760 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/ResetSelection.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/ResetSelection.cpp @@ -4,6 +4,10 @@ #include #include +#include +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/RotateSelectedObjectInstances.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/RotateSelectedObjectInstances.cpp index 90dbd5bd..c05fe1ec 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/RotateSelectedObjectInstances.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/RotateSelectedObjectInstances.cpp @@ -4,6 +4,10 @@ #include #include +#include +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/ScaleSelectedObjectInstances.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/ScaleSelectedObjectInstances.cpp index d9dbc0c0..3364f4e3 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/ScaleSelectedObjectInstances.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/ScaleSelectedObjectInstances.cpp @@ -4,6 +4,12 @@ #include #include +#include +#include + +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/ScaleSelectedObjectInstances.hpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/ScaleSelectedObjectInstances.hpp index fc882d80..e36f5a3f 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/ScaleSelectedObjectInstances.hpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/ScaleSelectedObjectInstances.hpp @@ -4,7 +4,6 @@ #define NOGGIT_SCALESELECTEDOBJECTINSTANCES_HPP #include -#include using QtNodes::PortType; using QtNodes::PortIndex; @@ -13,6 +12,8 @@ using QtNodes::NodeDataType; using QtNodes::NodeDataModel; using QtNodes::NodeValidationState; +class QComboBox; + namespace Noggit { namespace Ui::Tools::NodeEditor::Nodes diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/SelectionInfo.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/SelectionInfo.cpp index 54f3afc7..25327a36 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/SelectionInfo.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/SelectionInfo.cpp @@ -4,6 +4,10 @@ #include #include +#include +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/SetCurrentSelection.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/SetCurrentSelection.cpp index 594bb17f..70f06dd4 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/SetCurrentSelection.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/SetCurrentSelection.cpp @@ -4,6 +4,10 @@ #include #include +#include +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/SetSelectedObjectInstancesPosition.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/SetSelectedObjectInstancesPosition.cpp index f361f419..e94b83d8 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/SetSelectedObjectInstancesPosition.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/SetSelectedObjectInstancesPosition.cpp @@ -4,6 +4,10 @@ #include #include +#include +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/SetSelectedObjectInstancesRotation.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/SetSelectedObjectInstancesRotation.cpp index 4c795a16..25efa414 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/SetSelectedObjectInstancesRotation.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/SetSelectedObjectInstancesRotation.cpp @@ -4,6 +4,10 @@ #include #include +#include +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/SnapSelectedObjectInstancesToGround.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/SnapSelectedObjectInstancesToGround.cpp index f5cde474..bd4ba4e2 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/SnapSelectedObjectInstancesToGround.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Selection/SnapSelectedObjectInstancesToGround.cpp @@ -4,6 +4,10 @@ #include #include +#include +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Shading/ShadingPaintColorNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Shading/ShadingPaintColorNode.cpp index e3fa92c9..00e51a92 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Shading/ShadingPaintColorNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Shading/ShadingPaintColorNode.cpp @@ -4,7 +4,11 @@ #include #include +#include #include +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Shading/ShadingPickColorNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Shading/ShadingPickColorNode.cpp index 46170e43..e95dfa38 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Shading/ShadingPickColorNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Shading/ShadingPickColorNode.cpp @@ -4,7 +4,11 @@ #include #include +#include #include +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Terrain/TerrainBlurNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Terrain/TerrainBlurNode.cpp index af501b8d..f6aac91d 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Terrain/TerrainBlurNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Terrain/TerrainBlurNode.cpp @@ -4,7 +4,13 @@ #include #include +#include #include +#include + +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Terrain/TerrainBlurNode.hpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Terrain/TerrainBlurNode.hpp index f1dac24e..4e4f1305 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Terrain/TerrainBlurNode.hpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Terrain/TerrainBlurNode.hpp @@ -4,7 +4,6 @@ #define NOGGIT_TERRAINBLURNODE_HPP #include -#include using QtNodes::PortType; using QtNodes::PortIndex; @@ -13,6 +12,8 @@ using QtNodes::NodeDataType; using QtNodes::NodeDataModel; using QtNodes::NodeValidationState; +class QComboBox; + namespace Noggit { namespace Ui::Tools::NodeEditor::Nodes diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Terrain/TerrainClearHeight.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Terrain/TerrainClearHeight.cpp index 50cdb583..2ca61ff2 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Terrain/TerrainClearHeight.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Terrain/TerrainClearHeight.cpp @@ -4,7 +4,11 @@ #include #include +#include #include +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Terrain/TerrainClearVertexSelection.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Terrain/TerrainClearVertexSelection.cpp index 09c4135f..e68dac3a 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Terrain/TerrainClearVertexSelection.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Terrain/TerrainClearVertexSelection.cpp @@ -4,7 +4,11 @@ #include #include +#include #include +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Terrain/TerrainDeselectVertices.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Terrain/TerrainDeselectVertices.cpp index 2dd596f4..a91e8ec6 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Terrain/TerrainDeselectVertices.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Terrain/TerrainDeselectVertices.cpp @@ -4,7 +4,11 @@ #include #include +#include #include +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Terrain/TerrainFlattenNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Terrain/TerrainFlattenNode.cpp index a21b20dc..d5f36060 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Terrain/TerrainFlattenNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Terrain/TerrainFlattenNode.cpp @@ -4,7 +4,13 @@ #include #include +#include #include +#include + +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Terrain/TerrainFlattenNode.hpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Terrain/TerrainFlattenNode.hpp index 5f09f13b..c33dc6a5 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Terrain/TerrainFlattenNode.hpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Terrain/TerrainFlattenNode.hpp @@ -4,7 +4,6 @@ #define NOGGIT_TERRAINFLATTENNODE_HPP #include -#include using QtNodes::PortType; using QtNodes::PortIndex; @@ -13,6 +12,8 @@ using QtNodes::NodeDataType; using QtNodes::NodeDataModel; using QtNodes::NodeValidationState; +class QComboBox; + namespace Noggit { namespace Ui::Tools::NodeEditor::Nodes diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Terrain/TerrainFlattenVertices.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Terrain/TerrainFlattenVertices.cpp index 8a6fd687..fffe427b 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Terrain/TerrainFlattenVertices.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Terrain/TerrainFlattenVertices.cpp @@ -4,7 +4,11 @@ #include #include +#include #include +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Terrain/TerrainMoveSelectedVertices.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Terrain/TerrainMoveSelectedVertices.cpp index 6f8dca1c..94897cb2 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Terrain/TerrainMoveSelectedVertices.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Terrain/TerrainMoveSelectedVertices.cpp @@ -4,7 +4,11 @@ #include #include +#include #include +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Terrain/TerrainOrientVerticesNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Terrain/TerrainOrientVerticesNode.cpp index 3af212e4..8d31a00d 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Terrain/TerrainOrientVerticesNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Terrain/TerrainOrientVerticesNode.cpp @@ -4,7 +4,11 @@ #include #include +#include #include +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Terrain/TerrainRaiseLowerNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Terrain/TerrainRaiseLowerNode.cpp index cd986a22..8762585e 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Terrain/TerrainRaiseLowerNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Terrain/TerrainRaiseLowerNode.cpp @@ -4,7 +4,13 @@ #include #include +#include #include +#include + +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Terrain/TerrainRaiseLowerNode.hpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Terrain/TerrainRaiseLowerNode.hpp index d24bf2a1..8fb8e94c 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Terrain/TerrainRaiseLowerNode.hpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Terrain/TerrainRaiseLowerNode.hpp @@ -4,7 +4,6 @@ #define NOGGIT_TERRAINRAISELOWERNODE_HPP #include -#include using QtNodes::PortType; using QtNodes::PortIndex; @@ -13,6 +12,7 @@ using QtNodes::NodeDataType; using QtNodes::NodeDataModel; using QtNodes::NodeValidationState; +class QComboBox; namespace Noggit { diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Terrain/TerrainSelectVertices.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Terrain/TerrainSelectVertices.cpp index e206f412..a4a8818a 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Terrain/TerrainSelectVertices.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Terrain/TerrainSelectVertices.cpp @@ -4,7 +4,11 @@ #include #include +#include #include +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Texturing/TexturingClearTexturesAdtAtPosNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Texturing/TexturingClearTexturesAdtAtPosNode.cpp index 0d785db1..d9898d1a 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Texturing/TexturingClearTexturesAdtAtPosNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Texturing/TexturingClearTexturesAdtAtPosNode.cpp @@ -4,7 +4,11 @@ #include #include +#include #include +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Texturing/TexturingPaintTextureNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Texturing/TexturingPaintTextureNode.cpp index 4f883038..af009c41 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Texturing/TexturingPaintTextureNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Texturing/TexturingPaintTextureNode.cpp @@ -4,8 +4,12 @@ #include #include +#include #include #include +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Texturing/TexturingRemoveTexDuplisAdtAtPosNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Texturing/TexturingRemoveTexDuplisAdtAtPosNode.cpp index 902062cb..05acc341 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Texturing/TexturingRemoveTexDuplisAdtAtPosNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Texturing/TexturingRemoveTexDuplisAdtAtPosNode.cpp @@ -4,7 +4,11 @@ #include #include +#include #include +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Texturing/TexturingSetAdtBaseTextureAtPosNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Texturing/TexturingSetAdtBaseTextureAtPosNode.cpp index 25224d71..a496cbd9 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Texturing/TexturingSetAdtBaseTextureAtPosNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Texturing/TexturingSetAdtBaseTextureAtPosNode.cpp @@ -4,9 +4,12 @@ #include #include +#include #include #include +#include + using namespace Noggit::Ui::Tools::NodeEditor::Nodes; TexturingSetAdtBaseTextureAtPosNode::TexturingSetAdtBaseTextureAtPosNode() diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Texturing/TexturingSprayTextureNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Texturing/TexturingSprayTextureNode.cpp index 1859b423..59081b9f 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Texturing/TexturingSprayTextureNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Texturing/TexturingSprayTextureNode.cpp @@ -4,8 +4,12 @@ #include #include +#include #include #include +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Texturing/TexturingSwapTextureAtPosNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Texturing/TexturingSwapTextureAtPosNode.cpp index b632fc4e..37e963ea 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Texturing/TexturingSwapTextureAtPosNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Texturing/TexturingSwapTextureAtPosNode.cpp @@ -4,7 +4,11 @@ #include #include +#include #include +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Texturing/TexturingSwapTextureAtPosRadiusNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Texturing/TexturingSwapTextureAtPosRadiusNode.cpp index a4bf420a..ff7960be 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Texturing/TexturingSwapTextureAtPosRadiusNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Texturing/TexturingSwapTextureAtPosRadiusNode.cpp @@ -4,7 +4,11 @@ #include #include +#include #include +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Texturing/TexturingTilesetNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Texturing/TexturingTilesetNode.cpp index 0cdbe172..6f5ff387 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Texturing/TexturingTilesetNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Texturing/TexturingTilesetNode.cpp @@ -6,6 +6,11 @@ #include #include +#include + +#include +#include + using namespace Noggit::Ui::Tools::NodeEditor::Nodes; TexturingTilesetNode::TexturingTilesetNode() diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Texturing/TexturingTilesetNode.hpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Texturing/TexturingTilesetNode.hpp index 0809208b..a655cd82 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Texturing/TexturingTilesetNode.hpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Texturing/TexturingTilesetNode.hpp @@ -4,8 +4,6 @@ #define NOGGIT_TEXTURINGTILESETNODE_HPP #include -#include -#include using QtNodes::PortType; using QtNodes::PortIndex; @@ -14,6 +12,8 @@ using QtNodes::NodeDataType; using QtNodes::NodeDataModel; using QtNodes::NodeValidationState; +class QLabel; +class QPushButton; namespace Noggit { diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Tile/ReloadTileNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Tile/ReloadTileNode.cpp index 8070175b..5a90a173 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Tile/ReloadTileNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Tile/ReloadTileNode.cpp @@ -4,7 +4,11 @@ #include #include +#include #include +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Tile/TileGetAlphaLayer.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Tile/TileGetAlphaLayer.cpp index e6fb1e4d..d7c5edbd 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Tile/TileGetAlphaLayer.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Tile/TileGetAlphaLayer.cpp @@ -4,8 +4,11 @@ #include #include +#include #include +#include + using namespace Noggit::Ui::Tools::NodeEditor::Nodes; TileGetAlphaLayerNode::TileGetAlphaLayerNode() diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Tile/TileGetAlphaLayerTexture.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Tile/TileGetAlphaLayerTexture.cpp index 941e958c..1f611dce 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Tile/TileGetAlphaLayerTexture.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Tile/TileGetAlphaLayerTexture.cpp @@ -4,8 +4,11 @@ #include #include +#include #include +#include + using namespace Noggit::Ui::Tools::NodeEditor::Nodes; TileGetAlphaLayerTextureNode::TileGetAlphaLayerTextureNode() diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Tile/TileGetHeightMapImage.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Tile/TileGetHeightMapImage.cpp index ba1bf158..aae58779 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Tile/TileGetHeightMapImage.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Tile/TileGetHeightMapImage.cpp @@ -4,8 +4,11 @@ #include #include +#include #include +#include + using namespace Noggit::Ui::Tools::NodeEditor::Nodes; TileGetHeightmapImageNode::TileGetHeightmapImageNode() diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Tile/TileGetMinMaxHeight.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Tile/TileGetMinMaxHeight.cpp index 27d40a6d..e39690c5 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Tile/TileGetMinMaxHeight.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Tile/TileGetMinMaxHeight.cpp @@ -4,8 +4,11 @@ #include #include +#include #include +#include + using namespace Noggit::Ui::Tools::NodeEditor::Nodes; TileGetMinMaxHeightNode::TileGetMinMaxHeightNode() diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Tile/TileGetObjectsUIDs.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Tile/TileGetObjectsUIDs.cpp index f6ff877a..997c9e71 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Tile/TileGetObjectsUIDs.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Tile/TileGetObjectsUIDs.cpp @@ -4,6 +4,9 @@ #include #include +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Tile/TileGetVertexColorsImage.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Tile/TileGetVertexColorsImage.cpp index ce0298c1..50fb02d9 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Tile/TileGetVertexColorsImage.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Tile/TileGetVertexColorsImage.cpp @@ -4,6 +4,9 @@ #include #include +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Tile/TileGetVertexNode.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Tile/TileGetVertexNode.cpp index 07f41d0e..cebfb9af 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Tile/TileGetVertexNode.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Tile/TileGetVertexNode.cpp @@ -4,8 +4,11 @@ #include #include +#include #include +#include + using namespace Noggit::Ui::Tools::NodeEditor::Nodes; TileGetVertexNode::TileGetVertexNode() diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Tile/TileRecalculateNormals.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Tile/TileRecalculateNormals.cpp index 01c7a183..363b84e7 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Tile/TileRecalculateNormals.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Tile/TileRecalculateNormals.cpp @@ -4,6 +4,10 @@ #include #include +#include +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Tile/TileSetAlphaLayer.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Tile/TileSetAlphaLayer.cpp index defbc001..6cb51c1e 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Tile/TileSetAlphaLayer.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Tile/TileSetAlphaLayer.cpp @@ -4,8 +4,11 @@ #include #include +#include #include +#include + using namespace Noggit::Ui::Tools::NodeEditor::Nodes; TileSetAlphaLayerNode::TileSetAlphaLayerNode() diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Tile/TileSetHeightmapImage.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Tile/TileSetHeightmapImage.cpp index ba72666f..0c037090 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Tile/TileSetHeightmapImage.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Tile/TileSetHeightmapImage.cpp @@ -4,8 +4,13 @@ #include #include +#include #include +#include + +#include + using namespace Noggit::Ui::Tools::NodeEditor::Nodes; TileSetHeightmapImageNode::TileSetHeightmapImageNode() diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Tile/TileSetHeightmapImage.hpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Tile/TileSetHeightmapImage.hpp index 0b30e126..2183330e 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Tile/TileSetHeightmapImage.hpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Tile/TileSetHeightmapImage.hpp @@ -4,7 +4,6 @@ #define NOGGIT_TILESETHEIGHTMAPIMAGE_HPP #include -#include using QtNodes::PortType; using QtNodes::PortIndex; @@ -13,6 +12,8 @@ using QtNodes::NodeDataType; using QtNodes::NodeDataModel; using QtNodes::NodeValidationState; +class QComboBox; + namespace Noggit { namespace Ui::Tools::NodeEditor::Nodes diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Tile/TileSetVertexColorsImage.cpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Tile/TileSetVertexColorsImage.cpp index 2aeef7fa..68c36da2 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Tile/TileSetVertexColorsImage.cpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Tile/TileSetVertexColorsImage.cpp @@ -4,6 +4,11 @@ #include #include +#include + +#include + +#include using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Nodes/World/Tile/TileSetVertexColorsImage.hpp b/src/noggit/ui/tools/NodeEditor/Nodes/World/Tile/TileSetVertexColorsImage.hpp index 4c6088aa..6b338fd9 100755 --- a/src/noggit/ui/tools/NodeEditor/Nodes/World/Tile/TileSetVertexColorsImage.hpp +++ b/src/noggit/ui/tools/NodeEditor/Nodes/World/Tile/TileSetVertexColorsImage.hpp @@ -4,7 +4,6 @@ #define NOGGIT_TILESETVERTEXCOLORSIMAGE_HPP #include -#include using QtNodes::PortType; using QtNodes::PortIndex; @@ -13,6 +12,8 @@ using QtNodes::NodeDataType; using QtNodes::NodeDataModel; using QtNodes::NodeValidationState; +class QComboBox; + namespace Noggit { namespace Ui::Tools::NodeEditor::Nodes diff --git a/src/noggit/ui/tools/NodeEditor/Ui/NodeEditor.cpp b/src/noggit/ui/tools/NodeEditor/Ui/NodeEditor.cpp index e11d5cb5..f95c24c5 100755 --- a/src/noggit/ui/tools/NodeEditor/Ui/NodeEditor.cpp +++ b/src/noggit/ui/tools/NodeEditor/Ui/NodeEditor.cpp @@ -1,23 +1,31 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). +#include + +#include "../NodeRegistry.hpp" #include "NodeEditor.hpp" -#include -#include -#include -#include #include #include +#include +#include +#include +#include +#include + +#include #include #include #include -#include -#include -#include -#include -#include #include +#include +#include +#include #include +#include +#include +#include +#include using namespace Noggit::Ui::Tools::NodeEditor::Ui; using namespace Noggit::Ui::Tools::NodeEditor::Nodes; diff --git a/src/noggit/ui/tools/NodeEditor/Ui/NodeEditor.hpp b/src/noggit/ui/tools/NodeEditor/Ui/NodeEditor.hpp index baac39d9..366de8ca 100755 --- a/src/noggit/ui/tools/NodeEditor/Ui/NodeEditor.hpp +++ b/src/noggit/ui/tools/NodeEditor/Ui/NodeEditor.hpp @@ -3,37 +3,45 @@ #ifndef NOGGIT_NODEEDITOR_HPP #define NOGGIT_NODEEDITOR_HPP -#include -#include "../NodeRegistry.hpp" -#include #include -#include -#include -#include + +class QFileSystemModel; +class QSortFilterProxyModel; +class QWidget; + +namespace Ui +{ + class NodeEditor; +} namespace Noggit { - namespace Ui::Tools::NodeEditor::Ui + namespace Ui::Tools + { + class PreviewRenderer; + } + + namespace Ui::Tools::NodeEditor::Ui + { + class NodeEditorWidget : public QMainWindow { - class NodeEditorWidget : public QMainWindow - { - public: - explicit NodeEditorWidget(QWidget* parent = nullptr); - ~NodeEditorWidget() override; + public: + explicit NodeEditorWidget(QWidget* parent = nullptr); + ~NodeEditorWidget() override; - void loadScene(QString const& filepath); + void loadScene(QString const& filepath); - private: + private: - ::Ui::NodeEditor* ui; + ::Ui::NodeEditor* ui; - QFileSystemModel* _model; - QSortFilterProxyModel* _sort_model; - PreviewRenderer* _preview_renderer; - }; - } + QFileSystemModel* _model; + QSortFilterProxyModel* _sort_model; + PreviewRenderer* _preview_renderer; + }; + } } #endif //NOGGIT_NODEEDITOR_HPP diff --git a/src/noggit/ui/tools/PresetEditor/ModelView.cpp b/src/noggit/ui/tools/PresetEditor/ModelView.cpp index dc7e1d6e..6f0d6670 100755 --- a/src/noggit/ui/tools/PresetEditor/ModelView.cpp +++ b/src/noggit/ui/tools/PresetEditor/ModelView.cpp @@ -1,6 +1,12 @@ #include "ModelView.hpp" -#include -#include + +#include +#include + +#include + +#include +#include #include @@ -138,6 +144,21 @@ void ModelViewer::loadWorldUnderlay(const std::string& internal_name, int map_id } +World* Noggit::Ui::Tools::PresetEditor::ModelViewer::getWorld() +{ + return _world.get(); +} + +Noggit::Camera* Noggit::Ui::Tools::PresetEditor::ModelViewer::getCamera() +{ + return &_camera; +} + +Noggit::Camera* Noggit::Ui::Tools::PresetEditor::ModelViewer::getWorldCamera() +{ + return &_world_camera; +} + glm::mat4x4 ModelViewer::world_model_view() const { return _world_camera.look_at_matrix(); diff --git a/src/noggit/ui/tools/PresetEditor/ModelView.hpp b/src/noggit/ui/tools/PresetEditor/ModelView.hpp index 1eff6b1e..be80ed28 100755 --- a/src/noggit/ui/tools/PresetEditor/ModelView.hpp +++ b/src/noggit/ui/tools/PresetEditor/ModelView.hpp @@ -2,14 +2,12 @@ #define NOGGIT_BROWSER_MODELVIEW_HPP #include -#include #include #include #include -#include #include - +class World; namespace Noggit { @@ -21,9 +19,9 @@ namespace Noggit explicit ModelViewer(QWidget* parent = nullptr); void loadWorldUnderlay(std::string const& internal_name, int map_id); - World* getWorld() { return _world.get(); }; - Noggit::Camera* getCamera() { return &_camera; }; - Noggit::Camera* getWorldCamera() { return &_world_camera; }; + World* getWorld();; + Noggit::Camera* getCamera();; + Noggit::Camera* getWorldCamera();; private: std::unique_ptr _world; @@ -45,11 +43,7 @@ namespace Noggit ImGuizmo::MODE _gizmo_mode = ImGuizmo::MODE::WORLD; ImGuizmo::OPERATION _gizmo_operation = ImGuizmo::OPERATION::TRANSLATE; Noggit::BoolToggleProperty _gizmo_on = {true}; - - }; - - } } diff --git a/src/noggit/ui/tools/PresetEditor/Ui/PresetEditor.cpp b/src/noggit/ui/tools/PresetEditor/Ui/PresetEditor.cpp index 0b82ec33..8fb7300c 100755 --- a/src/noggit/ui/tools/PresetEditor/Ui/PresetEditor.cpp +++ b/src/noggit/ui/tools/PresetEditor/Ui/PresetEditor.cpp @@ -1,8 +1,18 @@ #include "PresetEditor.hpp" -#include -#include -#include + +#include +#include + #include +#include +#include +#include +#include + +#include + +#include +#include using namespace Noggit::Ui::Tools::PresetEditor::Ui; using namespace Noggit::Ui; @@ -233,4 +243,4 @@ void PresetEditorWidget::setupConnectsCommon() PresetEditorWidget::~PresetEditorWidget() { -} \ No newline at end of file +} diff --git a/src/noggit/ui/tools/PresetEditor/Ui/PresetEditor.hpp b/src/noggit/ui/tools/PresetEditor/Ui/PresetEditor.hpp index 16e00bf4..aca439b3 100755 --- a/src/noggit/ui/tools/PresetEditor/Ui/PresetEditor.hpp +++ b/src/noggit/ui/tools/PresetEditor/Ui/PresetEditor.hpp @@ -1,20 +1,30 @@ #ifndef NOGGIT_PRESETEDITOR_HPP #define NOGGIT_PRESETEDITOR_HPP -#include -#include - -#include -#include - #include #include -#include -#include + +class QFileSystemModel; +class QSortFilterProxyModel; + +namespace Ui +{ + class PresetEditor; + class PresetEditorOverlay; +} namespace Noggit { - namespace Ui::Tools::PresetEditor::Ui + namespace Project + { + class NoggitProject; + } + + namespace Ui::Tools + { + class PreviewRenderer; + + namespace PresetEditor::Ui { class PresetEditorWidget : public QMainWindow { @@ -30,10 +40,9 @@ namespace Noggit QFileSystemModel* _model; QSortFilterProxyModel* _sort_model; PreviewRenderer* _preview_renderer; - - }; } + } } #endif //NOGGIT_PRESETEDITOR_HPP diff --git a/src/noggit/ui/tools/PreviewRenderer/PreviewRenderer.cpp b/src/noggit/ui/tools/PreviewRenderer/PreviewRenderer.cpp index 5de78446..3be5a109 100755 --- a/src/noggit/ui/tools/PreviewRenderer/PreviewRenderer.cpp +++ b/src/noggit/ui/tools/PreviewRenderer/PreviewRenderer.cpp @@ -1,21 +1,24 @@ #include "PreviewRenderer.hpp" -#include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include +#include + +#include -#include #include -#include #include -#include -#include +#include +#include -#include #include #include +#include #include @@ -48,6 +51,10 @@ PreviewRenderer::PreviewRenderer(int width, int height, Noggit::NoggitRenderCont _light_dir = glm::vec3(0.0f, 1.0f, 0.0f); } +Noggit::Ui::Tools::PreviewRenderer::~PreviewRenderer() +{ +} + void PreviewRenderer::setModel(std::string const &filename) { _filename = filename; diff --git a/src/noggit/ui/tools/PreviewRenderer/PreviewRenderer.hpp b/src/noggit/ui/tools/PreviewRenderer/PreviewRenderer.hpp index 255394bd..98810c8b 100755 --- a/src/noggit/ui/tools/PreviewRenderer/PreviewRenderer.hpp +++ b/src/noggit/ui/tools/PreviewRenderer/PreviewRenderer.hpp @@ -2,18 +2,12 @@ #define NOGGIT_PREVIEWRENDERER_HPP #include -#include -#include -#include -#include #include #include #include #include #include "noggit/rendering/LiquidTextureManager.hpp" -#include -#include #include #include #include @@ -21,6 +15,10 @@ #include +class ModelInstance; +class WMOInstance; + +class QSettings; namespace Noggit::Ui::Tools { @@ -31,6 +29,7 @@ class PreviewRenderer : public Noggit::Ui::Tools::ViewportManager::Viewport public: explicit PreviewRenderer(int width, int height, Noggit::NoggitRenderContext context, QWidget* parent = nullptr); + ~PreviewRenderer(); void resetCamera(float x = 0.f, float y = 0.f, float z = 0.f, float roll = 0.f, float yaw = 120.f, float pitch = 20.f); QPixmap* renderToPixmap(); diff --git a/src/noggit/ui/tools/ToolPanel/ToolPanel.cpp b/src/noggit/ui/tools/ToolPanel/ToolPanel.cpp index 066026c9..cbe90275 100755 --- a/src/noggit/ui/tools/ToolPanel/ToolPanel.cpp +++ b/src/noggit/ui/tools/ToolPanel/ToolPanel.cpp @@ -3,7 +3,6 @@ #include #include "ToolPanel.hpp" -#include using namespace Noggit::Ui::Tools; diff --git a/src/noggit/ui/tools/UiCommon/ExtendedSlider.cpp b/src/noggit/ui/tools/UiCommon/ExtendedSlider.cpp index 7676caaa..5109cf18 100755 --- a/src/noggit/ui/tools/UiCommon/ExtendedSlider.cpp +++ b/src/noggit/ui/tools/UiCommon/ExtendedSlider.cpp @@ -1,15 +1,17 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). #include "ExtendedSlider.hpp" +#include #include + #include -#include -#include #include -#include #include +#include #include +#include +#include using namespace Noggit::Ui::Tools::UiCommon; using namespace Noggit::Ui; diff --git a/src/noggit/ui/tools/UiCommon/ExtendedSlider.hpp b/src/noggit/ui/tools/UiCommon/ExtendedSlider.hpp index 5b9e19e9..94b11aa1 100755 --- a/src/noggit/ui/tools/UiCommon/ExtendedSlider.hpp +++ b/src/noggit/ui/tools/UiCommon/ExtendedSlider.hpp @@ -3,46 +3,47 @@ #ifndef NOGGIT_EXTENDEDSLIDER_HPP #define NOGGIT_EXTENDEDSLIDER_HPP -#include #include #include "ui_ExtendedSliderUi.h" namespace Noggit { - namespace Ui::Tools::UiCommon + class TabletManager; + + namespace Ui::Tools::UiCommon + { + class ExtendedSlider : public QWidget { - class ExtendedSlider : public QWidget - { - Q_OBJECT - public: - ExtendedSlider(QWidget* parent = nullptr); + Q_OBJECT + public: + ExtendedSlider(QWidget* parent = nullptr); - void setPrefix(const QString& prefix); - void setMinimum(double min); - void setMaximum(double max); - void setRange(double min, double max); - void setDecimals(int decimals); - void setSingleStep(double val); - void setSliderRange(int min, int max); - void setValue(double value); - double value(); - double rawValue(); + void setPrefix(const QString& prefix); + void setMinimum(double min); + void setMaximum(double max); + void setRange(double min, double max); + void setDecimals(int decimals); + void setSingleStep(double val); + void setSliderRange(int min, int max); + void setValue(double value); + double value(); + double rawValue(); - void setTabletSupportEnabled(bool state); + void setTabletSupportEnabled(bool state); - signals: - void valueChanged(double value); + signals: + void valueChanged(double value); - private: - ::Ui::ExtendedSliderUi _ui; - bool _is_tablet_supported = true; - bool _is_tablet_affecting = false; - QWidget* _tablet_popup; - unsigned _tablet_sens_factor = 300; - TabletManager* _tablet_manager; + private: + ::Ui::ExtendedSliderUi _ui; + bool _is_tablet_supported = true; + bool _is_tablet_affecting = false; + QWidget* _tablet_popup; + unsigned _tablet_sens_factor = 300; + TabletManager* _tablet_manager; - }; - } + }; + } } #endif //NOGGIT_EXTENDEDSLIDER_HPP diff --git a/src/noggit/ui/tools/UiCommon/ImageBrowser.cpp b/src/noggit/ui/tools/UiCommon/ImageBrowser.cpp index b287d883..e36b4a86 100755 --- a/src/noggit/ui/tools/UiCommon/ImageBrowser.cpp +++ b/src/noggit/ui/tools/UiCommon/ImageBrowser.cpp @@ -2,12 +2,12 @@ #include "ImageBrowser.hpp" -#include #include -#include -#include #include #include +#include +#include +#include using namespace Noggit::Ui::Tools; @@ -128,4 +128,4 @@ void ImageBrowser::keyPressEvent(QKeyEvent* event) auto samples_path = QDir::cleanPath(QCoreApplication::applicationDirPath() + QDir::separator() + "samples"); _ui.treeView->setRootIndex(_dir_proxy_model->mapFromSource(_model->index(samples_path))); } -} \ No newline at end of file +} diff --git a/src/noggit/ui/tools/UiCommon/ImageBrowser.hpp b/src/noggit/ui/tools/UiCommon/ImageBrowser.hpp index 857a80fd..326ca73b 100755 --- a/src/noggit/ui/tools/UiCommon/ImageBrowser.hpp +++ b/src/noggit/ui/tools/UiCommon/ImageBrowser.hpp @@ -5,9 +5,10 @@ #include #include -#include #include +class QSortFilterProxyModel; + namespace Noggit::Ui::Tools { class ImageBrowserFilesystemModel : public QFileSystemModel diff --git a/src/noggit/ui/tools/UiCommon/ImageMaskSelector.cpp b/src/noggit/ui/tools/UiCommon/ImageMaskSelector.cpp index 9cd0d229..2bc4620d 100755 --- a/src/noggit/ui/tools/UiCommon/ImageMaskSelector.cpp +++ b/src/noggit/ui/tools/UiCommon/ImageMaskSelector.cpp @@ -1,8 +1,9 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). #include "ImageMaskSelector.hpp" + #include -#include +#include using namespace Noggit::Ui::Tools; @@ -63,6 +64,16 @@ ImageMaskSelector::ImageMaskSelector( MapView* map_view, QWidget* parent) } +bool Noggit::Ui::Tools::ImageMaskSelector::isEnabled() const +{ + return _ui.groupBox->isChecked(); +} + +int Noggit::Ui::Tools::ImageMaskSelector::getRotation() const +{ + return _ui.dial->value(); +} + void ImageMaskSelector::setImageMask(QString const& path) { _pixmap = QPixmap(path, "PNG"); @@ -75,6 +86,17 @@ void ImageMaskSelector::setImageMask(QString const& path) emit pixmapUpdated(&_pixmap); } +QString const& Noggit::Ui::Tools::ImageMaskSelector::getImageMaskPath() const +{ + return _image_path; +} + +void Noggit::Ui::Tools::ImageMaskSelector::enableControls(bool state) +{ + _ui.dial->setEnabled(!state); + _ui.randomizeRotation->setEnabled(!state); +} + void ImageMaskSelector::setRotation(int value) { int orientation = _ui.dial->value() + value; @@ -90,8 +112,56 @@ void ImageMaskSelector::setRotation(int value) _ui.dial->setSliderPosition(orientation); } +void Noggit::Ui::Tools::ImageMaskSelector::setRotationRaw(int value) +{ + _ui.dial->setValue(value); +} + +int Noggit::Ui::Tools::ImageMaskSelector::getBrushMode() const +{ + return _ui.brushMode->checkedId(); +} + +void Noggit::Ui::Tools::ImageMaskSelector::setBrushMode(int mode) +{ + if (mode) + { + _ui.sculptRadio->setChecked(true); + } + else + { + _ui.stampRadio->setChecked(true); + } +} + +QPixmap* Noggit::Ui::Tools::ImageMaskSelector::getPixmap() +{ + return &_pixmap; +} + void ImageMaskSelector::setContinuousActionName(QString const& name) { _ui.sculptRadio->setText(name); } +bool Noggit::Ui::Tools::ImageMaskSelector::getRandomizeRotation() const +{ + return _ui.randomizeRotation->isChecked(); +} + +void Noggit::Ui::Tools::ImageMaskSelector::setRandomizeRotation(bool state) +{ + _ui.randomizeRotation->setChecked(state); +} + +void Noggit::Ui::Tools::ImageMaskSelector::setBrushModeVisible(bool state) +{ + _ui.sculptRadio->setVisible(state); + _ui.stampRadio->setVisible(state); +} + +QDial* Noggit::Ui::Tools::ImageMaskSelector::getMaskOrientationDial() +{ + return _ui.dial; +} + diff --git a/src/noggit/ui/tools/UiCommon/ImageMaskSelector.hpp b/src/noggit/ui/tools/UiCommon/ImageMaskSelector.hpp index 628f4b88..88416f07 100755 --- a/src/noggit/ui/tools/UiCommon/ImageMaskSelector.hpp +++ b/src/noggit/ui/tools/UiCommon/ImageMaskSelector.hpp @@ -6,33 +6,34 @@ #include #include #include -#include class MapView; namespace Noggit::Ui::Tools { + class ImageBrowser; + class ImageMaskSelector : public QWidget { Q_OBJECT public: ImageMaskSelector(MapView* map_view, QWidget* parent = nullptr); - bool isEnabled() { return _ui.groupBox->isChecked(); }; - int getRotation() { return _ui.dial->value(); }; + bool isEnabled() const;; + int getRotation() const;; void setRotation(int value); - void setRotationRaw(int value) { _ui.dial->setValue(value); }; - int getBrushMode() { return _ui.brushMode->checkedId(); }; - void setBrushMode(int mode) { if (mode) _ui.sculptRadio->setChecked(true); else _ui.stampRadio->setChecked(true); }; - QPixmap* getPixmap() { return &_pixmap; }; + void setRotationRaw(int value);; + int getBrushMode() const;; + void setBrushMode(int mode);; + QPixmap* getPixmap();; void setContinuousActionName(QString const& name); - bool getRandomizeRotation() { return _ui.randomizeRotation->isChecked();}; - void setRandomizeRotation(bool state) { _ui.randomizeRotation->setChecked(state);}; - void setBrushModeVisible(bool state) { _ui.sculptRadio->setVisible(state); _ui.stampRadio->setVisible(state);}; - QDial* getMaskOrientationDial() { return _ui.dial; }; + bool getRandomizeRotation() const;; + void setRandomizeRotation(bool state);; + void setBrushModeVisible(bool state);; + QDial* getMaskOrientationDial();; void setImageMask(QString const& path); - QString const& getImageMaskPath() { return _image_path; }; - void enableControls(bool state) {_ui.dial->setEnabled(!state); _ui.randomizeRotation->setEnabled(!state); }; + QString const& getImageMaskPath() const;; + void enableControls(bool state);; signals: void pixmapUpdated(QPixmap* pixmap); diff --git a/src/noggit/ui/tools/UiCommon/ReorderableVerticalBox.cpp b/src/noggit/ui/tools/UiCommon/ReorderableVerticalBox.cpp index 4d465d54..be5b1bda 100755 --- a/src/noggit/ui/tools/UiCommon/ReorderableVerticalBox.cpp +++ b/src/noggit/ui/tools/UiCommon/ReorderableVerticalBox.cpp @@ -9,6 +9,16 @@ using namespace Noggit::Ui::Tools; +ReorderableVerticalBox::ReorderableVerticalBox(QWidget* parent) + : QWidget(parent) + , oldX{0} + , oldY{0} + , mouseClickX{0} + , mouseClickY{0} + , activeRectWidget{nullptr} +{ +} + void ReorderableVerticalBox::mouseMoveEvent(QMouseEvent* event) { if (!dragInitiated) diff --git a/src/noggit/ui/tools/UiCommon/ReorderableVerticalBox.hpp b/src/noggit/ui/tools/UiCommon/ReorderableVerticalBox.hpp index 4c1b25e8..0c5f38c7 100755 --- a/src/noggit/ui/tools/UiCommon/ReorderableVerticalBox.hpp +++ b/src/noggit/ui/tools/UiCommon/ReorderableVerticalBox.hpp @@ -5,18 +5,14 @@ #include #include -#include namespace Noggit::Ui::Tools { - - class ReorderableVerticalBox : public QWidget { - public: - ReorderableVerticalBox(QWidget* parent = nullptr) : QWidget(parent) {}; + ReorderableVerticalBox(QWidget* parent = nullptr);; void mouseMoveEvent(QMouseEvent* event) override; diff --git a/src/noggit/ui/tools/UiCommon/StackedWidget.cpp b/src/noggit/ui/tools/UiCommon/StackedWidget.cpp index 7dcd4408..66001f69 100755 --- a/src/noggit/ui/tools/UiCommon/StackedWidget.cpp +++ b/src/noggit/ui/tools/UiCommon/StackedWidget.cpp @@ -5,15 +5,18 @@ #include StackedWidget::StackedWidget(QWidget * parent, Qt::WindowFlags f) - : QWidget(parent, f), - curr_index(0) + : QWidget(parent, f) + , auto_resize{false} + , curr_index(0) { QVBoxLayout* layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); } int StackedWidget::count() -{ return widgets.count(); } +{ + return widgets.count(); +} void StackedWidget::addWidget(QWidget* w) { @@ -60,4 +63,4 @@ QSize StackedWidget::sizeHint() return currentWidget()->minimumSize(); else return QWidget::sizeHint(); -} \ No newline at end of file +} diff --git a/src/noggit/ui/tools/UiCommon/expanderwidget.h b/src/noggit/ui/tools/UiCommon/expanderwidget.h index d43dd129..69d92618 100755 --- a/src/noggit/ui/tools/UiCommon/expanderwidget.h +++ b/src/noggit/ui/tools/UiCommon/expanderwidget.h @@ -8,67 +8,65 @@ class QPushButton; class QStackedWidget; class QVBoxLayout; - - class ExpanderWidget : public QWidget { - Q_OBJECT + Q_OBJECT Q_PROPERTY(QString expanderTitle READ expanderTitle WRITE setExpanderTitle STORED true) Q_PROPERTY(bool isExpanded READ isExpanded WRITE setExpanded STORED true) Q_PROPERTY(QIcon collapsedIcon READ collapsedIcon WRITE setCollapsedIcon STORED true) Q_PROPERTY(QIcon expandedIcon READ expandedIcon WRITE setExpandedIcon STORED true) - Q_PROPERTY(QString settingsKey READ settingsKey WRITE setSettingsKey STORED true) - + Q_PROPERTY(QString settingsKey READ settingsKey WRITE setSettingsKey STORED true) + public: - explicit ExpanderWidget(QWidget *parent = 0, bool in_designer=false); - virtual ~ExpanderWidget(){}; + explicit ExpanderWidget(QWidget* parent = 0, bool in_designer = false); + virtual ~ExpanderWidget() {}; - QSize sizeHint() const; + QSize sizeHint() const; - int count() const; - int currentIndex() const; - QWidget *widget(int index); + int count() const; + int currentIndex() const; + QWidget* widget(int index); - QString expanderTitle() const; - bool isExpanded() const; + QString expanderTitle() const; + bool isExpanded() const; + + QIcon collapsedIcon() const; + QIcon expandedIcon() const; + + QString settingsKey() const; - QIcon collapsedIcon() const; - QIcon expandedIcon() const; - - QString settingsKey() const; - public slots: - void buttonPressed(); - void setExpanderTitle(const QString &title); - void setExpanded(bool flag); - - void setCollapsedIcon(const QIcon & icon); - void setExpandedIcon(const QIcon & icon); - - void setSettingsKey(QString key); - - void addPage(QWidget *page); - void insertPage(int index, QWidget *page); - void removePage(int index); - void setCurrentIndex(int index); + void buttonPressed(); + void setExpanderTitle(const QString& title); + void setExpanded(bool flag); + + void setCollapsedIcon(const QIcon& icon); + void setExpandedIcon(const QIcon& icon); + + void setSettingsKey(QString key); + + void addPage(QWidget* page); + void insertPage(int index, QWidget* page); + void removePage(int index); + void setCurrentIndex(int index); signals: - void currentIndexChanged(int index); - void expanderChanged(bool flag); + void currentIndexChanged(int index); + void expanderChanged(bool flag); private: - bool m_expanded; - bool m_in_designer; - - QPushButton *m_button; - QStackedWidget *m_stackWidget; - QVBoxLayout *m_layout; - - QIcon m_collapsedIcon; - QIcon m_expandedIcon; - - QString m_settingsKey; + bool m_expanded; + bool m_in_designer; + + QPushButton* m_button; + QStackedWidget* m_stackWidget; + QVBoxLayout* m_layout; + + QIcon m_collapsedIcon; + QIcon m_expandedIcon; + + QString m_settingsKey; }; #endif diff --git a/src/noggit/ui/tools/ViewToolbar/Ui/ViewToolbar.cpp b/src/noggit/ui/tools/ViewToolbar/Ui/ViewToolbar.cpp index e33e998a..f131905c 100755 --- a/src/noggit/ui/tools/ViewToolbar/Ui/ViewToolbar.cpp +++ b/src/noggit/ui/tools/ViewToolbar/Ui/ViewToolbar.cpp @@ -1,16 +1,65 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). -#include #include -#include -#include +#include #include +#include +#include +#include + +#include +#include +#include +#include +#include #include #include using namespace Noggit::Ui; using namespace Noggit::Ui::Tools::ViewToolbar::Ui; +class SliderAction : public QWidgetAction +{ +public: + template + SliderAction(const QString& title, int min, int max, int value, const QString& prec, + std::function func) + : QWidgetAction(NULL) + { + static_assert (std::is_arithmetic::value, "SliderAction - T must be numeric"); + + QWidget* _widget = new QWidget(NULL); + QHBoxLayout* _layout = new QHBoxLayout(); + _layout->setSpacing(3); + _layout->setMargin(0); + QLabel* _label = new QLabel(title); + QLabel* _display = new QLabel(QString::number(func(static_cast(value)), 'f', 2) + tr(" %1").arg(prec)); + + _slider = new QSlider(NULL); + _slider->setOrientation(Qt::Horizontal); + _slider->setMinimum(min); + _slider->setMaximum(max); + _slider->setValue(value); + + connect(_slider, &QSlider::valueChanged, [_display, prec, func](int value) + { + _display->setText(QString::number(func(static_cast(value)), 'f', 2) + tr(" %1").arg(prec)); + }); + + _layout->addWidget(_label); + _layout->addWidget(_slider); + _layout->addWidget(_display); + _widget->setLayout(_layout); + + setDefaultWidget(_widget); + } + + QSlider* slider() { return _slider; } + +private: + QSlider* _slider; +}; + ViewToolbar::ViewToolbar(MapView* mapView) : _tool_group(this) { @@ -381,6 +430,11 @@ void ViewToolbar::setCurrentMode(MapView* mapView, editing_mode mode) } } +editing_mode ViewToolbar::getCurrentMode() const +{ + return current_mode; +} + void ViewToolbar::add_tool_icon(MapView* mapView, Noggit::BoolToggleProperty* view_state, const QString& name, @@ -466,4 +520,134 @@ float ViewToolbar::getAlphaSphereLight() auto slider = toolbar->GET(sphere_light_alpha_index)->slider(); return float(slider->value()) / 100.f; -} \ No newline at end of file +} + +PushButtonAction::PushButtonAction(const QString& text) + : QWidgetAction(NULL) +{ + QWidget* _widget = new QWidget(NULL); + QHBoxLayout* _layout = new QHBoxLayout(); + _layout->setMargin(0); + + _push = new QPushButton(text); + + _layout->addWidget(_push); + _widget->setLayout(_layout); + + setDefaultWidget(_widget); +} + +QPushButton* PushButtonAction::pushbutton() +{ + return _push; +} + +CheckBoxAction::CheckBoxAction(const QString& text, bool checked) + : QWidgetAction(NULL) +{ + QWidget* _widget = new QWidget(NULL); + QHBoxLayout* _layout = new QHBoxLayout(); + _layout->setMargin(0); + + _checkbox = new QCheckBox(text); + _checkbox->setChecked(checked); + + _layout->addWidget(_checkbox); + _widget->setLayout(_layout); + + setDefaultWidget(_widget); +} + +QCheckBox* CheckBoxAction::checkbox() +{ + return _checkbox; +} + +IconAction::IconAction(const QIcon& icon) + : QWidgetAction(NULL) +{ + QWidget* _widget = new QWidget(NULL); + QHBoxLayout* _layout = new QHBoxLayout(); + _layout->setMargin(0); + + _icon = new QLabel(); + _icon->setPixmap(icon.pixmap(QSize(22, 22))); + + _layout->addWidget(_icon); + _widget->setLayout(_layout); + setDefaultWidget(_widget); +} + +QLabel* IconAction::icon() +{ + return _icon; +} + +SpacerAction::SpacerAction(Qt::Orientation orientation) + : QWidgetAction(NULL) +{ + QWidget* _widget = new QWidget(NULL); + QHBoxLayout* _layout = new QHBoxLayout(); + _layout->setMargin(0); + + if (orientation == Qt::Vertical) + _layout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::Expanding)); + + if (orientation == Qt::Horizontal) + _layout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum)); + + _widget->setLayout(_layout); + setDefaultWidget(_widget); +} + +SubToolBarAction::SubToolBarAction() + : QWidgetAction(NULL) +{ + QWidget* _widget = new QWidget(NULL); + QHBoxLayout* _layout = new QHBoxLayout(); + _layout->setSpacing(5); + _layout->setMargin(0); + + _toolbar = new QToolBar(); + _toolbar->setContextMenuPolicy(Qt::PreventContextMenu); + _toolbar->setAllowedAreas(Qt::TopToolBarArea | Qt::BottomToolBarArea); + _toolbar->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); + _toolbar->setOrientation(Qt::Horizontal); + + _layout->addWidget(_toolbar); + _widget->setLayout(_layout); + + setDefaultWidget(_widget); +} + +QToolBar* SubToolBarAction::toolbar() +{ + return _toolbar; +} + +void SubToolBarAction::ADD_ACTION(QWidgetAction* _act) +{ + _actions.push_back(_act); +} + +void SubToolBarAction::SETUP_WIDGET(bool forceSpacer, Qt::Orientation orientation) +{ + _toolbar->clear(); + for (int i = 0; i < _actions.size(); ++i) + { + _toolbar->addAction(_actions[i]); + if (i == _actions.size() - 1) + { + if (forceSpacer) + { + /* TODO: fix this spacer */ + + _toolbar->addAction(new SpacerAction(orientation)); + } + } + else + { + _toolbar->addSeparator(); + } + } +} diff --git a/src/noggit/ui/tools/ViewToolbar/Ui/ViewToolbar.hpp b/src/noggit/ui/tools/ViewToolbar/Ui/ViewToolbar.hpp index e7523bb3..87cac89b 100755 --- a/src/noggit/ui/tools/ViewToolbar/Ui/ViewToolbar.hpp +++ b/src/noggit/ui/tools/ViewToolbar/Ui/ViewToolbar.hpp @@ -2,16 +2,20 @@ #pragma once -#include "qcheckbox.h" -#include - -#include -#include - #include +#include + +#include +#include +#include +#include + class MapView; +class QPushButton; +class QCheckBox; + namespace Noggit { struct BoolToggleProperty; @@ -29,7 +33,7 @@ namespace Noggit void setCurrentMode(MapView* mapView, editing_mode mode); - editing_mode getCurrentMode() const { return current_mode; } + editing_mode getCurrentMode() const; /*secondary top tool*/ QVector _climb_secondary_tool; @@ -74,67 +78,12 @@ namespace Noggit }; - class SliderAction : public QWidgetAction - { - public: - template - SliderAction (const QString &title, int min, int max, int value, const QString& prec, - std::function func) - : QWidgetAction(NULL) - { - static_assert (std::is_arithmetic::value, "SliderAction - T must be numeric"); - - QWidget* _widget = new QWidget(NULL); - QHBoxLayout* _layout = new QHBoxLayout(); - _layout->setSpacing(3); - _layout->setMargin(0); - QLabel* _label = new QLabel(title); - QLabel* _display = new QLabel(QString::number(func(static_cast(value)), 'f', 2) + tr(" %1").arg(prec)); - - _slider = new QSlider(NULL); - _slider->setOrientation(Qt::Horizontal); - _slider->setMinimum(min); - _slider->setMaximum(max); - _slider->setValue(value); - - connect(_slider, &QSlider::valueChanged, [_display, prec, func](int value) - { - _display->setText(QString::number(func(static_cast(value)), 'f', 2) + tr(" %1").arg(prec)); - }); - - _layout->addWidget(_label); - _layout->addWidget(_slider); - _layout->addWidget(_display); - _widget->setLayout(_layout); - - setDefaultWidget(_widget); - } - - QSlider* slider() { return _slider; } - - private: - QSlider *_slider; - }; - class PushButtonAction : public QWidgetAction { public: - PushButtonAction (const QString& text) - : QWidgetAction(NULL) - { - QWidget* _widget = new QWidget(NULL); - QHBoxLayout* _layout = new QHBoxLayout(); - _layout->setMargin(0); + PushButtonAction (const QString& text); - _push = new QPushButton(text); - - _layout->addWidget(_push); - _widget->setLayout(_layout); - - setDefaultWidget(_widget); - } - - QPushButton* pushbutton() { return _push; }; + QPushButton* pushbutton();; private: QPushButton *_push; @@ -143,23 +92,9 @@ namespace Noggit class CheckBoxAction : public QWidgetAction { public: - CheckBoxAction (const QString& text, bool checked = false) - : QWidgetAction(NULL) - { - QWidget* _widget = new QWidget(NULL); - QHBoxLayout* _layout = new QHBoxLayout(); - _layout->setMargin(0); + CheckBoxAction (const QString& text, bool checked = false); - _checkbox = new QCheckBox(text); - _checkbox->setChecked(checked); - - _layout->addWidget(_checkbox); - _widget->setLayout(_layout); - - setDefaultWidget(_widget); - } - - QCheckBox* checkbox() { return _checkbox; }; + QCheckBox* checkbox();; private: QCheckBox *_checkbox; @@ -168,22 +103,9 @@ namespace Noggit class IconAction : public QWidgetAction { public: - IconAction (const QIcon& icon) - : QWidgetAction(NULL) - { - QWidget* _widget = new QWidget(NULL); - QHBoxLayout* _layout = new QHBoxLayout(); - _layout->setMargin(0); + IconAction (const QIcon& icon); - _icon = new QLabel(); - _icon->setPixmap(icon.pixmap(QSize(22,22))); - - _layout->addWidget(_icon); - _widget->setLayout(_layout); - setDefaultWidget(_widget); - } - - QLabel* icon() { return _icon; }; + QLabel* icon();; private: QLabel *_icon; @@ -192,48 +114,15 @@ namespace Noggit class SpacerAction : public QWidgetAction { public: - SpacerAction(Qt::Orientation orientation) - : QWidgetAction(NULL) - { - QWidget* _widget = new QWidget(NULL); - QHBoxLayout* _layout = new QHBoxLayout(); - _layout->setMargin(0); - - if (orientation == Qt::Vertical) - _layout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::Expanding)); - - if (orientation == Qt::Horizontal) - _layout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum)); - - _widget->setLayout(_layout); - setDefaultWidget(_widget); - } + SpacerAction(Qt::Orientation orientation); }; class SubToolBarAction : public QWidgetAction { public: - SubToolBarAction() - : QWidgetAction(NULL) - { - QWidget* _widget = new QWidget(NULL); - QHBoxLayout* _layout = new QHBoxLayout(); - _layout->setSpacing(5); - _layout->setMargin(0); + SubToolBarAction();; - _toolbar = new QToolBar(); - _toolbar->setContextMenuPolicy(Qt::PreventContextMenu); - _toolbar->setAllowedAreas(Qt::TopToolBarArea | Qt::BottomToolBarArea); - _toolbar->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); - _toolbar->setOrientation(Qt::Horizontal); - - _layout->addWidget(_toolbar); - _widget->setLayout(_layout); - - setDefaultWidget(_widget); - }; - - QToolBar* toolbar() { return _toolbar; }; + QToolBar* toolbar();; template T GET(int index) @@ -244,27 +133,8 @@ namespace Noggit return T(); } - void ADD_ACTION(QWidgetAction* _act) { _actions.push_back(_act); }; - void SETUP_WIDGET(bool forceSpacer, Qt::Orientation orientation = Qt::Horizontal) { - _toolbar->clear(); - for (int i = 0; i < _actions.size(); ++i) - { - _toolbar->addAction(_actions[i]); - if (i == _actions.size() - 1) - { - if (forceSpacer) - { - /* TODO: fix this spacer */ - - _toolbar->addAction(new SpacerAction(orientation)); - } - } - else - { - _toolbar->addSeparator(); - } - } - }; + void ADD_ACTION(QWidgetAction* _act);; + void SETUP_WIDGET(bool forceSpacer, Qt::Orientation orientation = Qt::Horizontal);; private: QToolBar* _toolbar; diff --git a/src/noggit/ui/tools/ViewportGizmo/ViewportGizmo.cpp b/src/noggit/ui/tools/ViewportGizmo/ViewportGizmo.cpp index d1fedc3d..5fca4fb5 100755 --- a/src/noggit/ui/tools/ViewportGizmo/ViewportGizmo.cpp +++ b/src/noggit/ui/tools/ViewportGizmo/ViewportGizmo.cpp @@ -1,25 +1,27 @@ #include "ViewportGizmo.hpp" -#include "noggit/ModelInstance.h" -#include "noggit/WMOInstance.h" -#include "noggit/ActionManager.hpp" -#include "noggit/Action.hpp" -#include "external/glm/glm.hpp" -#include -#include -#include -#include -#include + +#include +#include +#include #include +#include +#include +#include +#include -#include - +#include +#include +#include +#include +#include using namespace Noggit::Ui::Tools::ViewportGizmo; ViewportGizmo::ViewportGizmo(Noggit::Ui::Tools::ViewportGizmo::GizmoContext gizmo_context, World* world) : _gizmo_context(gizmo_context) , _world(world) -{} +{ +} void ViewportGizmo::handleTransformGizmo(MapView* map_view , const std::vector& selection @@ -345,3 +347,40 @@ void ViewportGizmo::handleTransformGizmo(MapView* map_view _world->update_selection_pivot(); } +void ViewportGizmo::ViewportGizmo::setCurrentGizmoOperation(ImGuizmo::OPERATION operation) +{ + _gizmo_operation = operation; +} + +void ViewportGizmo::ViewportGizmo::setCurrentGizmoMode(ImGuizmo::MODE mode) +{ + _gizmo_mode = mode; +} + +bool ViewportGizmo::ViewportGizmo::isOver() const +{ + ImGuizmo::SetID(_gizmo_context); + return ImGuizmo::IsOver(); +} + +bool ViewportGizmo::ViewportGizmo::isUsing() const +{ + ImGuizmo::SetID(_gizmo_context); + return ImGuizmo::IsUsing(); +} + +void ViewportGizmo::ViewportGizmo::setUseMultiselectionPivot(bool use_pivot) +{ + _use_multiselection_pivot = use_pivot; +} + +void ViewportGizmo::ViewportGizmo::setMultiselectionPivot(glm::vec3 const& pivot) +{ + _multiselection_pivot = pivot; +} + +void ViewportGizmo::ViewportGizmo::setWorld(World* world) +{ + _world = world; +} + diff --git a/src/noggit/ui/tools/ViewportGizmo/ViewportGizmo.hpp b/src/noggit/ui/tools/ViewportGizmo/ViewportGizmo.hpp index 05af3623..fae2d1a9 100755 --- a/src/noggit/ui/tools/ViewportGizmo/ViewportGizmo.hpp +++ b/src/noggit/ui/tools/ViewportGizmo/ViewportGizmo.hpp @@ -1,16 +1,16 @@ #ifndef NOGGIT_VIEWPORTGIZMO_HPP #define NOGGIT_VIEWPORTGIZMO_HPP -#include #include #include #include -#include +#include #include class MapView; +class World; namespace Noggit { @@ -43,13 +43,13 @@ namespace Noggit , glm::mat4x4 const& model_view , glm::mat4x4 const& projection); - void setCurrentGizmoOperation(ImGuizmo::OPERATION operation) { _gizmo_operation = operation; } - void setCurrentGizmoMode(ImGuizmo::MODE mode) { _gizmo_mode = mode; } - bool isOver() {ImGuizmo::SetID(_gizmo_context); return ImGuizmo::IsOver();}; - bool isUsing() {ImGuizmo::SetID(_gizmo_context); return ImGuizmo::IsUsing();}; - void setUseMultiselectionPivot(bool use_pivot) { _use_multiselection_pivot = use_pivot; }; - void setMultiselectionPivot(glm::vec3 const& pivot) { _multiselection_pivot = pivot; }; - void setWorld(World* world) { _world = world; } + void setCurrentGizmoOperation(ImGuizmo::OPERATION operation); + void setCurrentGizmoMode(ImGuizmo::MODE mode); + bool isOver() const;; + bool isUsing() const;; + void setUseMultiselectionPivot(bool use_pivot);; + void setMultiselectionPivot(glm::vec3 const& pivot);; + void setWorld(World* world); private: diff --git a/src/noggit/ui/tools/ViewportManager/ViewportManager.cpp b/src/noggit/ui/tools/ViewportManager/ViewportManager.cpp index b21e96ae..0cfdd793 100755 --- a/src/noggit/ui/tools/ViewportManager/ViewportManager.cpp +++ b/src/noggit/ui/tools/ViewportManager/ViewportManager.cpp @@ -16,11 +16,33 @@ Viewport::Viewport(QWidget* parent) } +Noggit::NoggitRenderContext Noggit::Ui::Tools::ViewportManager::Viewport::getRenderContext() const +{ + return _context; +} + Viewport::~Viewport() { ViewportManager::unregisterViewport(this); } +void Noggit::Ui::Tools::ViewportManager::ViewportManager::registerViewport(Viewport* viewport) +{ + ViewportManager::_viewports.push_back(viewport); +} + +void Noggit::Ui::Tools::ViewportManager::ViewportManager::unregisterViewport(Viewport* viewport) +{ + for (auto it = ViewportManager::_viewports.begin(); it != ViewportManager::_viewports.end(); ++it) + { + if (viewport == *it) + { + ViewportManager::_viewports.erase(it); + break; + } + } +} + void ViewportManager::unloadOpenglData(Viewport* caller) { for (auto viewport : ViewportManager::_viewports) diff --git a/src/noggit/ui/tools/ViewportManager/ViewportManager.hpp b/src/noggit/ui/tools/ViewportManager/ViewportManager.hpp index b1703324..4586b262 100755 --- a/src/noggit/ui/tools/ViewportManager/ViewportManager.hpp +++ b/src/noggit/ui/tools/ViewportManager/ViewportManager.hpp @@ -14,22 +14,9 @@ namespace Noggit::Ui::Tools::ViewportManager public: static std::vector _viewports; - static void registerViewport(Viewport* viewport) - { - ViewportManager::_viewports.push_back(viewport); - }; + static void registerViewport(Viewport* viewport);; - static void unregisterViewport(Viewport* viewport) - { - for (auto it = ViewportManager::_viewports.begin(); it != ViewportManager::_viewports.end(); ++it) - { - if (viewport == *it) - { - ViewportManager::_viewports.erase(it); - break; - } - } - }; + static void unregisterViewport(Viewport* viewport);; static void unloadOpenglData(Viewport* caller); static void unloadAll(); @@ -44,8 +31,7 @@ namespace Noggit::Ui::Tools::ViewportManager virtual void unloadOpenglData() = 0; - Noggit::NoggitRenderContext getRenderContext() - { return _context; }; + Noggit::NoggitRenderContext getRenderContext() const;; ~Viewport(); diff --git a/src/noggit/ui/uid_fix_mode.hpp b/src/noggit/ui/uid_fix_mode.hpp new file mode 100644 index 00000000..9b3a538f --- /dev/null +++ b/src/noggit/ui/uid_fix_mode.hpp @@ -0,0 +1,10 @@ +// This file is part of Noggit3, licensed under GNU General Public License (version 3). +#pragma once + +enum class uid_fix_mode +{ + none, + max_uid, + fix_all_fail_on_model_loading_error, + fix_all_fuckporting_edition +}; diff --git a/src/noggit/ui/widgets/DynamicMouseWidget.cpp b/src/noggit/ui/widgets/DynamicMouseWidget.cpp index 58592948..0fea518e 100644 --- a/src/noggit/ui/widgets/DynamicMouseWidget.cpp +++ b/src/noggit/ui/widgets/DynamicMouseWidget.cpp @@ -1,5 +1,9 @@ #include "DynamicMouseWidget.h" +#include +#include +#include + DynamicMouseWidget::DynamicMouseWidget(QWidget* parent) : QWidget(parent) { } @@ -25,4 +29,4 @@ void DynamicMouseWidget::mousePressEvent(QMouseEvent* event) void DynamicMouseWidget::mouseReleaseEvent(QMouseEvent* event) { QApplication::restoreOverrideCursor(); -} \ No newline at end of file +} diff --git a/src/noggit/ui/widgets/DynamicMouseWidget.h b/src/noggit/ui/widgets/DynamicMouseWidget.h index 4041f9e1..291ba343 100644 --- a/src/noggit/ui/widgets/DynamicMouseWidget.h +++ b/src/noggit/ui/widgets/DynamicMouseWidget.h @@ -1,9 +1,9 @@ #pragma once -#include -#include #include -#include + +class QEvent; +class QMouseEvent; class DynamicMouseWidget : public QWidget { diff --git a/src/noggit/ui/widgets/LightViewWidget.cpp b/src/noggit/ui/widgets/LightViewWidget.cpp index fb3b37a8..88ef39b7 100644 --- a/src/noggit/ui/widgets/LightViewWidget.cpp +++ b/src/noggit/ui/widgets/LightViewWidget.cpp @@ -1,5 +1,18 @@ #include "LightViewWidget.h" + +#include +#include + +#include + +#include +#include #include +#include +#include +#include +#include +#include LightViewWidget::LightViewWidget(QWidget* parent) : QWidget(parent) @@ -135,6 +148,16 @@ void LightViewPixmap::ShowLines(bool Show) SetPreview(Data); } +void LightViewPixmap::EnableEvent() +{ + bEvent = true; +} + +void LightViewPixmap::DisableEvent() +{ + bEvent = false; +} + void LightViewPixmap::SetPreview(std::vector& data, int Index) { Data = data; @@ -832,4 +855,4 @@ void LightViewArrow::mouseMoveEvent(QMouseEvent* event) int Time = (event->pos().x() - 15) * MAX_TIME_VALUE / (PixmapSize.width() - 30); emit ChangeTime(CurrentHover, Time); } -} \ No newline at end of file +} diff --git a/src/noggit/ui/widgets/LightViewWidget.h b/src/noggit/ui/widgets/LightViewWidget.h index fcafa100..aed8f0a4 100644 --- a/src/noggit/ui/widgets/LightViewWidget.h +++ b/src/noggit/ui/widgets/LightViewWidget.h @@ -1,17 +1,23 @@ #pragma once #include -#include -#include -#include -#include -#include -#include -#include + #include #include #include "DynamicMouseWidget.h" +namespace color_widgets +{ + class ColorSelector; +} + +class MapView; + +class QMouseEvent; +class QPushButton; +class QSpinBox; +class QVBoxLayout; + #define MAX_TIME_VALUE 2880 #define LIGHT_VIEW_PREVIEW_WIDTH 340 @@ -52,8 +58,8 @@ class LightViewPixmap : public QLabel public: LightViewPixmap(QSize Size = QSize(LIGHT_VIEW_PREVIEW_WIDTH, LIGHT_VIEW_PREVIEW_HEIGHT), QWidget* parent = nullptr); void ShowLines(bool show); - void EnableEvent() { bEvent = true; }; - void DisableEvent() { bEvent = false; }; + void EnableEvent();; + void DisableEvent();; void SetPreview(std::vector& data, int Index = -1); protected: @@ -217,7 +223,7 @@ public: static QColor InterpolateColor(QColor& a, QColor& b, float t); static QImage FillImagePart(QImage Image, int X, QColor Color); - static inline int ClampColor(int Value); + static int ClampColor(int Value); static QColor GetColorFromStyleSheet(); static void SortSkyColorVector(std::vector& Vector); }; diff --git a/src/noggit/ui/windows/EditorWindows/SoundEntryPickerWindow.cpp b/src/noggit/ui/windows/EditorWindows/SoundEntryPickerWindow.cpp index f47e43d6..85657576 100644 --- a/src/noggit/ui/windows/EditorWindows/SoundEntryPickerWindow.cpp +++ b/src/noggit/ui/windows/EditorWindows/SoundEntryPickerWindow.cpp @@ -1,29 +1,21 @@ #include "SoundEntryPickerWindow.h" +#include +#include #include // #include -#include -#include -#include -#include -#include - #include #include -#include #include -#include +#include +#include +#include +#include #include -#include -#include -#include -#include -#include -#include +#include #include #include -#include #include #include diff --git a/src/noggit/ui/windows/EditorWindows/SoundEntryPickerWindow.h b/src/noggit/ui/windows/EditorWindows/SoundEntryPickerWindow.h index 88dd8835..6b475d99 100644 --- a/src/noggit/ui/windows/EditorWindows/SoundEntryPickerWindow.h +++ b/src/noggit/ui/windows/EditorWindows/SoundEntryPickerWindow.h @@ -2,22 +2,19 @@ #pragma once -#include - #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include +class QComboBox; +class QCheckBox; +class QDoubleSpinBox; +class QListWidget; +class QLabel; +class QLineEdit; +class QPushButton; +class QSlider; +class QSpinBox; namespace Noggit { diff --git a/src/noggit/ui/windows/EditorWindows/ZoneIntroMusicPickerWindow.cpp b/src/noggit/ui/windows/EditorWindows/ZoneIntroMusicPickerWindow.cpp index f31162eb..b3ac6105 100644 --- a/src/noggit/ui/windows/EditorWindows/ZoneIntroMusicPickerWindow.cpp +++ b/src/noggit/ui/windows/EditorWindows/ZoneIntroMusicPickerWindow.cpp @@ -1,30 +1,20 @@ -#include #include "ZoneIntroMusicPickerWindow.h" +#include // #include #include "SoundEntryPickerWindow.h" - #include -#include -#include -#include -#include +#include #include #include #include #include -#include #include -#include -#include -#include -#include -#include -#include #include +#include +#include #include -#include #include #include diff --git a/src/noggit/ui/windows/EditorWindows/ZoneIntroMusicPickerWindow.h b/src/noggit/ui/windows/EditorWindows/ZoneIntroMusicPickerWindow.h index 442a0248..723c0c2c 100644 --- a/src/noggit/ui/windows/EditorWindows/ZoneIntroMusicPickerWindow.h +++ b/src/noggit/ui/windows/EditorWindows/ZoneIntroMusicPickerWindow.h @@ -2,21 +2,13 @@ #pragma once -#include +#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include +class QLabel; +class QLineEdit; +class QListWidget; +class QPushButton; +class QSpinBox; namespace Noggit diff --git a/src/noggit/ui/windows/EditorWindows/ZoneMusicPickerWindow.cpp b/src/noggit/ui/windows/EditorWindows/ZoneMusicPickerWindow.cpp index 0b2a75b1..b9b05bd8 100644 --- a/src/noggit/ui/windows/EditorWindows/ZoneMusicPickerWindow.cpp +++ b/src/noggit/ui/windows/EditorWindows/ZoneMusicPickerWindow.cpp @@ -1,29 +1,20 @@ -#include "ZoneMusicPickerWindow.h" -#include #include "SoundEntryPickerWindow.h" +#include "ZoneMusicPickerWindow.h" #include -#include -#include -#include -#include +#include +#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include #include +#include +#include +#include +#include +#include +#include +#include -#include #include #include diff --git a/src/noggit/ui/windows/EditorWindows/ZoneMusicPickerWindow.h b/src/noggit/ui/windows/EditorWindows/ZoneMusicPickerWindow.h index 38a231c3..e0934fc5 100644 --- a/src/noggit/ui/windows/EditorWindows/ZoneMusicPickerWindow.h +++ b/src/noggit/ui/windows/EditorWindows/ZoneMusicPickerWindow.h @@ -2,22 +2,13 @@ #pragma once -#include - #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include +class QLabel; +class QLineEdit; +class QListWidget; +class QPushButton; +class QSpinBox; namespace Noggit { diff --git a/src/noggit/ui/windows/SoundPlayer/SoundEntryPlayer.cpp b/src/noggit/ui/windows/SoundPlayer/SoundEntryPlayer.cpp index f71d57cb..6b9817a2 100644 --- a/src/noggit/ui/windows/SoundPlayer/SoundEntryPlayer.cpp +++ b/src/noggit/ui/windows/SoundPlayer/SoundEntryPlayer.cpp @@ -1,27 +1,22 @@ #include "SoundEntryPlayer.h" +#include #include #include -#include +#include + #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include #include +#include +#include +#include #include +#include +#include +#include -#include #include #include diff --git a/src/noggit/ui/windows/SoundPlayer/SoundEntryPlayer.h b/src/noggit/ui/windows/SoundPlayer/SoundEntryPlayer.h index eb5270e1..9520d54e 100644 --- a/src/noggit/ui/windows/SoundPlayer/SoundEntryPlayer.h +++ b/src/noggit/ui/windows/SoundPlayer/SoundEntryPlayer.h @@ -2,22 +2,14 @@ #pragma once -#include - #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include +class QLabel; +class QListWidget; +class QMediaPlayer; +class QSlider; namespace Noggit { @@ -46,4 +38,4 @@ namespace Noggit void closeEvent(QCloseEvent* event) override; }; } -} \ No newline at end of file +} diff --git a/src/noggit/ui/windows/changelog/Changelog.cpp b/src/noggit/ui/windows/changelog/Changelog.cpp index cca57b44..55eb7d44 100644 --- a/src/noggit/ui/windows/changelog/Changelog.cpp +++ b/src/noggit/ui/windows/changelog/Changelog.cpp @@ -1,14 +1,16 @@ #include "Changelog.hpp" +#include "ui_Changelog.h" + #include #include -#include "ui_Changelog.h" namespace Noggit { namespace Ui { - CChangelog::CChangelog(QWidget* parent) : - QDialog(parent) + CChangelog::CChangelog(QWidget* parent) + : QDialog(parent) + , ui{nullptr} { /*setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); ui = new ::Ui::Changelog; diff --git a/src/noggit/ui/windows/changelog/Changelog.hpp b/src/noggit/ui/windows/changelog/Changelog.hpp index 971377de..63694153 100644 --- a/src/noggit/ui/windows/changelog/Changelog.hpp +++ b/src/noggit/ui/windows/changelog/Changelog.hpp @@ -1,7 +1,11 @@ #pragma once #include -#include "ui_Changelog.h" + +namespace Ui +{ + class Changelog; +} namespace Noggit { diff --git a/src/noggit/ui/windows/downloadFileDialog/DownloadFileDialog.cpp b/src/noggit/ui/windows/downloadFileDialog/DownloadFileDialog.cpp index 86f1b6dd..359147af 100755 --- a/src/noggit/ui/windows/downloadFileDialog/DownloadFileDialog.cpp +++ b/src/noggit/ui/windows/downloadFileDialog/DownloadFileDialog.cpp @@ -1,14 +1,25 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). -#include -#include "revision.h" #include -#include +#include + +#include + +#include "revision.h" + +#include +#include +#include + #include +#include namespace Noggit::Ui { - DownloadFileDialog::DownloadFileDialog(QString url, QString fileDownloadPath, QWidget* parent) : QDialog(parent), ui(new ::Ui::DownloadFileDialog) + DownloadFileDialog::DownloadFileDialog(QString url, QString fileDownloadPath, QWidget* parent) + : QDialog(parent) + , ui(new ::Ui::DownloadFileDialog) + , _response{nullptr} { ui->setupUi(this); diff --git a/src/noggit/ui/windows/downloadFileDialog/DownloadFileDialog.h b/src/noggit/ui/windows/downloadFileDialog/DownloadFileDialog.h index b0992e14..60012f2a 100755 --- a/src/noggit/ui/windows/downloadFileDialog/DownloadFileDialog.h +++ b/src/noggit/ui/windows/downloadFileDialog/DownloadFileDialog.h @@ -4,11 +4,14 @@ #include #include #include -#include -#include -#include -#include -#include + +namespace Ui +{ + class DownloadFileDialog; +} + +class QFile; +class QNetworkReply; namespace Noggit::Ui { diff --git a/src/noggit/ui/windows/noggitWindow/NoggitWindow.cpp b/src/noggit/ui/windows/noggitWindow/NoggitWindow.cpp index ae2942b9..3cedb922 100755 --- a/src/noggit/ui/windows/noggitWindow/NoggitWindow.cpp +++ b/src/noggit/ui/windows/noggitWindow/NoggitWindow.cpp @@ -1,27 +1,40 @@ -#include +#include +#include +#include #include #include #include -#include -#include -#include #include -#include -#include -#include -#include +#include #include #include -#include -#include -#include -#include -#include -#include -#include +#include #include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include #include +#include #include #include #include @@ -30,17 +43,9 @@ #include #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include +#include #ifdef USE_MYSQL_UID_STORAGE #include @@ -51,7 +56,6 @@ #include "revision.h" #include "ui_TitleBar.h" -#include #include namespace Noggit::Ui::Windows diff --git a/src/noggit/ui/windows/noggitWindow/NoggitWindow.hpp b/src/noggit/ui/windows/noggitWindow/NoggitWindow.hpp index aa5ce268..da8a5634 100755 --- a/src/noggit/ui/windows/noggitWindow/NoggitWindow.hpp +++ b/src/noggit/ui/windows/noggitWindow/NoggitWindow.hpp @@ -1,21 +1,38 @@ #ifndef NOGGIT_WINDOW_NOGGIT_HPP #define NOGGIT_WINDOW_NOGGIT_HPP -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +class MapView; class StackedWidget; +class World; + +class QListWidget; + +namespace Noggit::Application +{ + struct NoggitApplicationConfiguration; +} + +namespace Noggit::Ui::Component +{ + class BuildMapListComponent; +} + +namespace Noggit::Project +{ + class NoggitProject; +} namespace Noggit::Ui::Tools::MapCreationWizard::Ui { diff --git a/src/noggit/ui/windows/noggitWindow/components/BuildMapListComponent.cpp b/src/noggit/ui/windows/noggitWindow/components/BuildMapListComponent.cpp index 4fa83d3f..16bb634c 100755 --- a/src/noggit/ui/windows/noggitWindow/components/BuildMapListComponent.cpp +++ b/src/noggit/ui/windows/noggitWindow/components/BuildMapListComponent.cpp @@ -1,11 +1,18 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). -#include -#include -#include #include -#include +#include +#include +#include +#include +#include +#include + +#include + #include +#include +#include #include using namespace Noggit::Ui::Component; diff --git a/src/noggit/ui/windows/noggitWindow/components/BuildMapListComponent.hpp b/src/noggit/ui/windows/noggitWindow/components/BuildMapListComponent.hpp index 03a9df12..7e09aceb 100755 --- a/src/noggit/ui/windows/noggitWindow/components/BuildMapListComponent.hpp +++ b/src/noggit/ui/windows/noggitWindow/components/BuildMapListComponent.hpp @@ -1,8 +1,6 @@ #ifndef NOGGIT_COMPONENT_BUILD_MAP_LIST_HPP #define NOGGIT_COMPONENT_BUILD_MAP_LIST_HPP -#include - namespace Noggit::Ui::Windows { class NoggitWindow; @@ -15,10 +13,6 @@ namespace Noggit::Ui::Component friend class Noggit::Ui::Windows::NoggitWindow; public: void buildMapList(Noggit::Ui::Windows::NoggitWindow* parent); - - private: - void buildPinMapContextMenu(const QPoint& pos); - void buildUnPinMapContextMenu(const QPoint& pos); }; } -#endif //NOGGIT_COMPONENT_BUILD_MAP_LIST_HPP \ No newline at end of file +#endif //NOGGIT_COMPONENT_BUILD_MAP_LIST_HPP diff --git a/src/noggit/ui/windows/noggitWindow/widgets/MapBookmarkListItem.cpp b/src/noggit/ui/windows/noggitWindow/widgets/MapBookmarkListItem.cpp index 4acb4684..69d234f2 100755 --- a/src/noggit/ui/windows/noggitWindow/widgets/MapBookmarkListItem.cpp +++ b/src/noggit/ui/windows/noggitWindow/widgets/MapBookmarkListItem.cpp @@ -1,7 +1,13 @@ -#include -#include #include +#include + +#include +#include +#include +#include + #include + namespace Noggit::Ui::Widget { MapListBookmarkItem::MapListBookmarkItem(const MapListBookmarkData& data, QWidget* parent = nullptr) : QWidget(parent) diff --git a/src/noggit/ui/windows/noggitWindow/widgets/MapBookmarkListItem.hpp b/src/noggit/ui/windows/noggitWindow/widgets/MapBookmarkListItem.hpp index 87e575c5..5d4f5f3d 100755 --- a/src/noggit/ui/windows/noggitWindow/widgets/MapBookmarkListItem.hpp +++ b/src/noggit/ui/windows/noggitWindow/widgets/MapBookmarkListItem.hpp @@ -1,13 +1,13 @@ #ifndef NOGGIT_WIGDET_MAP_BOOKMARK_LIST_ITEM_HPP #define NOGGIT_WIGDET_MAP_BOOKMARK_LIST_ITEM_HPP -#include -#include -#include -#include +#include + +#include #include #include -#include + +class QLabel; namespace Noggit::Ui::Widget { @@ -33,4 +33,4 @@ namespace Noggit::Ui::Widget }; } -#endif //NOGGIT_WIGDET_MAP_BOOKMARK_LIST_ITEM_HPP \ No newline at end of file +#endif //NOGGIT_WIGDET_MAP_BOOKMARK_LIST_ITEM_HPP diff --git a/src/noggit/ui/windows/noggitWindow/widgets/MapListItem.cpp b/src/noggit/ui/windows/noggitWindow/widgets/MapListItem.cpp index e90ae3fc..005ae35d 100755 --- a/src/noggit/ui/windows/noggitWindow/widgets/MapListItem.cpp +++ b/src/noggit/ui/windows/noggitWindow/widgets/MapListItem.cpp @@ -1,6 +1,10 @@ -#include -#include #include +#include + +#include +#include +#include +#include namespace Noggit::Ui::Widget { @@ -105,6 +109,31 @@ namespace Noggit::Ui::Widget return QSize(300, 32); } + const QString MapListItem::name() const + { + return _map_data.map_name; + } + + int MapListItem::id() const + { + return _map_data.map_id; + } + + int MapListItem::type() const + { + return _map_data.map_type_id; + } + + int MapListItem::expansion() const + { + return _map_data.expansion_id; + } + + bool MapListItem::wmo_map() const + { + return _map_data.wmo_map; + } + QString MapListItem::toCamelCase(const QString& s) { QStringList parts = s.split(' ', Qt::SplitBehaviorFlags::SkipEmptyParts); diff --git a/src/noggit/ui/windows/noggitWindow/widgets/MapListItem.hpp b/src/noggit/ui/windows/noggitWindow/widgets/MapListItem.hpp index d74f57e9..8d98c4c1 100755 --- a/src/noggit/ui/windows/noggitWindow/widgets/MapListItem.hpp +++ b/src/noggit/ui/windows/noggitWindow/widgets/MapListItem.hpp @@ -1,13 +1,11 @@ #ifndef NOGGIT_WIGDET_MAP_LIST_ITEM_HPP #define NOGGIT_WIGDET_MAP_LIST_ITEM_HPP -#include -#include -#include -#include +#include #include #include -#include + +class QLabel; namespace Noggit::Ui::Widget { @@ -37,11 +35,11 @@ namespace Noggit::Ui::Widget MapListItem(const MapListData& data, QWidget* parent); QSize minimumSizeHint() const override; - const QString name() { return _map_data.map_name; }; - int id() { return _map_data.map_id; }; - int type() { return _map_data.map_type_id; }; - int expansion() { return _map_data.expansion_id; }; - bool wmo_map() { return _map_data.wmo_map; }; + const QString name() const;; + int id() const;; + int type() const;; + int expansion() const;; + bool wmo_map() const;; private: QString toCamelCase(const QString& s); diff --git a/src/noggit/ui/windows/projectSelection/NoggitProjectSelectionWindow.cpp b/src/noggit/ui/windows/projectSelection/NoggitProjectSelectionWindow.cpp index e2d0e167..03e399bb 100755 --- a/src/noggit/ui/windows/projectSelection/NoggitProjectSelectionWindow.cpp +++ b/src/noggit/ui/windows/projectSelection/NoggitProjectSelectionWindow.cpp @@ -1,17 +1,27 @@ -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include -#include -#include +#include +#include +#include + -#include -#include #include -#include +#include +#include +#include #include "ui_NoggitProjectSelectionWindow.h" +#include + using namespace Noggit::Ui::Windows; diff --git a/src/noggit/ui/windows/projectSelection/NoggitProjectSelectionWindow.hpp b/src/noggit/ui/windows/projectSelection/NoggitProjectSelectionWindow.hpp index 837d38e5..7d300833 100755 --- a/src/noggit/ui/windows/projectSelection/NoggitProjectSelectionWindow.hpp +++ b/src/noggit/ui/windows/projectSelection/NoggitProjectSelectionWindow.hpp @@ -2,24 +2,16 @@ #define NOGGITREDPROJECTPAGE_H #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include QT_BEGIN_NAMESPACE namespace Ui { class NoggitProjectSelectionWindow; } QT_END_NAMESPACE +namespace Noggit::Ui +{ + class settings; +} + namespace Noggit::Ui::Component { class RecentProjectsComponent; @@ -27,6 +19,11 @@ namespace Noggit::Ui::Component class LoadProjectComponent; } +namespace Noggit::Ui::Windows +{ + class NoggitWindow; +} + namespace Noggit::Application { class NoggitApplication; @@ -61,4 +58,4 @@ namespace Noggit::Ui::Windows void resetFavoriteProject(); }; } -#endif // NOGGITREDPROJECTPAGE_H \ No newline at end of file +#endif // NOGGITREDPROJECTPAGE_H diff --git a/src/noggit/ui/windows/projectSelection/components/CreateProjectComponent.cpp b/src/noggit/ui/windows/projectSelection/components/CreateProjectComponent.cpp index 951ff85c..cb12ce57 100755 --- a/src/noggit/ui/windows/projectSelection/components/CreateProjectComponent.cpp +++ b/src/noggit/ui/windows/projectSelection/components/CreateProjectComponent.cpp @@ -1,7 +1,14 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). #include "CreateProjectComponent.hpp" + +#include +#include +#include #include +#include + +#include using namespace Noggit::Ui::Component; @@ -30,4 +37,4 @@ void CreateProjectComponent::createProject(Noggit::Ui::Windows::NoggitProjectSel RecentProjectsComponent::registerProjectChange(project_information.project_path); -} \ No newline at end of file +} diff --git a/src/noggit/ui/windows/projectSelection/components/CreateProjectComponent.hpp b/src/noggit/ui/windows/projectSelection/components/CreateProjectComponent.hpp index 98bb648d..17c8655e 100755 --- a/src/noggit/ui/windows/projectSelection/components/CreateProjectComponent.hpp +++ b/src/noggit/ui/windows/projectSelection/components/CreateProjectComponent.hpp @@ -3,10 +3,12 @@ #ifndef NOGGIT_COMPONENT_CREATE_PROJECT_HPP #define NOGGIT_COMPONENT_CREATE_PROJECT_HPP -#include -#include +namespace Noggit::Ui::Windows +{ + class NoggitProjectSelectionWindow; +} -#include +struct ProjectInformation; namespace Noggit::Ui::Component { @@ -19,4 +21,4 @@ namespace Noggit::Ui::Component }; } -#endif //NOGGIT_COMPONENT_CREATE_PROJECT_HPP \ No newline at end of file +#endif //NOGGIT_COMPONENT_CREATE_PROJECT_HPP diff --git a/src/noggit/ui/windows/projectSelection/components/LoadProjectComponent.cpp b/src/noggit/ui/windows/projectSelection/components/LoadProjectComponent.cpp new file mode 100644 index 00000000..2e46c989 --- /dev/null +++ b/src/noggit/ui/windows/projectSelection/components/LoadProjectComponent.cpp @@ -0,0 +1,181 @@ +#include "LoadProjectComponent.hpp" +#include "ui_NoggitProjectSelectionWindow.h" + +#include +#include +#include +#include + +#include + + +namespace Noggit::Ui::Component +{ + std::shared_ptr LoadProjectComponent::loadProject(Windows::NoggitProjectSelectionWindow* parent, QString force_project_path) + { + QString project_path; + + if (!force_project_path.isEmpty()) + project_path = force_project_path; + else + { + QModelIndex index = parent->_ui->listView->currentIndex(); + project_path = index.data(Qt::UserRole).toString(); + } + + auto application_configuration = parent->_noggit_application->getConfiguration(); + // auto application_projects_folder_path = std::filesystem::path(application_configuration->ApplicationProjectPath); + + + auto application_project_service = Noggit::Project::ApplicationProject(application_configuration); + + if (!QDir(project_path).exists()) + { + LogError << "Project path does not exist : " << project_path.toStdString() << std::endl; + return {}; + } + Log << "Loading Project path : " << project_path.toStdString() << std::endl; + + // check if current filesystem is case sensitive + QDir q_project_path{ project_path }; + + bool is_case_sensitive_fs = false; + QString file_1_path{ q_project_path.filePath("__noggit_fs_test.t") }; + QString file_2_path{ q_project_path.filePath("__NOGGIT_FS_TEST.t") }; + + QFile file_1{ file_1_path }; + if (file_1.open(QIODevice::ReadWrite)) + { + QTextStream stream(&file_1); + stream << "a" << Qt::endl; + } + else + { + LogError << "Failed to open file : " << file_1_path.toStdString() << std::endl; + assert(false); + return {}; + } + file_1.close(); + + QFile file_2{ file_2_path }; + if (file_2.open(QIODevice::ReadWrite)) + { + QTextStream stream(&file_2); + stream << "b" << Qt::endl; + } + else + { + LogError << "Failed to open file : " << file_2_path.toStdString() << std::endl; + assert(false); + return {}; + } + file_2.close(); + + // read the file contents + QFile file_test{ file_1_path }; + if (file_test.open(QIODevice::ReadOnly)) + { + QTextStream stream(&file_test); + QString line = stream.readLine(); + if (line.contains("a")) + { + is_case_sensitive_fs = true; + } + } + else + { + LogError << "Failed to read file content : " << file_1_path.toStdString() << std::endl; + assert(false); + return {}; + } + file_test.close(); + file_1.remove(); + + if (is_case_sensitive_fs) + { + file_2.remove(); + + // scan directory for non-lowercase entries + bool has_uppercase = false; + QDirIterator it(project_path, QDir::AllEntries | QDir::NoDotAndDotDot, QDirIterator::Subdirectories); + + while (it.hasNext()) + { + QString filepath = it.next(); + QString remainder = q_project_path.relativeFilePath(filepath); + + if (!remainder.isLower()) + { + has_uppercase = true; + break; + } + } + + if (has_uppercase) + { + QMessageBox prompt; + prompt.setWindowIcon(QIcon(":/icon")); + prompt.setWindowTitle("Convert project?"); + prompt.setIcon(QMessageBox::Warning); + prompt.setWindowFlags(Qt::WindowStaysOnTopHint); + prompt.setText("Your project contains upper-case named files, " + "which won't be visible to Noggit running on your OS with case-sensitive filesystems. " + "Do you want to fix your filenames?"); + prompt.addButton("Accept", QMessageBox::AcceptRole); + prompt.setDefaultButton(prompt.addButton("Cancel", QMessageBox::RejectRole)); + prompt.setWindowFlags(Qt::CustomizeWindowHint | Qt::WindowTitleHint); + + prompt.exec(); + + switch (prompt.buttonRole(prompt.clickedButton())) + { + case QMessageBox::AcceptRole: + { + std::vector incorrect_paths; + + QDirIterator it(project_path, QDir::AllEntries | QDir::NoDotAndDotDot, QDirIterator::Subdirectories); + + while (it.hasNext()) + { + QString filepath = it.next(); + if (!filepath.isLower()) + { + incorrect_paths.push_back(filepath); + } + } + + std::sort(incorrect_paths.begin(), incorrect_paths.end(), std::greater{}); + + for (auto& path : incorrect_paths) + { + QFileInfo f_info_path{ path }; + QString filename_lower = f_info_path.fileName().toLower(); + QDir path_dir = f_info_path.dir(); + QFile::rename(path, path_dir.filePath(filename_lower)); + } + + break; + } + case QMessageBox::DestructiveRole: + default: + { + LogError << "Failed to convert uppercase sensitive project." << std::endl; + assert(false); + return {}; + } + + } + } + } + + auto project = application_project_service.loadProject(project_path.toStdString()); + + //This to not be static, but its hard to remove + if (project) + Noggit::Application::NoggitApplication::instance()->setClientData(project->ClientData); + else + LogError << "Couldn't set client data : Project loading failed." << std::endl; + + return project; + } +} diff --git a/src/noggit/ui/windows/projectSelection/components/LoadProjectComponent.hpp b/src/noggit/ui/windows/projectSelection/components/LoadProjectComponent.hpp index 95168393..06e922fc 100755 --- a/src/noggit/ui/windows/projectSelection/components/LoadProjectComponent.hpp +++ b/src/noggit/ui/windows/projectSelection/components/LoadProjectComponent.hpp @@ -1,18 +1,26 @@ #ifndef NOGGIT_COMPONENT_LOAD_PROJECT_HPP #define NOGGIT_COMPONENT_LOAD_PROJECT_HPP -#include -#include -#include -#include -#include -#include -#include -#include #include #include #include +#include +#include +#include + +#include +#include + +namespace Noggit::Project +{ + class NoggitProject; +} + +namespace Noggit::Ui::Windows +{ + class NoggitProjectSelectionWindow; +} namespace Noggit::Ui::Component { @@ -21,174 +29,8 @@ namespace Noggit::Ui::Component friend Windows::NoggitProjectSelectionWindow; public: - std::shared_ptr loadProject(Noggit::Ui::Windows::NoggitProjectSelectionWindow* parent, QString force_project_path = "") - { - QString project_path; - - if (!force_project_path.isEmpty()) - project_path = force_project_path; - else - { - QModelIndex index = parent->_ui->listView->currentIndex(); - project_path = index.data(Qt::UserRole).toString(); - } - - auto application_configuration = parent->_noggit_application->getConfiguration(); - // auto application_projects_folder_path = std::filesystem::path(application_configuration->ApplicationProjectPath); - - - auto application_project_service = Noggit::Project::ApplicationProject(application_configuration); - - if (!QDir(project_path).exists()) - { - LogError << "Project path does not exist : " << project_path.toStdString() << std::endl; - return {}; - } - Log << "Loading Project path : " << project_path.toStdString() << std::endl; - - // check if current filesystem is case sensitive - QDir q_project_path{project_path}; - - bool is_case_sensitive_fs = false; - QString file_1_path { q_project_path.filePath("__noggit_fs_test.t") }; - QString file_2_path { q_project_path.filePath("__NOGGIT_FS_TEST.t") }; - - QFile file_1 {file_1_path}; - if (file_1.open(QIODevice::ReadWrite)) - { - QTextStream stream(&file_1); - stream << "a" << Qt::endl; - } - else - { - LogError << "Failed to open file : " << file_1_path.toStdString() << std::endl; - assert(false); - return {}; - } - file_1.close(); - - QFile file_2 {file_2_path}; - if (file_2.open(QIODevice::ReadWrite)) - { - QTextStream stream(&file_2); - stream << "b" << Qt::endl; - } - else - { - LogError << "Failed to open file : " << file_2_path.toStdString() << std::endl; - assert(false); - return {}; - } - file_2.close(); - - // read the file contents - QFile file_test {file_1_path}; - if (file_test.open(QIODevice::ReadOnly)) - { - QTextStream stream(&file_test); - QString line = stream.readLine(); - if (line.contains("a")) - { - is_case_sensitive_fs = true; - } - } - else - { - LogError << "Failed to read file content : " << file_1_path.toStdString() << std::endl; - assert(false); - return {}; - } - file_test.close(); - file_1.remove(); - - if (is_case_sensitive_fs) - { - file_2.remove(); - - // scan directory for non-lowercase entries - bool has_uppercase = false; - QDirIterator it(project_path, QDir::AllEntries | QDir::NoDotAndDotDot, QDirIterator::Subdirectories); - - while(it.hasNext()) - { - QString filepath = it.next(); - QString remainder = q_project_path.relativeFilePath(filepath); - - if (!remainder.isLower()) - { - has_uppercase = true; - break; - } - } - - if (has_uppercase) - { - QMessageBox prompt; - prompt.setWindowIcon(QIcon(":/icon")); - prompt.setWindowTitle("Convert project?"); - prompt.setIcon(QMessageBox::Warning); - prompt.setWindowFlags(Qt::WindowStaysOnTopHint); - prompt.setText("Your project contains upper-case named files, " - "which won't be visible to Noggit running on your OS with case-sensitive filesystems. " - "Do you want to fix your filenames?"); - prompt.addButton("Accept", QMessageBox::AcceptRole); - prompt.setDefaultButton(prompt.addButton("Cancel", QMessageBox::RejectRole)); - prompt.setWindowFlags(Qt::CustomizeWindowHint | Qt::WindowTitleHint); - - prompt.exec(); - - switch (prompt.buttonRole(prompt.clickedButton())) - { - case QMessageBox::AcceptRole: - { - std::vector incorrect_paths; - - QDirIterator it (project_path, QDir::AllEntries | QDir::NoDotAndDotDot, QDirIterator::Subdirectories); - - while(it.hasNext()) - { - QString filepath = it.next(); - if (!filepath.isLower()) - { - incorrect_paths.push_back(filepath); - } - } - - std::sort(incorrect_paths.begin(), incorrect_paths.end(), std::greater{}); - - for (auto& path : incorrect_paths) - { - QFileInfo f_info_path{path}; - QString filename_lower = f_info_path.fileName().toLower(); - QDir path_dir = f_info_path.dir(); - QFile::rename(path, path_dir.filePath(filename_lower)); - } - - break; - } - case QMessageBox::DestructiveRole: - default: - { - LogError << "Failed to convert uppercase sensitive project." << std::endl; - assert(false); - return {}; - } - - } - } - } - - auto project = application_project_service.loadProject(project_path.toStdString()); - - //This to not be static, but its hard to remove - if (project) - Noggit::Application::NoggitApplication::instance()->setClientData(project->ClientData); - else - LogError << "Couldn't set client data : Project loading failed." << std::endl; - - return project; - } + std::shared_ptr loadProject(Noggit::Ui::Windows::NoggitProjectSelectionWindow* parent, QString force_project_path = ""); }; } -#endif //NOGGIT_COMPONENT_LOAD_PROJECT_HPP \ No newline at end of file +#endif //NOGGIT_COMPONENT_LOAD_PROJECT_HPP diff --git a/src/noggit/ui/windows/projectSelection/components/RecentProjectsComponent.cpp b/src/noggit/ui/windows/projectSelection/components/RecentProjectsComponent.cpp index 0219650e..1ecdc8d5 100755 --- a/src/noggit/ui/windows/projectSelection/components/RecentProjectsComponent.cpp +++ b/src/noggit/ui/windows/projectSelection/components/RecentProjectsComponent.cpp @@ -1,11 +1,24 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). #include "RecentProjectsComponent.hpp" +#include "ui_NoggitProjectSelectionWindow.h" -#include -#include +#include +#include +#include +#include + +#include #include +#include +#include +#include +#include +#include #include +#include + +#include using namespace Noggit::Ui::Component; diff --git a/src/noggit/ui/windows/projectSelection/components/RecentProjectsComponent.hpp b/src/noggit/ui/windows/projectSelection/components/RecentProjectsComponent.hpp index ad4127ef..208e4fea 100755 --- a/src/noggit/ui/windows/projectSelection/components/RecentProjectsComponent.hpp +++ b/src/noggit/ui/windows/projectSelection/components/RecentProjectsComponent.hpp @@ -3,10 +3,12 @@ #ifndef NOGGIT_COMPONENT_EXISTING_PROJECT_ENUMERATION_HPP #define NOGGIT_COMPONENT_EXISTING_PROJECT_ENUMERATION_HPP -#include -#include -#include -#include "ui_NoggitProjectSelectionWindow.h" +#include + +namespace Noggit::Ui::Windows +{ + class NoggitProjectSelectionWindow; +} namespace Noggit::Ui::Component { @@ -20,8 +22,7 @@ namespace Noggit::Ui::Component static void registerProjectRemove(std::string const& project_path); private: static void openDirectory(std::string const& directory_path); - }; } -#endif //NOGGIT_COMPONENT_EXISTING_PROJECT_ENUMERATION_HPP \ No newline at end of file +#endif //NOGGIT_COMPONENT_EXISTING_PROJECT_ENUMERATION_HPP diff --git a/src/noggit/ui/windows/projectSelection/widgets/ProjectListItem.cpp b/src/noggit/ui/windows/projectSelection/widgets/ProjectListItem.cpp index 5c8eb805..66adf499 100755 --- a/src/noggit/ui/windows/projectSelection/widgets/ProjectListItem.cpp +++ b/src/noggit/ui/windows/projectSelection/widgets/ProjectListItem.cpp @@ -1,5 +1,10 @@ -#include #include +#include + +#include +#include +#include + namespace Noggit::Ui::Widget { @@ -108,4 +113,4 @@ namespace Noggit::Ui::Widget return parts.join(" "); } -} \ No newline at end of file +} diff --git a/src/noggit/ui/windows/projectSelection/widgets/ProjectListItem.hpp b/src/noggit/ui/windows/projectSelection/widgets/ProjectListItem.hpp index d9f08552..0dd54787 100755 --- a/src/noggit/ui/windows/projectSelection/widgets/ProjectListItem.hpp +++ b/src/noggit/ui/windows/projectSelection/widgets/ProjectListItem.hpp @@ -1,14 +1,13 @@ #ifndef NOGGIT_WIGDET_PROJECT_LIST_ITEM_HPP #define NOGGIT_WIGDET_PROJECT_LIST_ITEM_HPP -#include -#include -#include -#include #include +#include #include #include +class QLabel; + namespace Noggit::Ui::Widget { struct ProjectListItemData @@ -38,4 +37,4 @@ namespace Noggit::Ui::Widget }; } -#endif //NOGGIT_WIGDET_PROJECT_LIST_ITEM_HPP \ No newline at end of file +#endif //NOGGIT_WIGDET_PROJECT_LIST_ITEM_HPP diff --git a/src/noggit/ui/windows/settingsPanel/SettingsPanel.cpp b/src/noggit/ui/windows/settingsPanel/SettingsPanel.cpp index d38a55fc..143610a0 100644 --- a/src/noggit/ui/windows/settingsPanel/SettingsPanel.cpp +++ b/src/noggit/ui/windows/settingsPanel/SettingsPanel.cpp @@ -1,34 +1,22 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). -#include #include - -#include -#include #include -#include +#include -#include -#include -#include -#include -#include -#include -#include -#include #include -#include +#include +#include +#include +#include #ifdef USE_MYSQL_UID_STORAGE #include #endif #include -#include - -#include -#include +#include namespace Noggit diff --git a/src/noggit/ui/windows/settingsPanel/SettingsPanel.h b/src/noggit/ui/windows/settingsPanel/SettingsPanel.h index 10ff9613..0916c9fb 100755 --- a/src/noggit/ui/windows/settingsPanel/SettingsPanel.h +++ b/src/noggit/ui/windows/settingsPanel/SettingsPanel.h @@ -2,12 +2,14 @@ #pragma once -#include - -#include #include -#include +namespace Ui +{ + class SettingsPanel; +} + +class QSettings; namespace Noggit { diff --git a/src/noggit/ui/windows/updater/Updater.cpp b/src/noggit/ui/windows/updater/Updater.cpp index 27467aa7..2d90e3fc 100644 --- a/src/noggit/ui/windows/updater/Updater.cpp +++ b/src/noggit/ui/windows/updater/Updater.cpp @@ -1,5 +1,14 @@ -#include "Updater.h" #include "ui_Updater.h" +#include "Updater.h" + +#include "qprocess.h" +#include +#include +#include +#include +#include +#include + namespace Noggit { diff --git a/src/noggit/ui/windows/updater/Updater.h b/src/noggit/ui/windows/updater/Updater.h index e16af297..e5f6dd84 100644 --- a/src/noggit/ui/windows/updater/Updater.h +++ b/src/noggit/ui/windows/updater/Updater.h @@ -1,16 +1,17 @@ #pragma once -#include -#include "qprocess.h" -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include -#include "ui_Updater.h" +namespace Ui +{ + class Updater; +} namespace Noggit { @@ -19,7 +20,7 @@ namespace Noggit class CUpdater : public QDialog { Q_OBJECT - ::Ui::Updater* ui; + ::Ui::Updater* ui = nullptr; public: CUpdater(QWidget* parent = nullptr); @@ -47,11 +48,11 @@ namespace Noggit const QString FileURL = "%1%2/%3"; const QString ExternalProcess = "/noggit-updater.exe"; - int FileNeededCount; - int FileMissingCount; + int FileNeededCount = 0; + int FileMissingCount = 0; - bool NeedUpdate; - bool LocalCheck, OnlineCheck; + bool NeedUpdate = false; + bool LocalCheck = false, OnlineCheck = false; QMap LocalMD5; QMap OnlineMD5; @@ -59,6 +60,4 @@ namespace Noggit QVector FileNeeded; }; } - } - diff --git a/src/noggit/wmo_liquid.cpp b/src/noggit/wmo_liquid.cpp index 52180c19..1da02ac8 100755 --- a/src/noggit/wmo_liquid.cpp +++ b/src/noggit/wmo_liquid.cpp @@ -1,17 +1,13 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). -#include -#include -#include #include -#include #include #include #include #include +#include #include -#include namespace { @@ -244,4 +240,4 @@ void wmo_liquid::draw ( glm::mat4x4 const& transform gl.drawElements (GL_TRIANGLES, indices.size(), GL_UNSIGNED_SHORT, nullptr); } - */ \ No newline at end of file + */ diff --git a/src/noggit/wmo_liquid.hpp b/src/noggit/wmo_liquid.hpp index 5f94e638..8c3f586a 100755 --- a/src/noggit/wmo_liquid.hpp +++ b/src/noggit/wmo_liquid.hpp @@ -1,10 +1,8 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). #pragma once -#include #include -#include namespace BlizzardArchive { @@ -97,6 +95,11 @@ struct WmoLiquidVertex { float height; }; +namespace OpenGL::Scoped +{ + struct use_program; +} + class wmo_liquid { public: diff --git a/src/noggit/world_model_instances_storage.cpp b/src/noggit/world_model_instances_storage.cpp index d86ee618..50352c9b 100755 --- a/src/noggit/world_model_instances_storage.cpp +++ b/src/noggit/world_model_instances_storage.cpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace Noggit { @@ -363,6 +364,11 @@ namespace Noggit Log << "Deleted " << deleted_uids << " duplicate Model/WMO" << std::endl; } + bool world_model_instances_storage::uid_duplicates_found() const + { + return _uid_duplicates_found.load(); + } + void world_model_instances_storage::upload() { if (_transform_storage_uploaded) @@ -390,4 +396,9 @@ namespace Noggit _transform_storage_uploaded = false; } + + unsigned int world_model_instances_storage::getTotalModelsCount() const + { + return _m2s.size() + _wmos.size(); + } } diff --git a/src/noggit/world_model_instances_storage.hpp b/src/noggit/world_model_instances_storage.hpp index 862239fb..5949abe3 100755 --- a/src/noggit/world_model_instances_storage.hpp +++ b/src/noggit/world_model_instances_storage.hpp @@ -3,16 +3,14 @@ #pragma once #include #include -#include #include #include -#include #include -#include #include #include #include +struct TileIndex; class World; using m2_instance_umap = std::unordered_map; @@ -49,18 +47,15 @@ namespace Noggit void clear_duplicates(bool action); - bool uid_duplicates_found() const - { - return _uid_duplicates_found.load(); - } + bool uid_duplicates_found() const; void upload(); void unload(); - unsigned int getTotalModelsCount() const { return _m2s.size() + _wmos.size(); }; + unsigned int getTotalModelsCount() const;; private: // private functions aren't thread safe - inline bool unsafe_uid_is_used(std::uint32_t uid) const; + bool unsafe_uid_is_used(std::uint32_t uid) const; std::uint32_t unsafe_add_model_instance_no_world_upd(ModelInstance instance, bool action); std::uint32_t unsafe_add_wmo_instance_no_world_upd(WMOInstance instance, bool action); diff --git a/src/noggit/world_tile_update_queue.cpp b/src/noggit/world_tile_update_queue.cpp index 7bf4e910..38684017 100755 --- a/src/noggit/world_tile_update_queue.cpp +++ b/src/noggit/world_tile_update_queue.cpp @@ -3,8 +3,6 @@ #include #include -#include -#include #include diff --git a/src/opengl/context.cpp b/src/opengl/context.cpp index cec2c3c7..f6e3a5b7 100755 --- a/src/opengl/context.cpp +++ b/src/opengl/context.cpp @@ -4,7 +4,6 @@ #include #include #include -#include OpenGL::context gl; diff --git a/src/opengl/scoped.hpp b/src/opengl/scoped.hpp index 0044eddb..0417758f 100755 --- a/src/opengl/scoped.hpp +++ b/src/opengl/scoped.hpp @@ -5,7 +5,6 @@ #include #include #include -#include namespace OpenGL { diff --git a/src/opengl/shader.cpp b/src/opengl/shader.cpp index ef21aed9..61be7419 100755 --- a/src/opengl/shader.cpp +++ b/src/opengl/shader.cpp @@ -5,7 +5,8 @@ #include #include #include -#include + +#include #include #include diff --git a/src/opengl/shader.hpp b/src/opengl/shader.hpp index 6d23da79..0a94d984 100755 --- a/src/opengl/shader.hpp +++ b/src/opengl/shader.hpp @@ -2,23 +2,20 @@ #pragma once -#include -#include #include + +#include + +#include +#include +#include + #include -#include #include #include #include #include -#include #include -#include -#include -#include -#include -#include -#include namespace math { @@ -29,6 +26,11 @@ namespace math namespace OpenGL { + namespace Scoped + { + struct use_program; + } + struct shader { shader(GLenum type, std::string const& source); @@ -65,9 +67,9 @@ namespace OpenGL tsl::robin_map const* getUniformsBoolCache() const { return &_uniforms_bool_cache; }; private: - inline GLuint uniform_location (std::string const& name) const; - inline GLuint uniform_block_location (std::string const& name) const; - inline GLuint attrib_location (std::string const& name) const; + GLuint uniform_location (std::string const& name) const; + GLuint uniform_block_location (std::string const& name) const; + GLuint attrib_location (std::string const& name) const; friend struct Scoped::use_program;