pax_global_header00006660000000000000000000000064147440344300014515gustar00rootroot0000000000000052 comment=f65c3d1cd1af3f501aaf51e9015a0691e0fd4125 rapidfuzz-cpp-3.3.1/000077500000000000000000000000001474403443000143175ustar00rootroot00000000000000rapidfuzz-cpp-3.3.1/.clang-format000066400000000000000000000014311474403443000166710ustar00rootroot00000000000000ColumnLimit: 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 IfMacros: - RAPIDFUZZ_IF_CONSTEXPR IndentPPDirectives: AfterHash rapidfuzz-cpp-3.3.1/.gitattributes000066400000000000000000000000351474403443000172100ustar00rootroot00000000000000*.impl linguist-language=C++ rapidfuzz-cpp-3.3.1/.github/000077500000000000000000000000001474403443000156575ustar00rootroot00000000000000rapidfuzz-cpp-3.3.1/.github/FUNDING.yml000066400000000000000000000001361474403443000174740ustar00rootroot00000000000000github: maxbachmann custom: ["https://www.paypal.com/donate/?hosted_button_id=VGWQBBD5CTWJU"] rapidfuzz-cpp-3.3.1/.github/RapidFuzz.svg000066400000000000000000000126361474403443000203260ustar00rootroot00000000000000 image/svg+xml Page-1 Rectangle Rectangle.2 Sheet.3 Rapid Rapid Sheet.4 Fuzz Fuzz rapidfuzz-cpp-3.3.1/.github/workflows/000077500000000000000000000000001474403443000177145ustar00rootroot00000000000000rapidfuzz-cpp-3.3.1/.github/workflows/cmake.yml000066400000000000000000000137361474403443000215310ustar00rootroot00000000000000name: 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 rapidfuzz-cpp-3.3.1/.github/workflows/documentation.yml000066400000000000000000000005541474403443000233140ustar00rootroot00000000000000name: 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 rapidfuzz-cpp-3.3.1/.github/workflows/linux-simple.yml000066400000000000000000000067711474403443000231000ustar00rootroot00000000000000name: Linux builds (basic) on: [push, pull_request] jobs: build: name: ${{matrix.cxx}}, C++${{matrix.std}}, ${{matrix.build_type}} runs-on: ubuntu-20.04 strategy: fail-fast: false matrix: compiler: - g++-5 - g++-6 - g++-7 - g++-8 - g++-9 - g++-10 - clang++-6.0 - clang++-7 - clang++-8 - clang++-9 - clang++-10 build_type: [Debug, Release] std: [11] include: - cxx: g++-5 other_pkgs: g++-5 - cxx: g++-6 other_pkgs: g++-6 - cxx: g++-7 other_pkgs: g++-7 - cxx: g++-8 other_pkgs: g++-8 - cxx: g++-9 other_pkgs: g++-9 - cxx: g++-10 other_pkgs: g++-10 - cxx: clang++-6.0 other_pkgs: clang-6.0 - cxx: clang++-7 other_pkgs: clang-7 - cxx: clang++-8 other_pkgs: clang-8 - cxx: clang++-9 other_pkgs: clang-9 - cxx: clang++-10 other_pkgs: clang-10 - cxx: clang++-10 other_pkgs: clang-10 std: 14 build_type: Debug - cxx: clang++-10 other_pkgs: clang-10 std: 17 build_type: Debug - cxx: clang++-10 other_pkgs: clang-10 std: 20 build_type: Debug - cxx: g++-10 other_pkgs: g++-10 std: 14 build_type: Debug - cxx: g++-10 other_pkgs: g++-10 std: 17 build_type: Debug - cxx: g++-10 other_pkgs: g++-10 std: 20 build_type: Debug - cxx: clang++-10 other_pkgs: clang-10 std: 14 build_type: Release - cxx: clang++-10 other_pkgs: clang-10 std: 17 build_type: Release - cxx: clang++-10 other_pkgs: clang-10 std: 20 build_type: Release - cxx: g++-10 other_pkgs: g++-10 std: 14 build_type: Release - cxx: g++-10 other_pkgs: g++-10 std: 17 build_type: Release - cxx: g++-10 other_pkgs: g++-10 std: 20 build_type: Release steps: - uses: actions/checkout@v4 - name: Add repositories for older GCC run: | sudo apt-add-repository 'deb http://azure.archive.ubuntu.com/ubuntu/ bionic main' sudo apt-add-repository 'deb http://azure.archive.ubuntu.com/ubuntu/ bionic universe' if: ${{ matrix.cxx == 'g++-5' || matrix.cxx == 'g++-6' }} - name: Prepare environment run: | sudo apt-get update sudo apt-get install -y ninja-build ${{matrix.other_pkgs}} - name: Configure CMake env: CXX: ${{matrix.cxx}} run: | cmake -B build \ -DCMAKE_BUILD_TYPE=${{matrix.build_type}} \ -DCMAKE_CXX_STANDARD=${{matrix.std}} \ -DCMAKE_CXX_STANDARD_REQUIRED=ON \ -DCMAKE_CXX_EXTENSIONS=OFF \ -DRAPIDFUZZ_BUILD_TESTING=1 \ -DRAPIDFUZZ_ENABLE_LINTERS=1 \ -G Ninja - name: Build working-directory: build run: ninja - name: Test working-directory: build run: ctest -C ${{matrix.build_type}} --rerun-failed --output-on-failure -j `nproc` rapidfuzz-cpp-3.3.1/.gitignore000066400000000000000000000002071474403443000163060ustar00rootroot00000000000000.vscode/ .cache/ .idea/ build/ .cache/ *.data *.so *.o *.out .vs/ CMakeCache.txt CMakeFiles CMakeScripts Makefile cmake_install.cmake rapidfuzz-cpp-3.3.1/CHANGELOG.md000066400000000000000000000145441474403443000161400ustar00rootroot00000000000000## Changelog ## [3.3.1] - 2025-01-22 ### Fixed - fixed tests not building with catch2 versions >= 3.0 ## [3.3.0] - 2025-01-18 ### Changed - add C++11 and C++14 support ## [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 rapidfuzz-cpp-3.3.1/CMakeLists.txt000066400000000000000000000104531474403443000170620ustar00rootroot00000000000000# 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.3.1) 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_11) 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) rapidfuzz-cpp-3.3.1/Doxyfile000066400000000000000000000066641474403443000160410ustar00rootroot00000000000000# 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 rapidfuzz-cpp-3.3.1/LICENSE000066400000000000000000000020741474403443000153270ustar00rootroot00000000000000Copyright © 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. rapidfuzz-cpp-3.3.1/README.md000066400000000000000000000212441474403443000156010ustar00rootroot00000000000000

RapidFuzz

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

Continuous Integration Documentation GitHub license

DescriptionInstallationUsageLicense

--- ## 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). rapidfuzz-cpp-3.3.1/SECURITY.md000066400000000000000000000017331474403443000161140ustar00rootroot00000000000000## 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. rapidfuzz-cpp-3.3.1/bench/000077500000000000000000000000001474403443000153765ustar00rootroot00000000000000rapidfuzz-cpp-3.3.1/bench/CMakeLists.txt000066400000000000000000000015621474403443000201420ustar00rootroot00000000000000include(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) rapidfuzz-cpp-3.3.1/bench/bench-fuzz.cpp000066400000000000000000000131711474403443000201600ustar00rootroot00000000000000#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(); rapidfuzz-cpp-3.3.1/bench/bench-jarowinkler.cpp000066400000000000000000000146661474403443000215230ustar00rootroot00000000000000#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();rapidfuzz-cpp-3.3.1/bench/bench-lcs.cpp000066400000000000000000000136231474403443000177450ustar00rootroot00000000000000#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();rapidfuzz-cpp-3.3.1/bench/bench-levenshtein.cpp000066400000000000000000000162771474403443000215200ustar00rootroot00000000000000#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();rapidfuzz-cpp-3.3.1/cmake/000077500000000000000000000000001474403443000153775ustar00rootroot00000000000000rapidfuzz-cpp-3.3.1/cmake/rapidfuzzConfig.cmake.in000066400000000000000000000004011474403443000221450ustar00rootroot00000000000000@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()rapidfuzz-cpp-3.3.1/docs/000077500000000000000000000000001474403443000152475ustar00rootroot00000000000000rapidfuzz-cpp-3.3.1/docs/literature/000077500000000000000000000000001474403443000174275ustar00rootroot00000000000000rapidfuzz-cpp-3.3.1/docs/literature/hyrro_2002.bib000066400000000000000000000003071474403443000217130ustar00rootroot00000000000000@article{hyrro_2002, author = {Hyyro, Heikki}, year = {2002}, month = {10}, pages = {}, title = {Explaining and Extending the Bit-parallel Approximate String Matching Algorithm of Myers} } rapidfuzz-cpp-3.3.1/docs/literature/hyrro_2004.bib000066400000000000000000000003751474403443000217220ustar00rootroot00000000000000@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)} } rapidfuzz-cpp-3.3.1/docs/literature/hyrro_lcs_2004.bib000066400000000000000000000003701474403443000225560ustar00rootroot00000000000000@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)} }rapidfuzz-cpp-3.3.1/docs/literature/myers_1999.bib000066400000000000000000000010441474403443000217360ustar00rootroot00000000000000@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} } rapidfuzz-cpp-3.3.1/docs/literature/wagner_fischer_1974.bib000066400000000000000000000021501474403443000235550ustar00rootroot00000000000000@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} } rapidfuzz-cpp-3.3.1/examples/000077500000000000000000000000001474403443000161355ustar00rootroot00000000000000rapidfuzz-cpp-3.3.1/examples/cmake_export/000077500000000000000000000000001474403443000206165ustar00rootroot00000000000000rapidfuzz-cpp-3.3.1/examples/cmake_export/CMakeLists.txt000066400000000000000000000026221474403443000233600ustar00rootroot00000000000000cmake_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}) rapidfuzz-cpp-3.3.1/examples/cmake_export/fooConfig.cmake.in000066400000000000000000000004221474403443000241340ustar00rootroot00000000000000@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()rapidfuzz-cpp-3.3.1/examples/cmake_export/foo_lib.cc000066400000000000000000000002371474403443000225400ustar00rootroot00000000000000#include "foo_lib.hpp" double fooFunc() { std::string_view a("aaaa"), b("abaa"); FooType cache(a.begin(), a.end()); return cache.similarity(b); } rapidfuzz-cpp-3.3.1/examples/cmake_export/foo_lib.hpp000066400000000000000000000001451474403443000227400ustar00rootroot00000000000000#include using FooType = rapidfuzz::fuzz::CachedRatio; double fooFunc(); rapidfuzz-cpp-3.3.1/examples/cmake_export/indirect_app/000077500000000000000000000000001474403443000232575ustar00rootroot00000000000000rapidfuzz-cpp-3.3.1/examples/cmake_export/indirect_app/CMakeLists.txt000066400000000000000000000002641474403443000260210ustar00rootroot00000000000000cmake_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) rapidfuzz-cpp-3.3.1/examples/cmake_export/indirect_app/foo_app.cc000066400000000000000000000001541474403443000252110ustar00rootroot00000000000000#include #include int main() { std::cout << fooFunc() << '\n'; return 0; }rapidfuzz-cpp-3.3.1/examples/cmake_installed/000077500000000000000000000000001474403443000212545ustar00rootroot00000000000000rapidfuzz-cpp-3.3.1/examples/cmake_installed/CMakeLists.txt000066400000000000000000000002571474403443000240200ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.8) project(cmake_installed CXX) find_package(rapidfuzz REQUIRED) add_executable(foo main.cpp) target_link_libraries(foo rapidfuzz::rapidfuzz)rapidfuzz-cpp-3.3.1/examples/cmake_installed/main.cpp000066400000000000000000000003071474403443000227040ustar00rootroot00000000000000#include #include #include int main() { std::string a = "aaaa"; std::string b = "abab"; std::cout << rapidfuzz::fuzz::ratio(a, b) << std::endl; }rapidfuzz-cpp-3.3.1/extras/000077500000000000000000000000001474403443000156255ustar00rootroot00000000000000rapidfuzz-cpp-3.3.1/extras/rapidfuzz_amalgamated.hpp000066400000000000000000013603221474403443000227000ustar00rootroot00000000000000// Licensed under the MIT License . // SPDX-License-Identifier: MIT // RapidFuzz v1.0.2 // Generated: 2024-12-25 11:44:52.213162 // ---------------------------------------------------------- // 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 { namespace 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 detail } // namespace rapidfuzz #include #include #include #include #include namespace rapidfuzz { namespace detail { template struct BitMatrixView { using value_type = T; using size_type = size_t; using pointer = typename std::conditional::type; using reference = typename std::conditional::type; 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); } BitMatrixView operator[](size_t row) noexcept { return m_matrix[row]; } BitMatrixView 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 detail } // namespace rapidfuzz #include #include #include #include #include #include #include #include #include #include #if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || __cplusplus >= 201703L) # define RAPIDFUZZ_DEDUCTION_GUIDES # define RAPIDFUZZ_IF_CONSTEXPR_AVAILABLE 1 # define RAPIDFUZZ_IF_CONSTEXPR if constexpr #else # define RAPIDFUZZ_IF_CONSTEXPR_AVAILABLE 0 # define RAPIDFUZZ_IF_CONSTEXPR if #endif #if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201402L) || __cplusplus >= 201402L) # define RAPIDFUZZ_CONSTEXPR_CXX14 constexpr #else # define RAPIDFUZZ_CONSTEXPR_CXX14 #endif #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); }; template using rf_enable_if_t = typename std::enable_if::type; } // namespace rapidfuzz namespace rapidfuzz { namespace 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 } namespace to_begin_detail { using std::begin; template CharT* to_begin(CharT* s) { return s; } template auto to_begin(T& x) -> decltype(begin(x)) { return begin(x); } } // namespace to_begin_detail using to_begin_detail::to_begin; namespace to_end_detail { using std::end; template CharT* to_end(CharT* s) { assume(s != nullptr); while (*s != 0) ++s; return s; } template auto to_end(T& x) -> decltype(end(x)) { return end(x); } } // namespace to_end_detail using to_end_detail::to_end; 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; Range(Iter first, Iter last) : _first(first), _last(last) { assert(std::distance(_first, _last) >= 0); _size = static_cast(std::distance(_first, _last)); } Range(Iter first, Iter last, size_t size) : _first(first), _last(last), _size(size) {} template Range(T& x) : Range(to_begin(x), to_end(x)) {} iterator begin() const noexcept { return _first; } iterator end() const noexcept { return _last; } reverse_iterator rbegin() const noexcept { return reverse_iterator(end()); } reverse_iterator rend() const noexcept { return reverse_iterator(begin()); } size_t size() const { return _size; } bool empty() const { return size() == 0; } explicit operator bool() const { return !empty(); } template ::iterator_category>::value>> auto operator[](size_t n) const -> decltype(*_first) { return _first[static_cast(n)]; } void remove_prefix(size_t n) { std::advance(_first, static_cast(n)); _size -= n; } void remove_suffix(size_t n) { std::advance(_last, -static_cast(n)); _size -= n; } 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; } const value_type& front() const { return *_first; } const value_type& back() const { return *(_last - 1); } 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 auto make_range(Iter first, Iter last) -> Range { return Range(first, last); } template auto make_range(T& x) -> Range { return {to_begin(x), to_end(x)}; } template inline bool operator==(const Range& a, const Range& b) { if (a.size() != b.size()) return false; return std::equal(a.begin(), a.end(), b.begin()); } 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 detail } // namespace rapidfuzz #include #include namespace rapidfuzz { namespace detail { template class SplittedSentenceView { public: using CharT = iter_value_t; SplittedSentenceView(RangeVec sentence) noexcept( std::is_nothrow_move_constructible>::value) : 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 detail } // namespace rapidfuzz #include #include #include #include #include #include #if defined(_MSC_VER) && !defined(__clang__) # include #endif namespace rapidfuzz { namespace 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; } RAPIDFUZZ_CONSTEXPR_CXX14 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 RAPIDFUZZ_CONSTEXPR_CXX14 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 RAPIDFUZZ_CONSTEXPR_CXX14 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 struct UnrollImpl; template struct UnrollImpl { template static void call(F&& f) { f(Pos); UnrollImpl::call(std::forward(f)); } }; template struct UnrollImpl { template static void call(F&&) {} }; template RAPIDFUZZ_CONSTEXPR_CXX14 void unroll(F&& f) { UnrollImpl::call(f); } } // namespace detail } // namespace rapidfuzz #if defined(__APPLE__) && !defined(_LIBCPP_HAS_C11_FEATURES) # include #endif namespace rapidfuzz { namespace 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) { /* calling the cast through this template function somehow avoids useless cast warnings */ 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 detail } // namespace rapidfuzz #include #include #include namespace rapidfuzz { namespace 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}; } template std::pair rf_mismatch(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2) { while (first1 != last1 && first2 != last2 && *first1 == *first2) ++first1, ++first2; return std::make_pair(first1, first2); } /** * 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, rf_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 = s1.rbegin(); size_t suffix = static_cast( std::distance(rfirst1, rf_mismatch(rfirst1, s1.rend(), s2.rbegin(), s2.rend()).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 detail } // namespace rapidfuzz #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 { namespace detail { template struct NormalizedMetricBase { template ::value>> static double normalized_distance(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, Args... args, double score_cutoff, double score_hint) { return _normalized_distance(make_range(first1, last1), make_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(make_range(s1), make_range(s2), std::forward(args)..., score_cutoff, score_hint); } template ::value>> static double normalized_similarity(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, Args... args, double score_cutoff, double score_hint) { return _normalized_similarity(make_range(first1, last1), make_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(make_range(s1), make_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 ::value>> static ResType distance(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, Args... args, ResType score_cutoff, ResType score_hint) { return T::_distance(make_range(first1, last1), make_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(make_range(s1), make_range(s2), std::forward(args)..., score_cutoff, score_hint); } template ::value>> static ResType similarity(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, Args... args, ResType score_cutoff, ResType score_hint) { return _similarity(make_range(first1, last1), make_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(make_range(s1), make_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 ::value>> static ResType distance(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, Args... args, ResType score_cutoff, ResType score_hint) { return _distance(make_range(first1, last1), make_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(make_range(s1), make_range(s2), std::forward(args)..., score_cutoff, score_hint); } template ::value>> static ResType similarity(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, Args... args, ResType score_cutoff, ResType score_hint) { return T::_similarity(make_range(first1, last1), make_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(make_range(s1), make_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; return _apply_distance_score_cutoff(dist, score_cutoff); } template static rapidfuzz::rf_enable_if_t::value, U> _apply_distance_score_cutoff(U score, U score_cutoff) { return (score <= score_cutoff) ? score : 1.0; } template static rapidfuzz::rf_enable_if_t::value, U> _apply_distance_score_cutoff(U score, U score_cutoff) { return (score <= score_cutoff) ? score : 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(make_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(make_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(make_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(make_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(make_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(make_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(make_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(make_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(make_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(make_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(make_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(make_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; return _apply_distance_score_cutoff(dist, score_cutoff); } template static rapidfuzz::rf_enable_if_t::value, U> _apply_distance_score_cutoff(U score, U score_cutoff) { return (score <= score_cutoff) ? score : 1.0; } template static rapidfuzz::rf_enable_if_t::value, U> _apply_distance_score_cutoff(U score, U score_cutoff) { return (score <= score_cutoff) ? score : 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, make_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, make_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, make_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, make_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; RAPIDFUZZ_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; } RAPIDFUZZ_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, make_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, make_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, make_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, make_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, make_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, make_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, make_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, make_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]; scores[i] = _apply_distance_score_cutoff(dist, score_cutoff); } } template static rapidfuzz::rf_enable_if_t::value, U> _apply_distance_score_cutoff(U score, U score_cutoff) { return (score <= score_cutoff) ? score : 1.0; } template static rapidfuzz::rf_enable_if_t::value, U> _apply_distance_score_cutoff(U score, U score_cutoff) { return (score <= score_cutoff) ? score : score_cutoff + 1; } MultiSimilarityBase() {} friend T; }; } // namespace detail } // namespace rapidfuzz namespace rapidfuzz { namespace 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, size_t) { return damerau_levenshtein_distance(s1, s2, score_cutoff); } }; } // namespace detail } // namespace rapidfuzz 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, size_t) const { return rapidfuzz::experimental::damerau_levenshtein_distance(s1, s2, score_cutoff); } std::vector s1; }; #ifdef RAPIDFUZZ_DEDUCTION_GUIDES template explicit CachedDamerauLevenshtein(const Sentence1& s1_) -> CachedDamerauLevenshtein>; template CachedDamerauLevenshtein(InputIt1 first1, InputIt1 last1) -> CachedDamerauLevenshtein>; #endif } // namespace experimental } // namespace rapidfuzz #include #include namespace rapidfuzz { namespace 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, size_t) { 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 detail } // namespace rapidfuzz 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::make_range(first1, last1), detail::make_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::make_range(s1), detail::make_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, size_t score_hint) const { return detail::Hamming::distance(s1, s2, pad, score_cutoff, score_hint); } std::vector s1; bool pad; }; #ifdef RAPIDFUZZ_DEDUCTION_GUIDES template explicit CachedHamming(const Sentence1& s1_, bool pad_ = true) -> CachedHamming>; template CachedHamming(InputIt1 first1, InputIt1 last1, bool pad_ = true) -> CachedHamming>; #endif /**@}*/ } // namespace rapidfuzz #include #include #include #include namespace rapidfuzz { namespace 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 detail } // namespace rapidfuzz #include #include #include namespace rapidfuzz { namespace detail { template struct LCSseqResult; template <> struct LCSseqResult { ShiftedBitMatrix S; size_t sim; }; template <> struct LCSseqResult { size_t sim; }; template LCSseqResult& getMatrixRef(LCSseqResult& res) { #if RAPIDFUZZ_IF_CONSTEXPR_AVAILABLE return res; #else // this is a hack since the compiler doesn't know early enough that // this is never called when the types differ. // On C++17 this properly uses if constexpr assert(RecordMatrix); return reinterpret_cast&>(res); #endif } /* * 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([&](size_t j) { S[j] = static_cast(-1); }); for (const auto& ch : s2) { unroll([&](size_t j) { alignas(32) std::array stored; unroll([&](size_t 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([&](size_t j) { auto counts = popcount(~S[j]); unroll([&](size_t 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([&](size_t 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([&](size_t 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; RAPIDFUZZ_IF_CONSTEXPR (RecordMatrix) { auto& res_ = getMatrixRef(res); 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); RAPIDFUZZ_IF_CONSTEXPR (RecordMatrix) { auto& res_ = getMatrixRef(res); 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); RAPIDFUZZ_IF_CONSTEXPR (RecordMatrix) { auto& res_ = getMatrixRef(res); 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; RAPIDFUZZ_IF_CONSTEXPR (RecordMatrix) { auto& res_ = getMatrixRef(res); 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; RAPIDFUZZ_IF_CONSTEXPR (RecordMatrix) { auto& res_ = getMatrixRef(res); 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); RAPIDFUZZ_IF_CONSTEXPR (RecordMatrix) { auto& res_ = getMatrixRef(res); 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 s1 == s2 ? 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 s1 == s2 ? 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; #ifndef NDEBUG size_t band_width_right = s2.size() - matrix.sim; #endif 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, size_t) { return lcs_seq_similarity(s1, s2, score_cutoff); } }; } // namespace detail } // namespace rapidfuzz #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::make_range(first1, last1), detail::make_range(first2, last2)); } template Editops lcs_seq_editops(const Sentence1& s1, const Sentence2& s2) { return detail::lcs_seq_editops(detail::make_range(s1), detail::make_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>; RAPIDFUZZ_CONSTEXPR_CXX14 static size_t get_vec_size() { # ifdef RAPIDFUZZ_AVX2 using namespace detail::simd_avx2; # else using namespace detail::simd_sse2; # endif RAPIDFUZZ_IF_CONSTEXPR (MaxLen <= 8) return native_simd::size; else RAPIDFUZZ_IF_CONSTEXPR (MaxLen <= 16) return native_simd::size; else RAPIDFUZZ_IF_CONSTEXPR (MaxLen <= 32) return native_simd::size; else RAPIDFUZZ_IF_CONSTEXPR (MaxLen <= 64) return native_simd::size; static_assert(MaxLen <= 64, "expected MaxLen <= 64"); } 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"); auto scores_ = detail::make_range(scores, scores + score_count); RAPIDFUZZ_IF_CONSTEXPR (MaxLen == 8) detail::lcs_simd(scores_, PM, s2, score_cutoff); else RAPIDFUZZ_IF_CONSTEXPR (MaxLen == 16) detail::lcs_simd(scores_, PM, s2, score_cutoff); else RAPIDFUZZ_IF_CONSTEXPR (MaxLen == 32) detail::lcs_simd(scores_, PM, s2, score_cutoff); else RAPIDFUZZ_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::make_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, size_t) const { return detail::lcs_seq_similarity(PM, detail::make_range(s1), s2, score_cutoff); } std::vector s1; detail::BlockPatternMatchVector PM; }; #ifdef RAPIDFUZZ_DEDUCTION_GUIDES template explicit CachedLCSseq(const Sentence1& s1_) -> CachedLCSseq>; template CachedLCSseq(InputIt1 first1, InputIt1 last1) -> CachedLCSseq>; #endif } // namespace rapidfuzz namespace rapidfuzz { namespace 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 detail } // namespace rapidfuzz 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; }; #ifdef RAPIDFUZZ_DEDUCTION_GUIDES template explicit CachedIndel(const Sentence1& s1_) -> CachedIndel>; template CachedIndel(InputIt1 first1, InputIt1 last1) -> CachedIndel>; #endif } // namespace rapidfuzz #include #include #include namespace rapidfuzz { namespace 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, #ifdef NDEBUG const Range&, #else const Range& P, #endif 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([&](size_t 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 -> JaroSimilaritySimdBounds> # else -> JaroSimilaritySimdBounds> # endif { # ifdef RAPIDFUZZ_AVX2 using namespace simd_avx2; # else using namespace simd_sse2; # endif # ifndef RAPIDFUZZ_AVX2 static constexpr size_t alignment = native_simd::alignment; # 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]; # 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 -> JaroSimilaritySimdBounds> # else -> JaroSimilaritySimdBounds> # endif { # 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([&](size_t 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([&](size_t 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([&](size_t 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([&](size_t 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, double) { return jaro_similarity(s1, s2, score_cutoff); } }; } // namespace detail } // namespace rapidfuzz #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, "incorrect MaxLen used"); using VecType = typename std::conditional< MaxLen == 8, uint8_t, typename std::conditional::type>::type>:: type; 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 } 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"); auto scores_ = detail::make_range(scores, scores + score_count); detail::jaro_similarity_simd(scores_, PM, str_lens, str_lens_size, s2, score_cutoff); } template double maximum(size_t, 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::make_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, double) const { return detail::jaro_similarity(PM, detail::make_range(s1), s2, score_cutoff); } std::vector s1; detail::BlockPatternMatchVector PM; }; #ifdef RAPIDFUZZ_DEDUCTION_GUIDES template explicit CachedJaro(const Sentence1& s1_) -> CachedJaro>; template CachedJaro(InputIt1 first1, InputIt1 last1) -> CachedJaro>; #endif } // namespace rapidfuzz namespace rapidfuzz { namespace 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, double) { return jaro_winkler_similarity(s1, s2, prefix_weight, score_cutoff); } }; } // namespace detail } // namespace rapidfuzz namespace rapidfuzz { template ::value>> 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 ::value>> 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 ::value>> 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 ::value>> 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(size_t, 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::make_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, double) const { return detail::jaro_winkler_similarity(PM, detail::make_range(s1), s2, prefix_weight, score_cutoff); } double prefix_weight; std::vector s1; detail::BlockPatternMatchVector PM; }; #ifdef RAPIDFUZZ_DEDUCTION_GUIDES template explicit CachedJaroWinkler(const Sentence1& s1_, double _prefix_weight = 0.1) -> CachedJaroWinkler>; template CachedJaroWinkler(InputIt1 first1, InputIt1 last1, double _prefix_weight = 0.1) -> CachedJaroWinkler>; #endif } // namespace rapidfuzz #include #include #include #include #include namespace rapidfuzz { namespace 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 LevenshteinResult& getMatrixRef(LevenshteinResult& res) { #if RAPIDFUZZ_IF_CONSTEXPR_AVAILABLE return res; #else // this is a hack since the compiler doesn't know early enough that // this is never called when the types differ. // On C++17 this properly uses if constexpr assert(RecordMatrix); return reinterpret_cast&>(res); #endif } template LevenshteinResult& getBitRowRef(LevenshteinResult& res) { #if RAPIDFUZZ_IF_CONSTEXPR_AVAILABLE return res; #else // this is a hack since the compiler doesn't know early enough that // this is never called when the types differ. // On C++17 this properly uses if constexpr assert(RecordBitRow); return reinterpret_cast&>(res); #endif } 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(); RAPIDFUZZ_IF_CONSTEXPR (RecordMatrix) { auto& res_ = getMatrixRef(res); 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; RAPIDFUZZ_IF_CONSTEXPR (RecordMatrix) { auto& res_ = getMatrixRef(res); res_.VP[i][0] = VP; res_.VN[i][0] = VN; } } if (res.dist > max) res.dist = max + 1; RAPIDFUZZ_IF_CONSTEXPR (RecordBitRow) { auto& res_ = getBitRowRef(res); 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( [&](size_t 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([&](size_t 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([&](size_t 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([&](size_t 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 { RAPIDFUZZ_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; RAPIDFUZZ_IF_CONSTEXPR (RecordMatrix) { auto& res_ = getMatrixRef(res); 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; RAPIDFUZZ_IF_CONSTEXPR (RecordMatrix) { auto& res_ = getMatrixRef(res); 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; RAPIDFUZZ_IF_CONSTEXPR (RecordMatrix) { auto& res_ = getMatrixRef(res); 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(); RAPIDFUZZ_IF_CONSTEXPR (RecordMatrix) { auto& res_ = getMatrixRef(res); 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); } RAPIDFUZZ_IF_CONSTEXPR (RecordBitRow) { auto& res_ = getBitRowRef(res); 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; RAPIDFUZZ_IF_CONSTEXPR (RecordMatrix) { auto& res_ = getMatrixRef(res); 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; RAPIDFUZZ_IF_CONSTEXPR (RecordMatrix) { auto& res_ = getMatrixRef(res); 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; } RAPIDFUZZ_IF_CONSTEXPR (RecordBitRow) { if (row == stop_row) { auto& res_ = getBitRowRef(res); 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 s1 != s2; 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 s1 != s2; // 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 detail } // namespace rapidfuzz 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::make_range(first1, last1), detail::make_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::make_range(s1), detail::make_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>; RAPIDFUZZ_CONSTEXPR_CXX14 static size_t get_vec_size() { # ifdef RAPIDFUZZ_AVX2 using namespace detail::simd_avx2; # else using namespace detail::simd_sse2; # endif RAPIDFUZZ_IF_CONSTEXPR (MaxLen <= 8) return native_simd::size; else RAPIDFUZZ_IF_CONSTEXPR (MaxLen <= 16) return native_simd::size; else RAPIDFUZZ_IF_CONSTEXPR (MaxLen <= 32) return native_simd::size; else RAPIDFUZZ_IF_CONSTEXPR (MaxLen <= 64) return native_simd::size; static_assert(MaxLen <= 64, "expected MaxLen <= 64"); } 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"); auto scores_ = detail::make_range(scores, scores + score_count); RAPIDFUZZ_IF_CONSTEXPR (MaxLen == 8) detail::levenshtein_hyrroe2003_simd(scores_, PM, str_lens, s2, score_cutoff); else RAPIDFUZZ_IF_CONSTEXPR (MaxLen == 16) detail::levenshtein_hyrroe2003_simd(scores_, PM, str_lens, s2, score_cutoff); else RAPIDFUZZ_IF_CONSTEXPR (MaxLen == 32) detail::levenshtein_hyrroe2003_simd(scores_, PM, str_lens, s2, score_cutoff); else RAPIDFUZZ_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::make_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::make_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::make_range(s1), s2, new_max); dist *= weights.insert_cost; return (dist <= score_cutoff) ? dist : score_cutoff + 1; } } return detail::generalized_levenshtein_distance(detail::make_range(s1), s2, weights, score_cutoff); } std::vector s1; detail::BlockPatternMatchVector PM; LevenshteinWeightTable weights; }; #ifdef RAPIDFUZZ_DEDUCTION_GUIDES template explicit CachedLevenshtein(const Sentence1& s1_, LevenshteinWeightTable aWeights = { 1, 1, 1}) -> CachedLevenshtein>; template CachedLevenshtein(InputIt1 first1, InputIt1 last1, LevenshteinWeightTable aWeights = {1, 1, 1}) -> CachedLevenshtein>; #endif } // namespace rapidfuzz #include #include namespace rapidfuzz { namespace 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( [&](size_t 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([&](size_t 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([&](size_t 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([&](size_t 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 { RAPIDFUZZ_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 detail } // namespace rapidfuzz 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>; RAPIDFUZZ_CONSTEXPR_CXX14 static size_t get_vec_size() { # ifdef RAPIDFUZZ_AVX2 using namespace detail::simd_avx2; # else using namespace detail::simd_sse2; # endif RAPIDFUZZ_IF_CONSTEXPR (MaxLen <= 8) return native_simd::size; else RAPIDFUZZ_IF_CONSTEXPR (MaxLen <= 16) return native_simd::size; else RAPIDFUZZ_IF_CONSTEXPR (MaxLen <= 32) return native_simd::size; else RAPIDFUZZ_IF_CONSTEXPR (MaxLen <= 64) return native_simd::size; static_assert(MaxLen <= 64, "expected MaxLen <= 64"); } 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"); auto scores_ = detail::make_range(scores, scores + score_count); RAPIDFUZZ_IF_CONSTEXPR (MaxLen == 8) detail::osa_hyrroe2003_simd(scores_, PM, str_lens, s2, score_cutoff); else RAPIDFUZZ_IF_CONSTEXPR (MaxLen == 16) detail::osa_hyrroe2003_simd(scores_, PM, str_lens, s2, score_cutoff); else RAPIDFUZZ_IF_CONSTEXPR (MaxLen == 32) detail::osa_hyrroe2003_simd(scores_, PM, str_lens, s2, score_cutoff); else RAPIDFUZZ_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::make_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, size_t) 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::make_range(s1), s2, score_cutoff); else res = detail::osa_hyrroe2003_block(PM, detail::make_range(s1), s2, score_cutoff); return (res <= score_cutoff) ? res : score_cutoff + 1; } std::vector s1; detail::BlockPatternMatchVector PM; }; #ifdef RAPIDFUZZ_DEDUCTION_GUIDES template CachedOSA(const Sentence1& s1_) -> CachedOSA>; template CachedOSA(InputIt1 first1, InputIt1 last1) -> CachedOSA>; #endif /**@}*/ } // namespace rapidfuzz #include namespace rapidfuzz { namespace 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, size_t) { size_t dist = remove_common_suffix(s1, s2); return (dist >= score_cutoff) ? dist : 0; } }; } // namespace detail } // namespace rapidfuzz 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, size_t score_hint) const { return detail::Postfix::similarity(s1, s2, score_cutoff, score_hint); } std::vector s1; }; #ifdef RAPIDFUZZ_DEDUCTION_GUIDES template explicit CachedPostfix(const Sentence1& s1_) -> CachedPostfix>; template CachedPostfix(InputIt1 first1, InputIt1 last1) -> CachedPostfix>; #endif /**@}*/ } // namespace rapidfuzz #include namespace rapidfuzz { namespace 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, size_t) { size_t dist = remove_common_prefix(s1, s2); return (dist >= score_cutoff) ? dist : 0; } }; } // namespace detail } // namespace rapidfuzz 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, size_t) const { return detail::Prefix::similarity(s1, s2, score_cutoff, score_cutoff); } std::vector s1; }; #ifdef RAPIDFUZZ_DEDUCTION_GUIDES template explicit CachedPrefix(const Sentence1& s1_) -> CachedPrefix>; template CachedPrefix(InputIt1 first1, InputIt1 last1) -> CachedPrefix>; #endif /**@}*/ } // 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 { namespace 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 detail } // namespace rapidfuzz namespace rapidfuzz { namespace 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::make_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; }; #ifdef RAPIDFUZZ_DEDUCTION_GUIDES template CachedRatio(const Sentence1& s1) -> CachedRatio>; template CachedRatio(InputIt1 first1, InputIt1 last1) -> CachedRatio>; #endif 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; }; #ifdef RAPIDFUZZ_DEDUCTION_GUIDES template explicit CachedPartialRatio(const Sentence1& s1) -> CachedPartialRatio>; template CachedPartialRatio(InputIt1 first1, InputIt1 last1) -> CachedPartialRatio>; #endif /** * @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; }; #ifdef RAPIDFUZZ_DEDUCTION_GUIDES template explicit CachedTokenSortRatio(const Sentence1& s1) -> CachedTokenSortRatio>; template CachedTokenSortRatio(InputIt1 first1, InputIt1 last1) -> CachedTokenSortRatio>; #endif /** * @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; }; #ifdef RAPIDFUZZ_DEDUCTION_GUIDES template explicit CachedPartialTokenSortRatio(const Sentence1& s1) -> CachedPartialTokenSortRatio>; template CachedPartialTokenSortRatio(InputIt1 first1, InputIt1 last1) -> CachedPartialTokenSortRatio>; #endif /** * @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; }; #ifdef RAPIDFUZZ_DEDUCTION_GUIDES template explicit CachedTokenSetRatio(const Sentence1& s1) -> CachedTokenSetRatio>; template CachedTokenSetRatio(InputIt1 first1, InputIt1 last1) -> CachedTokenSetRatio>; #endif /** * @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; }; #ifdef RAPIDFUZZ_DEDUCTION_GUIDES template explicit CachedPartialTokenSetRatio(const Sentence1& s1) -> CachedPartialTokenSetRatio>; template CachedPartialTokenSetRatio(InputIt1 first1, InputIt1 last1) -> CachedPartialTokenSetRatio>; #endif /** * @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; }; #ifdef RAPIDFUZZ_DEDUCTION_GUIDES template explicit CachedTokenRatio(const Sentence1& s1) -> CachedTokenRatio>; template CachedTokenRatio(InputIt1 first1, InputIt1 last1) -> CachedTokenRatio>; #endif /** * @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; }; #ifdef RAPIDFUZZ_DEDUCTION_GUIDES template explicit CachedPartialTokenRatio(const Sentence1& s1) -> CachedPartialTokenRatio>; template CachedPartialTokenRatio(InputIt1 first1, InputIt1 last1) -> CachedPartialTokenRatio>; #endif /** * @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; }; #ifdef RAPIDFUZZ_DEDUCTION_GUIDES template explicit CachedWRatio(const Sentence1& s1) -> CachedWRatio>; template CachedWRatio(InputIt1 first1, InputIt1 last1) -> CachedWRatio>; #endif /** * @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::make_range(first2, last2), score_cutoff); } template void similarity(double* scores, size_t score_count, const Sentence2& s2, double score_cutoff = 0) const { auto s2_ = detail::make_range(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; }; #ifdef RAPIDFUZZ_DEDUCTION_GUIDES template explicit CachedQRatio(const Sentence1& s1) -> CachedQRatio>; template CachedQRatio(InputIt1 first1, InputIt1 last1) -> CachedQRatio>; #endif /**@}*/ } // namespace fuzz } // namespace rapidfuzz #include #include #include #include #include #include namespace rapidfuzz { namespace fuzz { /********************************************** * ratio *********************************************/ template double ratio(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, double score_cutoff) { return ratio(detail::make_range(first1, last1), detail::make_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::make_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 RAPIDFUZZ_CONSTEXPR_CXX14 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); auto subseq1 = detail::make_range(subseq1_first, subseq1_first + static_cast(len1)); auto subseq2 = detail::make_range(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) { auto subseq = rapidfuzz::detail::make_range(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) { auto subseq = rapidfuzz::detail::make_range(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::make_range(first1, last1); auto s2 = detail::make_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, double) 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::make_range(s1); auto s2 = detail::make_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, double) 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, double) 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, double) 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, double) 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, double) 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, double) 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, double) 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, double) 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, double) 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::make_range(s1_sorted), detail::make_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, double) 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, double) 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, double) 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, double) 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::make_range(s1_sorted)) {} template template double CachedWRatio::similarity(InputIt2 first2, InputIt2 last2, double score_cutoff, double) 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, double) 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, double) 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, double) const { return similarity(detail::to_begin(s2), detail::to_end(s2), score_cutoff); } } // namespace fuzz } // namespace rapidfuzz #endif // RAPIDFUZZ_AMALGAMATED_HPP_INCLUDED rapidfuzz-cpp-3.3.1/fuzzing/000077500000000000000000000000001474403443000160135ustar00rootroot00000000000000rapidfuzz-cpp-3.3.1/fuzzing/CMakeLists.txt000066400000000000000000000013171474403443000205550ustar00rootroot00000000000000function(create_fuzzer fuzzer) add_executable(fuzz_${fuzzer} fuzz_${fuzzer}.cpp) target_compile_features(fuzz_${fuzzer} PUBLIC cxx_std_11) 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) rapidfuzz-cpp-3.3.1/fuzzing/fuzz_damerau_levenshtein_distance.cpp000066400000000000000000000037211474403443000254740ustar00rootroot00000000000000/* 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; } rapidfuzz-cpp-3.3.1/fuzzing/fuzz_indel_distance.cpp000066400000000000000000000027741474403443000225540ustar00rootroot00000000000000/* 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; } rapidfuzz-cpp-3.3.1/fuzzing/fuzz_indel_editops.cpp000066400000000000000000000012421474403443000224160ustar00rootroot00000000000000/* 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; } rapidfuzz-cpp-3.3.1/fuzzing/fuzz_jaro_similarity.cpp000066400000000000000000000061171474403443000230030ustar00rootroot00000000000000/* 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; } rapidfuzz-cpp-3.3.1/fuzzing/fuzz_lcs_similarity.cpp000066400000000000000000000040271474403443000226270ustar00rootroot00000000000000/* 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; } rapidfuzz-cpp-3.3.1/fuzzing/fuzz_levenshtein_distance.cpp000066400000000000000000000066151474403443000240030ustar00rootroot00000000000000/* 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; } rapidfuzz-cpp-3.3.1/fuzzing/fuzz_levenshtein_editops.cpp000066400000000000000000000032671474403443000236600ustar00rootroot00000000000000/* 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::make_range(s1), rapidfuzz::detail::make_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; } rapidfuzz-cpp-3.3.1/fuzzing/fuzz_osa_distance.cpp000066400000000000000000000035651474403443000222420ustar00rootroot00000000000000/* 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; } rapidfuzz-cpp-3.3.1/fuzzing/fuzz_partial_ratio.cpp000066400000000000000000000032221474403443000224260ustar00rootroot00000000000000/* 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; } rapidfuzz-cpp-3.3.1/fuzzing/fuzzing.hpp000066400000000000000000000024501474403443000202210ustar00rootroot00000000000000#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; } rapidfuzz-cpp-3.3.1/rapidfuzz/000077500000000000000000000000001474403443000163355ustar00rootroot00000000000000rapidfuzz-cpp-3.3.1/rapidfuzz/details/000077500000000000000000000000001474403443000177625ustar00rootroot00000000000000rapidfuzz-cpp-3.3.1/rapidfuzz/details/CharSet.hpp000066400000000000000000000033211474403443000220230ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* Copyright (c) 2022 Max Bachmann */ #pragma once #include #include #include #include #include #include namespace rapidfuzz { namespace 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 detail } // namespace rapidfuzzrapidfuzz-cpp-3.3.1/rapidfuzz/details/GrowingHashmap.hpp000066400000000000000000000114201474403443000234070ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* Copyright (c) 2022 Max Bachmann */ #pragma once #include #include #include namespace rapidfuzz { namespace 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 detail } // namespace rapidfuzzrapidfuzz-cpp-3.3.1/rapidfuzz/details/Matrix.hpp000066400000000000000000000110461474403443000217410ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* Copyright (c) 2022 Max Bachmann */ #pragma once #include #include #include #include #include namespace rapidfuzz { namespace detail { template struct BitMatrixView { using value_type = T; using size_type = size_t; using pointer = typename std::conditional::type; using reference = typename std::conditional::type; 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); } BitMatrixView operator[](size_t row) noexcept { return m_matrix[row]; } BitMatrixView 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 detail } // namespace rapidfuzzrapidfuzz-cpp-3.3.1/rapidfuzz/details/PatternMatchVector.hpp000066400000000000000000000126371474403443000242610ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* Copyright (c) 2022 Max Bachmann */ #pragma once #include #include #include #include #include #include #include namespace rapidfuzz { namespace 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 detail } // namespace rapidfuzz rapidfuzz-cpp-3.3.1/rapidfuzz/details/Range.hpp000066400000000000000000000122461474403443000215340ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* Copyright (c) 2022 Max Bachmann */ #pragma once #include #include #include #include #include #include #include #include #include #include namespace rapidfuzz { namespace 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 } namespace to_begin_detail { using std::begin; template CharT* to_begin(CharT* s) { return s; } template auto to_begin(T& x) -> decltype(begin(x)) { return begin(x); } } // namespace to_begin_detail using to_begin_detail::to_begin; namespace to_end_detail { using std::end; template CharT* to_end(CharT* s) { assume(s != nullptr); while (*s != 0) ++s; return s; } template auto to_end(T& x) -> decltype(end(x)) { return end(x); } } // namespace to_end_detail using to_end_detail::to_end; 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; Range(Iter first, Iter last) : _first(first), _last(last) { assert(std::distance(_first, _last) >= 0); _size = static_cast(std::distance(_first, _last)); } Range(Iter first, Iter last, size_t size) : _first(first), _last(last), _size(size) {} template Range(T& x) : Range(to_begin(x), to_end(x)) {} iterator begin() const noexcept { return _first; } iterator end() const noexcept { return _last; } reverse_iterator rbegin() const noexcept { return reverse_iterator(end()); } reverse_iterator rend() const noexcept { return reverse_iterator(begin()); } size_t size() const { return _size; } bool empty() const { return size() == 0; } explicit operator bool() const { return !empty(); } template ::iterator_category>::value>> auto operator[](size_t n) const -> decltype(*_first) { return _first[static_cast(n)]; } void remove_prefix(size_t n) { std::advance(_first, static_cast(n)); _size -= n; } void remove_suffix(size_t n) { std::advance(_last, -static_cast(n)); _size -= n; } 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; } const value_type& front() const { return *_first; } const value_type& back() const { return *(_last - 1); } 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 auto make_range(Iter first, Iter last) -> Range { return Range(first, last); } template auto make_range(T& x) -> Range { return {to_begin(x), to_end(x)}; } template inline bool operator==(const Range& a, const Range& b) { if (a.size() != b.size()) return false; return std::equal(a.begin(), a.end(), b.begin()); } 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 detail } // namespace rapidfuzz rapidfuzz-cpp-3.3.1/rapidfuzz/details/SplittedSentenceView.hpp000066400000000000000000000040211474403443000246000ustar00rootroot00000000000000#pragma once #include #include #include namespace rapidfuzz { namespace detail { template class SplittedSentenceView { public: using CharT = iter_value_t; SplittedSentenceView(RangeVec sentence) noexcept( std::is_nothrow_move_constructible>::value) : 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 detail } // namespace rapidfuzz rapidfuzz-cpp-3.3.1/rapidfuzz/details/common.hpp000066400000000000000000000057551474403443000217770ustar00rootroot00000000000000/* 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 { namespace 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) { /* calling the cast through this template function somehow avoids useless cast warnings */ 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 detail } // namespace rapidfuzz #include rapidfuzz-cpp-3.3.1/rapidfuzz/details/common_impl.hpp000066400000000000000000000110071474403443000230030ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* Copyright © 2020 Max Bachmann */ #include #include #include namespace rapidfuzz { namespace 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}; } template std::pair rf_mismatch(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2) { while (first1 != last1 && first2 != last2 && *first1 == *first2) ++first1, ++first2; return std::make_pair(first1, first2); } /** * 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, rf_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 = s1.rbegin(); size_t suffix = static_cast( std::distance(rfirst1, rf_mismatch(rfirst1, s1.rend(), s2.rbegin(), s2.rend()).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 detail } // namespace rapidfuzz rapidfuzz-cpp-3.3.1/rapidfuzz/details/config.hpp000066400000000000000000000011011474403443000217310ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* Copyright © 2020 Max Bachmann */ #pragma once #if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || __cplusplus >= 201703L) # define RAPIDFUZZ_DEDUCTION_GUIDES # define RAPIDFUZZ_IF_CONSTEXPR_AVAILABLE 1 # define RAPIDFUZZ_IF_CONSTEXPR if constexpr #else # define RAPIDFUZZ_IF_CONSTEXPR_AVAILABLE 0 # define RAPIDFUZZ_IF_CONSTEXPR if #endif #if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201402L) || __cplusplus >= 201402L) # define RAPIDFUZZ_CONSTEXPR_CXX14 constexpr #else # define RAPIDFUZZ_CONSTEXPR_CXX14 #endif rapidfuzz-cpp-3.3.1/rapidfuzz/details/distance.hpp000066400000000000000000000601461474403443000222740ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* Copyright © 2022 Max Bachmann */ #pragma once #include #include #include #include #include namespace rapidfuzz { namespace detail { template struct NormalizedMetricBase { template ::value>> static double normalized_distance(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, Args... args, double score_cutoff, double score_hint) { return _normalized_distance(make_range(first1, last1), make_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(make_range(s1), make_range(s2), std::forward(args)..., score_cutoff, score_hint); } template ::value>> static double normalized_similarity(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, Args... args, double score_cutoff, double score_hint) { return _normalized_similarity(make_range(first1, last1), make_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(make_range(s1), make_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 ::value>> static ResType distance(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, Args... args, ResType score_cutoff, ResType score_hint) { return T::_distance(make_range(first1, last1), make_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(make_range(s1), make_range(s2), std::forward(args)..., score_cutoff, score_hint); } template ::value>> static ResType similarity(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, Args... args, ResType score_cutoff, ResType score_hint) { return _similarity(make_range(first1, last1), make_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(make_range(s1), make_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 ::value>> static ResType distance(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, Args... args, ResType score_cutoff, ResType score_hint) { return _distance(make_range(first1, last1), make_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(make_range(s1), make_range(s2), std::forward(args)..., score_cutoff, score_hint); } template ::value>> static ResType similarity(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, Args... args, ResType score_cutoff, ResType score_hint) { return T::_similarity(make_range(first1, last1), make_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(make_range(s1), make_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; return _apply_distance_score_cutoff(dist, score_cutoff); } template static rapidfuzz::rf_enable_if_t::value, U> _apply_distance_score_cutoff(U score, U score_cutoff) { return (score <= score_cutoff) ? score : 1.0; } template static rapidfuzz::rf_enable_if_t::value, U> _apply_distance_score_cutoff(U score, U score_cutoff) { return (score <= score_cutoff) ? score : 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(make_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(make_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(make_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(make_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(make_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(make_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(make_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(make_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(make_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(make_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(make_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(make_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; return _apply_distance_score_cutoff(dist, score_cutoff); } template static rapidfuzz::rf_enable_if_t::value, U> _apply_distance_score_cutoff(U score, U score_cutoff) { return (score <= score_cutoff) ? score : 1.0; } template static rapidfuzz::rf_enable_if_t::value, U> _apply_distance_score_cutoff(U score, U score_cutoff) { return (score <= score_cutoff) ? score : 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, make_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, make_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, make_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, make_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; RAPIDFUZZ_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; } RAPIDFUZZ_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, make_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, make_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, make_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, make_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, make_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, make_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, make_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, make_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]; scores[i] = _apply_distance_score_cutoff(dist, score_cutoff); } } template static rapidfuzz::rf_enable_if_t::value, U> _apply_distance_score_cutoff(U score, U score_cutoff) { return (score <= score_cutoff) ? score : 1.0; } template static rapidfuzz::rf_enable_if_t::value, U> _apply_distance_score_cutoff(U score, U score_cutoff) { return (score <= score_cutoff) ? score : score_cutoff + 1; } MultiSimilarityBase() {} friend T; }; } // namespace detail } // namespace rapidfuzz rapidfuzz-cpp-3.3.1/rapidfuzz/details/intrinsics.hpp000066400000000000000000000127711474403443000226700ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* Copyright © 2021 Max Bachmann */ #pragma once #include #include #include #include #include #include #include #if defined(_MSC_VER) && !defined(__clang__) # include #endif namespace rapidfuzz { namespace 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; } RAPIDFUZZ_CONSTEXPR_CXX14 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 RAPIDFUZZ_CONSTEXPR_CXX14 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 RAPIDFUZZ_CONSTEXPR_CXX14 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 struct UnrollImpl; template struct UnrollImpl { template static void call(F&& f) { f(Pos); UnrollImpl::call(std::forward(f)); } }; template struct UnrollImpl { template static void call(F&&) {} }; template RAPIDFUZZ_CONSTEXPR_CXX14 void unroll(F&& f) { UnrollImpl::call(f); } } // namespace detail } // namespace rapidfuzz rapidfuzz-cpp-3.3.1/rapidfuzz/details/simd.hpp000066400000000000000000000012171474403443000214300ustar00rootroot00000000000000 /* 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 #endifrapidfuzz-cpp-3.3.1/rapidfuzz/details/simd_avx2.hpp000066400000000000000000000430521474403443000223730ustar00rootroot00000000000000/* 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 rapidfuzz-cpp-3.3.1/rapidfuzz/details/simd_sse2.hpp000066400000000000000000000376421474403443000223770ustar00rootroot00000000000000/* 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 rapidfuzz-cpp-3.3.1/rapidfuzz/details/type_traits.hpp000066400000000000000000000025641474403443000230510ustar00rootroot00000000000000/* 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); }; template using rf_enable_if_t = typename std::enable_if::type; } // namespace rapidfuzz rapidfuzz-cpp-3.3.1/rapidfuzz/details/types.hpp000066400000000000000000000420541474403443000216440ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* Copyright © 2020 Max Bachmann */ #pragma once #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 rapidfuzz-cpp-3.3.1/rapidfuzz/distance.hpp000066400000000000000000000142251474403443000206440ustar00rootroot00000000000000/* 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 rapidfuzz-cpp-3.3.1/rapidfuzz/distance/000077500000000000000000000000001474403443000201275ustar00rootroot00000000000000rapidfuzz-cpp-3.3.1/rapidfuzz/distance/DamerauLevenshtein.hpp000066400000000000000000000140631474403443000244270ustar00rootroot00000000000000/* 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, size_t) const { return rapidfuzz::experimental::damerau_levenshtein_distance(s1, s2, score_cutoff); } std::vector s1; }; #ifdef RAPIDFUZZ_DEDUCTION_GUIDES template explicit CachedDamerauLevenshtein(const Sentence1& s1_) -> CachedDamerauLevenshtein>; template CachedDamerauLevenshtein(InputIt1 first1, InputIt1 last1) -> CachedDamerauLevenshtein>; #endif } // namespace experimental } // namespace rapidfuzz rapidfuzz-cpp-3.3.1/rapidfuzz/distance/DamerauLevenshtein_impl.hpp000066400000000000000000000111361474403443000254460ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* Copyright © 2022-present Max Bachmann */ #include #include #include #include #include #include #include #include #include namespace rapidfuzz { namespace 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, size_t) { return damerau_levenshtein_distance(s1, s2, score_cutoff); } }; } // namespace detail } // namespace rapidfuzzrapidfuzz-cpp-3.3.1/rapidfuzz/distance/Hamming.hpp000066400000000000000000000150201474403443000222160ustar00rootroot00000000000000/* 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::make_range(first1, last1), detail::make_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::make_range(s1), detail::make_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, size_t score_hint) const { return detail::Hamming::distance(s1, s2, pad, score_cutoff, score_hint); } std::vector s1; bool pad; }; #ifdef RAPIDFUZZ_DEDUCTION_GUIDES template explicit CachedHamming(const Sentence1& s1_, bool pad_ = true) -> CachedHamming>; template CachedHamming(InputIt1 first1, InputIt1 last1, bool pad_ = true) -> CachedHamming>; #endif /**@}*/ } // namespace rapidfuzz rapidfuzz-cpp-3.3.1/rapidfuzz/distance/Hamming_impl.hpp000066400000000000000000000040621474403443000232430ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* Copyright © 2021 Max Bachmann */ #pragma once #include #include #include namespace rapidfuzz { namespace 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, size_t) { 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 detail } // namespace rapidfuzz rapidfuzz-cpp-3.3.1/rapidfuzz/distance/Indel.hpp000066400000000000000000000145711474403443000217030ustar00rootroot00000000000000/* 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; }; #ifdef RAPIDFUZZ_DEDUCTION_GUIDES template explicit CachedIndel(const Sentence1& s1_) -> CachedIndel>; template CachedIndel(InputIt1 first1, InputIt1 last1) -> CachedIndel>; #endif } // namespace rapidfuzz rapidfuzz-cpp-3.3.1/rapidfuzz/distance/Indel_impl.hpp000066400000000000000000000057421474403443000227240ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* Copyright © 2022-present Max Bachmann */ #include #include #include #include #include #include namespace rapidfuzz { namespace 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 detail } // namespace rapidfuzz rapidfuzz-cpp-3.3.1/rapidfuzz/distance/Jaro.hpp000066400000000000000000000166071474403443000215450ustar00rootroot00000000000000/* 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, "incorrect MaxLen used"); using VecType = typename std::conditional< MaxLen == 8, uint8_t, typename std::conditional::type>::type>:: type; 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 } 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"); auto scores_ = detail::make_range(scores, scores + score_count); detail::jaro_similarity_simd(scores_, PM, str_lens, str_lens_size, s2, score_cutoff); } template double maximum(size_t, 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::make_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, double) const { return detail::jaro_similarity(PM, detail::make_range(s1), s2, score_cutoff); } std::vector s1; detail::BlockPatternMatchVector PM; }; #ifdef RAPIDFUZZ_DEDUCTION_GUIDES template explicit CachedJaro(const Sentence1& s1_) -> CachedJaro>; template CachedJaro(InputIt1 first1, InputIt1 last1) -> CachedJaro>; #endif } // namespace rapidfuzz rapidfuzz-cpp-3.3.1/rapidfuzz/distance/JaroWinkler.hpp000066400000000000000000000176151474403443000231010ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* Copyright © 2022-present Max Bachmann */ #pragma once #include #include namespace rapidfuzz { template ::value>> 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 ::value>> 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 ::value>> 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 ::value>> 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(size_t, 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::make_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, double) const { return detail::jaro_winkler_similarity(PM, detail::make_range(s1), s2, prefix_weight, score_cutoff); } double prefix_weight; std::vector s1; detail::BlockPatternMatchVector PM; }; #ifdef RAPIDFUZZ_DEDUCTION_GUIDES template explicit CachedJaroWinkler(const Sentence1& s1_, double _prefix_weight = 0.1) -> CachedJaroWinkler>; template CachedJaroWinkler(InputIt1 first1, InputIt1 last1, double _prefix_weight = 0.1) -> CachedJaroWinkler>; #endif } // namespace rapidfuzz rapidfuzz-cpp-3.3.1/rapidfuzz/distance/JaroWinkler_impl.hpp000066400000000000000000000057531474403443000241220ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* Copyright © 2022-present Max Bachmann */ #include namespace rapidfuzz { namespace 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, double) { return jaro_winkler_similarity(s1, s2, prefix_weight, score_cutoff); } }; } // namespace detail } // namespace rapidfuzz rapidfuzz-cpp-3.3.1/rapidfuzz/distance/Jaro_impl.hpp000066400000000000000000000747711474403443000225740ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* Copyright © 2022-present Max Bachmann */ #include #include #include #include #include #include #include namespace rapidfuzz { namespace 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, #ifdef NDEBUG const Range&, #else const Range& P, #endif 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([&](size_t 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 -> JaroSimilaritySimdBounds> # else -> JaroSimilaritySimdBounds> # endif { # ifdef RAPIDFUZZ_AVX2 using namespace simd_avx2; # else using namespace simd_sse2; # endif # ifndef RAPIDFUZZ_AVX2 static constexpr size_t alignment = native_simd::alignment; # 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]; # 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 -> JaroSimilaritySimdBounds> # else -> JaroSimilaritySimdBounds> # endif { # 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([&](size_t 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([&](size_t 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([&](size_t 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([&](size_t 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, double) { return jaro_similarity(s1, s2, score_cutoff); } }; } // namespace detail } // namespace rapidfuzz rapidfuzz-cpp-3.3.1/rapidfuzz/distance/LCSseq.hpp000066400000000000000000000200631474403443000217730ustar00rootroot00000000000000/* 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::make_range(first1, last1), detail::make_range(first2, last2)); } template Editops lcs_seq_editops(const Sentence1& s1, const Sentence2& s2) { return detail::lcs_seq_editops(detail::make_range(s1), detail::make_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>; RAPIDFUZZ_CONSTEXPR_CXX14 static size_t get_vec_size() { # ifdef RAPIDFUZZ_AVX2 using namespace detail::simd_avx2; # else using namespace detail::simd_sse2; # endif RAPIDFUZZ_IF_CONSTEXPR (MaxLen <= 8) return native_simd::size; else RAPIDFUZZ_IF_CONSTEXPR (MaxLen <= 16) return native_simd::size; else RAPIDFUZZ_IF_CONSTEXPR (MaxLen <= 32) return native_simd::size; else RAPIDFUZZ_IF_CONSTEXPR (MaxLen <= 64) return native_simd::size; static_assert(MaxLen <= 64, "expected MaxLen <= 64"); } 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"); auto scores_ = detail::make_range(scores, scores + score_count); RAPIDFUZZ_IF_CONSTEXPR (MaxLen == 8) detail::lcs_simd(scores_, PM, s2, score_cutoff); else RAPIDFUZZ_IF_CONSTEXPR (MaxLen == 16) detail::lcs_simd(scores_, PM, s2, score_cutoff); else RAPIDFUZZ_IF_CONSTEXPR (MaxLen == 32) detail::lcs_simd(scores_, PM, s2, score_cutoff); else RAPIDFUZZ_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::make_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, size_t) const { return detail::lcs_seq_similarity(PM, detail::make_range(s1), s2, score_cutoff); } std::vector s1; detail::BlockPatternMatchVector PM; }; #ifdef RAPIDFUZZ_DEDUCTION_GUIDES template explicit CachedLCSseq(const Sentence1& s1_) -> CachedLCSseq>; template CachedLCSseq(InputIt1 first1, InputIt1 last1) -> CachedLCSseq>; #endif } // namespace rapidfuzz rapidfuzz-cpp-3.3.1/rapidfuzz/distance/LCSseq_impl.hpp000066400000000000000000000453701474403443000230240ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* Copyright © 2022-present Max Bachmann */ #include #include #include #include #include #include #include #include #include #include namespace rapidfuzz { namespace detail { template struct LCSseqResult; template <> struct LCSseqResult { ShiftedBitMatrix S; size_t sim; }; template <> struct LCSseqResult { size_t sim; }; template LCSseqResult& getMatrixRef(LCSseqResult& res) { #if RAPIDFUZZ_IF_CONSTEXPR_AVAILABLE return res; #else // this is a hack since the compiler doesn't know early enough that // this is never called when the types differ. // On C++17 this properly uses if constexpr assert(RecordMatrix); return reinterpret_cast&>(res); #endif } /* * 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([&](size_t j) { S[j] = static_cast(-1); }); for (const auto& ch : s2) { unroll([&](size_t j) { alignas(32) std::array stored; unroll([&](size_t 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([&](size_t j) { auto counts = popcount(~S[j]); unroll([&](size_t 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([&](size_t 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([&](size_t 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; RAPIDFUZZ_IF_CONSTEXPR (RecordMatrix) { auto& res_ = getMatrixRef(res); 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); RAPIDFUZZ_IF_CONSTEXPR (RecordMatrix) { auto& res_ = getMatrixRef(res); 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); RAPIDFUZZ_IF_CONSTEXPR (RecordMatrix) { auto& res_ = getMatrixRef(res); 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; RAPIDFUZZ_IF_CONSTEXPR (RecordMatrix) { auto& res_ = getMatrixRef(res); 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; RAPIDFUZZ_IF_CONSTEXPR (RecordMatrix) { auto& res_ = getMatrixRef(res); 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); RAPIDFUZZ_IF_CONSTEXPR (RecordMatrix) { auto& res_ = getMatrixRef(res); 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 s1 == s2 ? 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 s1 == s2 ? 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; #ifndef NDEBUG size_t band_width_right = s2.size() - matrix.sim; #endif 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, size_t) { return lcs_seq_similarity(s1, s2, score_cutoff); } }; } // namespace detail } // namespace rapidfuzz rapidfuzz-cpp-3.3.1/rapidfuzz/distance/Levenshtein.hpp000066400000000000000000000507521474403443000231350ustar00rootroot00000000000000/* 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::make_range(first1, last1), detail::make_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::make_range(s1), detail::make_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>; RAPIDFUZZ_CONSTEXPR_CXX14 static size_t get_vec_size() { # ifdef RAPIDFUZZ_AVX2 using namespace detail::simd_avx2; # else using namespace detail::simd_sse2; # endif RAPIDFUZZ_IF_CONSTEXPR (MaxLen <= 8) return native_simd::size; else RAPIDFUZZ_IF_CONSTEXPR (MaxLen <= 16) return native_simd::size; else RAPIDFUZZ_IF_CONSTEXPR (MaxLen <= 32) return native_simd::size; else RAPIDFUZZ_IF_CONSTEXPR (MaxLen <= 64) return native_simd::size; static_assert(MaxLen <= 64, "expected MaxLen <= 64"); } 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"); auto scores_ = detail::make_range(scores, scores + score_count); RAPIDFUZZ_IF_CONSTEXPR (MaxLen == 8) detail::levenshtein_hyrroe2003_simd(scores_, PM, str_lens, s2, score_cutoff); else RAPIDFUZZ_IF_CONSTEXPR (MaxLen == 16) detail::levenshtein_hyrroe2003_simd(scores_, PM, str_lens, s2, score_cutoff); else RAPIDFUZZ_IF_CONSTEXPR (MaxLen == 32) detail::levenshtein_hyrroe2003_simd(scores_, PM, str_lens, s2, score_cutoff); else RAPIDFUZZ_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::make_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::make_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::make_range(s1), s2, new_max); dist *= weights.insert_cost; return (dist <= score_cutoff) ? dist : score_cutoff + 1; } } return detail::generalized_levenshtein_distance(detail::make_range(s1), s2, weights, score_cutoff); } std::vector s1; detail::BlockPatternMatchVector PM; LevenshteinWeightTable weights; }; #ifdef RAPIDFUZZ_DEDUCTION_GUIDES template explicit CachedLevenshtein(const Sentence1& s1_, LevenshteinWeightTable aWeights = { 1, 1, 1}) -> CachedLevenshtein>; template CachedLevenshtein(InputIt1 first1, InputIt1 last1, LevenshteinWeightTable aWeights = {1, 1, 1}) -> CachedLevenshtein>; #endif } // namespace rapidfuzz rapidfuzz-cpp-3.3.1/rapidfuzz/distance/Levenshtein_impl.hpp000066400000000000000000001342771474403443000241630ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* Copyright © 2022-present Max Bachmann */ #include #include #include #include #include #include #include #include #include #include #include #include namespace rapidfuzz { namespace 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 LevenshteinResult& getMatrixRef(LevenshteinResult& res) { #if RAPIDFUZZ_IF_CONSTEXPR_AVAILABLE return res; #else // this is a hack since the compiler doesn't know early enough that // this is never called when the types differ. // On C++17 this properly uses if constexpr assert(RecordMatrix); return reinterpret_cast&>(res); #endif } template LevenshteinResult& getBitRowRef(LevenshteinResult& res) { #if RAPIDFUZZ_IF_CONSTEXPR_AVAILABLE return res; #else // this is a hack since the compiler doesn't know early enough that // this is never called when the types differ. // On C++17 this properly uses if constexpr assert(RecordBitRow); return reinterpret_cast&>(res); #endif } 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(); RAPIDFUZZ_IF_CONSTEXPR (RecordMatrix) { auto& res_ = getMatrixRef(res); 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; RAPIDFUZZ_IF_CONSTEXPR (RecordMatrix) { auto& res_ = getMatrixRef(res); res_.VP[i][0] = VP; res_.VN[i][0] = VN; } } if (res.dist > max) res.dist = max + 1; RAPIDFUZZ_IF_CONSTEXPR (RecordBitRow) { auto& res_ = getBitRowRef(res); 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( [&](size_t 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([&](size_t 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([&](size_t 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([&](size_t 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 { RAPIDFUZZ_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; RAPIDFUZZ_IF_CONSTEXPR (RecordMatrix) { auto& res_ = getMatrixRef(res); 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; RAPIDFUZZ_IF_CONSTEXPR (RecordMatrix) { auto& res_ = getMatrixRef(res); 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; RAPIDFUZZ_IF_CONSTEXPR (RecordMatrix) { auto& res_ = getMatrixRef(res); 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(); RAPIDFUZZ_IF_CONSTEXPR (RecordMatrix) { auto& res_ = getMatrixRef(res); 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); } RAPIDFUZZ_IF_CONSTEXPR (RecordBitRow) { auto& res_ = getBitRowRef(res); 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; RAPIDFUZZ_IF_CONSTEXPR (RecordMatrix) { auto& res_ = getMatrixRef(res); 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; RAPIDFUZZ_IF_CONSTEXPR (RecordMatrix) { auto& res_ = getMatrixRef(res); 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; } RAPIDFUZZ_IF_CONSTEXPR (RecordBitRow) { if (row == stop_row) { auto& res_ = getBitRowRef(res); 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 s1 != s2; 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 s1 != s2; // 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; } } 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 detail } // namespace rapidfuzz rapidfuzz-cpp-3.3.1/rapidfuzz/distance/OSA.hpp000066400000000000000000000227261474403443000212730ustar00rootroot00000000000000/* 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>; RAPIDFUZZ_CONSTEXPR_CXX14 static size_t get_vec_size() { # ifdef RAPIDFUZZ_AVX2 using namespace detail::simd_avx2; # else using namespace detail::simd_sse2; # endif RAPIDFUZZ_IF_CONSTEXPR (MaxLen <= 8) return native_simd::size; else RAPIDFUZZ_IF_CONSTEXPR (MaxLen <= 16) return native_simd::size; else RAPIDFUZZ_IF_CONSTEXPR (MaxLen <= 32) return native_simd::size; else RAPIDFUZZ_IF_CONSTEXPR (MaxLen <= 64) return native_simd::size; static_assert(MaxLen <= 64, "expected MaxLen <= 64"); } 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"); auto scores_ = detail::make_range(scores, scores + score_count); RAPIDFUZZ_IF_CONSTEXPR (MaxLen == 8) detail::osa_hyrroe2003_simd(scores_, PM, str_lens, s2, score_cutoff); else RAPIDFUZZ_IF_CONSTEXPR (MaxLen == 16) detail::osa_hyrroe2003_simd(scores_, PM, str_lens, s2, score_cutoff); else RAPIDFUZZ_IF_CONSTEXPR (MaxLen == 32) detail::osa_hyrroe2003_simd(scores_, PM, str_lens, s2, score_cutoff); else RAPIDFUZZ_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::make_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, size_t) 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::make_range(s1), s2, score_cutoff); else res = detail::osa_hyrroe2003_block(PM, detail::make_range(s1), s2, score_cutoff); return (res <= score_cutoff) ? res : score_cutoff + 1; } std::vector s1; detail::BlockPatternMatchVector PM; }; #ifdef RAPIDFUZZ_DEDUCTION_GUIDES template CachedOSA(const Sentence1& s1_) -> CachedOSA>; template CachedOSA(InputIt1 first1, InputIt1 last1) -> CachedOSA>; #endif /**@}*/ } // namespace rapidfuzz rapidfuzz-cpp-3.3.1/rapidfuzz/distance/OSA_impl.hpp000066400000000000000000000233051474403443000223060ustar00rootroot00000000000000 /* SPDX-License-Identifier: MIT */ /* Copyright © 2022-present Max Bachmann */ #pragma once #include #include #include #include #include #include namespace rapidfuzz { namespace 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( [&](size_t 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([&](size_t 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([&](size_t 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([&](size_t 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 { RAPIDFUZZ_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 detail } // namespace rapidfuzzrapidfuzz-cpp-3.3.1/rapidfuzz/distance/Postfix.hpp000066400000000000000000000075001474403443000222760ustar00rootroot00000000000000/* 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, size_t score_hint) const { return detail::Postfix::similarity(s1, s2, score_cutoff, score_hint); } std::vector s1; }; #ifdef RAPIDFUZZ_DEDUCTION_GUIDES template explicit CachedPostfix(const Sentence1& s1_) -> CachedPostfix>; template CachedPostfix(InputIt1 first1, InputIt1 last1) -> CachedPostfix>; #endif /**@}*/ } // namespace rapidfuzz rapidfuzz-cpp-3.3.1/rapidfuzz/distance/Postfix_impl.hpp000066400000000000000000000017261474403443000233230ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* Copyright © 2021 Max Bachmann */ #pragma once #include #include #include namespace rapidfuzz { namespace 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, size_t) { size_t dist = remove_common_suffix(s1, s2); return (dist >= score_cutoff) ? dist : 0; } }; } // namespace detail } // namespace rapidfuzz rapidfuzz-cpp-3.3.1/rapidfuzz/distance/Prefix.hpp000066400000000000000000000073551474403443000221070ustar00rootroot00000000000000/* 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, size_t) const { return detail::Prefix::similarity(s1, s2, score_cutoff, score_cutoff); } std::vector s1; }; #ifdef RAPIDFUZZ_DEDUCTION_GUIDES template explicit CachedPrefix(const Sentence1& s1_) -> CachedPrefix>; template CachedPrefix(InputIt1 first1, InputIt1 last1) -> CachedPrefix>; #endif /**@}*/ } // namespace rapidfuzz rapidfuzz-cpp-3.3.1/rapidfuzz/distance/Prefix_impl.hpp000066400000000000000000000017221474403443000231200ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* Copyright © 2021 Max Bachmann */ #pragma once #include #include #include namespace rapidfuzz { namespace 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, size_t) { size_t dist = remove_common_prefix(s1, s2); return (dist >= score_cutoff) ? dist : 0; } }; } // namespace detail } // namespace rapidfuzz rapidfuzz-cpp-3.3.1/rapidfuzz/fuzz.hpp000066400000000000000000000667311474403443000200610ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* Copyright © 2021 Max Bachmann */ /* Copyright © 2011 Adam Cohen */ #pragma once #include #include #include #include namespace rapidfuzz { namespace 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::make_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; }; #ifdef RAPIDFUZZ_DEDUCTION_GUIDES template CachedRatio(const Sentence1& s1) -> CachedRatio>; template CachedRatio(InputIt1 first1, InputIt1 last1) -> CachedRatio>; #endif 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; }; #ifdef RAPIDFUZZ_DEDUCTION_GUIDES template explicit CachedPartialRatio(const Sentence1& s1) -> CachedPartialRatio>; template CachedPartialRatio(InputIt1 first1, InputIt1 last1) -> CachedPartialRatio>; #endif /** * @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; }; #ifdef RAPIDFUZZ_DEDUCTION_GUIDES template explicit CachedTokenSortRatio(const Sentence1& s1) -> CachedTokenSortRatio>; template CachedTokenSortRatio(InputIt1 first1, InputIt1 last1) -> CachedTokenSortRatio>; #endif /** * @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; }; #ifdef RAPIDFUZZ_DEDUCTION_GUIDES template explicit CachedPartialTokenSortRatio(const Sentence1& s1) -> CachedPartialTokenSortRatio>; template CachedPartialTokenSortRatio(InputIt1 first1, InputIt1 last1) -> CachedPartialTokenSortRatio>; #endif /** * @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; }; #ifdef RAPIDFUZZ_DEDUCTION_GUIDES template explicit CachedTokenSetRatio(const Sentence1& s1) -> CachedTokenSetRatio>; template CachedTokenSetRatio(InputIt1 first1, InputIt1 last1) -> CachedTokenSetRatio>; #endif /** * @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; }; #ifdef RAPIDFUZZ_DEDUCTION_GUIDES template explicit CachedPartialTokenSetRatio(const Sentence1& s1) -> CachedPartialTokenSetRatio>; template CachedPartialTokenSetRatio(InputIt1 first1, InputIt1 last1) -> CachedPartialTokenSetRatio>; #endif /** * @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; }; #ifdef RAPIDFUZZ_DEDUCTION_GUIDES template explicit CachedTokenRatio(const Sentence1& s1) -> CachedTokenRatio>; template CachedTokenRatio(InputIt1 first1, InputIt1 last1) -> CachedTokenRatio>; #endif /** * @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; }; #ifdef RAPIDFUZZ_DEDUCTION_GUIDES template explicit CachedPartialTokenRatio(const Sentence1& s1) -> CachedPartialTokenRatio>; template CachedPartialTokenRatio(InputIt1 first1, InputIt1 last1) -> CachedPartialTokenRatio>; #endif /** * @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; }; #ifdef RAPIDFUZZ_DEDUCTION_GUIDES template explicit CachedWRatio(const Sentence1& s1) -> CachedWRatio>; template CachedWRatio(InputIt1 first1, InputIt1 last1) -> CachedWRatio>; #endif /** * @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::make_range(first2, last2), score_cutoff); } template void similarity(double* scores, size_t score_count, const Sentence2& s2, double score_cutoff = 0) const { auto s2_ = detail::make_range(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; }; #ifdef RAPIDFUZZ_DEDUCTION_GUIDES template explicit CachedQRatio(const Sentence1& s1) -> CachedQRatio>; template CachedQRatio(InputIt1 first1, InputIt1 last1) -> CachedQRatio>; #endif /**@}*/ } // namespace fuzz } // namespace rapidfuzz #include rapidfuzz-cpp-3.3.1/rapidfuzz/fuzz_impl.hpp000066400000000000000000001105551474403443000210740ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* Copyright © 2021-present Max Bachmann */ /* Copyright © 2011 Adam Cohen */ #include "rapidfuzz/details/Range.hpp" #include #include #include #include #include #include #include namespace rapidfuzz { namespace fuzz { /********************************************** * ratio *********************************************/ template double ratio(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, double score_cutoff) { return ratio(detail::make_range(first1, last1), detail::make_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::make_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 RAPIDFUZZ_CONSTEXPR_CXX14 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); auto subseq1 = detail::make_range(subseq1_first, subseq1_first + static_cast(len1)); auto subseq2 = detail::make_range(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) { auto subseq = rapidfuzz::detail::make_range(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) { auto subseq = rapidfuzz::detail::make_range(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::make_range(first1, last1); auto s2 = detail::make_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, double) 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::make_range(s1); auto s2 = detail::make_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, double) 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, double) 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, double) 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, double) 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, double) 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, double) 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, double) 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, double) 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, double) 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::make_range(s1_sorted), detail::make_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, double) 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, double) 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, double) 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, double) 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::make_range(s1_sorted)) {} template template double CachedWRatio::similarity(InputIt2 first2, InputIt2 last2, double score_cutoff, double) 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, double) 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, double) 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, double) const { return similarity(detail::to_begin(s2), detail::to_end(s2), score_cutoff); } } // namespace fuzz } // namespace rapidfuzz rapidfuzz-cpp-3.3.1/rapidfuzz/rapidfuzz_all.hpp000066400000000000000000000002351474403443000217140ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* Copyright © 2022-present Max Bachmann */ #pragma once #include #include rapidfuzz-cpp-3.3.1/rapidfuzz_reference/000077500000000000000000000000001474403443000203535ustar00rootroot00000000000000rapidfuzz-cpp-3.3.1/rapidfuzz_reference/DamerauLevenshtein.hpp000066400000000000000000000046231474403443000246540ustar00rootroot00000000000000/* 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 rapidfuzz-cpp-3.3.1/rapidfuzz_reference/Hamming.hpp000066400000000000000000000021011474403443000224360ustar00rootroot00000000000000/* 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 rapidfuzz-cpp-3.3.1/rapidfuzz_reference/Indel.hpp000066400000000000000000000024031474403443000221160ustar00rootroot00000000000000/* 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 rapidfuzz-cpp-3.3.1/rapidfuzz_reference/Jaro.hpp000066400000000000000000000045741474403443000217710ustar00rootroot00000000000000/* 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 */ rapidfuzz-cpp-3.3.1/rapidfuzz_reference/JaroWinkler.hpp000066400000000000000000000026271474403443000233220ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /* Copyright © 2022-present Max Bachmann */ #pragma once #include "Jaro.hpp" namespace rapidfuzz_reference { template ::value>::type> 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 */ rapidfuzz-cpp-3.3.1/rapidfuzz_reference/LCSseq.hpp000066400000000000000000000015631474403443000222230ustar00rootroot00000000000000/* 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 rapidfuzz-cpp-3.3.1/rapidfuzz_reference/Levenshtein.hpp000066400000000000000000000077351474403443000233640ustar00rootroot00000000000000/* 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 rapidfuzz-cpp-3.3.1/rapidfuzz_reference/OSA.hpp000066400000000000000000000040521474403443000215070ustar00rootroot00000000000000/* 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 rapidfuzz-cpp-3.3.1/rapidfuzz_reference/README.md000066400000000000000000000002531474403443000216320ustar00rootroot00000000000000## rapidfuzz_reference This includes reference implementations of various string matching algorithms, which can be used to validate the results of faster implementations.rapidfuzz-cpp-3.3.1/rapidfuzz_reference/common.hpp000066400000000000000000000012231474403443000223520ustar00rootroot00000000000000/* 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 rapidfuzz-cpp-3.3.1/rapidfuzz_reference/fuzz.hpp000066400000000000000000000045331474403443000220670ustar00rootroot00000000000000/* 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 rapidfuzz-cpp-3.3.1/test/000077500000000000000000000000001474403443000152765ustar00rootroot00000000000000rapidfuzz-cpp-3.3.1/test/CMakeLists.txt000066400000000000000000000051171474403443000200420ustar00rootroot00000000000000find_package(Catch2 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) set(Catch2_VERSION "2.13.10") 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) if(Catch2_VERSION VERSION_LESS "3.0") add_executable(test_${test} tests-main.cpp tests-${test}.cpp) target_link_libraries(test_${test} PRIVATE Catch2::Catch2) target_compile_definitions(test_${test} PRIVATE CATCH2_VERSION=2) else() add_executable(test_${test} tests-${test}.cpp) target_link_libraries(test_${test} PRIVATE Catch2::Catch2WithMain) target_compile_definitions(test_${test} PRIVATE CATCH2_VERSION=3) endif() target_link_libraries(test_${test} PRIVATE ${PROJECT_NAME}) if (RAPIDFUZZ_ENABLE_LINTERS) target_link_libraries(test_${test} PRIVATE project_warnings) endif() add_test(NAME ${test} COMMAND test_${test}) endfunction() rapidfuzz_add_test(fuzz) rapidfuzz_add_test(common) add_subdirectory(distance) rapidfuzz-cpp-3.3.1/test/common.hpp000066400000000000000000000031141474403443000172760ustar00rootroot00000000000000#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 value_type& operator*() const { return *iter; } private: T iter; }; template constexpr auto make_bidir(Iter iter) -> BidirectionalIterWrapper { return BidirectionalIterWrapper(iter); } template ::value>> std::basic_string str_multiply(std::basic_string a, size_t b) { std::basic_string output; while (b--) output += a; return output; } rapidfuzz-cpp-3.3.1/test/distance/000077500000000000000000000000001474403443000170705ustar00rootroot00000000000000rapidfuzz-cpp-3.3.1/test/distance/CMakeLists.txt000066400000000000000000000023331474403443000216310ustar00rootroot00000000000000function(rapidfuzz_add_test test) if(Catch2_VERSION VERSION_LESS "3.0") add_executable(test_${test} ../tests-main.cpp tests-${test}.cpp examples/ocr.cpp examples/pythonLevenshteinIssue9.cpp) target_link_libraries(test_${test} PRIVATE Catch2::Catch2) target_compile_definitions(test_${test} PRIVATE CATCH2_VERSION=2) else() add_executable(test_${test} tests-${test}.cpp examples/ocr.cpp examples/pythonLevenshteinIssue9.cpp) target_link_libraries(test_${test} PRIVATE Catch2::Catch2WithMain) target_compile_definitions(test_${test} PRIVATE CATCH2_VERSION=3) endif() target_link_libraries(test_${test} PRIVATE ${PROJECT_NAME}) 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) rapidfuzz-cpp-3.3.1/test/distance/examples/000077500000000000000000000000001474403443000207065ustar00rootroot00000000000000rapidfuzz-cpp-3.3.1/test/distance/examples/ocr.cpp000066400000000000000000041404261474403443000222100ustar00rootroot00000000000000#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}; rapidfuzz-cpp-3.3.1/test/distance/examples/ocr.hpp000066400000000000000000000002071474403443000222010ustar00rootroot00000000000000#pragma once #include #include extern std::vector ocr_example1; extern std::vector ocr_example2; rapidfuzz-cpp-3.3.1/test/distance/examples/pythonLevenshteinIssue9.cpp000066400000000000000000001274361474403443000262570ustar00rootroot00000000000000#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 pythonLevenshteinIssue9rapidfuzz-cpp-3.3.1/test/distance/examples/pythonLevenshteinIssue9.hpp000066400000000000000000000003121474403443000262430ustar00rootroot00000000000000#pragma once #include #include namespace pythonLevenshteinIssue9 { extern std::vector example1; extern std::vector example2; } // namespace pythonLevenshteinIssue9 rapidfuzz-cpp-3.3.1/test/distance/tests-DamerauLevenshtein.cpp000066400000000000000000000141231474403443000245200ustar00rootroot00000000000000#if CATCH2_VERSION == 2 # include #else # include # include #endif #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( make_bidir(s1.begin()), make_bidir(s1.end()), make_bidir(s2.begin()), make_bidir(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( make_bidir(s1.begin()), make_bidir(s1.end()), make_bidir(s2.begin()), make_bidir(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( make_bidir(s1.begin()), make_bidir(s1.end()), make_bidir(s2.begin()), make_bidir(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( make_bidir(s1.begin()), make_bidir(s1.end()), make_bidir(s2.begin()), make_bidir(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)); } } } rapidfuzz-cpp-3.3.1/test/distance/tests-Hamming.cpp000066400000000000000000000123071474403443000223170ustar00rootroot00000000000000#if CATCH2_VERSION == 2 # include #else # include # include #endif #include #include #include #include "../common.hpp" #include "rapidfuzz/details/type_traits.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(make_bidir(s1.begin()), make_bidir(s1.end()), make_bidir(s2.begin()), make_bidir(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(make_bidir(s1.begin()), make_bidir(s1.end()), make_bidir(s2.begin()), make_bidir(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(make_bidir(s1.begin()), make_bidir(s1.end()), make_bidir(s2.begin()), make_bidir(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(make_bidir(s1.begin()), make_bidir(s1.end()), make_bidir(s2.begin()), make_bidir(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()); } }rapidfuzz-cpp-3.3.1/test/distance/tests-Indel.cpp000066400000000000000000000272721474403443000220010ustar00rootroot00000000000000#if CATCH2_VERSION == 2 # include #else # include # include #endif #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(make_bidir(s1.begin()), make_bidir(s1.end()), make_bidir(s2.begin()), make_bidir(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(make_bidir(s1.begin()), make_bidir(s1.end()), make_bidir(s2.begin()), make_bidir(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(make_bidir(s1.begin()), make_bidir(s1.end()), make_bidir(s2.begin()), make_bidir(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(make_bidir(s1.begin()), make_bidir(s1.end()), make_bidir(s2.begin()), make_bidir(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)); } } } rapidfuzz-cpp-3.3.1/test/distance/tests-Jaro.cpp000066400000000000000000000263031474403443000216330ustar00rootroot00000000000000#if CATCH2_VERSION == 2 # include #else # include # include #endif #include "../../rapidfuzz_reference/Jaro.hpp" #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( make_bidir(s1.begin()), make_bidir(s1.end()), make_bidir(s2.begin()), make_bidir(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( make_bidir(s1.begin()), make_bidir(s1.end()), make_bidir(s2.begin()), make_bidir(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 } } rapidfuzz-cpp-3.3.1/test/distance/tests-JaroWinkler.cpp000066400000000000000000000250211474403443000231630ustar00rootroot00000000000000#if CATCH2_VERSION == 2 # include #else # include # include #endif #include "../../rapidfuzz_reference/JaroWinkler.hpp" #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)); } }rapidfuzz-cpp-3.3.1/test/distance/tests-LCSseq.cpp000066400000000000000000000222511474403443000220700ustar00rootroot00000000000000#if CATCH2_VERSION == 2 # include #else # include # include #endif #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(make_bidir(s1.begin()), make_bidir(s1.end()), make_bidir(s2.begin()), make_bidir(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(make_bidir(s1.begin()), make_bidir(s1.end()), make_bidir(s2.begin()), make_bidir(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(make_bidir(s1.begin()), make_bidir(s1.end()), make_bidir(s2.begin()), make_bidir(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(make_bidir(s1.begin()), make_bidir(s1.end()), make_bidir(s2.begin()), make_bidir(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 rapidfuzz-cpp-3.3.1/test/distance/tests-Levenshtein.cpp000066400000000000000000000640051474403443000232250ustar00rootroot00000000000000#if CATCH2_VERSION == 2 # include #else # include # include #endif #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(make_bidir(s1.begin()), make_bidir(s1.end()), make_bidir(s2.begin()), make_bidir(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(make_bidir(s1.begin()), make_bidir(s1.end()), make_bidir(s2.begin()), make_bidir(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::make_range(s1), rapidfuzz::detail::make_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::make_range(s1), rapidfuzz::detail::make_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::make_range(s1), rapidfuzz::detail::make_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::make_range(s1), rapidfuzz::detail::make_range(s2)); REQUIRE(s2 == rapidfuzz::editops_apply_str(ops1, s1, s2)); rapidfuzz::Editops ops2; rapidfuzz::detail::levenshtein_align(ops2, rapidfuzz::detail::make_range(s1), rapidfuzz::detail::make_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::make_range(s1), rapidfuzz::detail::make_range(s2)); REQUIRE(s2 == rapidfuzz::editops_apply_str(ops1, s1, s2)); rapidfuzz::Editops ops2; rapidfuzz::detail::levenshtein_align(ops2, rapidfuzz::detail::make_range(s1), rapidfuzz::detail::make_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::make_range(s1), rapidfuzz::detail::make_range(s2)); REQUIRE(s2 == rapidfuzz::editops_apply_vec(ops1, s1, s2)); rapidfuzz::Editops ops2; rapidfuzz::detail::levenshtein_align(ops2, rapidfuzz::detail::make_range(s1), rapidfuzz::detail::make_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 rapidfuzz-cpp-3.3.1/test/distance/tests-OSA.cpp000066400000000000000000000062221474403443000213600ustar00rootroot00000000000000#if CATCH2_VERSION == 2 # include #else # include # include #endif #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(make_bidir(s1.begin()), make_bidir(s1.end()), make_bidir(s2.begin()), make_bidir(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); } } rapidfuzz-cpp-3.3.1/test/tests-common.cpp000066400000000000000000000025601474403443000204350ustar00rootroot00000000000000#include "rapidfuzz/details/Range.hpp" #if CATCH2_VERSION == 2 # include #else # include # include #endif #include TEST_CASE("remove affix") { std::string s1 = "aabbbbaaaa"; std::string s2 = "aaabbbbaaaaa"; { auto s1_ = rapidfuzz::detail::make_range(s1); auto s2_ = rapidfuzz::detail::make_range(s2); REQUIRE(rapidfuzz::detail::remove_common_prefix(s1_, s2_) == 2); REQUIRE(s1_ == rapidfuzz::detail::make_range("bbbbaaaa")); REQUIRE(s2_ == rapidfuzz::detail::make_range("abbbbaaaaa")); } { auto s1_ = rapidfuzz::detail::make_range(s1); auto s2_ = rapidfuzz::detail::make_range(s2); REQUIRE(rapidfuzz::detail::remove_common_suffix(s1_, s2_) == 4); REQUIRE(s1_ == rapidfuzz::detail::make_range("aabbbb")); REQUIRE(s2_ == rapidfuzz::detail::make_range("aaabbbba")); } { auto s1_ = rapidfuzz::detail::make_range(s1); auto s2_ = rapidfuzz::detail::make_range(s2); auto affix = rapidfuzz::detail::remove_common_affix(s1_, s2_); REQUIRE(affix.prefix_len == 2); REQUIRE(affix.suffix_len == 4); REQUIRE(s1_ == rapidfuzz::detail::make_range("bbbb")); REQUIRE(s2_ == rapidfuzz::detail::make_range("abbbba")); } } rapidfuzz-cpp-3.3.1/test/tests-fuzz.cpp000066400000000000000000000273311474403443000201460ustar00rootroot00000000000000#if CATCH2_VERSION == 2 # include #else # include # include #endif #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)); } } rapidfuzz-cpp-3.3.1/test/tests-main.cpp000066400000000000000000000001571474403443000200710ustar00rootroot00000000000000// test main file so catch2 does not has to be recompiled #define CATCH_CONFIG_MAIN #include rapidfuzz-cpp-3.3.1/tools/000077500000000000000000000000001474403443000154575ustar00rootroot00000000000000rapidfuzz-cpp-3.3.1/tools/amalgamation.py000066400000000000000000000073701474403443000204720ustar00rootroot00000000000000#!/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