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 1ee856b5..22d9a0ef 100644 Binary files a/resources/noggit_font.ttf and b/resources/noggit_font.ttf differ diff --git a/src/external/rapidfuzz-cpp/.clang-format b/src/external/rapidfuzz-cpp/.clang-format new file mode 100644 index 00000000..ee0513ae --- /dev/null +++ b/src/external/rapidfuzz-cpp/.clang-format @@ -0,0 +1,28 @@ +ColumnLimit: 110 +IndentWidth: 4 +AccessModifierOffset: -4 + +AllowShortIfStatementsOnASingleLine: true +PointerAlignment: Left +AllowShortBlocksOnASingleLine: Always +AllowShortFunctionsOnASingleLine: None +BreakBeforeBraces: Custom +AlwaysBreakTemplateDeclarations: true +BraceWrapping: + SplitEmptyFunction: false + AfterCaseLabel: true + AfterClass: false + AfterControlStatement: MultiLine + AfterEnum: false + AfterFunction: true + AfterNamespace: false + AfterStruct: false + AfterUnion: false + BeforeCatch: true + BeforeElse: true + SplitEmptyRecord: false + SplitEmptyNamespace: false +AllowAllConstructorInitializersOnNextLine: true +ConstructorInitializerAllOnOneLineOrOnePerLine: true +AllowShortCaseLabelsOnASingleLine: true +IndentPPDirectives: AfterHash diff --git a/src/external/rapidfuzz-cpp/.gitattributes b/src/external/rapidfuzz-cpp/.gitattributes new file mode 100644 index 00000000..f3415466 --- /dev/null +++ b/src/external/rapidfuzz-cpp/.gitattributes @@ -0,0 +1 @@ +*.impl linguist-language=C++ diff --git a/src/external/rapidfuzz-cpp/.github/FUNDING.yml b/src/external/rapidfuzz-cpp/.github/FUNDING.yml new file mode 100644 index 00000000..286d9b7c --- /dev/null +++ b/src/external/rapidfuzz-cpp/.github/FUNDING.yml @@ -0,0 +1,2 @@ +github: maxbachmann +custom: ["https://www.paypal.com/donate/?hosted_button_id=VGWQBBD5CTWJU"] diff --git a/src/external/rapidfuzz-cpp/.github/RapidFuzz.svg b/src/external/rapidfuzz-cpp/.github/RapidFuzz.svg new file mode 100644 index 00000000..0fb18c78 --- /dev/null +++ b/src/external/rapidfuzz-cpp/.github/RapidFuzz.svg @@ -0,0 +1,195 @@ + + + +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(); } + }; +}