├── third_party_modules.md ├── .gitignore ├── .github ├── CODEOWNERS ├── dependabot.yml └── workflows │ ├── nim_test.yml │ ├── nim_test_cyclic.yml │ ├── check_code_format.yml │ └── build_directory_md.yml ├── .gitpod.dockerfile ├── .gitpod.yml ├── DIRECTORY.md ├── sorts └── bogo_sort.nim ├── maths ├── aliquot_sum.nim ├── bitwise_addition.nim ├── arc_length.nim ├── haversine_distance.nim ├── modular_inverse.nim ├── extended_gcd.nim └── abs.nim ├── LICENSE.md ├── config.nims ├── README.md ├── dynamic_programming ├── viterbi.nim ├── levenshtein_distance.nim ├── catalan_numbers.nim └── fibonacci_numbers.nim ├── graphics └── bresenhams_line.nim ├── stable_marriage ├── README.md └── gale_shapley.nim ├── searches ├── linear_search.nim └── binary_search.nim ├── .scripts └── directory.nim ├── strings └── check_anagram.nim └── CONTRIBUTING.md /third_party_modules.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | nimcache/ 2 | nimblecache/ 3 | htmldocs/ 4 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @dlesnoff 2 | *.md @Panquesito7 3 | /.github @Panquesito7 4 | .gitpod.yml @Panquesito7 5 | .gitpod.dockerfile @Panquesito7 6 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 2 3 | updates: 4 | - package-ecosystem: "github-actions" 5 | directory: "/.github/workflows/" 6 | schedule: 7 | interval: "daily" 8 | ... 9 | -------------------------------------------------------------------------------- /.gitpod.dockerfile: -------------------------------------------------------------------------------- 1 | FROM gitpod/workspace-base 2 | 3 | RUN curl https://nim-lang.org/choosenim/init.sh -sSf -o install_nim.sh \ 4 | && chmod +x ./install_nim.sh \ 5 | && ./install_nim.sh -y \ 6 | && rm install_nim.sh \ 7 | && echo "export PATH=$HOME/.nimble/bin:\$PATH" >> ~/.bashrc 8 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | --- 2 | github: 3 | prebuilds: 4 | addBadge: true 5 | addComment: false 6 | addCheck: false 7 | master: true 8 | branches: true 9 | pullRequestsFromForks: true 10 | 11 | 12 | image: 13 | file: .gitpod.dockerfile 14 | 15 | vscode: 16 | extensions: 17 | - nimsaem.nimvscode 18 | ... 19 | -------------------------------------------------------------------------------- /.github/workflows/nim_test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: nim_test 3 | 4 | # yamllint disable-line rule:truthy 5 | on: 6 | workflow_dispatch: 7 | push: 8 | branches: 9 | - main 10 | pull_request: 11 | 12 | jobs: 13 | nim_test: 14 | runs-on: ${{matrix.os}} 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | os: 19 | - ubuntu-latest 20 | - macos-latest 21 | - windows-latest 22 | nim-version: 23 | - '2.2.6' 24 | 25 | steps: 26 | - uses: actions/checkout@v6 27 | 28 | - uses: jiro4989/setup-nim-action@v2 29 | with: 30 | nim-version: ${{matrix.nim-version}} 31 | 32 | - name: Build and test 33 | run: | 34 | nim test 35 | ... 36 | -------------------------------------------------------------------------------- /.github/workflows/nim_test_cyclic.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: nim_test_cyclic 3 | 4 | # yamllint disable-line rule:truthy 5 | on: 6 | workflow_dispatch: 7 | schedule: 8 | - cron: '3 1 * * 1' 9 | 10 | jobs: 11 | nim_test: 12 | runs-on: ${{matrix.os}} 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | os: 17 | - ubuntu-latest 18 | nim-version: 19 | - 'stable' 20 | - 'devel' 21 | 22 | steps: 23 | - uses: actions/checkout@v6 24 | 25 | - uses: jiro4989/setup-nim-action@v2 26 | with: 27 | nim-version: ${{matrix.nim-version}} 28 | 29 | - name: Display nim version 30 | run: | 31 | nim --version 32 | 33 | - name: Build and test 34 | run: | 35 | nim test 36 | ... 37 | -------------------------------------------------------------------------------- /DIRECTORY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # The Algorithms — Nim: Directory Hierarchy 4 | 5 | ## Dynamic Programming 6 | * [Catalan Numbers](dynamic_programming/catalan_numbers.nim) 7 | * [Levenshtein distance](dynamic_programming/levenshtein_distance.nim) 8 | * [Viterbi](dynamic_programming/viterbi.nim) 9 | 10 | ## Maths 11 | * [Absolute value](maths/abs.nim) 12 | * [Aliquot sum](maths/aliquot_sum.nim) 13 | * [Arc Length](maths/arc_length.nim) 14 | * [Bitwise Addition](maths/bitwise_addition.nim) 15 | 16 | ## Searches 17 | * [Binary Search](searches/binary_search.nim) 18 | * [Linear Search](searches/linear_search.nim) 19 | 20 | ## [Stable Marriage](stable_marriage/README.md) 21 | * [Gale-Shapley](stable_marriage/gale_shapley.nim) 22 | 23 | ## Strings 24 | * [Check Anagram](strings/check_anagram.nim) 25 | 26 | -------------------------------------------------------------------------------- /sorts/bogo_sort.nim: -------------------------------------------------------------------------------- 1 | ## Bogo Sort 2 | ## ============= 3 | ## wiki: https://en.wikipedia.org/wiki/Bogosort 4 | ## 5 | {.push raises: [].} 6 | 7 | runnableExamples: 8 | 9 | var arr = @[3, 1, 2] 10 | bogoSort(arr) 11 | doAssert isSorted(arr) 12 | 13 | var arr2 = @["c", "a", "b"] 14 | bogoSort(arr2) 15 | doAssert isSorted(arr2) 16 | 17 | 18 | import random 19 | 20 | func isSorted[T](arr: openArray[T]): bool = 21 | for i in 0.. arr[i + 1]: 23 | return false 24 | return true 25 | 26 | proc bogoSort*[T](arr: var openArray[T]) = 27 | while not isSorted(arr): 28 | shuffle(arr) 29 | 30 | when isMainModule: 31 | import std/unittest 32 | suite "BogoSortTests": 33 | test "sort an array of integers": 34 | var arr = @[3, 1, 2] 35 | bogoSort(arr) 36 | check isSorted(arr) 37 | 38 | test "sort an array of strings": 39 | var arr = @["c", "a", "b"] 40 | bogoSort(arr) 41 | check isSorted(arr) 42 | -------------------------------------------------------------------------------- /.github/workflows/check_code_format.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: check_code_format 3 | 4 | # yamllint disable-line rule:truthy 5 | on: 6 | workflow_dispatch: 7 | push: 8 | branches: 9 | - main 10 | pull_request: 11 | 12 | jobs: 13 | check_format_code: 14 | name: check format code 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v6 18 | with: 19 | fetch-depth: 0 20 | 21 | - uses: jiro4989/setup-nim-action@v2 22 | with: 23 | nim-version: stable 24 | repo-token: ${{ secrets.GITHUB_TOKEN }} 25 | parent-nim-install-directory: ${{ runner.temp }} 26 | 27 | - name: Format code 28 | run: | 29 | git clean -f -x -d 30 | nim prettyfy 31 | 32 | - name: Fail if needs reformatting 33 | run: | 34 | if [[ $(git status --porcelain) ]]; then 35 | echo "please reformat/prettyfy these files:" 36 | git status --porcelain=v1 37 | exit 1 38 | fi 39 | ... 40 | -------------------------------------------------------------------------------- /maths/aliquot_sum.nim: -------------------------------------------------------------------------------- 1 | ## Aliquot sum 2 | ## In number theory, the aliquot sum s(n) of a positive integer n is the sum of 3 | ## all proper divisors of n, that is, all divisors of n other than n itself. 4 | ## https://en.wikipedia.org/wiki/Aliquot_sum 5 | 6 | runnableExamples: 7 | import std/strformat 8 | const expected = [16, 117] 9 | for i, number in [12, 100].pairs(): 10 | let sum = aliquotSum(number) 11 | assert sum == expected[i] 12 | echo fmt"The sum of all the proper divisors of {number} is {sum}" 13 | 14 | func aliquotSum*(number: Positive): Natural = 15 | ## Returns the sum of all the proper divisors of the number 16 | ## Example: aliquotSum(12) = 1 + 2 + 3 + 4 + 6 = 16 17 | result = 0 18 | for divisor in 1 .. (number div 2): 19 | if number mod divisor == 0: 20 | result += divisor 21 | 22 | when isMainModule: 23 | import std/unittest 24 | suite "Check aliquotSum": 25 | test "aliquotSum on small values": 26 | var 27 | input = @[1, 2, 9, 12, 27, 100] 28 | expected = @[0, 1, 4, 16, 13, 117] 29 | for i in 0 ..< input.len: 30 | check: 31 | aliquotSum(input[i]) == expected[i] 32 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 TheAlgorithms and contributors. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /maths/bitwise_addition.nim: -------------------------------------------------------------------------------- 1 | ## Bitwise Addition 2 | ## Illustrate how to implement addition of integers using bitwise operations 3 | ## See https://en.wikipedia.org/wiki/Bitwise_operation#Applications 4 | runnableExamples: 5 | import std/strformat 6 | var 7 | a = 5 8 | b = 6 9 | echo fmt"The sum of {a} and {b} is {add(a,b)}" 10 | 11 | func add*(first: int, second: int): int = 12 | ## Implementation of addition of integer with `and`, `xor` and `shl` 13 | ## boolean operators. 14 | var first = first 15 | var second = second 16 | while second != 0: 17 | var c = first and second 18 | first = first xor second 19 | second = c shl 1 20 | return first 21 | 22 | when isMainModule: 23 | import std/unittest 24 | 25 | suite "Check addition": 26 | test "Addition of two positive numbers": 27 | check: 28 | add(3, 5) == 8 29 | add(13, 5) == 18 30 | test "Addition of two negative numbers": 31 | check: 32 | add(-7, -2) == -9 33 | add(-321, -0) == -321 34 | test "Addition of one positive and one negative number": 35 | check: 36 | add(-7, 2) == -5 37 | add(-13, 5) == -8 38 | add(13, -5) == 8 39 | -------------------------------------------------------------------------------- /maths/arc_length.nim: -------------------------------------------------------------------------------- 1 | ## Arc Length 2 | ## https://en.wikipedia.org/wiki/Arc_length 3 | import std/math 4 | {.push raises: [].} 5 | 6 | func arcLengthDegree(radius: float, angle: float): float = 7 | ## Calculate the length of an arc given the `radius` and `angle` in degrees. 8 | return PI * radius * (angle / 180) 9 | 10 | func arcLengthRadian(radius: float, angle: float): float = 11 | ## Calculate the length of an arc given the `radius` and `angle` in radians. 12 | return radius * angle 13 | 14 | when isMainModule: 15 | import std/unittest 16 | const UnitsInLastPlace = 1 17 | suite "Arc Length": 18 | test "radius 5, angle 45": 19 | check almostEqual(arcLengthDegree(5, 45), 3.926990816987241, UnitsInLastPlace) 20 | test "radius 15, angle 120": 21 | check almostEqual(arcLengthDegree(15, 120), 31.41592653589793, UnitsInLastPlace) 22 | test "radius 10, angle 90": 23 | check almostEqual(arcLengthDegree(10, 90), 15.70796326794897, UnitsInLastPlace) 24 | 25 | suite "Arc Length": 26 | test "radius 5, angle 45": 27 | check almostEqual(arcLengthRadian(5, degToRad(45.0)), 3.926990816987241, 28 | UnitsInLastPlace) 29 | test "radius 15, angle 120": 30 | check almostEqual(arcLengthRadian(15, degToRad(120.0)), 31.41592653589793, 31 | UnitsInLastPlace) 32 | test "radius 10, angle 90": 33 | check almostEqual(arcLengthRadian(10, degToRad(90.0)), 15.70796326794897, 34 | UnitsInLastPlace) 35 | -------------------------------------------------------------------------------- /config.nims: -------------------------------------------------------------------------------- 1 | if defined(release) or defined(danger): 2 | --opt: speed 3 | --passC: "-flto" 4 | --passL: "-flto" 5 | --passL: "-s" 6 | else: 7 | --checks: on 8 | --assertions: on 9 | --spellSuggest 10 | --styleCheck: error 11 | 12 | --mm:arc 13 | 14 | import std/[os, sequtils] 15 | from std/strutils import startsWith, endsWith 16 | from std/strformat import `&` 17 | 18 | const IgnorePathPrefixes = ["."] 19 | 20 | func isIgnored(path: string): bool = 21 | IgnorePathPrefixes.mapIt(path.startsWith(it)).anyIt(it) 22 | 23 | iterator modules(dir: string = getCurrentDir()): string = 24 | ## Iterate over paths to all nim files in directory `dir`, skipping 25 | ## paths starting with substrings from the `IgnorePathPrefixes` const 26 | for path in walkDirRec(dir, relative = true): 27 | if not path.isIgnored() and path.endsWith(".nim"): 28 | yield path 29 | 30 | ############ Tasks 31 | task test, "Test everything": 32 | --warning: "BareExcept:off" 33 | --hints: off 34 | var failedUnits: seq[string] 35 | 36 | for path in modules(): 37 | echo &"Testing {path}:" 38 | try: selfExec(&"-f --warning[BareExcept]:off --hints:off r \"{path}\"") 39 | except OSError: 40 | failedUnits.add(path) 41 | if failedUnits.len > 0: 42 | echo "Failed tests:" 43 | for path in failedUnits: 44 | echo &"- {path}" 45 | quit(1) 46 | else: 47 | echo "All tests passed successfully" 48 | 49 | task prettyfy, "Run nimpretty on everything": 50 | for path in modules(): 51 | exec(&"nimpretty --indent:2 \"{path}\"") 52 | -------------------------------------------------------------------------------- /.github/workflows/build_directory_md.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: build_directory_md 3 | 4 | # yamllint disable-line rule:truthy 5 | on: 6 | workflow_dispatch: 7 | push: 8 | branches: 9 | - 'main' 10 | paths-ignore: 11 | - '.github/**' 12 | - '.scripts/**' 13 | - 'DIRECTORY.md' 14 | 15 | jobs: 16 | build_directory_md: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v6 20 | 21 | - uses: jiro4989/setup-nim-action@v2 22 | with: 23 | parent-nim-install-directory: ${{ runner.temp }} 24 | 25 | - name: Build file 26 | run: | 27 | git clean --force -x -d 28 | 29 | # Compile the script first 30 | nim c -o:directory_script .scripts/directory.nim 31 | compile_status=$? 32 | if [ $compile_status -ne 0 ]; then 33 | echo "Compilation of directory.nim failed with exit code $compile_status!" 34 | exit $compile_status 35 | fi 36 | 37 | # Run the compiled script 38 | ./directory_script > DIRECTORY.md 39 | exec_status=$? 40 | if [ $exec_status -ne 0 ]; then 41 | echo "Execution of directory.nim script failed with exit code $exec_status!" 42 | exit $exec_status 43 | fi 44 | 45 | - name: Setup Git configurations 46 | run: | 47 | git config --global user.name github-actions[bot] 48 | git config --global user.email 'github-actions@users.noreply.github.com' 49 | - name: Commit and push changes 50 | run: | 51 | git checkout -b directory_update-${{ github.sha }} 52 | git add . 53 | 54 | git commit -m "docs: update DIRECTORY.md" 55 | git push origin directory_update-${{ github.sha }}:directory_update-${{ github.sha }} 56 | - name: Create a pull request 57 | run: | 58 | if [[ $(git log --branches --not --remotes) ]]; then 59 | gh pr create --base ${GITHUB_REF##*/} --head directory_update-${{ github.sha }} --title 'docs: update DIRECTORY.md' --body 'Updated the DIRECTORY.md file (see the diff. for changes).' 60 | fi 61 | 62 | ... 63 | -------------------------------------------------------------------------------- /maths/haversine_distance.nim: -------------------------------------------------------------------------------- 1 | # Haversine formula 2 | 3 | import std/math 4 | 5 | func haversineDistance(latitudeA, longitudeA, latitudeB, 6 | longitudeB: float): float = 7 | ## returns the length of the shortest path connecting the input points on an unit sphere. 8 | ## The input points are represented by their spherical/geographical coordinates. 9 | ## The inputs are expected to be in radians. 10 | let 11 | dLatitude = latitudeB - latitudeA 12 | dLongitude = longitudeB - longitudeA 13 | a = sin(dLatitude / 2.0)^2 + cos(latitudeA) * cos(latitudeB) * sin( 14 | dLongitude / 2.0)^2 15 | 2.0 * arcsin(sqrt(a)) 16 | 17 | when isMainModule: 18 | import std/[unittest, sequtils, strformat] 19 | suite "haversineDistance": 20 | const testCases = [ 21 | (0.0, 0.0, 0.0, 0.0, 0.0), 22 | (0.0, 0.0, PI / 2.0, 0.0, PI / 2.0), 23 | (-PI / 2.0, 0.0, PI / 2.0, 0.0, PI), 24 | (0.0, 0.0, 0.0, PI / 2.0, PI / 2.0), 25 | (0.0, -PI / 2.0, 0.0, PI / 2.0, PI), 26 | (1.0, -PI / 2.0, -1.0, PI / 2.0, PI), 27 | (2.0, -PI / 2.0, -2.0, PI / 2.0, PI), 28 | (3.0, -PI / 2.0, -3.0, PI / 2.0, PI), 29 | (3.0, -PI / 2.0 + 0.5, -3.0, PI / 2.0 + 0.5, PI), 30 | (0.0, 0.0, 0.0, PI, PI), 31 | (PI / 2.0, 1.0, PI / 2.0, 2.0, 0.0), 32 | (-PI / 2.0, 1.0, -PI / 2.0, 2.0, 0.0), 33 | (0.0, 0.0, -PI / 4.0, 0.0, PI / 4.0), 34 | (0.0, 1.0, PI / 4.0, 1.0, PI / 4.0), 35 | (-PI / 2.0, 0.0, -PI / 4.0, 0.0, PI / 4.0), 36 | (-PI / 2.0, 0.0, -PI / 4.0, 0.6, PI / 4.0), 37 | (-PI / 2.0, 3.0, -PI / 4.0, 0.2, PI / 4.0), 38 | ].mapIt: 39 | (id: fmt"posA=({it[0]}, {it[1]}), posB=({it[2]}, {it[3]})", 40 | latitudeA: it[0], longitudeA: it[1], 41 | latitudeB: it[2], longitudeB: it[3], 42 | expected: it[4]) 43 | 44 | func isClose(a, b: float): bool = 45 | return abs(a-b) < 0.0000001 46 | 47 | for tc in testCases: 48 | test tc.id: 49 | checkpoint("returns expected result") 50 | check isClose(haversineDistance(tc.latitudeA, tc.longitudeA, 51 | tc.latitudeB, tc.longitudeB), tc.expected) 52 | check isClose(haversineDistance(tc.latitudeB, tc.longitudeB, 53 | tc.latitudeA, tc.longitudeA), tc.expected) 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The Algorithms – Nim  2 | 3 | [![Gitpod Ready-to-Code][badge-gitpod]](https://gitpod.io/#https://github.com/TheAlgorithms/Nim) 4 | [![Contributions Welcome][badge-contributions]](https://github.com/TheAlgorithms/Nim/blob/main/CONTRIBUTING.md) 5 | ![Repo size][badge-reposize] 6 |
7 | [![Discord chat][badge-discord]][chat-discord] 8 | [![Gitter chat][badge-gitter]][chat-gitter] 9 | [![Matrix room][badge-matrix]][chat-matrix] 10 | 11 | ## Algorithms collection implemented in Nim - for education 12 | 13 | Implementations are for learning purposes only. They may be less efficient than the implementations in the [Nim standard library](https://nim-lang.org/docs/lib.html) or [Nimble packages](https://nimble.directory/). Use them at your discretion. 14 | 15 | ## Getting Started 16 | 17 | Read through our [Contribution Guidelines](CONTRIBUTING.md) before you contribute. 18 | 19 | ## List of Algorithms 20 | 21 | See our [directory](DIRECTORY.md) for easier navigation and a better overview of the project. 22 | 23 | You can also browse the implementations on [the website](https://the-algorithms.com/language/nim). 24 | 25 | ## Community Channels 26 | 27 | We are on [Discord](https://the-algorithms.com/discord) and [Gitter][chat-gitter]/[Matrix][chat-matrix]! 28 | 29 | Community channels are a great way for you to ask questions and get help. Please join us! 30 | 31 | 32 | [chat-discord]: https://the-algorithms.com/discord 33 | [chat-gitter]: https://gitter.im/#TheAlgorithms_community:gitter.im 34 | [chat-matrix]: https://matrix.to/#/#TheAlgorithms_community:gitter.im 35 | [badge-contributions]: https://img.shields.io/static/v1.svg?label=Contributions&message=Welcome&color=0059b3&style=flat-square 36 | [badge-discord]: https://img.shields.io/discord/808045925556682782.svg?logo=discord&colorB=7289DA&style=flat-square 37 | [badge-matrix]: https://img.shields.io/badge/matrix-TheAlgorithms_community-0dbd8b?logo=matrix 38 | [badge-gitpod]: https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod&style=flat-square 39 | [badge-gitter]: https://img.shields.io/badge/Chat-Gitter-ff69b4.svg?label=Chat&logo=gitter&style=flat-square 40 | [badge-reposize]: https://img.shields.io/github/repo-size/TheAlgorithms/Nim.svg?label=Repo%20size&style=flat-square 41 | -------------------------------------------------------------------------------- /maths/modular_inverse.nim: -------------------------------------------------------------------------------- 1 | ## Modular inverse 2 | 3 | import std/[options, math] 4 | 5 | 6 | func euclidHalfIteration(inA, inB: Positive): tuple[gcd: Natural, coeff: int] = 7 | var (a, b) = (inA.Natural, inB.Natural) 8 | var (x0, x1) = (1, 0) 9 | while b != 0: 10 | (x0, x1) = (x1, x0 - (a div b) * x1) 11 | (a, b) = (b, math.floorMod(a, b)) 12 | 13 | (a, x0) 14 | 15 | 16 | func modularInverse*(inA: int, modulus: Positive): Option[Positive] = 17 | ## For a given integer `a` and a natural number `modulus` it 18 | ## computes the inverse of `a` modulo `modulus`, i.e. 19 | ## it finds an integer `0 < inv < modulus` such that 20 | ## `(a * inv) mod modulus == 1`. 21 | let a = math.floorMod(inA, modulus) 22 | if a == 0: 23 | return none(Positive) 24 | let (gcd, x) = euclidHalfIteration(a, modulus) 25 | if gcd == 1: 26 | return some(math.floorMod(x, modulus).Positive) 27 | none(Positive) 28 | 29 | 30 | when isMainModule: 31 | import std/[unittest, sequtils, strformat, random] 32 | suite "modularInverse": 33 | const testCases = [ 34 | (3, 7, 5), 35 | (-1, 5, 4), # Inverse of a negative 36 | (-7, 5, 2), # Inverse of a negative lower than modulus 37 | (-7, 4, 1), # Inverse of a negative with non-prime modulus 38 | (4, 5, 4), 39 | (9, 5, 4), 40 | (5, 21, 17), 41 | (2, 21, 11), 42 | (4, 21, 16), 43 | (55, 372, 115), 44 | (1, 100, 1), 45 | ].mapIt: 46 | let tc = (id: fmt"a={it[0]}, modulus={it[1]}", a: it[0], modulus: it[1], 47 | inv: it[2]) 48 | assert 0 < tc.inv 49 | assert tc.inv < tc.modulus 50 | assert math.floorMod(tc.a * tc.inv, tc.modulus) == 1 51 | tc 52 | 53 | for tc in testCases: 54 | test tc.id: 55 | checkpoint("returns expected result") 56 | check modularInverse(tc.a, tc.modulus).get() == tc.inv 57 | 58 | test "No inverse when modulus is 1": 59 | check modularInverse(0, 1).isNone() 60 | check modularInverse(1, 1).isNone() 61 | check modularInverse(-1, 1).isNone() 62 | 63 | test "No inverse when inputs are not co-prime": 64 | check modularInverse(2, 4).isNone() 65 | check modularInverse(-5, 25).isNone() 66 | check modularInverse(0, 17).isNone() 67 | check modularInverse(17, 17).isNone() 68 | 69 | randomize() 70 | const randomTestSize = 10 71 | for testNum in 0..randomTestSize: 72 | let a = rand(-10000000..10000000) 73 | let modulus = rand(1..1000000) 74 | test fmt"(random test) a={a}, modulus={modulus}": 75 | let inv = modularInverse(a, modulus) 76 | if inv.isSome(): 77 | check 0 < inv.get() 78 | check inv.get() < modulus 79 | check math.floorMod(a * inv.get(), modulus) == 1 80 | else: 81 | check math.gcd(a, modulus) != 1 82 | -------------------------------------------------------------------------------- /dynamic_programming/viterbi.nim: -------------------------------------------------------------------------------- 1 | ## Viterbi 2 | {.push raises: [].} 3 | 4 | type 5 | HiddenMarkovModel[S] = ref object 6 | states: seq[S] 7 | startProbability: seq[float] # Sum of all elements must be 1 8 | transitionProbability: seq[seq[float]] # Sum of all elements in each row must be 1 9 | emissionProbability: seq[seq[float]] # Sum of all elements in each row must be 1 10 | 11 | func viterbi*[S, O](hmm: HiddenMarkovModel[S], observations: seq[O]): seq[S] = 12 | var 13 | probabilities = newSeq[seq[float]](len(observations)) 14 | backpointers = newSeq[seq[int]](len(observations)) 15 | 16 | # Initialization 17 | for i in 0 ..< len(observations): 18 | probabilities[i] = newSeq[float](len(hmm.states)) 19 | backpointers[i] = newSeq[int](len(hmm.states)) 20 | 21 | for state in 0 ..< len(hmm.states): 22 | probabilities[0][state] = hmm.startProbability[state] * 23 | hmm.emissionProbability[state][observations[0].ord] 24 | backpointers[0][state] = 0 25 | 26 | # Forward Pass - Derive the probabilities 27 | for nObs in 1 ..< len(observations): 28 | var 29 | obs = observations[nObs].ord 30 | for state in 0 ..< len(hmm.states): 31 | # Compute the argmax for probability of the current state 32 | var 33 | maxProb = -1.0 34 | maxProbState = 0 35 | for priorState in 0 ..< len(hmm.states): 36 | var 37 | prob = probabilities[nObs - 1][priorState] * 38 | hmm.transitionProbability[priorState][state] * 39 | hmm.emissionProbability[state][obs] 40 | if prob > maxProb: 41 | maxProb = prob 42 | maxProbState = priorState 43 | # Update probabilities and backpointers 44 | probabilities[nObs][state] = maxProb 45 | backpointers[nObs][state] = maxProbState 46 | 47 | # Final observation 48 | var 49 | maxProb = -1.0 50 | maxProbState = 0 51 | for state in 0 ..< len(hmm.states): 52 | if probabilities[len(observations) - 1][state] > maxProb: 53 | maxProb = probabilities[len(observations) - 1][state] 54 | maxProbState = state 55 | 56 | result = newSeq[S](len(observations)) 57 | result[^1] = hmm.states[maxProbState] 58 | 59 | # Backward Pass - Derive the states from the probabilities 60 | for i in 1 ..< len(observations): 61 | result[^(i+1)] = hmm.states[backpointers[^i][maxProbState]] 62 | maxProbState = backpointers[^i][maxProbState] 63 | 64 | when isMainModule: 65 | import std/unittest 66 | 67 | suite "Viterbi algorithm": 68 | test "Example from Wikipedia": 69 | type 70 | States = enum 71 | Healthy, Fever 72 | Observations = enum 73 | Normal, Cold, Dizzy 74 | var 75 | hmm = HiddenMarkovModel[States]() 76 | observations = @[Normal, Cold, Dizzy] 77 | hmm.states = @[Healthy, Fever] 78 | hmm.startProbability = @[0.6, 0.4] 79 | hmm.transitionProbability = @[@[0.7, 0.3], @[0.4, 0.6]] 80 | hmm.emissionProbability = @[@[0.5, 0.4, 0.1], @[0.1, 0.3, 0.6]] 81 | check viterbi(hmm, observations) == @[Healthy, Healthy, Fever] 82 | -------------------------------------------------------------------------------- /graphics/bresenhams_line.nim: -------------------------------------------------------------------------------- 1 | ## Bresenham's line algorithm 2 | 3 | func computeStep(inStart, inEnd: int): int = 4 | if inStart < inEnd: 1 else: -1 5 | 6 | type Point = tuple[x, y: int] 7 | 8 | func drawBresenhamLine*(posA, posB: Point): seq[Point] = 9 | ## returns a sequence of coordinates approximating the straight line 10 | ## between points `posA` and `posB`. 11 | ## These points are determined using the 12 | ## [Bresenham's line algorithm](https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm). 13 | ## This implementation balances the positive and negative errors between `x` and `y` coordinates. 14 | let 15 | dx = abs(posA.x-posB.x) 16 | dy = -abs(posA.y-posB.y) 17 | xStep = computeStep(posA.x, posB.x) 18 | yStep = computeStep(posA.y, posB.y) 19 | 20 | var 21 | difference = dx + dy 22 | res: seq[Point] = @[] 23 | x = posA.x 24 | y = posA.y 25 | 26 | res.add((x, y)) 27 | while (x, y) != posB: 28 | let doubleDifference = 2 * difference 29 | if doubleDifference >= dy: 30 | difference += dy 31 | x += xStep 32 | if doubleDifference <= dx: 33 | difference += dx 34 | y += yStep 35 | res.add((x, y)) 36 | 37 | res 38 | 39 | when isMainModule: 40 | import std/[unittest, sequtils, algorithm] 41 | suite "bresenhamLine": 42 | const testCases = [ 43 | ("horizontal", (1, 0), (3, 0), @[(1, 0), (2, 0), (3, 0)]), 44 | ("vertical", (0, -1), (0, 1), @[(0, -1), (0, 0), (0, 1)]), 45 | ("trivial", (0, 0), (0, 0), @[(0, 0)]), 46 | ("diagonal", (0, 0), (3, 3), @[(0, 0), (1, 1), (2, 2), (3, 3)]), 47 | ("shiftedDiagonal", (5, 1), (8, 4), @[(5, 1), (6, 2), (7, 3), (8, 4)]), 48 | ("halfDiagonal", 49 | (0, 0), (5, 2), 50 | @[(0, 0), (1, 0), (2, 1), (3, 1), (4, 2), (5, 2)]), 51 | ("doubleDiagonal", 52 | (0, 0), (2, 5), 53 | @[(0, 0), (0, 1), (1, 2), (1, 3), (2, 4), (2, 5)]), 54 | ("line1", 55 | (2, 3), (8, 7), 56 | @[(2, 3), (3, 4), (4, 4), (5, 5), (6, 6), (7, 6), (8, 7)]), 57 | ("line2", 58 | (2, 1), (8, 5), 59 | @[(2, 1), (3, 2), (4, 2), (5, 3), (6, 4), (7, 4), (8, 5)]), 60 | ].mapIt: 61 | (name: it[0], posA: it[1], posB: it[2], path: it[3]) 62 | 63 | func swapCoordinates(inPos: Point): Point = 64 | (inPos.y, inPos.x) 65 | 66 | func swapCoordinates(path: openArray[Point]): seq[Point] = 67 | path.map(swapCoordinates) 68 | 69 | for tc in testCases: 70 | test tc.name: 71 | checkpoint("returns expected result") 72 | check drawBresenhamLine(tc.posA, tc.posB) == tc.path 73 | 74 | checkpoint("is symmetric") 75 | check drawBresenhamLine(tc.posB, tc.posA) == reversed(tc.path) 76 | 77 | checkpoint("returns expected result when coordinates are swapped") 78 | check drawBresenhamLine(swapCoordinates(tc.posA), 79 | swapCoordinates(tc.posB)) == swapCoordinates(tc.path) 80 | 81 | checkpoint("is symmetric when coordinates are swapped") 82 | check drawBresenhamLine(swapCoordinates(tc.posB), 83 | swapCoordinates(tc.posA)) == reversed(swapCoordinates(tc.path)) 84 | -------------------------------------------------------------------------------- /maths/extended_gcd.nim: -------------------------------------------------------------------------------- 1 | ## Extended Euclidean algorithm 2 | 3 | import std/math 4 | 5 | 6 | func updateCoefficients(t0, t1, q: int): (int, int) = 7 | (t1, t0 - q * t1) 8 | 9 | 10 | func euclidIteration(inA, inB: int): (Natural, int, int) = 11 | var (a, b) = (inA.abs().Natural, inB.abs().Natural) 12 | var (x0, x1) = (1, 0) 13 | var (y0, y1) = (0, 1) 14 | while b != 0: 15 | let q = int(a div b) 16 | (x0, x1) = updateCoefficients(x0, x1, q) 17 | (y0, y1) = updateCoefficients(y0, y1, q) 18 | (a, b) = (b, a mod b) 19 | 20 | (a, x0, y0) 21 | 22 | 23 | func extendedGCD*(a, b: int): tuple[gcd: Natural; x, y: int] = 24 | ## Implements the 25 | ## [Extended Euclidean algorithm](https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm). 26 | ## For given integers `a`, `b` it 27 | ## computes their [`gcd`](https://en.wikipedia.org/wiki/Greatest_common_divisor) 28 | ## and integers `x` and `y`, such that 29 | ## `gcd = a * x + b * y`, 30 | ## and furthermore: 31 | ## - if `a != 0`, then `abs(y) <= abs(a div gcd)`, 32 | ## - if `b != 0`, then `abs(x) <= abs(b div gcd)`. 33 | let (gcd, x, y) = euclidIteration(a, b) 34 | (gcd: gcd, x: math.sgn(a) * x, y: math.sgn(b) * y) 35 | 36 | 37 | when isMainModule: 38 | import std/[unittest, sequtils, strformat] 39 | suite "extendedGCD": 40 | const testCases = [ 41 | (10, 15, 5, -1, 1), 42 | (-10, -15, 5, 1, -1), 43 | (32, 64, 32, 1, 0), 44 | (0, 0, 0, 0, 0), 45 | (7, 0, 7, 1, 0), 46 | (-8, 0, 8, -1, 0), 47 | (48, 60, 12, -1, 1), 48 | (98, 56, 14, -1, 2), 49 | (10, -15, 5, -1, -1), 50 | (997, 12345, 1, -3467, 280), 51 | (997, 1234567, 1, -456926, 369), 52 | ].mapIt: 53 | let tc = (id: fmt"a={it[0]}, b={it[1]}", a: it[0], b: it[1], 54 | gcd: it[2].Natural, x: it[3], y: it[4]) 55 | if tc.gcd != 0: 56 | assert tc.a mod int(tc.gcd) == 0 57 | assert tc.b mod int(tc.gcd) == 0 58 | if tc.b != 0: 59 | assert abs(tc.x) <= abs(tc.b div int(tc.gcd)) 60 | else: 61 | assert abs(tc.x) == 1 62 | assert tc.y == 0 63 | if tc.a != 0: 64 | assert abs(tc.y) <= abs(tc.a div int(tc.gcd)) 65 | else: 66 | assert abs(tc.y) == 1 67 | assert tc.x == 0 68 | else: 69 | assert tc.a == 0 and tc.b == 0 70 | assert tc.x == 0 and tc.y == 0 71 | assert int(tc.gcd) == tc.a * tc.x + tc.b * tc.y 72 | tc 73 | 74 | for tc in testCases: 75 | test tc.id: 76 | checkpoint("returns expected result") 77 | check extendedGCD(tc.a, tc.b) == (tc.gcd, tc.x, tc.y) 78 | checkpoint("returns expected result when first argument negated") 79 | check extendedGCD(-tc.a, tc.b) == (tc.gcd, -tc.x, tc.y) 80 | checkpoint("returns expected result when second argument negated") 81 | check extendedGCD(tc.a, -tc.b) == (tc.gcd, tc.x, -tc.y) 82 | checkpoint("returns expected result when both arguments negated") 83 | check extendedGCD(-tc.a, -tc.b) == (tc.gcd, -tc.x, -tc.y) 84 | checkpoint("is symmetric") 85 | check extendedGCD(tc.b, tc.a) == (tc.gcd, tc.y, tc.x) 86 | -------------------------------------------------------------------------------- /dynamic_programming/levenshtein_distance.nim: -------------------------------------------------------------------------------- 1 | ## Levenshtein distance 2 | ## 3 | ## [Levenshtein distance](https://en.wikipedia.org/wiki/Levenshtein_distance) 4 | ## is an example of 5 | ## [edit distance](https://en.wikipedia.org/wiki/Edit_distance). 6 | 7 | import tables 8 | 9 | type Key = (Natural, Natural) 10 | 11 | 12 | func toKey(indA, indB: int): Key = 13 | return (indA.Natural, indB.Natural) 14 | 15 | 16 | func initLevenshteinDistanceMatrix(lenA, lenB: Natural): Table[Key, Natural] = 17 | # Partially init the distance matrix: 18 | # - Pre-fill with distances between all prefixes of `a` and an empty string 19 | for indA in 0.Natural..lenA: 20 | result[toKey(indA, 0)] = indA 21 | 22 | # - Pre-fill with distances between an empty string and all prefixes of `b` 23 | for indB in 1.Natural..lenB: 24 | result[toKey(0, indB)] = indB 25 | 26 | 27 | proc fillLevenshteinDistanceMatrix( 28 | distances: var Table[Key, Natural], 29 | a, b: string) = 30 | ## `distances[toKey(posA, posB)]` is the Levenshtein distance between 31 | ## prefix of `a` with length `posA` and prefix of `b` with length `posB` 32 | for indA in 0..= 0 77 | (name: it[0], a: it[1], b: it[2], distance: it[3].Natural) 78 | 79 | for tc in testCases: 80 | test tc.name: 81 | checkpoint("returns expected result") 82 | check levenshteinDistance(tc.a, tc.b) == tc.distance 83 | checkpoint("is symmetric") 84 | check levenshteinDistance(tc.b, tc.a) == tc.distance 85 | -------------------------------------------------------------------------------- /stable_marriage/README.md: -------------------------------------------------------------------------------- 1 | # Stable Marriage Problem 2 | > Also known as the Stable Matching Problem 3 | 4 | ## Problem overview 5 | The Stable Marriage Problem[^gs] involves finding a pairing between two equally sized sets of elements based on their preferences for each other. In the context of the Stable Marriage Problem, there are `n` men (contenders) and `n` women (receivers) with ranked preferences for members of the opposite group. The objective is to match men and women together so that there are no pairs in which **both** participants would prefer some other pairing over their current partners. 6 | 7 | Stable Matching has real-world applications in various domains such as assigning graduating medical students to hospitals, assigning users to servers in distributed Internet services, and market design. 8 | 9 | The theory of stable allocations and the practice of market design, which includes the Stable Matching problem, was recognised with the Nobel Prize in Economic Sciences in 2012, awarded to Alvin E. Roth and Lloyd S. Shapley. 10 | 11 | ## The Gale-Shapley Algorithm 12 | The [Gale-Shapley algorithm](gale_shapley.nim), proposed by David Gale and Lloyd Shapley, is a widely used solution for the Stable Marriage Problem. It guarantees a stable matching (marriage) for all participants and has a time complexity of `O(n^2)`, where `n` is the number of contenders or receivers (men or women). 13 | 14 | Key properties of the Gale-Shapley algorithm include: 15 | 16 | - Yielding the best matching for all contenders among all possible stable matchings, but the worst for all receivers. 17 | - Being truthful and group-strategy proof for contenders (no contender or their coalition can get a better outcome by misrepresenting their preferences). 18 | - Allowing receivers to potentially manipulate their preferences for a better match. 19 | 20 | The Algorithm guarantees that everyone gets matched and that the matches are stable (there's no other pairing for which both the contender and the receiver are more satisfied). 21 | 22 | ## Other algorithms 23 | 24 | While the Gale-Shapley algorithm is popular due to its efficiency and stability guarantees, other algorithms[^other] exist with different trade-offs in stability, optimality, and computational complexity. 25 | 26 | ### Vande Vate algorithm 27 | 28 | Vande Vate's algorithm[^rvv], also known as the Roth and Vande Vate (RVV) mechanism, is an alternative algorithm with the following differences, compared to Gale-Shapley: 29 | 30 | - Starts with an arbitrary initial assignment of contenders to receivers, not with an empty matching. 31 | - Allows introducing participants incrementally and let them iteratively reach a stable matching, making it an online algorithm[^online]. 32 | - Accommodates scenarios with changing preferences. 33 | 34 | ## Related Problems: 35 | Several related problems stem from the Stable Matching Problem, including stable matching with indifference, the stable roommates problem, the hospitals/residents problem, and matching with contracts. These variations address scenarios with slightly different sets of constraints, such as: ties in preferences, a single pool of participants, multiple residents and hospitals/colleges, and different contract terms. 36 | 37 | [^gs]: https://en.wikipedia.org/wiki/Stable_marriage_problem 38 | [^other]: https://arxiv.org/pdf/2007.07121 39 | [^rvv]: Alvin E Roth and John H Vande Vate. *Random paths to stability in two-sided matching.* Econometrica: Journal of the Econometric Society, pages 1475–1480, 1990 40 | [^online]: https://en.wikipedia.org/wiki/Online_algorithm 41 | -------------------------------------------------------------------------------- /searches/linear_search.nim: -------------------------------------------------------------------------------- 1 | ## Linear Search 2 | ## ============= 3 | ## Linear search is the simplest but least efficient searching algorithm 4 | ## to search for an element in an array. 5 | ## It examines each element until it finds a match, 6 | ## starting at the beginning of the data set toward the end. 7 | ## The search ends when the element is located or when the end of the array is reached. 8 | ## https://en.wikipedia.org/wiki/Linear_search 9 | ## 10 | ## Time Complexity: O(n) where n is the length of the array. 11 | ## Space Complexity in for-loop linear search: O(1) 12 | ## Space Complexity in recursive linear search: O(n) 13 | ## Notice that recursive algorithms are nice to write and provide elegant implementations, 14 | ## but they are impeded by call stack management. Whatever the problem we face, 15 | ## there will be as much memory requirement as the number of stack frames. 16 | ## Therefore the recursive linear search is less efficient than the for-loop-based one. 17 | 18 | runnableExamples: 19 | import std/options 20 | 21 | var arr1 = [0, 3, 1, 4, 5, 6] 22 | doAssert linearSearch(arr1, 5) == some(Natural(4)) 23 | doAssert recursiveLinearSearch(arr1, 5) == some(Natural(4)) 24 | 25 | var arr2 = ['0', 'c', 'a', 'u', '5', '7'] 26 | doAssert linearSearch(arr2, '5') == some(Natural(4)) 27 | doAssert recursiveLinearSearch(arr2, '5') == some(Natural(4)) 28 | 29 | var arr3 = [0, 3, 1, 4, 5, 6] 30 | doAssert linearSearch(arr3, 7) == none(Natural) 31 | doAssert recursiveLinearSearch(arr3, 7) == none(Natural) 32 | 33 | import std/options 34 | 35 | func linearSearch*[T](arr: openArray[T], key: T): Option[Natural] = 36 | ## Searches for the `key` in the array `arr` and returns its absolute index (counting from 0) 37 | ## in the array. 38 | ## .. Note:: For arrays indexed with a range type or an enum the returned value 39 | ## may not be consistent with the indexing of the initial array. 40 | for i, val in arr.pairs(): 41 | if val == key: 42 | return some(Natural(i)) 43 | none(Natural) # `key` not found 44 | 45 | func recursiveLinearSearch*[T]( 46 | arr: openArray[T], key: T, idx: Natural = Natural(0)): Option[Natural] = 47 | ## Searches for the `key` in `arr` and returns its absolute index (counting from 0) 48 | ## in the array. Search is performed in a recursive manner. 49 | ## .. Note:: For arrays indexed with a range type or an enum the returned value 50 | ## is not consistent with the indexing of the parameter array if `arr.low` is not 0. 51 | # Recursive calls replace the `for` loop in `linearSearch`. 52 | 53 | # `none(Natural)` is returned when the array is traversed completely 54 | # and no key is matched, or when `arr` is empty. 55 | if idx > arr.high: 56 | return none(Natural) 57 | if arr[idx] == key: 58 | return some(idx) 59 | recursiveLinearSearch(arr, key, idx + 1) 60 | 61 | 62 | when isMainModule: 63 | import unittest 64 | 65 | template checkLinearSearch[T](arr: openArray[T], key: T, 66 | expectedIdx: Option[Natural]): untyped = 67 | check linearSearch(arr, key) == expectedIdx 68 | check recursiveLinearSearch(arr, key) == expectedIdx 69 | 70 | suite "Linear search": 71 | test "Search in an empty array": 72 | var arr: array[0, int] 73 | checkLinearSearch(arr, 5, none(Natural)) 74 | 75 | test "Search in an int array matching with a valid value": 76 | var arr = [0, 3, 1, 4, 5, 6] 77 | checkLinearSearch(arr, 5, some(Natural(4))) 78 | 79 | test "Search in an int array for a missing value": 80 | var arr = [0, 3, 1, 4, 5, 6] 81 | checkLinearSearch(arr, 7, none(Natural)) 82 | 83 | test "Search in a char array matching with a char matching value": 84 | var arr = ['0', 'c', 'a', 'u', '5', '7'] 85 | checkLinearSearch(arr, '5', some(Natural(4))) 86 | 87 | test "Search in a string sequence matching with a string matching value": 88 | var arr = @["0", "c", "a", "u", "5", "7"] 89 | checkLinearSearch(arr, "5", some(Natural(4))) 90 | 91 | test "Search in an int array with a valid key at the end": 92 | var arr = [1, 5, 3, 6, 5, 7] 93 | checkLinearSearch(arr, 7, some(Natural(5))) 94 | -------------------------------------------------------------------------------- /maths/abs.nim: -------------------------------------------------------------------------------- 1 | ## Absolute value 2 | {.push raises: [].} 3 | import std/strutils 4 | 5 | runnableExamples: 6 | assert absVal(-5.1) == 5.1 7 | assert absMin(@[-1, 2, -3]) == 1 8 | assert absMax(@[-1, 2, -3]) == 3 9 | assert signedMinAbs(@[3, -10, -2]) == -2 10 | assert signedMaxAbs(@[3, -10, -2]) == -10 11 | 12 | func absVal*[T: SomeFloat](num: T): T = 13 | ## Returns the absolute value of a number. 14 | ## Use `math.abs `_ instead! 15 | return if num < 0.0: -num else: num 16 | 17 | # Same for Integers but returns a Natural 18 | func absVal*[T: SomeInteger](num: T): Natural = (if num < 0: -num else: num) 19 | 20 | func absMin*(x: openArray[int]): Natural {.raises: [ValueError].} = 21 | ## Returns the smallest element in absolute value in a sequence. 22 | if x.len == 0: 23 | raise newException(ValueError, """Cannot find absolute minimum 24 | of an empty sequence""".unindent) 25 | result = absVal(x[0]) 26 | for i in 1 ..< x.len: 27 | if absVal(x[i]) < result: 28 | result = absVal(x[i]) 29 | 30 | func absMax*(x: openArray[int]): Natural {.raises: [ValueError].} = 31 | ## Returns the largest element in absolute value in a sequence. 32 | if x.len == 0: 33 | raise newException(ValueError, """Cannot find absolute maximum of an empty 34 | sequence""".unindent) 35 | result = absVal(x[0]) 36 | for i in 1 ..< x.len: 37 | if absVal(x[i]) > result: 38 | result = absVal(x[i]) 39 | 40 | func signedMinAbs*(x: openArray[int]): int {.raises: [ValueError].} = 41 | ## Returns the first signed element whose absolute value 42 | ## is the smallest in a sequence. 43 | if x.len == 0: 44 | raise newException(ValueError, """Cannot find absolute maximum of an empty 45 | sequence""".unindent) 46 | var (min, minAbs) = (x[0], absVal(x[0])) 47 | for n in x: 48 | let nAbs = absVal(n) 49 | if nAbs < minAbs: (min, minAbs) = (n, nAbs) 50 | min 51 | 52 | func signedMaxAbs*(x: openArray[int]): int {.raises: [ValueError].} = 53 | ## Returns the first signed element whose absolute value 54 | ## is the largest in a sequence. 55 | if x.len == 0: 56 | raise newException(ValueError, """Cannot find absolute maximum of an empty 57 | sequence""".unindent) 58 | var (max, maxAbs) = (x[0], absVal(x[0])) 59 | for n in x: 60 | let nAbs = absVal(n) 61 | if nAbs > maxAbs: (max, maxAbs) = (n, nAbs) 62 | max 63 | 64 | when isMainModule: 65 | import std/[unittest, random] 66 | randomize() 67 | 68 | suite "Check absVal": 69 | test "Check absVal": 70 | check: 71 | absVal(11.2) == 11.2 72 | absVal(5) == 5 73 | absVal(-5.1) == 5.1 74 | absVal(-5) == absVal(5) 75 | absVal(0) == 0 76 | 77 | suite "Check absMin": 78 | test "Check absMin": 79 | check: 80 | absMin(@[-1, 2, -3]) == 1 81 | absMin(@[0, 5, 1, 11]) == 0 82 | absMin(@[3, -10, -2]) == 2 83 | absMin([-1, 2, -3]) == 1 84 | absMin([0, 5, 1, 11]) == 0 85 | absMin([3, -10, -2]) == 2 86 | 87 | test "absMin on empty sequence raises ValueError": 88 | doAssertRaises(ValueError): 89 | discard absMin(@[]) 90 | 91 | suite "Check absMax": 92 | test "Check absMax": 93 | check: 94 | absMax(@[0, 5, 1, 11]) == 11 95 | absMax(@[3, -10, -2]) == 10 96 | absMax(@[-1, 2, -3]) == 3 97 | 98 | test "`absMax` on empty sequence raises ValueError": 99 | doAssertRaises(ValueError): 100 | discard absMax(@[]) 101 | 102 | suite "Check signedMinAbs": 103 | test "Check signedMinAbs": 104 | check: 105 | signedMinAbs(@[0, 5, 1, 11]) == 0 106 | signedMinAbs(@[3, -2, 1, -4, 5, -6]) == 1 107 | signedMinAbs(@[3, -2, -1, -4, 5, -6]) == -1 108 | 109 | test "Among two minimal elements, the first one is returned": 110 | check signedMinAbs(@[3, -2, 1, -4, 5, -6, -1]) == 1 111 | 112 | suite "Check signedMaxAbs": 113 | test "Check signedMaxAbs": 114 | check: 115 | signedMaxAbs(@[3, -2, 1, -4, 5, -6]) == -6 116 | signedMaxAbs(@[0, 5, 1, 11]) == 11 117 | 118 | test "signedMaxAbs on empty sequence raises ValueError": 119 | doAssertRaises(ValueError): 120 | discard signedMaxAbs(@[]) 121 | -------------------------------------------------------------------------------- /.scripts/directory.nim: -------------------------------------------------------------------------------- 1 | ## Build the contents of `directory.md` for The Algorithms/Nim repository 2 | ## 3 | ## # Usage: 4 | ## * Navigate to repo's root directory 5 | ## * Execute and save the output: `nim r .scripts/directory.nim > DIRECTORY.md` 6 | ## * Check the changes: `git diff directory.md` 7 | ## 8 | ## # Overview: 9 | ## - Walks the current directory for subdirectories. 10 | ## - Each subdirectory (but not the root) is treated as a category. 11 | ## - The title of the category is inferred from the subdir's name. 12 | ## - Walks .nim source files in each subdirectory, non-recursively. 13 | ## - Looks for algorithms's title on the first line of the file when it's a 14 | ## doc comment, otherwise canonicalizes the file basename. 15 | ## - Prints the markdown header. 16 | ## - Prints the collected directory structure sorted alphabetically in markdown. 17 | 18 | import std/[os, unicode, critbits, options, strformat] 19 | from std/strutils import startsWith, Whitespace, splitLines, toLowerAscii 20 | from std/strbasics import strip 21 | 22 | const 23 | # unicodeSpaces from unicode_ranges in stdlib are not public 24 | WhitespaceUC = toRunes("_\t\n\x0b\x0c\r\x1c\x1d\x1e\x1f \x85\xa0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u2028\u2029\u202f\u205f\u3000") 25 | Warning = "\n" 26 | Header = "# The Algorithms — Nim: Directory Hierarchy\n" 27 | 28 | type 29 | ## TODO: Allow nested structure with variant type [Directory | CritBitTree[string]] 30 | Category = object 31 | readme: Option[string] # Path to category overview 32 | contents: CritBitTree[string] # Paths to algorithm implementations 33 | Directory = CritBitTree[Category] 34 | 35 | func canonicalize(s: string): string = 36 | ## Splits input by whitespace and underscore, titlecases each word and 37 | ## joins them in the result with spaces. Consecutive whitespace is merged. 38 | ## Slightly Unicode-aware. 39 | var isFirst = true 40 | for word in unicode.split(s, WhitespaceUC): 41 | if word.len > 0: 42 | if isFirst: isFirst = false 43 | else: result.add(' ') 44 | result.add(word.title()) 45 | 46 | proc extractTitle(s, fpath: string): Option[string] = 47 | ## Reads the title of the file from its first line if it's a doc comment. 48 | var s = s.strip() 49 | if s.startsWith("##"): 50 | s.strip(trailing = false, chars = Whitespace + {'#'}) 51 | if s.len > 0: some(s) 52 | else: none(string) 53 | else: 54 | stderr.writeLine(&"\u26A0: \"{fpath}\". First line is not a doc comment! Deriving title from the file name.") 55 | none(string) 56 | 57 | proc readLn(fpath: string): Option[string] = 58 | ## Tries its best to read the first line of the file 59 | var f: File = nil 60 | var s: string 61 | if open(f, fpath): 62 | try: 63 | if not readLine(f, s): none(string) 64 | else: s.extractTitle(fpath) 65 | except CatchableError: none(string) 66 | finally: close(f) 67 | else: none(string) 68 | 69 | proc collectDirectory(dir = ""): Directory = 70 | ## Walks the subdirectories of `dir` non-recursively, collects `.nim` files 71 | ## and their titles into a sorted structure. Dotfiles are skipped. 72 | for (pc, path) in walkDir(dir, relative = true): 73 | if pc == pcDir and path[0] != '.': 74 | var categoryDir: Category 75 | for (pc, fname) in walkDir(path, relative = true): 76 | if pc == pcFile and fname[0] != '.': 77 | let (_, name, ext) = splitFile(fname) 78 | let fpath = path / fname 79 | if ext == ".nim": 80 | # if can't read the title from the source, derive from the file name 81 | let title = readLn(fpath).get(name.canonicalize()) 82 | categoryDir.contents[title] = fname 83 | elif ext.toLowerAscii() in [".md", ".rst"] and 84 | name.toLowerAscii() == "readme": 85 | categoryDir.readme = some(path & '/' & fname) # note the hardcoded separator 86 | if categoryDir.contents.len > 0: 87 | result[path] = categoryDir 88 | 89 | when isMainModule: 90 | let directory = collectDirectory(getCurrentDir()) 91 | if directory.len > 0: 92 | echo Warning, "\n", Header 93 | for (categoryDir, category) in directory.pairs(): 94 | if category.contents.len > 0: 95 | let categoryName = categoryDir.canonicalize() 96 | let categoryHeader = if category.readme.isSome: 97 | &"## [{categoryName}]({category.readme.get()})" 98 | else: 99 | &"## {categoryName}" 100 | echo categoryHeader 101 | for (title, fname) in category.contents.pairs(): 102 | echo &" * [{title}]({categoryDir}/{fname})" # note the hardcoded separator 103 | echo "" 104 | -------------------------------------------------------------------------------- /searches/binary_search.nim: -------------------------------------------------------------------------------- 1 | ## Binary Search 2 | ## ============= 3 | ## Binary search is an efficient algorithm for searching sorted arrays, 4 | ## generally outperforming linear search except for small arrays. 5 | ## However, binary search requires the array to be pre-sorted. 6 | ## 7 | ## If the input is not already sorted, it is faster to use a linear search instead. 8 | ## Moreover, for more complex data structures like trees, it might be more 9 | ## suitable to use specialized tree-based search algorithms. 10 | ## 11 | ## Binary search starts by comparing the key value with the middle element of the array. 12 | ## If the key value matches the middle element, the search is complete. 13 | ## If the key value is less than the middle element, the search continues on the lower half of the array. 14 | ## If the key value is greater than the middle element, the search continues on the upper half of the array. 15 | ## This process repeats until the middle element matches the key value or the search space is exhausted. 16 | ## 17 | ## Best Time Complexity: O(1) when the key value is the middle element. 18 | ## Average and Worst Time Complexity: O(log n), where n is the length of the array. 19 | ## Space Complexity in iterative approach: O(1) 20 | ## Space Complexity in recursive approach: O(n) 21 | ## https://en.wikipedia.org/wiki/Binary_search_algorithm 22 | {.push raises: [].} 23 | 24 | runnableExamples: 25 | import std/options 26 | 27 | var arr1 = [0, 1, 2, 4, 5, 6] 28 | doAssert binarySearchIterative(arr1, 5) == some(Natural(4)) 29 | 30 | var arr2 = ['a', 'c', 'd', 'e', 'x', 'z'] 31 | doAssert binarySearchIterative(arr2, 'e') == some(Natural(3)) 32 | 33 | var arr3 = [0, 1, 2, 3, 4] 34 | doAssert binarySearchIterative(arr3, 7) == none(Natural) 35 | 36 | import std/options 37 | 38 | func binarySearchIterative*[T: Ordinal](arr: openArray[T], key: T): Option[Natural] = 39 | ## Binary search can only be applied to sorted arrays. 40 | ## For this function array should be sorted in ascending order. 41 | var 42 | left = arr.low 43 | right = arr.high 44 | 45 | while left <= right: 46 | # avoids overflow with large 'left' and 'right' values 47 | # compared to naive (left+right)/2 48 | let mid = left + (right - left) div 2 49 | 50 | if arr[mid] == key: 51 | return some(Natural(mid)) 52 | 53 | if key < arr[mid]: 54 | right = mid - 1 55 | else: 56 | left = mid + 1 57 | # `none(Natural)` is returned when both halves are empty after some iterations. 58 | none(Natural) 59 | 60 | func binarySearchRecursive[T: Ordinal]( 61 | arr: openArray[T], left, right: Natural, key: T): Option[Natural] = 62 | if left > right: return none(Natural) 63 | let 64 | mid = left + (right - left) div 2 65 | newLeft = mid + 1 66 | newRight = mid - 1 67 | 68 | return 69 | if arr[mid] == key: 70 | some(Natural(mid)) 71 | elif newRight < 0: 72 | none(Natural) 73 | elif key < arr[mid]: 74 | binarySearchRecursive(arr, left, newRight, key) 75 | else: 76 | binarySearchRecursive(arr, newLeft, right, key) 77 | 78 | func binarySearchRecursive*[T: Ordinal](arr: openArray[T], key: T): Option[Natural] = 79 | ## Recursive implementation of binary search for an array sorted in ascending order 80 | if arr.len < 1: return none(Natural) 81 | binarySearchRecursive(arr, 0, arr.high(), key) 82 | 83 | when isMainModule: 84 | import unittest 85 | 86 | const 87 | empty: array[0, int] = [] 88 | single = [1] 89 | arr = [0, 1, 2, 3, 5, 6] 90 | odd = [0, 1, 2, 3, 5] 91 | chars = ['0', '1', '2', '3', '5', 'a'] 92 | 93 | template checkBinarySearch[T: Ordinal]( 94 | arr: openArray[T], key: T, expected: Option[Natural]): untyped = 95 | check binarySearchIterative(arr, key) == expected 96 | check binarySearchRecursive(arr, key) == expected 97 | 98 | suite "Binary Search": 99 | test "Empty array": 100 | checkBinarySearch(empty, 5, none(Natural)) 101 | test "Matching value in a single entry array": 102 | checkBinarySearch(single, 1, some(Natural(0))) 103 | test "Missing value in a single entry array": 104 | checkBinarySearch(single, -1, none(Natural)) 105 | test "Matching value in an int array": 106 | checkBinarySearch(arr, 5, some(Natural(4))) 107 | test "Missing value less than first element": 108 | checkBinarySearch(arr, -1, none(Natural)) 109 | test "Missing value greater than last element": 110 | checkBinarySearch(arr, 7, none(Natural)) 111 | test "Missing value between first and last elements": 112 | checkBinarySearch(arr, 4, none(Natural)) 113 | test "Matching value in a char array": 114 | checkBinarySearch(chars, '5', some(Natural(4))) 115 | test "Matching key at the start": 116 | checkBinarySearch(arr, 0, some(Natural(0))) 117 | test "Matching key at the end": 118 | checkBinarySearch(arr, 6, some(Natural(5))) 119 | test "Even-length array with a matching key in the middle": 120 | checkBinarySearch(arr, 3, some(Natural(3))) 121 | test "Odd-length array with a matching key in the middle": 122 | checkBinarySearch(odd, 2, some(Natural(2))) 123 | -------------------------------------------------------------------------------- /dynamic_programming/catalan_numbers.nim: -------------------------------------------------------------------------------- 1 | ## Catalan Numbers 2 | #[ 3 | The Catalan numbers are a sequence of natural numbers that occur in the 4 | most large set of combinatorial problems. 5 | For example, it describes: 6 | - the number of ways to parenthesize a product of n factors 7 | - the number of ways to form a binary search tree with n+1 leaves 8 | - the number of Dyck paths of length 2n 9 | - the number of ways to triangulate a convex polygon with n+2 sides 10 | References: 11 | https://en.wikipedia.org/wiki/Catalan_number 12 | https://oeis.org/A000108 13 | ]# 14 | import std/math 15 | {.push raises: [].} 16 | 17 | func catalanNumbersRecursive(index: Natural): Positive = 18 | ## Returns the index-th Catalan number recursively. 19 | ## As Nim does not make automatic memoization, this function is not 20 | ## efficient. 21 | if index < 2: 22 | return 1 23 | var n: Natural = 0 24 | for i in 0 ..< index: 25 | n += catalanNumbersRecursive(i) * catalanNumbersRecursive(index - i - 1) 26 | n 27 | 28 | func catalanNumbersRecursive2(index: Natural): Positive = 29 | if index < 2: 30 | return 1 31 | else: 32 | 2 * (2 * index - 1) * catalanNumbersRecursive2(index - 1) div (index + 1) 33 | 34 | func catalanNumbers(indexLimit: Natural): seq[Positive] {.noinit.} = 35 | ## Returns all Catalan numbers up to the index-th number iteratively. 36 | result = newSeq[Positive](indexLimit) 37 | result[0] = 1 38 | for i in 1 ..< indexLimit: 39 | for j in 0 ..< i: 40 | result[i] += result[j] * result[i - j - 1] 41 | 42 | func catalanNumbers2(index: Natural): Positive = 43 | ## Returns the index-th Catalan number iteratively with a second formula. 44 | ## Due to the division, this formula overflows at the 30th Catalan number. 45 | binom(2 * index, index) div (index + 1) 46 | 47 | iterator catalanNumbersIt(index: Natural): Positive = 48 | ## Iterates over all Catalan numbers up to the index-th number. 49 | var catalanNumbers = newSeq[Positive](index) 50 | catalanNumbers[0] = 1 51 | for i in 0 ..< index: 52 | for j in 0 ..< i: 53 | catalanNumbers[i] += catalanNumbers[j] * catalanNumbers[i - j - 1] 54 | yield catalanNumbers[i] 55 | 56 | func createCatalanTable(index: static[Natural]): array[index, Positive] = 57 | ## Creates a table of Catalan numbers up to the index-th number. 58 | when index > 36: 59 | raise newException(OverflowDefect, "Index must be less or equal than 36") 60 | result[0] = 1 61 | for i in 1 ..< index: 62 | for j in 0 ..< i: 63 | result[i] += result[j] * result[i - j - 1] 64 | 65 | func catalanNumbersCompileTime(index: Natural): Positive = 66 | if index > 36: 67 | raise newException(OverflowDefect, "N must be less or equal than 36") 68 | when sizeof(int) == 2: 69 | const catalanTable = createCatalanTable(12) 70 | when sizeof(int) == 4: 71 | const catalanTable = createCatalanTable(20) 72 | when sizeof(int) == 8: 73 | const catalanTable = createCatalanTable(36) 74 | catalanTable[index-1] 75 | 76 | when isMainModule: 77 | import std/unittest 78 | 79 | const RecursiveLimit = 16 # The first recursive formula gets too slow above 16 80 | const LowerLimit = 30 # The formulas involving a division overflows above 30 81 | const UpperLimit = 36 # Other methods overflow above 36 82 | 83 | let expectedResult: seq[Positive] = @[Positive(1), 1, 2, 5, 14, 42, 132, 429, 84 | 1430, 4862, 16796, 58786, 208012, 85 | 742900, 2674440, 9694845] 86 | const CatalanNumbersList: seq[Positive] = @[Positive(1), 1, 2, 5, 14, 42, 132, 87 | 429, 1430, 4862, 16796, 88 | 58786, 208012, 742900, 2674440, 9694845, 35357670, 129644790, 477638700, 89 | 1767263190, 6564120420, 24466267020, 91482563640, 343059613650, 90 | 1289904147324, 4861946401452, 18367353072152, 69533550916004, 91 | 263747951750360, 1002242216651368, 3814986502092304, 14544636039226909, 92 | 55534064877048198, 212336130412243110, 812944042149730764, 93 | 3116285494907301262] 94 | 95 | static: 96 | for i in 0 ..< 36: 97 | doAssert (CatalanNumbersList[i] == createCatalanTable(36)[i]) 98 | 99 | suite "Catalan Numbers": 100 | test "The seventeen first Catalan numbers recursively, first formula": 101 | let limit = RecursiveLimit 102 | for index in 0 .. limit: 103 | let catalanNumber = catalanNumbersRecursive(index) 104 | check catalanNumber == CatalanNumbersList[index] 105 | 106 | test "The thirty-one first Catalan numbers recursively, second formula": 107 | let limit = LowerLimit 108 | for index in 0 .. limit: 109 | let catalanNumber = catalanNumbersRecursive2(index) 110 | check catalanNumber == CatalanNumbersList[index] 111 | 112 | test "The sixteen first Catalan numbers iteratively": 113 | check catalanNumbers(16) == expectedResult 114 | 115 | test "We can compute up to the thirty-seventh Catalan number iteratively": 116 | let limit = UpperLimit 117 | let catalanNumbersSeq = catalanNumbers(limit) 118 | for index in 0 ..< limit: 119 | check catalanNumbersSeq[index] == CatalanNumbersList[index] 120 | 121 | test "The thirty-seventh first Catalan numbers with iterator": 122 | let limit = UpperLimit 123 | var index = 0 124 | for catalanNumber in catalanNumbersIt(limit): 125 | check catalanNumber == CatalanNumbersList[index] 126 | inc index 127 | 128 | test "Test the catalan number function with binomials": 129 | let limit = LowerLimit 130 | for index in 0 .. limit: 131 | check catalanNumbers2(index) == CatalanNumbersList[index] 132 | 133 | test "The Catalan Numbers binomial formula overflows at 31": 134 | doAssertRaises(OverflowDefect): 135 | discard catalanNumbers2(LowerLimit + 1) 136 | 137 | test "Uses compile-time table": 138 | check catalanNumbersCompileTime(UpperLimit) == Positive(3116285494907301262) 139 | 140 | test "The compile-time table overflows at 37": 141 | doAssertRaises(OverflowDefect): 142 | discard catalanNumbersCompileTime(UpperLimit + 1) 143 | -------------------------------------------------------------------------------- /strings/check_anagram.nim: -------------------------------------------------------------------------------- 1 | ## Check Anagram 2 | ## ============= 3 | ## wiki: https://en.wikipedia.org/wiki/Anagram 4 | ## 5 | ## Two words are anagrams if: 6 | ## - They are made up of the same letters. 7 | ## - The letters are arranged differently. 8 | ## - The case of the characters in a word is ignored. 9 | ## - They are not the same word (a word is not an anagram of itself). 10 | ## 11 | ## A word is any string of characters `{'A'..'Z', 'a'..'z'}`. Other characters, 12 | ## including whitespace, numbers and punctuation, are considered invalid and 13 | ## raise a `ValueError` exception. 14 | ## 15 | ## Note: Generate full doc with `nim doc --docinternal check_anagram.nim` 16 | 17 | runnableExamples: 18 | 19 | doAssert "coder".isAnagram("credo") 20 | 21 | doAssert not "Nim".isAnagram("Nim") 22 | 23 | doAssert not "parrot".isAnagram("rapport") 24 | 25 | doAssertRaises(ValueError): discard "Pearl Jam".isAnagram("Maple Jar") 26 | 27 | ## Tests 28 | ## ----- 29 | ## This module includes test suites for each public function, which will run if 30 | ## the module is compiled as an executable. 31 | ## 32 | ## TODO 33 | ## ---- 34 | ## 35 | ## - Unicode version of the algorithm. 36 | ## - Check if the arguably more idiomatic solution using whole-word operations 37 | ## is beneficial or detrimental to the performance, compared to char-by-char 38 | ## approach of this module. 39 | 40 | {.push raises: [].} 41 | 42 | type 43 | Map = array[range['a'..'z'], Natural] ## An associative array with a direct 44 | ## mapping from Char to a counter slot. 45 | 46 | const 47 | UpperAlpha = {'A'..'Z'} 48 | LowerAlpha = {'a'..'z'} 49 | 50 | func toLowerUnchecked(c: char): char {.inline.} = 51 | ## Lowers the case of an ASCII uppercase letter ('A'..'Z'). 52 | ## No checks performed, the caller must ensure the input is a valid character. 53 | ## 54 | ## See also: 55 | ## * `strutils.toLowerAscii `_ 56 | assert c in UpperAlpha 57 | # The difference between numerical values for chars of uppercase and 58 | # lowercase alphabets in the ASCII table is 32. Uppercase letters start from 59 | # 0b100_0001, so setting the sixth bit to 1 gives letter's lowercase pair. 60 | char(uint8(c) or 0b0010_0000'u8) 61 | 62 | template normalizeChar(c: char): char = 63 | ## Checks if the character is a letter and lowers its case. 64 | ## 65 | ## Raises a `ValueError` on other characters. 66 | if c in LowerAlpha: c 67 | elif c in UpperAlpha: toLowerUnchecked(c) 68 | else: raise newException(ValueError, "Character '" & c & "' is not a letter!") 69 | 70 | func isAnagram*(wordA, wordB: openArray[char]): bool {.raises: ValueError.} = 71 | ## Checks if two words are anagrams of one another. 72 | ## 73 | ## Raises a `ValueError` on any non-letter character. 74 | if wordA.len != wordB.len: return false 75 | var seenDifferent = false 76 | var mapA, mapB: Map 77 | for chIdx in 0.. 1: 54 | result[1] = 1 55 | for i in 2.. 1: 98 | result[1] = 1 99 | let fib = makeFibClosure() 100 | for i in 2..= s.a: yield i 111 | else: 112 | swap(prev, current) 113 | current += prev 114 | if i >= s.a: yield current 115 | 116 | 117 | ## An asymptotic faster matrix algorithm 118 | ## --------------------------------------------------------------------------- 119 | 120 | func `*`(m1, m2: Matrix2x2): Matrix2x2 = 121 | let 122 | a = m1[0][0] * m2[0][0] + m1[0][1] * m2[1][0] 123 | b = m1[0][0] * m2[0][1] + m1[0][1] * m2[1][1] 124 | z = m1[1][0] * m2[0][0] + m1[1][1] * m2[1][0] 125 | y = m1[1][0] * m2[0][1] + m1[1][1] * m2[1][1] 126 | 127 | [[a, b], [z, y]] 128 | 129 | func pow(matrix: Matrix2x2, n: Natural): Matrix2x2 = 130 | ## Fast binary matrix exponentiation (divide-and-conquer approach) 131 | var matrix = matrix 132 | var n = n 133 | 134 | result = IdentityMatrix 135 | while n != 0: 136 | if n mod 2 == 1: 137 | result = result * matrix 138 | matrix = matrix * matrix 139 | n = n div 2 140 | 141 | func fibonacciMatrix*(nth: Natural): Natural = 142 | ## Calculates the n-th fibonacci number with use of matrix arithmetic. 143 | if nth <= 1: return nth 144 | var matrix = InitialFibonacciMatrix 145 | matrix.pow(nth - 1)[0][0] 146 | 147 | 148 | when isMainModule: 149 | import std/unittest 150 | import std/sequtils 151 | import std/sugar 152 | 153 | const 154 | GeneralMatrix = [[1, 2], [3, 4]] 155 | ExpectedMatrixPow4 = [[199, 290], [435, 634]] 156 | ExpectedMatrixPow5 = [[1069, 1558], [2337, 3406]] 157 | 158 | LowerNth: Natural = 0 159 | UpperNth: Natural = 31 160 | OverflowNth: Natural = 93 161 | 162 | Count = 32 163 | OverflowCount = 94 164 | 165 | HighSlice = Natural(32)..Natural(40) 166 | 167 | Expected = @[Natural(0), 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 168 | 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368, 169 | 75025, 121393, 196418, 317811, 514229, 832040, 1346269] 170 | ## F0 .. F31 171 | ExpectedHigh = @[Natural(2178309), 3524578, 5702887, 9227465, 14930352, 172 | 24157817, 39088169, 63245986, 102334155] 173 | ## F32 .. F40 174 | 175 | template checkFib(list: openArray[Natural]) = 176 | check list == Expected 177 | 178 | template checkFib(calcTerm: proc(n: Natural): Natural, 179 | range = LowerNth..UpperNth) = 180 | let list = collect(for i in range: calcTerm(i)) 181 | check list == Expected 182 | 183 | template checkFibOverflow(code: typed) = 184 | expect OverflowDefect: 185 | discard code 186 | 187 | suite "Matrix exponentiation": 188 | test "Matrix to the power of 0": 189 | check pow(GeneralMatrix, 0) == IdentityMatrix 190 | test "Matrix to the power of 1": 191 | check pow(GeneralMatrix, 1) == GeneralMatrix 192 | test "Matrix to the power of 4": 193 | check pow(GeneralMatrix, 4) == ExpectedMatrixPow4 194 | test "Matrix to the power of 5": 195 | check pow(GeneralMatrix, 5) == ExpectedMatrixPow5 196 | 197 | suite "Fibonacci Numbers": 198 | test "F0..F31 - Recursive Version": 199 | checkFib(fibonacciRecursive) 200 | test "F0..F31 - Closure Version": 201 | checkFib(fibonacciClosure) 202 | test "F0..F31 - Matrix Version": 203 | checkFib(fibonacciMatrix) 204 | test "F0..F31 - Iterative Sequence Version": 205 | checkFib(fibonacciSeqIterative(Count)) 206 | test "F0..F31 - Closure Sequence Version": 207 | checkFib(fibonacciSeqClosure(Count)) 208 | test "F0..F31 - Closed-form Approximation": 209 | checkFib(fibonacciClosedFormApproximation) 210 | test "F0..F31 - Nim Iterator": 211 | checkFib(fibonacciIterator(LowerNth..UpperNth).toSeq) 212 | 213 | test "Closed-form approximation fails when nth >= 32": 214 | let list = collect(for i in HighSlice: fibonacciClosedFormApproximation(i)) 215 | check list != ExpectedHigh 216 | 217 | #test "Recursive procedure overflows when nth >= 93": # too slow at this point 218 | # checkFibOverflow(fibonacciRecursive(OverflowNth)) 219 | test "Closure procedure overflows when nth >= 93": 220 | checkFibOverflow(fibonacciClosure(OverflowNth)) 221 | test "Matrix procedure overflows when nth >= 93": 222 | checkFibOverflow(fibonacciMatrix(OverflowNth)) 223 | test "Iterative Sequence function overflows when n >= 94": 224 | checkFibOverflow(fibonacciSeqIterative(OverflowCount)) 225 | test "Closure Sequence procedure overflows when n >= 94": 226 | checkFibOverflow(fibonacciSeqClosure(OverflowCount)) 227 | test "Nim Iterator overflows when one or both slice indexes >= 93": 228 | checkFibOverflow(fibonacciIterator(LowerNth..OverflowNth).toSeq()) 229 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing guidelines 2 | 3 | Welcome to [TheAlgorithms/Nim](https://github.com/TheAlgorithms/Nim)! 4 | 5 | We are very happy that you are considering working on algorithm and data structure implementations in Nim! This repository is meant to be referenced and used by learners from all over the globe, and we aspire to maintain the highest possible quality of the code presented here. This is why we ask you to **read all of the following guidelines beforehand** to know what we expect of your contributions. If you have any doubts about this guide, please feel free to [state them clearly in an issue](https://github.com/TheAlgorithms/Nim/issues/new) or ask the community in [Discord](https://the-algorithms.com/discord). 6 | 7 | ## Table Of Contents 8 | 9 | * [What is an Algorithm?](#what-is-an-algorithm) 10 | * [Contributor agreement](#contributor-agreement) 11 | * [Contribution guidelines](#contribution-guidelines) 12 | + [Implementation requirements](#implementation-requirements) 13 | + [Nim Coding Style](#nim-coding-style) 14 | - [Readability and naming conventions](#readability-and-naming-conventions) 15 | - [Compilation](#compilation) 16 | - [Types](#types) 17 | - [Result return](#result-return) 18 | - [Exceptions and side-effects](#exceptions-and-side-effects) 19 | - [Documentation, examples and tests](#documentation-examples-and-tests) 20 | - [Other](#other) 21 | + [Minimal example](#minimal-example) 22 | + [Submissions Requirements](#submissions-requirements) 23 | 24 | ## What is an Algorithm? 25 | 26 | An Algorithm is one or more functions that: 27 | 28 | - take one or more inputs, 29 | - perform some internal calculations or data manipulations, 30 | - return one or more outputs, 31 | - have minimal side effects (Examples of side effects: `echo()`, `rand()`, `read()`, `write()`). 32 | 33 | ## Contributor agreement 34 | 35 | Being one of our contributors, you agree and confirm that: 36 | 37 | - Your work will be distributed under [MIT License](LICENSE.md) once your pull request is merged. 38 | - Your work meets the standards of this guideline. 39 | 40 | ## Contribution guidelines 41 | 42 | We appreciate any contribution, from fixing a grammar mistake in a comment to implementing complex algorithms. Please check the [directory](DIRECTORY.md) and [issues](https://github.com/TheAlgorithms/Nim/issues/) for an existing (or declined) implementation of your algorithm and relevant discussions. 43 | 44 | **New implementations** are welcome! This includes new solutions for a problem, different representations for a data structure, and algorithm design with different complexity or features. 45 | 46 | **Improving documentation and comments** and **adding tests** is also highly welcome. 47 | 48 | **Identical implementations** are not allowed. 49 | 50 | ### Implementation requirements 51 | 52 | - The unit of an implementation we expect is a [**Nim module**](https://nim-lang.org/docs/manual.html#modules). Although the main goals of this repository are educational, the module form mirrors a real-world scenario and makes it easy to use the code from this repository in other projects. 53 | - The first line must contain the canonical title of the module prefixed by double hashes (`## Title Of The Module`). This title is used in this repository's automation for populating the [Directory](DIRECTORY.md). 54 | - The module should be thoroughly documented with doc comments. Follow the [Nim documentation style](https://nim-lang.org/docs/docstyle.html). 55 | - The file begins with the module-level documentation with the general description and explanation of the algorithm/data structure. If possible, please include: 56 | * Any restrictions of the implementation and any constraints for the input data. 57 | * An overview of use cases. 58 | * Recommendations for when to use or avoid using it. 59 | * Comparison with the alternatives. 60 | * Links to source materials and further reading. 61 | - Use intuitive and descriptive names for objects, functions, and variables. 62 | - Return all calculation results instead of printing or plotting them. 63 | - This repository is not simply a compilation of *how-to* examples for existing Nim packages and routines. Each algorithm implementation should add unique value. It is fine to leverage the standard library or third-party packages as long as it doesn't substitute writing the algorithm itself. In other words, you don't need to reimplement a basic hash table ([`std/tables`](https://nim-lang.org/docs/tables.html)) each time you need to use it, unless it is the goal of the module. 64 | - Avoid importing third-party libraries. Only use those for complicated algorithms and only if the alternatives of relying on the standard library or including a short amount of the appropriately licensed external code are not feasible. 65 | - Include tests that cover valid and erroneous input values and the appropriate edge-cases. See [Documentation, examples and tests](#documentation-examples-and-tests) below. 66 | 67 | ### Nim Coding Style 68 | 69 | #### Readability and naming conventions 70 | 71 | We want your work to be readable by others; therefore, we encourage you to follow the official [Nim Coding Style](https://nim-lang.org/docs/nep1.html). 72 | 73 | - Help your readers by using **descriptive names** that eliminate the need for redundant comments. 74 | - Follow Nim naming conventions: camelCase for variables and functions, PascalCase for types and objects, PascalCase or UPPERCASE for constants. 75 | - Avoid single-letter variable names, unless their lifespan is minimal. If your variable comes from a mathematical context or no confusion is possible with another variable, you may use single-letter variables. Generally, single-letter variables stop being OK if there's more than just a couple of them in a scope. Some examples: 76 | * Prefer `index` or `idx` to `i` for loops. 77 | * Prefer `src` and `dst` to `a` and `b`. 78 | * Prefer `remainder` to `r` and `prefix` to `p`. 79 | - Expand acronyms. For instance, use `greatestCommonDivisor()` rather than `gcd()` for better clarity, especially for non-native English speakers. 80 | 81 | #### Compilation 82 | 83 | - The code should successfully compile using the stable version of the Nim compiler. It's a good idea to check compilation against the development version as well for future-proofing. 84 | 85 | #### Types 86 | 87 | - Use the strictest suitable types for input, output and object fields. Prefer `Natural`, `Positive` or custom [subrange types](https://nim-lang.org/docs/manual.html#types-subrange-types) over unconstrained `int` where applicable, use `Natural` for indexing. 88 | - On the other hand, write generic code where appropriate. Do not impose arbitrary limitations if the code can work on a wider range of data types. 89 | - Don't use unsigned numerical types (`uint` and its sized variants), unless wrapping behaviour or binary manipulation is required for the algorithm. 90 | - Prefer the [`Option[T]`](https://nim-lang.org/docs/options.html) to encode an [optional value](https://en.wikipedia.org/wiki/Option_type) instead of using an invalid value (like the `-1` or an empty string `""`), unless it is critical for the algorithm. It may be also fitting if you are looking for the equivalent of "NULL" (default value for pointers)[^null]. 91 | 92 | #### Result return 93 | 94 | - Prefer the expression-based return over using the implicitly declared `result` variable[^result]. 95 | - Use `return` keyword only for changing the control-flow of a function. Minimize such cases. 96 | 97 | #### Exceptions and side-effects 98 | 99 | - Raise Nim exceptions (`ValueError`, etc.) on erroneous input values. 100 | - Use [exception tracking](https://nim-lang.org/docs/manual.html#effect-system-exception-tracking). Right after the module-level documentation, add a `{.push raises: [].}` module pragma. This enforces that all `func`s do not raise any exceptions. If they do raise at least one, list them all with the `raises` pragma after the return type and before the `=` sign like this: `func foo(bar: int) {.raises: [IOError].} =`. 101 | 102 | #### Documentation, examples and tests 103 | 104 | - Consider including a usage example after the module documentation and the `push raises` pragma in the top-level `runnableExamples` block. 105 | - Use the [`std/unittest` module](https://nim-lang.org/docs/unittest.html) to test your code. 106 | - We recommend using a `when isMainModule:` block to run tests. This block runs when the module is compiled as an executable. See the [minimal example](#minimal-example). 107 | 108 | #### Other 109 | 110 | - If you need a third-party module not listed in [third_party_modules.md](https://github.com/TheAlgorithms/Nim/blob/master/third_party_modules.md), please add it to that file as part of your submission. 111 | - Use the export marker `*` to distinguish the functions of your user-facing [application programming interface (API)](https://en.wikipedia.org/wiki/API) from internal helper functions. 112 | 113 | ### Minimal example 114 | 115 | ```nim 116 | ## My Algorithm 117 | ## 118 | ## Description, explanation, recommendations, sources, links. 119 | {.push raises: [].} 120 | 121 | runnableExamples: 122 | echo myFunction("bla") 123 | 124 | func myFunction*(s: string): string {.raises: [ValueError].} = 125 | ## Function documentation 126 | if s.len == 0: 127 | raise newException(ValueError, "Empty string") 128 | return s 129 | 130 | when isMainModule: 131 | import std/unittest 132 | 133 | suite "A suite of tests": 134 | test "runs correctly": 135 | check myFunction("bla") == "bla" 136 | test "raises ValueError": 137 | expect(ValueError): discard myFunction("") 138 | 139 | ``` 140 | 141 | ### Submissions Requirements 142 | 143 | - Make sure the code [compiles](#compilation) before submitting. 144 | - Look up the name of your algorithm in other active repositories of [TheAlgorithms](https://github.com/TheAlgorithms/), like [TheAlgorithms/Python](https://github.com/TheAlgorithms/Python). By reusing the same name, your implementation will be appropriately grouped alongside other implementations on the [project's website](https://the-algorithms.com/). 145 | - Please help us keep our issue list small by adding fixes: Reference the number of the issue you solved — even if only partially — ino the commit message of your pull request. 146 | - Use *snake_case* (words separated with an underscore `_`) for the filename. 147 | - Try to fit your work into the existing directory structure as much as possible. If you want to create a new subdirectory, please open an issue first. 148 | - Writing documentation, be concise and verify your spelling and grammar. 149 | - Optionally but recommended, provide an explanation in [Algorithms-Explanation](https://github.com/TheAlgorithms/Algorithms-Explanation). 150 | - Most importantly, **be consistent in adhering to these guidelines**. 151 | 152 | **Happy coding!** 153 | 154 | --- 155 | 156 | Authors: [@dlesnoff](https://github.com/dlesnoff), [@Zoom](https://github.com/ZoomRmc). 157 | 158 | [^null]: If you are wondering why it's preferable to avoid Null references, you should check [Tony Hoare's report at the QCon 2009 conference](https://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare/). 159 | [^result]: Refer to comparison of different ways of returning results in [The Status Nim style guide](https://status-im.github.io/nim-style-guide/language.result.html). 160 | -------------------------------------------------------------------------------- /stable_marriage/gale_shapley.nim: -------------------------------------------------------------------------------- 1 | ## Gale-Shapley 2 | ## ============ 3 | ## 4 | ## This module implements the classical Gale-Shapley_ algorithm for solving 5 | ## the stable matching problem. 6 | ## 7 | ## Algorithm features 8 | ## ------------------ 9 | ## - Time complexity of `O(n^2)`, where `n` is the number of contenders or 10 | ## receivers (men or women). 11 | ## - Guarantees a stable matching (marriage) for all participants. 12 | ## - Best matching for all contenders (neb) among all possible stable matchings. 13 | ## - The worst matching for all receivers (women). 14 | ## - Being truthful and `resistant to group-strategy`_ 15 | ## for contenders, meaning no contender or their coalition can achieve a 16 | ## uniformly better outcome by misrepresenting their preferences. 17 | ## - Allowing receivers to manipulate their preferences for a better match. 18 | ## 19 | ## Implementation details 20 | ## ---------------------- 21 | ## The implementation uses only an array-based table of preferences for each 22 | ## of the participants of the matching. Preprocessing this look-up table for one 23 | ## of the sides (here: for recipients) by 24 | ## `inverting<#invertPrefs,array[N,array[N,int]]>`_ and then double-booking the 25 | ## preferences into a corresponding sequence for each side allows 26 | ## the main algorithm to rely only on direct access without linear searches 27 | ## (except a single use of `contains`), hash tables or other 28 | ## associative data structures. 29 | ## 30 | ## Usage example 31 | ## ------------- 32 | ## The following example uses the implemented algorithm to solve the task of 33 | ## stable matching as posed by RosettaCode_: 34 | ## 35 | ## * Finds a stable set of matches ("engagements"). 36 | ## * Randomly destabilizes the matches and checks the results for stability. 37 | ## 38 | ## Example starts with a discarded string containing a possible output: 39 | ## 40 | ## .. _Gale-Shapley: https://en.wikipedia.org/wiki/Gale%E2%80%93Shapley_algorithm 41 | ## .. _RosettaCode: https://rosettacode.org/wiki/Stable_marriage_problem 42 | ## .. _resistant to group-strategy: https://en.wikipedia.org/wiki/Strategyproofness 43 | ## 44 | runnableExamples: 45 | discard """ 46 | abe 💑 ivy, bob 💑 cath, col 💑 dee, dan 💑 fay, ed 💑 jan, 47 | fred 💑 bea, gav 💑 gay, hal 💑 eve, ian 💑 hope, jon 💑 abi 48 | Current matching stability: ✓ Stable 49 | Swapping matches for random contenders: bob <=> gav 50 | abe 💑 ivy, bob 💑 gay, col 💑 dee, dan 💑 fay, ed 💑 jan, 51 | fred 💑 bea, gav 💑 cath, hal 💑 eve, ian 💑 hope, jon 💑 abi 52 | Current matching stability: ✗ Unstable 53 | 💔 bob prefers cath over gay 54 | 💔 cath prefers bob over gav 55 | """ 56 | 57 | import std/[random, strutils, sequtils, strformat, options] 58 | 59 | const 60 | MNames = ["abe", "bob", "col", "dan", "ed", "fred", "gav", "hal", "ian", "jon"] 61 | FNames = ["abi", "bea", "cath", "dee", "eve", "fay", "gay", "hope", "ivy", "jan"] 62 | MPreferences = [ 63 | ["abi", "eve", "cath", "ivy", "jan", "dee", "fay", "bea", "hope", "gay"], 64 | ["cath", "hope", "abi", "dee", "eve", "fay", "bea", "jan", "ivy", "gay"], 65 | ["hope", "eve", "abi", "dee", "bea", "fay", "ivy", "gay", "cath", "jan"], 66 | ["ivy", "fay", "dee", "gay", "hope", "eve", "jan", "bea", "cath", "abi"], 67 | ["jan", "dee", "bea", "cath", "fay", "eve", "abi", "ivy", "hope", "gay"], 68 | ["bea", "abi", "dee", "gay", "eve", "ivy", "cath", "jan", "hope", "fay"], 69 | ["gay", "eve", "ivy", "bea", "cath", "abi", "dee", "hope", "jan", "fay"], 70 | ["abi", "eve", "hope", "fay", "ivy", "cath", "jan", "bea", "gay", "dee"], 71 | ["hope", "cath", "dee", "gay", "bea", "abi", "fay", "ivy", "jan", "eve"], 72 | ["abi", "fay", "jan", "gay", "eve", "bea", "dee", "cath", "ivy", "hope"] 73 | ] 74 | FPreferences = [ 75 | ["bob", "fred", "jon", "gav", "ian", "abe", "dan", "ed", "col", "hal"], 76 | ["bob", "abe", "col", "fred", "gav", "dan", "ian", "ed", "jon", "hal"], 77 | ["fred", "bob", "ed", "gav", "hal", "col", "ian", "abe", "dan", "jon"], 78 | ["fred", "jon", "col", "abe", "ian", "hal", "gav", "dan", "bob", "ed"], 79 | ["jon", "hal", "fred", "dan", "abe", "gav", "col", "ed", "ian", "bob"], 80 | ["bob", "abe", "ed", "ian", "jon", "dan", "fred", "gav", "col", "hal"], 81 | ["jon", "gav", "hal", "fred", "bob", "abe", "col", "ed", "dan", "ian"], 82 | ["gav", "jon", "bob", "abe", "ian", "dan", "hal", "ed", "col", "fred"], 83 | ["ian", "col", "hal", "gav", "fred", "bob", "abe", "ed", "jon", "dan"], 84 | ["ed", "hal", "gav", "abe", "bob", "jon", "col", "ian", "fred", "dan"] 85 | ] 86 | # Recipient ids in descending order of preference 87 | ContenderPrefs = initContenderPrefs(MPreferences, FNames) 88 | # Preference score for each contender's id 89 | RecipientPrefs = initRecipientPrefs(FPreferences, MNames) 90 | 91 | proc randomPair(max: Positive): (Natural, Natural) = 92 | ## Returns a random fair pair of non-equal numbers up to and including `max` 93 | let a = rand(max) 94 | var b = rand(max - 1) 95 | if b == a: b = max 96 | (a.Natural, b.Natural) 97 | 98 | proc perturbPairs(ms: var Matches) = 99 | randomize() 100 | let (a, b) = randomPair(ms.contenderMatches.len - 1) 101 | echo "Swapping matches between random contenders: ", 102 | MNames[a], " <=> ", MNames[b] 103 | template swap(arr: var openArray[int]; a, b: int) = swap(arr[a], arr[b]) 104 | ms.contenderMatches.swap(a, b) 105 | ms.recipientMatches.swap(ms.contenderMatches[a], ms.contenderMatches[b]) 106 | 107 | func str(c: Clash; aNames, bNames: openArray[string]): string = 108 | &"\u{0001F494} {aNames[c.id]} prefers {bNames[c.prefers]} over {bNames[c.match]}" 109 | 110 | proc checkPairStability(matches: Matches; recipientPrefs, contenderPrefs: 111 | openArray[Ranking]): bool = 112 | let clashes = checkMatchingStability(matches, contenderPrefs, recipientPrefs) 113 | if clashes.isSome(): 114 | let (clC, clR) = clashes.get() 115 | echo "\u2717 Unstable\n", clC.str(MNames, FNames), '\n', clR.str(FNames, MNames) 116 | false 117 | else: 118 | echo "\u2713 Stable" 119 | true 120 | 121 | proc render(contMatches: seq[int]; cNames, rNames: openArray[ 122 | string]): string = 123 | for c, r in pairs(contMatches): 124 | result.add(cNames[c] & " \u{0001F491} " & rNames[r]) 125 | if c < contMatches.high: result.add(", ") 126 | 127 | var matches = stableMatching(ContenderPrefs, RecipientPrefs) 128 | 129 | template checkStabilityAndLog(matches: Matches; perturb: bool = false): bool = 130 | if perturb: perturbPairs(matches) 131 | echo render(matches.contenderMatches, MNames, FNames) 132 | stdout.write "Current matching stability: " 133 | checkPairStability(matches, RecipientPrefs, ContenderPrefs) 134 | 135 | doAssert matches.checkStabilityAndLog() == true 136 | doAssert matches.checkStabilityAndLog(perturb = true) == false 137 | 138 | #=============================================================================== 139 | {.push raises: [].} 140 | 141 | import std/options 142 | from std/sequtils import newSeqWith 143 | 144 | type 145 | Ranking*[T: int] = concept c ## A wide concept allowing `stableMatching` 146 | ## to accept both arrays and sequences in an openArray. 147 | c.len is Ordinal 148 | c[int] is T 149 | 150 | Matches* = object ## An object to keep the calculated matches. 151 | ## Both fields hold the same information from opposite points of view, 152 | ## providing a way to look up matching in 0(1) (without linear search). 153 | contenderMatches*: seq[int] ## Matched recipients for each contender 154 | recipientMatches*: seq[int] ## Matched contenders for each recipient 155 | 156 | Clash* = tuple[id, match, prefers: Natural] 157 | 158 | # Helper functions to prepare the numerical representation of preferences from 159 | # an array of arrays of names in order of descending preference. 160 | func initContenderPrefs*[N: static int](prefs: array[N, array[N, string]]; 161 | rNames: openArray[string]): array[N, array[N, int]] {.compileTime.} = 162 | ## Contender's preferences hold the recipient ids in descending order 163 | ## of preference. 164 | for c, ranking in pairs(prefs): 165 | for rank, recipient in pairs(ranking): 166 | assert recipient in ranking 167 | result[c][rank] = rNames.find(recipient) 168 | 169 | func initRecipientPrefs*[N: static int](prefs: array[N, array[N, string]]; 170 | cNames: openArray[string]): array[N, array[N, int]] {.compileTime.} = 171 | ## Recipient's preferences hold the preference score for each contender's id. 172 | for r, ranking in pairs(prefs): 173 | for rank, contender in pairs(ranking): 174 | assert contender in ranking 175 | result[r][cNames.find(contender)] = rank 176 | 177 | func invertPrefs*[N: static int](prefs: array[N, array[N, int]]): 178 | array[N, array[N, int]] = 179 | ## Converts each element of `prefs` from Ids in order of decreasing 180 | ## preference to ranking score for each Id. 181 | ## Used to convert from format used for Contenders to one used for Recipients. 182 | for rId, ranking in prefs.pairs(): 183 | for rank, id in ranking.pairs(): 184 | result[rId][id] = rank 185 | 186 | 187 | func stableMatching*(contenderPrefs, recipientPrefs: openArray[ 188 | Ranking]): Matches = 189 | ## Calculates a stable matching for a given set of preferences. 190 | ## Returns an object with corresponding match ids 191 | ## for each contender and each recipient. 192 | ## 193 | ## Each element of the argument arrays is an array of ints, meaning slightly 194 | ## different things: 195 | ## * `contenderPrefs`: recipient ids in descending order of preference 196 | ## * `recipientPrefs`: preference score for each contender's id 197 | ## 198 | assert recipientPrefs.len == recipientPrefs[0].len 199 | assert contenderPrefs.len == contenderPrefs[0].len 200 | assert recipientPrefs.len == contenderPrefs.len 201 | let rosterLen = recipientPrefs.len 202 | var 203 | # Initializing result sequences with -1, meaning "unmatched" 204 | recMatches = newSeqWith(rosterLen, -1) 205 | contMatches = newSeqWith(rosterLen, -1) 206 | # Queue holding the currently considered "preference score" 207 | # (idx of contenderPrefs) for each contender. 208 | contQueue = newSeqWith(rosterLen, 0) 209 | template match(c, r: Natural) = 210 | # Recipient accepts contender, the match is stored 211 | contMatches[c] = r 212 | recMatches[r] = c 213 | while contMatches.contains(-1): # While exist unmatched contenders... 214 | for c in 0..) than contender's score 245 | if recipientPrefs[checkedRec][checkedRival] > recipientPrefs[checkedRec][c]: 246 | let clashC = (id: c.Natural, match: checkedRec.Natural, 247 | prefers: curMatch.Natural) 248 | let clashR = (id: checkedRec.Natural, match: c.Natural, 249 | prefers: checkedRival.Natural) 250 | return some((clashC, clashR)) 251 | none((Clash, Clash)) 252 | 253 | 254 | when isMainModule: 255 | import std/unittest 256 | 257 | suite "Stable Matching": 258 | test "RosettaCode": 259 | const 260 | MNames = ["abe", "bob", "col"] 261 | FNames = ["abi", "bea", "cath"] 262 | MPreferences = [ 263 | ["abi", "cath", "bea"], 264 | ["cath", "abi", "bea"], 265 | ["abi", "bea", "cath"]] 266 | FPreferences = [ 267 | ["bob", "abe", "col"], 268 | ["bob", "abe", "col"], 269 | ["bob", "col", "abe"]] 270 | ContenderPrefs = initContenderPrefs(MPreferences, FNames) 271 | RecipientPrefs = initRecipientPrefs(FPreferences, MNames) 272 | 273 | func isStable(matches: Matches; 274 | contenderPrefs, recipientPrefs: openArray[Ranking]): bool = 275 | let c = checkMatchingStability(matches, contenderPrefs, recipientPrefs) 276 | c.isNone() 277 | 278 | let matches = stableMatching(ContenderPrefs, RecipientPrefs) 279 | # abe+abi, bob+cath, col+bea 280 | check matches.contenderMatches == @[0, 2, 1] 281 | check matches.recipientMatches == @[0, 2, 1] 282 | check isStable(matches, ContenderPrefs, RecipientPrefs) 283 | 284 | test "TheAlgorithms/Python": 285 | const DonorPrefs = [[0, 1, 3, 2], [0, 2, 3, 1], [1, 0, 2, 3], [0, 3, 1, 2]] 286 | const RecipientRrefs = invertPrefs([[3, 1, 2, 0], [3, 1, 0, 2], 287 | [0, 3, 1, 2], [1, 0, 3, 2]]) 288 | let matches = stableMatching(DonorPrefs, RecipientRrefs) 289 | check matches.contenderMatches == @[1, 2, 3, 0] 290 | check matches.recipientMatches == @[3, 0, 1, 2] 291 | 292 | test "Defect: mismatched number of participants": 293 | const ContenderPrefs = @[@[0, 1, 2], @[0, 2, 1], @[1, 0, 2], @[0, 1, 2]] 294 | const RecipientRrefs = @[@[1, 0], @[1, 0], @[0, 1], @[1, 0]] 295 | expect(AssertionDefect): 296 | discard stableMatching(ContenderPrefs, RecipientRrefs) 297 | --------------------------------------------------------------------------------