From 9c67ee67c3797d265c8399e364dd548ae07e68da Mon Sep 17 00:00:00 2001 From: Juri Date: Fri, 20 Jan 2023 22:09:13 +0200 Subject: [PATCH 1/8] Switched publishing from MyGet to GitHub, updated actions --- .github/workflows/build.yml | 4 +-- .github/workflows/publish_ci.yml | 37 ++++++++++++++------ .github/workflows/publish_release.yml | 50 ++++++++++++++++----------- 3 files changed, 57 insertions(+), 34 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a45ad74..c612c01 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,9 +12,9 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Setup .NET Core - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v3 - name: Build with dotnet run: | DOTNET_CLI_TELEMETRY_OPTOUT=1 diff --git a/.github/workflows/publish_ci.yml b/.github/workflows/publish_ci.yml index b63bbb6..7f7b918 100644 --- a/.github/workflows/publish_ci.yml +++ b/.github/workflows/publish_ci.yml @@ -1,4 +1,4 @@ -name: publish to MyGet +name: publish to GitHub on: push: @@ -6,20 +6,35 @@ on: - develop jobs: - build: + publish: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Setup .NET Core - uses: actions/setup-dotnet@v1 - - name: Build with dotnet + - name: Checkout + uses: actions/checkout@v3 + + - name: Set Build Version + run: | + $File = ( + Select-Xml -XPath "/Project/PropertyGroup/Version" -Path "src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj" + )[0].Node + $version = "$($File.InnerText)-ci-$Env:GITHUB_RUN_ID" + $File.InnerText = $version + $File.OwnerDocument.Save((Join-Path $PWD.ProviderPath src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj)) + echo "VERSION=$version" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append + shell: pwsh + + - name: Install .NET Core + uses: actions/setup-dotnet@v3 + + - name: Add the GitHub source + run: dotnet nuget add source --username USERNAME --password ${{secrets.GITHUB_TOKEN}} --store-password-in-clear-text --name github "https://nuget.pkg.github.com/fsprojects/index.json" + + - name: Pack FSharp.Collections.Immutable project run: | - sed -i "s|\(.*\)|\1-ci-$GITHUB_RUN_ID|" src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj dotnet pack --nologo --configuration Release -o nuget - - name: MyGet push + + - name: Publish FSharp.Collections.Immutable project to GitHub run: | - source=https://www.myget.org/F/fsharp-collections-immutable/api/v3/index.json - key=${{secrets.MyGet_Key}} - dotnet nuget push -s $source -k $key nuget/*.nupkg + dotnet nuget push nuget/FSharp.Collections.Immutable*.nupkg -s "github" -k ${{secrets.GITHUB_TOKEN}} diff --git a/.github/workflows/publish_release.yml b/.github/workflows/publish_release.yml index acd6a0f..5e6ca53 100644 --- a/.github/workflows/publish_release.yml +++ b/.github/workflows/publish_release.yml @@ -2,8 +2,8 @@ name: publish to NuGet on: push: - branches: - - master + tags: + - 'releases/*' jobs: publish: @@ -11,22 +11,30 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - # Required for a specific dotnet version that doesn't come with ubuntu-latest / windows-latest - # Visit bit.ly/2synnZl to see the list of SDKs that are pre-installed with ubuntu-latest / windows-latest - # - name: Setup dotnet - # uses: actions/setup-dotnet@v1 - # with: - # dotnet-version: 3.1.100 - - # Publish - - name: publish on version change - uses: rohith/publish-nuget@v2 - with: - PROJECT_FILE_PATH: src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj # Relative to repository root - # VERSION_FILE_PATH: Directory.Build.props # Filepath with version info, relative to repository root. Defaults to project file - # VERSION_REGEX: (.*)<\/Version> # Regex pattern to extract version info in a capturing group - # TAG_COMMIT: true # Flag to enable / disalge git tagging - # TAG_FORMAT: v* # Format of the git tag, [*] gets replaced with version - NUGET_KEY: ${{secrets.NuGet_Key}} + - name: Checkout + uses: actions/checkout@v3 + with: + # This is necessary so that we have the tags. + fetch-depth: 0 + + - name: Set Build Version + run: | + $version = "$env:GITHUB_REF_NAME" + $File = ( + Select-Xml -XPath "/Project/PropertyGroup/Version" -Path "src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj" + )[0].Node + $File.InnerText = $version + $File.OwnerDocument.Save((Join-Path $PWD.ProviderPath src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj)) + echo "VERSION=$version" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append + shell: pwsh + + - name: Install .NET Core + uses: actions/setup-dotnet@v3 + + - name: Pack FSharp.Collections.Immutable project + run: | + dotnet pack --nologo --configuration Release -o nuget + + - name: Publish FSharp.Collections.Immutable project to GitHub + run: | + dotnet nuget push nuget/FSharp.Collections.Immutable*.nupkg -k ${{secrets.NUGET_SECRET}} From 864fa9a63fc2eea686eb88d5ee508daf552cb0ae Mon Sep 17 00:00:00 2001 From: Andrii Chebukin Date: Tue, 3 Jun 2025 02:07:17 +0400 Subject: [PATCH 2/8] feat: release automation with FAKE and minimal documentation * Added build and release automation * Updated tools * Added a logo and corrected packaging settings * Updated NuGet packages * Added Copilot instructions * Fixed documentation * Migrated test coverage to TRX * Set .NET SDK to `9.x` in pipelines * Added `dotnet tool update` and fixed `dotnet tool restore` * Migrated to `slnx` --- .config/dotnet-tools.json | 34 + .devcontainer/Dockerfile | 17 + .devcontainer/devcontainer.json | 73 ++ .editorconfig | 332 ++++++-- .fantomasignore | 2 + .git-blame-ignore-revs | 6 + .gitattributes | 4 +- .github/ISSUE_TEMPLATE.md | 31 + .github/ISSUE_TEMPLATE/bug_report.md | 35 + .github/ISSUE_TEMPLATE/feature_request.md | 17 + .github/PULL_REQUEST_TEMPLATE.md | 25 + .github/copilot-instructions.md | 57 ++ .github/workflows/build.yml | 60 +- .github/workflows/fsdocs-gh-pages.yml | 61 ++ .github/workflows/publish_ci.yml | 87 ++- .github/workflows/publish_release.yml | 58 +- .gitignore | 244 +++++- .vscode/extensions.json | 9 + .vscode/settings.json | 7 + CHANGELOG.md | 24 + Directory.Build.props | 49 ++ Directory.Build.targets | 47 ++ Directory.Packages.props | 34 + FSharp.Collections.Immutable.sln | 34 - FSharp.Collections.Immutable.slnf | 9 + FSharp.Collections.Immutable.slnx | 14 + README.md | 111 ++- build.cmd | 1 + build.sh | 6 + build/Changelog.fs | 233 ++++++ build/FsDocs.fs | 224 ++++++ build/Properties/launchSettings.json | 24 + build/build.fs | 731 ++++++++++++++++++ build/build.fsproj | 40 + docsSrc/Explanations/Background.md | 39 + docsSrc/How_Tos/Doing_A_Thing.md | 14 + docsSrc/How_Tos/Doing_Another_Thing.md | 9 + docsSrc/Tutorials/Getting_Started.md | 41 + docsSrc/_menu-item_template.html | 1 + docsSrc/_menu_template.html | 9 + docsSrc/_template.html | 166 ++++ docsSrc/content/fsdocs-custom.css | 15 + docsSrc/content/fsdocs-dark.css | 50 ++ docsSrc/content/fsdocs-light.css | 43 ++ docsSrc/content/fsdocs-main.css | 604 +++++++++++++++ docsSrc/content/logo.pdn | Bin 0 -> 8637 bytes docsSrc/content/logo.png | Bin 0 -> 1904 bytes docsSrc/content/logo.svg | 57 ++ docsSrc/content/navbar-fixed-left.css | 91 +++ docsSrc/content/theme-toggle.js | 68 ++ docsSrc/index.md | 79 ++ global.json | 6 + .../FSharp.Collections.Immutable.fsproj | 74 +- .../{flat-list.fs => FlatList.fs} | 367 ++++++--- ...ion-util.fs => ImmutableCollectionUtil.fs} | 10 +- .../{immutable-list.fs => ImmutableList.fs} | 255 +++--- .../IndexedSeq.fs | 17 + src/FSharp.Collections.Immutable/Maps.fs | 339 ++++++++ src/FSharp.Collections.Immutable/Queue.fs | 132 ++++ .../{seq.fs => Seq.fs} | 0 src/FSharp.Collections.Immutable/Sets.fs | 266 +++++++ .../{stack.fs => Stack.fs} | 23 +- .../indexed-seq.fs | 10 - src/FSharp.Collections.Immutable/maps.fs | 225 ------ src/FSharp.Collections.Immutable/queue.fs | 80 -- src/FSharp.Collections.Immutable/sets.fs | 156 ---- tests/Directory.Build.props | 7 + .../Attributes.fs | 7 + .../FSharp.Collections.Immutable.Tests.fsproj | 32 + .../FlatList.fs | 10 + .../ImmutableList.fs | 10 + .../IndexedSeq.fs | 10 + .../Maps.fs | 16 + .../Queue.fs | 10 + .../Sets.fs | 16 + .../Stack.fs | 10 + 76 files changed, 5188 insertions(+), 926 deletions(-) create mode 100644 .config/dotnet-tools.json create mode 100644 .devcontainer/Dockerfile create mode 100644 .devcontainer/devcontainer.json create mode 100644 .fantomasignore create mode 100644 .git-blame-ignore-revs create mode 100644 .github/ISSUE_TEMPLATE.md create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/copilot-instructions.md create mode 100644 .github/workflows/fsdocs-gh-pages.yml create mode 100644 .vscode/extensions.json create mode 100644 .vscode/settings.json create mode 100644 CHANGELOG.md create mode 100644 Directory.Build.props create mode 100644 Directory.Build.targets create mode 100644 Directory.Packages.props delete mode 100644 FSharp.Collections.Immutable.sln create mode 100644 FSharp.Collections.Immutable.slnf create mode 100644 FSharp.Collections.Immutable.slnx create mode 100644 build.cmd create mode 100644 build.sh create mode 100644 build/Changelog.fs create mode 100644 build/FsDocs.fs create mode 100644 build/Properties/launchSettings.json create mode 100644 build/build.fs create mode 100644 build/build.fsproj create mode 100644 docsSrc/Explanations/Background.md create mode 100644 docsSrc/How_Tos/Doing_A_Thing.md create mode 100644 docsSrc/How_Tos/Doing_Another_Thing.md create mode 100644 docsSrc/Tutorials/Getting_Started.md create mode 100644 docsSrc/_menu-item_template.html create mode 100644 docsSrc/_menu_template.html create mode 100644 docsSrc/_template.html create mode 100644 docsSrc/content/fsdocs-custom.css create mode 100644 docsSrc/content/fsdocs-dark.css create mode 100644 docsSrc/content/fsdocs-light.css create mode 100644 docsSrc/content/fsdocs-main.css create mode 100644 docsSrc/content/logo.pdn create mode 100644 docsSrc/content/logo.png create mode 100644 docsSrc/content/logo.svg create mode 100644 docsSrc/content/navbar-fixed-left.css create mode 100644 docsSrc/content/theme-toggle.js create mode 100644 docsSrc/index.md create mode 100644 global.json rename src/FSharp.Collections.Immutable/{flat-list.fs => FlatList.fs} (58%) rename src/FSharp.Collections.Immutable/{immutable-collection-util.fs => ImmutableCollectionUtil.fs} (73%) rename src/FSharp.Collections.Immutable/{immutable-list.fs => ImmutableList.fs} (50%) create mode 100644 src/FSharp.Collections.Immutable/IndexedSeq.fs create mode 100644 src/FSharp.Collections.Immutable/Maps.fs create mode 100644 src/FSharp.Collections.Immutable/Queue.fs rename src/FSharp.Collections.Immutable/{seq.fs => Seq.fs} (100%) create mode 100644 src/FSharp.Collections.Immutable/Sets.fs rename src/FSharp.Collections.Immutable/{stack.fs => Stack.fs} (62%) delete mode 100644 src/FSharp.Collections.Immutable/indexed-seq.fs delete mode 100644 src/FSharp.Collections.Immutable/maps.fs delete mode 100644 src/FSharp.Collections.Immutable/queue.fs delete mode 100644 src/FSharp.Collections.Immutable/sets.fs create mode 100644 tests/Directory.Build.props create mode 100644 tests/FSharp.Collections.Immutable.Tests/Attributes.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/FSharp.Collections.Immutable.Tests.fsproj create mode 100644 tests/FSharp.Collections.Immutable.Tests/FlatList.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/ImmutableList.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/IndexedSeq.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/Maps.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/Queue.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/Sets.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/Stack.fs diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json new file mode 100644 index 0000000..3c0225f --- /dev/null +++ b/.config/dotnet-tools.json @@ -0,0 +1,34 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "dotnet-reportgenerator-globaltool": { + "version": "5.4.7", + "commands": [ + "reportgenerator" + ], + "rollForward": false + }, + "fsharp-analyzers": { + "version": "0.31.0", + "commands": [ + "fsharp-analyzers" + ], + "rollForward": false + }, + "fantomas": { + "version": "7.0.2", + "commands": [ + "fantomas" + ], + "rollForward": false + }, + "fsdocs-tool": { + "version": "20.0.1", + "commands": [ + "fsdocs" + ], + "rollForward": false + } + } +} \ No newline at end of file diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..96c07a3 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,17 @@ +# [Choice] Debian version (use bullseye on local arm64/Apple Silicon): bookworm, bullseye, buster +ARG VARIANT="bookworm" +FROM buildpack-deps:${VARIANT}-curl + + +ENV \ + # Enable detection of running in a container + DOTNET_RUNNING_IN_CONTAINER=true \ + DOTNET_ROOT=/usr/share/dotnet/ \ + DOTNET_NOLOGO=true \ + DOTNET_CLI_TELEMETRY_OPTOUT=false\ + DOTNET_USE_POLLING_FILE_WATCHER=true + + +# [Optional] Uncomment this section to install additional OS packages. +# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ +# && apt-get -y install --no-install-recommends diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..5b1e8df --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,73 @@ +{ + "name": "dotnet", + // Set the build context one level higher so we can grab metadata like global.json + "context": "..", + "dockerFile": "Dockerfile", + "forwardPorts": [ + 0 + ], + "features": { + // https://github.com/devcontainers/features/blob/main/src/common-utils/README.md + "ghcr.io/devcontainers/features/common-utils:2": { + "installZsh": true, + "installOhMyZshConfig": true, + "configureZshAsDefaultShell": true, + "username": "vscode", + "userUid": "1000", + "userGid": "1000", + "upgradePackages": true + }, + // https://github.com/devcontainers/features/blob/main/src/github-cli/README.md + "ghcr.io/devcontainers/features/github-cli:1": {}, + // https://github.com/devcontainers-contrib/features/blob/main/src/starship/README.md + "ghcr.io/devcontainers-contrib/features/starship:1": {}, + // https://github.com/devcontainers/features/blob/main/src/dotnet/README.md + "ghcr.io/devcontainers/features/dotnet:2": { + "version": "9.0", + "additionalVersions": "8.0" + } + }, + "overrideFeatureInstallOrder": [ + "ghcr.io/devcontainers/features/common-utils", + "ghcr.io/devcontainers/features/github-cli", + "ghcr.io/devcontainers-contrib/features/starship", + "ghcr.io/devcontainers/features/dotnet" + ], + "customizations": { + "vscode": { + // Add the IDs of extensions you want installed when the container is created. + "extensions": [ + "ms-dotnettools.csharp", + "Ionide.Ionide-fsharp", + "tintoy.msbuild-project-tools", + "ionide.ionide-paket", + "usernamehw.errorlens", + "alefragnani.Bookmarks", + "oderwat.indent-rainbow", + "vscode-icons-team.vscode-icons", + "EditorConfig.EditorConfig", + "ms-azuretools.vscode-docker", + "GitHub.vscode-pull-request-github", + "github.vscode-github-actions" + ], + "settings": { + "terminal.integrated.defaultProfile.linux": "zsh", + "csharp.suppressDotnetInstallWarning": true + } + } + }, + "remoteUser": "vscode", + "containerUser": "vscode", + "containerEnv": { + // Expose the local environment variable to the container + // They are used for releasing and publishing from the container + "GITHUB_TOKEN": "${localEnv:GITHUB_TOKEN}" + }, + "onCreateCommand": { + "enable-starship": "echo 'eval \"$(starship init zsh)\"' >> ~/.zshrc" + }, + "postAttachCommand": { + "restore": "dotnet tool restore && dotnet restore" + }, + "waitFor": "updateContentCommand" +} diff --git a/.editorconfig b/.editorconfig index 31577aa..869005a 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,56 +1,296 @@ -# To learn more about .editorconfig see https://aka.ms/editorconfigdocs +# EditorConfig is awesome: +http://EditorConfig.org + +# top-most EditorConfig file +root = true + ############################### # Core EditorConfig Options # ############################### # All files -[*] -end_of_line = crlf +[*] # Do not apply to all files not to break something +guidelines = 120 dashed, 130 +# Either crlf | lf, default is system-dependent (when not specified at all) +# end_of_line=crlf +# Remove whitespace at the end of any line + +# Visual Studio Solution Files +[*.sln] +indent_style = tab + +# Code files +[*.{cs,csx,fs,fsi,fsx}] +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = space # default=space +indent_size = 4 # default=4 +charset = utf-8 + +# Project files and app specific XML files +[*.{csproj,fsproj,shproj,sfproj,projitems,props,xaml,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = space +indent_size = 2 + +# XML configuration files +[{app.config,nuget.config,packages.config,web.config}] +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = space +indent_size = 2 + +# XML files +[*.xml] +trim_trailing_whitespace = false # do not trim as it affects CData +insert_final_newline = true indent_style = space +indent_size = 2 + +# JSON and YAML files +[*.{json,yml,yaml}] +trim_trailing_whitespace = true insert_final_newline = true +indent_style = space +indent_size = 2 + +# Proto files +[*.proto] trim_trailing_whitespace = true -# Code files -[*.{cs,csx,fs,fsx,vb,vbx}] -indent_size = 4 -charset = utf-8-bom -# Project files -[*.{csproj,fsproj,vbproj,shproj,projitems}] -indent_size = 2 -charset = utf-8-bom +insert_final_newline = true +indent_style = space +indent_size = 4 + +# Markdown Files +[*.{md,mdx}] +trim_trailing_whitespace = false + +# Bash Files +[*.{sh}] +end_of_line = lf + +# Batch Files +[*.{cmd,bat}] +end_of_line = crlf + +# Powershell Files +[*.{ps1, psm1}] +end_of_line = crlf + +# Paket files +[paket.*] +trim_trailing_whitespace = true +indent_size = 2 + +[*.paket.references] +trim_trailing_whitespace = true +indent_size = 2 + ############################### # F# Coding Conventions # ############################### -[*.{fs,fsx}] -max_line_length = 120 -fsharp_semicolon_at_end_of_line = false -fsharp_space_before_parameter = true -fsharp_space_before_lowercase_invocation = true -fsharp_space_before_uppercase_invocation = true -fsharp_space_before_class_constructor = true -fsharp_space_before_member = true -fsharp_space_before_colon = true -fsharp_space_after_comma = true -fsharp_space_before_semicolon = false -fsharp_space_after_semicolon = true -fsharp_indent_on_try_with = false -fsharp_space_around_delimiter = true -fsharp_max_if_then_else_short_width = 80 -fsharp_max_infix_operator_expression = 80 -fsharp_max_record_width = 80 -fsharp_max_record_number_of_items = 1 -fsharp_record_multiline_formatter = character_width -fsharp_max_array_or_list_width = 80 -fsharp_max_array_or_list_number_of_items = 10 -fsharp_array_or_list_multiline_formatter = character_width -fsharp_max_value_binding_width = 80 -fsharp_max_function_binding_width = 80 -fsharp_max_dot_get_expression_width = 40 -fsharp_multiline_block_brackets_on_same_column = false -fsharp_newline_between_type_definition_and_members = false -fsharp_keep_if_then_in_same_line = false -fsharp_max_elmish_width = 80 -fsharp_single_argument_web_mode = false -fsharp_align_function_signature_to_indentation = false -fsharp_alternative_long_member_definitions = false -fsharp_multi_line_lambda_closing_newline = false -fsharp_disable_elmish_syntax = false -fsharp_strict_mode = false +# https://fsprojects.github.io/fantomas/docs/end-users/Configuration.html + +# filetypes that need to be formatted by Fantomas: +[*.{fs,fsi,fsx}] + +# files to be ignored for Fantomas may go into this file, if present: +# .fantomasignore + +# indentation size, default=4 +indent_size=4 + +# line length before it gets broken down into multiple lines +# default 120 +max_line_length=130 + +# Either crlf | lf, default is system-dependent (when not specified at all) +# end_of_line=crlf + +# Whether end-of-file has a newline, default=true +insert_final_newline=true + +# false: someLineOfCode +# true: someLineOfCode; +# default false +fsharp_semicolon_at_end_of_line=false + +# false: f(1,2) +# true: f(1, 2) +# default true +fsharp_space_before_parameter=true + +# false: Option.map(fun x -> x) +# true: Option.map (fun x -> x) +# default true +fsharp_space_before_lowercase_invocation=true + +# false: x.ToString() +# true: x.ToString () +# default false +fsharp_space_before_uppercase_invocation=true + +# false: new Ship(withBeans) +# true: new Ship (withBeans) +# default false +fsharp_space_before_class_constructor=true + +# false: __.member Foo(x) = x +# true: __.member Foo (x) = x +# default false +fsharp_space_before_member=true + +# false: type Point = { x: int; y: int } +# true: type Point = { x : int; y : int } +# default false +fsharp_space_before_colon=true + +# false: (a,b,c) +# true: (a, b, c) +# default true +fsharp_space_after_comma=true + +# false: [a; b; 42] +# true: [a ; b ; 42] +# default false +fsharp_space_before_semicolon=false + +# false: [a;b;42] +# true: [a; b; 42] +# default true +fsharp_space_after_semicolon=true + +# false: let a = [1;2;3] +# true: let a = [ 1;2;3 ] +# default true +fsharp_space_around_delimiter=true + +# breaks an if-then-else in smaller parts if it is on one line +# default 40 +fsharp_max_if_then_else_short_width=60 + +# breaks an infix operator expression if it is on one line +# infix: a + b + c +# default 50 +fsharp_max_infix_operator_expression=60 + +# breaks a single-line record declaration +# i.e. if this gets too wide: { X = 10; Y = 12 } +# default 40 +fsharp_max_record_width=80 + +# breaks a record into one item per line if items exceed this number +# i.e. if set to 1, this will be on three lines: { X = 10; Y = 12 } +# requires fsharp_record_multiline_formatter=number_of_items to take effect +# default 1 +fsharp_max_record_number_of_items=1 + +# whether to use line-length (by counting chars) or items (by counting fields) +# for the record settings above +# either number_of_items or character_width +# default character_width +fsharp_record_multiline_formatter=character_width + +# breaks a single line array or list if it exceeds this size +# default 40 +fsharp_max_array_or_list_width=100 + +# breaks an array or list into one item per line if items exceeds this number +# i.e. if set to 1, this will be shown on three lines [1; 2; 3] +# requires fsharp_array_or_list_multiline_formatter=number_of_items to take effect +# default 1 +fsharp_max_array_or_list_number_of_items=1 + +# whether to use line-length (by counting chars) or items (by counting fields) +# for the list and array settings above +# either number_of_items or character_width +# default character_width +fsharp_array_or_list_multiline_formatter=character_width + +# maximum with of a value binding, does not include keyword "let" +# default 80 +fsharp_max_value_binding_width=100 + +# maximum width for function and member binding (rh-side) +# default 40 +fsharp_max_function_binding_width=80 + +# maximum width for expressions like X.DoY().GetZ(10).Help() +# default 50 +fsharp_max_dot_get_expression_width=80 + +# whether open/close brackets go on the same column +# cramped: type Range = +# { From: float +# To: float } +# aligned: type Range = +# { +# From: float +# To: float +# } +# stroustrup: type Range = { +# From: float +# To: float +# } +# default cramped +fsharp_multiline_bracket_style=stroustrup + +# whether to move the beginning of compuitation expression to the new line +# true: let x = +# computation { +# ... +# } +# false: let x = computation { +# .. +# } +fsharp_newline_before_multiline_computation_expression=false + +# whether a newline should be placed before members +# false: type Range = +# { From: float } +# member this.Length = this.To - this.From +# true: type Range = +# { From: float } +# +# member this.Length = this.To - this.From +# default false +fsharp_newline_between_type_definition_and_members=true + +# if a function sign exceeds max_line_length, then: +# false: do not place the equal-sign on a single line +# true: place the equal-sign on a single line +# default false +fsharp_align_function_signature_to_indentation=false + +# see docs: https://github.com/fsprojects/fantomas/blob/master/docs/Documentation.md#fsharp_alternative_long_member_definitions +# default false +fsharp_alternative_long_member_definitions=true + +# places closing paren in lambda on a newline in multiline lambdas +# default false +fsharp_multi_line_lambda_closing_newline=true + +# allows the 'else'-branch to be aligned at same level as 'else' if the ret type allows it +# false: match x with +# | null -> () +# | _ -> () +# true: match x with +# | null -> () +# | _ -> +# () +# default false +fsharp_keep_indent_in_branch=true + +# whether a bar is placed before DU +# false: type MyDU = Short of int +# true: type MyDU = | Short of int +# default false +fsharp_bar_before_discriminated_union_declaration=false + +# multiline, nested expressions must be surrounded by blank lines +# default true +fsharp_blank_lines_around_nested_multiline_expressions=false + +# set maximal number of consecutive blank lines to keep from original source +# it doesn't change number of new blank lines generated by Fantomas +fsharp_keep_max_number_of_blank_lines=2 diff --git a/.fantomasignore b/.fantomasignore new file mode 100644 index 0000000..d9f2aa7 --- /dev/null +++ b/.fantomasignore @@ -0,0 +1,2 @@ +# Ignore AssemblyInfo files +AssemblyInfo.fs diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000..9c8817e --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,6 @@ +# This file contains a list of git hashes of revisions to be ignored by git +# These revisions are considered "unimportant" in +# that they are unlikely to be what you are interested in when blaming. +# Like formatting with Fantomas +# https://docs.github.com/en/repositories/working-with-files/using-files/viewing-a-file#ignore-commits-in-the-blame-view +# Add formatting commits here diff --git a/.gitattributes b/.gitattributes index 206349c..cb6f883 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,8 +1,9 @@ # Auto detect text files -* text=auto +* text=auto # Custom for Visual Studio *.cs diff=csharp text=auto eol=lf +*.vb diff=csharp text=auto eol=lf *.fs diff=csharp text=auto eol=lf *.fsi diff=csharp text=auto eol=lf *.fsx diff=csharp text=auto eol=lf @@ -11,6 +12,7 @@ *.vbproj merge=union *.fsproj merge=union *.dbproj merge=union +*.sh text eol=lf # Standard to msysgit *.doc diff=astextplain diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..02ca9be --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,31 @@ +## Description + +Please insert a description of your problem or question. + +## Error messages, screenshots + +Please add any error logs or screenshots if available. + +## Failing test, failing GitHub repo, or reproduction steps + +Please add either a failing test, a GitHub repo of the problem or detailed reproduction steps. + +## Expected Behavior + +Please define what you would expect the behavior to be like. + +## Known workarounds + +Please provide a description of any known workarounds. + +## Other information + +* Operating System: + - [ ] windows [insert version here] + - [ ] macOs [insert version] + - [ ] linux [insert flavor/version here] +* Platform + - [ ] dotnet core + - [ ] dotnet full + - [ ] mono +* Branch or release version: diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..b735373 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,35 @@ +--- +name: Bug report +about: Create a report to help us improve + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..066b2d9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,17 @@ +--- +name: Feature request +about: Suggest an idea for this project + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..ab249a8 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,25 @@ +## Proposed Changes + +Describe the big picture of your changes here to communicate to the maintainers why we should accept this pull request. If it fixes a bug or resolves a feature request, be sure to link to that issue. + +## Types of changes + +What types of changes does your code introduce to FSharp.Azure.Cosmos? +_Put an `x` in the boxes that apply_ + +- [ ] Bugfix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) + + +## Checklist + +_Put an `x` in the boxes that apply. You can also fill these out after creating the PR. If you're unsure about any of them, don't hesitate to ask. We're here to help! This is simply a reminder of what we are going to look for before merging your code._ + +- [ ] Build and tests pass locally +- [ ] I have added tests that prove my fix is effective or that my feature works (if appropriate) +- [ ] I have added necessary documentation (if appropriate) + +## Further comments + +If this is a relatively large or complex change, kick off the discussion by explaining why you chose the solution you did and what alternatives you considered, etc... diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..8690c43 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,57 @@ +We prefer the latest F# 9 features over the old syntax + +Prefer `voption` over `option` + +Prefer `task` CE over `async` CE + +This is how you define a non-default F# class constructor: +```fsharp +type DerivedClass = + inherit BaseClass + + new (``arguments here``) as ``created object`` + = + // create any objects used in the base class constructor + let fieldValue = "" + { + inherit + BaseClass (``arguments here``) + } + then + ``created object``.otherField <- fieldValue + + [] + val mutable otherField : FieldType +``` + +Always prefer F# class initializers over property assignment! **You absolutely must use F# class initializers instead of property assignment**! + +Class declaration: +``` F# +type MyClass (someConstructorParam : string) = + member ReadOnlyProperty = someConstructorParam + + member val MutableProperty1 = "" with get, set + member val MutableProperty2 = "" with get, set +``` + +Wrong: +``` F# +let myClass = MyClass("some value") +myClass.MutableProperty1 <- "new value" +myClass.MutableProperty2 <- "new value" +``` + +Right: +``` F# +let myClass = + MyClass( + // constructor parameters go first without names + "some value", + // then mutable properties go next with names + MutableProperty1 = "new value", + MutableProperty2 = + // operations must be placed into parentheses + (5 |> string) + ) +``` diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c612c01..31c54a8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,21 +1,57 @@ -name: .NET Core +name: Build main on: push: - branches-ignore: - - master - - develop + branches: + - main + pull_request: + branches: + - main jobs: build: + strategy: + matrix: + configuration: [Debug, Release] + os: [ubuntu-latest, windows-latest, macOS-latest] + runs-on: ${{ matrix.os }} - runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Setup necessary dotnet SDKs + uses: actions/setup-dotnet@v3 + with: + global-json-file: global.json + dotnet-version: | + 9.x + 8.x + - name: Build via Bash + if: runner.os != 'Windows' + run: | + chmod +x ./build.sh + ./build.sh + env: + CI: true + CONFIGURATION: ${{ matrix.configuration }} + ENABLE_COVERAGE: true + - name: Build via Windows + if: runner.os == 'Windows' + run: ./build.cmd + env: + CI: true + CONFIGURATION: ${{ matrix.configuration }} + ENABLE_COVERAGE: true + # Builds the project in a dev container + build-devcontainer: + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - name: Setup .NET Core - uses: actions/setup-dotnet@v3 - - name: Build with dotnet - run: | - DOTNET_CLI_TELEMETRY_OPTOUT=1 - dotnet build --nologo --configuration Release + + - uses: actions/checkout@v3 + + - name: Build and run dev container task + uses: devcontainers/ci@v0.3 + with: + runCmd: | + chmod +x ./build.sh + ./build.sh diff --git a/.github/workflows/fsdocs-gh-pages.yml b/.github/workflows/fsdocs-gh-pages.yml new file mode 100644 index 0000000..0ebb9ec --- /dev/null +++ b/.github/workflows/fsdocs-gh-pages.yml @@ -0,0 +1,61 @@ +name: Deploy Docs + +on: + # Runs on pushes targeting the default branch + push: + branches: ["main"] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + # Build job + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Pages + uses: actions/configure-pages@v4 + + - name: Setup necessary dotnet SDKs + uses: actions/setup-dotnet@v4 + with: + global-json-file: global.json + dotnet-version: | + 9.x + + - name: Build Docs + run: | + chmod +x ./build.sh + ./build.sh builddocs + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: docs/ + + # Deployment job + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/publish_ci.yml b/.github/workflows/publish_ci.yml index 7f7b918..9a0f45b 100644 --- a/.github/workflows/publish_ci.yml +++ b/.github/workflows/publish_ci.yml @@ -1,40 +1,65 @@ -name: publish to GitHub +name: Publish to GitHub on: push: branches: - - develop + - main -jobs: - publish: +env: + CONFIGURATION: Release +jobs: + build: + # Sets permissions of the GITHUB_TOKEN to allow release creating + permissions: + packages: write + environment: + name: nuget runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Set Build Version - run: | - $File = ( - Select-Xml -XPath "/Project/PropertyGroup/Version" -Path "src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj" - )[0].Node - $version = "$($File.InnerText)-ci-$Env:GITHUB_RUN_ID" - $File.InnerText = $version - $File.OwnerDocument.Save((Join-Path $PWD.ProviderPath src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj)) - echo "VERSION=$version" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append - shell: pwsh - - - name: Install .NET Core - uses: actions/setup-dotnet@v3 - - - name: Add the GitHub source - run: dotnet nuget add source --username USERNAME --password ${{secrets.GITHUB_TOKEN}} --store-password-in-clear-text --name github "https://nuget.pkg.github.com/fsprojects/index.json" - - - name: Pack FSharp.Collections.Immutable project - run: | - dotnet pack --nologo --configuration Release -o nuget - - - name: Publish FSharp.Collections.Immutable project to GitHub - run: | - dotnet nuget push nuget/FSharp.Collections.Immutable*.nupkg -s "github" -k ${{secrets.GITHUB_TOKEN}} + - uses: actions/checkout@v3 + + - name: Setup necessary dotnet SDKs + uses: actions/setup-dotnet@v3 + with: + global-json-file: global.json + dotnet-version: | + 9.x + 8.x + + - name: Add the GitHub source + run: dotnet nuget add source --name "github.com" "https://nuget.pkg.github.com/fsprojects/index.json" + + - name: Ensure NuGet package source mapping + shell: pwsh + run: | + $nugetConfigPath = "$HOME/.nuget/NuGet/NuGet.Config" + [xml]$nugetConfig = Get-Content $nugetConfigPath + + $packageSourceMapping = $nugetConfig.configuration.packageSourceMapping + if ($packageSourceMapping -ne $null) { + $packageSourceMapping.RemoveAll() + } else { + $packageSourceMapping = $nugetConfig.CreateElement("packageSourceMapping") + $nugetConfig.configuration.AppendChild($packageSourceMapping) + } + + $nugetSource = $nugetConfig.CreateElement("packageSource") + $nugetSource.SetAttribute("key", "nuget.org") + $nugetPattern = $nugetConfig.CreateElement("package") + $nugetPattern.SetAttribute("pattern", "*") + $nugetSource.AppendChild($nugetPattern) + $packageSourceMapping.AppendChild($nugetSource) + + $nugetConfig.Save($nugetConfigPath) + + - name: Publish to GitHub + env: + NUGET_TOKEN: ${{ secrets.NUGET_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + FAKE_DETAILED_ERRORS: true + ENABLE_COVERAGE: false # AltCover doesn't work with Release builds, reports lower coverage than actual + run: | + chmod +x ./build.sh + ./build.sh "PublishToGitHub" diff --git a/.github/workflows/publish_release.yml b/.github/workflows/publish_release.yml index 5e6ca53..d92d34f 100644 --- a/.github/workflows/publish_release.yml +++ b/.github/workflows/publish_release.yml @@ -1,40 +1,36 @@ -name: publish to NuGet +name: Publish to NuGet on: push: tags: - 'releases/*' +env: + CONFIGURATION: Release jobs: - publish: - + build: + # Sets permissions of the GITHUB_TOKEN to allow release creating + permissions: + contents: write + environment: + name: nuget runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - with: - # This is necessary so that we have the tags. - fetch-depth: 0 - - - name: Set Build Version - run: | - $version = "$env:GITHUB_REF_NAME" - $File = ( - Select-Xml -XPath "/Project/PropertyGroup/Version" -Path "src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj" - )[0].Node - $File.InnerText = $version - $File.OwnerDocument.Save((Join-Path $PWD.ProviderPath src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj)) - echo "VERSION=$version" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append - shell: pwsh - - - name: Install .NET Core - uses: actions/setup-dotnet@v3 - - - name: Pack FSharp.Collections.Immutable project - run: | - dotnet pack --nologo --configuration Release -o nuget - - - name: Publish FSharp.Collections.Immutable project to GitHub - run: | - dotnet nuget push nuget/FSharp.Collections.Immutable*.nupkg -k ${{secrets.NUGET_SECRET}} + - uses: actions/checkout@v3 + - name: Setup necessary dotnet SDKs + uses: actions/setup-dotnet@v3 + with: + global-json-file: global.json + dotnet-version: | + 9.x + 8.x + + - name: Publish to NuGet + env: + NUGET_TOKEN: ${{ secrets.NUGET_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + FAKE_DETAILED_ERRORS: true + ENABLE_COVERAGE: false # AltCover doesn't work with Release builds, reports lower coverage than actual + run: | + chmod +x ./build.sh + ./build.sh Publish diff --git a/.gitignore b/.gitignore index 98a4281..99fd3b7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,10 @@ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore # User-specific files +*.rsuser *.suo *.user *.userosscache @@ -10,6 +13,9 @@ # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs +# Mono auto generated files +mono_crash.* + # Build results [Dd]ebug/ [Dd]ebugPublic/ @@ -17,39 +23,62 @@ [Rr]eleases/ x64/ x86/ -build/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ bld/ [Bb]in/ [Oo]bj/ +[Ll]og/ +[Ll]ogs/ -# Visual Studio 2015 cache/options directory +# Visual Studio 2015/2017 cache/options directory .vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* -# NUNIT +# NUnit *.VisualState.xml TestResult.xml +nunit-*.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c -# DNX +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core project.lock.json +project.fragment.lock.json artifacts/ +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio *_i.c *_p.c -*_i.h +*_h.h *.ilk *.meta *.obj +*.iobj *.pch *.pdb +*.ipdb *.pgc *.pgd *.rsp @@ -59,7 +88,9 @@ artifacts/ *.tlh *.tmp *.tmp_proj +*_wpftmp.csproj *.log +*.tlog *.vspscc *.vssscc .builds @@ -74,14 +105,21 @@ _Chutzpah* ipch/ *.aps *.ncb +*.opendb *.opensdf *.sdf *.cachefile +*.VC.db +*.VC.VC.opendb # Visual Studio profiler *.psess *.vsp *.vspx +*.sap + +# Visual Studio Trace Files +*.e2e # TFS 2012 Local Workspace $tf/ @@ -89,9 +127,6 @@ $tf/ # Guidance Automation Toolkit *.gpState -# CodeRush is a .NET coding add-in -.cr/ - # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper @@ -106,9 +141,23 @@ _TeamCity* # DotCover is a Code Coverage Tool *.dotCover +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + # NCrunch _NCrunch_* .*crunch*.local.xml +nCrunchTemp_* # MightyMoose *.mm.* @@ -136,49 +185,72 @@ publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml -## TODO: Comment the next line if you want to checkin your -## web deploy settings but do note that will include unencrypted -## passwords -#*.pubxml - +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml *.publishproj +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + # NuGet Packages *.nupkg +# NuGet Symbol Packages +*.snupkg # The packages folder can be ignored because of Package Restore -**/packages/* -# project.json output for .csproj and .fsproj projects can be ignored because of Package Restore -*.nuget.targets +**/[Pp]ackages/* # except build/, which is used as an MSBuild target. -!**/packages/build/ +!**/[Pp]ackages/build/ # Uncomment if necessary however generally it will be regenerated when needed -#!**/packages/repositories.config +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets -# Windows Azure Build Output +# Microsoft Azure Build Output csx/ *.build.csdef -# Windows Store app package directory +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache -!*.[Cc]ache/ +!?*.[Cc]ache/ # Others ClientBin/ -[Ss]tyle[Cc]op.* ~$* *~ *.dbmdl *.dbproj.schemaview +*.jfm *.pfx *.publishsettings -node_modules/ orleans.codegen.cs +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + # RIA/Silverlight projects Generated_Code/ @@ -189,21 +261,32 @@ _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak # SQL Server files *.mdf *.ldf +*.ndf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl # Microsoft Fakes FakesAssemblies/ +# GhostDoc plugin setting file +*.GhostDoc.xml + # Node.js Tools for Visual Studio .ntvs_analysis.dat +node_modules/ # Visual Studio 6 build log *.plg @@ -211,9 +294,114 @@ FakesAssemblies/ # Visual Studio 6 workspace options file *.opt -# LightSwitch generated files -GeneratedArtifacts/ -_Pvt_Extensions/ -ModelManifest.xml +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder .ionide/ -.idea/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml + +# fsdocs generated +tmp/ +temp/ +.fsdocs +docs/ diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..d566ef1 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,9 @@ +{ + "recommendations": [ + "ionide.ionide-paket", + "ionide.ionide-fsharp", + "ionide.ionide-fake", + "ms-dotnettools.csharp", + "editorConfig.editorConfig" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..b98926a --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "FSharp.fsacRuntime":"netcore", + "FSharp.enableAnalyzers": false, + "FSharp.analyzersPath": [ + "./packages/analyzers" + ] +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..e3f2456 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,24 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [1.0.0] - 2020-02-23 + +First release + +### Implemented type abbreviations and modules with functions for: +* `FlatList` (`ImmutableArray`) +* `ImmutableList` +* `Stack` (`ImmutableStack`) +* `Queue` (`ImmutableQueue`) +* `HashMap` (`ImmutableDictionary`) +* `SortedMap` (`ImmutableSortedDictionary`) +* `HashSet` (`ImmutableHashSet`) +* `SortedSet` (`ImmutableSortedSet`) +* `IIndexedSeq` (`IReadOnlyList`) + +[Unreleased]: https://github.com/fsprojects/FSharp.Collections.Immutable/compare/releases/1.0.0...HEAD +[1.0.0]: https://github.com/fsprojects/FSharp.Collections.Immutable/releases/tag/releases/1.0.0 diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..d14b42f --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,49 @@ + + + + + F#;FSharp;Collections;Immutable + https://github.com/fsprojects/FSharp.Collections.Immutable + false + LICENSE + README.md + logo.png + + git + fsprojects, EventHelix;vilinski;anthony-mi;dim-37 + https://github.com/fsprojects/FSharp.Collections.Immutable + + true + + true + snupkg + true + + + + FSharp.Collections.Immutable + 9.0 + enable + true + false + true + + + + + + + + + + + + + + diff --git a/Directory.Build.targets b/Directory.Build.targets new file mode 100644 index 0000000..484ac5a --- /dev/null +++ b/Directory.Build.targets @@ -0,0 +1,47 @@ + + + + + <_BuildProjBaseIntermediateOutputPath>$(MSBuildThisFileDirectory)build/obj/ + <_DotnetToolManifestFile>$(MSBuildThisFileDirectory).config/dotnet-tools.json + <_DotnetToolRestoreOutputFile>$(_BuildProjBaseIntermediateOutputPath)/dotnet-tool-restore-$(NETCoreSdkVersion)-$(OS) + <_DotnetFantomasOutputFile>$(BaseIntermediateOutputPath)dotnet-fantomas-msbuild-$(NETCoreSdkVersion)-$(OS) + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Directory.Packages.props b/Directory.Packages.props new file mode 100644 index 0000000..b822078 --- /dev/null +++ b/Directory.Packages.props @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/FSharp.Collections.Immutable.sln b/FSharp.Collections.Immutable.sln deleted file mode 100644 index a9324bb..0000000 --- a/FSharp.Collections.Immutable.sln +++ /dev/null @@ -1,34 +0,0 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29021.104 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharp.Collections.Immutable", "src\FSharp.Collections.Immutable\FSharp.Collections.Immutable.fsproj", "{9805E74C-D028-4D05-9256-5C2FDC9B6EA8}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{9BE8C74B-E5EF-4A0E-8E77-CDCD967B6E88}" - ProjectSection(SolutionItems) = preProject - .github\workflows\build.yml = .github\workflows\build.yml - .github\workflows\publish_ci.yml = .github\workflows\publish_ci.yml - .github\workflows\publish_release.yml = .github\workflows\publish_release.yml - EndProjectSection -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {9805E74C-D028-4D05-9256-5C2FDC9B6EA8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9805E74C-D028-4D05-9256-5C2FDC9B6EA8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9805E74C-D028-4D05-9256-5C2FDC9B6EA8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9805E74C-D028-4D05-9256-5C2FDC9B6EA8}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {5A4E44BA-0D9F-4E81-A35E-83A38C3E43CF} - EndGlobalSection - GlobalSection(CodealikeProperties) = postSolution - SolutionGuid = f92c102d-027e-479b-995c-b971032fea8c - EndGlobalSection -EndGlobal diff --git a/FSharp.Collections.Immutable.slnf b/FSharp.Collections.Immutable.slnf new file mode 100644 index 0000000..097e0fa --- /dev/null +++ b/FSharp.Collections.Immutable.slnf @@ -0,0 +1,9 @@ +{ + "solution": { + "path": "FSharp.Collections.Immutable.slnx", + "projects": [ + "src\\FSharp.Collections.Immutable\\FSharp.Collections.Immutable.fsproj", + "tests\\FSharp.Collections.Immutable.Tests\\FSharp.Collections.Immutable.Tests.fsproj" + ] + } +} \ No newline at end of file diff --git a/FSharp.Collections.Immutable.slnx b/FSharp.Collections.Immutable.slnx new file mode 100644 index 0000000..e0ade1a --- /dev/null +++ b/FSharp.Collections.Immutable.slnx @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/README.md b/README.md index d2bc569..7819e3f 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,122 @@ -# FSharp.Collections.Immutable [![NuGet Status](http://img.shields.io/nuget/v/FSharp.Collections.Immutable.svg?style=flat)](https://www.nuget.org/packages/FSharp.Collections.Immutable/) [![MyGet Status](https://img.shields.io/myget/fsharp-collections-immutable/v/fsharp.collections.immutable.svg?style=flat)](https://www.myget.org/feed/Packages/fsharp-collections-immutable) +# FSharp.Collections.Immutable [![NuGet Status](http://img.shields.io/nuget/v/FSharp.Collections.Immutable.svg?style=flat)](https://www.nuget.org/packages/FSharp.Collections.Immutable/) [![GitHub Actions](https://github.com/fsprojects/FSharp.Collections.Immutable/workflows/Build%20main/badge.svg)](https://github.com/fsprojects/FSharp.Collections.Immutable/actions?query=branch%3Amain) F# bindings for System.Collections.Immutable ![Build status](https://github.com/fsprojects/FSharp.Collections.Immutable/workflows/.NET%20Core/badge.svg) -**FSharp.Collections.Immutable** is a collection of F# bindings for System.Collections.Immutable. +**FSharp.Collections.Immutable** is a collection of F# bindings for [System.Collections.Immutable](https://learn.microsoft.com/en-us/dotnet/api/system.collections.immutable). Please contribute to this project. Don't ask for permission, just fork the repository and send pull requests. Please also join the [F# Open Source Group](http://fsharp.github.com) +## Features + +* `FlatList` (`ImmutableArray`) +* `ImmutableList` +* `Stack` (`ImmutableStack`) +* `Queue` (`ImmutableQueue`) +* `HashMap` (`ImmutableDictionary`) +* `SortedMap` (`ImmutableSortedDictionary`) +* `HashSet` (`ImmutableHashSet`) +* `SortedSet` (`ImmutableSortedSet`) +* `IIndexedSeq` (`IReadOnlyList`) + +[Documentation](https://fsprojects.github.io/FSharp.Collections.Immutable/) + +--- + +## Builds + +GitHub Actions | +:---: | +[![GitHub Actions](https://github.com/fsprojects/FSharp.Collections.Immutable/workflows/Build%20main/badge.svg)](https://github.com/fsprojects/FSharp.Collections.Immutable/actions?query=branch%3Amain) | + +## NuGet + +Package | Stable | Prerelease +--- | --- | --- +FSharp.Collections.Immutable | [![NuGet Badge](https://img.shields.io/nuget/v/FSharp.Collections.Immutable.svg)](https://www.nuget.org/packages/FSharp.Collections.Immutable/) | [![NuGet Badge](https://img.shields.io/nuget/vpre/FSharp.Collections.Immutable.svg)](https://www.nuget.org/packages/FSharp.Collections.Immutable/) + +--- + +### Developing + +Make sure the following **requirements** are installed on your system: + +- [dotnet SDK](https://www.microsoft.com/net/download/core) 8.0 or higher + +or + +- [VSCode Dev Container](https://code.visualstudio.com/docs/remote/containers) + + +--- + +### Environment Variables + +- `CONFIGURATION` will set the [configuration](https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-build?tabs=netcore2x#options) of the dotnet commands. If not set, it will default to Release. + - `CONFIGURATION=Debug ./build.sh` will result in `-c` additions to commands such as in `dotnet build -c Debug` +- `ENABLE_COVERAGE` Will enable running code coverage metrics. AltCover can have [severe performance degradation](https://github.com/SteveGilham/altcover/issues/57) so code coverage evaluation are disabled by default to speed up the feedback loop. + - `ENABLE_COVERAGE=1 ./build.sh` will enable code coverage evaluation + + +--- + +### Building +> build.cmd // on windows + +> ./build.sh // on unix +--- + +### Build Targets + +- `Clean` - Cleans artifact and temp directories. +- `DotnetRestore` - Runs [dotnet restore](https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-restore?tabs=netcore2x) on the [solution file](https://docs.microsoft.com/en-us/visualstudio/extensibility/internals/solution-dot-sln-file?view=vs-2019). +- [`DotnetBuild`](#Building) - Runs [dotnet build](https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-build?tabs=netcore2x) on the [solution file](https://docs.microsoft.com/en-us/visualstudio/extensibility/internals/solution-dot-sln-file?view=vs-2019). +- `FSharpAnalyzers` - Runs [BinaryDefense.FSharp.Analyzers](https://github.com/BinaryDefense/BinaryDefense.FSharp.Analyzers). +- `DotnetTest` - Runs [dotnet test](https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-test?tabs=netcore21) on the [solution file](https://docs.microsoft.com/en-us/visualstudio/extensibility/internals/solution-dot-sln-file?view=vs-2019). +- `GenerateCoverageReport` - Code coverage is run during `DotnetTest` and this generates a report via [ReportGenerator](https://github.com/danielpalme/ReportGenerator). +- `ShowCoverageReport` - Shows the report generated in `GenerateCoverageReport`. +- `WatchTests` - Runs [dotnet watch](https://docs.microsoft.com/en-us/aspnet/core/tutorials/dotnet-watch?view=aspnetcore-3.0) with the test projects. Useful for rapid feedback loops. +- `GenerateAssemblyInfo` - Generates [AssemblyInfo](https://docs.microsoft.com/en-us/dotnet/api/microsoft.visualbasic.applicationservices.assemblyinfo?view=netframework-4.8) for libraries. +- `DotnetPack` - Runs [dotnet pack](https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-pack). This includes running [Source Link](https://github.com/dotnet/sourcelink). +- `SourceLinkTest` - Runs a Source Link test tool to verify Source Links were properly generated. +- `PublishToNuGet` - Publishes the NuGet packages generated in `DotnetPack` to NuGet via [paket push](https://fsprojects.github.io/Paket/paket-push.html). Runs only from `Github Actions`. +- `GitRelease` - Creates a commit message with the [Release Notes](https://fake.build/apidocs/v5/fake-core-releasenotes.html) and a git tag via the version in the `Release Notes`. +- `GitHubRelease` - Publishes a [GitHub Release](https://help.github.com/en/articles/creating-releases) with the Release Notes and any NuGet packages. Runs only from `Github Actions`. +- `FormatCode` - Runs [Fantomas](https://github.com/fsprojects/fantomas) on the solution file. +- `CheckFormatCode` - Runs [Fantomas --check](https://fsprojects.github.io/fantomas/docs/end-users/FormattingCheck.html) on the solution file. +- `BuildDocs` - Generates [Documentation](https://fsprojects.github.io/FSharp.Formatting) from `docsSrc` and the [XML Documentation Comments](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/xmldoc/) from your libraries in `src`. +- `WatchDocs` - Generates documentation and starts a webserver locally. It will rebuild and hot reload if it detects any changes made to `docsSrc` files, or libraries in `src`. + +--- + + +### Releasing + +- [Start a git repo with a remote](https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/) +git init +git add . +git commit -m "Scaffold" +git branch -M main +git remote add origin https://github.com/fsprojects/FSharp.Collections.Immutable.git +git push -u origin main +- [Create an Environment](https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment#creating-an-environment) on your repository named `nuget`. +- [Create a NuGet API key](https://learn.microsoft.com/en-us/nuget/nuget-org/publish-a-package#create-an-api-key) +- Add your `NUGET_TOKEN` to the [Environment Secrets](https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment#environment-secrets) of your newly created environment. +- Then update the `CHANGELOG.md` with an "Unreleased" section containing release notes for this version, in [KeepAChangelog](https://keepachangelog.com/en/1.1.0/) format. + +NOTE: Its highly recommend to add a link to the Pull Request next to the release note that it affects. The reason for this is when the `RELEASE` target is run, it will add these new notes into the body of git commit. GitHub will notice the links and will update the Pull Request with what commit referenced it saying ["added a commit that referenced this pull request"](https://github.com/TheAngryByrd/MiniScaffold/pull/179#ref-commit-837ad59). Since the build script automates the commit message, it will say "Bump Version to x.y.z". The benefit of this is when users goto a Pull Request, it will be clear when and which version those code changes released. Also when reading the `CHANGELOG`, if someone is curious about how or why those changes were made, they can easily discover the work and discussions. + +### Releasing Documentation + +- Set Source for "Build and deployment" on [GitHub Pages](https://github.com/fsprojects/FSharp.Collections.Immutable/settings/pages) to `GitHub Actions`. +- Documentation is auto-deployed via [GitHub Action](https://github.com/fsprojects/FSharp.Collections.Immutable/blob/main/.github/workflows/fsdocs-gh-pages.yml) to [Your GitHub Page](https://fsprojects.github.io/FSharp.Collections.Immutable/) + # Maintainer(s) - [@xperiandri](https://github.com/xperiandri) - [@eventhelix](https://github.com/eventhelix) - -The default maintainer account for projects under "fsprojects" is [@fsprojectsgit](https://github.com/fsprojectsgit) - F# Community Project Incubation Space (repo management) \ No newline at end of file +The default maintainer account for projects under "fsprojects" is [@fsprojectsgit](https://github.com/fsprojectsgit) - F# Community Project Incubation Space (repo management) diff --git a/build.cmd b/build.cmd new file mode 100644 index 0000000..b9f6df4 --- /dev/null +++ b/build.cmd @@ -0,0 +1 @@ +dotnet run --project ./build/build.fsproj -- -t %* diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..0783d63 --- /dev/null +++ b/build.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +set -eu +set -o pipefail + +FAKE_DETAILED_ERRORS=true dotnet run --project ./build/build.fsproj -- -t "$@" diff --git a/build/Changelog.fs b/build/Changelog.fs new file mode 100644 index 0000000..27ceada --- /dev/null +++ b/build/Changelog.fs @@ -0,0 +1,233 @@ +module Changelog + +open System +open Fake.Core +open Fake.IO + +let isEmptyChange = + function + | Changelog.Change.Added s + | Changelog.Change.Changed s + | Changelog.Change.Deprecated s + | Changelog.Change.Fixed s + | Changelog.Change.Removed s + | Changelog.Change.Security s + | Changelog.Change.Custom (_, s) -> String.IsNullOrWhiteSpace s.CleanedText + +let tagFromVersionNumber versionNumber = sprintf "releases/%s" versionNumber + +let failOnEmptyChangelog (latestEntry : Changelog.ChangelogEntry) = + let isEmpty = + (latestEntry.Changes |> Seq.forall isEmptyChange) + || latestEntry.Changes |> Seq.isEmpty + + if isEmpty then + failwith "No changes in CHANGELOG. Please add your changes under a heading specified in https://keepachangelog.com/" + +let mkLinkReference (newVersion : SemVerInfo) (changelog : Changelog.Changelog) (gitHubRepoUrl : string) = + if changelog.Entries |> List.isEmpty then + // No actual changelog entries yet: link reference will just point to the Git tag + sprintf "[%s]: %s/releases/tag/%s" newVersion.AsString gitHubRepoUrl (tagFromVersionNumber newVersion.AsString) + else + let versionTuple version = (version.Major, version.Minor, version.Patch) + // Changelog entries come already sorted, most-recent first, by the Changelog module + let prevEntry = + changelog.Entries + |> List.skipWhile (fun entry -> + entry.SemVer.PreRelease.IsSome + || versionTuple entry.SemVer = versionTuple newVersion + ) + |> List.tryHead + + let linkTarget = + match prevEntry with + | Some entry -> + sprintf + "%s/compare/%s...%s" + gitHubRepoUrl + (tagFromVersionNumber entry.SemVer.AsString) + (tagFromVersionNumber newVersion.AsString) + | None -> sprintf "%s/releases/tag/%s" gitHubRepoUrl (tagFromVersionNumber newVersion.AsString) + + sprintf "[%s]: %s" newVersion.AsString linkTarget + +let mkReleaseNotes changelog (latestEntry : Changelog.ChangelogEntry) gitHubRepoUrl = + let linkReference = mkLinkReference latestEntry.SemVer changelog gitHubRepoUrl + + if String.isNullOrEmpty linkReference then + latestEntry.ToString () + else + // Add link reference target to description before building release notes, since in main changelog file it's at the bottom of the file + let description = + match latestEntry.Description with + | None -> linkReference + | Some desc when desc.Contains (linkReference) -> desc + | Some desc -> sprintf "%s\n\n%s" (desc.Trim ()) linkReference + + { latestEntry with Description = Some description }.ToString () + +let getVersionNumber envVarName ctx = + let args = ctx.Context.Arguments + + let verArg = + args + |> List.tryHead + |> Option.defaultWith (fun () -> Environment.environVarOrDefault envVarName "") + + if SemVer.isValid verArg then + verArg + elif verArg.StartsWith ("v") && SemVer.isValid verArg.[1..] then + let target = ctx.Context.FinalTarget + + Trace.traceImportantfn + "Please specify a version number without leading 'v' next time, e.g. \"./build.sh %s %s\" rather than \"./build.sh %s %s\"" + target + verArg.[1..] + target + verArg + + verArg.[1..] + elif String.isNullOrEmpty verArg then + let target = ctx.Context.FinalTarget + + Trace.traceErrorfn + "Please specify a version number, either at the command line (\"./build.sh %s 1.0.0\") or in the %s environment variable" + target + envVarName + + failwith "No version number found" + else + Trace.traceErrorfn "Please specify a valid version number: %A could not be recognized as a version number" verArg + + failwith "Invalid version number" + +let mutable changelogBackupFilename = "" + +let updateChangelog changelogPath (changelog : Fake.Core.Changelog.Changelog) gitHubRepoUrl ctx = + + let verStr = ctx |> getVersionNumber "RELEASE_VERSION" + + let description, unreleasedChanges = + match changelog.Unreleased with + | None -> None, [] + | Some u -> u.Description, u.Changes + + let newVersion = SemVer.parse verStr + + changelog.Entries + |> List.tryFind (fun entry -> entry.SemVer = newVersion) + |> Option.iter (fun entry -> + Trace.traceErrorfn + "Version %s already exists in %s, released on %s" + verStr + changelogPath + (if entry.Date.IsSome then + entry.Date.Value.ToString ("yyyy-MM-dd") + else + "(no date specified)") + + failwith "Can't release with a duplicate version number" + ) + + changelog.Entries + |> List.tryFind (fun entry -> entry.SemVer > newVersion) + |> Option.iter (fun entry -> + Trace.traceErrorfn + "You're trying to release version %s, but a later version %s already exists, released on %s" + verStr + entry.SemVer.AsString + (if entry.Date.IsSome then + entry.Date.Value.ToString ("yyyy-MM-dd") + else + "(no date specified)") + + failwith "Can't release with a version number older than an existing release" + ) + + let versionTuple version = (version.Major, version.Minor, version.Patch) + + let prereleaseEntries = + changelog.Entries + |> List.filter (fun entry -> + entry.SemVer.PreRelease.IsSome + && versionTuple entry.SemVer = versionTuple newVersion + ) + + let prereleaseChanges = + prereleaseEntries + |> List.collect (fun entry -> entry.Changes |> List.filter (not << isEmptyChange)) + |> List.distinct + + let assemblyVersion, nugetVersion = Changelog.parseVersions newVersion.AsString + + let newEntry = + Changelog.ChangelogEntry.New ( + assemblyVersion.Value, + nugetVersion.Value, + Some System.DateTime.Today, + description, + unreleasedChanges @ prereleaseChanges, + false + ) + + let newChangelog = + Changelog.Changelog.New (changelog.Header, changelog.Description, None, newEntry :: changelog.Entries) + + // Save changelog to temporary file before making any edits + changelogBackupFilename <- System.IO.Path.GetTempFileName () + + changelogPath |> Shell.copyFile changelogBackupFilename + + Target.activateFinal "DeleteChangelogBackupFile" + + newChangelog |> Changelog.save changelogPath + + // Now update the link references at the end of the file + let linkReferenceForLatestEntry = mkLinkReference newVersion changelog gitHubRepoUrl + + let linkReferenceForUnreleased = + sprintf "[Unreleased]: %s/compare/%s...%s" gitHubRepoUrl (tagFromVersionNumber newVersion.AsString) "HEAD" + + let tailLines = File.read changelogPath |> List.ofSeq |> List.rev + + let isRef (line : string) = + System.Text.RegularExpressions.Regex.IsMatch (line, @"^\[.+?\]:\s?[a-z]+://.*$") + + let linkReferenceTargets = + tailLines + |> List.skipWhile String.isNullOrWhiteSpace + |> List.takeWhile isRef + |> List.rev // Now most recent entry is at the head of the list + + let newLinkReferenceTargets = + match linkReferenceTargets with + | [] -> [ linkReferenceForUnreleased; linkReferenceForLatestEntry ] + | first :: rest when first |> String.startsWith "[Unreleased]:" -> + linkReferenceForUnreleased + :: linkReferenceForLatestEntry + :: rest + | first :: rest -> + linkReferenceForUnreleased + :: linkReferenceForLatestEntry + :: first + :: rest + + let blankLineCount = + tailLines + |> Seq.takeWhile String.isNullOrWhiteSpace + |> Seq.length + + let linkRefCount = linkReferenceTargets |> List.length + + let skipCount = blankLineCount + linkRefCount + + let updatedLines = + List.rev (tailLines |> List.skip skipCount) + @ newLinkReferenceTargets + + File.write false changelogPath updatedLines + + // If build fails after this point but before we commit changes, undo our modifications + Target.activateBuildFailure "RevertChangelog" + + newEntry diff --git a/build/FsDocs.fs b/build/FsDocs.fs new file mode 100644 index 0000000..8c16481 --- /dev/null +++ b/build/FsDocs.fs @@ -0,0 +1,224 @@ +namespace Fake.DotNet + +open Fake.Core +open Fake.IO +open Fake.IO.FileSystemOperators + +/// +/// Contains tasks to interact with fsdocs tool to +/// process F# script files, markdown and for generating API documentation. +/// +[] +module Fsdocs = + + /// + /// Fsdocs build command parameters and options + /// + type BuildCommandParams = { + /// Input directory of content (default: docs) + Input : string option + + /// Project files to build API docs for outputs, defaults to all packable projects + Projects : seq option + + /// Output Directory (default output for build and tmp/watch for watch) + Output : string option + + /// Disable generation of API docs + NoApiDocs : bool option + + /// Evaluate F# fragments in scripts + Eval : bool option + + /// Save images referenced in docs + SaveImages : bool option + + /// Add line numbers + LineNumbers : bool option + + /// Additional substitution parameters for templates + Parameters : seq option + + /// Disable project cracking. + IgnoreProjects : bool option + + /// In API doc generation qualify the output by the collection name, e.g. 'reference/FSharp.Core/...' instead of 'reference/...' . + Qualify : bool option + + /// The tool will also generate documentation for non-public members + NoPublic : bool option + + /// Do not copy default content styles, javascript or use default templates + NoDefaultContent : bool option + + /// Clean the output directory + Clean : bool option + + /// Display version information + Version : bool option + + /// Provide properties to dotnet msbuild, e.g. --properties Configuration=Release Version=3.4 + Properties : string option + + /// Additional arguments passed down as otherflags to the F# compiler when the API is being generated. + /// Note that these arguments are trimmed, this is to overcome a limitation in the command line argument + /// processing. A typical use-case would be to pass an addition assembly reference. + /// Example --fscoptions " -r:MyAssembly.dll" + FscOptions : string option + + /// Fail if docs are missing or can't be generated + Strict : bool option + + /// Source folder at time of component build (<FsDocsSourceFolder>) + SourceFolder : string option + + /// Source repository for github links (<FsDocsSourceRepository>) + SourceRepository : string option + + /// Assume comments in F# code are markdown (<UsesMarkdownComments>) + MdComments : bool option + } with + + /// Parameter default values. + static member Default = { + Input = None + Projects = None + Output = None + NoApiDocs = None + Eval = None + SaveImages = None + LineNumbers = None + Parameters = None + IgnoreProjects = None + Qualify = None + NoPublic = None + NoDefaultContent = None + Clean = None + Version = None + Properties = None + FscOptions = None + Strict = None + SourceFolder = None + SourceRepository = None + MdComments = None + } + + /// + /// Fsdocs watch command parameters and options + /// + type WatchCommandParams = { + /// Do not serve content when watching. + NoServer : bool option + + /// Do not launch a browser window. + NoLaunch : bool option + + /// URL extension to launch http://localhost:/%s. + Open : string option + + /// Port to serve content for http://localhost serving. + Port : int option + + /// Build Commands + BuildCommandParams : BuildCommandParams option + } with + + /// Parameter default values. + static member Default = { + NoServer = None + NoLaunch = None + Open = None + Port = None + BuildCommandParams = None + } + + let internal buildBuildCommandParams (buildParams : BuildCommandParams) = + let buildSubstitutionParameters (subParameters : seq) = + seq { + yield "--parameters" + for (key, value) in subParameters do + yield key + yield value + } + |> String.concat " " + + System.Text.StringBuilder () + |> StringBuilder.appendIfSome buildParams.Input (sprintf "--input %s") + |> StringBuilder.appendIfSome buildParams.Projects (fun projects -> $"""--projects %s{projects |> String.concat " "}""") + |> StringBuilder.appendIfSome buildParams.Output (sprintf "--output %s") + |> StringBuilder.appendIfSome buildParams.NoApiDocs (fun _ -> "--noapidocs") + |> StringBuilder.appendIfSome buildParams.Eval (fun _ -> "--eval") + |> StringBuilder.appendIfSome buildParams.SaveImages (fun _ -> "--saveimages") + |> StringBuilder.appendIfSome buildParams.LineNumbers (fun _ -> "--linenumbers") + |> StringBuilder.appendIfSome buildParams.Parameters (fun parameters -> buildSubstitutionParameters parameters) + |> StringBuilder.appendIfSome buildParams.IgnoreProjects (fun _ -> "--ignoreprojects") + |> StringBuilder.appendIfSome buildParams.Qualify (fun _ -> "--qualify") + |> StringBuilder.appendIfSome buildParams.NoPublic (fun _ -> "--nonpublic") + |> StringBuilder.appendIfSome buildParams.NoDefaultContent (fun _ -> "--nodefaultcontent") + |> StringBuilder.appendIfSome buildParams.Clean (fun _ -> "--clean") + |> StringBuilder.appendIfSome buildParams.Version (fun _ -> "--version") + |> StringBuilder.appendIfSome buildParams.Properties (sprintf "--properties %s") + |> StringBuilder.appendIfSome buildParams.FscOptions (sprintf "--fscoptions %s") + |> StringBuilder.appendIfSome buildParams.Strict (fun _ -> "--strict") + |> StringBuilder.appendIfSome buildParams.SourceFolder (sprintf "--sourcefolder %s") + |> StringBuilder.appendIfSome buildParams.SourceRepository (sprintf "--sourcerepo %s") + |> StringBuilder.appendIfSome buildParams.MdComments (fun _ -> "--mdcomments") + |> StringBuilder.toText + |> String.trim + + let internal buildWatchCommandParams (watchParams : WatchCommandParams) = + System.Text.StringBuilder () + |> StringBuilder.appendIfSome watchParams.NoServer (fun _ -> "--noserver") + |> StringBuilder.appendIfSome watchParams.NoLaunch (fun _ -> "--nolaunch") + |> StringBuilder.appendIfSome watchParams.Open (sprintf "--open %s") + |> StringBuilder.appendIfSome watchParams.Port (sprintf "--port %i") + |> StringBuilder.appendIfSome watchParams.BuildCommandParams buildBuildCommandParams + |> StringBuilder.toText + |> String.trim + + + let cleanCache (workingDirectory) = Shell.cleanDirs [ workingDirectory ".fsdocs" ] + + /// + /// Build documentation using fsdocs build command + /// + /// + /// Function used to overwrite the dotnetOptions. + /// Function used to overwrite the build command default parameters. + /// + /// + /// + /// Fsdocs.build (fun p -> { p with Clean = Some(true); Strict = Some(true) }) + /// + /// + let build dotnetOptions setBuildParams = + let buildParams = setBuildParams BuildCommandParams.Default + let formattedParameters = buildBuildCommandParams buildParams + + // let dotnetOptions = (fun (buildOptions: DotNet.Options) -> buildOptions) + let result = DotNet.exec dotnetOptions "fsdocs build" formattedParameters + + if 0 <> result.ExitCode then + failwithf "fsdocs build failed with exit code '%d'" result.ExitCode + + /// + /// Watch documentation using fsdocs watch command + /// + /// + /// Function used to overwrite the dotnetOptions. + /// Function used to overwrite the watch command default parameters. + /// + /// + /// + /// Fsdocs.watch (fun p -> { p with Port = Some(3005) }) + /// + /// + let watch dotnetOptions setWatchParams = + let watchParams = setWatchParams WatchCommandParams.Default + let formattedParameters = buildWatchCommandParams watchParams + + // let dotnetOptions = (fun (buildOptions: DotNet.Options) -> buildOptions) + let result = DotNet.exec dotnetOptions "fsdocs watch" formattedParameters + + if 0 <> result.ExitCode then + failwithf "fsdocs watch failed with exit code '%d'" result.ExitCode diff --git a/build/Properties/launchSettings.json b/build/Properties/launchSettings.json new file mode 100644 index 0000000..f0c59e8 --- /dev/null +++ b/build/Properties/launchSettings.json @@ -0,0 +1,24 @@ +{ + "profiles": { + "BuildAndTest": { + "commandName": "Project", + "commandLineArgs": "--target DotnetTest" + }, + "Publish": { + "commandName": "Project", + "commandLineArgs": "--target Publish" + }, + "PublishToGitHub": { + "commandName": "Project", + "commandLineArgs": "--target PublishToGitHub" + }, + "BuildDocs": { + "commandName": "Project", + "commandLineArgs": "--target BuildDocs" + }, + "Release": { + "commandName": "Project", + "commandLineArgs": "--target Release 1.0.0" + } + } +} diff --git a/build/build.fs b/build/build.fs new file mode 100644 index 0000000..c28e99c --- /dev/null +++ b/build/build.fs @@ -0,0 +1,731 @@ +open System +open Fake.Core +open Fake.DotNet +open Fake.Tools +open Fake.IO +open Fake.IO.FileSystemOperators +open Fake.IO.Globbing.Operators +open Fake.Core.TargetOperators +open Fake.Api +open Fake.BuildServer +open Argu + +let environVarAsBoolOrDefault varName defaultValue = + let truthyConsts = [ "1"; "Y"; "YES"; "T"; "TRUE" ] + Environment.environVar varName + |> ValueOption.ofObj + |> ValueOption.map (fun envvar -> + truthyConsts + |> List.exists (fun ``const`` -> String.Equals (``const``, envvar, StringComparison.InvariantCultureIgnoreCase)) + ) + |> ValueOption.defaultValue defaultValue + +//----------------------------------------------------------------------------- +// Metadata and Configuration +//----------------------------------------------------------------------------- + +let rootDirectory = __SOURCE_DIRECTORY__ ".." + +let productName = "FSharp.Collections.Immutable" + +let sln = rootDirectory "FSharp.Collections.Immutable.slnf" + +let srcCodeGlob = + !!(rootDirectory "src/**/*.fs") + ++ (rootDirectory "src/**/*.fsx") + -- (rootDirectory "src/**/obj/**/*.fs") + +let testsCodeGlob = + !!(rootDirectory "tests/**/*.fs") + ++ (rootDirectory "tests/**/*.fsx") + -- (rootDirectory "tests/**/obj/**/*.fs") + +let srcGlob = rootDirectory "src/**/*.??proj" + +let testsGlob = rootDirectory "tests/**/*.??proj" + +let srcAndTest = !!srcGlob ++ testsGlob + +let distDir = rootDirectory "dist" + +let distGlob = distDir "*.nupkg" + +let testResultsDir = rootDirectory "TestResults" + +let coverageReportDir = rootDirectory "docs" "coverage" + +let docsDir = rootDirectory "docs" + +let docsSrcDir = rootDirectory "docsSrc" + +let temp = rootDirectory "temp" + +let watchDocsDir = temp "watch-docs" + +let gitOwner = "fsprojects" +let gitRepoName = "FSharp.Collections.Immutable" + +let gitHubRepoUrl = $"https://github.com/%s{gitOwner}/%s{gitRepoName}" + +let documentationRootUrl = $"https://%s{gitOwner}.github.io/%s{gitRepoName}" + +let releaseBranch = "main" +let readme = "README.md" +let changelogFile = "CHANGELOG.md" + +let READMElink = Uri (Uri (gitHubRepoUrl), $"blob/{releaseBranch}/{readme}") +let CHANGELOGlink = Uri (Uri (gitHubRepoUrl), $"blob/{releaseBranch}/{changelogFile}") + +let changelogPath = rootDirectory changelogFile + +let changelog = Fake.Core.Changelog.load changelogPath + +let mutable latestEntry = + if Seq.isEmpty changelog.Entries then + Changelog.ChangelogEntry.New ("0.0.1", "0.0.1-alpha.1", Some DateTime.Today, None, [], false) + else + changelog.LatestEntry + +let mutable changelogBackupFilename = "" + +let publishUrl = "https://www.nuget.org" + +let enableCodeCoverage = environVarAsBoolOrDefault "ENABLE_COVERAGE" false + +let githubToken = Environment.environVarOrNone "GITHUB_TOKEN" + +let nugetToken = Environment.environVarOrNone "NUGET_TOKEN" + +//----------------------------------------------------------------------------- +// Helpers +//----------------------------------------------------------------------------- + + +let isRelease (targets : Target list) = + targets + |> Seq.map (fun t -> t.Name) + |> Seq.exists ((=) "PublishToNuGet") + +let invokeAsync f = async { f () } + +let configuration (targets : Target list) = + let defaultVal = if isRelease targets then "Release" else "Debug" + + match Environment.environVarOrDefault "CONFIGURATION" defaultVal with + | "Debug" -> DotNet.BuildConfiguration.Debug + | "Release" -> DotNet.BuildConfiguration.Release + | config -> DotNet.BuildConfiguration.Custom config + +let failOnBadExitAndPrint (p : ProcessResult) = + if p.ExitCode <> 0 then + p.Errors |> Seq.iter Trace.traceError + + failwithf "failed with exitcode %d" p.ExitCode + +let isPublishToGitHub ctx = ctx.Context.FinalTarget = "PublishToGitHub" + +let isCI = lazy environVarAsBoolOrDefault "CI" false + +// CI Servers can have bizarre failures that have nothing to do with your code +let rec retryIfInCI times fn = + match isCI.Value with + | true -> + if times > 1 then + try + fn () + with _ -> + retryIfInCI (times - 1) fn + else + fn () + | _ -> fn () + +let failOnWrongBranch () = + if Git.Information.getBranchName "" <> releaseBranch then + failwithf "Not on %s. If you want to release please switch to this branch." releaseBranch + + +module dotnet = + let watch cmdParam program args = DotNet.exec cmdParam (sprintf "watch %s" program) args + + let run cmdParam args = DotNet.exec cmdParam "run" args + + let tool optionConfig command args = + DotNet.exec optionConfig (sprintf "%s" command) args + |> failOnBadExitAndPrint + + let reportgenerator optionConfig args = tool optionConfig "reportgenerator" args + + let sourcelink optionConfig args = tool optionConfig "sourcelink" args + + let fcswatch optionConfig args = tool optionConfig "fcswatch" args + + let fsharpAnalyzer optionConfig args = tool optionConfig "fsharp-analyzers" args + + let fantomas args = DotNet.exec id "fantomas" args + +module FSharpAnalyzers = + type Arguments = + | Project of string + | Analyzers_Path of string + | Fail_On_Warnings of string list + | Ignore_Files of string list + | Verbose + + interface IArgParserTemplate with + member s.Usage = "" + + +module DocsTool = + let quoted s = $"\"%s{s}\"" + + let fsDocsDotnetOptions (o : DotNet.Options) = { o with WorkingDirectory = rootDirectory } + + let fsDocsBuildParams configuration (p : Fsdocs.BuildCommandParams) = { + p with + Clean = Some true + Input = Some (quoted docsSrcDir) + Output = Some (quoted docsDir) + Eval = Some true + Projects = Some (Seq.map quoted (!!srcGlob)) + Properties = Some ($"Configuration=%s{configuration}") + Parameters = + Some [ + // https://fsprojects.github.io/FSharp.Formatting/content.html#Templates-and-Substitutions + "root", quoted $"{documentationRootUrl}/" + "fsdocs-collection-name", quoted productName + "fsdocs-repository-branch", quoted releaseBranch + "fsdocs-package-version", quoted latestEntry.NuGetVersion + "fsdocs-readme-link", quoted (READMElink.ToString ()) + "fsdocs-release-notes-link", quoted (CHANGELOGlink.ToString ()) + "fsdocs-logo-src", + quoted ( + "https://raw.githubusercontent.com/fsprojects/FSharp.Collections.Immutable/refs/heads/main/docsSrc/content/logo.png" + ) + ] + Strict = Some true + } + + let cleanDocsCache () = Fsdocs.cleanCache rootDirectory + + let build (configuration) = Fsdocs.build fsDocsDotnetOptions (fsDocsBuildParams configuration) + + + let watch (configuration) = + let buildParams bp = + let bp = + Option.defaultValue Fsdocs.BuildCommandParams.Default bp + |> fsDocsBuildParams configuration + + { bp with Output = Some watchDocsDir; Strict = None } + + Fsdocs.watch fsDocsDotnetOptions (fun p -> { p with BuildCommandParams = Some (buildParams p.BuildCommandParams) }) + +let allReleaseChecks () = failOnWrongBranch () +//Changelog.failOnEmptyChangelog latestEntry + + +let failOnLocalBuild () = + if not isCI.Value then + failwith "Not on CI. If you want to publish, please use CI." + +let failOnCIBuild () = + if isCI.Value then + failwith "On CI. If you want to run this target, please use a local build." + +let allPublishChecks () = failOnLocalBuild () +//Changelog.failOnEmptyChangelog latestEntry + +//----------------------------------------------------------------------------- +// Target Implementations +//----------------------------------------------------------------------------- + +/// So we don't require always being on the latest MSBuild.StructuredLogger +let disableBinLog (p : MSBuild.CliArguments) = { p with DisableInternalBinLog = true } + +let clean _ = + [ "bin"; "temp"; distDir; coverageReportDir; testResultsDir ] + |> Shell.cleanDirs + + !!srcGlob ++ testsGlob + |> Seq.collect (fun p -> + [ "bin"; "obj" ] + |> Seq.map (fun sp -> IO.Path.GetDirectoryName p sp) + ) + |> Shell.cleanDirs + +let dotnetRestore _ = + [ sln ] + |> Seq.map (fun dir -> + fun () -> + let args = [] |> String.concat " " + + DotNet.restore + (fun c -> { + c with + MSBuildParams = disableBinLog c.MSBuildParams + Common = c.Common |> DotNet.Options.withCustomParams (Some (args)) + }) + dir + ) + |> Seq.iter (retryIfInCI 10) + +let dotnetToolRestore _ = + let result = + fun () -> DotNet.exec id "tool" "restore" + |> (retryIfInCI 10) + + + if not result.OK then + failwithf "Failed to restore .NET tools: %A" result.Errors + +let updateChangelog ctx = + latestEntry <- + if not <| isPublishToGitHub ctx then + Changelog.updateChangelog changelogPath changelog gitHubRepoUrl ctx + elif Seq.isEmpty changelog.Entries then + latestEntry + else + let latest = changelog.LatestEntry + let semVer = { + latest.SemVer with + Original = None + Patch = latest.SemVer.Patch + 1u + PreRelease = PreRelease.TryParse "ci" + } + { + latest with + SemVer = semVer + NuGetVersion = semVer.AsString + AssemblyVersion = semVer.AsString + } + +let revertChangelog _ = + if String.isNotNullOrEmpty Changelog.changelogBackupFilename then + Changelog.changelogBackupFilename + |> Shell.copyFile changelogPath + +let deleteChangelogBackupFile _ = + if String.isNotNullOrEmpty Changelog.changelogBackupFilename then + Shell.rm Changelog.changelogBackupFilename + +let getPackageVersionProperty publishToGitHub = + if publishToGitHub then + let runId = Environment.environVar "GITHUB_RUN_ID" + $"/p:PackageVersion=%s{latestEntry.NuGetVersion}-%s{runId}" + else + $"/p:PackageVersion=%s{latestEntry.NuGetVersion}" + +let dotnetBuild ctx = + + let publishToGitHub = isPublishToGitHub ctx + + let args = [ getPackageVersionProperty publishToGitHub; "--no-restore" ] + + DotNet.build + (fun c -> { + c with + Configuration = configuration (ctx.Context.AllExecutingTargets) + Common = c.Common |> DotNet.Options.withAdditionalArgs args + MSBuildParams = { + (disableBinLog c.MSBuildParams) with + Properties = [ + if publishToGitHub then + ("DebugType", "embedded") + ("EmbedAllSources", "true") + ] + } + }) + sln + +let fsharpAnalyzers _ = + let argParser = ArgumentParser.Create (programName = "fsharp-analyzers") + + !!srcGlob + |> Seq.iter (fun proj -> + let args = + [ + FSharpAnalyzers.Analyzers_Path (rootDirectory "packages/analyzers") + FSharpAnalyzers.Arguments.Project proj + FSharpAnalyzers.Arguments.Fail_On_Warnings [ "BDH0002" ] + FSharpAnalyzers.Arguments.Ignore_Files [ "*AssemblyInfo.fs" ] + FSharpAnalyzers.Verbose + ] + |> argParser.PrintCommandLineArgumentsFlat + + dotnet.fsharpAnalyzer id args + ) + +let dotnetTest ctx = + // Create test results directory if it doesn't exist + Directory.create testResultsDir + + let args = [ + "--no-build" + if enableCodeCoverage then + "--collect:\"Code Coverage\"" + "--results-directory" + testResultsDir + "--logger:trx" // Enable TRX report generation + ] + + DotNet.test + (fun c -> { + c with + MSBuildParams = disableBinLog c.MSBuildParams + Configuration = configuration (ctx.Context.AllExecutingTargets) + Common = c.Common |> DotNet.Options.withAdditionalArgs args + }) + sln + +let generateCoverageReport _ = + + let coverageFiles = !!(testResultsDir "*/coverage.cobertura.xml") + + let sourceDirs = !!srcGlob |> Seq.map Path.getDirectory |> String.concat ";" + + let independentArgs = [ + sprintf "-reports:\"%s\"" (coverageFiles |> String.concat ";") + sprintf "-targetdir:\"%s\"" coverageReportDir + // Add source dir + sprintf "-sourcedirs:\"%s\"" sourceDirs + // Ignore test assemblies + sprintf "-assemblyfilters:\"%s\"" "-*.Tests" + // Generate HTML and Cobertura reports + sprintf "-reporttypes:%s" "Html;Cobertura" + ] + + let args = independentArgs |> String.concat " " + + dotnet.reportgenerator id args + +let showCoverageReport _ = + failOnCIBuild () + + coverageReportDir "index.html" + |> Command.ShellCommand + |> CreateProcess.fromCommand + |> Proc.start + |> ignore + + +let watchTests _ = + !!testsGlob + |> Seq.map (fun proj -> + fun () -> + dotnet.watch + (fun opt -> + opt + |> DotNet.Options.withWorkingDirectory (IO.Path.GetDirectoryName proj) + ) + "test" + "" + |> ignore + ) + |> Seq.iter (invokeAsync >> Async.Catch >> Async.Ignore >> Async.Start) + + printfn "Press Ctrl+C (or Ctrl+Break) to stop..." + + let cancelEvent = + Console.CancelKeyPress + |> Async.AwaitEvent + |> Async.RunSynchronously + + cancelEvent.Cancel <- true + +let generateAssemblyInfo _ = + + let (|Fsproj|Csproj|Vbproj|) (projFileName : string) = + match projFileName with + | f when f.EndsWith ("fsproj") -> Fsproj + | f when f.EndsWith ("csproj") -> Csproj + | f when f.EndsWith ("vbproj") -> Vbproj + | _ -> failwith (sprintf "Project file %s not supported. Unknown project type." projFileName) + + let releaseChannel = + match latestEntry.SemVer.PreRelease with + | Some pr -> pr.Name + | _ -> "release" + + let getAssemblyInfoAttributes projectName = [ + AssemblyInfo.Title (projectName) + AssemblyInfo.Product productName + AssemblyInfo.Version latestEntry.AssemblyVersion + AssemblyInfo.Metadata ("ReleaseDate", latestEntry.Date.Value.ToString ("o")) + AssemblyInfo.FileVersion latestEntry.AssemblyVersion + AssemblyInfo.InformationalVersion latestEntry.AssemblyVersion + AssemblyInfo.Metadata ("ReleaseChannel", releaseChannel) + AssemblyInfo.Metadata ("GitHash", Git.Information.getCurrentSHA1 (null)) + ] + + let getProjectDetails (projectPath : string) = + let projectName = IO.Path.GetFileNameWithoutExtension (projectPath) + + (projectPath, projectName, IO.Path.GetDirectoryName (projectPath), (getAssemblyInfoAttributes projectName)) + + !!srcGlob + |> Seq.map getProjectDetails + |> Seq.iter (fun (projFileName, _, folderName, attributes) -> + match projFileName with + | Fsproj -> AssemblyInfoFile.createFSharp (folderName "AssemblyInfo.fs") attributes + | Csproj -> AssemblyInfoFile.createCSharp ((folderName "Properties") "AssemblyInfo.cs") attributes + | Vbproj -> AssemblyInfoFile.createVisualBasic ((folderName "My Project") "AssemblyInfo.vb") attributes + ) + +let dotnetPack ctx = + // Get release notes with properly-linked version number + let releaseNotes = Changelog.mkReleaseNotes changelog latestEntry gitHubRepoUrl + + let args = [ getPackageVersionProperty (isPublishToGitHub ctx); $"/p:PackageReleaseNotes=\"{releaseNotes}\"" ] + + DotNet.pack + (fun c -> { + c with + MSBuildParams = disableBinLog c.MSBuildParams + Configuration = configuration (ctx.Context.AllExecutingTargets) + OutputPath = Some distDir + Common = c.Common |> DotNet.Options.withAdditionalArgs args + }) + sln + +let sourceLinkTest _ = + !!distGlob + |> Seq.iter (fun nupkg -> dotnet.sourcelink id $"test %s{nupkg}") + +type PushSource = + | NuGet + | GitHub + +let publishTo (source : PushSource) _ = + allPublishChecks () + + distGlob + |> DotNet.nugetPush (fun o -> { + o with + Common = { + o.Common with + WorkingDirectory = "dist" + CustomParams = Some "--skip-duplicate" + } + PushParams = { + o.PushParams with + // TODO: Uncomment when migrated to F# 9 + //NoSymbols = source.IsGitHub + Source = + match source with + | NuGet -> Some "nuget.org" + | GitHub -> Some "github.com" + ApiKey = + match source with + | NuGet -> nugetToken + | GitHub -> githubToken + } + }) + +let gitRelease _ = + allReleaseChecks () + + let releaseNotesGitCommitFormat = latestEntry.ToString () + + Git.Staging.stageFile "" (rootDirectory "CHANGELOG.md") + |> ignore + + !!(rootDirectory "src/**/AssemblyInfo.fs") + ++ (rootDirectory "tests/**/AssemblyInfo.fs") + |> Seq.iter (Git.Staging.stageFile "" >> ignore) + + let msg = $"Bump version to `%s{latestEntry.NuGetVersion}`\n\n%s{releaseNotesGitCommitFormat}" + + Git.Commit.exec "" msg + + Target.deactivateBuildFailure "RevertChangelog" + + Git.Branches.push "" + + let tag = Changelog.tagFromVersionNumber latestEntry.NuGetVersion + + Git.Branches.tag "" tag + Git.Branches.pushTag "" "origin" tag + +let githubRelease _ = + allPublishChecks () + + let token = + match githubToken with + | Some s -> s + | _ -> failwith "please set the `GITHUB_TOKEN` environment variable to a github personal access token with repo access." + + let files = !!distGlob + // Get release notes with properly-linked version number + let releaseNotes = Changelog.mkReleaseNotes changelog latestEntry gitHubRepoUrl + + GitHub.createClientWithToken token + |> GitHub.draftNewRelease + gitOwner + gitRepoName + (Changelog.tagFromVersionNumber latestEntry.NuGetVersion) + (latestEntry.SemVer.PreRelease <> None) + (releaseNotes |> Seq.singleton) + |> GitHub.uploadFiles files + |> GitHub.publishDraft + |> Async.RunSynchronously + +let formatCode _ = + let result = dotnet.fantomas $"{rootDirectory}" + + if not result.OK then + printfn "Errors while formatting all files: %A" result.Messages + +let checkFormatCode ctx = + let result = dotnet.fantomas $"{rootDirectory} --check" + + if result.ExitCode = 0 then + Trace.log "No files need formatting" + elif result.ExitCode = 99 then + failwith "Some files need formatting, check output for more info" + else + Trace.logf "Errors while formatting: %A" result.Errors + + +let cleanDocsCache _ = DocsTool.cleanDocsCache () + +let buildDocs ctx = + let configuration = configuration (ctx.Context.AllExecutingTargets) + DocsTool.build (string configuration) + +let watchDocs ctx = + let configuration = configuration (ctx.Context.AllExecutingTargets) + DocsTool.watch (string configuration) + + +let initTargets (ctx : Context.FakeExecutionContext) = + BuildServer.install [ GitHubActions.Installer ] + + let isPublishToGitHub = + ctx.Arguments + |> Seq.pairwise + |> Seq.exists (fun (arg, value) -> + (String.Equals (arg, "-t", StringComparison.OrdinalIgnoreCase) + || String.Equals (arg, "--target", StringComparison.OrdinalIgnoreCase)) + && String.Equals (value, "PublishToGitHub", StringComparison.OrdinalIgnoreCase) + ) + + /// Defines a dependency - y is dependent on x. Finishes the chain. + let (==>!) x y = x ==> y |> ignore + + /// Defines a soft dependency. x must run before y, if it is present, but y does not require x to be run. Finishes the chain. + let (?=>!) x y = x ?=> y |> ignore + //----------------------------------------------------------------------------- + // Hide Secrets in Logger + //----------------------------------------------------------------------------- + Option.iter (TraceSecrets.register "") githubToken + Option.iter (TraceSecrets.register "") nugetToken + //----------------------------------------------------------------------------- + // Target Declaration + //----------------------------------------------------------------------------- + + Target.create "Clean" clean + Target.create "DotnetRestore" dotnetRestore + Target.create "DotnetToolRestore" dotnetToolRestore + Target.create "UpdateChangelog" updateChangelog + Target.createBuildFailure "RevertChangelog" revertChangelog // Do NOT put this in the dependency chain + Target.createFinal "DeleteChangelogBackupFile" deleteChangelogBackupFile // Do NOT put this in the dependency chain + Target.create "DotnetBuild" dotnetBuild + Target.create "FSharpAnalyzers" fsharpAnalyzers + Target.create "DotnetTest" dotnetTest + Target.create "GenerateCoverageReport" generateCoverageReport + Target.create "ShowCoverageReport" showCoverageReport + Target.create "WatchTests" watchTests + Target.create "GenerateAssemblyInfo" generateAssemblyInfo + Target.create "DotnetPack" dotnetPack + Target.create "SourceLinkTest" sourceLinkTest + Target.create "PublishToNuGet" (publishTo NuGet) + Target.create "PublishToGitHub" (publishTo GitHub) + Target.create "GitRelease" gitRelease + Target.create "GitHubRelease" githubRelease + Target.create "FormatCode" formatCode + Target.create "CheckFormatCode" checkFormatCode + Target.create "Release" ignore // For local + Target.create "Publish" ignore //For CI + Target.create "CleanDocsCache" cleanDocsCache + Target.create "BuildDocs" buildDocs + Target.create "WatchDocs" watchDocs + + //----------------------------------------------------------------------------- + // Target Dependencies + //----------------------------------------------------------------------------- + + + // Only call Clean if DotnetPack was in the call chain + // Ensure Clean is called before DotnetRestore + "Clean" ?=>! "DotnetRestore" + + "Clean" ==>! "DotnetPack" + + // Only call GenerateAssemblyInfo if GitRelease was in the call chain + // Ensure GenerateAssemblyInfo is called after DotnetRestore and before DotnetBuild + "DotnetRestore" ?=>! "GenerateAssemblyInfo" + + "GenerateAssemblyInfo" ?=>! "DotnetBuild" + + // Ensure UpdateChangelog is called after DotnetRestore + "DotnetRestore" ?=>! "UpdateChangelog" + + "UpdateChangelog" ?=>! "GenerateAssemblyInfo" + + "CleanDocsCache" ==>! "BuildDocs" + + "DotnetBuild" ?=>! "BuildDocs" + + "DotnetBuild" ==>! "BuildDocs" + + + "DotnetBuild" ==>! "WatchDocs" + + "DotnetTest" ==> "GenerateCoverageReport" + ==>! "ShowCoverageReport" + + "UpdateChangelog" + ==> "GenerateAssemblyInfo" + ==> "GitRelease" + ==>! "Release" + + + "DotnetRestore" =?> ("CheckFormatCode", isCI.Value) + ==> "DotnetBuild" + ==> "DotnetTest" + ==> "DotnetPack" + ==> "PublishToNuGet" + ==> "GitHubRelease" + ==>! "Publish" + + "DotnetRestore" + =?> ("CheckFormatCode", isCI.Value) + =?> ("GenerateAssemblyInfo", isPublishToGitHub) + ==> "DotnetBuild" + ==> "DotnetTest" + ==> "DotnetPack" + ==>! "PublishToGitHub" + + "DotnetRestore" ==>! "WatchTests" + + //"DotnetToolRestore" ?=>! "DotnetRestore" + "DotnetToolRestore" ==>! "BuildDocs" + "DotnetToolRestore" ?=>! "CheckFormatCode" + "DotnetToolRestore" ?=>! "FormatCode" + +//----------------------------------------------------------------------------- +// Target Start +//----------------------------------------------------------------------------- +[] +let main argv = + + let ctx = + argv + |> Array.toList + |> Context.FakeExecutionContext.Create false "build.fsx" + + Context.setExecutionContext (Context.RuntimeContext.Fake ctx) + initTargets ctx + Target.runOrDefaultWithArguments "DotnetPack" + + 0 // return an integer exit code diff --git a/build/build.fsproj b/build/build.fsproj new file mode 100644 index 0000000..a12452d --- /dev/null +++ b/build/build.fsproj @@ -0,0 +1,40 @@ + + + Exe + net8.0 + 3390;$(WarnOn) + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docsSrc/Explanations/Background.md b/docsSrc/Explanations/Background.md new file mode 100644 index 0000000..a1525b3 --- /dev/null +++ b/docsSrc/Explanations/Background.md @@ -0,0 +1,39 @@ +--- +title: Background +category: Explanations +categoryindex: 3 +index: 1 +--- + +# Background + +## System.Collections.Immutable and F# + +[System.Collections.Immutable](https://learn.microsoft.com/en-us/dotnet/api/system.collections.immutable) is a high-performance .NET library providing a suite of immutable collection types, such as arrays, lists, stacks, queues, dictionaries, and sets. These collections are designed for scenarios where data structures need to be shared safely across threads or require non-destructive updates, making them ideal for functional programming patterns. + +While F# has its own built-in immutable collections, System.Collections.Immutable collections are engineered for performance and scalability, especially in concurrent and multi-threaded environments. They use advanced algorithms to minimize memory allocations and maximize efficiency when creating modified copies of collections. + +**FSharp.Collections.Immutable** provides idiomatic F# bindings for these .NET collections, allowing F# developers to leverage their performance and safety benefits with a familiar F#-style API. + +## Why Use System.Collections.Immutable in F#? + +- **Performance**: Optimized for fast structural sharing and minimal memory overhead compared to standard F# collections in certain scenarios. +- **Thread Safety**: Immutable by design, making them safe for concurrent access without locks. +- **Rich API**: Feature-rich and consistent with .NET ecosystem standards. +- **Interoperability**: Seamless integration with C# and other .NET languages. + +## Available Collections + +- `FlatList` (`ImmutableArray`) +- `ImmutableList` +- `Stack` (`ImmutableStack`) +- `Queue` (`ImmutableQueue`) +- `HashMap` (`ImmutableDictionary`) +- `SortedMap` (`ImmutableSortedDictionary`) +- `HashSet` (`ImmutableHashSet`) +- `SortedSet` (`ImmutableSortedSet`) +- `IIndexedSeq` (`IReadOnlyList`) + +--- + +FSharp.Collections.Immutable enables F# developers to use these performant, thread-safe, and feature-rich immutable collections in a natural and idiomatic way. diff --git a/docsSrc/How_Tos/Doing_A_Thing.md b/docsSrc/How_Tos/Doing_A_Thing.md new file mode 100644 index 0000000..071e5c1 --- /dev/null +++ b/docsSrc/How_Tos/Doing_A_Thing.md @@ -0,0 +1,14 @@ +--- +title: How To do a first thing +category: How To Guides +categoryindex: 2 +index: 1 +--- + +# How To do a first thing + +The best way to use IObservable is to use one .Subscribe()-method which will take function parameters (which will be "injected" to the right place). + +Use Rx (or R3) when you need async events to communicate with each other, e.g.: +- Events, WebServices, Threads, Timers, AutoComplete, Drag & Drop, ... + diff --git a/docsSrc/How_Tos/Doing_Another_Thing.md b/docsSrc/How_Tos/Doing_Another_Thing.md new file mode 100644 index 0000000..03064b3 --- /dev/null +++ b/docsSrc/How_Tos/Doing_Another_Thing.md @@ -0,0 +1,9 @@ +--- +title: How To do a second thing +category: How To Guides +categoryindex: 2 +index: 2 +--- + +# How To do a second thing + diff --git a/docsSrc/Tutorials/Getting_Started.md b/docsSrc/Tutorials/Getting_Started.md new file mode 100644 index 0000000..eea423e --- /dev/null +++ b/docsSrc/Tutorials/Getting_Started.md @@ -0,0 +1,41 @@ +--- +title: Getting Started +category: Tutorials +categoryindex: 1 +index: 1 +--- + +# Getting Started + +## Installation + +First, add the NuGet package to your project: +``` +dotnet add package FSharp.Collections.Immutable +``` +## Basic Setup + +Here's a minimal example to get started: +``` F# +open FSharp.Collections.Immutable +``` + +``` F# +// Create an immutable list +let list1 = ImmutableList.ofSeq [1; 2; 3] + +// Create an immutable hash set +let set1 = HashSet.ofSeq ["a"; "b"] + +// Create an immutable dictionary +let dict1 = HashMap.ofSeq [KeyValuePair ("key1", 42); KeyValuePair ("key2", 100)] + + +printfn "List: %A" (list1 |> Seq.toList) +printfn "Set: %A" (set1 |> Seq.toList) +printfn "Dict: %A" (dict1 |> Seq.toList) +## Next Steps + +- Check out the [How-To Guides](../How_Tos/Doing_A_Thing.html) for common scenarios +- Read the [Background](../Explanations/Background.html) for deeper understanding +- Browse the [API Reference](../reference/index.html) for detailed documentation diff --git a/docsSrc/_menu-item_template.html b/docsSrc/_menu-item_template.html new file mode 100644 index 0000000..dc1b656 --- /dev/null +++ b/docsSrc/_menu-item_template.html @@ -0,0 +1 @@ +
  • {{fsdocs-menu-item-content}}
  • \ No newline at end of file diff --git a/docsSrc/_menu_template.html b/docsSrc/_menu_template.html new file mode 100644 index 0000000..066716c --- /dev/null +++ b/docsSrc/_menu_template.html @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/docsSrc/_template.html b/docsSrc/_template.html new file mode 100644 index 0000000..2e69546 --- /dev/null +++ b/docsSrc/_template.html @@ -0,0 +1,166 @@ + + + + + + {{fsdocs-page-title}} + + + + + + + + + + + + + + + + {{fsdocs-watch-script}} + + + + + + +
    + {{fsdocs-content}} + {{fsdocs-tooltips}} +
    + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docsSrc/content/fsdocs-custom.css b/docsSrc/content/fsdocs-custom.css new file mode 100644 index 0000000..62b2801 --- /dev/null +++ b/docsSrc/content/fsdocs-custom.css @@ -0,0 +1,15 @@ +.fsharp-icon-logo { + width: 25px; + margin-top: -2px; + -webkit-filter: grayscale(100%) brightness(1) invert(1); /* Safari 6.0 - 9.0 */ + filter: grayscale(100%) brightness(1) invert(1); +} + + +body .navbar .dropdown-menu .active .bi { + display: block !important; +} + +nav.navbar .dropdown-item img.fsharp-icon-logo { + margin-right: 0px; +} diff --git a/docsSrc/content/fsdocs-dark.css b/docsSrc/content/fsdocs-dark.css new file mode 100644 index 0000000..001b653 --- /dev/null +++ b/docsSrc/content/fsdocs-dark.css @@ -0,0 +1,50 @@ +@import url('https://raw.githubusercontent.com/tonsky/FiraCode/fixed/distr/fira_code.css'); +@import url('https://fonts.googleapis.com/css2?family=Hind+Vadodara&family=Roboto+Mono&display=swap'); +/*-------------------------------------------------------------------------- + Formatting for page & standard document content +/*--------------------------------------------------------------------------*/ + +:root { + --fsdocs-text-color:#d1d1d1; + --fsdocs-pre-border-color: #000000; + --fsdocs-pre-border-color-top: #070707; + --fsdocs-pre-background-color: #1E1E1E; + --fsdocs-pre-color: #e2e2e2; + --fsdocs-table-pre-background-color: #1d1d1d; + --fsdocs-table-pre-color: #c9c9c9; + + --fsdocs-code-strings-color: #ea9a75; + --fsdocs-code-printf-color: #E0C57F; + --fsdocs-code-escaped-color: #EA8675; + --fsdocs-code-identifiers-color: var(--fsdocs-text-color); + --fsdocs-code-module-color: #43AEC6; + --fsdocs-code-reference-color: #6a8dd8; + --fsdocs-code-value-color: #43AEC6; + --fsdocs-code-interface-color: #43AEC6; + --fsdocs-code-typearg-color: #43AEC6; + --fsdocs-code-disposable-color: #2f798a; + --fsdocs-code-property-color: #43AEC6; + --fsdocs-code-punctuation-color: #43AEC6; + --fsdocs-code-punctuation2-color: #e1e1e1; + --fsdocs-code-function-color: #e1e1e1; + --fsdocs-code-function2-color: #43AEC6; + --fsdocs-code-activepattern-color: #4ec9b0; + --fsdocs-code-unioncase-color: #4ec9b0; + --fsdocs-code-enumeration-color: #4ec9b0; + --fsdocs-code-keywords-color: #2248c4; + --fsdocs-code-comment-color: #329215; + --fsdocs-code-operators-color: #af75c1; + --fsdocs-code-numbers-color: #96C71D; + --fsdocs-code-linenumbers-color: #80b0b0; + --fsdocs-code-mutable-color: #997f0c; + --fsdocs-code-inactive-color: #808080; + --fsdocs-code-preprocessor-color: #af75c1; + --fsdocs-code-fsioutput-color: #808080; + --fsdocs-code-tooltip-color: #d1d1d1; +} + + +.fsdocs-source-link img { + -webkit-filter: grayscale(100%) brightness(1) invert(1); /* Safari 6.0 - 9.0 */ + filter: grayscale(100%) brightness(1) invert(1); +} \ No newline at end of file diff --git a/docsSrc/content/fsdocs-light.css b/docsSrc/content/fsdocs-light.css new file mode 100644 index 0000000..318ccd9 --- /dev/null +++ b/docsSrc/content/fsdocs-light.css @@ -0,0 +1,43 @@ +@import url('https://fonts.googleapis.com/css2?family=Hind+Vadodara&family=Roboto+Mono&display=swap'); +/*-------------------------------------------------------------------------- + Formatting for page & standard document content +/*--------------------------------------------------------------------------*/ + +:root { + --fsdocs-text-color:#262626; + --fsdocs-pre-border-color: #d8d8d8; + --fsdocs-pre-border-color-top: #e3e3e3; + --fsdocs-pre-background-color: #f3f4f7; + --fsdocs-pre-color: #8e0e2b; + --fsdocs-table-pre-background-color: #fff7ed; + --fsdocs-table-pre-color: #837b79; + + --fsdocs-code-strings-color: #dd1144; + --fsdocs-code-printf-color: #E0C57F; + --fsdocs-code-escaped-color: #EA8675; + --fsdocs-code-identifiers-color: var(--fsdocs-text-color); + --fsdocs-code-module-color: #009999; + --fsdocs-code-reference-color: #4974D1; + --fsdocs-code-value-color: #43AEC6; + --fsdocs-code-interface-color: #43AEC6; + --fsdocs-code-typearg-color: #43AEC6; + --fsdocs-code-disposable-color: #43AEC6; + --fsdocs-code-property-color: #43AEC6; + --fsdocs-code-punctuation-color: #43AEC6; + --fsdocs-code-punctuation2-color: #var(--fsdocs-text-color); + --fsdocs-code-function-color: #e1e1e1; + --fsdocs-code-function2-color: #990000; + --fsdocs-code-activepattern-color: #4ec9b0; + --fsdocs-code-unioncase-color: #4ec9b0; + --fsdocs-code-enumeration-color: #4ec9b0; + --fsdocs-code-keywords-color: #b68015; + --fsdocs-code-comment-color: #808080; + --fsdocs-code-operators-color: #af75c1; + --fsdocs-code-numbers-color: #009999; + --fsdocs-code-linenumbers-color: #80b0b0; + --fsdocs-code-mutable-color: #d1d1d1; + --fsdocs-code-inactive-color: #808080; + --fsdocs-code-preprocessor-color: #af75c1; + --fsdocs-code-fsioutput-color: #808080; + --fsdocs-code-tooltip-color: #d1d1d1; +} diff --git a/docsSrc/content/fsdocs-main.css b/docsSrc/content/fsdocs-main.css new file mode 100644 index 0000000..2ad1582 --- /dev/null +++ b/docsSrc/content/fsdocs-main.css @@ -0,0 +1,604 @@ +@import url('https://fonts.googleapis.com/css2?family=Hind+Vadodara&family=Roboto+Mono&display=swap'); +/*-------------------------------------------------------------------------- + Formatting for page & standard document content +/*--------------------------------------------------------------------------*/ + +body { + font-family: 'Hind Vadodara', sans-serif; + /* padding-top: 0px; + padding-bottom: 40px; +*/ +} + +blockquote { + margin: 0 1em 0 0.25em; + margin-top: 0px; + margin-right: 1em; + margin-bottom: 0px; + margin-left: 0.25em; + padding: 0 .75em 0 1em; + border-left: 1px solid #777; + border-right: 0px solid #777; +} + +/* Format the heading - nicer spacing etc. */ +.masthead { + overflow: hidden; +} + + .masthead .muted a { + text-decoration: none; + color: #999999; + } + + .masthead ul, .masthead li { + margin-bottom: 0px; + } + + .masthead .nav li { + margin-top: 15px; + font-size: 110%; + } + + .masthead h3 { + margin-top: 15px; + margin-bottom: 5px; + font-size: 170%; + } + +/*-------------------------------------------------------------------------- + Formatting fsdocs-content +/*--------------------------------------------------------------------------*/ + +/* Change font sizes for headings etc. */ +#fsdocs-content h1 { + margin: 30px 0px 15px 0px; + /* font-weight: 400; */ + font-size: 2rem; + letter-spacing: 1.78px; + line-height: 2.5rem; + font-weight: 400; +} + +#fsdocs-content h2 { + font-size: 1.6rem; + margin: 20px 0px 10px 0px; + font-weight: 400; +} + +#fsdocs-content h3 { + font-size: 1.2rem; + margin: 15px 0px 10px 0px; + font-weight: 400; +} + +#fsdocs-content hr { + margin: 0px 0px 20px 0px; +} + +#fsdocs-content li { + font-size: 1.0rem; + line-height: 1.375rem; + letter-spacing: 0.01px; + font-weight: 500; + margin: 0px 0px 15px 0px; +} + +#fsdocs-content p { + font-size: 1.0rem; + line-height: 1.375rem; + letter-spacing: 0.01px; + font-weight: 500; + color: var(--fsdocs-text-color);; +} + +#fsdocs-content a:not(.btn) { + color: #4974D1; +} +/* remove the default bootstrap bold on dt elements */ +#fsdocs-content dt { + font-weight: normal; +} + + + +/*-------------------------------------------------------------------------- + Formatting tables in fsdocs-content, using learn.microsoft.com tables +/*--------------------------------------------------------------------------*/ + +#fsdocs-content .table { + table-layout: auto; + width: 100%; + font-size: 0.875rem; +} + + #fsdocs-content .table caption { + font-size: 0.8rem; + font-weight: 600; + letter-spacing: 2px; + text-transform: uppercase; + padding: 1.125rem; + border-width: 0 0 1px; + border-style: solid; + border-color: #e3e3e3; + text-align: right; + } + + #fsdocs-content .table td, + #fsdocs-content .table th { + display: table-cell; + word-wrap: break-word; + padding: 0.75rem 1rem 0.75rem 0rem; + line-height: 1.5; + vertical-align: top; + border-top: 1px solid #e3e3e3; + border-right: 0; + border-left: 0; + border-bottom: 0; + border-style: solid; + } + + /* suppress the top line on inner lists such as tables of exceptions */ + #fsdocs-content .table .fsdocs-exception-list td, + #fsdocs-content .table .fsdocs-exception-list th { + border-top: 0 + } + + #fsdocs-content .table td p:first-child, + #fsdocs-content .table th p:first-child { + margin-top: 0; + } + + #fsdocs-content .table td.nowrap, + #fsdocs-content .table th.nowrap { + white-space: nowrap; + } + + #fsdocs-content .table td.is-narrow, + #fsdocs-content .table th.is-narrow { + width: 15%; + } + + #fsdocs-content .table th:not([scope='row']) { + border-top: 0; + border-bottom: 1px; + } + + #fsdocs-content .table > caption + thead > tr:first-child > td, + #fsdocs-content .table > colgroup + thead > tr:first-child > td, + #fsdocs-content .table > thead:first-child > tr:first-child > td { + border-top: 0; + } + + #fsdocs-content .table table-striped > tbody > tr:nth-of-type(odd) { + background-color: var(--box-shadow-light); + } + + #fsdocs-content .table.min { + width: unset; + } + + #fsdocs-content .table.is-left-aligned td:first-child, + #fsdocs-content .table.is-left-aligned th:first-child { + padding-left: 0; + } + + #fsdocs-content .table.is-left-aligned td:first-child a, + #fsdocs-content .table.is-left-aligned th:first-child a { + outline-offset: -0.125rem; + } + +@media screen and (max-width: 767px), screen and (min-resolution: 120dpi) and (max-width: 767.9px) { + #fsdocs-content .table.is-stacked-mobile td:nth-child(1) { + display: block; + width: 100%; + padding: 1rem 0; + } + + #fsdocs-content .table.is-stacked-mobile td:not(:nth-child(1)) { + display: block; + border-width: 0; + padding: 0 0 1rem; + } +} + +#fsdocs-content .table.has-inner-borders th, +#fsdocs-content .table.has-inner-borders td { + border-right: 1px solid #e3e3e3; +} + + #fsdocs-content .table.has-inner-borders th:last-child, + #fsdocs-content .table.has-inner-borders td:last-child { + border-right: none; + } + +.fsdocs-entity-list .fsdocs-entity-name { + width: 25%; + font-weight: bold; +} + +.fsdocs-member-list .fsdocs-member-usage { + width: 35%; +} + +/*-------------------------------------------------------------------------- + Formatting xmldoc sections in fsdocs-content +/*--------------------------------------------------------------------------*/ + +.fsdocs-xmldoc, .fsdocs-entity-xmldoc, .fsdocs-member-xmldoc { + font-size: 1.0rem; + line-height: 1.375rem; + letter-spacing: 0.01px; + font-weight: 500; + color: var(--fsdocs-text-color);; +} + +.fsdocs-xmldoc h1 { + font-size: 1.2rem; + margin: 10px 0px 0px 0px; +} + +.fsdocs-xmldoc h2 { + font-size: 1.2rem; + margin: 10px 0px 0px 0px; +} + +.fsdocs-xmldoc h3 { + font-size: 1.1rem; + margin: 10px 0px 0px 0px; +} + +/* #fsdocs-nav .searchbox { + margin-top: 30px; + margin-bottom: 30px; +} */ + +#fsdocs-nav img.logo{ + width:90%; + /* height:140px; */ + /* margin:10px 0px 0px 20px; */ + margin-top:40px; + border-style:none; +} + +#fsdocs-nav input{ + /* margin-left: 20px; */ + margin-right: 20px; + margin-top: 20px; + margin-bottom: 20px; + width: 93%; + -webkit-border-radius: 0; + border-radius: 0; +} + +#fsdocs-nav { + /* margin-left: -5px; */ + /* width: 90%; */ + font-size:0.95rem; +} + +#fsdocs-nav li.nav-header{ + /* margin-left: -5px; */ + /* width: 90%; */ + padding-left: 0; + color: var(--fsdocs-text-color);; + text-transform: none; + font-size:16px; + margin-top: 9px; + font-weight: bold; +} + +#fsdocs-nav a{ + padding-left: 0; + color: #6c6c6d; + /* margin-left: 5px; */ + /* width: 90%; */ +} + +/*-------------------------------------------------------------------------- + Formatting pre and code sections in fsdocs-content (code highlighting is + further below) +/*--------------------------------------------------------------------------*/ + +#fsdocs-content code { + /* font-size: 0.83rem; */ + font: 0.85rem 'Roboto Mono', monospace; + background-color: #f7f7f900; + border: 0px; + padding: 0px; + /* word-wrap: break-word; */ + /* white-space: pre; */ +} + +/* omitted */ +#fsdocs-content span.omitted { + background: #3c4e52; + border-radius: 5px; + color: #808080; + padding: 0px 0px 1px 0px; +} + +#fsdocs-content pre .fssnip code { + font: 0.86rem 'Roboto Mono', monospace; +} + +#fsdocs-content table.pre, +#fsdocs-content pre.fssnip, +#fsdocs-content pre { + line-height: 13pt; + border: 0px solid var(--fsdocs-pre-border-color); + border-top: 0px solid var(--fsdocs-pre-border-color-top); + border-collapse: separate; + white-space: pre; + font: 0.86rem 'Roboto Mono', monospace; + width: 100%; + margin: 10px 0px 20px 0px; + background-color: var(--fsdocs-pre-background-color); + padding: 10px; + border-radius: 5px; + color: var(--fsdocs-pre-color); + max-width: none; + box-sizing: border-box; +} + +#fsdocs-content pre.fssnip code { + font: 0.86rem 'Roboto Mono', monospace; + font-weight: 600; +} + +#fsdocs-content table.pre { + background-color: var(--fsdocs-table-pre-background-color);; +} + +#fsdocs-content table.pre pre { + padding: 0px; + margin: 0px; + border-radius: 0px; + width: 100%; + background-color: var(--fsdocs-table-pre-background-color); + color: var(--fsdocs-table-pre-color); +} + +#fsdocs-content table.pre td { + padding: 0px; + white-space: normal; + margin: 0px; + width: 100%; +} + +#fsdocs-content table.pre td.lines { + width: 30px; +} + + +#fsdocs-content pre { + word-wrap: inherit; +} + +.fsdocs-example-header { + font-size: 1.0rem; + line-height: 1.375rem; + letter-spacing: 0.01px; + font-weight: 700; + color: var(--fsdocs-text-color);; +} + +/*-------------------------------------------------------------------------- + Formatting github source links +/*--------------------------------------------------------------------------*/ + +.fsdocs-source-link { + float: right; + text-decoration: none; +} + + .fsdocs-source-link img { + border-style: none; + margin-left: 10px; + width: auto; + height: 1.4em; + } + + .fsdocs-source-link .hover { + display: none; + } + + .fsdocs-source-link:hover .hover { + display: block; + } + + .fsdocs-source-link .normal { + display: block; + } + + .fsdocs-source-link:hover .normal { + display: none; + } + +/*-------------------------------------------------------------------------- + Formatting logo +/*--------------------------------------------------------------------------*/ + +#fsdocs-logo { + width:40px; + height:40px; + margin:10px 0px 0px 0px; + border-style:none; +} + +/*-------------------------------------------------------------------------- + +/*--------------------------------------------------------------------------*/ + +#fsdocs-content table.pre pre { + padding: 0px; + margin: 0px; + border: none; +} + +/*-------------------------------------------------------------------------- + Remove formatting from links +/*--------------------------------------------------------------------------*/ + +#fsdocs-content h1 a, +#fsdocs-content h1 a:hover, +#fsdocs-content h1 a:focus, +#fsdocs-content h2 a, +#fsdocs-content h2 a:hover, +#fsdocs-content h2 a:focus, +#fsdocs-content h3 a, +#fsdocs-content h3 a:hover, +#fsdocs-content h3 a:focus, +#fsdocs-content h4 a, +#fsdocs-content h4 a:hover, #fsdocs-content +#fsdocs-content h4 a:focus, +#fsdocs-content h5 a, +#fsdocs-content h5 a:hover, +#fsdocs-content h5 a:focus, +#fsdocs-content h6 a, +#fsdocs-content h6 a:hover, +#fsdocs-content h6 a:focus { + color: var(--fsdocs-text-color);; + text-decoration: none; + text-decoration-style: none; + /* outline: none */ +} + +/*-------------------------------------------------------------------------- + Formatting for F# code snippets +/*--------------------------------------------------------------------------*/ + +.fsdocs-param-name, +.fsdocs-return-name, +.fsdocs-param { + font-weight: 900; + font-size: 0.85rem; + font-family: 'Roboto Mono', monospace; +} +/* strings --- and stlyes for other string related formats */ +#fsdocs-content span.s { + color: var(--fsdocs-code-strings-color); +} +/* printf formatters */ +#fsdocs-content span.pf { + color: var(--fsdocs-code-printf-color); +} +/* escaped chars */ +#fsdocs-content span.e { + color: var(--fsdocs-code-escaped-color); +} + +/* identifiers --- and styles for more specific identifier types */ +#fsdocs-content span.id { + color: var(--fsdocs-identifiers-color);; +} +/* module */ +#fsdocs-content span.m { + color:var(--fsdocs-code-module-color); +} +/* reference type */ +#fsdocs-content span.rt { + color: var(--fsdocs-code-reference-color); +} +/* value type */ +#fsdocs-content span.vt { + color: var(--fsdocs-code-value-color); +} +/* interface */ +#fsdocs-content span.if { + color: var(--fsdocs-code-interface-color); +} +/* type argument */ +#fsdocs-content span.ta { + color: var(--fsdocs-code-typearg-color); +} +/* disposable */ +#fsdocs-content span.d { + color: var(--fsdocs-code-disposable-color); +} +/* property */ +#fsdocs-content span.prop { + color: var(--fsdocs-code-property-color); +} +/* punctuation */ +#fsdocs-content span.p { + color: var(--fsdocs-code-punctuation-color); +} +#fsdocs-content span.pn { + color: var(--fsdocs-code-punctuation2-color); +} +/* function */ +#fsdocs-content span.f { + color: var(--fsdocs-code-function-color); +} +#fsdocs-content span.fn { + color: var(--fsdocs-code-function2-color); +} +/* active pattern */ +#fsdocs-content span.pat { + color: var(--fsdocs-code-activepattern-color); +} +/* union case */ +#fsdocs-content span.u { + color: var(--fsdocs-code-unioncase-color); +} +/* enumeration */ +#fsdocs-content span.e { + color: var(--fsdocs-code-enumeration-color); +} +/* keywords */ +#fsdocs-content span.k { + color: var(--fsdocs-code-keywords-color); + /* font-weight: bold; */ +} +/* comment */ +#fsdocs-content span.c { + color: var(--fsdocs-code-comment-color); + font-weight: 400; + font-style: italic; +} +/* operators */ +#fsdocs-content span.o { + color: var(--fsdocs-code-operators-color); +} +/* numbers */ +#fsdocs-content span.n { + color: var(--fsdocs-code-numbers-color); +} +/* line number */ +#fsdocs-content span.l { + color: var(--fsdocs-code-linenumbers-color); +} +/* mutable var or ref cell */ +#fsdocs-content span.v { + color: var(--fsdocs-code-mutable-color); + font-weight: bold; +} +/* inactive code */ +#fsdocs-content span.inactive { + color: var(--fsdocs-code-inactive-color); +} +/* preprocessor */ +#fsdocs-content span.prep { + color: var(--fsdocs-code-preprocessor-color); +} +/* fsi output */ +#fsdocs-content span.fsi { + color: var(--fsdocs-code-fsioutput-color); +} + +/* tool tip */ +div.fsdocs-tip { + background: #475b5f; + border-radius: 4px; + font: 0.85rem 'Roboto Mono', monospace; + padding: 6px 8px 6px 8px; + display: none; + color: var(--fsdocs-code-tooltip-color); + pointer-events: none; +} + + div.fsdocs-tip code { + color: var(--fsdocs-code-tooltip-color); + font: 0.85rem 'Roboto Mono', monospace; + } diff --git a/docsSrc/content/logo.pdn b/docsSrc/content/logo.pdn new file mode 100644 index 0000000000000000000000000000000000000000..c1ffec090291db78b4d4662b0ac7e3988cf39415 GIT binary patch literal 8637 zcmeHL3zQVqnVyDug6v3q&m@^~#?{?4nSQ?v%vz7G?&^1SP4#rQB2-sbS5;ScS9N#w zYse%dF=3-l#?f<(nixO{YKVf00*WT6L3X1NP*D)#$$|o+5EUF96!umR$Y7F9vd2AV z&oR}fZ{5d#zyIF<|L%Y9tq>Y8HJ{TkS5D^eEX}dq6(Onc^LmVWeNQ*f3LLMTDrD#y zTPV)!G4*s8=`x!f6cj!JY6K|g{qF6ts1S^YjP%TD)fMOz@3lFP}mF_hAmm;repEaPrJ<1yQG28eg2QYj@8 zhY4FP=8MCa+f_vfy$8x7gG8h{BymiQ2$>3s&Kh;$hBRftoqBVLgTon<%}(nQ_>dUz zrS&PwkSC!8PN%(92y=?E>=#j*@d_mZ4&!-OX#n!!L0&Jxk~AO+Oo|<}NkRQ!0fV$z zgs@TwmC$MJ=_H=_4GLUz0FzP@M3D%uND?MW9@Ie$3Ve{1s}4u1l(vSwya)GkW&?~1 zR<<;lG7{xbDk1CL`T-1LvnWx~W0D9X`6`9Uh&_lAn3IbUgWgI4cfyrCB5|acNwV5p zLDuEdL4kv67|wcO)K^YN*pM%&&l!>_i9kG+JgiPColj&4jvC-8p8)3_+H%Sl(3=d& zfGShXd#aEj=u1;W#sDM`G;fyzVcaE_OI3&|!9=N=(7FoYwANux`1J82E8`)Q3>)Ia zAc@#nZJADHla87(g%}+rSsl?>IU3U%Eg3qE0_)`StfiVG2 zn?0P{$;)I?&C5d6WzQrPn=Z~`Mmm+r!WkS+M@6%k3wq5-rs%2yFKTTGL&}^-dCcd7 zie$iSb=%P55NphZYN@KDj0hw}r3Jzth3)pF7$TF}R1MEM?PkOj$zxEp=;ep>IR~7v z1U+!oP|8tW2rwK~qPd`>!b#CV41&c3uPxY|GRlxB7h(fa01O5}!Ds>z+#<4k)sf-@ z25YqDs*oZESMvxA%$&{V0~Uk?+~HO3(7p&uI18vmdZB!viU<9X>YzZO-muFpa9#n} zgU<&7a)uzq2=J>@;t+(+1wkK|M0??SA4qG~p%Uy38{IVyb6ThY#2d3^lxhOU+=vu) z0;&b`3dZmh(95WLt@%o%=5?a+YCe^A`mL$bP%S+euw?VroT$&zDN!3`xIh@zv8hC^ zhJ*%GBW&>#VFzm$TzNZz zyW}FMW79!|OU6LQLBBvo4G1R%tr#Rb(=kf4P}#WC&ax(wp$q!lfDf+WE-vJ;+Z|ea zK^qDHr|?y)%^H{VrUVXAgJv;G;BGk{3kZnhAxSgCrc)eUv!@b?N_HTjEfN^vA>BT~ z>^0T~ZRH{nAY-H-2Ek124>k~j2@DZ&XE5#-0W(mO#iA9tP>L>!-f%F2Fy%C7j2Q}6 zRI31Y)KIfNO=!zLR2;|Xk@hMCfanwe!%X`7PCg|c~j*+FGtDy;B%!Y9H*ISYh~z8IwTuA#-W zP|!0j(MLeBtP~-`B_fJ)6cNA?!G~cUPZ6%gGAKk63g;0LxKTvRX)cG=U>O=Dogl7f zFq@9i`m&G}BL}LC&Vx}X35k5B-UlNFRV>m)!5+j2<`jqYoCl8s{`+Dug^7QIEJdMu z)^CW?DI?g`2&#fsG1LfUAuld#Ik*akp^%db;ocZz7BW@{bz)AZE)1DDnR5$Xw+QRWxznFf+Th!%1RDk%Y0=|gFS zp3{B$LDxsX0|fq)dio6mXd-_PN*`TM(E& zFz5)KDX{>qu+Vt*)u%*Br6AQShSV+)RAaYK zsVE9V?3bpL%aixQMgae3{XFRlHEmP5(AEMHbrkAw8dasAaZ`O93;c9iDl{+f3pw=# zmxN5RZ9$kV3+f#dbOP>eZB<28sAtY^Xo&o@p`k^^tkb&QvhNGw@Wv0p68>ULRVC4M zk*zhA)upP@G`E3@8JDqDA=RhQoVj@hD5G=La<`BKUn=yLp6(2YL_G#;Pq$K&S@3zn6f}t|pVHM^2-D z=`2)R^Ld{|1w;B*r@^Wf3Z8LIxdjKFcfo@P}Qj!0@S*ek@B>^~*49bm{Pn`n=Hd=VH zp{NZ=g)E&pRa7TYpU9=EPIV%G&Mqo-&f3JAi(n5;vQ0&X&gns8BM97Qx!liYr9utP zNQ|f&RsEmdxVk}fZ|hMJRZIPKTdS05%+Xo)PtOZ2>ec~fe2!3rX-4D$EHVK;BG#Zus4f#{1(zK|CuAgVr_w+BuCa+Ox zp;}*~vOqQI8G~q4|JK`7cb?rgb8_2K<-v)t>xR~Prm^9jrww4wZk?{b|I7E@@hYv+ zWVb(O{l)*^XSAjE$sd0=IcGJP)}QI*lnL#6FiSxfUq{^0z2dwXJ(tT}3Ntu<@50ME zM|U3k@J8jmRWF}p$KMzqmvpDQfA;yulRKXjhvkP~tlZz8D^EOd zY}50B*B|L$zw_YZJ6dduUhtpz=-rts4^B4@U$wG*%Tdkk{oRk>*Z%NpE88EQJAB(4 z2cMo3>`K*^ZfM`L)1~HC4gd7Sky~a>EcSKXbXKnyTauq&u40ciT?cMc+5-d!&)>A!XD zJ?+DDgkaZ1^dEwMr+MCW9RKi@JD+_0m7$4yTdI43T|0qUuP9F(K6bYM4(zo8=pWws zW*TpX_!J?~7*>esf zy}x{Zu@yVEP29Klj?tNKcD%azK!2fkwdJYs2+xE8;(geoh7F_F_AY(tn;Z2nU1v=l z8|(b&8~48;_UG(eqVwV7TU+n{$u?}P-wdxxJxcr%h%VT%@vCbFmyO&teDul?!u|wo&i>Hn6-Pk@Nw?Wz64_xs?apNm{ zKKW!h7QOM;)+hZh>{&Ol-?OgEWx07ryYR&PLsvf3|Kkj!duUAAeNo5q>pHS;3)kKH za)voFZDiwl!(WLly>Qp7Gjq@U62mqqod-`W+5f?c*>U-xb^CDF%GJZBq|~u|r6;@b zZS3P~d)Ci3ecN$wqI31;qhr6aJv94$Uk!P~ij9wrE}#9BW$We@{qH?^SEu3q%F`VJ zQ|S8Wwy}}hUS3bkdc?45^qZNHkdMhecer;c()-+wg`LicE&HzriZ@>%t{q7X?<-=~ z#Lo_TEW5F7{qJqCJhgCS9kFqApx5@^@saVj`aBc2&+{Fs zUDd0T7HAJIJ@SF&jSn9A`EUBSzq@+cYkypKd z7Gu|s&))rm@&9_jw|C_HcaTosHo5mVK&oioCtm{|&=g5si{ps&@e9db7 z&~eF5EFQe!+EC=4aPQJtyRY7NXfyTEs{iPi|Jc}f7Hw~Sp1*JGUh89zkDnO6pfoGC z?!u0?+|$PsxBooVyK7-v+I-inTld{O()j4RSKc2K=Oa75zGm#mqW#tLU*0^jVOitM z^uEU`AKz(>KC~E2&;9IiZ|61fKVb00fsYq-e(h_oy5H?z3(cHi39oI>ZoPTh$oYrv ze*7mwRmSu$86O_&8CKpL-oECNk>?I0opUxlgQbpbyZ!sW{jQHs7P{`d{ReZlZow`; zwtUvD8MOEA<6E0W{hBdiSV{fip`Pg-m+W?NzrRAOtQ>=e`R`f2v2DxF{wp7rcP{Q7 z`RUttn_j$WmSgLdZT)LEUARZwx3Fv1!ru;!eEF@t!f)<}&MWjTIGQ_h<@^2Wf%o%# Q7JvWvMNruPO*L5kH{P=V$^ZZW literal 0 HcmV?d00001 diff --git a/docsSrc/content/logo.png b/docsSrc/content/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..1d43c1f56ea6c56db841128aab35592a7bf34664 GIT binary patch literal 1904 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k8A#4*i(3Pv7>k44ofy`glX(f`u%tWsIx;Y9 z?C1WI$jZRL%n;xc;+mQ`J1q%_=47PI%}$?}lewTEXHik!;^O=zC51~XN|slZuV}1W z+uX3ewP}54+oqn*Exlb^r%c>AZOZN$Q}@i8zIXP_eRF2+n>Xjc!g&W5EjYAv@sZ_A zj;>gGY}NAPt5=>_v+Crg4QID+y|823g+042@85g%!2WB84qiWY^wx>vw@;nCbLQ0D zE0-Q!x%~L*mB%-4JiB}6<%9dL|Ns9Fbnqw`4FOt*!1j4nHb6T#3p^r=85sBugD~Uq z{1qucL5ULAh?3y^w370~qEv>0#LT=By}Z;C1rt33J>#Bd(*uB+=&GlSV@O5Z+uPN_ zmjeWjJycL&x+fa)fvZE@q2vDl!!x(tp5$BhS3;(5=69_ow`Slgzwwzx|Zsk+ehr`pEkyvy?kn+!q-x>lWU!wH+e?&@h#a` zr<84UrL08p>sjtdQTwMa96xz%S7@maN9|{?Yi{daZ%`<$RapJ_TJenuA13WP z+;}DbiKwBmjNFpEdHl1V7AQ8VE_?iE!>Voisqc5K5V~Es))L6+Odf*TXH8yQ#KxMbeTBC+QviRWcAIZuU^0Vb8Df^ z6dR9Ho#^*pir4P{n^!j}ca@*~*>$-xCl9;IJ#jF*mwDITeww$8hrn@J6|*CSe`Y** z{xhhPvry*ppKH+?8x=a&Z1zqWRgFTKkMAm&CYkUlcs`Msa-;9{@^10A)6;5JtdhOCQ}4sv&C8ZlI-9)b zzClTc0GLiLRboHDhYj5{M_gxuFFzjVG-ljq}IHNm8> zZwdFzM-|`iZB>=^SaN$aSC*%V%FM;OffGvlrY+%}`3WectE%g9x9#$7L)kiB8O)}jW_yqCd^D>ER@Mq=uP1(>@UCU zubkd_A= zjRPySS-~nYLcsp3nOlp5pb-}0kVJCN-}#|e + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docsSrc/content/navbar-fixed-left.css b/docsSrc/content/navbar-fixed-left.css new file mode 100644 index 0000000..28e4c57 --- /dev/null +++ b/docsSrc/content/navbar-fixed-left.css @@ -0,0 +1,91 @@ +/* CSS for Bootstrap 5 Fixed Left Sidebar Navigation */ + + + +@media (min-width: 992px){ + + body { + padding-left: 300px; + padding-right: 60px; + } + + #fsdocs-logo { + width:140px; + height:140px; + margin:10px 0px 0px 0px; + border-style:none; + } + + + nav.navbar { + position: fixed; + left: 0; + width: 300px; + bottom: 0; + top: 0; + overflow-y: auto; + overflow-x: hidden; + display: block; + border-right: 1px solid #cecece; + } + + nav.navbar>.container { + flex-direction: column; + padding: 0; + } + + nav.navbar .navbar-nav { + flex-direction: column; + } + nav.navbar .navbar-collapse { + width: 100%; + } + + nav.navbar .navbar-nav { + width: 100%; + } + + nav.navbar .navbar-nav .dropdown-menu { + position: static; + display: block; + } + + nav.navbar .dropdown { + margin-bottom: 5px; + font-size: 14px; + } + + nav.navbar .dropdown-item { + white-space: normal; + font-size: 14px; + vertical-align: middle; + } + + nav.navbar .dropdown-item img { + margin-right: 5px; + } + + nav.navbar .dropdown-toggle { + cursor: default; + } + + nav.navbar .dropdown-menu { + border-radius: 0; + border-left: 0; + border-right: 0; + } + + nav.navbar .dropdown-toggle:not(#bd-theme)::after { + display: none; + } + + .dropdown-menu[data-bs-popper] { + top: auto; + left: auto; + margin-top: auto; + } + + .nav-link:focus, .nav-link:hover { + color: auto; + } +} \ No newline at end of file diff --git a/docsSrc/content/theme-toggle.js b/docsSrc/content/theme-toggle.js new file mode 100644 index 0000000..c208c08 --- /dev/null +++ b/docsSrc/content/theme-toggle.js @@ -0,0 +1,68 @@ +/*! + * Color mode toggler for Bootstrap's docs (https://getbootstrap.com/) + * Copyright 2011-2022 The Bootstrap Authors + * Licensed under the Creative Commons Attribution 3.0 Unported License. + */ + +(() => { + 'use strict' + + const storedTheme = localStorage.getItem('theme') + + const getPreferredTheme = () => { + if (storedTheme) { + return storedTheme + } + + return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light' + } + + const setTheme = function (theme) { + const fsdocsTheme = document.getElementById("fsdocs-theme") + const re = /fsdocs-.*.css/ + if (theme === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches) { + document.documentElement.setAttribute('data-bs-theme', 'dark') + fsdocsTheme.setAttribute("href", fsdocsTheme.getAttribute("href").replace(re,"fsdocs-dark.css")) + + } else { + document.documentElement.setAttribute('data-bs-theme', theme) + + fsdocsTheme.setAttribute("href", fsdocsTheme.getAttribute("href").replace(re,`fsdocs-${theme}.css`)) + } + } + + setTheme(getPreferredTheme()) + + const showActiveTheme = theme => { + const activeThemeIcon = document.getElementById('theme-icon-active') + const btnToActive = document.querySelector(`[data-bs-theme-value="${theme}"]`) + const svgOfActiveBtn = btnToActive.querySelector('i').getAttribute('class') + + document.querySelectorAll('[data-bs-theme-value]').forEach(element => { + element.classList.remove('active') + }) + + btnToActive.classList.add('active') + activeThemeIcon.setAttribute('class', svgOfActiveBtn) + } + + window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => { + if (storedTheme !== 'light' || storedTheme !== 'dark') { + setTheme(getPreferredTheme()) + } + }) + + window.addEventListener('DOMContentLoaded', () => { + showActiveTheme(getPreferredTheme()) + + document.querySelectorAll('[data-bs-theme-value]') + .forEach(toggle => { + toggle.addEventListener('click', () => { + const theme = toggle.getAttribute('data-bs-theme-value') + localStorage.setItem('theme', theme) + setTheme(theme) + showActiveTheme(theme) + }) + }) + }) +})() \ No newline at end of file diff --git a/docsSrc/index.md b/docsSrc/index.md new file mode 100644 index 0000000..a58115a --- /dev/null +++ b/docsSrc/index.md @@ -0,0 +1,79 @@ +# FSharp.Collections.Immutable + +F# bindings for [System.Collections.Immutable](https://learn.microsoft.com/en-us/dotnet/api/system.collections.immutable). + +**FSharp.Collections.Immutable** provides idiomatic F# wrappers for the .NET immutable collections, making it easy to use persistent data structures in F# code. + +## Key Features + +- `FlatList` (`ImmutableArray`) +- `ImmutableList` +- `Stack` (`ImmutableStack`) +- `Queue` (`ImmutableQueue`) +- `HashMap` (`ImmutableDictionary`) +- `SortedMap` (`ImmutableSortedDictionary`) +- `HashSet` (`ImmutableHashSet`) +- `SortedSet` (`ImmutableSortedSet`) +- `IIndexedSeq` (`IReadOnlyList`) + +--- + +
    +
    +
    +
    + The FSharp.Collections.Immutable library can be installed from NuGet: +
    PM> Install-Package FSharp.Collections.Immutable
    +
    +
    +
    +
    + +--- + +
    +
    +
    +
    +
    Tutorials
    +

    Step-by-step guide to get started with FSharp.Collections.Immutable.

    +
    + +
    +
    +
    +
    +
    +
    How-To Guides
    +

    Guides you through the steps involved in addressing key problems and use-cases.

    +
    + +
    +
    +
    +
    +
    +
    Explanations
    +

    Discusses key topics and concepts at a fairly high level and provide useful background information and explanation.

    +
    + +
    +
    +
    +
    +
    +
    Api Reference
    +

    Contain technical reference for APIs.

    +
    + +
    +
    +
    diff --git a/global.json b/global.json new file mode 100644 index 0000000..ccbe73c --- /dev/null +++ b/global.json @@ -0,0 +1,6 @@ +{ + "sdk": { + "version": "9.0.300", + "rollForward": "latestMinor" + } +} diff --git a/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj b/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj index 2b3fbfb..9f5941f 100644 --- a/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj +++ b/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj @@ -1,45 +1,43 @@  - - netstandard2.0 - $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb - F# bindings for System.Collections.Immutable - Copyright © XperiAndri 2016 - FSharp.Collections.Immutable - XperiAndri - FSharp.Collections.Immutable - 2.0.0 - XperiAndri;EventHelix;vilinski;anthony-mi;dim-37 - true - FSharp.Collections.Immutable - System;Immutable;Collections;FSharp;F# - git - embedded - https://github.com/fsprojects/FSharp.Collections.Immutable/ - https://github.com/fsprojects/FSharp.Collections.Immutable/ - true - true - + + net8.0 + $(AssemblyBaseName) + true + true + True + - - - - - - - - - - - + + FSharp.Collections.Immutable + FSharp.Collections.Immutable + F# API for using Microsoft Azure Cosmos DB service via NoSQL API + Provides extension methods for the FeedIterator and computation expressions to build operations + - - true - + + true + true + - - - - + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + diff --git a/src/FSharp.Collections.Immutable/flat-list.fs b/src/FSharp.Collections.Immutable/FlatList.fs similarity index 58% rename from src/FSharp.Collections.Immutable/flat-list.fs rename to src/FSharp.Collections.Immutable/FlatList.fs index 5f86c40..82bcce7 100644 --- a/src/FSharp.Collections.Immutable/flat-list.fs +++ b/src/FSharp.Collections.Immutable/FlatList.fs @@ -1,4 +1,4 @@ -#if INTERACTIVE +#if INTERACTIVE namespace global #else namespace FSharp.Collections.Immutable @@ -8,76 +8,89 @@ namespace FSharp.Collections.Immutable type FlatList<'T> = System.Collections.Immutable.ImmutableArray<'T> // based on the F# Array module source -[] +[] module FlatList = type internal FlatListFactory = System.Collections.Immutable.ImmutableArray let inline internal checkNotDefault argName (list : FlatList<'T>) = - if list.IsDefault then invalidArg argName "Uninstantiated ImmutableArray/FlatList" + if list.IsDefault then + invalidArg argName "Uninstantiated ImmutableArray/FlatList" + let inline internal check (list : FlatList<'T>) = checkNotDefault (nameof list) list ////////// Creating ////////// - let inline empty<'T> : FlatList<_> = FlatListFactory.Create<'T>() + let inline empty<'T> : FlatList<_> = FlatListFactory.Create<'T> () let inline singleton<'T> (item : 'T) : FlatList<'T> = FlatListFactory.Create<'T> (item) let inline ofSeq source = FlatListFactory.CreateRange source let inline ofArray (source : _ array) = FlatListFactory.CreateRange source - let inline toSeq (flatList: FlatList<_>) = flatList :> seq<_> - let inline toArray (list : FlatList<_>) = check list; Seq.toArray list + let inline toSeq (flatList : FlatList<_>) = flatList :> seq<_> + + let inline toArray (list : FlatList<_>) = + check list + Seq.toArray list ////////// Building ////////// let moveFromBuilder (builder : FlatList<_>.Builder) : FlatList<_> = checkNotNull (nameof builder) builder - builder.MoveToImmutable() + builder.MoveToImmutable () + let ofBuilder (builder : FlatList<_>.Builder) : FlatList<_> = checkNotNull (nameof builder) builder - builder.ToImmutable() + builder.ToImmutable () - let inline builder () : FlatList<'T>.Builder = FlatListFactory.CreateBuilder() - let inline builderWith capacity : FlatList<'T>.Builder = FlatListFactory.CreateBuilder(capacity) + let inline builder () : FlatList<'T>.Builder = FlatListFactory.CreateBuilder () + let inline builderWith capacity : FlatList<'T>.Builder = FlatListFactory.CreateBuilder (capacity) - let toBuilder list: FlatList<_>.Builder = check list; list.ToBuilder() + let toBuilder list : FlatList<_>.Builder = + check list + list.ToBuilder () module Builder = - let inline private check (builder: FlatList<'T>.Builder) = checkNotNull (nameof builder) builder + let inline private check (builder : FlatList<'T>.Builder) = checkNotNull (nameof builder) builder - let add item builder = check builder; builder.Add(item) + let add item builder = + check builder + builder.Add (item) - let inline internal indexNotFound() = raise <| System.Collections.Generic.KeyNotFoundException() + let inline internal indexNotFound () = raise <| System.Collections.Generic.KeyNotFoundException () - let isEmpty (list: FlatList<_>) = list.IsEmpty - let isDefault (list: FlatList<_>) = list.IsDefault - let isDefaultOrEmpty (list: FlatList<_>) = list.IsDefaultOrEmpty + let isEmpty (list : FlatList<_>) = list.IsEmpty + let isDefault (list : FlatList<_>) = list.IsDefault + let isDefaultOrEmpty (list : FlatList<_>) = list.IsDefaultOrEmpty ////////// IReadOnly* ////////// - let length list = check list; list.Length + let length list = + check list + list.Length - let item index list = check list; list.[index] + let item index list = + check list + list.[index] let append list1 list2 : FlatList<'T> = checkNotDefault (nameof list1) list1 checkNotDefault (nameof list2) list2 - list1.AddRange(list2 : FlatList<_>) + list1.AddRange (list2 : FlatList<_>) /// Searches for the specified object and returns the zero-based index of the first occurrence within the range /// of elements in the list that starts at the specified index and /// contains the specified number of elements. let indexRangeWith comparer index count item list = check list - list.IndexOf(item, index, count, comparer) - let indexRange index count item list = - indexRangeWith HashIdentity.Structural index count item list - let indexFromWith comparer index item list = - indexRangeWith comparer index (length list - index) item - let indexFrom index item list = - indexFromWith HashIdentity.Structural index item list - let indexWith comparer item list = - indexFromWith comparer 0 item list + list.IndexOf (item, index, count, comparer) + + let indexRange index count item list = indexRangeWith HashIdentity.Structural index count item list + let indexFromWith comparer index item list = indexRangeWith comparer index (length list - index) item + let indexFrom index item list = indexFromWith HashIdentity.Structural index item list + let indexWith comparer item list = indexFromWith comparer 0 item list let index item list = indexWith HashIdentity.Structural item list /// Searches for the specified object and returns the zero-based index of the last occurrence within the @@ -85,126 +98,159 @@ module FlatList = /// of elements and ends at the specified index. let lastIndexRangeWith comparer index count item list = check list - list.LastIndexOf(item, index, count, comparer) - let lastIndexRange index count item list = - lastIndexRangeWith HashIdentity.Structural index count item list - let lastIndexFromWith comparer index item list = - lastIndexRangeWith comparer index (index + 1) item list - let lastIndexFrom index item list = - lastIndexFromWith HashIdentity.Structural index item list - let lastIndexWith comparer item list = - lastIndexFromWith comparer (length list - 1) item list + list.LastIndexOf (item, index, count, comparer) + + let lastIndexRange index count item list = lastIndexRangeWith HashIdentity.Structural index count item list + let lastIndexFromWith comparer index item list = lastIndexRangeWith comparer index (index + 1) item list + let lastIndexFrom index item list = lastIndexFromWith HashIdentity.Structural index item list + let lastIndexWith comparer item list = lastIndexFromWith comparer (length list - 1) item list let lastIndex item list = lastIndexWith HashIdentity.Structural item list /// Removes the specified objects from the list with the given comparer. - let removeAllWith (comparer: System.Collections.Generic.IEqualityComparer<_>) items list: FlatList<_> = + let removeAllWith (comparer : System.Collections.Generic.IEqualityComparer<'T>) (items : 'T seq) list : FlatList<_> = check list - list.RemoveRange(items, comparer) + list.RemoveRange (items, comparer) /// Removes the specified objects from the list. let removeAll items list = removeAllWith HashIdentity.Structural items list /// Removes all the elements that do not match the conditions defined by the specified predicate. - let filter predicate list: FlatList<_> = + let filter predicate list : FlatList<_> = check list - System.Predicate(not << predicate) - |> list.RemoveAll + System.Predicate (not << predicate) |> list.RemoveAll /// Removes all the elements that do not match the conditions defined by the specified predicate. let where predicate list = filter predicate list /// Removes a range of elements from the list. - let removeRange index (count: int) list: FlatList<_> = check list; list.RemoveRange(index, count) + let removeRange index (count : int) list : FlatList<_> = + check list + list.RemoveRange (index, count) - let blit source sourceIndex (destination: 'T[]) destinationIndex count = + let blit source sourceIndex (destination : 'T[]) destinationIndex count = checkNotDefault (nameof source) source - try source.CopyTo(sourceIndex, destination, destinationIndex, count) - with exn -> raise exn // throw same exception with the correct stack trace. Update exception code + + try + source.CopyTo (sourceIndex, destination, destinationIndex, count) + with exn -> + raise exn // throw same exception with the correct stack trace. Update exception code let sortRangeWithComparer comparer index count list = check list - list.Sort(index, count, comparer) + list.Sort (index, count, comparer) + let sortRangeWith comparer index count list = sortRangeWithComparer (ComparisonIdentity.FromFunction comparer) index count list + let sortRange index count list = sortRangeWithComparer ComparisonIdentity.Structural index count list - let sortWithComparer (comparer : System.Collections.Generic.IComparer<_>) list = check list; list.Sort(comparer) + + let sortWithComparer (comparer : System.Collections.Generic.IComparer<_>) list = + check list + list.Sort (comparer) + let sortWith comparer list = sortWithComparer (ComparisonIdentity.FromFunction comparer) list - let sort list = check list; list.Sort() + + let sort list = + check list + list.Sort () ////////// Loop-based ////////// let inline private builderWithLengthOf list = builderWith <| length list let init count initializer = - if count < 0 then invalidArg (nameof count) ErrorStrings.InputMustBeNonNegative + if count < 0 then + invalidArg (nameof count) ErrorStrings.InputMustBeNonNegative + let builder = builderWith count + for i = 0 to count - 1 do builder.Add <| initializer i + moveFromBuilder builder - let rec private concatAddLengths (arrs: FlatList>) i acc = - if i >= length arrs then acc - else concatAddLengths arrs (i+1) (acc + arrs.[i].Length) + let rec private concatAddLengths (arrs : FlatList>) i acc = + if i >= length arrs then + acc + else + concatAddLengths arrs (i + 1) (acc + arrs.[i].Length) let concat (arrs : FlatList>) = // consider generalizing - let result: FlatList<'T>.Builder = builderWith <| concatAddLengths arrs 0 0 + let result : FlatList<'T>.Builder = builderWith <| concatAddLengths arrs 0 0 + for i = 0 to length arrs - 1 do - result.AddRange(arrs.[i]: FlatList<'T>) + result.AddRange (arrs.[i] : FlatList<'T>) + moveFromBuilder result let inline map mapping list = check list let builder = builderWithLengthOf list + for i = 0 to length list - 1 do - builder.Add(mapping list.[i]) + builder.Add (mapping list.[i]) + moveFromBuilder builder let countBy projection list = check list // need struct box optimization - let dict = new System.Collections.Generic.Dictionary<'Key, int>(HashIdentity.Structural) + let dict = new System.Collections.Generic.Dictionary<'Key, int> (HashIdentity.Structural) // Build the groupings for v in list do let key = projection v let mutable prev = Unchecked.defaultof<_> - if dict.TryGetValue(key, &prev) then dict.[key] <- prev + 1 else dict.[key] <- 1 + + if dict.TryGetValue (key, &prev) then + dict.[key] <- prev + 1 + else + dict.[key] <- 1 let res = builderWith dict.Count let mutable i = 0 + for group in dict do - res.Add(group.Key, group.Value) + res.Add (group.Key, group.Value) i <- i + 1 + moveFromBuilder res let indexed list = check list let builder = builderWithLengthOf list + for i = 0 to length list - 1 do - builder.Add(i, list.[i]) + builder.Add (i, list.[i]) + moveFromBuilder builder let inline iter action list = check list + for i = 0 to length list - 1 do action list.[i] let iter2 action list1 list2 = checkNotDefault (nameof list1) list1 checkNotDefault (nameof list2) list2 - let f = OptimizedClosures.FSharpFunc<'T,'U, unit>.Adapt(action) + let f = OptimizedClosures.FSharpFunc<'T, 'U, unit>.Adapt (action) let len = length list1 - if len <> length list2 then invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths + + if len <> length list2 then + invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths + for i = 0 to len - 1 do - f.Invoke(list1.[i], list2.[i]) + f.Invoke (list1.[i], list2.[i]) - let distinctBy projection (list: FlatList<'T>) = - let builder: FlatList<'T>.Builder = builderWith <| length list - let set = System.Collections.Generic.HashSet<'Key>(HashIdentity.Structural) + let distinctBy projection (list : FlatList<'T>) = + let builder : FlatList<'T>.Builder = builderWith <| length list + let set = System.Collections.Generic.HashSet<'Key> (HashIdentity.Structural) let mutable outputIndex = 0 for i = 0 to length list - 1 do let item = list.[i] + if set.Add <| projection item then outputIndex <- outputIndex + 1 Builder.add item builder @@ -214,204 +260,272 @@ module FlatList = let map2 mapping list1 list2 = checkNotDefault (nameof list1) list1 checkNotDefault (nameof list2) list2 - let f = OptimizedClosures.FSharpFunc<_,_,_>.Adapt(mapping) + let f = OptimizedClosures.FSharpFunc<_, _, _>.Adapt (mapping) let len1 = list1.Length - if len1 <> list2.Length then invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths + + if len1 <> list2.Length then + invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths + let res = builderWith len1 + for i = 0 to len1 - 1 do - res.Add <| f.Invoke(list1.[i], list2.[i]) + res.Add <| f.Invoke (list1.[i], list2.[i]) + moveFromBuilder res let map3 mapping list1 list2 list3 = checkNotDefault (nameof list1) list1 checkNotDefault (nameof list2) list2 checkNotDefault (nameof list3) list3 - let f = OptimizedClosures.FSharpFunc<_,_,_,_>.Adapt(mapping) + let f = OptimizedClosures.FSharpFunc<_, _, _, _>.Adapt (mapping) let len1 = list1.Length - if not (len1 = list2.Length) - then invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths - if not (len1 = list3.Length) - then invalidArg (nameof list3) ErrorStrings.ListsHaveDifferentLengths + + if not (len1 = list2.Length) then + invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths + + if not (len1 = list3.Length) then + invalidArg (nameof list3) ErrorStrings.ListsHaveDifferentLengths let res = builderWith len1 + for i = 0 to len1 - 1 do - res.Add <| f.Invoke(list1.[i], list2.[i], list3.[i]) + res.Add <| f.Invoke (list1.[i], list2.[i], list3.[i]) + moveFromBuilder res + let mapi2 mapping list1 list2 = checkNotDefault (nameof list1) list1 checkNotDefault (nameof list2) list2 - let f = OptimizedClosures.FSharpFunc<_,_,_,_>.Adapt(mapping) + let f = OptimizedClosures.FSharpFunc<_, _, _, _>.Adapt (mapping) let len1 = list1.Length - if len1 <> list2.Length then invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths + + if len1 <> list2.Length then + invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths + let res = builderWith len1 + for i = 0 to len1 - 1 do - res.Add <| f.Invoke(i,list1.[i], list2.[i]) + res.Add <| f.Invoke (i, list1.[i], list2.[i]) + moveFromBuilder res let iteri action list = check list - let f = OptimizedClosures.FSharpFunc<_,_,_>.Adapt(action) + let f = OptimizedClosures.FSharpFunc<_, _, _>.Adapt (action) let len = list.Length + for i = 0 to len - 1 do - f.Invoke(i, list.[i]) + f.Invoke (i, list.[i]) let iteri2 action list1 list2 = checkNotDefault (nameof list1) list1 checkNotDefault (nameof list2) list2 - let f = OptimizedClosures.FSharpFunc<_,_,_,_>.Adapt(action) + let f = OptimizedClosures.FSharpFunc<_, _, _, _>.Adapt (action) let len1 = list1.Length - if len1 <> list2.Length then invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths + + if len1 <> list2.Length then + invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths + for i = 0 to len1 - 1 do - f.Invoke(i,list1.[i], list2.[i]) + f.Invoke (i, list1.[i], list2.[i]) let mapi mapping list = check list - let f = OptimizedClosures.FSharpFunc<_,_,_>.Adapt(mapping) + let f = OptimizedClosures.FSharpFunc<_, _, _>.Adapt (mapping) let len = list.Length let res = builderWithLengthOf list + for i = 0 to len - 1 do - res.Add <| f.Invoke(i,list.[i]) + res.Add <| f.Invoke (i, list.[i]) + moveFromBuilder res let exists predicate list = check list let len = list.Length - let rec loop i = i < len && (predicate list.[i] || loop (i+1)) + let rec loop i = i < len && (predicate list.[i] || loop (i + 1)) loop 0 let inline contains e list = check list let mutable state = false let mutable i = 0 + while (not state && i < list.Length) do state <- e = list.[i] i <- i + 1 + state let exists2 predicate list1 list2 = checkNotDefault (nameof list1) list1 checkNotDefault (nameof list2) list2 - let f = OptimizedClosures.FSharpFunc<_,_,_>.Adapt(predicate) + let f = OptimizedClosures.FSharpFunc<_, _, _>.Adapt (predicate) let len1 = list1.Length - if len1 <> list2.Length then invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths - let rec loop i = i < len1 && (f.Invoke(list1.[i], list2.[i]) || loop (i+1)) + + if len1 <> list2.Length then + invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths + + let rec loop i = + i < len1 + && (f.Invoke (list1.[i], list2.[i]) || loop (i + 1)) + loop 0 let forall predicate list = check list let len = list.Length - let rec loop i = i >= len || (predicate list.[i] && loop (i+1)) + let rec loop i = i >= len || (predicate list.[i] && loop (i + 1)) loop 0 let forall2 predicate list1 list2 = checkNotDefault (nameof list1) list1 checkNotDefault (nameof list2) list2 - let f = OptimizedClosures.FSharpFunc<_,_,_>.Adapt(predicate) + let f = OptimizedClosures.FSharpFunc<_, _, _>.Adapt (predicate) let len1 = list1.Length - if len1 <> list2.Length then invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths - let rec loop i = i >= len1 || (f.Invoke(list1.[i], list2.[i]) && loop (i+1)) + + if len1 <> list2.Length then + invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths + + let rec loop i = + i >= len1 + || (f.Invoke (list1.[i], list2.[i]) && loop (i + 1)) + loop 0 let groupBy projection list = check list - let dict = new System.Collections.Generic.Dictionary<'Key,ResizeArray<'T>>(HashIdentity.Structural) + let dict = new System.Collections.Generic.Dictionary<'Key, ResizeArray<'T>> (HashIdentity.Structural) // Build the groupings for i = 0 to (list.Length - 1) do let v = list.[i] let key = projection v - let ok, prev = dict.TryGetValue(key) + let ok, prev = dict.TryGetValue (key) + if ok then - prev.Add(v) + prev.Add (v) else - let prev = new ResizeArray<'T>(1) + let prev = new ResizeArray<'T> (1) dict.[key] <- prev - prev.Add(v) + prev.Add (v) // Return the list-of-lists. let result = builderWith dict.Count let mutable i = 0 + for group in dict do - result.Add(group.Key, ofSeq group.Value) + result.Add (group.Key, ofSeq group.Value) i <- i + 1 moveFromBuilder result let pick chooser list = check list + let rec loop i = if i >= list.Length then - indexNotFound() + indexNotFound () else match chooser list.[i] with - | None -> loop(i+1) + | None -> loop (i + 1) | Some res -> res + loop 0 let tryPick chooser list = check list + let rec loop i = - if i >= list.Length then None else - match chooser list.[i] with - | None -> loop(i+1) - | res -> res + if i >= list.Length then + None + else + match chooser list.[i] with + | None -> loop (i + 1) + | res -> res + loop 0 let choose chooser list = check list let res = builderWith list.Length + for i = 0 to list.Length - 1 do match chooser list.[i] with | None -> () - | Some b -> res.Add(b) + | Some b -> res.Add (b) + ofBuilder res let partition predicate list = check list let res1 = builderWith list.Length let res2 = builderWith list.Length + for i = 0 to list.Length - 1 do let x = list.[i] - if predicate x then res1.Add(x) else res2.Add(x) + if predicate x then res1.Add (x) else res2.Add (x) + ofBuilder res1, ofBuilder res2 let find predicate list = check list + let rec loop i = - if i >= list.Length then indexNotFound() else - if predicate list.[i] then list.[i] else loop (i+1) + if i >= list.Length then indexNotFound () + else if predicate list.[i] then list.[i] + else loop (i + 1) + loop 0 + let tryFind predicate list = check list + let rec loop i = - if i >= list.Length then None else - if predicate list.[i] then Some list.[i] else loop (i+1) + if i >= list.Length then None + else if predicate list.[i] then Some list.[i] + else loop (i + 1) + loop 0 + let findBack predicate list = check list + let rec loop i = - if i < 0 then indexNotFound() else - if predicate list.[i] then list.[i] else loop (i - 1) + if i < 0 then indexNotFound () + else if predicate list.[i] then list.[i] + else loop (i - 1) + loop <| length list - 1 + let tryFindBack predicate list = check list + let rec loop i = - if i < 0 then None else - if predicate list.[i] then Some list.[i] else loop (i+1) + if i < 0 then None + else if predicate list.[i] then Some list.[i] + else loop (i + 1) + loop <| length list - 1 let findIndexBack predicate list = check list + let rec loop i = - if i < 0 then indexNotFound() else - if predicate list.[i] then i else loop (i - 1) + if i < 0 then indexNotFound () + else if predicate list.[i] then i + else loop (i - 1) + loop <| length list - 1 let tryFindIndexBack predicate list = check list + let rec loop i = - if i < 0 then None else - if predicate list.[i] then Some i else loop (i - 1) + if i < 0 then None + else if predicate list.[i] then Some i + else loop (i - 1) + loop <| length list - 1 // TODO: windowed @@ -422,9 +536,12 @@ module FlatList = let inline private lengthWhile predicate list = check list let mutable count = 0 + while count < list.Length && predicate list.[count] do count <- count + 1 + count + let takeWhile predicate list = take (lengthWhile predicate list) list let skip index list = removeRange 0 index list @@ -440,8 +557,10 @@ module FlatList = let head list = item 0 list let tryItem index list = - if index >= length list || index < 0 then None - else Some(list.[index]) + if index >= length list || index < 0 then + None + else + Some (list.[index]) let tryHead list = tryItem 0 list @@ -460,7 +579,7 @@ module FlatList = let collect mapping list = concat <| map mapping list let inline build f = - let builder = builder() + let builder = builder () f builder moveFromBuilder builder @@ -469,6 +588,6 @@ module FlatList = f builder moveFromBuilder builder - ////////// +////////// module ImmutableArray = FlatList diff --git a/src/FSharp.Collections.Immutable/immutable-collection-util.fs b/src/FSharp.Collections.Immutable/ImmutableCollectionUtil.fs similarity index 73% rename from src/FSharp.Collections.Immutable/immutable-collection-util.fs rename to src/FSharp.Collections.Immutable/ImmutableCollectionUtil.fs index af62e58..608fe4f 100644 --- a/src/FSharp.Collections.Immutable/immutable-collection-util.fs +++ b/src/FSharp.Collections.Immutable/ImmutableCollectionUtil.fs @@ -1,4 +1,4 @@ -#if INTERACTIVE +#if INTERACTIVE namespace global #else namespace FSharp.Collections.Immutable @@ -6,14 +6,14 @@ namespace FSharp.Collections.Immutable [] module internal ImmutableCollectionUtil = - let inline checkNotNull name arg = + let inline checkNotNull name (arg : _ | null) = match arg with - |null -> nullArg name - |_ -> () + | null -> nullArg name + | _ -> () module internal ErrorStrings = [] let InputMustBeNonNegative = "The input must be non-negative." [] - let ListsHaveDifferentLengths = "The lists have different lengths." \ No newline at end of file + let ListsHaveDifferentLengths = "The lists have different lengths." diff --git a/src/FSharp.Collections.Immutable/immutable-list.fs b/src/FSharp.Collections.Immutable/ImmutableList.fs similarity index 50% rename from src/FSharp.Collections.Immutable/immutable-list.fs rename to src/FSharp.Collections.Immutable/ImmutableList.fs index 7586d64..48ced7c 100644 --- a/src/FSharp.Collections.Immutable/immutable-list.fs +++ b/src/FSharp.Collections.Immutable/ImmutableList.fs @@ -2,6 +2,7 @@ namespace global #else namespace FSharp.Collections.Immutable + open FSharp.Collections.Immutable.ImmutableCollectionUtil #endif open System.Collections.Immutable @@ -11,35 +12,46 @@ module ImmutableList = ////////// Factory ////////// - let inline internal check (list: IImmutableList<_>) = checkNotNull (nameof list) list + let inline internal check (list : IImmutableList<_>) = checkNotNull (nameof list) list - let inline empty<'T> = ImmutableList.Create<'T>() + let inline empty<'T> = ImmutableList.Create<'T> () let inline singleton<'T> (item : 'T) : ImmutableList<'T> = ImmutableList.Create<'T> (item) - let inline ofSeq source = checkNotNull (nameof source) source; ImmutableList.CreateRange source - let inline ofArray (source : _ array) = checkNotNull (nameof source) source; ImmutableList.CreateRange source + let inline ofSeq source = + checkNotNull (nameof source) source + ImmutableList.CreateRange source + + let inline ofArray (source : _ array) = + checkNotNull (nameof source) source + ImmutableList.CreateRange source + let inline ofList (list : _ list) = ofSeq list let inline toSeq (list : ImmutableList<_>) = list :> seq<_> - let inline toArray (list : ImmutableList<_>) = check list; Seq.toArray list + + let inline toArray (list : ImmutableList<_>) = + check list + Seq.toArray list ////////// Building ////////// - let inline ofBuilder (builder : ImmutableList<_>.Builder) = builder.ToImmutable() + let inline ofBuilder (builder : ImmutableList<_>.Builder) = builder.ToImmutable () - let inline builder () = ImmutableList.CreateBuilder() + let inline builder () = ImmutableList.CreateBuilder () - let toBuilder (list: ImmutableList<_>) = check list; list.ToBuilder() + let toBuilder (list : ImmutableList<_>) = + check list + list.ToBuilder () let inline build f = - let builder = builder() + let builder = builder () f builder - builder.ToImmutable() + builder.ToImmutable () let inline update f list = let builder = toBuilder list f builder - builder.ToImmutable() + builder.ToImmutable () open System.Collections.Generic @@ -47,63 +59,80 @@ module ImmutableList = ////////// IReadOnly* ////////// - let length list = check list; list.Count + let length list = + check list + list.Count - let item index list = check list; list.[index] + let item index list = + check list + list.[index] ////////// ImmutableList ////////// - let contains item (list : ImmutableList<_>) = list.Contains(item) + let contains item (list : ImmutableList<_>) = list.Contains (item) - let reverse (list : ImmutableList<_>) = list.Reverse() + let reverse (list : ImmutableList<_>) = list.Reverse () - let reverseRange (index, count) (list : ImmutableList<_>) = list.Reverse(index, count) + let reverseRange (index, count) (list : ImmutableList<_>) = list.Reverse (index, count) ////////// IImmutableList ////////// /// Replaces an element in the list at a given position with the specified element. - let withItem index value list = check list; list.SetItem(index, value) + let withItem index value list = + check list + list.SetItem (index, value) /// Returns a new list with the first matching element in the list replaced with the specified element with /// the given comparer. let replaceWith comparer oldValue value list = check list - list.Replace(oldValue, value, comparer) + list.Replace (oldValue, value, comparer) /// Returns a new list with the first matching element in the list replaced with the specified element. - let replace oldValue value list = - replaceWith HashIdentity.Structural oldValue value list + let replace oldValue value list = replaceWith HashIdentity.Structural oldValue value list /// Creates a list with all the items removed, but with the same sorting and ordering semantics as /// this list. - let clear list = check list; list.Clear() + let clear list = + check list + list.Clear () /// Makes a copy of the list, and adds the specified object to the end of the copied list. - let add item list = check list; list.Add item + let add item list = + check list + list.Add item /// Makes a copy of the list and adds the specified objects to the end of the copied list. - let append list items = check list; list.AddRange items + let append list items = + check list + list.AddRange items /// Inserts the specified element at the specified index in a immutable list. - let insert index item list = check list; list.Insert(index, item) + let insert index item list = + check list + list.Insert (index, item) /// Inserts the specified elements at the specified index in the immutable list. - let insertRange index items list = check list; list.InsertRange(index, items) // TODO: rename + let insertRange index items list = + check list + list.InsertRange (index, items) // TODO: rename /// Removes the first occurrence of a specified object from this immutable list using the given comparer. - let removeWith comparer item list = check list; list.Remove(item, comparer) + let removeWith comparer item list = + check list + list.Remove (item, comparer) /// Removes the first occurrence of a specified object from this immutable list. let remove item list = removeWith HashIdentity.Structural item list /// Removes the specified objects from the list with the given comparer. - let exceptWith (comparer: IEqualityComparer<_>) items list = + let exceptWith (comparer : IEqualityComparer<_>) items list = check list - list.RemoveRange(items, comparer) + list.RemoveRange (items, comparer) /// Removes the specified objects from the list. let except items list = exceptWith HashIdentity.Structural items list @@ -112,29 +141,29 @@ module ImmutableList = /// Removes all the elements that do not match the conditions defined by the specified predicate. let filter predicate list = check list - Predicate(not << predicate) - |> list.RemoveAll + Predicate (not << predicate) |> list.RemoveAll /// Removes a range of elements from the System.Collections.Immutable.IImmutableList`1. - let removeRange index (count: int) list = check list; list.RemoveRange(index, count) + let removeRange index (count : int) list = + check list + list.RemoveRange (index, count) /// Removes the element at the specified index of the immutable list. - let removeAt index list = check list; list.RemoveAt index + let removeAt index list = + check list + list.RemoveAt index /// Searches for the specified object and returns the zero-based index of the first occurrence within the range /// of elements in the list that starts at the specified index and /// contains the specified number of elements. let indexRangeWith comparer index count item list = - check list; - list.IndexOf(item, index, count, comparer) - let indexRange index count item list = - indexRangeWith HashIdentity.Structural index count item list - let indexFromWith comparer index item list = - indexRangeWith comparer index (length list - index) item - let indexFrom index item list = - indexFromWith HashIdentity.Structural index item list - let indexWith comparer item list = - indexFromWith comparer 0 item list + check list + list.IndexOf (item, index, count, comparer) + + let indexRange index count item list = indexRangeWith HashIdentity.Structural index count item list + let indexFromWith comparer index item list = indexRangeWith comparer index (length list - index) item + let indexFrom index item list = indexFromWith HashIdentity.Structural index item list + let indexWith comparer item list = indexFromWith comparer 0 item list let index item list = indexWith HashIdentity.Structural item list @@ -143,74 +172,92 @@ module ImmutableList = /// of elements and ends at the specified index. let lastIndexRangeWith comparer index count item list = check list - list.LastIndexOf(item, index, count, comparer) - let lastIndexRange index count item list = - lastIndexRangeWith HashIdentity.Structural index count item list - let lastIndexFromWith comparer index item list = - lastIndexRangeWith comparer index (index + 1) item list - let lastIndexFrom index item list = - lastIndexFromWith HashIdentity.Structural index item list - let lastIndexWith comparer item list = - lastIndexFromWith comparer (length list - 1) item list + list.LastIndexOf (item, index, count, comparer) + + let lastIndexRange index count item list = lastIndexRangeWith HashIdentity.Structural index count item list + let lastIndexFromWith comparer index item list = lastIndexRangeWith comparer index (index + 1) item list + let lastIndexFrom index item list = lastIndexFromWith HashIdentity.Structural index item list + let lastIndexWith comparer item list = lastIndexFromWith comparer (length list - 1) item list let lastIndex item list = lastIndexWith HashIdentity.Structural item list ////////// Filter-based ////////// - let filterFold (predicate: 'State -> 'T -> bool * 'State) initial list = + let filterFold (predicate : 'State -> 'T -> bool * 'State) initial list = let state = ref initial - filter (fun item -> - let condition, state' = predicate !state item - state := state' - condition) list, !state + + filter + (fun item -> + let condition, state' = predicate !state item + state := state' + condition + ) + list, + !state let skipWhile predicate list = let condition = ref true - filter (fun item -> - if !condition then - condition := !condition && predicate item - !condition - else false) list + + filter + (fun item -> + if !condition then + condition := !condition && predicate item + !condition + else + false + ) + list let skipUntil predicate list = skipWhile (not << predicate) list let takeWhile predicate list = let condition = ref true - filter (fun item -> - if !condition then - condition := !condition && predicate item - not !condition - else true) list + + filter + (fun item -> + if !condition then + condition := !condition && predicate item + not !condition + else + true + ) + list + let takeUntil predicate list = takeWhile (not << predicate) list ////////// Loop-based ////////// let concat lists = checkNotNull (nameof lists) lists - build <| fun result -> + + build + <| fun result -> for list in lists do result.AddRange list let map mapping list = check list - build <| fun builder -> + + build + <| fun builder -> for item in list do - builder.Add(mapping item) + builder.Add (mapping item) let choose chooser list = check list - build <| fun builder -> + + build + <| fun builder -> for item in list do match chooser item with - |Some item -> builder.Add item - |None -> () + | Some item -> builder.Add item + | None -> () ////////// Based on other operations ////////// let isEmpty list = length list = 0 - let take count list = - removeRange count (length list - count) list + let take count list = removeRange count (length list - count) list let skip index list = removeRange 0 index list @@ -225,8 +272,10 @@ module ImmutableList = let tail list = removeAt 0 list let tryItem index list = - if index >= length list || index < 0 then None - else Some(list.[index]) + if index >= length list || index < 0 then + None + else + Some (list.[index]) let tryHead list = tryItem 0 list @@ -245,38 +294,60 @@ module ImmutableList = // throw the same exception try Seq.init count initializer |> ignore - with - |exn -> raise exn // get the right stack trace - build <| fun builder -> + with exn -> + raise exn // get the right stack trace + + build + <| fun builder -> for i = 0 to count - 1 do builder.Add <| initializer i let unfold generator state = - let rec unfoldLoop state (builder: ImmutableList<_>.Builder) = + let rec unfoldLoop state (builder : ImmutableList<_>.Builder) = match generator state with - |Some(state, item) -> builder.Add(item); unfoldLoop state builder - |None -> () + | Some (state, item) -> + builder.Add (item) + unfoldLoop state builder + | None -> () + build <| unfoldLoop state ////////// Seq-based ////////// - let find predicate list = check list; Seq.find predicate list + let find predicate list = + check list + Seq.find predicate list - let tryFind predicate list = check list; Seq.tryFind predicate list + let tryFind predicate list = + check list + Seq.tryFind predicate list - let findIndex predicate list = check list; Seq.findIndex predicate list + let findIndex predicate list = + check list + Seq.findIndex predicate list - let tryFindIndex predicate list = check list; Seq.tryFindIndex predicate list + let tryFindIndex predicate list = + check list + Seq.tryFindIndex predicate list - let pick chooser list = check list; Seq.pick chooser list + let pick chooser list = + check list + Seq.pick chooser list - let fold folder state list = check list; Seq.fold folder state list + let fold folder state list = + check list + Seq.fold folder state list - let forall predicate list = check list; Seq.forall predicate list + let forall predicate list = + check list + Seq.forall predicate list - let forall2 predicate (list1: IImmutableList<_>) (list2: IImmutableList<_>) = - checkNotNull (nameof list1) list1; checkNotNull (nameof list2) list2 + let forall2 predicate (list1 : IImmutableList<_>) (list2 : IImmutableList<_>) = + checkNotNull (nameof list1) list1 + checkNotNull (nameof list2) list2 Seq.forall2 predicate list1 list2 - let iter action list = check list; Seq.iter action list + let iter action list = + check list + Seq.iter action list diff --git a/src/FSharp.Collections.Immutable/IndexedSeq.fs b/src/FSharp.Collections.Immutable/IndexedSeq.fs new file mode 100644 index 0000000..7d388b4 --- /dev/null +++ b/src/FSharp.Collections.Immutable/IndexedSeq.fs @@ -0,0 +1,17 @@ +namespace FSharp.Collections.Immutable + +type IIndexedSeq<'T> = System.Collections.Generic.IReadOnlyList<'T> + +module IndexedSeq = + + let check (seq : IIndexedSeq<_>) = checkNotNull (nameof seq) seq + + let item index seq = + check seq + seq.[index] + + let length seq = + check seq + seq.Count + +module ReadOnlyList = IndexedSeq diff --git a/src/FSharp.Collections.Immutable/Maps.fs b/src/FSharp.Collections.Immutable/Maps.fs new file mode 100644 index 0000000..12f5d63 --- /dev/null +++ b/src/FSharp.Collections.Immutable/Maps.fs @@ -0,0 +1,339 @@ +namespace FSharp.Collections.Immutable + +open System.Collections.Generic + +type IMap<'Key, 'Value> = System.Collections.Immutable.IImmutableDictionary<'Key, 'Value> + +type HashMap<'Key, 'Value when 'Key : not null> = System.Collections.Immutable.ImmutableDictionary<'Key, 'Value> + +type HashMapBuilder<'Key, 'Value when 'Key : not null> = HashMap<'Key, 'Value>.Builder + +[] +module HashMap = + + type internal HashMapFactory = System.Collections.Immutable.ImmutableDictionary + + let inline check (map : HashMap<_, _>) = checkNotNull (nameof map) map + + ////////// Creating ////////// + + let inline empty<'Key, 'Value when 'Key : not null> = HashMapFactory.Create<'Key, 'Value> () + let inline singleton item = empty.Add (item) + + let inline ofSeq source = HashMapFactory.CreateRange (source) + + let inline ofSeqWith getKey source = + source + |> Seq.map (fun i -> KeyValuePair (getKey i, i)) + |> HashMapFactory.CreateRange + + let inline ofSeqGroupBy getKey source = + source + |> Seq.groupBy getKey + |> Seq.map (fun (key, value) -> KeyValuePair (key, value)) + |> HashMapFactory.CreateRange + + let inline ofArray (source : _ array) = HashMapFactory.CreateRange (source) + + let inline toSeq (map : HashMap<_, _>) = map :> seq<_> + + let inline toArray (map : HashMap<_, _>) = + check map + Seq.toArray map + + ////////// Building ////////// + + let inline builder () = HashMapFactory.CreateBuilder () + let inline builderWithKeyComparer comparer = HashMapFactory.CreateBuilder (comparer) + let inline builderWithComparers keyComparer valueComparer = HashMapFactory.CreateBuilder (keyComparer, valueComparer) + + let inline ofBuilder (mapBuilder : HashMapBuilder<_, _>) = + checkNotNull (nameof mapBuilder) mapBuilder + mapBuilder.ToImmutable () + + let inline toBuilder map : HashMapBuilder<_, _> = + check map + map.ToBuilder () + + let inline ofKeyComparer<'Key, 'Value when 'Key : not null> comparer = HashMapFactory.Create<'Key, 'Value> (comparer) + let inline ofComparers<'Key, 'Value when 'Key : not null> keyComparer valueComparer = + HashMapFactory.Create<'Key, 'Value> (keyComparer, valueComparer) + + + let inline isEmpty map = + check map + map.IsEmpty + + let inline length map = + check map + map.Count + + let inline keyComparer map = + check map + map.KeyComparer + + let inline valueComparer map = + check map + map.ValueComparer + + let inline containsKey key map = + check map + map.ContainsKey key + + let inline find key map = + check map + map.[key] + + let inline tryFind key map = + check map + + match map.TryGetValue (key) with + | true, value -> Some value + | false, _ -> None + + let inline vTryFind key map = + check map + + match map.TryGetValue (key) with + | true, value -> ValueSome value + | false, _ -> ValueNone + + let inline pick chooser map = + check map + map |> Seq.pick (fun kvp -> chooser kvp.Key kvp.Value) + + let inline tryPick chooser map = + check map + map |> Seq.tryPick (fun kvp -> chooser kvp.Key kvp.Value) + + let inline vTryPick chooser map = + check map + + match map |> Seq.tryPick (fun kvp -> chooser kvp.Key kvp.Value) with + | Some value -> ValueSome value + | None -> ValueNone + + let inline iter action (map : HashMap<_, _>) = + check map + map |> Seq.iter (fun kvp -> action kvp.Key kvp.Value) + + let inline exists predicate map = + check map + map |> Seq.exists (fun kvp -> predicate kvp.Key kvp.Value) + + let inline add key value map : HashMap<_, _> = + check map + map.Add (key, value) + + let inline append map pairs : HashMap<_, _> = + check map + checkNotNull (nameof pairs) pairs + map.AddRange pairs + + let inline remove key map : HashMap<_, _> = + check map + map.Remove key + + let inline except keys map : HashMap<_, _> = + check map + map.RemoveRange keys + + let inline clear map : HashMap<_, _> = + check map + map.Clear () + + let inline filter predicate map = + map + |> Seq.filter (fun (kvp : KeyValuePair<_, _>) -> predicate kvp.Key kvp.Value) + + let inline forall predicate map = + map + |> Seq.forall (fun (kvp : KeyValuePair<_, _>) -> predicate kvp.Key kvp.Value) + + let inline map mapping map' = + map' + |> Seq.map (fun (kvp : KeyValuePair<_, _>) -> mapping kvp.Key kvp.Value) + |> ofSeq + + let inline where predicate map = + map + |> Seq.where (fun (kvp : KeyValuePair<_, _>) -> predicate kvp.Key kvp.Value) + |> empty.AddRange + + +type SortedMap<'Key, 'Value when 'Key : not null> = System.Collections.Immutable.ImmutableSortedDictionary<'Key, 'Value> + +type SortedMapBuilder<'Key, 'Value when 'Key : not null> = SortedMap<'Key, 'Value>.Builder + +[] +module SortedMap = + + type internal SortedMapFactory = System.Collections.Immutable.ImmutableSortedDictionary + + let inline check (sortedMap : SortedMap<_, _>) = checkNotNull (nameof sortedMap) sortedMap + + ////////// Creating ////////// + + let inline empty<'Key, 'Value when 'Key : not null> = SortedMapFactory.Create<'Key, 'Value> () + let inline singleton item = SortedMapFactory.Create (item) + + let inline ofSeq source = SortedMapFactory.CreateRange (source) + + let inline ofSeqWith getKey source = + source + |> Seq.map (fun i -> KeyValuePair (getKey i, i)) + |> SortedMapFactory.CreateRange + + let inline ofSeqGroupBy getKey source = + source + |> Seq.groupBy getKey + |> Seq.map (fun (key, value) -> KeyValuePair (key, value)) + + let inline ofArray (source : _ array) = SortedMapFactory.CreateRange (source) + + let inline toSeq (map : SortedMap<_, _>) = map :> seq<_> + + let inline toArray (map : SortedMap<_, _>) = + check map + Seq.toArray map + + ////////// Building ////////// + + let inline builder () = SortedMapFactory.CreateBuilder () + let inline builderWithKeyComparer comparer = SortedMapFactory.CreateBuilder (comparer) + let inline builderWithComparers keyComparer valueComparer = SortedMapFactory.CreateBuilder (keyComparer, valueComparer) + + let inline ofBuilder (sortedMapBuilder : SortedMapBuilder<_, _>) = + checkNotNull (nameof sortedMapBuilder) sortedMapBuilder + sortedMapBuilder.ToImmutable () + + let inline toBuilder map : SortedMapBuilder<_, _> = + check map + map.ToBuilder () + + let inline ofKeyComparer<'Key, 'Value when 'Key : not null> comparer = SortedMapFactory.Create<'Key, 'Value> (comparer) + let inline ofComparers<'Key, 'Value when 'Key : not null> keyComparer valueComparer = + SortedMapFactory.Create<'Key, 'Value> (keyComparer, valueComparer) + + + let inline isEmpty map = + check map + map.IsEmpty + + let inline length map = + check map + map.Count + + let inline keyComparer map = + check map + map.KeyComparer + + let inline valueComparer map = + check map + map.ValueComparer + + let inline containsKey key map = + check map + map.ContainsKey key + + let inline find key map = + check map + map.[key] + + let inline tryFind key map = + check map + + match map.TryGetValue (key) with + | true, value -> Some value + | false, _ -> None + + let inline vTryFind key map = + check map + + match map.TryGetValue (key) with + | true, value -> ValueSome value + | false, _ -> ValueNone + + let inline pick chooser map = + check map + map |> Seq.pick (fun kvp -> chooser kvp.Key kvp.Value) + + let inline tryPick chooser map = + check map + map |> Seq.tryPick (fun kvp -> chooser kvp.Key kvp.Value) + + let inline vTryPick chooser map = + check map + + match map |> Seq.tryPick (fun kvp -> chooser kvp.Key kvp.Value) with + | Some value -> ValueSome value + | None -> ValueNone + + let inline iter action (map : SortedMap<_, _>) = + check map + map |> Seq.iter (fun kvp -> action kvp.Key kvp.Value) + + let inline exists predicate map = + check map + map |> Seq.exists (fun kvp -> predicate kvp.Key kvp.Value) + + let inline add key value map : SortedMap<_, _> = + check map + map.Add (key, value) + + let inline append map pairs : SortedMap<_, _> = + check map + checkNotNull (nameof pairs) pairs + map.AddRange pairs + + let inline remove key map : SortedMap<_, _> = + check map + map.Remove key + + let inline except keys map : SortedMap<_, _> = + check map + map.RemoveRange keys + + let inline clear map : SortedMap<_, _> = + check map + map.Clear () + + let inline findKey predicate map = + check map + + match (map |> Seq.tryFind (fun kvp -> predicate kvp.Key kvp.Value)) with + | Some value -> value.Key + | None -> raise (new KeyNotFoundException ()) + + let inline tryFindKey predicate map = + check map + map |> Seq.tryFind (fun kvp -> predicate kvp.Key kvp.Value) + + let inline vTryFindKey predicate map = + check map + + match (map |> Seq.tryFind (fun kvp -> predicate kvp.Key kvp.Value)) with + | Some value -> ValueSome value.Key + | None -> ValueNone + + let inline filter predicate map = + map + |> Seq.filter (fun (kvp : KeyValuePair<_, _>) -> predicate kvp.Key kvp.Value) + + let inline forall predicate map = + map + |> Seq.forall (fun (kvp : KeyValuePair<_, _>) -> predicate kvp.Key kvp.Value) + + let inline map mapping (map' : SortedMap<_, _>) = + map' + |> Seq.map (fun (kvp : KeyValuePair<_, _>) -> mapping kvp.Key kvp.Value) + |> ofSeq + + let inline where predicate map = + map + |> Seq.where (fun (kvp : KeyValuePair<_, _>) -> predicate kvp.Key kvp.Value) + |> empty.AddRange diff --git a/src/FSharp.Collections.Immutable/Queue.fs b/src/FSharp.Collections.Immutable/Queue.fs new file mode 100644 index 0000000..dd8d64f --- /dev/null +++ b/src/FSharp.Collections.Immutable/Queue.fs @@ -0,0 +1,132 @@ +namespace FSharp.Collections.Immutable + +type IQueue<'T> = System.Collections.Immutable.IImmutableQueue<'T> + +type Queue<'T> = System.Collections.Immutable.ImmutableQueue<'T> + +[] +module Queue = + + type internal QueueFactory = System.Collections.Immutable.ImmutableQueue + + let inline private check (queue : IQueue<_>) = checkNotNull (nameof queue) queue + + let inline empty<'T> : Queue<'T> = QueueFactory.Create<'T> () + + let inline singleton<'T> (item : 'T) : Queue<'T> = QueueFactory.Create<'T> (item) + + let inline ofSeq (source : 'T seq) : Queue<'T> = QueueFactory.CreateRange source + + let inline toSeq (queue : Queue<_>) = queue :> seq<_> + + let isEmpty queue = + check queue + queue.IsEmpty + + let clear queue : IQueue<_> = + check queue + queue.Clear () + + let enqueue item queue : IQueue<_> = + check queue + queue.Enqueue item + + let head queue = + check queue + queue.Peek () + + let tail queue : IQueue<_> = + check queue + queue.Dequeue () + + ////////// + + let (|Cons|Nil|) queue = // consider renaming + if isEmpty queue then Nil else Cons (head queue, tail queue) + + ////////// Predicate based ////////// + + let filter predicate queue = + let rec loop queue result = + match queue with + | Cons (head, tail) -> + loop tail + <| if predicate head then enqueue head result else result + | Nil -> result + + loop queue <| clear queue + + ////////// Seq-based ////////// + + let find predicate queue = + check queue + Seq.find predicate queue + + let tryFind predicate queue = + check queue + Seq.tryFind predicate queue + + let findIndex predicate queue = + check queue + Seq.findIndex predicate queue + + let tryFindIndex predicate queue = + check queue + Seq.tryFindIndex predicate queue + + let pick chooser queue = + check queue + Seq.pick chooser queue + + let tryPick chooser queue = + check queue + Seq.tryPick chooser queue + + let iter action queue = + check queue + Seq.iter action queue + + let iteri action queue = + check queue + Seq.iteri action queue + + let iter2 action (queue1 : IQueue<_>) (queue2 : IQueue<_>) = + checkNotNull (nameof queue1) queue1 + checkNotNull (nameof queue2) queue2 + Seq.iter2 action queue1 queue2 + + let fold folder state queue = + check queue + Seq.fold folder state queue + + let forall predicate queue = + check queue + Seq.forall predicate + + let exists predicate queue = + check queue + Seq.exists predicate queue + + let reduce reduction queue = + check queue + Seq.reduce reduction queue + + let inline sum queue = + check queue + Seq.sum queue + + let inline sumBy projection queue = + check queue + Seq.sumBy projection queue + + let inline average queue = + check queue + Seq.average queue + + let inline averageBy projection queue = + check queue + Seq.averageBy projection + +module ImmutableQueue = Queue diff --git a/src/FSharp.Collections.Immutable/seq.fs b/src/FSharp.Collections.Immutable/Seq.fs similarity index 100% rename from src/FSharp.Collections.Immutable/seq.fs rename to src/FSharp.Collections.Immutable/Seq.fs diff --git a/src/FSharp.Collections.Immutable/Sets.fs b/src/FSharp.Collections.Immutable/Sets.fs new file mode 100644 index 0000000..cbdba22 --- /dev/null +++ b/src/FSharp.Collections.Immutable/Sets.fs @@ -0,0 +1,266 @@ +namespace FSharp.Collections.Immutable + +type ISet<'T> = System.Collections.Immutable.IImmutableSet<'T> + +type HashSet<'T> = System.Collections.Immutable.ImmutableHashSet<'T> +type HashSetBuilder<'T> = HashSet<'T>.Builder + +[] +module HashSet = + + type internal HashSetFactory = System.Collections.Immutable.ImmutableHashSet + + let inline check (set : HashSet<_>) = checkNotNull (nameof set) set + + ////////// Creating ////////// + + let inline empty<'T> = HashSetFactory.Create<'T> () + let inline singleton<'T> (item : 'T) = HashSetFactory.Create<'T> (item) + let inline ofSeq source = HashSetFactory.CreateRange (source) + let inline ofSeqWithComparer comparer source = HashSetFactory.Create (comparer, items = (source |> Array.ofSeq)) + let inline ofArray (source : _ array) = HashSetFactory.CreateRange (source) + + let inline ofBuilder (hashSetBuilder : HashSetBuilder<_>) = + checkNotNull (nameof hashSetBuilder) hashSetBuilder + hashSetBuilder.ToImmutable () + + let inline ofComparer<'T> comparer = HashSetFactory.Create<'T> (equalityComparer = comparer) + + let inline toSeq (set : HashSet<_>) = set :> seq<_> + + let inline toArray (set : HashSet<_>) = + check set + Seq.toArray set + + ////////// Building ////////// + + let inline builder () = HashSetFactory.CreateBuilder () + let inline builderWith capacity : HashSet<'T>.Builder = HashSetFactory.CreateBuilder (capacity) + let inline builderWithComparer comparer = HashSetFactory.CreateBuilder (comparer) + + let inline toBuilder set : HashSetBuilder<_> = + check set + set.ToBuilder () + + let inline keyComparer set = + check set + set.KeyComparer + + let inline length set = + check set + set.Count + + let inline isEmpty set = + check set + set.IsEmpty + + let inline contains value set = + check set + set.Contains value + + let inline exists predicate map = + check map + map |> Seq.exists predicate + + let inline isSubset (set1 : HashSet<_>) set2 = + check set1 + set1.IsSubsetOf set2 + + let inline isProperSubset (set1 : HashSet<_>) set2 = + check set1 + set1.IsProperSubsetOf set2 + + let inline isSuperset (set1 : HashSet<_>) set2 = + check set1 + set1.IsSupersetOf set2 + + let inline isProperSuperset (set1 : HashSet<_>) set2 = + check set1 + set1.IsProperSupersetOf set2 + + let inline add value set : HashSet<_> = + check set + set.Add (value) + + let inline union set values : HashSet<_> = + check set + values |> set.Union + + let inline unionMany (sets : HashSet<_> seq) = Seq.reduce union sets + + let inline intersect (set1 : HashSet<_>) set2 = + check set1 + set1.Intersect set2 + + let inline intersectMany (sets : HashSet<_> seq) = Seq.reduce intersect sets + + let inline remove value set : HashSet<_> = + check set + set.Remove value + + let inline difference values set : HashSet<_> = + check set + values |> set.Except + + let inline clear set : HashSet<_> = + check set + set.Clear () + + let inline filter predicate set = set |> Seq.filter predicate |> empty.Union + + let inline where predicate set = set |> Seq.where predicate |> empty.Union + + let inline pick chooser set = + check set + set |> Seq.pick chooser + + let inline tryPick chooser set = + check set + set |> Seq.tryPick chooser + + let inline vTryPick chooser set = + check set + + match set |> Seq.tryPick chooser with + | Some value -> ValueSome value + | None -> ValueNone + + let inline map mapping (set : HashSet<_>) = set |> Seq.map mapping |> ofSeq + + let inline forall predicate set = set |> Seq.forall predicate + + let inline iter action (set : HashSet<_>) = + check set + set |> Seq.iter action + +type SortedSet<'T> = System.Collections.Immutable.ImmutableSortedSet<'T> +type SortedSetBuilder<'T> = SortedSet<'T>.Builder + +[] +module SortedSet = + + type internal SortedSetFactory = System.Collections.Immutable.ImmutableSortedSet + + let inline check (sortedSet : SortedSet<_>) = checkNotNull (nameof sortedSet) sortedSet + + ////////// Creating ////////// + + let inline empty<'T> = SortedSetFactory.Create<'T> () + let inline singleton<'T> (item : 'T) = SortedSetFactory.Create<'T> (item) + let inline ofSeq source = SortedSetFactory.CreateRange (source) + let inline ofSeqWithComparer comparer source = SortedSetFactory.Create (comparer, items = (source |> Array.ofSeq)) + let inline ofArray (source : _ array) = SortedSetFactory.CreateRange (source) + + let inline ofBuilder (sortedSetBuilder : SortedSetBuilder<_>) = + checkNotNull (nameof sortedSetBuilder) sortedSetBuilder + sortedSetBuilder.ToImmutable () + + let inline ofComparer<'T> comparer = SortedSetFactory.Create<'T> (comparer = comparer) + + let inline toSeq (set : SortedSet<_>) = set :> seq<_> + + let inline toArray (set : SortedSet<_>) = + check set + Seq.toArray set + + ////////// Building ////////// + + let inline builder () = SortedSetFactory.CreateBuilder () + let inline builderWith capacity : SortedSet<'T>.Builder = SortedSetFactory.CreateBuilder (capacity) + let inline builderWithComparer comparer = SortedSetFactory.CreateBuilder (comparer) + + let inline toBuilder set : SortedSetBuilder<_> = + check set + set.ToBuilder () + + let inline keyComparer set = + check set + set.KeyComparer + + + let inline length set = + check set + set.Count + + let inline contains value set = + check set + set.Contains value + + let inline isEmpty set = + check set + set.IsEmpty + + let inline exists predicate map = + check map + map |> Seq.exists predicate + + let inline isSubset (set1 : SortedSet<_>) set2 = + check set1 + set1.IsSubsetOf set2 + + let inline isProperSubset (set1 : SortedSet<_>) set2 = + check set1 + set1.IsProperSubsetOf set2 + + let inline isSuperset (set1 : SortedSet<_>) set2 = + check set1 + set1.IsSupersetOf set2 + + let inline isProperSuperset (set1 : SortedSet<_>) set2 = + check set1 + set1.IsProperSupersetOf set2 + + let inline add value set : SortedSet<_> = + check set + set.Add (value) + + let inline union set values : SortedSet<_> = + check set + values |> set.Union + + let inline unionMany (sets : SortedSet<_> seq) = Seq.reduce union sets + let inline intersect (set1 : SortedSet<_>) set2 = set1.Intersect set2 + let inline intersectMany (sets : SortedSet<_> seq) = Seq.reduce intersect sets + + let inline remove value set : SortedSet<_> = + check set + set.Remove value + + let inline difference values set : SortedSet<_> = + check set + values |> set.Except + + let inline clear set : SortedSet<_> = + check set + set.Clear () + + let inline filter predicate set = set |> Seq.filter predicate |> empty.Union + + let inline where predicate set = set |> Seq.where predicate |> empty.Union + + let inline pick chooser set = + check set + set |> Seq.pick chooser + + let inline tryPick chooser set = + check set + set |> Seq.tryPick chooser + + let inline vTryPick chooser set = + check set + + match set |> Seq.tryPick chooser with + | Some value -> ValueSome value + | None -> ValueNone + + let inline map mapping (set : SortedSet<_>) = set |> Seq.map mapping |> ofSeq + + let inline forall predicate set = set |> Seq.forall predicate + + let inline iter action (set : SortedSet<_>) = + check set + set |> Seq.iter action diff --git a/src/FSharp.Collections.Immutable/stack.fs b/src/FSharp.Collections.Immutable/Stack.fs similarity index 62% rename from src/FSharp.Collections.Immutable/stack.fs rename to src/FSharp.Collections.Immutable/Stack.fs index 7fe7e57..a9502e5 100644 --- a/src/FSharp.Collections.Immutable/stack.fs +++ b/src/FSharp.Collections.Immutable/Stack.fs @@ -4,19 +4,21 @@ type IStack<'T> = System.Collections.Immutable.IImmutableStack<'T> type Stack<'T> = System.Collections.Immutable.ImmutableStack<'T> -[] +[] module Stack = type internal StackFactory = System.Collections.Immutable.ImmutableStack let inline internal check (stack : IStack<_>) = checkNotNull (nameof stack) stack - let inline empty<'T> = StackFactory.Create<'T>() + let inline empty<'T> = StackFactory.Create<'T> () let inline ofSeq source = StackFactory.CreateRange source - let inline ofArray (array : 'T []) : Stack<'T> = ofSeq array + let inline ofArray (array : 'T[]) : Stack<'T> = ofSeq array - let inline toSeq (stack: IStack<_>) = stack :> seq<_> + let inline toSeq (stack : IStack<_>) = stack :> seq<_> let push head stack : IStack<'T> = check stack @@ -26,22 +28,25 @@ module Stack = let peek stack = check stack - stack.Peek() + stack.Peek () let head stack = peek stack let tail stack : IStack<_> = check stack - stack.Pop() + stack.Pop () let pop stack = check stack - stack.Peek(), tail stack + stack.Peek (), tail stack let (|Cons|Nil|) stack = check stack - if stack.IsEmpty then Nil - else Cons(stack.Peek(), stack.Pop()) + + if stack.IsEmpty then + Nil + else + Cons (stack.Peek (), stack.Pop ()) ///////////// module ImmutableStack = Stack diff --git a/src/FSharp.Collections.Immutable/indexed-seq.fs b/src/FSharp.Collections.Immutable/indexed-seq.fs deleted file mode 100644 index f7aeb55..0000000 --- a/src/FSharp.Collections.Immutable/indexed-seq.fs +++ /dev/null @@ -1,10 +0,0 @@ -namespace FSharp.Collections.Immutable - -type IIndexedSeq<'T> = System.Collections.Generic.IReadOnlyList<'T> - -module IndexedSeq = - let check (seq: IIndexedSeq<_>) = checkNotNull (nameof seq) seq - let item index seq = check seq; seq.[index] - let length seq = check seq; seq.Count - -module ReadOnlyList = IndexedSeq diff --git a/src/FSharp.Collections.Immutable/maps.fs b/src/FSharp.Collections.Immutable/maps.fs deleted file mode 100644 index 01233f2..0000000 --- a/src/FSharp.Collections.Immutable/maps.fs +++ /dev/null @@ -1,225 +0,0 @@ -namespace FSharp.Collections.Immutable - -open System.Collections.Generic - -type IMap<'Key, 'Value> = System.Collections.Immutable.IImmutableDictionary<'Key, 'Value> - -type HashMap<'Key, 'Value> = - System.Collections.Immutable.ImmutableDictionary<'Key, 'Value> - -type HashMapBuilder<'Key, 'Value> = HashMap<'Key, 'Value>.Builder - -[] -module HashMap = - - type internal HashMapFactory = System.Collections.Immutable.ImmutableDictionary - - let inline check (map: HashMap<_, _>) = checkNotNull (nameof map) map - - ////////// Creating ////////// - - let inline empty<'Key, 'Value> = HashMapFactory.Create<'Key, 'Value>() - let inline singleton item = empty.Add(item) - - let inline ofSeq source = HashMapFactory.CreateRange(source) - let inline ofSeqWith getKey source = - source - |> Seq.map (fun i -> KeyValuePair(getKey i, i)) - |> HashMapFactory.CreateRange - let inline ofSeqGroupBy getKey source = - source - |> Seq.groupBy getKey - |> Seq.map (fun (key,value) -> KeyValuePair(key, value)) - |> HashMapFactory.CreateRange - let inline ofArray (source : _ array) = HashMapFactory.CreateRange(source) - - let inline toSeq (map: HashMap<_,_>) = map :> seq<_> - let inline toArray (map : HashMap<_,_>) = check map; Seq.toArray map - - ////////// Building ////////// - - let inline builder() = HashMapFactory.CreateBuilder() - let inline builderWithKeyComparer comparer = HashMapFactory.CreateBuilder(comparer) - let inline builderWithComparers keyComparer valueComparer = HashMapFactory.CreateBuilder(keyComparer, valueComparer) - - let inline ofBuilder (mapBuilder: HashMapBuilder<_,_>) = - checkNotNull (nameof mapBuilder) mapBuilder - mapBuilder.ToImmutable() - - let inline toBuilder map : HashMapBuilder<_,_> = check map; map.ToBuilder() - - let inline ofKeyComparer<'Key, 'Value> comparer = HashMapFactory.Create<'Key, 'Value>(comparer) - let inline ofComparers<'Key, 'Value> keyComparer valueComparer = HashMapFactory.Create<'Key, 'Value>(keyComparer, valueComparer) - - - let inline isEmpty map = check map; map.IsEmpty - - let inline length map = check map; map.Count - - let inline keyComparer map = check map; map.KeyComparer - let inline valueComparer map = check map; map.ValueComparer - - let inline containsKey key map = check map; map.ContainsKey key; - - let inline find key map = check map; map.[key] - let inline tryFind key map = - check map - match map.TryGetValue(key) with - | true, value -> Some value - | false, _ -> None - let inline vTryFind key map = - check map - match map.TryGetValue(key) with - | true, value -> ValueSome value - | false, _ -> ValueNone - - let inline pick chooser map = check map; map |> Seq.pick (fun kvp -> chooser kvp.Key kvp.Value) - let inline tryPick chooser map = check map; map |> Seq.tryPick (fun kvp -> chooser kvp.Key kvp.Value) - let inline vTryPick chooser map = - check map - match map |> Seq.tryPick (fun kvp -> chooser kvp.Key kvp.Value) with - | Some value -> ValueSome value - | None -> ValueNone - - let inline iter action (map: HashMap<_,_>) = check map; map |> Seq.iter (fun kvp -> action kvp.Key kvp.Value) - - let inline exists predicate map = check map; map |> Seq.exists (fun kvp -> predicate kvp.Key kvp.Value) - - let inline add key value map : HashMap<_,_> = check map; map.Add(key, value) - let inline append map pairs : HashMap<_,_> = - check map - checkNotNull (nameof pairs) pairs - map.AddRange pairs - - let inline remove key map : HashMap<_,_> = check map; map.Remove key - let inline except keys map : HashMap<_,_> = check map; map.RemoveRange keys - - let inline clear map: HashMap<_,_> = check map; map.Clear() - - let inline filter predicate map = - map |> Seq.filter (fun (kvp:KeyValuePair<_,_>) -> predicate kvp.Key kvp.Value) - - let inline forall predicate map = - map |> Seq.forall (fun (kvp:KeyValuePair<_,_>) -> predicate kvp.Key kvp.Value) - - let inline map mapping map' = - map' |> Seq.map (fun (kvp:KeyValuePair<_,_>) -> mapping kvp.Key kvp.Value) |> ofSeq - - let inline where predicate map = - map |> Seq.where (fun (kvp:KeyValuePair<_,_>) -> predicate kvp.Key kvp.Value) |> empty.AddRange - - -type SortedMap<'Key, 'Value> = - System.Collections.Immutable.ImmutableSortedDictionary<'Key, 'Value> - -type SortedMapBuilder<'Key, 'Value> = SortedMap<'Key, 'Value>.Builder - -[] -module SortedMap = - - type internal SortedMapFactory = System.Collections.Immutable.ImmutableSortedDictionary - - let inline check (sortedMap: SortedMap<_, _>) = checkNotNull (nameof sortedMap) sortedMap - - ////////// Creating ////////// - - let inline empty<'Key, 'Value> = SortedMapFactory.Create<'Key, 'Value>() - let inline singleton item = SortedMapFactory.Create(item) - - let inline ofSeq source = SortedMapFactory.CreateRange(source) - let inline ofSeqWith getKey source = - source - |> Seq.map (fun i -> KeyValuePair(getKey i, i)) - |> SortedMapFactory.CreateRange - let inline ofSeqGroupBy getKey source = - source - |> Seq.groupBy getKey - |> Seq.map (fun (key,value) -> KeyValuePair(key, value)) - let inline ofArray (source : _ array) = SortedMapFactory.CreateRange(source) - - let inline toSeq (map: SortedMap<_,_>) = map :> seq<_> - let inline toArray (map : SortedMap<_,_>) = check map; Seq.toArray map - - ////////// Building ////////// - - let inline builder() = SortedMapFactory.CreateBuilder() - let inline builderWithKeyComparer comparer = SortedMapFactory.CreateBuilder(comparer) - let inline builderWithComparers keyComparer valueComparer = SortedMapFactory.CreateBuilder(keyComparer, valueComparer) - - let inline ofBuilder (sortedMapBuilder: SortedMapBuilder<_,_>) = - checkNotNull (nameof sortedMapBuilder) sortedMapBuilder - sortedMapBuilder.ToImmutable() - - let inline toBuilder map : SortedMapBuilder<_,_> = check map; map.ToBuilder() - - let inline ofKeyComparer<'Key, 'Value> comparer = SortedMapFactory.Create<'Key, 'Value>(comparer) - let inline ofComparers<'Key, 'Value> keyComparer valueComparer = SortedMapFactory.Create<'Key, 'Value>(keyComparer, valueComparer) - - - let inline isEmpty map = check map; map.IsEmpty - - let inline length map = check map; map.Count - - let inline keyComparer map = check map; map.KeyComparer - let inline valueComparer map = check map; map.ValueComparer - - let inline containsKey key map = check map; map.ContainsKey key - - let inline find key map = check map; map.[key] - let inline tryFind key map = - check map - match map.TryGetValue(key) with - | true,value -> Some value - | false,_ -> None - let inline vTryFind key map = - check map - match map.TryGetValue(key) with - | true, value -> ValueSome value - | false, _ -> ValueNone - - let inline pick chooser map = check map; map |> Seq.pick (fun kvp -> chooser kvp.Key kvp.Value) - let inline tryPick chooser map = check map; map |> Seq.tryPick (fun kvp -> chooser kvp.Key kvp.Value) - let inline vTryPick chooser map = - check map - match map |> Seq.tryPick (fun kvp -> chooser kvp.Key kvp.Value) with - | Some value -> ValueSome value - | None -> ValueNone - - let inline iter action (map: SortedMap<_,_>) = check map; map |> Seq.iter (fun kvp -> action kvp.Key kvp.Value) - - let inline exists predicate map = check map; map |> Seq.exists (fun kvp -> predicate kvp.Key kvp.Value) - - let inline add key value map : SortedMap<_,_> = check map; map.Add(key, value) - let inline append map pairs : SortedMap<_,_> = - check map - checkNotNull (nameof pairs) pairs - map.AddRange pairs - - let inline remove key map : SortedMap<_,_> = check map; map.Remove key - let inline except keys map : SortedMap<_,_> = check map; map.RemoveRange keys - - let inline clear map: SortedMap<_,_> = check map; map.Clear() - - let inline findKey predicate map = - check map - match (map |> Seq.tryFind (fun kvp -> predicate kvp.Key kvp.Value)) with - | Some value -> value.Key - | None -> raise (new KeyNotFoundException()) - let inline tryFindKey predicate map = check map; map |> Seq.tryFind (fun kvp -> predicate kvp.Key kvp.Value) - let inline vTryFindKey predicate map = - check map - match (map |> Seq.tryFind (fun kvp -> predicate kvp.Key kvp.Value)) with - | Some value -> ValueSome value.Key - | None -> ValueNone - - let inline filter predicate map = - map |> Seq.filter (fun (kvp:KeyValuePair<_,_>) -> predicate kvp.Key kvp.Value) - - let inline forall predicate map = - map |> Seq.forall (fun (kvp:KeyValuePair<_,_>) -> predicate kvp.Key kvp.Value) - - let inline map mapping (map': SortedMap<_,_>) = - map' |> Seq.map (fun (kvp:KeyValuePair<_,_>) -> mapping kvp.Key kvp.Value) |> ofSeq - - let inline where predicate map = - map |> Seq.where (fun (kvp:KeyValuePair<_,_>) -> predicate kvp.Key kvp.Value) |> empty.AddRange diff --git a/src/FSharp.Collections.Immutable/queue.fs b/src/FSharp.Collections.Immutable/queue.fs deleted file mode 100644 index d255608..0000000 --- a/src/FSharp.Collections.Immutable/queue.fs +++ /dev/null @@ -1,80 +0,0 @@ -namespace FSharp.Collections.Immutable - -type IQueue<'T> = System.Collections.Immutable.IImmutableQueue<'T> - -type Queue<'T> = System.Collections.Immutable.ImmutableQueue<'T> - -[] -module Queue = - - type internal QueueFactory = System.Collections.Immutable.ImmutableQueue - - let inline private check (queue: IQueue<_>) = checkNotNull (nameof queue) queue - - let inline empty<'T> : Queue<'T> = QueueFactory.Create<'T>() - - let inline singleton<'T> (item : 'T) : Queue<'T> = QueueFactory.Create<'T> (item) - - let inline ofSeq(source : 'T seq) : Queue<'T> = QueueFactory.CreateRange source - - let inline toSeq (queue: Queue<_>) = queue :> seq<_> - - let isEmpty queue = check queue; queue.IsEmpty - - let clear queue : IQueue<_> = check queue; queue.Clear() - - let enqueue item queue : IQueue<_> = check queue; queue.Enqueue item - - let head queue = check queue; queue.Peek() - - let tail queue : IQueue<_> = check queue; queue.Dequeue() - - ////////// - - let (|Cons|Nil|) queue = // consider renaming - if isEmpty queue then Nil else Cons(head queue, tail queue) - - ////////// Predicate based ////////// - - let filter predicate queue = - let rec loop queue result = - match queue with - |Cons(head, tail) -> - loop tail <| if predicate head then enqueue head result else result - |Nil -> result - - loop queue <| clear queue - - ////////// Seq-based ////////// - - let find predicate queue = check queue; Seq.find predicate queue - let tryFind predicate queue = check queue; Seq.tryFind predicate queue - - let findIndex predicate queue = check queue; Seq.findIndex predicate queue - let tryFindIndex predicate queue = check queue; Seq.tryFindIndex predicate queue - - let pick chooser queue = check queue; Seq.pick chooser queue - let tryPick chooser queue = check queue; Seq.tryPick chooser queue - - let iter action queue = check queue; Seq.iter action queue - let iteri action queue = check queue; Seq.iteri action queue - let iter2 action (queue1: IQueue<_>) (queue2: IQueue<_>) = - checkNotNull (nameof queue1) queue1 - checkNotNull (nameof queue2) queue2 - Seq.iter2 action queue1 queue2 - - let fold folder state queue = check queue; Seq.fold folder state queue - - let forall predicate queue = check queue; Seq.forall predicate - - let exists predicate queue = check queue; Seq.exists predicate queue - - let reduce reduction queue = check queue; Seq.reduce reduction queue - - let inline sum queue = check queue; Seq.sum queue - let inline sumBy projection queue = check queue; Seq.sumBy projection queue - - let inline average queue = check queue; Seq.average queue - let inline averageBy projection queue = check queue; Seq.averageBy projection - -module ImmutableQueue = Queue diff --git a/src/FSharp.Collections.Immutable/sets.fs b/src/FSharp.Collections.Immutable/sets.fs deleted file mode 100644 index 3b091b1..0000000 --- a/src/FSharp.Collections.Immutable/sets.fs +++ /dev/null @@ -1,156 +0,0 @@ -namespace FSharp.Collections.Immutable - -type ISet<'T> = System.Collections.Immutable.IImmutableSet<'T> - -type HashSet<'T> = System.Collections.Immutable.ImmutableHashSet<'T> -type HashSetBuilder<'T> = HashSet<'T>.Builder - -[] -module HashSet = - - type internal HashSetFactory = System.Collections.Immutable.ImmutableHashSet - - let inline check (set: HashSet<_>) = checkNotNull (nameof set) set - - ////////// Creating ////////// - - let inline empty<'T> = HashSetFactory.Create<'T>() - let inline singleton<'T> (item : 'T) = HashSetFactory.Create<'T>(item) - let inline ofSeq source = HashSetFactory.CreateRange(source) - let inline ofSeqWithComparer comparer source = HashSetFactory.Create(comparer, items = (source |> Array.ofSeq)) - let inline ofArray (source : _ array) = HashSetFactory.CreateRange(source) - - let inline ofBuilder (hashSetBuilder: HashSetBuilder<_>) = - checkNotNull (nameof hashSetBuilder) hashSetBuilder - hashSetBuilder.ToImmutable() - let inline ofComparer<'T> comparer = HashSetFactory.Create<'T>(equalityComparer = comparer) - - let inline toSeq (set : HashSet<_>) = set :> seq<_> - let inline toArray (set : HashSet<_>) = check set; Seq.toArray set - - ////////// Building ////////// - - let inline builder() = HashSetFactory.CreateBuilder() - let inline builderWith capacity : HashSet<'T>.Builder = HashSetFactory.CreateBuilder(capacity) - let inline builderWithComparer comparer = HashSetFactory.CreateBuilder(comparer) - - let inline toBuilder set : HashSetBuilder<_> = check set; set.ToBuilder() - - let inline keyComparer set = check set; set.KeyComparer - - let inline length set = check set; set.Count - - let inline isEmpty set = check set; set.IsEmpty - let inline contains value set = check set; set.Contains value - let inline exists predicate map = check map; map |> Seq.exists predicate - let inline isSubset (set1:HashSet<_>) set2 = check set1; set1.IsSubsetOf set2 - let inline isProperSubset (set1:HashSet<_>) set2 = check set1; set1.IsProperSubsetOf set2 - let inline isSuperset (set1:HashSet<_>) set2 = check set1; set1.IsSupersetOf set2 - let inline isProperSuperset (set1:HashSet<_>) set2 = check set1; set1.IsProperSupersetOf set2 - - let inline add value set : HashSet<_> = check set; set.Add(value) - let inline union set values : HashSet<_> = check set; values |> set.Union - let inline unionMany (sets:HashSet<_> seq) = Seq.reduce union sets - let inline intersect (set1:HashSet<_>) set2 = check set1; set1.Intersect set2 - let inline intersectMany (sets:HashSet<_> seq) = Seq.reduce intersect sets - - let inline remove value set : HashSet<_> = check set; set.Remove value - let inline difference values set : HashSet<_> = check set; values |> set.Except - - let inline clear set: HashSet<_> = check set; set.Clear() - - let inline filter predicate set = - set |> Seq.filter predicate |> empty.Union - - let inline where predicate set = - set |> Seq.where predicate |> empty.Union - - let inline pick chooser set = check set; set |> Seq.pick chooser - let inline tryPick chooser set = check set; set |> Seq.tryPick chooser - let inline vTryPick chooser set = - check set - match set |> Seq.tryPick chooser with - | Some value -> ValueSome value - | None -> ValueNone - - let inline map mapping (set: HashSet<_>) = set |> Seq.map mapping |> ofSeq - - let inline forall predicate set = set |> Seq.forall predicate - - let inline iter action (set: HashSet<_>) = check set; set |> Seq.iter action - -type SortedSet<'T> = System.Collections.Immutable.ImmutableSortedSet<'T> -type SortedSetBuilder<'T> = SortedSet<'T>.Builder - -[] -module SortedSet = - - type internal SortedSetFactory = System.Collections.Immutable.ImmutableSortedSet - - let inline check (sortedSet: SortedSet<_>) = checkNotNull (nameof sortedSet) sortedSet - - ////////// Creating ////////// - - let inline empty<'T> = SortedSetFactory.Create<'T>() - let inline singleton<'T> (item : 'T) = SortedSetFactory.Create<'T>(item) - let inline ofSeq source = SortedSetFactory.CreateRange(source) - let inline ofSeqWithComparer comparer source = SortedSetFactory.Create(comparer, items = (source |> Array.ofSeq)) - let inline ofArray (source : _ array) = SortedSetFactory.CreateRange(source) - - let inline ofBuilder (sortedSetBuilder: SortedSetBuilder<_>) = - checkNotNull (nameof sortedSetBuilder) sortedSetBuilder - sortedSetBuilder.ToImmutable() - let inline ofComparer<'T> comparer = SortedSetFactory.Create<'T>(comparer = comparer) - - let inline toSeq (set: SortedSet<_>) = set :> seq<_> - let inline toArray (set : SortedSet<_>) = check set; Seq.toArray set - - ////////// Building ////////// - - let inline builder() = SortedSetFactory.CreateBuilder() - let inline builderWith capacity : SortedSet<'T>.Builder = SortedSetFactory.CreateBuilder(capacity) - let inline builderWithComparer comparer = SortedSetFactory.CreateBuilder(comparer) - - let inline toBuilder set : SortedSetBuilder<_> = check set; set.ToBuilder() - - let inline keyComparer set = check set; set.KeyComparer - - - let inline length set = check set; set.Count - - let inline contains value set = check set; set.Contains value - let inline isEmpty set = check set; set.IsEmpty - let inline exists predicate map = check map; map |> Seq.exists predicate - let inline isSubset (set1:SortedSet<_>) set2 = check set1; set1.IsSubsetOf set2 - let inline isProperSubset (set1:SortedSet<_>) set2 = check set1; set1.IsProperSubsetOf set2 - let inline isSuperset (set1:SortedSet<_>) set2 = check set1; set1.IsSupersetOf set2 - let inline isProperSuperset (set1:SortedSet<_>) set2 = check set1; set1.IsProperSupersetOf set2 - - let inline add value set : SortedSet<_> = check set; set.Add(value) - let inline union set values : SortedSet<_> = check set; values |> set.Union - let inline unionMany (sets:SortedSet<_> seq) = Seq.reduce union sets - let inline intersect (set1:SortedSet<_>) set2 = set1.Intersect set2 - let inline intersectMany (sets:SortedSet<_> seq) = Seq.reduce intersect sets - - let inline remove value set : SortedSet<_> = check set; set.Remove value - let inline difference values set : SortedSet<_> = check set; values |> set.Except - - let inline clear set: SortedSet<_> = check set; set.Clear() - - let inline filter predicate set = set |> Seq.filter predicate |> empty.Union - - let inline where predicate set = set |> Seq.where predicate |> empty.Union - - let inline pick chooser set = check set; set |> Seq.pick chooser - let inline tryPick chooser set = check set; set |> Seq.tryPick chooser - let inline vTryPick chooser set = - check set - match set |> Seq.tryPick chooser with - | Some value -> ValueSome value - | None -> ValueNone - - let inline map mapping (set: SortedSet<_>) = set |> Seq.map mapping |> ofSeq - - let inline forall predicate set = set |> Seq.forall predicate - - let inline iter action (set: SortedSet<_>) = check set; set |> Seq.iter action diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props new file mode 100644 index 0000000..e7a7266 --- /dev/null +++ b/tests/Directory.Build.props @@ -0,0 +1,7 @@ + + + + false + true + + diff --git a/tests/FSharp.Collections.Immutable.Tests/Attributes.fs b/tests/FSharp.Collections.Immutable.Tests/Attributes.fs new file mode 100644 index 0000000..923f313 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/Attributes.fs @@ -0,0 +1,7 @@ +namespace FSharp.Collections.Immutable + +open Microsoft.VisualStudio.TestTools.UnitTesting + +[] + +do () diff --git a/tests/FSharp.Collections.Immutable.Tests/FSharp.Collections.Immutable.Tests.fsproj b/tests/FSharp.Collections.Immutable.Tests/FSharp.Collections.Immutable.Tests.fsproj new file mode 100644 index 0000000..407a068 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/FSharp.Collections.Immutable.Tests.fsproj @@ -0,0 +1,32 @@ + + + + net8.0 + $(AssemblyBaseName).Tests + + Exe + true + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList.fs new file mode 100644 index 0000000..351bac9 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList.fs @@ -0,0 +1,10 @@ +namespace FSharp.Collections.Immutable.Tests + +open System +open Microsoft.VisualStudio.TestTools.UnitTesting + +[] +type FlatListTests () = + + [] + member this.TestMethodPassing () = Assert.IsTrue (true) diff --git a/tests/FSharp.Collections.Immutable.Tests/ImmutableList.fs b/tests/FSharp.Collections.Immutable.Tests/ImmutableList.fs new file mode 100644 index 0000000..452167f --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/ImmutableList.fs @@ -0,0 +1,10 @@ +namespace FSharp.Collections.Immutable.Tests + +open System +open Microsoft.VisualStudio.TestTools.UnitTesting + +[] +type ImmutableListTests () = + + [] + member this.TestMethodPassing () = Assert.IsTrue (true) diff --git a/tests/FSharp.Collections.Immutable.Tests/IndexedSeq.fs b/tests/FSharp.Collections.Immutable.Tests/IndexedSeq.fs new file mode 100644 index 0000000..17294a9 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/IndexedSeq.fs @@ -0,0 +1,10 @@ +namespace FSharp.Collections.Immutable.Tests + +open System +open Microsoft.VisualStudio.TestTools.UnitTesting + +[] +type IndexedSeqTests () = + + [] + member this.TestMethodPassing () = Assert.IsTrue (true) diff --git a/tests/FSharp.Collections.Immutable.Tests/Maps.fs b/tests/FSharp.Collections.Immutable.Tests/Maps.fs new file mode 100644 index 0000000..84f2fde --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/Maps.fs @@ -0,0 +1,16 @@ +namespace FSharp.Collections.Immutable.Tests + +open System +open Microsoft.VisualStudio.TestTools.UnitTesting + +[] +type HashMapTests () = + + [] + member this.TestMethodPassing () = Assert.IsTrue (true) + +[] +type SortedMapTests () = + + [] + member this.TestMethodPassing () = Assert.IsTrue (true) diff --git a/tests/FSharp.Collections.Immutable.Tests/Queue.fs b/tests/FSharp.Collections.Immutable.Tests/Queue.fs new file mode 100644 index 0000000..826a1cb --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/Queue.fs @@ -0,0 +1,10 @@ +namespace FSharp.Collections.Immutable.Tests + +open System +open Microsoft.VisualStudio.TestTools.UnitTesting + +[] +type QueueTests () = + + [] + member this.TestMethodPassing () = Assert.IsTrue (true) diff --git a/tests/FSharp.Collections.Immutable.Tests/Sets.fs b/tests/FSharp.Collections.Immutable.Tests/Sets.fs new file mode 100644 index 0000000..b170420 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/Sets.fs @@ -0,0 +1,16 @@ +namespace FSharp.Collections.Immutable.Tests + +open System +open Microsoft.VisualStudio.TestTools.UnitTesting + +[] +type HashSetTests () = + + [] + member this.TestMethodPassing () = Assert.IsTrue (true) + +[] +type SortedSetTests () = + + [] + member this.TestMethodPassing () = Assert.IsTrue (true) diff --git a/tests/FSharp.Collections.Immutable.Tests/Stack.fs b/tests/FSharp.Collections.Immutable.Tests/Stack.fs new file mode 100644 index 0000000..2674f68 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/Stack.fs @@ -0,0 +1,10 @@ +namespace FSharp.Collections.Immutable.Tests + +open System +open Microsoft.VisualStudio.TestTools.UnitTesting + +[] +type StackTests () = + + [] + member this.TestMethodPassing () = Assert.IsTrue (true) From e1178372738e73f16c9a4cd90760698c784406e0 Mon Sep 17 00:00:00 2001 From: Andrii Chebukin Date: Tue, 3 Jun 2025 02:38:51 +0400 Subject: [PATCH 3/8] fixup! feat: release automation with FAKE and minimal documentation --- .devcontainer/devcontainer.json | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 5b1e8df..c7955dd 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -19,8 +19,6 @@ }, // https://github.com/devcontainers/features/blob/main/src/github-cli/README.md "ghcr.io/devcontainers/features/github-cli:1": {}, - // https://github.com/devcontainers-contrib/features/blob/main/src/starship/README.md - "ghcr.io/devcontainers-contrib/features/starship:1": {}, // https://github.com/devcontainers/features/blob/main/src/dotnet/README.md "ghcr.io/devcontainers/features/dotnet:2": { "version": "9.0", @@ -30,7 +28,6 @@ "overrideFeatureInstallOrder": [ "ghcr.io/devcontainers/features/common-utils", "ghcr.io/devcontainers/features/github-cli", - "ghcr.io/devcontainers-contrib/features/starship", "ghcr.io/devcontainers/features/dotnet" ], "customizations": { @@ -63,9 +60,6 @@ // They are used for releasing and publishing from the container "GITHUB_TOKEN": "${localEnv:GITHUB_TOKEN}" }, - "onCreateCommand": { - "enable-starship": "echo 'eval \"$(starship init zsh)\"' >> ~/.zshrc" - }, "postAttachCommand": { "restore": "dotnet tool restore && dotnet restore" }, From 1d8ee35798ed98a78bc19753ae76ba926d093d0d Mon Sep 17 00:00:00 2001 From: Andrii Chebukin Date: Thu, 5 Jun 2025 16:47:09 +0400 Subject: [PATCH 4/8] Add support for `netstandard2.0;net8.0;net9.0` same as `System.Collections.Immutable` --- .../FSharp.Collections.Immutable.fsproj | 2 +- .../FSharp.Collections.Immutable.Tests.fsproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj b/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj index 9f5941f..bf684f1 100644 --- a/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj +++ b/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj @@ -1,7 +1,7 @@  - net8.0 + netstandard2.0;net8.0;net9.0 $(AssemblyBaseName) true true diff --git a/tests/FSharp.Collections.Immutable.Tests/FSharp.Collections.Immutable.Tests.fsproj b/tests/FSharp.Collections.Immutable.Tests/FSharp.Collections.Immutable.Tests.fsproj index 407a068..89d17e3 100644 --- a/tests/FSharp.Collections.Immutable.Tests/FSharp.Collections.Immutable.Tests.fsproj +++ b/tests/FSharp.Collections.Immutable.Tests/FSharp.Collections.Immutable.Tests.fsproj @@ -1,7 +1,7 @@  - net8.0 + net9.0 $(AssemblyBaseName).Tests Exe From 661c58d289e2a7f36ec36530ddf8bef1f52ae547 Mon Sep 17 00:00:00 2001 From: Andrii Chebukin Date: Thu, 5 Jun 2025 16:47:40 +0400 Subject: [PATCH 5/8] feat: implement missing FlatList functions --- src/FSharp.Collections.Immutable/FlatList.fs | 1181 ++++++++++++++---- 1 file changed, 972 insertions(+), 209 deletions(-) diff --git a/src/FSharp.Collections.Immutable/FlatList.fs b/src/FSharp.Collections.Immutable/FlatList.fs index 82bcce7..447fd40 100644 --- a/src/FSharp.Collections.Immutable/FlatList.fs +++ b/src/FSharp.Collections.Immutable/FlatList.fs @@ -21,33 +21,108 @@ module FlatList = let inline internal check (list : FlatList<'T>) = checkNotDefault (nameof list) list + let inline internal indexNotFound () = raise <| System.Collections.Generic.KeyNotFoundException () + + let inline private lengthWhile predicate list = + check list + let mutable count = 0 + + while count < list.Length && predicate list.[count] do + count <- count + 1 + + count + ////////// Creating ////////// - let inline empty<'T> : FlatList<_> = FlatListFactory.Create<'T> () - let inline singleton<'T> (item : 'T) : FlatList<'T> = FlatListFactory.Create<'T> (item) + /// Creates a new builder with the specified capacity + /// The initial capacity of the builder + /// An empty builder with the specified capacity + let inline builderWith capacity : FlatList<'T>.Builder = FlatListFactory.CreateBuilder (capacity) - let inline ofSeq source = FlatListFactory.CreateRange source + /// Builds a from a builder, moving the elements and leaving the builder empty + /// The builder to build from + /// A containing the elements from the builder + /// Thrown when builder is null + let moveFromBuilder (builder : FlatList<_>.Builder) : FlatList<_> = + checkNotNull (nameof builder) builder + builder.MoveToImmutable () + + /// Returns an empty + /// An empty + let inline empty<'T> : FlatList<'T> = FlatListFactory.Create<'T> () + + /// Builds a from the given array + /// The array to build the from + /// A containing the elements of the array let inline ofArray (source : _ array) = FlatListFactory.CreateRange source + /// Builds a from the given sequence + /// The sequence to build the from + /// A containing the elements of the sequence + let inline ofSeq source = FlatListFactory.CreateRange source + + /// Returns a with a single element + /// The item to put into the + /// A containing only the given item + let inline singleton<'T> (item : 'T) : FlatList<'T> = FlatListFactory.Create<'T> (item) + + /// Creates a by initializing each element with the given function + /// The number of elements to create + /// The function to initialize each element + /// A new with the initialized elements + /// Thrown when count is negative + let init count initializer = + if count < 0 then + invalidArg (nameof count) ErrorStrings.InputMustBeNonNegative + + let builder = builderWith count + + for i = 0 to count - 1 do + builder.Add <| initializer i + + moveFromBuilder builder + + /// Creates a of a given length with all elements set to the given value + /// The length of the to create + /// The value to replicate + /// A of the specified length with all elements equal to the given value + let create count item = init count <| fun _ -> item // optimize + + /// Replicates a value into a of a given length + /// The length of the to create + /// The value to replicate + /// A of the specified length with all elements equal to the given value + let replicate count item = create count item + + /// Views the as a sequence + /// The input + /// The sequence containing the elements of the let inline toSeq (flatList : FlatList<_>) = flatList :> seq<_> + /// Builds an array from the given + /// The to build the array from + /// An array containing the elements of the let inline toArray (list : FlatList<_>) = check list Seq.toArray list ////////// Building ////////// - let moveFromBuilder (builder : FlatList<_>.Builder) : FlatList<_> = - checkNotNull (nameof builder) builder - builder.MoveToImmutable () - + /// Builds a from a builder, copying the elements + /// The builder to build from + /// A containing the elements from the builder + /// Thrown when builder is null let ofBuilder (builder : FlatList<_>.Builder) : FlatList<_> = checkNotNull (nameof builder) builder builder.ToImmutable () + /// Creates a new builder + /// An empty builder let inline builder () : FlatList<'T>.Builder = FlatListFactory.CreateBuilder () - let inline builderWith capacity : FlatList<'T>.Builder = FlatListFactory.CreateBuilder (capacity) + /// Creates a builder containing the elements of the input + /// The to create the builder from + /// A builder containing the elements of the let toBuilder list : FlatList<_>.Builder = check list list.ToBuilder () @@ -55,78 +130,218 @@ module FlatList = module Builder = let inline private check (builder : FlatList<'T>.Builder) = checkNotNull (nameof builder) builder + /// Adds an item to the builder + /// The item to add + /// The builder to add to let add item builder = check builder builder.Add (item) - let inline internal indexNotFound () = raise <| System.Collections.Generic.KeyNotFoundException () - + /// Checks if the is empty + /// The to check + /// True if the is empty, false otherwise let isEmpty (list : FlatList<_>) = list.IsEmpty + + /// Checks if the is uninstantiated + /// The to check + /// True if the is uninstantiated, false otherwise let isDefault (list : FlatList<_>) = list.IsDefault + + /// Checks if the is uninstantiated or empty + /// The to check + /// True if the is uninstantiated or empty, false otherwise let isDefaultOrEmpty (list : FlatList<_>) = list.IsDefaultOrEmpty ////////// IReadOnly* ////////// + /// Returns the number of elements in the + /// The input + /// The number of elements in the let length list = check list list.Length + /// Gets the element at the specified index in the + /// The index to retrieve + /// The input + /// The element at the specified index + /// Thrown when the index is out of range let item index list = check list list.[index] + /// Appends two s to create a new containing all elements from both s + /// The first + /// The second + /// A new containing all elements from both input s let append list1 list2 : FlatList<'T> = checkNotDefault (nameof list1) list1 checkNotDefault (nameof list2) list2 list1.AddRange (list2 : FlatList<_>) - /// Searches for the specified object and returns the zero-based index of the first occurrence within the range - /// of elements in the list that starts at the specified index and - /// contains the specified number of elements. + /// Searches for the specified object and returns the zero-based index of the first occurrence within the range + /// of elements in the that starts at the specified index and + /// contains the specified number of elements. + /// The equality comparer to use + /// The starting index + /// The number of elements to search + /// The item to search for + /// The input + /// The zero-based index of the first occurrence of the item let indexRangeWith comparer index count item list = check list list.IndexOf (item, index, count, comparer) + /// Searches for the specified object and returns the zero-based index of the first occurrence within the range + /// of elements in the that starts at the specified index and + /// contains the specified number of elements. + /// The starting index + /// The number of elements to search + /// The item to search for + /// The input + /// The zero-based index of the first occurrence of the item let indexRange index count item list = indexRangeWith HashIdentity.Structural index count item list - let indexFromWith comparer index item list = indexRangeWith comparer index (length list - index) item + + /// Searches for the specified object and returns the zero-based index of the first occurrence within the range + /// of elements in the that starts at the specified index and + /// contains the specified number of elements. + /// The equality comparer to use + /// The starting index + /// The item to search for + /// The input + /// The zero-based index of the first occurrence of the item + let indexFromWith comparer index item list = indexRangeWith comparer index (length list - index) item list + + /// Searches for the specified object and returns the zero-based index of the first occurrence within the range + /// of elements in the that starts at the specified index and + /// contains the specified number of elements. + /// The starting index + /// The item to search for + /// The input + /// The zero-based index of the first occurrence of the item let indexFrom index item list = indexFromWith HashIdentity.Structural index item list + + /// Searches for the specified object and returns the zero-based index of the first occurrence within the range + /// of elements in the that starts at the specified index and + /// contains the specified number of elements. + /// The equality comparer to use + /// The item to search for + /// The input + /// The zero-based index of the first occurrence of the item let indexWith comparer item list = indexFromWith comparer 0 item list + + /// Searches for the specified object and returns the zero-based index of the first occurrence within the range + /// of elements in the that starts at the specified index and + /// contains the specified number of elements. + /// The item to search for + /// The input + /// The zero-based index of the first occurrence of the item let index item list = indexWith HashIdentity.Structural item list - /// Searches for the specified object and returns the zero-based index of the last occurrence within the - /// range of elements in the list that contains the specified number - /// of elements and ends at the specified index. + /// Searches for the specified object and returns the zero-based index of the last occurrence within the + /// range of elements in the that contains the specified number + /// of elements and ends at the specified index. + /// The equality comparer to use + /// The ending index + /// The number of elements to search + /// The item to search for + /// The input + /// The zero-based index of the last occurrence of the item let lastIndexRangeWith comparer index count item list = check list list.LastIndexOf (item, index, count, comparer) + /// Searches for the specified object and returns the zero-based index of the last occurrence within the + /// range of elements in the that contains the specified number + /// of elements and ends at the specified index. + /// The ending index + /// The number of elements to search + /// The item to search for + /// The input + /// The zero-based index of the last occurrence of the item let lastIndexRange index count item list = lastIndexRangeWith HashIdentity.Structural index count item list + + /// Searches for the specified object and returns the zero-based index of the last occurrence within the + /// range of elements in the that contains the specified number + /// of elements and ends at the specified index. + /// The equality comparer to use + /// The ending index + /// The item to search for + /// The input + /// The zero-based index of the last occurrence of the item let lastIndexFromWith comparer index item list = lastIndexRangeWith comparer index (index + 1) item list + + /// Searches for the specified object and returns the zero-based index of the last occurrence within the + /// range of elements in the that contains the specified number + /// of elements and ends at the specified index. + /// The ending index + /// The item to search for + /// The input + /// The zero-based index of the last occurrence of the item let lastIndexFrom index item list = lastIndexFromWith HashIdentity.Structural index item list + + /// Searches for the specified object and returns the zero-based index of the last occurrence within the + /// range of elements in the that contains the specified number + /// of elements and ends at the specified index. + /// The equality comparer to use + /// The item to search for + /// The input + /// The zero-based index of the last occurrence of the item let lastIndexWith comparer item list = lastIndexFromWith comparer (length list - 1) item list + + /// Searches for the specified object and returns the zero-based index of the last occurrence within the + /// range of elements in the that contains the specified number + /// of elements and ends at the specified index. + /// The item to search for + /// The input + /// The zero-based index of the last occurrence of the item let lastIndex item list = lastIndexWith HashIdentity.Structural item list - /// Removes the specified objects from the list with the given comparer. + /// Removes the specified objects from the with the given comparer. + /// The equality comparer to use + /// The items to remove + /// The input + /// A new with the specified items removed let removeAllWith (comparer : System.Collections.Generic.IEqualityComparer<'T>) (items : 'T seq) list : FlatList<_> = check list list.RemoveRange (items, comparer) - /// Removes the specified objects from the list. + /// Removes the specified objects from the . + /// The items to remove + /// The input + /// A new with the specified items removed let removeAll items list = removeAllWith HashIdentity.Structural items list - /// Removes all the elements that do not match the conditions defined by the specified predicate. + /// Removes all the elements that do not match the conditions defined by the specified predicate. + /// The predicate to test elements + /// The input + /// A new with elements that match the predicate let filter predicate list : FlatList<_> = check list System.Predicate (not << predicate) |> list.RemoveAll - /// Removes all the elements that do not match the conditions defined by the specified predicate. + /// Removes all the elements that do not match the conditions defined by the specified predicate. + /// The predicate to test elements + /// The input + /// A new with elements that match the predicate let where predicate list = filter predicate list - /// Removes a range of elements from the list. + /// Removes a range of elements from the . + /// The starting index + /// The number of elements to remove + /// The input + /// A new with the specified range of elements removed let removeRange index (count : int) list : FlatList<_> = check list list.RemoveRange (index, count) + /// Copies a range of elements from the source to the destination array + /// The source + /// The starting index in the source + /// The destination array + /// The starting index in the destination array + /// The number of elements to copy + /// Thrown when the range is invalid let blit source sourceIndex (destination : 'T[]) destinationIndex count = checkNotDefault (nameof source) source @@ -135,21 +350,49 @@ module FlatList = with exn -> raise exn // throw same exception with the correct stack trace. Update exception code + /// Sorts a range of elements in the using the specified comparer + /// The comparer to use + /// The starting index + /// The number of elements to sort + /// The input + /// A new with the specified range of elements sorted let sortRangeWithComparer comparer index count list = check list list.Sort (index, count, comparer) + /// Sorts a range of elements in the using the specified comparison function + /// The comparison function to use + /// The starting index + /// The number of elements to sort + /// The input + /// A new with the specified range of elements sorted let sortRangeWith comparer index count list = sortRangeWithComparer (ComparisonIdentity.FromFunction comparer) index count list + /// Sorts a range of elements in the using the default comparer + /// The starting index + /// The number of elements to sort + /// The input + /// A new with the specified range of elements sorted let sortRange index count list = sortRangeWithComparer ComparisonIdentity.Structural index count list + /// Sorts the elements in the using the specified comparer + /// The comparer to use + /// The input + /// A new with the elements sorted let sortWithComparer (comparer : System.Collections.Generic.IComparer<_>) list = check list list.Sort (comparer) + /// Sorts the elements in the using the specified comparison function + /// The comparison function to use + /// The input + /// A new with the elements sorted let sortWith comparer list = sortWithComparer (ComparisonIdentity.FromFunction comparer) list + /// Sorts the elements in the using the default comparer + /// The input + /// A new with the elements sorted let sort list = check list list.Sort () @@ -158,24 +401,16 @@ module FlatList = let inline private builderWithLengthOf list = builderWith <| length list - let init count initializer = - if count < 0 then - invalidArg (nameof count) ErrorStrings.InputMustBeNonNegative - - let builder = builderWith count - - for i = 0 to count - 1 do - builder.Add <| initializer i - - moveFromBuilder builder - let rec private concatAddLengths (arrs : FlatList>) i acc = if i >= length arrs then acc else concatAddLengths arrs (i + 1) (acc + arrs.[i].Length) - let concat (arrs : FlatList>) = // consider generalizing + /// Concatenates a of s into a single + /// The of s to concatenate + /// A new containing all elements from the input s + let concat (arrs : FlatList>) = let result : FlatList<'T>.Builder = builderWith <| concatAddLengths arrs 0 0 for i = 0 to length arrs - 1 do @@ -183,6 +418,10 @@ module FlatList = moveFromBuilder result + /// Builds a new from the elements of a by applying a mapping function to each element + /// A function to transform elements from the input + /// The input + /// A containing the transformed elements let inline map mapping list = check list let builder = builderWithLengthOf list @@ -192,12 +431,14 @@ module FlatList = moveFromBuilder builder + /// Counts the number of elements in the that satisfy the given predicate + /// A function to project elements from the input + /// The input + /// A of key-value pairs where the key is the projected value and the value is the count let countBy projection list = check list - // need struct box optimization let dict = new System.Collections.Generic.Dictionary<'Key, int> (HashIdentity.Structural) - // Build the groupings for v in list do let key = projection v let mutable prev = Unchecked.defaultof<_> @@ -216,6 +457,9 @@ module FlatList = moveFromBuilder res + /// Creates a containing the elements of the original paired with their indices + /// The input + /// A containing pairs of indices and elements let indexed list = check list let builder = builderWithLengthOf list @@ -225,12 +469,31 @@ module FlatList = moveFromBuilder builder + /// Applies the given function to each element of the + /// A function to apply to each element + /// The input let inline iter action list = check list for i = 0 to length list - 1 do action list.[i] + /// Applies the given function to each element of the and its index + /// A function to apply to each element and its index + /// The input + let iteri action list = + check list + let f = OptimizedClosures.FSharpFunc<_, _, _>.Adapt (action) + let len = list.Length + + for i = 0 to len - 1 do + f.Invoke (i, list.[i]) + + /// Applies the given function to pair of elements at the same position in the two s + /// A function to apply to pairs of elements + /// The first input + /// The second input + /// Thrown when the s have different lengths let iter2 action list1 list2 = checkNotDefault (nameof list1) list1 checkNotDefault (nameof list2) list2 @@ -243,80 +506,11 @@ module FlatList = for i = 0 to len - 1 do f.Invoke (list1.[i], list2.[i]) - let distinctBy projection (list : FlatList<'T>) = - let builder : FlatList<'T>.Builder = builderWith <| length list - let set = System.Collections.Generic.HashSet<'Key> (HashIdentity.Structural) - let mutable outputIndex = 0 - - for i = 0 to length list - 1 do - let item = list.[i] - - if set.Add <| projection item then - outputIndex <- outputIndex + 1 - Builder.add item builder - - ofBuilder builder - - let map2 mapping list1 list2 = - checkNotDefault (nameof list1) list1 - checkNotDefault (nameof list2) list2 - let f = OptimizedClosures.FSharpFunc<_, _, _>.Adapt (mapping) - let len1 = list1.Length - - if len1 <> list2.Length then - invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths - - let res = builderWith len1 - - for i = 0 to len1 - 1 do - res.Add <| f.Invoke (list1.[i], list2.[i]) - - moveFromBuilder res - - let map3 mapping list1 list2 list3 = - checkNotDefault (nameof list1) list1 - checkNotDefault (nameof list2) list2 - checkNotDefault (nameof list3) list3 - let f = OptimizedClosures.FSharpFunc<_, _, _, _>.Adapt (mapping) - let len1 = list1.Length - - if not (len1 = list2.Length) then - invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths - - if not (len1 = list3.Length) then - invalidArg (nameof list3) ErrorStrings.ListsHaveDifferentLengths - - let res = builderWith len1 - - for i = 0 to len1 - 1 do - res.Add <| f.Invoke (list1.[i], list2.[i], list3.[i]) - - moveFromBuilder res - - let mapi2 mapping list1 list2 = - checkNotDefault (nameof list1) list1 - checkNotDefault (nameof list2) list2 - let f = OptimizedClosures.FSharpFunc<_, _, _, _>.Adapt (mapping) - let len1 = list1.Length - - if len1 <> list2.Length then - invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths - - let res = builderWith len1 - - for i = 0 to len1 - 1 do - res.Add <| f.Invoke (i, list1.[i], list2.[i]) - - moveFromBuilder res - - let iteri action list = - check list - let f = OptimizedClosures.FSharpFunc<_, _, _>.Adapt (action) - let len = list.Length - - for i = 0 to len - 1 do - f.Invoke (i, list.[i]) - + /// Applies the given function to the pair of elements at the same position in the two s along with their index + /// A function to apply to pairs of elements and their index + /// The first input + /// The second input + /// Thrown when the s have different lengths let iteri2 action list1 list2 = checkNotDefault (nameof list1) list1 checkNotDefault (nameof list2) list2 @@ -329,34 +523,22 @@ module FlatList = for i = 0 to len1 - 1 do f.Invoke (i, list1.[i], list2.[i]) - let mapi mapping list = - check list - let f = OptimizedClosures.FSharpFunc<_, _, _>.Adapt (mapping) - let len = list.Length - let res = builderWithLengthOf list - - for i = 0 to len - 1 do - res.Add <| f.Invoke (i, list.[i]) - - moveFromBuilder res - + /// Tests if any element of the satisfies the given predicate + /// A function to test each element + /// The input + /// True if any element satisfies the predicate, false otherwise let exists predicate list = check list let len = list.Length let rec loop i = i < len && (predicate list.[i] || loop (i + 1)) loop 0 - let inline contains e list = - check list - let mutable state = false - let mutable i = 0 - - while (not state && i < list.Length) do - state <- e = list.[i] - i <- i + 1 - - state - + /// Tests if any corresponding pair of elements from the two s satisfies the given predicate + /// A function to test pairs of elements + /// The first input + /// The second input + /// True if any pair of elements satisfies the predicate, false otherwise + /// Thrown when the s have different lengths let exists2 predicate list1 list2 = checkNotDefault (nameof list1) list1 checkNotDefault (nameof list2) list2 @@ -372,12 +554,22 @@ module FlatList = loop 0 + /// Tests if all elements of the satisfy the given predicate + /// A function to test each element + /// The input + /// True if all elements satisfy the predicate, false otherwise let forall predicate list = check list let len = list.Length let rec loop i = i >= len || (predicate list.[i] && loop (i + 1)) loop 0 + /// Tests if all corresponding pairs of elements from the two s satisfy the given predicate + /// A function to test pairs of elements + /// The first input + /// The second input + /// True if all pairs of elements satisfy the predicate, false otherwise + /// Thrown when the s have different lengths let forall2 predicate list1 list2 = checkNotDefault (nameof list1) list1 checkNotDefault (nameof list2) list2 @@ -393,70 +585,25 @@ module FlatList = loop 0 - let groupBy projection list = + /// Tests if the given element exists in the + /// The element to find + /// The input + /// True if the element exists in the , false otherwise + let inline contains e list = check list - let dict = new System.Collections.Generic.Dictionary<'Key, ResizeArray<'T>> (HashIdentity.Structural) - - // Build the groupings - for i = 0 to (list.Length - 1) do - let v = list.[i] - let key = projection v - let ok, prev = dict.TryGetValue (key) - - if ok then - prev.Add (v) - else - let prev = new ResizeArray<'T> (1) - dict.[key] <- prev - prev.Add (v) - - // Return the list-of-lists. - let result = builderWith dict.Count + let mutable state = false let mutable i = 0 - for group in dict do - result.Add (group.Key, ofSeq group.Value) + while (not state && i < list.Length) do + state <- e = list.[i] i <- i + 1 - moveFromBuilder result - - let pick chooser list = - check list - - let rec loop i = - if i >= list.Length then - indexNotFound () - else - match chooser list.[i] with - | None -> loop (i + 1) - | Some res -> res - - loop 0 - - let tryPick chooser list = - check list - - let rec loop i = - if i >= list.Length then - None - else - match chooser list.[i] with - | None -> loop (i + 1) - | res -> res - - loop 0 - - let choose chooser list = - check list - let res = builderWith list.Length - - for i = 0 to list.Length - 1 do - match chooser list.[i] with - | None -> () - | Some b -> res.Add (b) - - ofBuilder res + state + /// Splits the into two s, containing the elements for which the given predicate returns true and false respectively + /// A function to test each element + /// The input + /// A tuple of two s, containing the elements for which the predicate returns true and false respectively let partition predicate list = check list let res1 = builderWith list.Length @@ -468,6 +615,11 @@ module FlatList = ofBuilder res1, ofBuilder res2 + /// Returns the first element for which the given predicate returns true + /// A function to test elements + /// The input + /// The first element for which the predicate returns true + /// Thrown if no element satisfies the predicate let find predicate list = check list @@ -478,6 +630,10 @@ module FlatList = loop 0 + /// Returns the first element for which the given predicate returns true, or None if no such element exists + /// A function to test elements + /// The input + /// Some value if an element satisfies the predicate, None otherwise let tryFind predicate list = check list @@ -488,6 +644,11 @@ module FlatList = loop 0 + /// Returns the last element for which the given predicate returns true + /// A function to test elements + /// The input + /// The last element for which the predicate returns true + /// Thrown if no element satisfies the predicate let findBack predicate list = check list @@ -498,16 +659,25 @@ module FlatList = loop <| length list - 1 + /// Returns the last element for which the given predicate returns true, or None if no such element exists + /// A function to test elements + /// The input + /// Some value if an element satisfies the predicate, None otherwise let tryFindBack predicate list = check list let rec loop i = if i < 0 then None else if predicate list.[i] then Some list.[i] - else loop (i + 1) + else loop (i - 1) loop <| length list - 1 + /// Returns the last index for which the given predicate returns true + /// A function to test elements + /// The input + /// The last index for which the predicate returns true + /// Thrown if no element satisfies the predicate let findIndexBack predicate list = check list @@ -518,6 +688,10 @@ module FlatList = loop <| length list - 1 + /// Returns the last index for which the given predicate returns true, or None if no such element exists + /// A function to test elements + /// The input + /// Some index if an element satisfies the predicate, None otherwise let tryFindIndexBack predicate list = check list @@ -527,67 +701,656 @@ module FlatList = else loop (i - 1) loop <| length list - 1 - // TODO: windowed - ////////// Based on other operations ////////// + /// Returns the first value for which the given function returns Some value + /// A function to generate options from the elements + /// The input + /// The first value for which the chooser returns Some value + /// Thrown if the chooser returns None for all elements + let pick chooser list = + check list - let take count list = removeRange count (length list - count) list + let rec loop i = + if i >= list.Length then + indexNotFound () + else + match chooser list.[i] with + | None -> loop (i + 1) + | Some res -> res - let inline private lengthWhile predicate list = + loop 0 + + /// Returns the first value for which the given function returns Some value, or None if no such element exists + /// A function to generate options from the elements + /// The input + /// The first value for which the chooser returns Some value, or None + let tryPick chooser list = check list - let mutable count = 0 - while count < list.Length && predicate list.[count] do - count <- count + 1 + let rec loop i = + if i >= list.Length then + None + else + match chooser list.[i] with + | None -> loop (i + 1) + | res -> res - count + loop 0 - let takeWhile predicate list = take (lengthWhile predicate list) list + /// Builds a new containing only the elements for which the given function returns Some value + /// A function to generate options from the elements + /// The input + /// A containing the values wrapped in Some by the chooser + let choose chooser list = + check list + let res = builderWith list.Length - let skip index list = removeRange 0 index list + for i = 0 to list.Length - 1 do + match chooser list.[i] with + | None -> () + | Some b -> res.Add (b) - let skipWhile predicate list = skip (lengthWhile predicate list) list + ofBuilder res - let sub start stop list = skip start list |> take (stop - start - 1) + /// Creates a by applying a key-generating function to each element of the and grouping the elements by the resulting keys + /// A function to transform elements into keys + /// The input + /// A of tuples where each tuple contains a key and a of all elements that match the key + let groupBy projection list = + check list + let dict = new System.Collections.Generic.Dictionary<'Key, ResizeArray<'T>> (HashIdentity.Structural) - let truncate count list = if count < length list then take count list else list + // Build the groupings + for i = 0 to (list.Length - 1) do + let v = list.[i] + let key = projection v + let ok, prev = dict.TryGetValue (key) - let splitAt index list = take index list, skip index list + if ok then + prev.Add (v) + else + let prev = new ResizeArray<'T> (1) + dict.[key] <- prev + prev.Add (v) - let head list = item 0 list + // Return the -of-s. + let result = builderWith dict.Count + let mutable i = 0 + + for group in dict do + result.Add (group.Key, ofSeq group.Value) + i <- i + 1 + + moveFromBuilder result + + /// Returns a new that contains the elements of the original but with duplicates eliminated by using the supplied projection function + /// A function to transform elements before comparing them + /// The input + /// A with distinct elements as determined by the projection function + let distinctBy projection (list : FlatList<'T>) = + let builder : FlatList<'T>.Builder = builderWith <| length list + let set = System.Collections.Generic.HashSet<'Key> (HashIdentity.Structural) + let mutable outputIndex = 0 + + for i = 0 to length list - 1 do + let item = list.[i] + + if set.Add <| projection item then + outputIndex <- outputIndex + 1 + Builder.add item builder + + ofBuilder builder + + /// Creates a new by applying a mapping function to each element of the input and concatenating the results + /// A function to transform elements of the input into s + /// The input + /// A containing the concatenation of all the s generated by the mapping function + let collect mapping list = concat <| map mapping list + /// Gets an element in the at the specified index + /// The index of the element to retrieve + /// The input + /// Some value containing the element, or None if the index is out of range let tryItem index list = if index >= length list || index < 0 then None else Some (list.[index]) + /// Returns the first element of the + /// The input + /// The first element of the + /// Thrown when the is empty + let head list = item 0 list + + /// Returns the first element of the , or None if the is empty + /// The input + /// Some value containing the first element, or None if the is empty let tryHead list = tryItem 0 list + /// Returns the last element of the + /// The input + /// The last element of the + /// Thrown when the is empty let last (list : FlatList<_>) = list.[length list - 1] + /// Returns the last element of the , or None if the is empty + /// The input + /// Some value containing the last element, or None if the is empty let tryLast list = tryItem (length list - 1) list + /// Returns the without its first element + /// The input + /// A containing all elements of the input except the first one let tail list = removeRange 1 (length list - 1) list + /// Returns the without its first element, or None if the is empty + /// The input + /// Some value containing the without its first element, or None if the is empty let tryTail list = if isEmpty list then None else Some <| tail list - let create count item = init count <| fun _ -> item // optimize + /// Returns the first N elements of the + /// The number of elements to take + /// The input + /// A containing the first N elements + let take count list = removeRange count (length list - count) list - let replicate count item = create item count + /// Returns a containing the first elements of the input for which the given predicate returns true + /// A function to test each element + /// The input + /// A containing the first elements for which the predicate returns true + let takeWhile predicate list = take (lengthWhile predicate list) list - let collect mapping list = concat <| map mapping list + /// Returns the without its first N elements + /// The number of elements to skip + /// The input + /// A containing all except the first N elements + let skip index list = removeRange 0 index list + + /// Returns a that skips the elements of the input while the given predicate returns true, then returns the rest + /// A function to test each element + /// The input + /// A that skips the elements while the predicate returns true, then contains the rest + let skipWhile predicate list = skip (lengthWhile predicate list) list + + /// Gets a sublist of the input + /// The index of the first element to include + /// The index of the element at which to end (exclusive) + /// The input + /// A containing the elements from start to stop-1 + let sub start stop list = skip start list |> take (stop - start - 1) + + /// Returns a that contains no more than N elements of the input + /// The maximum number of elements to include + /// The input + /// A containing at most N elements + let truncate count list = if count < length list then take count list else list + + /// Splits the into two s at the specified index + /// The index at which to split the + /// The input + /// A tuple of two s, the first containing the elements up to the index, the second containing the rest + let splitAt index list = take index list, skip index list + /// Applies a function to the builder and returns the resulting + /// The function to apply to the builder + /// The created from the builder after applying the function let inline build f = let builder = builder () f builder moveFromBuilder builder + /// Updates the by applying a function to a builder initialized with the 's elements + /// The function to apply to the builder + /// The input + /// The updated let inline update f list = let builder = toBuilder list f builder moveFromBuilder builder + /// Returns the index of the first element in the that satisfies the given predicate + /// The function to test the input elements + /// The input + /// The index of the first element that satisfies the predicate + /// Thrown if no element satisfies the predicate + let findIndex predicate list = + check list + let len = list.Length + let rec loop i = + if i >= len then indexNotFound () + elif predicate list.[i] then i + else loop (i + 1) + loop 0 + + /// Returns the index of the first element in the that satisfies the given predicate, or None if no such element exists + /// The function to test the input elements + /// The input + /// The index of the first element that satisfies the predicate, or None + let tryFindIndex predicate list = + check list + let len = list.Length + let rec loop i = + if i >= len then None + elif predicate list.[i] then Some i + else loop (i + 1) + loop 0 + + /// Returns a new containing elements corresponding to a sliding window of elements from the input + /// The size of the window + /// The input + /// The resulting of sliding windows + /// Thrown when windowSize is not positive or when is default + let windowed windowSize list = + check list + if windowSize <= 0 then + invalidArg (nameof windowSize) ErrorStrings.InputMustBeNonNegative + + let len = list.Length + if windowSize > len then + empty + else + let res = builderWith (len - windowSize + 1) + for i = 0 to len - windowSize do + let window = builderWith windowSize + for j = 0 to windowSize - 1 do + window.Add list.[i + j] + res.Add (ofBuilder window) + moveFromBuilder res + + /// Returns a new containing pairs of adjacent elements from the input + /// The input + /// The resulting of pairs + let pairwise list = + check list + let len = list.Length + if len < 2 then + empty + else + let res = builderWith (len - 1) + for i = 0 to len - 2 do + res.Add (list.[i], list.[i + 1]) + moveFromBuilder res + + /// Splits the into chunks of size at most 'chunkSize' + /// The maximum size of each chunk + /// The input + /// The split into chunks + /// Thrown when chunkSize is not positive or when is default + let chunkBySize chunkSize list = + check list + if chunkSize <= 0 then + invalidArg (nameof chunkSize) ErrorStrings.InputMustBeNonNegative + + let len = list.Length + if len = 0 then + empty + else + let chunkCount = (len + chunkSize - 1) / chunkSize + let res = builderWith chunkCount + + for i = 0 to chunkCount - 1 do + let startIndex = i * chunkSize + let size = min chunkSize (len - startIndex) + let chunk = builderWith size + + for j = 0 to size - 1 do + chunk.Add list.[startIndex + j] + + res.Add (ofBuilder chunk) + + moveFromBuilder res + + /// Returns a new that contains the elements of the original but with duplicates removed + /// The input + /// The with distinct elements + let distinct list = + check list + let len = list.Length + let res = builderWith len + let set = System.Collections.Generic.HashSet<'T> (HashIdentity.Structural) + + for i = 0 to len - 1 do + let item = list.[i] + if set.Add item then + res.Add item + + ofBuilder res + + /// Returns a new that contains all pairwise combinations of elements from the first and second s + /// The first input + /// The second input + /// The of all pairwise combinations + let allPairs xs ys = + check xs + check ys + let lenXs = xs.Length + let lenYs = ys.Length + let res = builderWith (lenXs * lenYs) + + for i = 0 to lenXs - 1 do + for j = 0 to lenYs - 1 do + res.Add (xs.[i], ys.[j]) + + moveFromBuilder res + + /// Returns a new with the elements permuted according to the specified permutation + /// The function that maps input indices to output indices + /// The input + /// The permuted + /// Thrown when the permutation function returns an out-of-range index + let permute indexMap list = + check list + let len = list.Length + let res = builderWith len + let permuted = Array.zeroCreate len + + for i = 0 to len - 1 do + let j = indexMap i + if j < 0 || j >= len then + invalidArg (nameof indexMap) "Invalid permutation" + permuted.[j] <- list.[i] + + for i = 0 to len - 1 do + res.Add permuted.[i] + + moveFromBuilder res + + /// Combines the two s into a of pairs. The two s must have equal lengths + /// The first input + /// The second input + /// The of pairs + /// Thrown when the s have different lengths + let zip list1 list2 = + checkNotDefault (nameof list1) list1 + checkNotDefault (nameof list2) list2 + + let len1 = list1.Length + if len1 <> list2.Length then + invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths + + let res = builderWith len1 + for i = 0 to len1 - 1 do + res.Add (list1.[i], list2.[i]) + + moveFromBuilder res + + /// Combines the three s into a of triples. The three s must have equal lengths + /// The first input + /// The second input + /// The third input + /// The of triples + /// Thrown when the s have different lengths + let zip3 list1 list2 list3 = + checkNotDefault (nameof list1) list1 + checkNotDefault (nameof list2) list2 + checkNotDefault (nameof list3) list3 + + let len1 = list1.Length + if len1 <> list2.Length then + invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths + if len1 <> list3.Length then + invalidArg (nameof list3) ErrorStrings.ListsHaveDifferentLengths + + let res = builderWith len1 + for i = 0 to len1 - 1 do + res.Add (list1.[i], list2.[i], list3.[i]) + + moveFromBuilder res + + /// Splits a of pairs into two s + /// The input + /// The two s unzipped from the input + let unzip list = + check list + let len = list.Length + let res1 = builderWith len + let res2 = builderWith len + + for i = 0 to len - 1 do + let x, y = list.[i] + res1.Add x + res2.Add y + + ofBuilder res1, ofBuilder res2 + + /// Splits a of triples into three s + /// The input + /// The three s unzipped from the input + let unzip3 list = + check list + let len = list.Length + let res1 = builderWith len + let res2 = builderWith len + let res3 = builderWith len + + for i = 0 to len - 1 do + let x, y, z = list.[i] + res1.Add x + res2.Add y + res3.Add z + + ofBuilder res1, ofBuilder res2, ofBuilder res3 + + /// Returns the average of the elements in the + /// The input + /// The average of the elements + /// Thrown when the is empty + let average list = + check list + let len = list.Length + if len = 0 then + invalidArg (nameof list) "The input list was empty." + + let mutable sum = 0.0 + for i = 0 to len - 1 do + sum <- sum + float list.[i] + + sum / float len + + /// Returns the average of the results of applying the function to each element of the + /// The function to transform the elements before averaging + /// The input + /// The average of the projected elements + /// Thrown when the is empty + let averageBy projection list = + check list + let len = list.Length + if len = 0 then + invalidArg (nameof list) "The input list was empty." + + let mutable sum = 0.0 + for i = 0 to len - 1 do + sum <- sum + (float (projection list.[i])) + + sum / float len + + /// Applies a function to each element of the , threading an accumulator argument through the computation + /// The function to update the state given the input elements + /// The initial state + /// The input + /// The final state + let fold<'T, 'State> folder (state : 'State) (list : FlatList<'T>) = + check list + let mutable state = state + for i = 0 to list.Length - 1 do + state <- folder state list.[i] + state + + /// Applies a function to corresponding elements of two s, threading an accumulator argument through the computation + /// The function to update the state given the input elements from both s + /// The initial state + /// The first input + /// The second input + /// The final state + /// Thrown when the s have different lengths + let fold2<'T1, 'T2, 'State> folder (state : 'State) (list1 : FlatList<'T1>) (list2 : FlatList<'T2>) = + checkNotDefault (nameof list1) list1 + checkNotDefault (nameof list2) list2 + let f = OptimizedClosures.FSharpFunc<_, _, _, _>.Adapt (folder) + let len1 = list1.Length + + if len1 <> list2.Length then + invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths + + let mutable state = state + for i = 0 to len1 - 1 do + state <- f.Invoke (state, list1.[i], list2.[i]) + state + + /// Applies a function to each element of the , threading an accumulator argument through the computation, starting from the end + /// The function to update the state given the input elements, starting from the end + /// The input + /// The initial state + /// The final state + let foldBack<'T, 'State> folder (list : FlatList<'T>) (state : 'State) = + check list + let mutable state = state + for i = list.Length - 1 downto 0 do + state <- folder list.[i] state + state + + /// Applies a function to corresponding elements of two s, threading an accumulator argument through the computation, starting from the end + /// The function to update the state given the input elements from both s, starting from the end + /// The first input + /// The second input + /// The initial state + /// The final state + /// Thrown when the s have different lengths + let foldBack2<'T1, 'T2, 'State> folder (list1 : FlatList<'T1>) (list2 : FlatList<'T2>) (state : 'State) = + checkNotDefault (nameof list1) list1 + checkNotDefault (nameof list2) list2 + let f = OptimizedClosures.FSharpFunc<_, _, _, _>.Adapt (folder) + let len1 = list1.Length + + if len1 <> list2.Length then + invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths + + let mutable state = state + for i = len1 - 1 downto 0 do + state <- f.Invoke (list1.[i], list2.[i], state) + state + + /// Applies a function to each element of the , threading an accumulator argument through the computation. + /// This function takes the second argument, and applies the function to it and the first element of the . + /// Then, it passes this result into the function along with the second element, and so on. + /// Finally, it returns the final result. If the is empty, an exception is raised. + /// The function to reduce the with + /// The input + /// The final accumulated value + /// Thrown when the is empty + let reduce operation list = + check list + if list.Length = 0 then + invalidArg (nameof list) "The input list was empty." + + let mutable state = list.[0] + for i = 1 to list.Length - 1 do + state <- operation state list.[i] + state + + /// Applies a function to each element of the , threading an accumulator argument through the computation, starting from the end. + /// This function takes the last element of the and the second-to-last element, and applies the function to them. + /// Then, it passes this result into the function along with the third-to-last element, and so on. + /// Finally, it returns the final result. If the is empty, an exception is raised. + /// The function to reduce the with, starting from the end + /// The input + /// The final accumulated value + /// Thrown when the is empty + let reduceBack operation list = + check list + if list.Length = 0 then + invalidArg (nameof list) "The input list was empty." + + let len = list.Length + let mutable state = list.[len - 1] + for i = len - 2 downto 0 do + state <- operation list.[i] state + state + + /// Like fold, but returns both the intermediate and final results + /// The function to update the state given the input elements + /// The initial state + /// The input + /// The of all intermediate and final states + let scan<'T, 'State> folder (state : 'State) (list : FlatList<'T>) = + check list + let len = list.Length + let res = builderWith (len + 1) + res.Add state + + let mutable state = state + for i = 0 to len - 1 do + state <- folder state list.[i] + res.Add state + + moveFromBuilder res + + /// Like foldBack, but returns both the intermediate and final results + /// The function to update the state given the input elements, starting from the end + /// The input + /// The initial state + /// The of all intermediate and final states, in reverse order of computation + let scanBack<'T, 'State> folder (list : FlatList<'T>) (state : 'State) = + check list + let len = list.Length + let res = builderWith (len + 1) + let mutable states = Array.zeroCreate (len + 1) + states.[len] <- state + + let mutable state = state + for i = len - 1 downto 0 do + state <- folder list.[i] state + states.[i] <- state + + for i = 0 to len do + res.Add states.[i] + + moveFromBuilder res + + /// Tries to reduce the using the given function, returning ValueNone if empty. + let tryReduce reduction (list: FlatList<'T>) : voption<'T> = + check list + if list.Length = 0 then ValueNone + else + let mutable state = list.[0] + for i = 1 to list.Length - 1 do + state <- reduction state list.[i] + ValueSome state + + /// Tries to reduce the from the end using the given function, returning ValueNone if empty. + let tryReduceBack reduction (list: FlatList<'T>) : voption<'T> = + check list + let len = list.Length + if len = 0 then ValueNone + else + let mutable state = list.[len - 1] + for i = len - 2 downto 0 do + state <- reduction list.[i] state + ValueSome state + + /// Tries to compute the average of the , returning ValueNone if empty. + let tryAverage (list: FlatList<'T>) : voption = + check list + let len = list.Length + if len = 0 then ValueNone + else + let mutable sum = 0.0 + for i = 0 to len - 1 do + sum <- sum + float list.[i] + ValueSome (sum / float len) + + /// Tries to compute the average of the projected values of the , returning ValueNone if empty. + let tryAverageBy projection (list: FlatList<'T>) : voption = + check list + let len = list.Length + if len = 0 then ValueNone + else + let mutable sum = 0.0 + for i = 0 to len - 1 do + sum <- sum + float (projection list.[i]) + ValueSome (sum / float len) + ////////// module ImmutableArray = FlatList From 185d6016fb4fbbc55fd81d985ff43e491d04c92a Mon Sep 17 00:00:00 2001 From: Andrii Chebukin Date: Sun, 8 Jun 2025 16:07:06 +0400 Subject: [PATCH 6/8] feat: implemented missing `FlatList` functions and tests --- .../FSharp.Collections.Immutable.fsproj | 8 +- src/FSharp.Collections.Immutable/FlatList.fs | 1301 +++++++++-------- src/FSharp.Collections.Immutable/Helper.fs | 55 + .../ImmutableCollectionUtil.fs | 19 - .../FSharp.Collections.Immutable.Tests.fsproj | 15 +- .../FlatList.fs | 10 - .../FlatList/BasicOperationsTests.fs | 137 ++ .../FlatList/BuilderTests.fs | 96 ++ .../FlatList/CreationTests.fs | 123 ++ .../FlatList/FilterMapTests.fs | 149 ++ .../FlatList/FlatListTestBase.fs | 16 + .../FlatList/FoldTests.fs | 189 +++ .../FlatList/GroupingTests.fs | 151 ++ .../FlatList/IterationTests.fs | 109 ++ .../FlatList/ManipulationTests.fs | 231 +++ .../FlatList/PairOperationTests.fs | 160 ++ .../FlatList/SearchTests.fs | 138 ++ .../FlatList/SortTests.fs | 126 ++ .../FlatList/UtilityTests.fs | 246 ++++ .../Helpers.fs | 5 + 20 files changed, 2605 insertions(+), 679 deletions(-) create mode 100644 src/FSharp.Collections.Immutable/Helper.fs delete mode 100644 src/FSharp.Collections.Immutable/ImmutableCollectionUtil.fs delete mode 100644 tests/FSharp.Collections.Immutable.Tests/FlatList.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/FlatList/BasicOperationsTests.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/FlatList/BuilderTests.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/FlatList/CreationTests.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/FlatList/FilterMapTests.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTestBase.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/FlatList/FoldTests.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/FlatList/GroupingTests.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/FlatList/IterationTests.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/FlatList/ManipulationTests.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/FlatList/PairOperationTests.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/FlatList/SearchTests.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/FlatList/SortTests.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/FlatList/UtilityTests.fs create mode 100644 tests/FSharp.Collections.Immutable.Tests/Helpers.fs diff --git a/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj b/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj index bf684f1..8b7d8e6 100644 --- a/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj +++ b/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj @@ -21,7 +21,7 @@ - + @@ -40,4 +40,10 @@ + + + <_Parameter1>FSharp.Collections.Immutable.Tests + + + diff --git a/src/FSharp.Collections.Immutable/FlatList.fs b/src/FSharp.Collections.Immutable/FlatList.fs index 447fd40..4edef62 100644 --- a/src/FSharp.Collections.Immutable/FlatList.fs +++ b/src/FSharp.Collections.Immutable/FlatList.fs @@ -4,6 +4,11 @@ namespace global namespace FSharp.Collections.Immutable #endif +open System +open System.Collections.Generic +open System.Collections.Immutable +open System.Linq + // The FlatList name comes from a similar construct seen in the official F# source code type FlatList<'T> = System.Collections.Immutable.ImmutableArray<'T> @@ -19,18 +24,9 @@ module FlatList = if list.IsDefault then invalidArg argName "Uninstantiated ImmutableArray/FlatList" - let inline internal check (list : FlatList<'T>) = checkNotDefault (nameof list) list - let inline internal indexNotFound () = raise <| System.Collections.Generic.KeyNotFoundException () - let inline private lengthWhile predicate list = - check list - let mutable count = 0 - - while count < list.Length && predicate list.[count] do - count <- count + 1 - - count + let inline private lengthWhile (predicate : 'T -> bool) (list : FlatList<'T>) = list.TakeWhile(predicate).Count () ////////// Creating ////////// @@ -44,11 +40,17 @@ module FlatList = /// A containing the elements from the builder /// Thrown when builder is null let moveFromBuilder (builder : FlatList<_>.Builder) : FlatList<_> = - checkNotNull (nameof builder) builder + checkNotNull (nameof builder) builder // Keep check for null builder, not default FlatList builder.MoveToImmutable () /// Returns an empty /// An empty + /// + /// + /// let emptyList = FlatList.empty<int> + /// printfn "Is empty? %b" (FlatList.isEmpty emptyList) // true + /// + /// let inline empty<'T> : FlatList<'T> = FlatListFactory.Create<'T> () /// Builds a from the given array @@ -71,28 +73,22 @@ module FlatList = /// The function to initialize each element /// A new with the initialized elements /// Thrown when count is negative - let init count initializer = + let init count (initializer : int -> 'T) = if count < 0 then invalidArg (nameof count) ErrorStrings.InputMustBeNonNegative - - let builder = builderWith count - - for i = 0 to count - 1 do - builder.Add <| initializer i - - moveFromBuilder builder + ParallelEnumerable.Range(0, count).Select(initializer).ToImmutableArray () /// Creates a of a given length with all elements set to the given value /// The length of the to create - /// The value to replicate + /// The value to replicate /// A of the specified length with all elements equal to the given value - let create count item = init count <| fun _ -> item // optimize + let create count (value : 'T) = init count (fun _ -> value) /// Replicates a value into a of a given length /// The length of the to create - /// The value to replicate + /// The value to replicate /// A of the specified length with all elements equal to the given value - let replicate count item = create count item + let replicate count initial = create count initial /// Views the as a sequence /// The input @@ -102,9 +98,7 @@ module FlatList = /// Builds an array from the given /// The to build the array from /// An array containing the elements of the - let inline toArray (list : FlatList<_>) = - check list - Seq.toArray list + let inline toArray (list : FlatList<_>) = list.ToArray () ////////// Building ////////// @@ -113,8 +107,8 @@ module FlatList = /// A containing the elements from the builder /// Thrown when builder is null let ofBuilder (builder : FlatList<_>.Builder) : FlatList<_> = - checkNotNull (nameof builder) builder - builder.ToImmutable () + checkNotNull (nameof builder) builder // Keep check for null builder + builder.MoveToImmutable () /// Creates a new builder /// An empty builder @@ -123,11 +117,10 @@ module FlatList = /// Creates a builder containing the elements of the input /// The to create the builder from /// A builder containing the elements of the - let toBuilder list : FlatList<_>.Builder = - check list - list.ToBuilder () + let toBuilder (list : FlatList<'T>) : FlatList<'T>.Builder = list.ToBuilder () module Builder = + let inline private check (builder : FlatList<'T>.Builder) = checkNotNull (nameof builder) builder /// Adds an item to the builder @@ -157,27 +150,21 @@ module FlatList = /// Returns the number of elements in the /// The input /// The number of elements in the - let length list = - check list - list.Length + let length (list : FlatList<'T>) = list.Length /// Gets the element at the specified index in the /// The index to retrieve /// The input /// The element at the specified index /// Thrown when the index is out of range - let item index list = - check list - list.[index] + let item index (list : FlatList<'T>) = list.[index] /// Appends two s to create a new containing all elements from both s /// The first /// The second /// A new containing all elements from both input s - let append list1 list2 : FlatList<'T> = - checkNotDefault (nameof list1) list1 - checkNotDefault (nameof list2) list2 - list1.AddRange (list2 : FlatList<_>) + let append (list1 : FlatList<'T>) (list2 : FlatList<'T>) : FlatList<'T> = + list1.AddRange (list2 :> System.Collections.Generic.IEnumerable<'T>) /// Searches for the specified object and returns the zero-based index of the first occurrence within the range /// of elements in the that starts at the specified index and @@ -188,9 +175,7 @@ module FlatList = /// The item to search for /// The input /// The zero-based index of the first occurrence of the item - let indexRangeWith comparer index count item list = - check list - list.IndexOf (item, index, count, comparer) + let indexRangeWith comparer index count item (list : FlatList<'T>) = list.IndexOf (item, index, count, comparer) /// Searches for the specified object and returns the zero-based index of the first occurrence within the range /// of elements in the that starts at the specified index and @@ -247,9 +232,7 @@ module FlatList = /// The item to search for /// The input /// The zero-based index of the last occurrence of the item - let lastIndexRangeWith comparer index count item list = - check list - list.LastIndexOf (item, index, count, comparer) + let lastIndexRangeWith comparer index count item (list : FlatList<'T>) = list.LastIndexOf (item, index, count, comparer) /// Searches for the specified object and returns the zero-based index of the last occurrence within the /// range of elements in the that contains the specified number @@ -302,38 +285,40 @@ module FlatList = /// The items to remove /// The input /// A new with the specified items removed - let removeAllWith (comparer : System.Collections.Generic.IEqualityComparer<'T>) (items : 'T seq) list : FlatList<_> = - check list - list.RemoveRange (items, comparer) + let removeAllWith comparer (items : 'T seq) (list : FlatList<'T>) : FlatList<'T> = list.RemoveRange (items, comparer) /// Removes the specified objects from the . /// The items to remove /// The input /// A new with the specified items removed - let removeAll items list = removeAllWith HashIdentity.Structural items list + let removeAll items (list : FlatList<'T>) = removeAllWith HashIdentity.Structural items list /// Removes all the elements that do not match the conditions defined by the specified predicate. /// The predicate to test elements /// The input /// A new with elements that match the predicate - let filter predicate list : FlatList<_> = - check list - System.Predicate (not << predicate) |> list.RemoveAll + /// + /// + /// let numbers = FlatList.ofArray [|1; 2; 3; 4; 5; 6|] + /// let evens = FlatList.filter (fun x -> x % 2 = 0) numbers + /// // evens is [|2; 4; 6|] + /// + /// + let filter (predicate : 'T -> bool) (list : FlatList<'T>) : FlatList<'T> = + list.RemoveAll (System.Predicate (not << predicate)) /// Removes all the elements that do not match the conditions defined by the specified predicate. /// The predicate to test elements /// The input /// A new with elements that match the predicate - let where predicate list = filter predicate list + let where (predicate : 'T -> bool) (list : FlatList<'T>) : FlatList<'T> = filter predicate list /// Removes a range of elements from the . /// The starting index /// The number of elements to remove /// The input /// A new with the specified range of elements removed - let removeRange index (count : int) list : FlatList<_> = - check list - list.RemoveRange (index, count) + let removeRange index (count : int) (list : FlatList<'T>) : FlatList<'T> = list.RemoveRange (index, count) /// Copies a range of elements from the source to the destination array /// The source @@ -342,13 +327,8 @@ module FlatList = /// The starting index in the destination array /// The number of elements to copy /// Thrown when the range is invalid - let blit source sourceIndex (destination : 'T[]) destinationIndex count = - checkNotDefault (nameof source) source - - try - source.CopyTo (sourceIndex, destination, destinationIndex, count) - with exn -> - raise exn // throw same exception with the correct stack trace. Update exception code + let blit (source : FlatList<'T>) sourceIndex (destination : 'T[]) destinationIndex count = + source.CopyTo (sourceIndex, destination, destinationIndex, count) /// Sorts a range of elements in the using the specified comparer /// The comparer to use @@ -356,9 +336,7 @@ module FlatList = /// The number of elements to sort /// The input /// A new with the specified range of elements sorted - let sortRangeWithComparer comparer index count list = - check list - list.Sort (index, count, comparer) + let sortRangeWithComparer comparer index count (list : FlatList<'T>) = list.Sort (index, count, comparer) /// Sorts a range of elements in the using the specified comparison function /// The comparison function to use @@ -380,9 +358,7 @@ module FlatList = /// The comparer to use /// The input /// A new with the elements sorted - let sortWithComparer (comparer : System.Collections.Generic.IComparer<_>) list = - check list - list.Sort (comparer) + let sortWithComparer (comparer : System.Collections.Generic.IComparer<'T>) (list : FlatList<'T>) = list.Sort (comparer) /// Sorts the elements in the using the specified comparison function /// The comparison function to use @@ -393,145 +369,145 @@ module FlatList = /// Sorts the elements in the using the default comparer /// The input /// A new with the elements sorted - let sort list = - check list - list.Sort () + let sort (list : FlatList<'T>) = list.Sort () - ////////// Loop-based ////////// + /// Returns a new array that contains elements of the original array sorted in descending order. + /// The input array. + /// The sorted array. + let inline sortDescending (list : FlatList<'T>) : FlatList<'T> when 'T : comparison = sortWith (fun x y -> compare y x) list - let inline private builderWithLengthOf list = builderWith <| length list + /// Returns a new array that contains elements of the original array sorted in descending order using the specified projection. + /// The function to transform the elements into a type that supports comparison. + /// The input array. + /// The sorted array. + let inline sortByDescending (projection : 'T -> 'Key) (list : FlatList<'T>) : FlatList<'T> when 'Key : comparison = + sortWith (fun x y -> compare (projection y) (projection x)) list - let rec private concatAddLengths (arrs : FlatList>) i acc = - if i >= length arrs then - acc - else - concatAddLengths arrs (i + 1) (acc + arrs.[i].Length) + /// Sorts the array using keys given by the given projection. Keys are compared using Operators.compare. + /// The function to transform the elements into a type supporting comparison. + /// The input array. + /// The sorted array. + let inline sortBy (projection : 'T -> 'Key) (list : FlatList<'T>) : FlatList<'T> when 'Key : comparison = + sortWith (fun x y -> compare (projection x) (projection y)) list + + ////////// Loop-based (now LINQ-based where applicable) ////////// /// Concatenates a of s into a single /// The of s to concatenate /// A new containing all elements from the input s let concat (arrs : FlatList>) = - let result : FlatList<'T>.Builder = builderWith <| concatAddLengths arrs 0 0 - - for i = 0 to length arrs - 1 do - result.AddRange (arrs.[i] : FlatList<'T>) - - moveFromBuilder result + let builder = FlatListFactory.CreateBuilder<'T> (arrs.Sum _.Length) + for i = 0 to arrs.Length - 1 do + let arr = arrs.[i] + for j = 0 to arrs.[i].Length - 1 do + builder.Add (arr.[j]) + builder.MoveToImmutable () /// Builds a new from the elements of a by applying a mapping function to each element /// A function to transform elements from the input /// The input /// A containing the transformed elements - let inline map mapping list = - check list - let builder = builderWithLengthOf list + /// + /// + /// let numbers = FlatList.ofArray [|1; 2; 3; 4; 5|] + /// let squares = FlatList.map (fun x -> x * x) numbers + /// // squares is [|1; 4; 9; 16; 25|] + /// + /// + let inline map (mapping : 'T -> 'U) (list : FlatList<'T>) : FlatList<'U> = list.Select(mapping).ToImmutableArray () + + /// Build a new array whose elements are the results of applying the given function + /// to each of the elements of the array. The integer index passed to the + /// function indicates the index of element being transformed. + /// A function to transform an element and its index into a result element. + /// The input array. + /// The array of transformed elements. + let mapi (mapping : int -> 'T -> 'U) (list : FlatList<'T>) : FlatList<'U> = + list.Select(fun x i -> mapping i x).ToImmutableArray () + + /// Builds a new array whose elements are the results of applying the given function + /// to the corresponding elements of the two collections pairwise, also passing the index of + /// the elements. The two input arrays must have the same lengths. + /// The function to transform pairs of input elements and their indices. + /// The first input array. + /// The second input array. + /// The array of transformed elements. + let mapi2 (mapping : int -> 'T1 -> 'T2 -> 'U) (list1 : FlatList<'T1>) (list2 : FlatList<'T2>) : FlatList<'U> = + let len1 = list1.Length + if len1 <> list2.Length then + invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths - for i = 0 to length list - 1 do - builder.Add (mapping list.[i]) + Enumerable.Range(0, len1).Select(fun i -> mapping i list1.[i] list2.[i]).ToImmutableArray () - moveFromBuilder builder + /// Builds a new collection whose elements are the results of applying the given function + /// to the corresponding elements of the two collections pairwise. The two input + /// arrays must have the same lengths. + /// The function to transform the pairs of the input elements. + /// The first input array. + /// The second input array. + /// The array of transformed elements. + let map2 (mapping : 'T1 -> 'T2 -> 'U) (list1 : FlatList<'T1>) (list2 : FlatList<'T2>) : FlatList<'U> = + let len1 = list1.Length + if len1 <> list2.Length then + invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths + + Enumerable.Range(0, len1).Select(fun i -> mapping list1.[i] list2.[i]).ToImmutableArray () /// Counts the number of elements in the that satisfy the given predicate /// A function to project elements from the input /// The input /// A of key-value pairs where the key is the projected value and the value is the count - let countBy projection list = - check list - let dict = new System.Collections.Generic.Dictionary<'Key, int> (HashIdentity.Structural) - - for v in list do - let key = projection v - let mutable prev = Unchecked.defaultof<_> - - if dict.TryGetValue (key, &prev) then - dict.[key] <- prev + 1 - else - dict.[key] <- 1 - - let res = builderWith dict.Count - let mutable i = 0 - - for group in dict do - res.Add (group.Key, group.Value) - i <- i + 1 - - moveFromBuilder res + let countBy (projection : 'T -> 'Key) (list : FlatList<'T>) = + list.GroupBy(projection).Select(fun group -> struct (group.Key, group.Count ())).ToImmutableArray () /// Creates a containing the elements of the original paired with their indices /// The input /// A containing pairs of indices and elements - let indexed list = - check list - let builder = builderWithLengthOf list - - for i = 0 to length list - 1 do - builder.Add (i, list.[i]) - - moveFromBuilder builder + let indexed (list : FlatList<'T>) = list.Select(fun item index -> struct (index, item)).ToImmutableArray () /// Applies the given function to each element of the /// A function to apply to each element /// The input let inline iter action list = - check list - - for i = 0 to length list - 1 do - action list.[i] + for item in list do + action item /// Applies the given function to each element of the and its index /// A function to apply to each element and its index /// The input - let iteri action list = - check list - let f = OptimizedClosures.FSharpFunc<_, _, _>.Adapt (action) - let len = list.Length - - for i = 0 to len - 1 do - f.Invoke (i, list.[i]) + let iteri action (list : FlatList<'T>) = + for i = 0 to list.Length - 1 do + do action i list.[i] /// Applies the given function to pair of elements at the same position in the two s /// A function to apply to pairs of elements /// The first input /// The second input /// Thrown when the s have different lengths - let iter2 action list1 list2 = - checkNotDefault (nameof list1) list1 - checkNotDefault (nameof list2) list2 - let f = OptimizedClosures.FSharpFunc<'T, 'U, unit>.Adapt (action) - let len = length list1 - - if len <> length list2 then + let iter2 action (list1 : FlatList<'T>) (list2 : FlatList<'T>) = + let len = list1.Length + if len <> list2.Length then invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths - for i = 0 to len - 1 do - f.Invoke (list1.[i], list2.[i]) + do action list1.[i] list2.[i] /// Applies the given function to the pair of elements at the same position in the two s along with their index /// A function to apply to pairs of elements and their index /// The first input /// The second input /// Thrown when the s have different lengths - let iteri2 action list1 list2 = - checkNotDefault (nameof list1) list1 - checkNotDefault (nameof list2) list2 - let f = OptimizedClosures.FSharpFunc<_, _, _, _>.Adapt (action) + let iteri2 action (list1 : FlatList<'T>) (list2 : FlatList<'T>) = let len1 = list1.Length - if len1 <> list2.Length then invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths - for i = 0 to len1 - 1 do - f.Invoke (i, list1.[i], list2.[i]) + action i list1.[i] list2.[i] /// Tests if any element of the satisfies the given predicate /// A function to test each element /// The input /// True if any element satisfies the predicate, false otherwise - let exists predicate list = - check list - let len = list.Length - let rec loop i = i < len && (predicate list.[i] || loop (i + 1)) - loop 0 + let exists (predicate : 'T -> bool) (list : FlatList<'T>) = list.Any (predicate) /// Tests if any corresponding pair of elements from the two s satisfies the given predicate /// A function to test pairs of elements @@ -539,30 +515,18 @@ module FlatList = /// The second input /// True if any pair of elements satisfies the predicate, false otherwise /// Thrown when the s have different lengths - let exists2 predicate list1 list2 = - checkNotDefault (nameof list1) list1 - checkNotDefault (nameof list2) list2 - let f = OptimizedClosures.FSharpFunc<_, _, _>.Adapt (predicate) + let exists2 (predicate : 'T -> 'T -> bool) (list1 : FlatList<'T>) (list2 : FlatList<'T>) = let len1 = list1.Length - if len1 <> list2.Length then invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths - - let rec loop i = - i < len1 - && (f.Invoke (list1.[i], list2.[i]) || loop (i + 1)) - + let rec loop i = i < len1 && (predicate list1.[i] list2.[i] || loop (i + 1)) loop 0 /// Tests if all elements of the satisfy the given predicate /// A function to test each element /// The input /// True if all elements satisfy the predicate, false otherwise - let forall predicate list = - check list - let len = list.Length - let rec loop i = i >= len || (predicate list.[i] && loop (i + 1)) - loop 0 + let forall (predicate : 'T -> bool) (list : FlatList<'T>) = list.All (predicate) /// Tests if all corresponding pairs of elements from the two s satisfy the given predicate /// A function to test pairs of elements @@ -570,327 +534,252 @@ module FlatList = /// The second input /// True if all pairs of elements satisfy the predicate, false otherwise /// Thrown when the s have different lengths - let forall2 predicate list1 list2 = - checkNotDefault (nameof list1) list1 - checkNotDefault (nameof list2) list2 - let f = OptimizedClosures.FSharpFunc<_, _, _>.Adapt (predicate) + let forall2 predicate (list1 : FlatList<'T>) (list2 : FlatList<'T>) = let len1 = list1.Length - if len1 <> list2.Length then invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths - - let rec loop i = - i >= len1 - || (f.Invoke (list1.[i], list2.[i]) && loop (i + 1)) - + let rec loop i = i >= len1 || (predicate list1.[i] list2.[i] && loop (i + 1)) loop 0 /// Tests if the given element exists in the /// The element to find /// The input /// True if the element exists in the , false otherwise - let inline contains e list = - check list - let mutable state = false - let mutable i = 0 - - while (not state && i < list.Length) do - state <- e = list.[i] - i <- i + 1 - - state + let inline contains item (list : FlatList<'T>) = list.Contains (item) /// Splits the into two s, containing the elements for which the given predicate returns true and false respectively /// A function to test each element /// The input /// A tuple of two s, containing the elements for which the predicate returns true and false respectively - let partition predicate list = - check list + let partition (predicate : 'T -> bool) (list : FlatList<'T>) = let res1 = builderWith list.Length let res2 = builderWith list.Length + for x in list do // Iteration will cause InvalidOperationException if list is default + if predicate x then res1.Add x else res2.Add x + (ofBuilder res1, ofBuilder res2) - for i = 0 to list.Length - 1 do - let x = list.[i] - if predicate x then res1.Add (x) else res2.Add (x) - - ofBuilder res1, ofBuilder res2 /// Returns the first element for which the given predicate returns true /// A function to test elements /// The input /// The first element for which the predicate returns true /// Thrown if no element satisfies the predicate - let find predicate list = - check list - - let rec loop i = - if i >= list.Length then indexNotFound () - else if predicate list.[i] then list.[i] - else loop (i + 1) + let find (predicate : 'T -> bool) (list : FlatList<'T>) = list.First (predicate) - loop 0 - - /// Returns the first element for which the given predicate returns true, or None if no such element exists + /// Returns the first element for which the given predicate returns true, or ValueNone if no such element exists /// A function to test elements /// The input - /// Some value if an element satisfies the predicate, None otherwise - let tryFind predicate list = - check list - - let rec loop i = - if i >= list.Length then None - else if predicate list.[i] then Some list.[i] - else loop (i + 1) - - loop 0 + /// ValueSome value if an element satisfies the predicate, ValueNone otherwise + let tryFind (predicate : 'T -> bool) (list : FlatList<'T>) : 'T voption = list.Where (predicate) |> Seq.vtryHead /// Returns the last element for which the given predicate returns true /// A function to test elements /// The input /// The last element for which the predicate returns true /// Thrown if no element satisfies the predicate - let findBack predicate list = - check list - - let rec loop i = - if i < 0 then indexNotFound () - else if predicate list.[i] then list.[i] - else loop (i - 1) + let findBack (predicate : 'T -> bool) (list : FlatList<'T>) = list.Last (predicate) - loop <| length list - 1 - - /// Returns the last element for which the given predicate returns true, or None if no such element exists + /// Returns the last element for which the given predicate returns true, or ValueNone if no such element exists /// A function to test elements /// The input - /// Some value if an element satisfies the predicate, None otherwise - let tryFindBack predicate list = - check list - - let rec loop i = - if i < 0 then None - else if predicate list.[i] then Some list.[i] - else loop (i - 1) - - loop <| length list - 1 + /// ValueSome value if an element satisfies the predicate, ValueNone otherwise + let tryFindBack (predicate : 'T -> bool) (list : FlatList<'T>) : 'T voption = + seq { + for i = list.Length - 1 downto 0 do + yield list.[i] + } + |> Seq.where predicate + |> Seq.vtryHead /// Returns the last index for which the given predicate returns true /// A function to test elements /// The input /// The last index for which the predicate returns true /// Thrown if no element satisfies the predicate - let findIndexBack predicate list = - check list - - let rec loop i = - if i < 0 then indexNotFound () - else if predicate list.[i] then i - else loop (i - 1) - - loop <| length list - 1 - - /// Returns the last index for which the given predicate returns true, or None if no such element exists + let findIndexBack (predicate : 'T -> bool) (list : FlatList<'T>) = + let len = list.Length - 1 + seq { + for i = len downto 0 do + yield struct (len - i, list.[i]) + } + |> Seq.where (fun struct (i, item) -> predicate item) + |> Seq.map (fun struct (i, item) -> i) + |> Seq.head + + /// Returns the last index for which the given predicate returns true, or ValueNone if no such element exists /// A function to test elements /// The input - /// Some index if an element satisfies the predicate, None otherwise - let tryFindIndexBack predicate list = - check list - - let rec loop i = - if i < 0 then None - else if predicate list.[i] then Some i - else loop (i - 1) - - loop <| length list - 1 - - /// Returns the first value for which the given function returns Some value + /// ValueSome index if an element satisfies the predicate, ValueNone otherwise + let tryFindIndexBack (predicate : 'T -> bool) (list : FlatList<'T>) : int voption = + let len = list.Length - 1 + seq { + for i = len downto 0 do + yield struct (len - i, list.[i]) + } + |> Seq.where (fun struct (i, item) -> predicate item) + |> Seq.map (fun struct (i, item) -> i) + |> Seq.vtryHead + + /// Returns the first value for which the given function returns ValueSome value /// A function to generate options from the elements /// The input - /// The first value for which the chooser returns Some value - /// Thrown if the chooser returns None for all elements - let pick chooser list = - check list + /// The first value for which the chooser returns ValueSome value + /// Thrown if the chooser returns ValueNone for all elements + let pick (chooser : 'T -> 'U voption) (list : FlatList<'T>) = + list.Select(chooser).Where(ValueOption.isSome).Select (ValueOption.get) + |> Seq.head - let rec loop i = - if i >= list.Length then - indexNotFound () - else - match chooser list.[i] with - | None -> loop (i + 1) - | Some res -> res - - loop 0 - - /// Returns the first value for which the given function returns Some value, or None if no such element exists + /// Returns the first value for which the given function returns ValueSome value, or ValueNone if no such element exists /// A function to generate options from the elements /// The input - /// The first value for which the chooser returns Some value, or None - let tryPick chooser list = - check list - - let rec loop i = - if i >= list.Length then - None - else - match chooser list.[i] with - | None -> loop (i + 1) - | res -> res - - loop 0 + /// The first value for which the chooser returns ValueSome value, or ValueNone + let tryPick (chooser : 'T -> 'U voption) (list : FlatList<'T>) : 'U voption = + list.Select(chooser).Where(ValueOption.isSome).Select (ValueOption.get) + |> Seq.vtryHead - /// Builds a new containing only the elements for which the given function returns Some value + /// Builds a new containing only the elements for which the given function returns ValueSome value /// A function to generate options from the elements /// The input - /// A containing the values wrapped in Some by the chooser - let choose chooser list = - check list - let res = builderWith list.Length - - for i = 0 to list.Length - 1 do - match chooser list.[i] with - | None -> () - | Some b -> res.Add (b) - - ofBuilder res + /// A containing the values wrapped in ValueSome by the chooser + let choose (chooser : 'T -> 'T voption) (list : FlatList<'T>) = + list.Select(chooser).Where(ValueOption.isSome).Select(ValueOption.get).ToImmutableArray () /// Creates a by applying a key-generating function to each element of the and grouping the elements by the resulting keys /// A function to transform elements into keys /// The input /// A of tuples where each tuple contains a key and a of all elements that match the key - let groupBy projection list = - check list - let dict = new System.Collections.Generic.Dictionary<'Key, ResizeArray<'T>> (HashIdentity.Structural) - - // Build the groupings - for i = 0 to (list.Length - 1) do - let v = list.[i] - let key = projection v - let ok, prev = dict.TryGetValue (key) - - if ok then - prev.Add (v) - else - let prev = new ResizeArray<'T> (1) - dict.[key] <- prev - prev.Add (v) - - // Return the -of-s. - let result = builderWith dict.Count - let mutable i = 0 - - for group in dict do - result.Add (group.Key, ofSeq group.Value) - i <- i + 1 - - moveFromBuilder result + let groupBy (projection : 'T -> 'Key) (list : FlatList<'T>) = + list.GroupBy(projection).Select(fun group -> struct (group.Key, group.ToImmutableArray ())).ToImmutableArray () /// Returns a new that contains the elements of the original but with duplicates eliminated by using the supplied projection function /// A function to transform elements before comparing them /// The input /// A with distinct elements as determined by the projection function - let distinctBy projection (list : FlatList<'T>) = - let builder : FlatList<'T>.Builder = builderWith <| length list - let set = System.Collections.Generic.HashSet<'Key> (HashIdentity.Structural) - let mutable outputIndex = 0 - - for i = 0 to length list - 1 do - let item = list.[i] - - if set.Add <| projection item then - outputIndex <- outputIndex + 1 - Builder.add item builder - - ofBuilder builder + let distinctBy (projection : 'T -> 'Key) (list : FlatList<'T>) = + let setBuilder = ImmutableHashSet.CreateBuilder<'Key> () + let arrayBuilder = ImmutableArray.CreateBuilder<'T> () + for i = 0 to list.Length - 1 do + let item = list.[i] // list.[i] will throw if list is default + if setBuilder.Add (projection item) then + arrayBuilder.Add (item) + arrayBuilder.ToImmutable () /// Creates a new by applying a mapping function to each element of the input and concatenating the results /// A function to transform elements of the input into s /// The input /// A containing the concatenation of all the s generated by the mapping function - let collect mapping list = concat <| map mapping list + let collect (mapping : 'T -> 'U seq) (list : FlatList<'T>) : FlatList<'U> = list.SelectMany(mapping).ToImmutableArray () /// Gets an element in the at the specified index /// The index of the element to retrieve /// The input - /// Some value containing the element, or None if the index is out of range - let tryItem index list = - if index >= length list || index < 0 then - None + /// ValueSome value containing the element, or ValueNone if the index is out of range + let tryItem index (list : FlatList<'T>) : voption<'T> = + // list.Length or list.[index] will throw if list is default before comparison happens + if list.IsDefault then + ValueNone // Explicitly handle default case for tryItem to return ValueNone + elif index >= 0 && index < list.Length then + ValueSome list.[index] else - Some (list.[index]) + ValueNone /// Returns the first element of the /// The input /// The first element of the /// Thrown when the is empty - let head list = item 0 list + let head (list : FlatList<'T>) = list.First () - /// Returns the first element of the , or None if the is empty + /// Returns the first element of the , or ValueNone if the is empty /// The input - /// Some value containing the first element, or None if the is empty - let tryHead list = tryItem 0 list + /// ValueSome value containing the first element, or ValueNone if the is empty + let tryHead (list : FlatList<'T>) : 'T voption = if list.IsEmpty then ValueNone else ValueSome list.[0] /// Returns the last element of the /// The input /// The last element of the /// Thrown when the is empty - let last (list : FlatList<_>) = list.[length list - 1] + let last (list : FlatList<_>) = list.Last () // Enumerable.Last throws if empty or default - /// Returns the last element of the , or None if the is empty + /// Returns the last element of the , or ValueNone if the is empty /// The input - /// Some value containing the last element, or None if the is empty - let tryLast list = tryItem (length list - 1) list + /// ValueSome value containing the last element, or ValueNone if the is empty + let tryLast (list : FlatList<'T>) : 'T voption = + if list.IsEmpty then + ValueNone + else + ValueSome list.[list.Length - 1] /// Returns the without its first element /// The input /// A containing all elements of the input except the first one - let tail list = removeRange 1 (length list - 1) list + let tail (list : FlatList<'T>) = + if list.IsEmpty then + invalidArg (nameof list) "List must not be empty to get tail." + list.Slice (1, list.Length - 1) - /// Returns the without its first element, or None if the is empty + /// Returns the without its first element, or ValueNone if the is empty /// The input - /// Some value containing the without its first element, or None if the is empty - let tryTail list = if isEmpty list then None else Some <| tail list + /// ValueSome value containing the without its first element, or ValueNone if the is empty + let tryTail (list : FlatList<'T>) : voption> = + if list.IsEmpty then + ValueNone + else + ValueSome (list.Slice (1, list.Length - 1)) /// Returns the first N elements of the /// The number of elements to take /// The input /// A containing the first N elements - let take count list = removeRange count (length list - count) list + let take (count : int) (list : FlatList<'T>) = + if count < 0 then + invalidArg (nameof count) ErrorStrings.InputMustBeNonNegative + let len = list.Length // Will throw if list is default + if count = 0 then empty + elif count >= len then list + else list.Slice (0, count) /// Returns a containing the first elements of the input for which the given predicate returns true /// A function to test each element /// The input /// A containing the first elements for which the predicate returns true - let takeWhile predicate list = take (lengthWhile predicate list) list + let takeWhile (predicate : 'T -> bool) (list : FlatList<'T>) = list.TakeWhile(predicate).ToImmutableArray () /// Returns the without its first N elements /// The number of elements to skip /// The input /// A containing all except the first N elements - let skip index list = removeRange 0 index list + let skip index (list : FlatList<'T>) = + if index < 0 then + invalidArg (nameof index) ErrorStrings.InputMustBeNonNegative + let len = list.Length // Will throw if list is default + if index = 0 then list + elif index >= len then empty + else list.Slice (index, len - index) /// Returns a that skips the elements of the input while the given predicate returns true, then returns the rest /// A function to test each element /// The input /// A that skips the elements while the predicate returns true, then contains the rest - let skipWhile predicate list = skip (lengthWhile predicate list) list + let skipWhile (predicate : 'T -> bool) (list : FlatList<'T>) = list.SkipWhile(predicate).ToImmutableArray () /// Gets a sublist of the input /// The index of the first element to include - /// The index of the element at which to end (exclusive) + /// The number of elements in the sublist /// The input - /// A containing the elements from start to stop-1 - let sub start stop list = skip start list |> take (stop - start - 1) + /// A containing the elements from start index for the given count + let sub start count (list : FlatList<'T>) = list.Slice (start, count) /// Returns a that contains no more than N elements of the input /// The maximum number of elements to include /// The input /// A containing at most N elements - let truncate count list = if count < length list then take count list else list + let truncate count (list : FlatList<'T>) = if count < list.Length then list.Slice (0, count) else list // list.Length throws if default /// Splits the into two s at the specified index /// The index at which to split the /// The input /// A tuple of two s, the first containing the elements up to the index, the second containing the rest - let splitAt index list = take index list, skip index list + let splitAt index (list : FlatList<'T>) = (list.Slice (0, index), list.Slice (index, list.Length - index)) /// Applies a function to the builder and returns the resulting /// The function to apply to the builder @@ -904,8 +793,8 @@ module FlatList = /// The function to apply to the builder /// The input /// The updated - let inline update f list = - let builder = toBuilder list + let inline update f (list : FlatList<'T>) = + let builder = toBuilder list // toBuilder will throw if list is default f builder moveFromBuilder builder @@ -914,166 +803,144 @@ module FlatList = /// The input /// The index of the first element that satisfies the predicate /// Thrown if no element satisfies the predicate - let findIndex predicate list = - check list - let len = list.Length - let rec loop i = - if i >= len then indexNotFound () - elif predicate list.[i] then i - else loop (i + 1) - loop 0 + let findIndex (predicate : 'T -> bool) (list : FlatList<'T>) = + list.Select (fun item i -> struct (item, i)) + |> Seq.where (fun struct (item, i) -> predicate item) + |> Seq.map (fun struct (item, i) -> i) + |> Seq.head - /// Returns the index of the first element in the that satisfies the given predicate, or None if no such element exists + /// Returns the index of the first element in the that satisfies the given predicate, or ValueNone if no such element exists /// The function to test the input elements /// The input - /// The index of the first element that satisfies the predicate, or None - let tryFindIndex predicate list = - check list - let len = list.Length - let rec loop i = - if i >= len then None - elif predicate list.[i] then Some i - else loop (i + 1) - loop 0 + /// The index of the first element that satisfies the predicate, or ValueNone + let tryFindIndex (predicate : 'T -> bool) (list : FlatList<'T>) : int voption = + list.Select (fun item i -> struct (item, i)) + |> Seq.where (fun struct (item, i) -> predicate item) + |> Seq.map (fun struct (item, i) -> i) + |> Seq.vtryHead /// Returns a new containing elements corresponding to a sliding window of elements from the input /// The size of the window /// The input /// The resulting of sliding windows /// Thrown when windowSize is not positive or when is default - let windowed windowSize list = - check list - if windowSize <= 0 then + /// + /// + /// let numbers = FlatList.ofArray [|1; 2; 3; 4; 5|] + /// let windows = FlatList.windowed 3 numbers + /// // windows is [|[|1; 2; 3|]; [|2; 3; 4|]; [|3; 4; 5|]|] + /// + /// // Calculate moving averages + /// let movingAverages = + /// windows + /// |> FlatList.map (fun window -> + /// FlatList.average window) + /// // movingAverages is [|2.0; 3.0; 4.0|] + /// + /// + let windowed windowSize (list : FlatList<'T>) = + if windowSize < 1 then invalidArg (nameof windowSize) ErrorStrings.InputMustBeNonNegative - - let len = list.Length + let len = list.Length // Will throw if list is default if windowSize > len then empty else - let res = builderWith (len - windowSize + 1) - for i = 0 to len - windowSize do - let window = builderWith windowSize - for j = 0 to windowSize - 1 do - window.Add list.[i + j] - res.Add (ofBuilder window) - moveFromBuilder res + Enumerable + .Range(0, len - windowSize + 1) + .Select(fun i -> list.Slice (i, windowSize)) // list.Slice throws if list is default (already caught by len) + .ToImmutableArray () /// Returns a new containing pairs of adjacent elements from the input /// The input /// The resulting of pairs - let pairwise list = - check list - let len = list.Length - if len < 2 then - empty + let pairwise (list : FlatList<'T>) = + if list.Length < 2 then + empty // list.Length throws if default else - let res = builderWith (len - 1) - for i = 0 to len - 2 do - res.Add (list.[i], list.[i + 1]) - moveFromBuilder res + Enumerable.Zip(list, list.Skip (1), fun first second -> (first, second)).ToImmutableArray () /// Splits the into chunks of size at most 'chunkSize' /// The maximum size of each chunk /// The input /// The split into chunks /// Thrown when chunkSize is not positive or when is default - let chunkBySize chunkSize list = - check list + let chunkBySize chunkSize (list : FlatList<'T>) = if chunkSize <= 0 then invalidArg (nameof chunkSize) ErrorStrings.InputMustBeNonNegative + let len = list.Length // Will throw if list is default + if len = 0 then + empty + else + let numChunks = (len + chunkSize - 1) / chunkSize + Enumerable + .Range(0, numChunks) + .Select(fun i -> + let start = i * chunkSize + let count = min chunkSize (len - start) + list.Slice (start, count) + ) // list.Slice throws if list is default (already caught by len) + .ToImmutableArray () + + /// Splits the input array into at most count chunks. + /// The maximum number of chunks. + /// The input array. + /// The array split into chunks. + /// Thrown when count is not positive. + let splitInto (count : int) (list : FlatList<'T>) : FlatList> = + if count <= 0 then + invalidArg (nameof count) ErrorStrings.InputMustBeNonNegative let len = list.Length if len = 0 then empty else - let chunkCount = (len + chunkSize - 1) / chunkSize - let res = builderWith chunkCount - - for i = 0 to chunkCount - 1 do - let startIndex = i * chunkSize - let size = min chunkSize (len - startIndex) - let chunk = builderWith size - - for j = 0 to size - 1 do - chunk.Add list.[startIndex + j] - - res.Add (ofBuilder chunk) - - moveFromBuilder res + let chunkSize = (len + count - 1) / count // ceil(len / count) + chunkBySize chunkSize list /// Returns a new that contains the elements of the original but with duplicates removed /// The input /// The with distinct elements - let distinct list = - check list - let len = list.Length - let res = builderWith len - let set = System.Collections.Generic.HashSet<'T> (HashIdentity.Structural) - - for i = 0 to len - 1 do - let item = list.[i] - if set.Add item then - res.Add item - - ofBuilder res + let distinct (list : FlatList<'T>) = + let builder = ImmutableHashSet.CreateBuilder<'T> () + for i = 0 to list.Length - 1 do + let item = list.[i] // list.[i] will throw if list is default + builder.Add (item) |> ignore + builder.ToImmutableArray () /// Returns a new that contains all pairwise combinations of elements from the first and second s /// The first input /// The second input /// The of all pairwise combinations - let allPairs xs ys = - check xs - check ys - let lenXs = xs.Length - let lenYs = ys.Length - let res = builderWith (lenXs * lenYs) - - for i = 0 to lenXs - 1 do - for j = 0 to lenYs - 1 do - res.Add (xs.[i], ys.[j]) - - moveFromBuilder res + let allPairs (xs : FlatList<'T>) (ys : FlatList<'U>) = xs.SelectMany(fun x -> ys.Select (fun y -> (x, y))).ToImmutableArray () /// Returns a new with the elements permuted according to the specified permutation /// The function that maps input indices to output indices /// The input /// The permuted /// Thrown when the permutation function returns an out-of-range index - let permute indexMap list = - check list - let len = list.Length - let res = builderWith len - let permuted = Array.zeroCreate len - + let permute indexMap (list : FlatList<'T>) = + let len = list.Length // Will throw if list is default + let permutedArray = Array.zeroCreate<'T> len for i = 0 to len - 1 do let j = indexMap i if j < 0 || j >= len then invalidArg (nameof indexMap) "Invalid permutation" - permuted.[j] <- list.[i] - - for i = 0 to len - 1 do - res.Add permuted.[i] - - moveFromBuilder res + permutedArray.[j] <- list.[i] // list.[i] will throw if list is default (already caught by len) + FlatListFactory.CreateRange permutedArray /// Combines the two s into a of pairs. The two s must have equal lengths /// The first input /// The second input /// The of pairs /// Thrown when the s have different lengths - let zip list1 list2 = - checkNotDefault (nameof list1) list1 - checkNotDefault (nameof list2) list2 - - let len1 = list1.Length + let zip (list1 : FlatList<'T>) (list2 : FlatList<'U>) = + let len1 = list1.Length // .Length throws if default if len1 <> list2.Length then invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths - - let res = builderWith len1 - for i = 0 to len1 - 1 do - res.Add (list1.[i], list2.[i]) - - moveFromBuilder res + Enumerable + .Range(0, len1) + .Select(fun i -> (list1.[i], list2.[i])) // .[i] throws if default (caught by .Length) + .ToImmutableArray () /// Combines the three s into a of triples. The three s must have equal lengths /// The first input @@ -1081,101 +948,87 @@ module FlatList = /// The third input /// The of triples /// Thrown when the s have different lengths - let zip3 list1 list2 list3 = - checkNotDefault (nameof list1) list1 - checkNotDefault (nameof list2) list2 - checkNotDefault (nameof list3) list3 - - let len1 = list1.Length + let zip3 (list1 : FlatList<'T>) (list2 : FlatList<'U>) (list3 : FlatList<'V>) = + let len1 = list1.Length // .Length throws if default if len1 <> list2.Length then invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths if len1 <> list3.Length then invalidArg (nameof list3) ErrorStrings.ListsHaveDifferentLengths - - let res = builderWith len1 - for i = 0 to len1 - 1 do - res.Add (list1.[i], list2.[i], list3.[i]) - - moveFromBuilder res + Enumerable + .Range(0, len1) + .Select(fun i -> (list1.[i], list2.[i], list3.[i])) // .[i] throws if default (caught by .Length) + .ToImmutableArray () /// Splits a of pairs into two s /// The input /// The two s unzipped from the input - let unzip list = - check list - let len = list.Length - let res1 = builderWith len - let res2 = builderWith len - - for i = 0 to len - 1 do - let x, y = list.[i] - res1.Add x - res2.Add y - - ofBuilder res1, ofBuilder res2 + let unzip (list : FlatList<'T * 'U>) = + if list.IsEmpty then + (empty, empty) // IsEmpty is safe for default (true) + else + (list.Select(fst).ToImmutableArray (), list.Select(snd).ToImmutableArray ()) /// Splits a of triples into three s /// The input /// The three s unzipped from the input - let unzip3 list = - check list - let len = list.Length - let res1 = builderWith len - let res2 = builderWith len - let res3 = builderWith len - - for i = 0 to len - 1 do - let x, y, z = list.[i] - res1.Add x - res2.Add y - res3.Add z - - ofBuilder res1, ofBuilder res2, ofBuilder res3 + let unzip3 (list : FlatList<'T * 'U * 'V>) = + if list.IsEmpty then + (empty, empty, empty) // IsEmpty is safe for default (true) + else + let res1 = list.Select(fun (x, _, _) -> x).ToImmutableArray () + let res2 = list.Select(fun (_, y, _) -> y).ToImmutableArray () + let res3 = list.Select(fun (_, _, z) -> z).ToImmutableArray () + (res1, res2, res3) /// Returns the average of the elements in the /// The input /// The average of the elements /// Thrown when the is empty - let average list = - check list - let len = list.Length - if len = 0 then - invalidArg (nameof list) "The input list was empty." - - let mutable sum = 0.0 - for i = 0 to len - 1 do - sum <- sum + float list.[i] - - sum / float len + let inline average<'T + when 'T : (static member (+) : 'T * 'T -> 'T) + and 'T : (static member DivideByInt : 'T * int -> 'T) + and 'T : (static member Zero : 'T)> + (list : FlatList<'T>) + = + if list.Length = 0 then + invalidArg (nameof list) LanguagePrimitives.ErrorStrings.InputArrayEmptyString + let sum = list.Aggregate ('T.Zero, fun acc x -> Checked.(+) acc x) // Average will throw if default + 'T.DivideByInt (sum, list.Length) /// Returns the average of the results of applying the function to each element of the /// The function to transform the elements before averaging /// The input /// The average of the projected elements /// Thrown when the is empty - let averageBy projection list = - check list - let len = list.Length - if len = 0 then - invalidArg (nameof list) "The input list was empty." - - let mutable sum = 0.0 - for i = 0 to len - 1 do - sum <- sum + (float (projection list.[i])) + let inline averageBy<'T, 'U + when 'U : (static member (+) : 'U * 'U -> 'U) + and 'U : (static member DivideByInt : 'U * int -> 'U) + and 'U : (static member Zero : 'U)> + (projection : 'T -> 'U) + (list : FlatList<'T>) + = + let sum = list.Aggregate ('U.Zero, fun acc x -> Checked.(+) acc (projection x)) // AverageBy will throw if default + 'U.DivideByInt (sum, list.Length) - sum / float len /// Applies a function to each element of the , threading an accumulator argument through the computation /// The function to update the state given the input elements /// The initial state /// The input /// The final state - let fold<'T, 'State> folder (state : 'State) (list : FlatList<'T>) = - check list - let mutable state = state - for i = 0 to list.Length - 1 do - state <- folder state list.[i] - state + /// + /// + /// let numbers = FlatList.ofArray [|1; 2; 3; 4; 5|] + /// let sum = FlatList.fold (fun acc x -> acc + x) 0 numbers + /// // sum is 15 + /// + /// // Computing the average + /// let count = FlatList.length numbers + /// let total = FlatList.fold (fun sum x -> sum + x) 0 numbers + /// let avg = float total / float count + /// + /// + let fold<'T, 'State> (folder : 'State -> 'T -> 'State) (state : 'State) (list : FlatList<'T>) = list.Aggregate (state, folder) // Aggregate will throw if default /// Applies a function to corresponding elements of two s, threading an accumulator argument through the computation /// The function to update the state given the input elements from both s @@ -1184,31 +1037,28 @@ module FlatList = /// The second input /// The final state /// Thrown when the s have different lengths - let fold2<'T1, 'T2, 'State> folder (state : 'State) (list1 : FlatList<'T1>) (list2 : FlatList<'T2>) = - checkNotDefault (nameof list1) list1 - checkNotDefault (nameof list2) list2 - let f = OptimizedClosures.FSharpFunc<_, _, _, _>.Adapt (folder) - let len1 = list1.Length - + let fold2<'T1, 'T2, 'State> + (folder : 'State -> 'T1 -> 'T2 -> 'State) + (state : 'State) + (list1 : FlatList<'T1>) + (list2 : FlatList<'T2>) + = + let len1 = list1.Length // .Length throws if default if len1 <> list2.Length then invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths - - let mutable state = state + let mutable acc = state for i = 0 to len1 - 1 do - state <- f.Invoke (state, list1.[i], list2.[i]) - state + acc <- folder acc list1.[i] list2.[i] // .[i] throws if default (caught by .Length) + acc /// Applies a function to each element of the , threading an accumulator argument through the computation, starting from the end /// The function to update the state given the input elements, starting from the end /// The input /// The initial state /// The final state - let foldBack<'T, 'State> folder (list : FlatList<'T>) (state : 'State) = - check list - let mutable state = state - for i = list.Length - 1 downto 0 do - state <- folder list.[i] state - state + let foldBack<'T, 'State> (folder : 'State -> 'T -> 'State) (list : FlatList<'T>) (state : 'State) = + seq { for i = list.Length - 1 downto 0 do yield list.[i] } + |> _.Aggregate(state, folder) /// Applies a function to corresponding elements of two s, threading an accumulator argument through the computation, starting from the end /// The function to update the state given the input elements from both s, starting from the end @@ -1217,56 +1067,44 @@ module FlatList = /// The initial state /// The final state /// Thrown when the s have different lengths - let foldBack2<'T1, 'T2, 'State> folder (list1 : FlatList<'T1>) (list2 : FlatList<'T2>) (state : 'State) = - checkNotDefault (nameof list1) list1 - checkNotDefault (nameof list2) list2 - let f = OptimizedClosures.FSharpFunc<_, _, _, _>.Adapt (folder) - let len1 = list1.Length - + let foldBack2<'T1, 'T2, 'State> + (folder : 'T1 -> 'T2 -> 'State -> 'State) + (list1 : FlatList<'T1>) + (list2 : FlatList<'T2>) + (state : 'State) + = + let len1 = list1.Length // .Length throws if default if len1 <> list2.Length then invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths - - let mutable state = state + let mutable acc = state for i = len1 - 1 downto 0 do - state <- f.Invoke (list1.[i], list2.[i], state) - state + acc <- folder list1.[i] list2.[i] acc // .[i] throws if default (caught by .Length) + acc /// Applies a function to each element of the , threading an accumulator argument through the computation. /// This function takes the second argument, and applies the function to it and the first element of the . /// Then, it passes this result into the function along with the second element, and so on. /// Finally, it returns the final result. If the is empty, an exception is raised. - /// The function to reduce the with + /// The function to reduce the with /// The input /// The final accumulated value /// Thrown when the is empty - let reduce operation list = - check list - if list.Length = 0 then - invalidArg (nameof list) "The input list was empty." - - let mutable state = list.[0] - for i = 1 to list.Length - 1 do - state <- operation state list.[i] - state + let reduce (reduction : 'T -> 'T -> 'T) (list : FlatList<'T>) = list.Aggregate (reduction) // Aggregate will throw if default (via GetEnumerator) /// Applies a function to each element of the , threading an accumulator argument through the computation, starting from the end. /// This function takes the last element of the and the second-to-last element, and applies the function to them. /// Then, it passes this result into the function along with the third-to-last element, and so on. /// Finally, it returns the final result. If the is empty, an exception is raised. - /// The function to reduce the with, starting from the end + /// The function to reduce the with, starting from the end /// The input /// The final accumulated value /// Thrown when the is empty - let reduceBack operation list = - check list - if list.Length = 0 then - invalidArg (nameof list) "The input list was empty." - - let len = list.Length - let mutable state = list.[len - 1] - for i = len - 2 downto 0 do - state <- operation list.[i] state - state + let reduceBack (reduction : 'T -> 'T -> 'T) (list : FlatList<'T>) = + (seq { + for i = list.Length - 1 downto 0 do + list[i] + }) + .Aggregate (reduction) /// Like fold, but returns both the intermediate and final results /// The function to update the state given the input elements @@ -1274,17 +1112,13 @@ module FlatList = /// The input /// The of all intermediate and final states let scan<'T, 'State> folder (state : 'State) (list : FlatList<'T>) = - check list - let len = list.Length - let res = builderWith (len + 1) - res.Add state - - let mutable state = state - for i = 0 to len - 1 do - state <- folder state list.[i] - res.Add state - - moveFromBuilder res + let builder = FlatListFactory.CreateBuilder<'State> (list.Length + 1) // list.Length throws if default + builder.Add state + let mutable currentState = state + for item in list do // Iteration throws if default (caught by list.Length) + currentState <- folder currentState item + builder.Add currentState + builder.MoveToImmutable () /// Like foldBack, but returns both the intermediate and final results /// The function to update the state given the input elements, starting from the end @@ -1292,65 +1126,236 @@ module FlatList = /// The initial state /// The of all intermediate and final states, in reverse order of computation let scanBack<'T, 'State> folder (list : FlatList<'T>) (state : 'State) = - check list - let len = list.Length - let res = builderWith (len + 1) - let mutable states = Array.zeroCreate (len + 1) - states.[len] <- state - - let mutable state = state + let len = list.Length // list.Length throws if default + let results = Array.zeroCreate<'State> (len + 1) + results.[len] <- state + let mutable currentState = state for i = len - 1 downto 0 do - state <- folder list.[i] state - states.[i] <- state - - for i = 0 to len do - res.Add states.[i] - - moveFromBuilder res - - /// Tries to reduce the using the given function, returning ValueNone if empty. - let tryReduce reduction (list: FlatList<'T>) : voption<'T> = - check list - if list.Length = 0 then ValueNone - else - let mutable state = list.[0] - for i = 1 to list.Length - 1 do - state <- reduction state list.[i] - ValueSome state - - /// Tries to reduce the from the end using the given function, returning ValueNone if empty. - let tryReduceBack reduction (list: FlatList<'T>) : voption<'T> = - check list - let len = list.Length - if len = 0 then ValueNone + currentState <- folder list.[i] currentState // list.[i] throws if default (caught by list.Length) + results.[i] <- currentState + FlatListFactory.CreateRange results + + /// Returns the only element of the array. + /// The input array. + /// The only element of the array. + /// Thrown when the input does not have precisely one element. + let exactlyOne (list : FlatList<'T>) = list.Single () + + /// Returns the only element of the array or None if the array is empty or contains more than one element. + /// The input array. + /// The only element of the array or None. + let tryExactlyOne (list : FlatList<'T>) : 'T voption = + if list.IsDefaultOrEmpty || list.Length <> 1 then + ValueNone else - let mutable state = list.[len - 1] - for i = len - 2 downto 0 do - state <- reduction list.[i] state - ValueSome state - - /// Tries to compute the average of the , returning ValueNone if empty. - let tryAverage (list: FlatList<'T>) : voption = - check list - let len = list.Length - if len = 0 then ValueNone + ValueSome list.[0] + + /// Returns a new list with the distinct elements of the input array which do not appear in the itemsToExclude sequence + /// A sequence whose elements that also occur in the input array will cause those elements to be removed + /// The input array + /// A new array that contains the distinct elements of list that do not appear in itemsToExclude + let except (itemsToExclude : 'T seq) (list : FlatList<'T>) : FlatList<'T> = + let excludeSet = HashSet (itemsToExclude) + filter (fun x -> not (excludeSet.Contains x)) list + + /// Returns the sum of the elements in the array. + /// The input array. + /// The resulting sum. + /// Thrown when the input array is null. + let inline sum (list : FlatList< ^T >) : ^T when ^T : (static member (+) : ^T * ^T -> ^T) and ^T : (static member Zero : ^T) = + list.Aggregate (LanguagePrimitives.GenericZero< ^T>, fun acc x -> acc + x) + + /// Returns the sum of the results generated by applying the function to each element of the array. + /// The function to transform the array elements into the type to be summed. + /// The input array. + /// The resulting sum. + let inline sumBy + (projection : 'T -> 'U) + (list : FlatList<'T>) + : 'U when 'U : (static member (+) : 'U * 'U -> 'U) and 'U : (static member Zero : 'U) = + list.Aggregate (LanguagePrimitives.GenericZero<'U>, fun acc x -> acc + (projection x)) + + /// Returns the transpose of the given sequence of arrays. + /// The input sequence of arrays. + /// The transposed array. + /// Thrown when the input arrays differ in length. + let transpose (lists : FlatList>) : FlatList> = + if lists.IsDefaultOrEmpty then + empty else - let mutable sum = 0.0 - for i = 0 to len - 1 do - sum <- sum + float list.[i] - ValueSome (sum / float len) - - /// Tries to compute the average of the projected values of the , returning ValueNone if empty. - let tryAverageBy projection (list: FlatList<'T>) : voption = - check list - let len = list.Length - if len = 0 then ValueNone + let len0 = lists.[0].Length + // Verify all inner arrays have the same length + for i = 1 to lists.Length - 1 do + if lists.[i].Length <> len0 then + invalidArg (nameof lists) "All inner arrays must have the same length." + + Enumerable + .Range(0, len0) + .Select(fun j -> Enumerable.Range(0, lists.Length).Select(fun i -> lists.[i].[j]).ToImmutableArray ()) + .ToImmutableArray () + + /// Builds a list from the given array. + /// The input array. + /// The list of array elements. + let toList (list : FlatList<'T>) : 'T list = + if list.IsDefaultOrEmpty then + [] else - let mutable sum = 0.0 - for i = 0 to len - 1 do - sum <- sum + float (projection list.[i]) - ValueSome (sum / float len) + seq { + for i = 0 to list.Length - 1 do + yield list.[i] + } + |> Seq.toList + + /// Builds a from a list. + /// The input list. + /// A containing the elements from the list. + let ofList (list : 'T list) : FlatList<'T> = + let builder = FlatListFactory.CreateBuilder () + list |> List.iter (fun item -> builder.Add item) + moveFromBuilder builder + + /// Return a new array with the item at a given index set to the new value. + /// The index of the item to be replaced. + /// The new value. + /// The input array. + /// The result array. + /// Thrown when index is outside 0..source.Length - 1 + let updateAt (index : int) (value : 'T) (list : FlatList<'T>) : FlatList<'T> = list.SetItem (index, value) + + /// Return a new array with the item at a given index removed. + /// The index of the item to be removed. + /// The input array. + /// The result array. + /// Thrown when index is outside 0..source.Length - 1 + let removeAt (index : int) (list : FlatList<'T>) : FlatList<'T> = list.RemoveAt (index) + + /// Return a new array with a new item inserted before the given index. + /// The index where the item should be inserted. + /// The value to insert. + /// The input array. + /// The result array. + /// Thrown when index is below 0 or greater than source.Length. + let insertAt (index : int) (value : 'T) (list : FlatList<'T>) : FlatList<'T> = list.Insert (index, value) + + /// Return a new array with new items inserted before the given index. + /// The index where the items should be inserted. + /// The values to insert. + /// The input array. + /// The result array. + /// Thrown when index is below 0 or greater than source.Length. + let insertManyAt (index : int) (values : 'T seq) (list : FlatList<'T>) : FlatList<'T> = list.InsertRange (index, values) + + /// Builds a new array that contains the elements of the given array. + /// The input array. + /// A copy of the input array. + let copy (list : FlatList<'T>) : FlatList<'T> = list.ToImmutableArray () + + /// Returns an array that contains no more than N elements in a new array. + /// A function that takes in the current state and returns an option tuple of the next + /// element of the array and the next state value. + /// The initial state value. + /// The result array. + let unfold<'T, 'State> (generator : 'State -> ('T * 'State) voption) (state : 'State) : FlatList<'T> = + let builder = builder () + let rec loop state = + match generator state with + | ValueSome (item, newState) -> + builder.Add item + loop newState + | ValueNone -> () + + loop state + moveFromBuilder builder + + /// Compares two arrays using the given comparison function, element by element. + /// A function that takes an element from each array and returns an int. + /// If it evaluates to a non-zero value iteration is stopped and that value is returned. + /// The first input array. + /// The second input array. + /// Returns the first non-zero result from the comparison function. If the first array has + /// a larger element, the return value is always positive. If the second array has a larger + /// element, the return value is always negative. When the elements are equal in the two + /// arrays, 1 is returned if the first array is longer, 0 is returned if they are equal in + /// length, and -1 is returned when the second array is longer. + let inline compareWith (comparer : 'T -> 'T -> int) (array1 : FlatList<'T>) (array2 : FlatList<'T>) : int = + let mutable result = 0 + let mutable i = 0 + let len1 = array1.Length + let len2 = array2.Length + let minLength = min len1 len2 + + while i < minLength && result = 0 do + result <- comparer array1.[i] array2.[i] + i <- i + 1 -////////// + if result <> 0 then result + elif len1 > len2 then 1 + elif len1 < len2 then -1 + else 0 -module ImmutableArray = FlatList + /// Returns the greatest of all elements of the array, compared via Operators.max. + /// The input array. + /// Thrown when the array is empty. + /// The maximum element. + let inline max (list : FlatList<'T>) : 'T when 'T : comparison = + checkNotDefault (nameof list) list + let mutable acc = list.[0] + + for i = 1 to list.Length - 1 do + let curr = list.[i] + + if curr > acc then + acc <- curr + + acc + + /// Returns the greatest of all elements of the array, compared via Operators.max on the function result. + /// The function to transform the elements into a type supporting comparison. + /// The input array. + /// Thrown when the array is empty. + /// The maximum element. + let inline maxBy (projection : 'T -> 'Key) (list : FlatList<'T>) : 'T when 'Key : comparison = + checkNotDefault (nameof list) list + let mutable maxVal = list.[0] + let mutable maxKey = projection maxVal + for i = 1 to list.Length - 1 do + let currVal = list.[i] + let currKey = projection currVal + if currKey > maxKey then + maxVal <- currVal + maxKey <- currKey + maxVal + + /// Returns the smallest of all elements of the array, compared via Operators.min. + /// The input array. + /// Thrown when the array is empty. + /// The minimum element. + let inline min (list : FlatList<'T>) : 'T when 'T : comparison = + checkNotDefault (nameof list) list + let mutable acc = list.[0] + + for i = 1 to list.Length - 1 do + let curr = list.[i] + + if curr < acc then + acc <- curr + + acc + + /// Returns the smallest of all elements of the array, compared via Operators.min on the function result. + /// The function to transform the elements into a type supporting comparison. + /// The input array. + /// Thrown when the array is empty. + /// The minimum element. + let inline minBy (projection : 'T -> 'Key) (list : FlatList<'T>) : 'T when 'Key : comparison = + checkNotDefault (nameof list) list + let mutable minVal = list.[0] + let mutable minKey = projection minVal + for i = 1 to list.Length - 1 do + let currVal = list.[i] + let currKey = projection currVal + if currKey < minKey then + minVal <- currVal + minKey <- currKey + minVal diff --git a/src/FSharp.Collections.Immutable/Helper.fs b/src/FSharp.Collections.Immutable/Helper.fs new file mode 100644 index 0000000..1777e1a --- /dev/null +++ b/src/FSharp.Collections.Immutable/Helper.fs @@ -0,0 +1,55 @@ +#if INTERACTIVE +namespace global +#else +namespace FSharp.Collections.Immutable +#endif + +[] +module internal ImmutableCollectionUtil = + + let inline checkNotNull name (arg : _ | null) = + match arg with + | null -> nullArg name + | _ -> () + +module internal ErrorStrings = + [] + let InputMustBeNonNegative = "The input must be non-negative." + + [] + let ListsHaveDifferentLengths = "The lists have different lengths." + +[] +module internal ValueOption = + + module internal Seq = + + let vtryHead (source : 'T seq) = + use enumerator = source.GetEnumerator () + if not (enumerator.MoveNext ()) then + ValueNone + else if obj.ReferenceEquals (enumerator.Current, null) then + ValueNone + else + ValueSome enumerator.Current + + let vtryLast (source : 'T seq) = + use enumerator = source.GetEnumerator () + if not (enumerator.MoveNext ()) then + ValueNone + else + let mutable last = enumerator.Current + while enumerator.MoveNext () do + last <- enumerator.Current + if obj.ReferenceEquals (enumerator.Current, null) then + ValueNone + else + ValueSome last + + let vchoose mapping (source : 'T seq) = + source + |> Seq.map mapping + |> Seq.where ValueOption.isSome + |> Seq.map ValueOption.get + + let vtryFind predicate (source : 'T seq) = source |> Seq.where predicate |> vtryHead diff --git a/src/FSharp.Collections.Immutable/ImmutableCollectionUtil.fs b/src/FSharp.Collections.Immutable/ImmutableCollectionUtil.fs deleted file mode 100644 index 608fe4f..0000000 --- a/src/FSharp.Collections.Immutable/ImmutableCollectionUtil.fs +++ /dev/null @@ -1,19 +0,0 @@ -#if INTERACTIVE -namespace global -#else -namespace FSharp.Collections.Immutable -#endif - -[] -module internal ImmutableCollectionUtil = - let inline checkNotNull name (arg : _ | null) = - match arg with - | null -> nullArg name - | _ -> () - -module internal ErrorStrings = - [] - let InputMustBeNonNegative = "The input must be non-negative." - - [] - let ListsHaveDifferentLengths = "The lists have different lengths." diff --git a/tests/FSharp.Collections.Immutable.Tests/FSharp.Collections.Immutable.Tests.fsproj b/tests/FSharp.Collections.Immutable.Tests/FSharp.Collections.Immutable.Tests.fsproj index 89d17e3..eaa1313 100644 --- a/tests/FSharp.Collections.Immutable.Tests/FSharp.Collections.Immutable.Tests.fsproj +++ b/tests/FSharp.Collections.Immutable.Tests/FSharp.Collections.Immutable.Tests.fsproj @@ -9,7 +9,20 @@ - + + + + + + + + + + + + + + diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList.fs deleted file mode 100644 index 351bac9..0000000 --- a/tests/FSharp.Collections.Immutable.Tests/FlatList.fs +++ /dev/null @@ -1,10 +0,0 @@ -namespace FSharp.Collections.Immutable.Tests - -open System -open Microsoft.VisualStudio.TestTools.UnitTesting - -[] -type FlatListTests () = - - [] - member this.TestMethodPassing () = Assert.IsTrue (true) diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/BasicOperationsTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/BasicOperationsTests.fs new file mode 100644 index 0000000..b6db069 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/BasicOperationsTests.fs @@ -0,0 +1,137 @@ +namespace FSharp.Collections.Immutable.Tests.FlatList + +open System +open System.Collections.Immutable +open System.Collections.Generic +open Microsoft.VisualStudio.TestTools.UnitTesting +open FSharp.Collections.Immutable + +[] +type BasicOperationsTests () = + + [] + member _.``isEmpty returns true for empty FlatList`` () = + Assert.IsTrue (FlatList.isEmpty FlatList.empty) + Assert.IsFalse (FlatList.isEmpty (FlatList.singleton 1)) + + [] + member _.``isDefault returns true for default FlatList`` () = + let defaultList = Unchecked.defaultof> + Assert.IsTrue (FlatList.isDefault defaultList) + Assert.IsFalse (FlatList.isDefault FlatList.empty) + + [] + member _.``isDefaultOrEmpty returns true for default or empty FlatList`` () = + let defaultList = Unchecked.defaultof> + Assert.IsTrue (FlatList.isDefaultOrEmpty defaultList) + Assert.IsTrue (FlatList.isDefaultOrEmpty FlatList.empty) + Assert.IsFalse (FlatList.isDefaultOrEmpty (FlatList.singleton 1)) + + [] + member _.``length returns number of elements`` () = + Assert.AreEqual (0, FlatList.length FlatList.empty) + Assert.AreEqual (3, FlatList.length (FlatList.ofArray [| 1; 2; 3 |])) + + [] + member _.``item returns element at index`` () = + let flatList = FlatList.ofArray [| 10; 20; 30 |] + + Assert.AreEqual (10, FlatList.item 0 flatList) + Assert.AreEqual (20, FlatList.item 1 flatList) + Assert.AreEqual (30, FlatList.item 2 flatList) + + [] + [)>] + member _.``item throws for out of range index`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + FlatList.item 3 flatList |> ignore + + [] + member _.``tryItem returns element or ValueNone`` () = + let flatList = FlatList.ofArray [| 10; 20; 30 |] + + Assert.AreEqual (ValueSome 10, FlatList.tryItem 0 flatList) + Assert.AreEqual (ValueSome 20, FlatList.tryItem 1 flatList) + Assert.AreEqual (ValueNone, FlatList.tryItem 3 flatList) + Assert.AreEqual (ValueNone, FlatList.tryItem -1 flatList) + Assert.AreEqual (ValueNone, FlatList.tryItem 0 (Unchecked.defaultof>)) + + [] + member _.``head returns first element`` () = + let flatList = FlatList.ofArray [| 10; 20; 30 |] + Assert.AreEqual (10, FlatList.head flatList) + + [] + [)>] + member _.``head throws for empty list`` () = FlatList.head FlatList.empty |> ignore + + [] + member _.``tryHead returns first element or ValueNone`` () = + let flatList = FlatList.ofArray [| 10; 20; 30 |] + Assert.AreEqual (ValueSome 10, FlatList.tryHead flatList) + Assert.AreEqual (ValueNone, FlatList.tryHead FlatList.empty) + + [] + member _.``tail returns all but first element`` () = + let flatList = FlatList.ofArray [| 10; 20; 30 |] + let result = FlatList.tail flatList + + Assert.AreEqual (2, result.Length) + Assert.AreEqual (20, result.[0]) + Assert.AreEqual (30, result.[1]) + + [] + [)>] + member _.``tail throws for empty list`` () = FlatList.tail FlatList.empty |> ignore + + [] + member _.``tryTail returns tail or ValueNone`` () = + let flatList = FlatList.ofArray [| 10; 20; 30 |] + + match FlatList.tryTail flatList with + | ValueSome tail -> + Assert.AreEqual (2, tail.Length) + Assert.AreEqual (20, tail.[0]) + Assert.AreEqual (30, tail.[1]) + | ValueNone -> Assert.Fail ("Should be ValueSome") + + Assert.AreEqual voption> (ValueNone, FlatList.tryTail FlatList.empty) + + [] + member _.``last returns last element`` () = + let flatList = FlatList.ofArray [| 10; 20; 30 |] + Assert.AreEqual (30, FlatList.last flatList) + + [] + [)>] + member _.``last throws for empty list`` () = FlatList.last FlatList.empty |> ignore + + [] + member _.``tryLast returns last element or ValueNone`` () = + let flatList = FlatList.ofArray [| 10; 20; 30 |] + Assert.AreEqual (ValueSome 30, FlatList.tryLast flatList) + Assert.AreEqual (ValueNone, FlatList.tryLast FlatList.empty) + + [] + member _.``exactlyOne returns the single element`` () = + let flatList = FlatList.singleton 42 + Assert.AreEqual (42, FlatList.exactlyOne flatList) + + [] + [)>] + member _.``exactlyOne throws for empty list`` () = FlatList.exactlyOne FlatList.empty |> ignore + + [] + [)>] + member _.``exactlyOne throws for list with multiple elements`` () = + FlatList.exactlyOne (FlatList.ofArray [| 1; 2 |]) |> ignore + + [] + member _.``tryExactlyOne returns the single element or ValueNone`` () = + let singletonList = FlatList.singleton 42 + let emptyList = FlatList.empty + let multipleList = FlatList.ofArray [| 1; 2 |] + + Assert.AreEqual (ValueSome 42, FlatList.tryExactlyOne singletonList) + Assert.AreEqual (ValueNone, FlatList.tryExactlyOne emptyList) + Assert.AreEqual (ValueNone, FlatList.tryExactlyOne multipleList) diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/BuilderTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/BuilderTests.fs new file mode 100644 index 0000000..2d4beeb --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/BuilderTests.fs @@ -0,0 +1,96 @@ +namespace FSharp.Collections.Immutable.Tests.FlatList + +open System +open System.Collections.Immutable +open System.Collections.Generic +open Microsoft.VisualStudio.TestTools.UnitTesting +open FSharp.Collections.Immutable + +[] +type BuilderTests () = + + [] + member _.``builder and ofBuilder create FlatList`` () = + let b = FlatList.builder () + b.Add (1) + b.Add (2) + b.Add (3) + + let flatList = FlatList.ofBuilder b + + Assert.AreEqual (3, flatList.Length) + Assert.AreEqual (1, flatList.[0]) + Assert.AreEqual (2, flatList.[1]) + Assert.AreEqual (3, flatList.[2]) + + [] + member _.``builderWith creates builder with capacity`` () = + let b = FlatList.builderWith 10 + for i = 1 to 10 do + b.Add (i) + + let flatList = FlatList.ofBuilder b + + Assert.AreEqual (10, flatList.Length) + for i = 0 to 9 do + Assert.AreEqual (i + 1, flatList.[i]) + + [] + member _.``moveFromBuilder builds FlatList and empties builder`` () = + let b = FlatList.builder () + b.Add (1) + b.Add (2) + b.Add (3) + + let flatList = FlatList.moveFromBuilder b + + Assert.AreEqual (3, flatList.Length) + Assert.AreEqual (0, b.Count) + + [] + member _.``toBuilder creates builder from FlatList`` () = + let original = FlatList.ofArray [| 1; 2; 3 |] + let builder = FlatList.toBuilder original + + Assert.AreEqual (original.Length, builder.Count) + for i = 0 to original.Length - 1 do + Assert.AreEqual (original.[i], builder.[i]) + + [] + member _.``Builder.add adds to builder`` () = + let b = FlatList.builder () + FlatList.Builder.add 42 b + + Assert.AreEqual (1, b.Count) + Assert.AreEqual (42, b.[0]) + + [] + member _.``build applies function to builder and returns FlatList`` () = + let addItems (builder : FlatList.Builder) = + builder.Add (1) + builder.Add (2) + builder.Add (3) + + let flatList = FlatList.build addItems + + Assert.AreEqual (3, flatList.Length) + Assert.AreEqual (1, flatList.[0]) + Assert.AreEqual (2, flatList.[1]) + Assert.AreEqual (3, flatList.[2]) + + [] + member _.``update applies function to builder from FlatList`` () = + let original = FlatList.ofArray [| 1; 2; 3 |] + + let addItems (builder : FlatList.Builder) = + builder.Add (4) + builder.Add (5) + + let result = FlatList.update addItems original + + Assert.AreEqual (5, result.Length) + Assert.AreEqual (1, result.[0]) + Assert.AreEqual (2, result.[1]) + Assert.AreEqual (3, result.[2]) + Assert.AreEqual (4, result.[3]) + Assert.AreEqual (5, result.[4]) diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/CreationTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/CreationTests.fs new file mode 100644 index 0000000..b2b6c98 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/CreationTests.fs @@ -0,0 +1,123 @@ +namespace FSharp.Collections.Immutable.Tests.FlatList + +open System +open System.Collections.Immutable +open System.Collections.Generic +open Microsoft.VisualStudio.TestTools.UnitTesting +open FSharp.Collections.Immutable + +[] +type CreationTests () = + + [] + member _.``empty returns empty FlatList`` () = + let empty = FlatList.empty + Assert.IsTrue (empty.IsEmpty) + Assert.AreEqual (0, empty.Length) + + [] + member _.``singleton creates FlatList with one element`` () = + let flatList = FlatList.singleton 42 + + Assert.AreEqual (1, flatList.Length) + Assert.AreEqual (42, flatList.[0]) + + [] + member _.``ofArray converts array to FlatList`` () = + let arr = [| 1; 2; 3 |] + let flatList = FlatList.ofArray arr + + Assert.AreEqual (arr.Length, flatList.Length) + for i = 0 to arr.Length - 1 do + Assert.AreEqual (arr.[i], flatList.[i]) + + [] + member _.``ofSeq converts sequence to FlatList`` () = + let seq = seq { + 1 + 2 + 3 + } + let flatList = FlatList.ofSeq seq + let expected = [| 1; 2; 3 |] + + Assert.AreEqual (expected.Length, flatList.Length) + for i = 0 to expected.Length - 1 do + Assert.AreEqual (expected.[i], flatList.[i]) + + [] + member _.``ofList converts list to FlatList`` () = + let list = [ 1; 2; 3 ] + let flatList = FlatList.ofList list + + Assert.AreEqual (list.Length, flatList.Length) + for i = 0 to list.Length - 1 do + Assert.AreEqual (list.[i], flatList.[i]) + + [] + member _.``init creates initialized FlatList`` () = + let flatList = FlatList.init 5 (fun i -> i * 2) + let expected = [| 0; 2; 4; 6; 8 |] + + Assert.AreEqual (expected.Length, flatList.Length) + for i = 0 to expected.Length - 1 do + Assert.AreEqual (expected.[i], flatList.[i]) + + [] + [)>] + member _.``init throws for negative count`` () = FlatList.init -1 id |> ignore + + [] + member _.``create makes FlatList with repeated values`` () = + let flatList = FlatList.create 3 "test" + + Assert.AreEqual (3, flatList.Length) + for i = 0 to flatList.Length - 1 do + Assert.AreEqual ("test", flatList.[i]) + + [] + member _.``replicate makes FlatList with repeated values`` () = + let flatList = FlatList.replicate 3 "test" + + Assert.AreEqual (3, flatList.Length) + for i = 0 to flatList.Length - 1 do + Assert.AreEqual ("test", flatList.[i]) + + [] + member _.``toSeq converts FlatList to sequence`` () = + let original = [| 1; 2; 3 |] + let flatList = FlatList.ofArray original + let seq = FlatList.toSeq flatList + + let result = Seq.toArray seq + CollectionAssert.AreEqual (original, result) + + [] + member _.``toArray converts FlatList to array`` () = + let original = [| 1; 2; 3 |] + let flatList = FlatList.ofArray original + let result = FlatList.toArray flatList + + CollectionAssert.AreEqual (original, result) + + [] + member _.``toList converts FlatList to list`` () = + let original = [| 1; 2; 3 |] + let flatList = FlatList.ofArray original + let result = FlatList.toList flatList + + Assert.AreEqual (original.Length, result.Length) + for i = 0 to original.Length - 1 do + Assert.AreEqual (original.[i], result.[i]) + + [] + member _.``copy makes a new FlatList with same elements`` () = + let original = FlatList.ofArray [| 1; 2; 3 |] + let copy = FlatList.copy original + + Assert.AreEqual (original.Length, copy.Length) + for i = 0 to original.Length - 1 do + Assert.AreEqual (original.[i], copy.[i]) + + // Verify copy is independent (this is true for immutable collections) + Assert.AreNotEqual (original.GetHashCode (), copy.GetHashCode ()) diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/FilterMapTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/FilterMapTests.fs new file mode 100644 index 0000000..9c16fbd --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/FilterMapTests.fs @@ -0,0 +1,149 @@ +namespace FSharp.Collections.Immutable.Tests.FlatList + +open System +open System.Collections.Immutable +open System.Collections.Generic +open Microsoft.VisualStudio.TestTools.UnitTesting +open FSharp.Collections.Immutable + +[] +type FilterMapTests () = + + [] + member _.``map transforms elements`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + let result = FlatList.map (fun x -> x * 2) flatList + + Assert.AreEqual (3, result.Length) + Assert.AreEqual (2, result.[0]) + Assert.AreEqual (4, result.[1]) + Assert.AreEqual (6, result.[2]) + + [] + member _.``mapi transforms elements with index`` () = + let flatList = FlatList.ofArray [| 10; 20; 30 |] + let result = FlatList.mapi (fun i x -> i + x) flatList + + Assert.AreEqual (3, result.Length) + Assert.AreEqual (10, result.[0]) // 0 + 10 + Assert.AreEqual (21, result.[1]) // 1 + 20 + Assert.AreEqual (32, result.[2]) // 2 + 30 + + [] + member _.``map2 transforms pairs of elements`` () = + let list1 = FlatList.ofArray [| 1; 2; 3 |] + let list2 = FlatList.ofArray [| 10; 20; 30 |] + + let result = FlatList.map2 (fun x y -> x * y) list1 list2 + + Assert.AreEqual (3, result.Length) + Assert.AreEqual (10, result.[0]) // 1 * 10 + Assert.AreEqual (40, result.[1]) // 2 * 20 + Assert.AreEqual (90, result.[2]) // 3 * 30 + + [] + [)>] + member _.``map2 throws when lists have different lengths`` () = + let list1 = FlatList.ofArray [| 1; 2 |] + let list2 = FlatList.ofArray [| 10; 20; 30 |] + + FlatList.map2 (fun x y -> x * y) list1 list2 |> ignore + + [] + member _.``mapi2 transforms pairs with index`` () = + let list1 = FlatList.ofArray [| 1; 2; 3 |] + let list2 = FlatList.ofArray [| 10; 20; 30 |] + + let result = FlatList.mapi2 (fun i x y -> i + x + y) list1 list2 + + Assert.AreEqual (3, result.Length) + Assert.AreEqual (11, result.[0]) // 0 + 1 + 10 + Assert.AreEqual (23, result.[1]) // 1 + 2 + 20 + Assert.AreEqual (35, result.[2]) // 2 + 3 + 30 + + [] + [)>] + member _.``mapi2 throws when lists have different lengths`` () = + let list1 = FlatList.ofArray [| 1; 2 |] + let list2 = FlatList.ofArray [| 10; 20; 30 |] + + FlatList.mapi2 (fun i x y -> i + x + y) list1 list2 + |> ignore + + [] + member _.``filter keeps elements matching predicate`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4; 5 |] + let result = FlatList.filter (fun x -> x % 2 = 0) flatList + + Assert.AreEqual (2, result.Length) + Assert.AreEqual (2, result.[0]) + Assert.AreEqual (4, result.[1]) + + [] + member _.``where is alias for filter`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4; 5 |] + let filtered = FlatList.filter (fun x -> x % 2 = 0) flatList + let wheered = FlatList.where (fun x -> x % 2 = 0) flatList + + Assert.AreEqual (filtered.Length, wheered.Length) + for i = 0 to filtered.Length - 1 do + Assert.AreEqual (filtered.[i], wheered.[i]) + + [] + member _.``choose selects and maps elements`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4; 5 |] + let result = FlatList.choose (fun x -> if x % 2 = 0 then ValueSome (x * 10) else ValueNone) flatList + + CollectionAssert.AreEqual ([| 20; 40 |], FlatList.toArray result) + + [] + member _.``collect maps and concatenates`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + let result = FlatList.collect (fun x -> [ x; x * 10 ]) flatList + + Assert.AreEqual (6, result.Length) + Assert.AreEqual (1, result.[0]) + Assert.AreEqual (10, result.[1]) + Assert.AreEqual (2, result.[2]) + Assert.AreEqual (20, result.[3]) + Assert.AreEqual (3, result.[4]) + Assert.AreEqual (30, result.[5]) + + [] + member _.``partition splits elements based on predicate`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4; 5 |] + let evens, odds = FlatList.partition (fun x -> x % 2 = 0) flatList + + CollectionAssert.AreEqual ([| 2; 4 |], FlatList.toArray evens) + CollectionAssert.AreEqual ([| 1; 3; 5 |], FlatList.toArray odds) + + [] + member _.``distinct removes duplicates`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 1; 2; 5 |] + let result = FlatList.distinct flatList + + Assert.AreEqual (4, result.Length) + Assert.IsTrue (FlatList.contains 1 result) + Assert.IsTrue (FlatList.contains 2 result) + Assert.IsTrue (FlatList.contains 3 result) + Assert.IsTrue (FlatList.contains 5 result) + + [] + member _.``distinctBy removes duplicates using projection`` () = + let flatList = FlatList.ofArray [| "apple"; "banana"; "apricot"; "berry" |] + let result = FlatList.distinctBy (fun (s : string) -> s.[0]) flatList + + Assert.AreEqual (2, result.Length) + // Only one item starting with 'a' and one with 'b' + Assert.IsTrue (result |> FlatList.exists (fun s -> s.[0] = 'a')) + Assert.IsTrue (result |> FlatList.exists (fun s -> s.[0] = 'b')) + + [] + member _.``indexed pairs elements with their indices`` () = + let flatList = FlatList.ofArray [| "a"; "b"; "c" |] + let result = FlatList.indexed flatList + + Assert.AreEqual (3, result.Length) + Assert.AreEqual (struct (0, "a"), result.[0]) + Assert.AreEqual (struct (1, "b"), result.[1]) + Assert.AreEqual (struct (2, "c"), result.[2]) diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTestBase.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTestBase.fs new file mode 100644 index 0000000..6fc3edc --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/FlatListTestBase.fs @@ -0,0 +1,16 @@ +namespace FSharp.Collections.Immutable.Tests.FlatList + +open System +open System.Collections.Immutable +open System.Collections.Generic +open Microsoft.VisualStudio.TestTools.UnitTesting +open FSharp.Collections.Immutable + +[] +module TestData = + let emptyIntList = FlatList.empty + let singletonIntList = FlatList.singleton 42 + let standardIntList = FlatList.ofArray [| 10; 20; 30; 40; 50 |] + let evenOddIntList = FlatList.ofArray [| 1; 2; 3; 4; 5; 6 |] + let repeatedIntList = FlatList.ofArray [| 1; 2; 3; 1; 2; 5 |] + let stringList = FlatList.ofArray [| "apple"; "banana"; "cherry"; "date" |] diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/FoldTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/FoldTests.fs new file mode 100644 index 0000000..8d3fbb8 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/FoldTests.fs @@ -0,0 +1,189 @@ +namespace FSharp.Collections.Immutable.Tests.FlatList + +open System +open System.Collections.Immutable +open System.Collections.Generic +open Microsoft.VisualStudio.TestTools.UnitTesting +open FSharp.Collections.Immutable + +[] +type FoldTests () = + + [] + member _.``fold accumulates values`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4 |] + let result = FlatList.fold (fun acc x -> acc + x) 0 flatList + + Assert.AreEqual (10, result) // 0 + 1 + 2 + 3 + 4 = 10 + + [] + member _.``fold with string concatenation works`` () = + let flatList = FlatList.ofArray [| "a"; "b"; "c" |] + let result = FlatList.fold (fun acc x -> acc + x) "" flatList + + Assert.AreEqual ("abc", result) + + [] + member _.``fold2 accumulates from two lists`` () = + let list1 = FlatList.ofArray [| 1; 2; 3 |] + let list2 = FlatList.ofArray [| 10; 20; 30 |] + + let result = FlatList.fold2 (fun acc x y -> acc + x * y) 0 list1 list2 + + Assert.AreEqual (140, result) // 0 + (1*10) + (2*20) + (3*30) = 140 + + [] + [)>] + member _.``fold2 throws when lists have different lengths`` () = + let list1 = FlatList.ofArray [| 1; 2 |] + let list2 = FlatList.ofArray [| 10; 20; 30 |] + + FlatList.fold2 (fun acc x y -> acc + x * y) 0 list1 list2 + |> ignore + + [] + member _.``foldBack accumulates values starting from the end`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4 |] + let result = FlatList.foldBack (fun x acc -> acc - x) flatList 0 + + // With foldBack: (0 - 4) - 3 - 2 - 1 = -10 + Assert.AreEqual (-10, result) + + [] + member _.``foldBack with string concatenation works`` () = + let flatList = FlatList.ofArray [| "a"; "b"; "c" |] + let result = FlatList.foldBack (fun x acc -> x + acc) flatList "" + + // With foldBack: "a" + "b" + "c" + "" = "abc" + Assert.AreEqual ("abc", result) + + [] + member _.``foldBack2 accumulates from two lists starting from the end`` () = + let list1 = FlatList.ofArray [| 1; 2; 3 |] + let list2 = FlatList.ofArray [| 10; 20; 30 |] + + let result = FlatList.foldBack2 (fun x y acc -> acc + x * y) list1 list2 0 + + // With foldBack2: 0 + 3*30 + 2*20 + 1*10 = 140 + Assert.AreEqual (140, result) + + [] + [)>] + member _.``foldBack2 throws when lists have different lengths`` () = + let list1 = FlatList.ofArray [| 1; 2 |] + let list2 = FlatList.ofArray [| 10; 20; 30 |] + + FlatList.foldBack2 (fun x y acc -> acc + x * y) list1 list2 0 + |> ignore + + [] + member _.``reduce combines elements`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4 |] + let result = FlatList.reduce (fun acc x -> acc + x) flatList + + // 1 + 2 + 3 + 4 = 10 + Assert.AreEqual (10, result) + + [] + [)>] + member _.``reduce throws on empty list`` () = + FlatList.reduce (fun acc x -> acc + x) FlatList.empty + |> ignore + + [] + member _.``reduceBack combines elements starting from the end`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4 |] + let result = FlatList.reduceBack (fun x acc -> x - acc) flatList + + // 1 - (2 - (3 - 4)) = 1 - (2 - (-1)) = 1 - 3 = -2 + Assert.AreEqual (-2, result) + + [] + [)>] + member _.``reduceBack throws on empty list`` () = + FlatList.reduceBack (fun x acc -> x + acc) FlatList.empty + |> ignore + + [] + member _.``scan produces intermediate results`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4 |] + let result = FlatList.scan (fun acc x -> acc + x) 0 flatList + + Assert.AreEqual (5, result.Length) + Assert.AreEqual (0, result.[0]) // Initial state + Assert.AreEqual (1, result.[1]) // 0+1 + Assert.AreEqual (3, result.[2]) // 1+2 + Assert.AreEqual (6, result.[3]) // 3+3 + Assert.AreEqual (10, result.[4]) // 6+4 + + [] + member _.``scanBack produces intermediate results starting from the end`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4 |] + let result = FlatList.scanBack (fun x acc -> x + acc) flatList 0 + + Assert.AreEqual (5, result.Length) + Assert.AreEqual (10, result.[0]) // 1 + (2 + (3 + (4 + 0))) + Assert.AreEqual (9, result.[1]) // 2 + (3 + (4 + 0)) + Assert.AreEqual (7, result.[2]) // 3 + (4 + 0) + Assert.AreEqual (4, result.[3]) // 4 + 0 + Assert.AreEqual (0, result.[4]) // Initial state + + [] + member _.``sum calculates sum of elements`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4 |] + let result = FlatList.sum flatList + + Assert.AreEqual (10, result) // 1 + 2 + 3 + 4 = 10 + + [] + member _.``sumBy calculates sum using projection function`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4 |] + let result = FlatList.sumBy (fun x -> x * x) flatList + + Assert.AreEqual (30, result) // 1*1 + 2*2 + 3*3 + 4*4 = 30 + + [] + member _.``average calculates average of elements`` () = + let flatList = FlatList.ofArray [| 1.0; 2.0; 3.0; 4.0 |] + let result = FlatList.average flatList + + Assert.AreEqual (2.5, result) // (1 + 2 + 3 + 4) / 4 = 10 / 4 = 2.5 + + [] + [)>] + member _.``average throws on empty list`` () = FlatList.average FlatList.empty |> ignore + + [] + member _.``averageBy calculates average using projection function`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4 |] + let result = FlatList.averageBy float flatList + + Assert.AreEqual (2.5, result) // (1 + 2 + 3 + 4) / 4 = 10 / 4 = 2.5 + + [] + member _.``min finds minimum element`` () = + let flatList = FlatList.ofArray [| 5; 3; 9; 1; 8 |] + let result = FlatList.min flatList + + Assert.AreEqual (1, result) + + [] + member _.``minBy finds element with minimum projected value`` () = + let people = FlatList.ofArray [| ("Alice", 25); ("Bob", 18); ("Charlie", 32) |] + let result = FlatList.minBy snd people + + Assert.AreEqual (("Bob", 18), result) + + [] + member _.``max finds maximum element`` () = + let flatList = FlatList.ofArray [| 5; 3; 9; 1; 8 |] + let result = FlatList.max flatList + + Assert.AreEqual (9, result) + + [] + member _.``maxBy finds element with maximum projected value`` () = + let people = FlatList.ofArray [| ("Alice", 25); ("Bob", 18); ("Charlie", 32) |] + let result = FlatList.maxBy snd people + + Assert.AreEqual (("Charlie", 32), result) diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/GroupingTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/GroupingTests.fs new file mode 100644 index 0000000..6f4a6ac --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/GroupingTests.fs @@ -0,0 +1,151 @@ +namespace FSharp.Collections.Immutable.Tests.FlatList + +open System +open System.Collections.Immutable +open System.Collections.Generic +open Microsoft.VisualStudio.TestTools.UnitTesting +open FSharp.Collections.Immutable +open FSharp.Collections.Immutable.Tests + +[] +type GroupingTests () = + + [] + member _.``countBy groups and counts elements`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 1; 2; 5 |] + let result = FlatList.countBy id flatList + + Assert.AreEqual (4, result.Length) + + // Find the counts for each key + let countFor key = + result + |> FlatList.find (fun struct (k, _) -> k = key) + |> sndv + + Assert.AreEqual (2, countFor 1) + Assert.AreEqual (2, countFor 2) + Assert.AreEqual (1, countFor 3) + Assert.AreEqual (1, countFor 5) + + [] + member _.``groupBy groups elements`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 1; 2; 5 |] + let result = FlatList.groupBy (fun x -> x % 2) flatList + + Assert.AreEqual (2, result.Length) + + // Find group for a key + let groupFor key = + result + |> FlatList.find (fun struct (k, _) -> k = key) + |> sndv + + let evenGroup = groupFor 0 + let oddGroup = groupFor 1 + + Assert.AreEqual (2, evenGroup.Length) + Assert.AreEqual (4, oddGroup.Length) + + Assert.IsTrue (FlatList.forall (fun x -> x % 2 = 0) evenGroup) + Assert.IsTrue (FlatList.forall (fun x -> x % 2 = 1) oddGroup) + + [] + member _.``chunkBySize splits into chunks`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4; 5; 6; 7 |] + let result = FlatList.chunkBySize 3 flatList + + Assert.AreEqual (3, result.Length) + + Assert.AreEqual (3, result.[0].Length) + Assert.AreEqual (3, result.[1].Length) + Assert.AreEqual (1, result.[2].Length) + + for i = 0 to 2 do + Assert.AreEqual (i * 3 + 1, result.[0].[i]) + + for i = 0 to 2 do + Assert.AreEqual (i * 3 + 4, result.[1].[i]) + + Assert.AreEqual (7, result.[2].[0]) + + [] + [)>] + member _.``chunkBySize throws for non-positive chunk size`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + FlatList.chunkBySize 0 flatList |> ignore + + [] + member _.``splitInto divides list into specified number of chunks`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4; 5; 6; 7; 8 |] + + // Split into 3 chunks: [|1; 2; 3|], [|4; 5; 6|], [|7; 8|] + let result = FlatList.splitInto 3 flatList + + Assert.AreEqual (3, result.Length) + Assert.AreEqual (3, result.[0].Length) + Assert.AreEqual (3, result.[1].Length) + Assert.AreEqual (2, result.[2].Length) + + // Check first chunk + Assert.AreEqual (1, result.[0].[0]) + Assert.AreEqual (2, result.[0].[1]) + Assert.AreEqual (3, result.[0].[2]) + + // Check second chunk + Assert.AreEqual (4, result.[1].[0]) + Assert.AreEqual (5, result.[1].[1]) + Assert.AreEqual (6, result.[1].[2]) + + // Check third chunk + Assert.AreEqual (7, result.[2].[0]) + Assert.AreEqual (8, result.[2].[1]) + + [] + [)>] + member _.``splitInto throws for non-positive count`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + FlatList.splitInto 0 flatList |> ignore + + [] + member _.``windowed creates sliding windows`` () = + let flatList = FlatList.ofArray [| 10; 20; 30; 40; 50 |] + let result = FlatList.windowed 3 flatList + + Assert.AreEqual (3, result.Length) + + Assert.AreEqual (3, result.[0].Length) + Assert.AreEqual (10, result.[0].[0]) + Assert.AreEqual (20, result.[0].[1]) + Assert.AreEqual (30, result.[0].[2]) + + Assert.AreEqual (3, result.[1].Length) + Assert.AreEqual (20, result.[1].[0]) + Assert.AreEqual (30, result.[1].[1]) + Assert.AreEqual (40, result.[1].[2]) + + Assert.AreEqual (3, result.[2].Length) + Assert.AreEqual (30, result.[2].[0]) + Assert.AreEqual (40, result.[2].[1]) + Assert.AreEqual (50, result.[2].[2]) + + [] + [)>] + member _.``windowed throws for non-positive window size`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + FlatList.windowed 0 flatList |> ignore + + [] + member _.``pairwise creates adjacent pairs`` () = + let flatList = FlatList.ofArray [| 10; 20; 30; 40 |] + let result = FlatList.pairwise flatList + + Assert.AreEqual (3, result.Length) + Assert.AreEqual ((10, 20), result.[0]) + Assert.AreEqual ((20, 30), result.[1]) + Assert.AreEqual ((30, 40), result.[2]) + + [] + member _.``pairwise returns empty for singleton or empty`` () = + Assert.AreEqual (0, (FlatList.pairwise (FlatList.singleton 1)).Length) + Assert.AreEqual (0, (FlatList.pairwise FlatList.empty).Length) diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/IterationTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/IterationTests.fs new file mode 100644 index 0000000..1d18580 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/IterationTests.fs @@ -0,0 +1,109 @@ +namespace FSharp.Collections.Immutable.Tests.FlatList + +open System +open System.Collections.Immutable +open System.Collections.Generic +open Microsoft.VisualStudio.TestTools.UnitTesting +open FSharp.Collections.Immutable + +[] +type IterationTests () = + + [] + member _.``iter applies function to each element`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + let mutable sum = 0 + + FlatList.iter (fun x -> sum <- sum + x) flatList + + Assert.AreEqual (6, sum) + + [] + member _.``iteri applies function with index`` () = + let flatList = FlatList.ofArray [| 10; 20; 30 |] + let mutable sum = 0 + + FlatList.iteri (fun i x -> sum <- sum + i + x) flatList + + Assert.AreEqual (63, sum) // (0+10) + (1+20) + (2+30) = 63 + + [] + member _.``iter2 applies function to pairs`` () = + let list1 = FlatList.ofArray [| 1; 2; 3 |] + let list2 = FlatList.ofArray [| 10; 20; 30 |] + let mutable sum = 0 + + FlatList.iter2 (fun x y -> sum <- sum + x + y) list1 list2 + + Assert.AreEqual (66, sum) // (1+10) + (2+20) + (3+30) = 66 + + [] + [)>] + member _.``iter2 throws for different length lists`` () = + let list1 = FlatList.ofArray [| 1; 2 |] + let list2 = FlatList.ofArray [| 10; 20; 30 |] + + FlatList.iter2 (fun x y -> ()) list1 list2 + + [] + member _.``iteri2 applies function with index`` () = + let list1 = FlatList.ofArray [| 1; 2; 3 |] + let list2 = FlatList.ofArray [| 10; 20; 30 |] + let mutable result = 0 + + FlatList.iteri2 (fun i x y -> result <- result + i + x + y) list1 list2 + + Assert.AreEqual (69, result) // (0+1+10) + (1+2+20) + (2+3+30) = 69 + + [] + member _.``contains checks if element exists`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + + Assert.IsTrue (FlatList.contains 2 flatList) + Assert.IsFalse (FlatList.contains 4 flatList) + + [] + member _.``exists checks if any element satisfies predicate`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + + Assert.IsTrue (FlatList.exists (fun x -> x = 2) flatList) + Assert.IsFalse (FlatList.exists (fun x -> x > 10) flatList) + + [] + member _.``exists2 checks elements from two lists`` () = + let list1 = FlatList.ofArray [| 1; 2; 3 |] + let list2 = FlatList.ofArray [| 3; 2; 1 |] + + Assert.IsTrue (FlatList.exists2 (fun x y -> x = y) list1 list2) + Assert.IsFalse (FlatList.exists2 (fun x y -> x > 10 && y > 10) list1 list2) + + [] + [)>] + member _.``exists2 throws when lists have different lengths`` () = + let list1 = FlatList.ofArray [| 1; 2 |] + let list2 = FlatList.ofArray [| 1; 2; 3 |] + + FlatList.exists2 (fun x y -> x = y) list1 list2 |> ignore + + [] + member _.``forall checks if all elements satisfy predicate`` () = + let flatList = FlatList.ofArray [| 2; 4; 6 |] + + Assert.IsTrue (FlatList.forall (fun x -> x % 2 = 0) flatList) + Assert.IsFalse (FlatList.forall (fun x -> x > 3) flatList) + + [] + member _.``forall2 checks all element pairs`` () = + let list1 = FlatList.ofArray [| 1; 2; 3 |] + let list2 = FlatList.ofArray [| 4; 5; 6 |] + + Assert.IsTrue (FlatList.forall2 (fun x y -> x < y) list1 list2) + Assert.IsFalse (FlatList.forall2 (fun x y -> x > y) list1 list2) + + [] + [)>] + member _.``forall2 throws when lists have different lengths`` () = + let list1 = FlatList.ofArray [| 1; 2 |] + let list2 = FlatList.ofArray [| 1; 2; 3 |] + + FlatList.forall2 (fun x y -> x = y) list1 list2 |> ignore diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/ManipulationTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/ManipulationTests.fs new file mode 100644 index 0000000..e8e56e9 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/ManipulationTests.fs @@ -0,0 +1,231 @@ +namespace FSharp.Collections.Immutable.Tests.FlatList + +open System +open System.Collections.Immutable +open System.Collections.Generic +open Microsoft.VisualStudio.TestTools.UnitTesting +open FSharp.Collections.Immutable + +[] +type ManipulationTests () = + + [] + member _.``append combines two FlatLists`` () = + let list1 = FlatList.ofArray [| 1; 2 |] + let list2 = FlatList.ofArray [| 3; 4 |] + + let result = FlatList.append list1 list2 + + Assert.AreEqual (4, result.Length) + Assert.AreEqual (1, result.[0]) + Assert.AreEqual (2, result.[1]) + Assert.AreEqual (3, result.[2]) + Assert.AreEqual (4, result.[3]) + + [] + member _.``concat combines multiple FlatLists`` () = + let lists = + FlatList.ofArray [| FlatList.ofArray [| 1; 2 |]; FlatList.ofArray [| 3; 4 |]; FlatList.ofArray [| 5; 6 |] |] + + let result = FlatList.concat lists + + Assert.AreEqual (6, result.Length) + for i = 0 to 5 do + Assert.AreEqual (i + 1, result.[i]) + + [] + member _.``take returns first N elements`` () = + let flatList = FlatList.ofArray [| 10; 20; 30; 40; 50 |] + + let result1 = FlatList.take 3 flatList + Assert.AreEqual (3, result1.Length) + Assert.AreEqual (10, result1.[0]) + Assert.AreEqual (20, result1.[1]) + Assert.AreEqual (30, result1.[2]) + + let result2 = FlatList.take 0 flatList + Assert.AreEqual (0, result2.Length) + + let result3 = FlatList.take 10 flatList + Assert.AreEqual (5, result3.Length) + + [] + [)>] + member _.``take throws for negative count`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + FlatList.take -1 flatList |> ignore + + [] + member _.``takeWhile returns elements while predicate is true`` () = + let flatList = FlatList.ofArray [| 2; 4; 6; 7; 8; 10 |] + let result = FlatList.takeWhile (fun x -> x % 2 = 0) flatList + + Assert.AreEqual (3, result.Length) + Assert.AreEqual (2, result.[0]) + Assert.AreEqual (4, result.[1]) + Assert.AreEqual (6, result.[2]) + + [] + member _.``skip returns all but first N elements`` () = + let flatList = FlatList.ofArray [| 10; 20; 30; 40; 50 |] + + let result1 = FlatList.skip 2 flatList + Assert.AreEqual (3, result1.Length) + Assert.AreEqual (30, result1.[0]) + Assert.AreEqual (40, result1.[1]) + Assert.AreEqual (50, result1.[2]) + + let result2 = FlatList.skip 0 flatList + Assert.AreEqual (5, result2.Length) + + let result3 = FlatList.skip 5 flatList + Assert.AreEqual (0, result3.Length) + + let result4 = FlatList.skip 10 flatList + Assert.AreEqual (0, result4.Length) + + [] + [)>] + member _.``skip throws for negative count`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + FlatList.skip -1 flatList |> ignore + + [] + member _.``skipWhile skips elements while predicate is true`` () = + let flatList = FlatList.ofArray [| 2; 4; 6; 7; 8; 10 |] + let result = FlatList.skipWhile (fun x -> x % 2 = 0) flatList + + Assert.AreEqual (3, result.Length) + Assert.AreEqual (7, result.[0]) + Assert.AreEqual (8, result.[1]) + Assert.AreEqual (10, result.[2]) + + [] + member _.``sub gets a sublist`` () = + let flatList = FlatList.ofArray [| 10; 20; 30; 40; 50 |] + let result = FlatList.sub 1 3 flatList + + Assert.AreEqual (3, result.Length) + Assert.AreEqual (20, result.[0]) + Assert.AreEqual (30, result.[1]) + Assert.AreEqual (40, result.[2]) + + [] + member _.``truncate limits to at most N elements`` () = + let flatList = FlatList.ofArray [| 10; 20; 30; 40; 50 |] + + let result1 = FlatList.truncate 3 flatList + Assert.AreEqual (3, result1.Length) + Assert.AreEqual (10, result1.[0]) + Assert.AreEqual (20, result1.[1]) + Assert.AreEqual (30, result1.[2]) + + let result2 = FlatList.truncate 10 flatList + Assert.AreEqual (5, result2.Length) + + [] + member _.``splitAt splits list at index`` () = + let flatList = FlatList.ofArray [| 10; 20; 30; 40; 50 |] + let first, second = FlatList.splitAt 2 flatList + + Assert.AreEqual (2, first.Length) + Assert.AreEqual (10, first.[0]) + Assert.AreEqual (20, first.[1]) + + Assert.AreEqual (3, second.Length) + Assert.AreEqual (30, second.[0]) + Assert.AreEqual (40, second.[1]) + Assert.AreEqual (50, second.[2]) + + [] + member _.``updateAt updates element at given index`` () = + let flatList = FlatList.ofArray [| 10; 20; 30 |] + let result = FlatList.updateAt 1 99 flatList + + Assert.AreEqual (3, result.Length) + Assert.AreEqual (10, result.[0]) + Assert.AreEqual (99, result.[1]) + Assert.AreEqual (30, result.[2]) + + [] + member _.``removeAt removes element at given index`` () = + let flatList = FlatList.ofArray [| 10; 20; 30 |] + let result = FlatList.removeAt 1 flatList + + Assert.AreEqual (2, result.Length) + Assert.AreEqual (10, result.[0]) + Assert.AreEqual (30, result.[1]) + + [] + member _.``insertAt inserts element at given index`` () = + let flatList = FlatList.ofArray [| 10; 20; 30 |] + + // Insert at beginning + let result1 = FlatList.insertAt 0 5 flatList + Assert.AreEqual (4, result1.Length) + Assert.AreEqual (5, result1.[0]) + Assert.AreEqual (10, result1.[1]) + + // Insert in middle + let result2 = FlatList.insertAt 2 25 flatList + Assert.AreEqual (4, result2.Length) + Assert.AreEqual (10, result2.[0]) + Assert.AreEqual (20, result2.[1]) + Assert.AreEqual (25, result2.[2]) + Assert.AreEqual (30, result2.[3]) + + // Insert at end + let result3 = FlatList.insertAt 3 40 flatList + Assert.AreEqual (4, result3.Length) + Assert.AreEqual (10, result3.[0]) + Assert.AreEqual (20, result3.[1]) + Assert.AreEqual (30, result3.[2]) + Assert.AreEqual (40, result3.[3]) + + [] + member _.``insertManyAt inserts multiple elements at given index`` () = + let flatList = FlatList.ofArray [| 10; 20; 30 |] + let valuesToInsert = [| 21; 22; 23 |] + + let result = FlatList.insertManyAt 2 valuesToInsert flatList + + Assert.AreEqual (6, result.Length) + Assert.AreEqual (10, result.[0]) + Assert.AreEqual (20, result.[1]) + Assert.AreEqual (21, result.[2]) + Assert.AreEqual (22, result.[3]) + Assert.AreEqual (23, result.[4]) + Assert.AreEqual (30, result.[5]) + + [] + member _.``removeRange removes elements in range`` () = + let flatList = FlatList.ofArray [| 10; 20; 30; 40; 50 |] + let result = FlatList.removeRange 1 3 flatList + + Assert.AreEqual (2, result.Length) + Assert.AreEqual (10, result.[0]) + Assert.AreEqual (50, result.[1]) + + [] + member _.``removeAll removes specified elements`` () = + let flatList = FlatList.ofArray [| 10; 20; 30; 20; 40 |] + let itemsToRemove = [| 20; 30 |] + + let result = FlatList.removeAll itemsToRemove flatList + + Assert.AreEqual (3, result.Length) + Assert.AreEqual (10, result.[0]) + Assert.AreEqual (20, result.[1]) // Only removes first occurrence by default + Assert.AreEqual (40, result.[2]) + + [] + member _.``except removes elements from another collection`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4; 5; 3; 1 |] + let toExclude = [| 1; 3; 9 |] + + let result = FlatList.except toExclude flatList + + Assert.AreEqual (3, result.Length) + Assert.IsTrue (FlatList.contains 2 result) + Assert.IsTrue (FlatList.contains 4 result) + Assert.IsTrue (FlatList.contains 5 result) diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/PairOperationTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/PairOperationTests.fs new file mode 100644 index 0000000..3b23c9a --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/PairOperationTests.fs @@ -0,0 +1,160 @@ +namespace FSharp.Collections.Immutable.Tests.FlatList + +open System +open System.Collections.Immutable +open System.Collections.Generic +open Microsoft.VisualStudio.TestTools.UnitTesting +open FSharp.Collections.Immutable + +[] +type PairOperationTests () = + + [] + member _.``allPairs creates all combinations`` () = + let list1 = FlatList.ofArray [| 1; 2 |] + let list2 = FlatList.ofArray [| 'a'; 'b'; 'c' |] + let result = FlatList.allPairs list1 list2 + + Assert.AreEqual (6, result.Length) + Assert.AreEqual ((1, 'a'), result.[0]) + Assert.AreEqual ((1, 'b'), result.[1]) + Assert.AreEqual ((1, 'c'), result.[2]) + Assert.AreEqual ((2, 'a'), result.[3]) + Assert.AreEqual ((2, 'b'), result.[4]) + Assert.AreEqual ((2, 'c'), result.[5]) + + [] + member _.``permute reorders elements`` () = + let flatList = FlatList.ofArray [| 10; 20; 30 |] + let result = FlatList.permute (fun i -> (i + 2) % 3) flatList + + Assert.AreEqual (3, result.Length) + Assert.AreEqual (30, result.[0]) + Assert.AreEqual (10, result.[1]) + Assert.AreEqual (20, result.[2]) + + [] + [)>] + member _.``permute throws for invalid permutation function`` () = + let flatList = FlatList.ofArray [| 10; 20; 30 |] + FlatList.permute (fun _ -> 10) flatList |> ignore + + [] + member _.``zip combines two lists into pairs`` () = + let list1 = FlatList.ofArray [| 1; 2; 3 |] + let list2 = FlatList.ofArray [| "a"; "b"; "c" |] + + let result = FlatList.zip list1 list2 + + Assert.AreEqual (3, result.Length) + Assert.AreEqual ((1, "a"), result.[0]) + Assert.AreEqual ((2, "b"), result.[1]) + Assert.AreEqual ((3, "c"), result.[2]) + + [] + [)>] + member _.``zip throws when lists have different lengths`` () = + let list1 = FlatList.ofArray [| 1; 2 |] + let list2 = FlatList.ofArray [| "a"; "b"; "c" |] + + FlatList.zip list1 list2 |> ignore + + [] + member _.``unzip splits pairs into two lists`` () = + let flatList = FlatList.ofArray [| (1, "a"); (2, "b"); (3, "c") |] + + let list1, list2 = FlatList.unzip flatList + + Assert.AreEqual (3, list1.Length) + Assert.AreEqual (3, list2.Length) + + Assert.AreEqual (1, list1.[0]) + Assert.AreEqual (2, list1.[1]) + Assert.AreEqual (3, list1.[2]) + + Assert.AreEqual ("a", list2.[0]) + Assert.AreEqual ("b", list2.[1]) + Assert.AreEqual ("c", list2.[2]) + + [] + member _.``zip3 combines three lists into triples`` () = + let list1 = FlatList.ofArray [| 1; 2; 3 |] + let list2 = FlatList.ofArray [| "a"; "b"; "c" |] + let list3 = FlatList.ofArray [| true; false; true |] + + let result = FlatList.zip3 list1 list2 list3 + + Assert.AreEqual (3, result.Length) + Assert.AreEqual ((1, "a", true), result.[0]) + Assert.AreEqual ((2, "b", false), result.[1]) + Assert.AreEqual ((3, "c", true), result.[2]) + + [] + [)>] + member _.``zip3 throws when lists have different lengths (first and second)`` () = + let list1 = FlatList.ofArray [| 1; 2 |] + let list2 = FlatList.ofArray [| "a"; "b"; "c" |] + let list3 = FlatList.ofArray [| true; false; true |] + + FlatList.zip3 list1 list2 list3 |> ignore + + [] + [)>] + member _.``zip3 throws when lists have different lengths (first and third)`` () = + let list1 = FlatList.ofArray [| 1; 2 |] + let list2 = FlatList.ofArray [| "a"; "b" |] + let list3 = FlatList.ofArray [| true; false; true |] + + FlatList.zip3 list1 list2 list3 |> ignore + + [] + member _.``unzip3 splits triples into three lists`` () = + let flatList = FlatList.ofArray [| (1, "a", true); (2, "b", false); (3, "c", true) |] + + let list1, list2, list3 = FlatList.unzip3 flatList + + Assert.AreEqual (3, list1.Length) + Assert.AreEqual (3, list2.Length) + Assert.AreEqual (3, list3.Length) + + Assert.AreEqual (1, list1.[0]) + Assert.AreEqual (2, list1.[1]) + Assert.AreEqual (3, list1.[2]) + + Assert.AreEqual ("a", list2.[0]) + Assert.AreEqual ("b", list2.[1]) + Assert.AreEqual ("c", list2.[2]) + + Assert.AreEqual (true, list3.[0]) + Assert.AreEqual (false, list3.[1]) + Assert.AreEqual (true, list3.[2]) + + [] + member _.``transpose reorients list of lists`` () = + let matrix = FlatList.ofArray [| FlatList.ofArray [| 1; 2; 3 |]; FlatList.ofArray [| 4; 5; 6 |] |] + + let result = FlatList.transpose matrix + + Assert.AreEqual (3, result.Length) + Assert.AreEqual (2, result.[0].Length) + Assert.AreEqual (2, result.[1].Length) + Assert.AreEqual (2, result.[2].Length) + + // First column + Assert.AreEqual (1, result.[0].[0]) + Assert.AreEqual (4, result.[0].[1]) + + // Second column + Assert.AreEqual (2, result.[1].[0]) + Assert.AreEqual (5, result.[1].[1]) + + // Third column + Assert.AreEqual (3, result.[2].[0]) + Assert.AreEqual (6, result.[2].[1]) + + [] + [)>] + member _.``transpose throws when inner arrays have different lengths`` () = + let matrix = FlatList.ofArray [| FlatList.ofArray [| 1; 2; 3 |]; FlatList.ofArray [| 4; 5 |] |] + + FlatList.transpose matrix |> ignore diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/SearchTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/SearchTests.fs new file mode 100644 index 0000000..b537c17 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/SearchTests.fs @@ -0,0 +1,138 @@ +namespace FSharp.Collections.Immutable.Tests.FlatList + +open System +open System.Collections.Immutable +open System.Collections.Generic +open Microsoft.VisualStudio.TestTools.UnitTesting +open FSharp.Collections.Immutable + +[] +type SearchTests () = + + [] + member _.``find returns first element matching predicate`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4 |] + Assert.AreEqual (2, FlatList.find (fun x -> x % 2 = 0) flatList) + + [] + [)>] + member _.``find throws when no element satisfies predicate`` () = + let flatList = FlatList.ofArray [| 1; 3; 5 |] + FlatList.find (fun x -> x % 2 = 0) flatList |> ignore + + [] + member _.``tryFind returns element or ValueNone`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + + Assert.AreEqual (ValueSome 2, FlatList.tryFind (fun x -> x % 2 = 0) flatList) + Assert.AreEqual (ValueNone, FlatList.tryFind (fun x -> x > 10) flatList) + + [] + member _.``findBack returns last element matching predicate`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4; 2 |] + Assert.AreEqual (4, FlatList.findBack (fun x -> x % 2 = 0) flatList) + + [] + [)>] + member _.``findBack throws when no element satisfies predicate`` () = + let flatList = FlatList.ofArray [| 1; 3; 5 |] + FlatList.findBack (fun x -> x % 2 = 0) flatList |> ignore + + [] + member _.``tryFindBack returns element or ValueNone`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4; 2 |] + + Assert.AreEqual (ValueSome 4, FlatList.tryFindBack (fun x -> x % 2 = 0) flatList) + Assert.AreEqual (ValueNone, FlatList.tryFindBack (fun x -> x > 10) flatList) + + [] + member _.``findIndex returns index of first element matching predicate`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4 |] + Assert.AreEqual (1, FlatList.findIndex (fun x -> x % 2 = 0) flatList) + + [] + [)>] + member _.``findIndex throws when no element satisfies predicate`` () = + let flatList = FlatList.ofArray [| 1; 3; 5 |] + FlatList.findIndex (fun x -> x % 2 = 0) flatList |> ignore + + [] + member _.``tryFindIndex returns index or ValueNone`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + + Assert.AreEqual (ValueSome 1, FlatList.tryFindIndex (fun x -> x % 2 = 0) flatList) + Assert.AreEqual (ValueNone, FlatList.tryFindIndex (fun x -> x > 10) flatList) + + [] + member _.``findIndexBack returns index of last element matching predicate`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4; 2 |] + Assert.AreEqual (3, FlatList.findIndexBack (fun x -> x % 2 = 0) flatList) + + [] + [)>] + member _.``findIndexBack throws when no element satisfies predicate`` () = + let flatList = FlatList.ofArray [| 1; 3; 5 |] + FlatList.findIndexBack (fun x -> x % 2 = 0) flatList + |> ignore + + [] + member _.``tryFindIndexBack returns index or ValueNone`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4; 2 |] + + Assert.AreEqual (ValueSome 3, FlatList.tryFindIndexBack (fun x -> x % 2 = 0) flatList) + Assert.AreEqual (ValueNone, FlatList.tryFindIndexBack (fun x -> x > 10) flatList) + + [] + member _.``pick returns first value from chooser that returns ValueSome`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4 |] + let result = FlatList.pick (fun x -> if x > 2 then ValueSome (x * 10) else ValueNone) flatList + + Assert.AreEqual (30, result) + + [] + [)>] + member _.``pick throws when chooser returns ValueNone for all elements`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + FlatList.pick (fun x -> if x > 10 then ValueSome x else ValueNone) flatList + |> ignore + + [] + member _.``tryPick returns first value from chooser that returns ValueSome, or ValueNone`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4 |] + + let result1 = FlatList.tryPick (fun x -> if x > 2 then ValueSome (x * 10) else ValueNone) flatList + Assert.AreEqual (ValueSome 30, result1) + + let result2 = FlatList.tryPick (fun x -> if x > 10 then ValueSome x else ValueNone) flatList + Assert.AreEqual (ValueNone, result2) + + [] + member _.``index returns position of first occurrence of item`` () = + let flatList = FlatList.ofArray [| 10; 20; 30; 20; 40 |] + + Assert.AreEqual (1, FlatList.index 20 flatList) + + [] + [)>] + member _.``index throws when item not found`` () = + let flatList = FlatList.ofArray [| 10; 20; 30 |] + FlatList.index 50 flatList |> ignore + + [] + member _.``lastIndex returns position of last occurrence of item`` () = + let flatList = FlatList.ofArray [| 10; 20; 30; 20; 40 |] + + Assert.AreEqual (3, FlatList.lastIndex 20 flatList) + + [] + [)>] + member _.``lastIndex throws when item not found`` () = + let flatList = FlatList.ofArray [| 10; 20; 30 |] + FlatList.lastIndex 50 flatList |> ignore + + [] + member _.``tryFindBack returns last element matching predicate`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4; 2 |] + + Assert.AreEqual (ValueSome 4, FlatList.tryFindBack (fun x -> x % 2 = 0) flatList) + Assert.AreEqual (ValueNone, FlatList.tryFindBack (fun x -> x > 10) flatList) diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/SortTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/SortTests.fs new file mode 100644 index 0000000..4fc1407 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/SortTests.fs @@ -0,0 +1,126 @@ +namespace FSharp.Collections.Immutable.Tests.FlatList + +open System +open System.Collections.Immutable +open System.Collections.Generic +open Microsoft.VisualStudio.TestTools.UnitTesting +open FSharp.Collections.Immutable + +[] +type SortTests () = + + [] + member _.``sort orders elements using default comparer`` () = + let flatList = FlatList.ofArray [| 3; 1; 4; 2; 5 |] + let result = FlatList.sort flatList + + CollectionAssert.AreEqual ([| 1; 2; 3; 4; 5 |], FlatList.toArray result) + + [] + member _.``sortDescending orders elements in reverse`` () = + let flatList = FlatList.ofArray [| 3; 1; 4; 2; 5 |] + let result = FlatList.sortDescending flatList + + CollectionAssert.AreEqual ([| 5; 4; 3; 2; 1 |], FlatList.toArray result) + + [] + member _.``sortBy orders using key selector`` () = + let flatList = FlatList.ofArray [| "apple"; "banana"; "cherry"; "date"; "fig" |] + let result = FlatList.sortBy String.length flatList + + // Should be sorted by length: "fig", "date", "apple", "cherry", "banana" + CollectionAssert.AreEqual ([| "fig"; "date"; "apple"; "cherry"; "banana" |], FlatList.toArray result) + + [] + member _.``sortByDescending orders using key selector in reverse`` () = + let flatList = FlatList.ofArray [| "apple"; "banana"; "cherry"; "date"; "fig" |] + let result = FlatList.sortByDescending String.length flatList + + // Should be sorted by length descending: "banana", "cherry", "apple", "date", "fig" + CollectionAssert.AreEqual ([| "banana"; "cherry"; "apple"; "date"; "fig" |], FlatList.toArray result) + + [] + member _.``sortWith uses custom comparison function`` () = + let flatList = FlatList.ofArray [| 3; 1; 4; 2; 5 |] + + // Sort using a custom comparison that compares the remainder when divided by 3 + let result = FlatList.sortWith (fun x y -> compare (x % 3) (y % 3)) flatList + + // Should be: 3, 6, 9 (rem 0), then 1, 4, 7 (rem 1), then 2, 5, 8 (rem 2) + // From our input: 3 (rem 0), then 1, 4 (rem 1), then 2, 5 (rem 2) + CollectionAssert.AreEqual ([| 3; 1; 4; 2; 5 |], FlatList.toArray result) + + [] + member _.``sortWithComparer uses IComparer`` () = + let flatList = FlatList.ofArray [| 3; 1; 4; 2; 5 |] + + let reverseComparer = + { new IComparer with + member _.Compare (x, y) = compare y x + } + + let result = FlatList.sortWithComparer reverseComparer flatList + + // Should sort in reverse + CollectionAssert.AreEqual ([| 5; 4; 3; 2; 1 |], FlatList.toArray result) + + [] + member _.``sortRange sorts portion of list`` () = + let flatList = FlatList.ofArray [| 3; 1; 4; 2; 5 |] + + // Sort only elements 1, 2, 3 (indices 1, 2, 3) + let result = FlatList.sortRange 1 3 flatList + + // Should be: 3, 1, 2, 4, 5 + CollectionAssert.AreEqual ([| 3; 1; 2; 4; 5 |], FlatList.toArray result) + + [] + member _.``sortRangeWith sorts portion with custom comparison`` () = + let flatList = FlatList.ofArray [| 3; 1; 4; 2; 5 |] + + // Sort only elements 1, 2, 3 (indices 1, 2, 3) in reverse + let result = FlatList.sortRangeWith (fun x y -> compare y x) 1 3 flatList + + // Should be: 3, 4, 2, 1, 5 + CollectionAssert.AreEqual ([| 3; 4; 2; 1; 5 |], FlatList.toArray result) + + [] + member _.``sortRangeWithComparer uses IComparer for portion`` () = + let flatList = FlatList.ofArray [| 3; 1; 4; 2; 5 |] + + let reverseComparer = + { new IComparer with + member _.Compare (x, y) = compare y x + } + + // Sort only elements 1, 2, 3 (indices 1, 2, 3) using reverse comparer + let result = FlatList.sortRangeWithComparer reverseComparer 1 3 flatList + + // Should be: 3, 4, 2, 1, 5 + CollectionAssert.AreEqual ([| 3; 4; 2; 1; 5 |], FlatList.toArray result) + + [] + member _.``compareWith compares elements`` () = + let list1 = FlatList.ofArray [| 1; 2; 3 |] + let list2 = FlatList.ofArray [| 1; 2; 4 |] + let list3 = FlatList.ofArray [| 1; 2; 3; 4 |] + + // Custom comparer that considers elements equal if both odd or both even + let comparer x y = + if x % 2 = y % 2 then 0 + elif x % 2 < y % 2 then -1 + else 1 + + // list1 and list2 differ at position 2, where 3 and 4 have same parity + Assert.AreEqual (0, FlatList.compareWith comparer list1 list2) + + // list1 is shorter than list3 (where all elements match) + Assert.AreEqual (-1, FlatList.compareWith comparer list1 list3) + + // list3 is longer than list2 (where all elements match) + Assert.AreEqual (1, FlatList.compareWith comparer list3 list2) + + // Standard comparison should still work normally + Assert.AreEqual (-1, FlatList.compareWith compare list1 list2) + Assert.AreEqual (-1, FlatList.compareWith compare list1 list3) + Assert.AreEqual (1, FlatList.compareWith compare list2 list1) diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/UtilityTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/UtilityTests.fs new file mode 100644 index 0000000..c2345ff --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/UtilityTests.fs @@ -0,0 +1,246 @@ +namespace FSharp.Collections.Immutable.Tests.FlatList + +open System +open System.Collections.Immutable +open System.Collections.Generic +open Microsoft.VisualStudio.TestTools.UnitTesting +open FSharp.Collections.Immutable + +[] +type UtilityTests () = + + [] + member _.``blit copies range of elements to array`` () = + let source = FlatList.ofArray [| 10; 20; 30; 40; 50 |] + let destination = Array.zeroCreate 5 + + FlatList.blit source 1 destination 2 3 + + Assert.AreEqual (0, destination.[0]) + Assert.AreEqual (0, destination.[1]) + Assert.AreEqual (20, destination.[2]) + Assert.AreEqual (30, destination.[3]) + Assert.AreEqual (40, destination.[4]) + + [] + member _.``unfold builds list from generator function`` () = + // Create a list of powers of 2 up to 2^5 + let result = + FlatList.unfold + (fun state -> + if state <= 32 then + ValueSome (state, state * 2) + else + ValueNone + ) + 1 + + CollectionAssert.AreEqual ([| 1; 2; 4; 8; 16; 32 |], FlatList.toArray result) + + [] + member _.``build creats list with builder function`` () = + let result = + FlatList.build (fun builder -> + builder.Add (1) + builder.Add (2) + builder.Add (3) + ) + + CollectionAssert.AreEqual ([| 1; 2; 3 |], FlatList.toArray result) + + [] + member _.``exactlyOne returns the only element`` () = + let flatList = FlatList.singleton 42 + let result = FlatList.exactlyOne flatList + + Assert.AreEqual (42, result) + + [] + [)>] + member _.``exactlyOne throws for empty list`` () = FlatList.exactlyOne FlatList.empty |> ignore + + [] + [)>] + member _.``exactlyOne throws for list with multiple elements`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + FlatList.exactlyOne flatList |> ignore + + [] + member _.``tryExactlyOne returns element for single-element list`` () = + let flatList = FlatList.singleton 42 + let result = FlatList.tryExactlyOne flatList + + Assert.AreEqual (ValueSome 42, result) + + [] + member _.``tryExactlyOne returns ValueNone for empty list`` () = + let result = FlatList.tryExactlyOne FlatList.empty + + Assert.AreEqual (ValueNone, result) + + [] + member _.``tryExactlyOne returns ValueNone for multi-element list`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + let result = FlatList.tryExactlyOne flatList + + Assert.AreEqual (ValueNone, result) + + [] + member _.``transpose transforms rows into columns`` () = + let rows = FlatList.ofArray [| FlatList.ofArray [| 1; 2; 3 |]; FlatList.ofArray [| 4; 5; 6 |] |] + + let result = FlatList.transpose rows + + Assert.AreEqual (3, result.Length) // Result has 3 columns + + CollectionAssert.AreEqual ([| 1; 4 |], FlatList.toArray result.[0]) + CollectionAssert.AreEqual ([| 2; 5 |], FlatList.toArray result.[1]) + CollectionAssert.AreEqual ([| 3; 6 |], FlatList.toArray result.[2]) + + [] + [)>] + member _.``transpose throws when inner arrays have different lengths`` () = + let rows = FlatList.ofArray [| FlatList.ofArray [| 1; 2; 3 |]; FlatList.ofArray [| 4; 5 |] |] + + FlatList.transpose rows |> ignore + + [] + member _.``except removes elements from another sequence`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4; 5 |] + let itemsToExclude = [| 2; 4; 6 |] // Note: 6 is not in the original list + + let result = FlatList.except itemsToExclude flatList + + CollectionAssert.AreEqual ([| 1; 3; 5 |], FlatList.toArray result) + + [] + member _.``ofList converts F# list to FlatList`` () = + let list = [ 1; 2; 3 ] + let result = FlatList.ofList list + + CollectionAssert.AreEqual ([| 1; 2; 3 |], FlatList.toArray result) + + [] + member _.``toList converts FlatList to F# list`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + let result = FlatList.toList flatList + + Assert.AreEqual ([ 1; 2; 3 ], result) + + [] + member _.``splitInto divides list into specified number of chunks`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4; 5; 6; 7; 8 |] + let result = FlatList.splitInto 3 flatList + + Assert.AreEqual (3, result.Length) + CollectionAssert.AreEqual ([| 1; 2; 3 |], FlatList.toArray result.[0]) + CollectionAssert.AreEqual ([| 4; 5; 6 |], FlatList.toArray result.[1]) + CollectionAssert.AreEqual ([| 7; 8 |], FlatList.toArray result.[2]) + + [] + [)>] + member _.``splitInto throws for non-positive count`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + FlatList.splitInto 0 flatList |> ignore + + [] + member _.``updateAt changes element at index`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4; 5 |] + let result = FlatList.updateAt 2 42 flatList + + CollectionAssert.AreEqual ([| 1; 2; 42; 4; 5 |], FlatList.toArray result) + + [] + [)>] + member _.``updateAt throws for negative index`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + FlatList.updateAt -1 42 flatList |> ignore + + [] + [)>] + member _.``updateAt throws for index beyond end`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + FlatList.updateAt 3 42 flatList |> ignore + + [] + member _.``removeAt removes element at index`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4; 5 |] + let result = FlatList.removeAt 2 flatList + + CollectionAssert.AreEqual ([| 1; 2; 4; 5 |], FlatList.toArray result) + + [] + [)>] + member _.``removeAt throws for negative index`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + FlatList.removeAt -1 flatList |> ignore + + [] + [)>] + member _.``removeAt throws for index beyond end`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + FlatList.removeAt 3 flatList |> ignore + + [] + member _.``insertAt adds element at index`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 5 |] + let result = FlatList.insertAt 3 4 flatList + + CollectionAssert.AreEqual ([| 1; 2; 3; 4; 5 |], FlatList.toArray result) + + [] + member _.``insertAt can add at beginning`` () = + let flatList = FlatList.ofArray [| 2; 3; 4 |] + let result = FlatList.insertAt 0 1 flatList + + CollectionAssert.AreEqual ([| 1; 2; 3; 4 |], FlatList.toArray result) + + [] + member _.``insertAt can add at end`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + let result = FlatList.insertAt 3 4 flatList + + CollectionAssert.AreEqual ([| 1; 2; 3; 4 |], FlatList.toArray result) + + [] + [)>] + member _.``insertAt throws for negative index`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + FlatList.insertAt -1 0 flatList |> ignore + + [] + [)>] + member _.``insertAt throws for index too high`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + FlatList.insertAt 4 0 flatList |> ignore + + [] + member _.``insertManyAt adds multiple elements at index`` () = + let flatList = FlatList.ofArray [| 1; 5 |] + let result = FlatList.insertManyAt 1 [| 2; 3; 4 |] flatList + + CollectionAssert.AreEqual ([| 1; 2; 3; 4; 5 |], FlatList.toArray result) + + [] + [)>] + member _.``insertManyAt throws for negative index`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + FlatList.insertManyAt -1 [| 4; 5 |] flatList |> ignore + + [] + [)>] + member _.``insertManyAt throws for index too high`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + FlatList.insertManyAt 4 [| 4; 5 |] flatList |> ignore + + [] + member _.``copy creates a new identical list`` () = + let original = FlatList.ofArray [| 1; 2; 3 |] + let copied = FlatList.copy original + + // Should be equal but not the same reference + CollectionAssert.AreEqual (FlatList.toArray original, FlatList.toArray copied) + + // Since ImmutableArray is a value type with structural equality, + // these should actually be equal (not reference equality) + Assert.AreEqual> (original, copied) diff --git a/tests/FSharp.Collections.Immutable.Tests/Helpers.fs b/tests/FSharp.Collections.Immutable.Tests/Helpers.fs new file mode 100644 index 0000000..1cf318f --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/Helpers.fs @@ -0,0 +1,5 @@ +[] +module FSharp.Collections.Immutable.Tests.Helpers + +let fstv tuple = let struct (a, _) = tuple in a +let sndv tuple = let struct (_, b) = tuple in b From 70f1af67af9015299d094671914da4835b96f833 Mon Sep 17 00:00:00 2001 From: Andrii Chebukin Date: Sun, 29 Jun 2025 05:22:55 +0400 Subject: [PATCH 7/8] feat: implemented missing `FlatList` functions and tests --- .../FSharp.Collections.Immutable.fsproj | 1 + src/FSharp.Collections.Immutable/FlatList.fs | 1689 ++++++++--------- src/FSharp.Collections.Immutable/FlatList.fsi | 1446 ++++++++++++++ src/FSharp.Collections.Immutable/Helper.fs | 6 + .../FSharp.Collections.Immutable.Tests.fsproj | 1 + .../FlatList/BasicOperationsTests.fs | 38 + .../FlatList/BuilderTests.fs | 5 +- .../FlatList/CreationTests.fs | 90 +- .../FlatList/FilterMapTests.fs | 66 + .../FlatList/FoldTests.fs | 4 +- .../FlatList/GroupingTests.fs | 19 +- .../FlatList/ManipulationTests.fs | 136 +- .../FlatList/MapFoldTests.fs | 83 + .../FlatList/PairOperationTests.fs | 20 +- .../FlatList/SearchTests.fs | 81 +- .../FlatList/SortTests.fs | 6 +- 16 files changed, 2769 insertions(+), 922 deletions(-) create mode 100644 src/FSharp.Collections.Immutable/FlatList.fsi create mode 100644 tests/FSharp.Collections.Immutable.Tests/FlatList/MapFoldTests.fs diff --git a/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj b/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj index 8b7d8e6..187ea7c 100644 --- a/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj +++ b/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj @@ -22,6 +22,7 @@ + diff --git a/src/FSharp.Collections.Immutable/FlatList.fs b/src/FSharp.Collections.Immutable/FlatList.fs index 4edef62..a98405f 100644 --- a/src/FSharp.Collections.Immutable/FlatList.fs +++ b/src/FSharp.Collections.Immutable/FlatList.fs @@ -1,3 +1,7 @@ +// This file contains F# bindings to ImmutableArray from System.Collections.Immutable. +// It provides a flat list implementation that is optimized for performance and memory usage. +// The FlatList type is a wrapper around ImmutableArray, providing a more convenient API for working with immutable lists. +// FlatList code is designed to perform operations without allocating new arrays unnecessarily, making it suitable for high-performance applications. #if INTERACTIVE namespace global #else @@ -5,6 +9,7 @@ namespace FSharp.Collections.Immutable #endif open System +open System.Buffers open System.Collections.Generic open System.Collections.Immutable open System.Linq @@ -24,414 +29,284 @@ module FlatList = if list.IsDefault then invalidArg argName "Uninstantiated ImmutableArray/FlatList" - let inline internal indexNotFound () = raise <| System.Collections.Generic.KeyNotFoundException () + let inline internal indexNotFound () = + raise + <| System.Collections.Generic.KeyNotFoundException ("An item with the specified key was not found.") - let inline private lengthWhile (predicate : 'T -> bool) (list : FlatList<'T>) = list.TakeWhile(predicate).Count () + let inline internal sequenceNotFound () = + raise + <| System.InvalidOperationException ("Sequence contains no matching element.") ////////// Creating ////////// - /// Creates a new builder with the specified capacity - /// The initial capacity of the builder - /// An empty builder with the specified capacity + [] let inline builderWith capacity : FlatList<'T>.Builder = FlatListFactory.CreateBuilder (capacity) - /// Builds a from a builder, moving the elements and leaving the builder empty - /// The builder to build from - /// A containing the elements from the builder - /// Thrown when builder is null + [] let moveFromBuilder (builder : FlatList<_>.Builder) : FlatList<_> = checkNotNull (nameof builder) builder // Keep check for null builder, not default FlatList builder.MoveToImmutable () - /// Returns an empty - /// An empty - /// - /// - /// let emptyList = FlatList.empty<int> - /// printfn "Is empty? %b" (FlatList.isEmpty emptyList) // true - /// - /// + [] let inline empty<'T> : FlatList<'T> = FlatListFactory.Create<'T> () - /// Builds a from the given array - /// The array to build the from - /// A containing the elements of the array + [] let inline ofArray (source : _ array) = FlatListFactory.CreateRange source - /// Builds a from the given sequence - /// The sequence to build the from - /// A containing the elements of the sequence + [] let inline ofSeq source = FlatListFactory.CreateRange source - /// Returns a with a single element - /// The item to put into the - /// A containing only the given item + [] + let inline ofList (source : 'T list) = FlatListFactory.CreateRange source + + [] let inline singleton<'T> (item : 'T) : FlatList<'T> = FlatListFactory.Create<'T> (item) - /// Creates a by initializing each element with the given function - /// The number of elements to create - /// The function to initialize each element - /// A new with the initialized elements - /// Thrown when count is negative + [] + let ofOption (option : 'T option) : FlatList<'T> = + match option with + | Some x -> singleton x + | None -> empty + + [] + let ofValueOption (voption : 'T voption) : FlatList<'T> = + match voption with + | ValueSome x -> singleton x + | ValueNone -> empty + + [] let init count (initializer : int -> 'T) = if count < 0 then invalidArg (nameof count) ErrorStrings.InputMustBeNonNegative - ParallelEnumerable.Range(0, count).Select(initializer).ToImmutableArray () - /// Creates a of a given length with all elements set to the given value - /// The length of the to create - /// The value to replicate - /// A of the specified length with all elements equal to the given value + if count = 0 then + empty + else + // Create a builder with exact capacity needed + let builder = FlatListFactory.CreateBuilder<'T> count + // Resize the internal array to ensure all indices are valid + builder.Count <- count + + // Use Parallel.For to initialize elements in parallel + System.Threading.Tasks.Parallel.For (0, count, fun i -> builder.[i] <- initializer i) + |> ignore + + builder.MoveToImmutable () + + [] let create count (value : 'T) = init count (fun _ -> value) - /// Replicates a value into a of a given length - /// The length of the to create - /// The value to replicate - /// A of the specified length with all elements equal to the given value + [] let replicate count initial = create count initial - /// Views the as a sequence - /// The input - /// The sequence containing the elements of the + [] + let zeroCreate<'T> (count : int) : FlatList<'T> = + if count < 0 then + invalidArg (nameof count) ErrorStrings.InputMustBeNonNegative + if count = 0 then + empty + else + let arr = Array.zeroCreate<'T> count + FlatListFactory.CreateRange (arr) + + [] let inline toSeq (flatList : FlatList<_>) = flatList :> seq<_> - /// Builds an array from the given - /// The to build the array from - /// An array containing the elements of the + [] let inline toArray (list : FlatList<_>) = list.ToArray () + [] + let toList (list : FlatList<'T>) : 'T list = + if list.IsDefaultOrEmpty then + [] + else + let len = list.Length + let mutable result = [] + for i = len - 1 downto 0 do + result <- list.[i] :: result + result + + [] + let toOption (list : FlatList<'T>) : 'T option = if list.Length = 1 then Some list.[0] else None + + [] + let toValueOption (list : FlatList<'T>) : 'T voption = if list.Length = 1 then ValueSome list.[0] else ValueNone + + [] + let copy (list : FlatList<'T>) : FlatList<'T> = list.ToImmutableArray () + ////////// Building ////////// - /// Builds a from a builder, copying the elements - /// The builder to build from - /// A containing the elements from the builder - /// Thrown when builder is null + [] let ofBuilder (builder : FlatList<_>.Builder) : FlatList<_> = checkNotNull (nameof builder) builder // Keep check for null builder - builder.MoveToImmutable () + builder.ToImmutable () - /// Creates a new builder - /// An empty builder + [] let inline builder () : FlatList<'T>.Builder = FlatListFactory.CreateBuilder () - /// Creates a builder containing the elements of the input - /// The to create the builder from - /// A builder containing the elements of the + [] let toBuilder (list : FlatList<'T>) : FlatList<'T>.Builder = list.ToBuilder () module Builder = let inline private check (builder : FlatList<'T>.Builder) = checkNotNull (nameof builder) builder - /// Adds an item to the builder - /// The item to add - /// The builder to add to - let add item builder = + [] + let add (item : 'T) (builder : FlatList<'T>.Builder) : FlatList<'T>.Builder = check builder - builder.Add (item) + builder.Add item + builder - /// Checks if the is empty - /// The to check - /// True if the is empty, false otherwise + [] let isEmpty (list : FlatList<_>) = list.IsEmpty - /// Checks if the is uninstantiated - /// The to check - /// True if the is uninstantiated, false otherwise + [] let isDefault (list : FlatList<_>) = list.IsDefault - /// Checks if the is uninstantiated or empty - /// The to check - /// True if the is uninstantiated or empty, false otherwise + [] let isDefaultOrEmpty (list : FlatList<_>) = list.IsDefaultOrEmpty ////////// IReadOnly* ////////// - /// Returns the number of elements in the - /// The input - /// The number of elements in the + [] let length (list : FlatList<'T>) = list.Length - /// Gets the element at the specified index in the - /// The index to retrieve - /// The input - /// The element at the specified index - /// Thrown when the index is out of range + [] let item index (list : FlatList<'T>) = list.[index] - /// Appends two s to create a new containing all elements from both s - /// The first - /// The second - /// A new containing all elements from both input s + [] let append (list1 : FlatList<'T>) (list2 : FlatList<'T>) : FlatList<'T> = list1.AddRange (list2 :> System.Collections.Generic.IEnumerable<'T>) - /// Searches for the specified object and returns the zero-based index of the first occurrence within the range - /// of elements in the that starts at the specified index and - /// contains the specified number of elements. - /// The equality comparer to use - /// The starting index - /// The number of elements to search - /// The item to search for - /// The input - /// The zero-based index of the first occurrence of the item + [] let indexRangeWith comparer index count item (list : FlatList<'T>) = list.IndexOf (item, index, count, comparer) - /// Searches for the specified object and returns the zero-based index of the first occurrence within the range - /// of elements in the that starts at the specified index and - /// contains the specified number of elements. - /// The starting index - /// The number of elements to search - /// The item to search for - /// The input - /// The zero-based index of the first occurrence of the item + [] let indexRange index count item list = indexRangeWith HashIdentity.Structural index count item list - /// Searches for the specified object and returns the zero-based index of the first occurrence within the range - /// of elements in the that starts at the specified index and - /// contains the specified number of elements. - /// The equality comparer to use - /// The starting index - /// The item to search for - /// The input - /// The zero-based index of the first occurrence of the item + [] let indexFromWith comparer index item list = indexRangeWith comparer index (length list - index) item list - /// Searches for the specified object and returns the zero-based index of the first occurrence within the range - /// of elements in the that starts at the specified index and - /// contains the specified number of elements. - /// The starting index - /// The item to search for - /// The input - /// The zero-based index of the first occurrence of the item + [] let indexFrom index item list = indexFromWith HashIdentity.Structural index item list - /// Searches for the specified object and returns the zero-based index of the first occurrence within the range - /// of elements in the that starts at the specified index and - /// contains the specified number of elements. - /// The equality comparer to use - /// The item to search for - /// The input - /// The zero-based index of the first occurrence of the item + [] let indexWith comparer item list = indexFromWith comparer 0 item list - /// Searches for the specified object and returns the zero-based index of the first occurrence within the range - /// of elements in the that starts at the specified index and - /// contains the specified number of elements. - /// The item to search for - /// The input - /// The zero-based index of the first occurrence of the item - let index item list = indexWith HashIdentity.Structural item list - - /// Searches for the specified object and returns the zero-based index of the last occurrence within the - /// range of elements in the that contains the specified number - /// of elements and ends at the specified index. - /// The equality comparer to use - /// The ending index - /// The number of elements to search - /// The item to search for - /// The input - /// The zero-based index of the last occurrence of the item + [] + let index item (list : FlatList<'T>) = + let idx = list.IndexOf (item) + if idx = -1 then indexNotFound () else idx + + [] let lastIndexRangeWith comparer index count item (list : FlatList<'T>) = list.LastIndexOf (item, index, count, comparer) - /// Searches for the specified object and returns the zero-based index of the last occurrence within the - /// range of elements in the that contains the specified number - /// of elements and ends at the specified index. - /// The ending index - /// The number of elements to search - /// The item to search for - /// The input - /// The zero-based index of the last occurrence of the item + [] let lastIndexRange index count item list = lastIndexRangeWith HashIdentity.Structural index count item list - /// Searches for the specified object and returns the zero-based index of the last occurrence within the - /// range of elements in the that contains the specified number - /// of elements and ends at the specified index. - /// The equality comparer to use - /// The ending index - /// The item to search for - /// The input - /// The zero-based index of the last occurrence of the item + [] let lastIndexFromWith comparer index item list = lastIndexRangeWith comparer index (index + 1) item list - /// Searches for the specified object and returns the zero-based index of the last occurrence within the - /// range of elements in the that contains the specified number - /// of elements and ends at the specified index. - /// The ending index - /// The item to search for - /// The input - /// The zero-based index of the last occurrence of the item + [] let lastIndexFrom index item list = lastIndexFromWith HashIdentity.Structural index item list - /// Searches for the specified object and returns the zero-based index of the last occurrence within the - /// range of elements in the that contains the specified number - /// of elements and ends at the specified index. - /// The equality comparer to use - /// The item to search for - /// The input - /// The zero-based index of the last occurrence of the item + [] let lastIndexWith comparer item list = lastIndexFromWith comparer (length list - 1) item list - /// Searches for the specified object and returns the zero-based index of the last occurrence within the - /// range of elements in the that contains the specified number - /// of elements and ends at the specified index. - /// The item to search for - /// The input - /// The zero-based index of the last occurrence of the item - let lastIndex item list = lastIndexWith HashIdentity.Structural item list - - /// Removes the specified objects from the with the given comparer. - /// The equality comparer to use - /// The items to remove - /// The input - /// A new with the specified items removed - let removeAllWith comparer (items : 'T seq) (list : FlatList<'T>) : FlatList<'T> = list.RemoveRange (items, comparer) - - /// Removes the specified objects from the . - /// The items to remove - /// The input - /// A new with the specified items removed + [] + let lastIndex item (list : FlatList<'T>) = + let idx = list.LastIndexOf (item) + if idx = -1 then indexNotFound () else idx + + [] + let removeAllWith comparer (items : 'T seq) (list : FlatList<'T>) : FlatList<'T> = + let itemsToRemove = HashSet (items, comparer) + list.RemoveAll (System.Predicate (fun x -> itemsToRemove.Contains x)) + + [] let removeAll items (list : FlatList<'T>) = removeAllWith HashIdentity.Structural items list - /// Removes all the elements that do not match the conditions defined by the specified predicate. - /// The predicate to test elements - /// The input - /// A new with elements that match the predicate - /// - /// - /// let numbers = FlatList.ofArray [|1; 2; 3; 4; 5; 6|] - /// let evens = FlatList.filter (fun x -> x % 2 = 0) numbers - /// // evens is [|2; 4; 6|] - /// - /// + [] let filter (predicate : 'T -> bool) (list : FlatList<'T>) : FlatList<'T> = list.RemoveAll (System.Predicate (not << predicate)) - /// Removes all the elements that do not match the conditions defined by the specified predicate. - /// The predicate to test elements - /// The input - /// A new with elements that match the predicate + [] let where (predicate : 'T -> bool) (list : FlatList<'T>) : FlatList<'T> = filter predicate list - /// Removes a range of elements from the . - /// The starting index - /// The number of elements to remove - /// The input - /// A new with the specified range of elements removed + [] let removeRange index (count : int) (list : FlatList<'T>) : FlatList<'T> = list.RemoveRange (index, count) - /// Copies a range of elements from the source to the destination array - /// The source - /// The starting index in the source - /// The destination array - /// The starting index in the destination array - /// The number of elements to copy - /// Thrown when the range is invalid + [] let blit (source : FlatList<'T>) sourceIndex (destination : 'T[]) destinationIndex count = source.CopyTo (sourceIndex, destination, destinationIndex, count) - /// Sorts a range of elements in the using the specified comparer - /// The comparer to use - /// The starting index - /// The number of elements to sort - /// The input - /// A new with the specified range of elements sorted + [] let sortRangeWithComparer comparer index count (list : FlatList<'T>) = list.Sort (index, count, comparer) - /// Sorts a range of elements in the using the specified comparison function - /// The comparison function to use - /// The starting index - /// The number of elements to sort - /// The input - /// A new with the specified range of elements sorted + [] let sortRangeWith comparer index count list = sortRangeWithComparer (ComparisonIdentity.FromFunction comparer) index count list - /// Sorts a range of elements in the using the default comparer - /// The starting index - /// The number of elements to sort - /// The input - /// A new with the specified range of elements sorted + [] let sortRange index count list = sortRangeWithComparer ComparisonIdentity.Structural index count list - /// Sorts the elements in the using the specified comparer - /// The comparer to use - /// The input - /// A new with the elements sorted + [] let sortWithComparer (comparer : System.Collections.Generic.IComparer<'T>) (list : FlatList<'T>) = list.Sort (comparer) - /// Sorts the elements in the using the specified comparison function - /// The comparison function to use - /// The input - /// A new with the elements sorted + [] let sortWith comparer list = sortWithComparer (ComparisonIdentity.FromFunction comparer) list - /// Sorts the elements in the using the default comparer - /// The input - /// A new with the elements sorted + [] let sort (list : FlatList<'T>) = list.Sort () - /// Returns a new array that contains elements of the original array sorted in descending order. - /// The input array. - /// The sorted array. + [] + let rev (list : FlatList<'T>) : FlatList<'T> = + if list.IsDefaultOrEmpty then + list + else + let len = list.Length + let builder = FlatListFactory.CreateBuilder<'T> len + for i = len - 1 downto 0 do + builder.Add list.[i] + builder.MoveToImmutable () + + [] let inline sortDescending (list : FlatList<'T>) : FlatList<'T> when 'T : comparison = sortWith (fun x y -> compare y x) list - /// Returns a new array that contains elements of the original array sorted in descending order using the specified projection. - /// The function to transform the elements into a type that supports comparison. - /// The input array. - /// The sorted array. + [] let inline sortByDescending (projection : 'T -> 'Key) (list : FlatList<'T>) : FlatList<'T> when 'Key : comparison = sortWith (fun x y -> compare (projection y) (projection x)) list - /// Sorts the array using keys given by the given projection. Keys are compared using Operators.compare. - /// The function to transform the elements into a type supporting comparison. - /// The input array. - /// The sorted array. - let inline sortBy (projection : 'T -> 'Key) (list : FlatList<'T>) : FlatList<'T> when 'Key : comparison = - sortWith (fun x y -> compare (projection x) (projection y)) list + [] + let sortBy (projection : 'T -> 'Key) (list : FlatList<'T>) : FlatList<'T> when 'Key : comparison = + if list.IsDefaultOrEmpty then + list + else + let items = list.ToArray () // Work with a mutable array for sorting + System.Array.Sort (items, fun x y -> compare (projection x) (projection y)) + FlatListFactory.CreateRange (items) ////////// Loop-based (now LINQ-based where applicable) ////////// - /// Concatenates a of s into a single - /// The of s to concatenate - /// A new containing all elements from the input s + [] let concat (arrs : FlatList>) = - let builder = FlatListFactory.CreateBuilder<'T> (arrs.Sum _.Length) + let totalLength = Seq.sumBy (fun (innerList : FlatList<'T>) -> innerList.Length) arrs + let builder = FlatListFactory.CreateBuilder<'T> (totalLength) for i = 0 to arrs.Length - 1 do let arr = arrs.[i] - for j = 0 to arrs.[i].Length - 1 do + for j = 0 to arr.Length - 1 do builder.Add (arr.[j]) builder.MoveToImmutable () - /// Builds a new from the elements of a by applying a mapping function to each element - /// A function to transform elements from the input - /// The input - /// A containing the transformed elements - /// - /// - /// let numbers = FlatList.ofArray [|1; 2; 3; 4; 5|] - /// let squares = FlatList.map (fun x -> x * x) numbers - /// // squares is [|1; 4; 9; 16; 25|] - /// - /// + [] let inline map (mapping : 'T -> 'U) (list : FlatList<'T>) : FlatList<'U> = list.Select(mapping).ToImmutableArray () - /// Build a new array whose elements are the results of applying the given function - /// to each of the elements of the array. The integer index passed to the - /// function indicates the index of element being transformed. - /// A function to transform an element and its index into a result element. - /// The input array. - /// The array of transformed elements. + [] let mapi (mapping : int -> 'T -> 'U) (list : FlatList<'T>) : FlatList<'U> = list.Select(fun x i -> mapping i x).ToImmutableArray () - /// Builds a new array whose elements are the results of applying the given function - /// to the corresponding elements of the two collections pairwise, also passing the index of - /// the elements. The two input arrays must have the same lengths. - /// The function to transform pairs of input elements and their indices. - /// The first input array. - /// The second input array. - /// The array of transformed elements. + [] let mapi2 (mapping : int -> 'T1 -> 'T2 -> 'U) (list1 : FlatList<'T1>) (list2 : FlatList<'T2>) : FlatList<'U> = let len1 = list1.Length if len1 <> list2.Length then @@ -439,13 +314,22 @@ module FlatList = Enumerable.Range(0, len1).Select(fun i -> mapping i list1.[i] list2.[i]).ToImmutableArray () - /// Builds a new collection whose elements are the results of applying the given function - /// to the corresponding elements of the two collections pairwise. The two input - /// arrays must have the same lengths. - /// The function to transform the pairs of the input elements. - /// The first input array. - /// The second input array. - /// The array of transformed elements. + [] + let mapi3 + (mapping : int -> 'T1 -> 'T2 -> 'T3 -> 'U) + (list1 : FlatList<'T1>) + (list2 : FlatList<'T2>) + (list3 : FlatList<'T3>) + : FlatList<'U> = + let len1 = list1.Length + if len1 <> list2.Length then + invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths + if len1 <> list3.Length then + invalidArg (nameof list3) ErrorStrings.ListsHaveDifferentLengths + + Enumerable.Range(0, len1).Select(fun i -> mapping i list1.[i] list2.[i] list3.[i]).ToImmutableArray () + + [] let map2 (mapping : 'T1 -> 'T2 -> 'U) (list1 : FlatList<'T1>) (list2 : FlatList<'T2>) : FlatList<'U> = let len1 = list1.Length if len1 <> list2.Length then @@ -453,227 +337,359 @@ module FlatList = Enumerable.Range(0, len1).Select(fun i -> mapping list1.[i] list2.[i]).ToImmutableArray () - /// Counts the number of elements in the that satisfy the given predicate - /// A function to project elements from the input - /// The input - /// A of key-value pairs where the key is the projected value and the value is the count + [] + let map3 + (mapping : 'T1 -> 'T2 -> 'T3 -> 'U) + (list1 : FlatList<'T1>) + (list2 : FlatList<'T2>) + (list3 : FlatList<'T3>) + : FlatList<'U> = + let len1 = list1.Length + if len1 <> list2.Length then + invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths + if len1 <> list3.Length then + invalidArg (nameof list3) ErrorStrings.ListsHaveDifferentLengths + + Enumerable.Range(0, len1).Select(fun i -> mapping list1.[i] list2.[i] list3.[i]).ToImmutableArray () + + [] + let mapFold<'T, 'State, 'Result> + (mapping : 'State -> 'T -> 'Result * 'State) + (state : 'State) + (list : FlatList<'T>) + : FlatList<'Result> * 'State = + checkNotDefault (nameof list) list + let len = list.Length + + if len = 0 then + empty, state + else + let resultBuilder = FlatListFactory.CreateBuilder<'Result> (len) + resultBuilder.Count <- len + + let mutable currentState = state + + for i = 0 to len - 1 do + let item = list.[i] + let result, newState = mapping currentState item + resultBuilder.[i] <- result + currentState <- newState + + resultBuilder.MoveToImmutable (), currentState + + [] + let mapFoldBack<'T, 'State, 'Result> + (mapping : 'T -> 'State -> 'Result * 'State) + (list : FlatList<'T>) + (state : 'State) + : FlatList<'Result> * 'State = + checkNotDefault (nameof list) list + let len = list.Length + + if len = 0 then + empty, state + else + let resultBuilder = FlatListFactory.CreateBuilder<'Result> (len) + resultBuilder.Count <- len + + let mutable currentState = state + + for i = len - 1 downto 0 do + let item = list.[i] + let result, newState = mapping item currentState + resultBuilder.[i] <- result + currentState <- newState + + resultBuilder.MoveToImmutable (), currentState + + [] let countBy (projection : 'T -> 'Key) (list : FlatList<'T>) = - list.GroupBy(projection).Select(fun group -> struct (group.Key, group.Count ())).ToImmutableArray () + list.GroupBy(projection).Select(fun group -> struct (group.Key, Seq.length group)).ToImmutableArray () - /// Creates a containing the elements of the original paired with their indices - /// The input - /// A containing pairs of indices and elements + [] let indexed (list : FlatList<'T>) = list.Select(fun item index -> struct (index, item)).ToImmutableArray () - /// Applies the given function to each element of the - /// A function to apply to each element - /// The input - let inline iter action list = + [] + let inline iter (action : 'T -> unit) (list : FlatList<'T>) = for item in list do action item - /// Applies the given function to each element of the and its index - /// A function to apply to each element and its index - /// The input + [] let iteri action (list : FlatList<'T>) = for i = 0 to list.Length - 1 do do action i list.[i] - /// Applies the given function to pair of elements at the same position in the two s - /// A function to apply to pairs of elements - /// The first input - /// The second input - /// Thrown when the s have different lengths - let iter2 action (list1 : FlatList<'T>) (list2 : FlatList<'T>) = + [] + let iter2 (action : 'T1 -> 'T2 -> unit) (list1 : FlatList<'T1>) (list2 : FlatList<'T2>) = let len = list1.Length if len <> list2.Length then invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths for i = 0 to len - 1 do do action list1.[i] list2.[i] - /// Applies the given function to the pair of elements at the same position in the two s along with their index - /// A function to apply to pairs of elements and their index - /// The first input - /// The second input - /// Thrown when the s have different lengths - let iteri2 action (list1 : FlatList<'T>) (list2 : FlatList<'T>) = + [] + let iter3 (action : 'T1 -> 'T2 -> 'T3 -> unit) (list1 : FlatList<'T1>) (list2 : FlatList<'T2>) (list3 : FlatList<'T3>) = + let len1 = list1.Length + if len1 <> list2.Length then + invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths + if len1 <> list3.Length then + invalidArg (nameof list3) ErrorStrings.ListsHaveDifferentLengths + for i = 0 to len1 - 1 do + action list1.[i] list2.[i] list3.[i] + + [] + let iteri2 (action : int -> 'T1 -> 'T2 -> unit) (list1 : FlatList<'T1>) (list2 : FlatList<'T2>) = let len1 = list1.Length if len1 <> list2.Length then invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths for i = 0 to len1 - 1 do action i list1.[i] list2.[i] - /// Tests if any element of the satisfies the given predicate - /// A function to test each element - /// The input - /// True if any element satisfies the predicate, false otherwise + [] + let iteri3 + (action : int -> 'T1 -> 'T2 -> 'T3 -> unit) + (list1 : FlatList<'T1>) + (list2 : FlatList<'T2>) + (list3 : FlatList<'T3>) + = + let len1 = list1.Length + if len1 <> list2.Length then + invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths + if len1 <> list3.Length then + invalidArg (nameof list3) ErrorStrings.ListsHaveDifferentLengths + for i = 0 to len1 - 1 do + action i list1.[i] list2.[i] list3.[i] + + [] let exists (predicate : 'T -> bool) (list : FlatList<'T>) = list.Any (predicate) - /// Tests if any corresponding pair of elements from the two s satisfies the given predicate - /// A function to test pairs of elements - /// The first input - /// The second input - /// True if any pair of elements satisfies the predicate, false otherwise - /// Thrown when the s have different lengths - let exists2 (predicate : 'T -> 'T -> bool) (list1 : FlatList<'T>) (list2 : FlatList<'T>) = + [] + let exists2 (predicate : 'T1 -> 'T2 -> bool) (list1 : FlatList<'T1>) (list2 : FlatList<'T2>) = + let len = list1.Length + if len <> list2.Length then + invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths + let rec loop i = i < len && (predicate list1.[i] list2.[i] || loop (i + 1)) + loop 0 + + [] + let exists3 (predicate : 'T1 -> 'T2 -> 'T3 -> bool) (list1 : FlatList<'T1>) (list2 : FlatList<'T2>) (list3 : FlatList<'T3>) = let len1 = list1.Length if len1 <> list2.Length then invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths - let rec loop i = i < len1 && (predicate list1.[i] list2.[i] || loop (i + 1)) + if len1 <> list3.Length then + invalidArg (nameof list3) ErrorStrings.ListsHaveDifferentLengths + let rec loop i = + i < len1 + && (predicate list1.[i] list2.[i] list3.[i] || loop (i + 1)) loop 0 - /// Tests if all elements of the satisfy the given predicate - /// A function to test each element - /// The input - /// True if all elements satisfy the predicate, false otherwise + [] let forall (predicate : 'T -> bool) (list : FlatList<'T>) = list.All (predicate) - /// Tests if all corresponding pairs of elements from the two s satisfy the given predicate - /// A function to test pairs of elements - /// The first input - /// The second input - /// True if all pairs of elements satisfy the predicate, false otherwise - /// Thrown when the s have different lengths - let forall2 predicate (list1 : FlatList<'T>) (list2 : FlatList<'T>) = + [] + let forall2 (predicate : 'T1 -> 'T2 -> bool) (list1 : FlatList<'T1>) (list2 : FlatList<'T2>) = let len1 = list1.Length if len1 <> list2.Length then invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths let rec loop i = i >= len1 || (predicate list1.[i] list2.[i] && loop (i + 1)) loop 0 - /// Tests if the given element exists in the - /// The element to find - /// The input - /// True if the element exists in the , false otherwise + [] + let forall3 (predicate : 'T1 -> 'T2 -> 'T3 -> bool) (list1 : FlatList<'T1>) (list2 : FlatList<'T2>) (list3 : FlatList<'T3>) = + let len1 = list1.Length + if len1 <> list2.Length then + invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths + if len1 <> list3.Length then + invalidArg (nameof list3) ErrorStrings.ListsHaveDifferentLengths + let rec loop i = + i >= len1 + || (predicate list1.[i] list2.[i] list3.[i] && loop (i + 1)) + loop 0 + + [] let inline contains item (list : FlatList<'T>) = list.Contains (item) - /// Splits the into two s, containing the elements for which the given predicate returns true and false respectively - /// A function to test each element - /// The input - /// A tuple of two s, containing the elements for which the predicate returns true and false respectively + [] let partition (predicate : 'T -> bool) (list : FlatList<'T>) = let res1 = builderWith list.Length let res2 = builderWith list.Length for x in list do // Iteration will cause InvalidOperationException if list is default if predicate x then res1.Add x else res2.Add x - (ofBuilder res1, ofBuilder res2) + (res1.ToImmutable (), res2.ToImmutable ()) - - /// Returns the first element for which the given predicate returns true - /// A function to test elements - /// The input - /// The first element for which the predicate returns true - /// Thrown if no element satisfies the predicate + [] let find (predicate : 'T -> bool) (list : FlatList<'T>) = list.First (predicate) - /// Returns the first element for which the given predicate returns true, or ValueNone if no such element exists - /// A function to test elements - /// The input - /// ValueSome value if an element satisfies the predicate, ValueNone otherwise + [] let tryFind (predicate : 'T -> bool) (list : FlatList<'T>) : 'T voption = list.Where (predicate) |> Seq.vtryHead - /// Returns the last element for which the given predicate returns true - /// A function to test elements - /// The input - /// The last element for which the predicate returns true - /// Thrown if no element satisfies the predicate + [] let findBack (predicate : 'T -> bool) (list : FlatList<'T>) = list.Last (predicate) - /// Returns the last element for which the given predicate returns true, or ValueNone if no such element exists - /// A function to test elements - /// The input - /// ValueSome value if an element satisfies the predicate, ValueNone otherwise + [] + let findLast (predicate : 'T -> bool) (list : FlatList<'T>) = list.Last (predicate) + + [] let tryFindBack (predicate : 'T -> bool) (list : FlatList<'T>) : 'T voption = - seq { - for i = list.Length - 1 downto 0 do - yield list.[i] - } - |> Seq.where predicate - |> Seq.vtryHead + let mutable result = ValueNone + for i = 0 to list.Length - 1 do + let item = list.[i] + if predicate item then + result <- ValueSome item + result - /// Returns the last index for which the given predicate returns true - /// A function to test elements - /// The input - /// The last index for which the predicate returns true - /// Thrown if no element satisfies the predicate + [] let findIndexBack (predicate : 'T -> bool) (list : FlatList<'T>) = - let len = list.Length - 1 - seq { - for i = len downto 0 do - yield struct (len - i, list.[i]) - } - |> Seq.where (fun struct (i, item) -> predicate item) - |> Seq.map (fun struct (i, item) -> i) - |> Seq.head - - /// Returns the last index for which the given predicate returns true, or ValueNone if no such element exists - /// A function to test elements - /// The input - /// ValueSome index if an element satisfies the predicate, ValueNone otherwise + let mutable idx = -1 + for i = 0 to list.Length - 1 do + if predicate list.[i] then + idx <- i + if idx >= 0 then idx else sequenceNotFound () + + [] + let tryFindLast (predicate : 'T -> bool) (list : FlatList<'T>) : 'T voption = + let mutable result = ValueNone + for i = list.Length - 1 downto 0 do + let item = list.[i] + if predicate item && ValueOption.isNone result then + result <- ValueSome item + result + + [] let tryFindIndexBack (predicate : 'T -> bool) (list : FlatList<'T>) : int voption = - let len = list.Length - 1 - seq { - for i = len downto 0 do - yield struct (len - i, list.[i]) - } - |> Seq.where (fun struct (i, item) -> predicate item) - |> Seq.map (fun struct (i, item) -> i) - |> Seq.vtryHead - - /// Returns the first value for which the given function returns ValueSome value - /// A function to generate options from the elements - /// The input - /// The first value for which the chooser returns ValueSome value - /// Thrown if the chooser returns ValueNone for all elements + if list.IsDefaultOrEmpty then + ValueNone + else + let mutable i = list.Length - 1 + let mutable result = ValueNone + + while i >= 0 && ValueOption.isNone result do + if predicate list.[i] then + result <- ValueSome i + i <- i - 1 + + result + + [] + let findLastIndex (predicate : 'T -> bool) (list : FlatList<'T>) : int = + let mutable found = false + let mutable idx = -1 + for i = list.Length - 1 downto 0 do + if predicate list.[i] && not found then + idx <- i + found <- true + if found then idx else sequenceNotFound () + + [] + let tryFindLastIndex (predicate : 'T -> bool) (list : FlatList<'T>) : int voption = + let mutable result = ValueNone + for i = list.Length - 1 downto 0 do + if predicate list.[i] && ValueOption.isNone result then + result <- ValueSome i + result + + [] let pick (chooser : 'T -> 'U voption) (list : FlatList<'T>) = - list.Select(chooser).Where(ValueOption.isSome).Select (ValueOption.get) - |> Seq.head + checkNotDefault (nameof list) list + let mutable result = ValueNone + let mutable i = 0 + while i < list.Length && ValueOption.isNone result do + result <- chooser list.[i] + i <- i + 1 + match result with + | ValueSome x -> x + | ValueNone -> sequenceNotFound () - /// Returns the first value for which the given function returns ValueSome value, or ValueNone if no such element exists - /// A function to generate options from the elements - /// The input - /// The first value for which the chooser returns ValueSome value, or ValueNone + [] let tryPick (chooser : 'T -> 'U voption) (list : FlatList<'T>) : 'U voption = list.Select(chooser).Where(ValueOption.isSome).Select (ValueOption.get) |> Seq.vtryHead - /// Builds a new containing only the elements for which the given function returns ValueSome value - /// A function to generate options from the elements - /// The input - /// A containing the values wrapped in ValueSome by the chooser - let choose (chooser : 'T -> 'T voption) (list : FlatList<'T>) = + [] + let pickBack (chooser : 'T -> 'U voption) (list : FlatList<'T>) : 'U = + let mutable result = ValueNone + for i = list.Length - 1 downto 0 do + let v = chooser list.[i] + if ValueOption.isSome v && ValueOption.isNone result then + result <- v + match result with + | ValueSome x -> x + | ValueNone -> sequenceNotFound () + + [] + let tryPickBack (chooser : 'T -> 'U voption) (list : FlatList<'T>) : 'U voption = + let mutable result = ValueNone + for i = list.Length - 1 downto 0 do + let v = chooser list.[i] + if ValueOption.isSome v && ValueOption.isNone result then + result <- v + result + + [] + let choose (chooser : 'T -> 'U voption) (list : FlatList<'T>) : FlatList<'U> = list.Select(chooser).Where(ValueOption.isSome).Select(ValueOption.get).ToImmutableArray () - /// Creates a by applying a key-generating function to each element of the and grouping the elements by the resulting keys - /// A function to transform elements into keys - /// The input - /// A of tuples where each tuple contains a key and a of all elements that match the key + [] + let chooseBack (chooser : 'T -> 'U voption) (list : FlatList<'T>) : FlatList<'U> = + let builder = FlatListFactory.CreateBuilder<'U> () + for i = list.Length - 1 downto 0 do + match chooser list.[i] with + | ValueSome v -> builder.Add v + | ValueNone -> () + builder.ToImmutable () + + [] let groupBy (projection : 'T -> 'Key) (list : FlatList<'T>) = list.GroupBy(projection).Select(fun group -> struct (group.Key, group.ToImmutableArray ())).ToImmutableArray () - /// Returns a new that contains the elements of the original but with duplicates eliminated by using the supplied projection function - /// A function to transform elements before comparing them - /// The input - /// A with distinct elements as determined by the projection function + [] let distinctBy (projection : 'T -> 'Key) (list : FlatList<'T>) = - let setBuilder = ImmutableHashSet.CreateBuilder<'Key> () - let arrayBuilder = ImmutableArray.CreateBuilder<'T> () - for i = 0 to list.Length - 1 do - let item = list.[i] // list.[i] will throw if list is default - if setBuilder.Add (projection item) then - arrayBuilder.Add (item) - arrayBuilder.ToImmutable () - - /// Creates a new by applying a mapping function to each element of the input and concatenating the results - /// A function to transform elements of the input into s - /// The input - /// A containing the concatenation of all the s generated by the mapping function + if list.IsDefaultOrEmpty then + list + else + let setBuilder = ImmutableHashSet.CreateBuilder<'Key> () + let arrayBuilder = ImmutableArray.CreateBuilder<'T> () + for i = 0 to list.Length - 1 do + let item = list.[i] + if setBuilder.Add (projection item) then + arrayBuilder.Add (item) + arrayBuilder.ToImmutable () + + [] + let findDup (list : FlatList<'T>) = + checkNotDefault (nameof list) list + let seen = System.Collections.Generic.HashSet<'T> () + let mutable result = ValueNone + let mutable i = 0 + while i < list.Length && ValueOption.isNone result do + let item = list.[i] + if not (seen.Add (item)) then + result <- ValueSome item + i <- i + 1 + match result with + | ValueSome x -> x + | ValueNone -> indexNotFound () + + [] + let findDupBy (projection : 'T -> 'Key) (list : FlatList<'T>) = + checkNotDefault (nameof list) list + let seen = System.Collections.Generic.HashSet<'Key> () + let mutable result = ValueNone + let mutable i = 0 + while i < list.Length && ValueOption.isNone result do + let item = list.[i] + let key = projection item + if not (seen.Add (key)) then + result <- ValueSome item + i <- i + 1 + match result with + | ValueSome x -> x + | ValueNone -> indexNotFound () + + [] let collect (mapping : 'T -> 'U seq) (list : FlatList<'T>) : FlatList<'U> = list.SelectMany(mapping).ToImmutableArray () - /// Gets an element in the at the specified index - /// The index of the element to retrieve - /// The input - /// ValueSome value containing the element, or ValueNone if the index is out of range + [] let tryItem index (list : FlatList<'T>) : voption<'T> = // list.Length or list.[index] will throw if list is default before comparison happens if list.IsDefault then @@ -683,209 +699,182 @@ module FlatList = else ValueNone - /// Returns the first element of the - /// The input - /// The first element of the - /// Thrown when the is empty + [] let head (list : FlatList<'T>) = list.First () - /// Returns the first element of the , or ValueNone if the is empty - /// The input - /// ValueSome value containing the first element, or ValueNone if the is empty - let tryHead (list : FlatList<'T>) : 'T voption = if list.IsEmpty then ValueNone else ValueSome list.[0] + [] + let tryHead (list : FlatList<'T>) : 'T voption = + if list.IsDefaultOrEmpty then + ValueNone + else + ValueSome list.[0] - /// Returns the last element of the - /// The input - /// The last element of the - /// Thrown when the is empty - let last (list : FlatList<_>) = list.Last () // Enumerable.Last throws if empty or default + [] + let last (list : FlatList<_>) = list.Last () - /// Returns the last element of the , or ValueNone if the is empty - /// The input - /// ValueSome value containing the last element, or ValueNone if the is empty + [] let tryLast (list : FlatList<'T>) : 'T voption = - if list.IsEmpty then + if list.IsDefaultOrEmpty then ValueNone else ValueSome list.[list.Length - 1] - /// Returns the without its first element - /// The input - /// A containing all elements of the input except the first one + [] let tail (list : FlatList<'T>) = - if list.IsEmpty then + if list.IsDefaultOrEmpty then invalidArg (nameof list) "List must not be empty to get tail." list.Slice (1, list.Length - 1) - /// Returns the without its first element, or ValueNone if the is empty - /// The input - /// ValueSome value containing the without its first element, or ValueNone if the is empty + [] let tryTail (list : FlatList<'T>) : voption> = - if list.IsEmpty then + if list.IsDefaultOrEmpty then ValueNone else ValueSome (list.Slice (1, list.Length - 1)) - /// Returns the first N elements of the - /// The number of elements to take - /// The input - /// A containing the first N elements + [] + let tryHeadAndTail (list : FlatList<'T>) : ('T * FlatList<'T>) voption = + if list.IsDefaultOrEmpty then + ValueNone + else + ValueSome (list.[0], list.Slice (1, list.Length - 1)) + + [] + let tryLastAndInit (list : FlatList<'T>) : (FlatList<'T> * 'T) voption = + if list.IsDefaultOrEmpty then + ValueNone + else + ValueSome (list.Slice (0, list.Length - 1), list.[list.Length - 1]) + + [] let take (count : int) (list : FlatList<'T>) = if count < 0 then invalidArg (nameof count) ErrorStrings.InputMustBeNonNegative - let len = list.Length // Will throw if list is default + let len = list.Length if count = 0 then empty elif count >= len then list else list.Slice (0, count) - /// Returns a containing the first elements of the input for which the given predicate returns true - /// A function to test each element - /// The input - /// A containing the first elements for which the predicate returns true + [] + let takeEnd (count : int) (list : FlatList<'T>) : FlatList<'T> = + if count < 0 || count > list.Length then + invalidArg (nameof count) ErrorStrings.InputMustBeNonNegative + if count = 0 then + empty + else + list.Slice (list.Length - count, count) + + [] let takeWhile (predicate : 'T -> bool) (list : FlatList<'T>) = list.TakeWhile(predicate).ToImmutableArray () - /// Returns the without its first N elements - /// The number of elements to skip - /// The input - /// A containing all except the first N elements + [] let skip index (list : FlatList<'T>) = if index < 0 then invalidArg (nameof index) ErrorStrings.InputMustBeNonNegative - let len = list.Length // Will throw if list is default + let len = list.Length if index = 0 then list elif index >= len then empty else list.Slice (index, len - index) - /// Returns a that skips the elements of the input while the given predicate returns true, then returns the rest - /// A function to test each element - /// The input - /// A that skips the elements while the predicate returns true, then contains the rest + [] + let skipEnd (count : int) (list : FlatList<'T>) : FlatList<'T> = + if count < 0 || count > list.Length then + invalidArg (nameof count) ErrorStrings.InputMustBeNonNegative + if count = 0 then + list + else + list.Slice (0, list.Length - count) + + [] let skipWhile (predicate : 'T -> bool) (list : FlatList<'T>) = list.SkipWhile(predicate).ToImmutableArray () - /// Gets a sublist of the input - /// The index of the first element to include - /// The number of elements in the sublist - /// The input - /// A containing the elements from start index for the given count + [] let sub start count (list : FlatList<'T>) = list.Slice (start, count) - /// Returns a that contains no more than N elements of the input - /// The maximum number of elements to include - /// The input - /// A containing at most N elements - let truncate count (list : FlatList<'T>) = if count < list.Length then list.Slice (0, count) else list // list.Length throws if default + [] + let truncate count (list : FlatList<'T>) = if count < list.Length then list.Slice (0, count) else list - /// Splits the into two s at the specified index - /// The index at which to split the - /// The input - /// A tuple of two s, the first containing the elements up to the index, the second containing the rest + [] let splitAt index (list : FlatList<'T>) = (list.Slice (0, index), list.Slice (index, list.Length - index)) - /// Applies a function to the builder and returns the resulting - /// The function to apply to the builder - /// The created from the builder after applying the function + [] + let chunkBySize chunkSize (list : FlatList<'T>) = + if chunkSize <= 0 then + invalidArg (nameof chunkSize) ErrorStrings.InputMustBeNonNegative + let len = list.Length + if len = 0 then + empty + else + let numChunks = (len + chunkSize - 1) / chunkSize + let builder = FlatListFactory.CreateBuilder> (numChunks) + + for i = 0 to numChunks - 1 do + let start = i * chunkSize + if start < len then + let remaining = len - start + let count = min chunkSize remaining + builder.Add (list.Slice (start, count)) + + builder.ToImmutable () + + [] let inline build f = let builder = builder () f builder - moveFromBuilder builder + builder.ToImmutable () - /// Updates the by applying a function to a builder initialized with the 's elements - /// The function to apply to the builder - /// The input - /// The updated + [] let inline update f (list : FlatList<'T>) = - let builder = toBuilder list // toBuilder will throw if list is default + let builder = toBuilder list f builder - moveFromBuilder builder + builder.ToImmutable () - /// Returns the index of the first element in the that satisfies the given predicate - /// The function to test the input elements - /// The input - /// The index of the first element that satisfies the predicate - /// Thrown if no element satisfies the predicate + [] let findIndex (predicate : 'T -> bool) (list : FlatList<'T>) = - list.Select (fun item i -> struct (item, i)) - |> Seq.where (fun struct (item, i) -> predicate item) - |> Seq.map (fun struct (item, i) -> i) - |> Seq.head + checkNotDefault (nameof list) list + + let mutable index = -1 + let mutable found = false + let len = list.Length + + if len = 0 then + sequenceNotFound () - /// Returns the index of the first element in the that satisfies the given predicate, or ValueNone if no such element exists - /// The function to test the input elements - /// The input - /// The index of the first element that satisfies the predicate, or ValueNone + let mutable i = 0 + while i < len && not found do + if predicate list.[i] then + index <- i + found <- true + else + i <- i + 1 + + if found then index else sequenceNotFound () + + [] let tryFindIndex (predicate : 'T -> bool) (list : FlatList<'T>) : int voption = list.Select (fun item i -> struct (item, i)) |> Seq.where (fun struct (item, i) -> predicate item) |> Seq.map (fun struct (item, i) -> i) |> Seq.vtryHead - /// Returns a new containing elements corresponding to a sliding window of elements from the input - /// The size of the window - /// The input - /// The resulting of sliding windows - /// Thrown when windowSize is not positive or when is default - /// - /// - /// let numbers = FlatList.ofArray [|1; 2; 3; 4; 5|] - /// let windows = FlatList.windowed 3 numbers - /// // windows is [|[|1; 2; 3|]; [|2; 3; 4|]; [|3; 4; 5|]|] - /// - /// // Calculate moving averages - /// let movingAverages = - /// windows - /// |> FlatList.map (fun window -> - /// FlatList.average window) - /// // movingAverages is [|2.0; 3.0; 4.0|] - /// - /// + [] let windowed windowSize (list : FlatList<'T>) = if windowSize < 1 then invalidArg (nameof windowSize) ErrorStrings.InputMustBeNonNegative - let len = list.Length // Will throw if list is default + let len = list.Length if windowSize > len then empty else - Enumerable - .Range(0, len - windowSize + 1) - .Select(fun i -> list.Slice (i, windowSize)) // list.Slice throws if list is default (already caught by len) - .ToImmutableArray () + Enumerable.Range(0, len - windowSize + 1).Select(fun i -> list.Slice (i, windowSize)).ToImmutableArray () - /// Returns a new containing pairs of adjacent elements from the input - /// The input - /// The resulting of pairs + [] let pairwise (list : FlatList<'T>) = if list.Length < 2 then - empty // list.Length throws if default - else - Enumerable.Zip(list, list.Skip (1), fun first second -> (first, second)).ToImmutableArray () - - /// Splits the into chunks of size at most 'chunkSize' - /// The maximum size of each chunk - /// The input - /// The split into chunks - /// Thrown when chunkSize is not positive or when is default - let chunkBySize chunkSize (list : FlatList<'T>) = - if chunkSize <= 0 then - invalidArg (nameof chunkSize) ErrorStrings.InputMustBeNonNegative - let len = list.Length // Will throw if list is default - if len = 0 then empty else - let numChunks = (len + chunkSize - 1) / chunkSize - Enumerable - .Range(0, numChunks) - .Select(fun i -> - let start = i * chunkSize - let count = min chunkSize (len - start) - list.Slice (start, count) - ) // list.Slice throws if list is default (already caught by len) - .ToImmutableArray () + Enumerable.Zip(list, list.Skip (1), fun first second -> struct (first, second)).ToImmutableArray () - /// Splits the input array into at most count chunks. - /// The maximum number of chunks. - /// The input array. - /// The array split into chunks. - /// Thrown when count is not positive. + [] let splitInto (count : int) (list : FlatList<'T>) : FlatList> = if count <= 0 then invalidArg (nameof count) ErrorStrings.InputMustBeNonNegative @@ -894,96 +883,81 @@ module FlatList = if len = 0 then empty else - let chunkSize = (len + count - 1) / count // ceil(len / count) + let chunkSize = (len + count - 1) / count chunkBySize chunkSize list - /// Returns a new that contains the elements of the original but with duplicates removed - /// The input - /// The with distinct elements + [] + let splitIntoN (count : int) (list : FlatList<'T>) : FlatList> = splitInto count list + + [] let distinct (list : FlatList<'T>) = - let builder = ImmutableHashSet.CreateBuilder<'T> () - for i = 0 to list.Length - 1 do - let item = list.[i] // list.[i] will throw if list is default - builder.Add (item) |> ignore - builder.ToImmutableArray () - - /// Returns a new that contains all pairwise combinations of elements from the first and second s - /// The first input - /// The second input - /// The of all pairwise combinations + if list.IsDefaultOrEmpty then + list + else + let seen = System.Collections.Generic.HashSet<'T> () + let builder = ImmutableArray.CreateBuilder<'T> () + for item in list do + if seen.Add (item) then + builder.Add (item) + builder.ToImmutable () + + [] let allPairs (xs : FlatList<'T>) (ys : FlatList<'U>) = xs.SelectMany(fun x -> ys.Select (fun y -> (x, y))).ToImmutableArray () - /// Returns a new with the elements permuted according to the specified permutation - /// The function that maps input indices to output indices - /// The input - /// The permuted - /// Thrown when the permutation function returns an out-of-range index + [] let permute indexMap (list : FlatList<'T>) = - let len = list.Length // Will throw if list is default - let permutedArray = Array.zeroCreate<'T> len - for i = 0 to len - 1 do - let j = indexMap i - if j < 0 || j >= len then - invalidArg (nameof indexMap) "Invalid permutation" - permutedArray.[j] <- list.[i] // list.[i] will throw if list is default (already caught by len) - FlatListFactory.CreateRange permutedArray - - /// Combines the two s into a of pairs. The two s must have equal lengths - /// The first input - /// The second input - /// The of pairs - /// Thrown when the s have different lengths + let len = list.Length + if len = 0 then + list + else + let builder = FlatListFactory.CreateBuilder<'T> len + builder.Count <- len + let usedSourceIndices = System.Collections.Generic.HashSet () + + for i = 0 to len - 1 do + let sourceIndex = indexMap i + if sourceIndex < 0 || sourceIndex >= len then + invalidArg (nameof indexMap) "Invalid permutation function, source index out of range" + if not (usedSourceIndices.Add (sourceIndex)) then + invalidArg (nameof indexMap) "Invalid permutation function, duplicate source indices mapped" + + builder.[i] <- list.[sourceIndex] + builder.MoveToImmutable () + + [] let zip (list1 : FlatList<'T>) (list2 : FlatList<'U>) = - let len1 = list1.Length // .Length throws if default + let len1 = list1.Length if len1 <> list2.Length then invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths - Enumerable - .Range(0, len1) - .Select(fun i -> (list1.[i], list2.[i])) // .[i] throws if default (caught by .Length) - .ToImmutableArray () - - /// Combines the three s into a of triples. The three s must have equal lengths - /// The first input - /// The second input - /// The third input - /// The of triples - /// Thrown when the s have different lengths + Enumerable.Range(0, len1).Select(fun i -> struct (list1.[i], list2.[i])).ToImmutableArray () + + [] let zip3 (list1 : FlatList<'T>) (list2 : FlatList<'U>) (list3 : FlatList<'V>) = - let len1 = list1.Length // .Length throws if default + let len1 = list1.Length if len1 <> list2.Length then invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths if len1 <> list3.Length then invalidArg (nameof list3) ErrorStrings.ListsHaveDifferentLengths - Enumerable - .Range(0, len1) - .Select(fun i -> (list1.[i], list2.[i], list3.[i])) // .[i] throws if default (caught by .Length) - .ToImmutableArray () - - /// Splits a of pairs into two s - /// The input - /// The two s unzipped from the input - let unzip (list : FlatList<'T * 'U>) = + Enumerable.Range(0, len1).Select(fun i -> struct (list1.[i], list2.[i], list3.[i])).ToImmutableArray () + + [] + let unzip (list : FlatList) = if list.IsEmpty then - (empty, empty) // IsEmpty is safe for default (true) + struct (empty, empty) else - (list.Select(fst).ToImmutableArray (), list.Select(snd).ToImmutableArray ()) + struct (list.Select(fstv).ToImmutableArray (), list.Select(sndv).ToImmutableArray ()) - /// Splits a of triples into three s - /// The input - /// The three s unzipped from the input - let unzip3 (list : FlatList<'T * 'U * 'V>) = + [] + let unzip3 (list : FlatList) = if list.IsEmpty then - (empty, empty, empty) // IsEmpty is safe for default (true) + struct (empty, empty, empty) else - let res1 = list.Select(fun (x, _, _) -> x).ToImmutableArray () - let res2 = list.Select(fun (_, y, _) -> y).ToImmutableArray () - let res3 = list.Select(fun (_, _, z) -> z).ToImmutableArray () - (res1, res2, res3) - - /// Returns the average of the elements in the - /// The input - /// The average of the elements - /// Thrown when the is empty + let res1 = list.Select(fun struct (x, _, _) -> x).ToImmutableArray () + let res2 = list.Select(fun struct (_, y, _) -> y).ToImmutableArray () + let res3 = list.Select(fun struct (_, _, z) -> z).ToImmutableArray () + struct (res1, res2, res3) + + [] let inline average<'T when 'T : (static member (+) : 'T * 'T -> 'T) and 'T : (static member DivideByInt : 'T * int -> 'T) @@ -992,14 +966,10 @@ module FlatList = = if list.Length = 0 then invalidArg (nameof list) LanguagePrimitives.ErrorStrings.InputArrayEmptyString - let sum = list.Aggregate ('T.Zero, fun acc x -> Checked.(+) acc x) // Average will throw if default + let sum = list.Aggregate ('T.Zero, fun acc x -> Checked.(+) acc x) 'T.DivideByInt (sum, list.Length) - /// Returns the average of the results of applying the function to each element of the - /// The function to transform the elements before averaging - /// The input - /// The average of the projected elements - /// Thrown when the is empty + [] let inline averageBy<'T, 'U when 'U : (static member (+) : 'U * 'U -> 'U) and 'U : (static member DivideByInt : 'U * int -> 'U) @@ -1007,184 +977,187 @@ module FlatList = (projection : 'T -> 'U) (list : FlatList<'T>) = - let sum = list.Aggregate ('U.Zero, fun acc x -> Checked.(+) acc (projection x)) // AverageBy will throw if default + let sum = list.Aggregate ('U.Zero, fun acc x -> Checked.(+) acc (projection x)) 'U.DivideByInt (sum, list.Length) + [] + let fold<'T, 'State> (folder : 'State -> 'T -> 'State) (state : 'State) (list : FlatList<'T>) = list.Aggregate (state, folder) - /// Applies a function to each element of the , threading an accumulator argument through the computation - /// The function to update the state given the input elements - /// The initial state - /// The input - /// The final state - /// - /// - /// let numbers = FlatList.ofArray [|1; 2; 3; 4; 5|] - /// let sum = FlatList.fold (fun acc x -> acc + x) 0 numbers - /// // sum is 15 - /// - /// // Computing the average - /// let count = FlatList.length numbers - /// let total = FlatList.fold (fun sum x -> sum + x) 0 numbers - /// let avg = float total / float count - /// - /// - let fold<'T, 'State> (folder : 'State -> 'T -> 'State) (state : 'State) (list : FlatList<'T>) = list.Aggregate (state, folder) // Aggregate will throw if default - - /// Applies a function to corresponding elements of two s, threading an accumulator argument through the computation - /// The function to update the state given the input elements from both s - /// The initial state - /// The first input - /// The second input - /// The final state - /// Thrown when the s have different lengths + [] let fold2<'T1, 'T2, 'State> (folder : 'State -> 'T1 -> 'T2 -> 'State) (state : 'State) (list1 : FlatList<'T1>) (list2 : FlatList<'T2>) = - let len1 = list1.Length // .Length throws if default + let len1 = list1.Length if len1 <> list2.Length then invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths let mutable acc = state for i = 0 to len1 - 1 do - acc <- folder acc list1.[i] list2.[i] // .[i] throws if default (caught by .Length) + acc <- folder acc list1.[i] list2.[i] + acc + + [] + let foldi<'T, 'State> (folder : int -> 'State -> 'T -> 'State) (state : 'State) (list : FlatList<'T>) : 'State = + let mutable acc = state + for i = 0 to list.Length - 1 do + acc <- folder i acc list.[i] + acc + + [] + let foldi2<'T1, 'T2, 'State> + (folder : int -> 'State -> 'T1 -> 'T2 -> 'State) + (state : 'State) + (list1 : FlatList<'T1>) + (list2 : FlatList<'T2>) + : 'State = + let len = list1.Length + if len <> list2.Length then + invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths + let mutable acc = state + for i = 0 to len - 1 do + acc <- folder i acc list1.[i] list2.[i] acc - /// Applies a function to each element of the , threading an accumulator argument through the computation, starting from the end - /// The function to update the state given the input elements, starting from the end - /// The input - /// The initial state - /// The final state - let foldBack<'T, 'State> (folder : 'State -> 'T -> 'State) (list : FlatList<'T>) (state : 'State) = - seq { for i = list.Length - 1 downto 0 do yield list.[i] } - |> _.Aggregate(state, folder) - - /// Applies a function to corresponding elements of two s, threading an accumulator argument through the computation, starting from the end - /// The function to update the state given the input elements from both s, starting from the end - /// The first input - /// The second input - /// The initial state - /// The final state - /// Thrown when the s have different lengths + [] + let foldBack<'T, 'State> (folder : 'T -> 'State -> 'State) (list : FlatList<'T>) (state : 'State) = + checkNotDefault (nameof list) list + let mutable acc = state + for i = list.Length - 1 downto 0 do + acc <- folder list.[i] acc + acc + + [] let foldBack2<'T1, 'T2, 'State> (folder : 'T1 -> 'T2 -> 'State -> 'State) (list1 : FlatList<'T1>) (list2 : FlatList<'T2>) (state : 'State) = - let len1 = list1.Length // .Length throws if default + let len1 = list1.Length if len1 <> list2.Length then invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths let mutable acc = state for i = len1 - 1 downto 0 do - acc <- folder list1.[i] list2.[i] acc // .[i] throws if default (caught by .Length) + acc <- folder list1.[i] list2.[i] acc acc - /// Applies a function to each element of the , threading an accumulator argument through the computation. - /// This function takes the second argument, and applies the function to it and the first element of the . - /// Then, it passes this result into the function along with the second element, and so on. - /// Finally, it returns the final result. If the is empty, an exception is raised. - /// The function to reduce the with - /// The input - /// The final accumulated value - /// Thrown when the is empty - let reduce (reduction : 'T -> 'T -> 'T) (list : FlatList<'T>) = list.Aggregate (reduction) // Aggregate will throw if default (via GetEnumerator) - - /// Applies a function to each element of the , threading an accumulator argument through the computation, starting from the end. - /// This function takes the last element of the and the second-to-last element, and applies the function to them. - /// Then, it passes this result into the function along with the third-to-last element, and so on. - /// Finally, it returns the final result. If the is empty, an exception is raised. - /// The function to reduce the with, starting from the end - /// The input - /// The final accumulated value - /// Thrown when the is empty + [] + let foldBacki (folder : int -> 'T -> 'State -> 'State) (list : FlatList<'T>) (state : 'State) : 'State = + let mutable acc = state + for i = list.Length - 1 downto 0 do + acc <- folder i list.[i] acc + acc + + [] + let foldBacki2 + (folder : int -> 'T1 -> 'T2 -> 'State -> 'State) + (list1 : FlatList<'T1>) + (list2 : FlatList<'T2>) + (state : 'State) + : 'State = + let len = list1.Length + if len <> list2.Length then + invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths + let mutable acc = state + for i = len - 1 downto 0 do + acc <- folder i list1.[i] list2.[i] acc + acc + + [] + let foldBack3<'T1, 'T2, 'T3, 'State> + (folder : 'T1 -> 'T2 -> 'T3 -> 'State -> 'State) + (list1 : FlatList<'T1>) + (list2 : FlatList<'T2>) + (list3 : FlatList<'T3>) + (state : 'State) + = + let len1 = list1.Length + if len1 <> list2.Length then + invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths + if len1 <> list3.Length then + invalidArg (nameof list3) ErrorStrings.ListsHaveDifferentLengths + let mutable acc = state + for i = len1 - 1 downto 0 do + acc <- folder list1.[i] list2.[i] list3.[i] acc + acc + + [] + let reduce (reduction : 'T -> 'T -> 'T) (list : FlatList<'T>) = + if list.IsDefaultOrEmpty then + invalidArg (nameof list) LanguagePrimitives.ErrorStrings.InputArrayEmptyString + else + list.Aggregate (reduction) + + [] let reduceBack (reduction : 'T -> 'T -> 'T) (list : FlatList<'T>) = - (seq { - for i = list.Length - 1 downto 0 do - list[i] - }) - .Aggregate (reduction) - - /// Like fold, but returns both the intermediate and final results - /// The function to update the state given the input elements - /// The initial state - /// The input - /// The of all intermediate and final states + if list.IsDefaultOrEmpty then + invalidArg (nameof list) LanguagePrimitives.ErrorStrings.InputArrayEmptyString + else + let len = list.Length + let mutable result = list.[len - 1] + for i = len - 2 downto 0 do + result <- reduction list.[i] result + result + + [] let scan<'T, 'State> folder (state : 'State) (list : FlatList<'T>) = - let builder = FlatListFactory.CreateBuilder<'State> (list.Length + 1) // list.Length throws if default + let builder = FlatListFactory.CreateBuilder<'State> (list.Length + 1) builder.Add state let mutable currentState = state - for item in list do // Iteration throws if default (caught by list.Length) + for item in list do currentState <- folder currentState item builder.Add currentState - builder.MoveToImmutable () + builder.ToImmutable () - /// Like foldBack, but returns both the intermediate and final results - /// The function to update the state given the input elements, starting from the end - /// The input - /// The initial state - /// The of all intermediate and final states, in reverse order of computation + [] let scanBack<'T, 'State> folder (list : FlatList<'T>) (state : 'State) = - let len = list.Length // list.Length throws if default - let results = Array.zeroCreate<'State> (len + 1) - results.[len] <- state + checkNotDefault (nameof list) list + let len = list.Length + let builder = FlatListFactory.CreateBuilder<'State> (len + 1) + builder.Count <- len + 1 + + builder.[len] <- state let mutable currentState = state for i = len - 1 downto 0 do - currentState <- folder list.[i] currentState // list.[i] throws if default (caught by list.Length) - results.[i] <- currentState - FlatListFactory.CreateRange results - - /// Returns the only element of the array. - /// The input array. - /// The only element of the array. - /// Thrown when the input does not have precisely one element. + currentState <- folder list.[i] currentState + builder.[i] <- currentState + + builder.MoveToImmutable () + + [] let exactlyOne (list : FlatList<'T>) = list.Single () - /// Returns the only element of the array or None if the array is empty or contains more than one element. - /// The input array. - /// The only element of the array or None. + [] let tryExactlyOne (list : FlatList<'T>) : 'T voption = if list.IsDefaultOrEmpty || list.Length <> 1 then ValueNone else ValueSome list.[0] - /// Returns a new list with the distinct elements of the input array which do not appear in the itemsToExclude sequence - /// A sequence whose elements that also occur in the input array will cause those elements to be removed - /// The input array - /// A new array that contains the distinct elements of list that do not appear in itemsToExclude + [] let except (itemsToExclude : 'T seq) (list : FlatList<'T>) : FlatList<'T> = let excludeSet = HashSet (itemsToExclude) filter (fun x -> not (excludeSet.Contains x)) list - /// Returns the sum of the elements in the array. - /// The input array. - /// The resulting sum. - /// Thrown when the input array is null. + [] let inline sum (list : FlatList< ^T >) : ^T when ^T : (static member (+) : ^T * ^T -> ^T) and ^T : (static member Zero : ^T) = list.Aggregate (LanguagePrimitives.GenericZero< ^T>, fun acc x -> acc + x) - /// Returns the sum of the results generated by applying the function to each element of the array. - /// The function to transform the array elements into the type to be summed. - /// The input array. - /// The resulting sum. + [] let inline sumBy (projection : 'T -> 'U) (list : FlatList<'T>) : 'U when 'U : (static member (+) : 'U * 'U -> 'U) and 'U : (static member Zero : 'U) = list.Aggregate (LanguagePrimitives.GenericZero<'U>, fun acc x -> acc + (projection x)) - /// Returns the transpose of the given sequence of arrays. - /// The input sequence of arrays. - /// The transposed array. - /// Thrown when the input arrays differ in length. + [] let transpose (lists : FlatList>) : FlatList> = if lists.IsDefaultOrEmpty then empty else let len0 = lists.[0].Length - // Verify all inner arrays have the same length for i = 1 to lists.Length - 1 do if lists.[i].Length <> len0 then invalidArg (nameof lists) "All inner arrays must have the same length." @@ -1194,129 +1167,85 @@ module FlatList = .Select(fun j -> Enumerable.Range(0, lists.Length).Select(fun i -> lists.[i].[j]).ToImmutableArray ()) .ToImmutableArray () - /// Builds a list from the given array. - /// The input array. - /// The list of array elements. - let toList (list : FlatList<'T>) : 'T list = - if list.IsDefaultOrEmpty then - [] - else - seq { - for i = 0 to list.Length - 1 do - yield list.[i] - } - |> Seq.toList - - /// Builds a from a list. - /// The input list. - /// A containing the elements from the list. - let ofList (list : 'T list) : FlatList<'T> = - let builder = FlatListFactory.CreateBuilder () - list |> List.iter (fun item -> builder.Add item) - moveFromBuilder builder - - /// Return a new array with the item at a given index set to the new value. - /// The index of the item to be replaced. - /// The new value. - /// The input array. - /// The result array. - /// Thrown when index is outside 0..source.Length - 1 + [] let updateAt (index : int) (value : 'T) (list : FlatList<'T>) : FlatList<'T> = list.SetItem (index, value) - /// Return a new array with the item at a given index removed. - /// The index of the item to be removed. - /// The input array. - /// The result array. - /// Thrown when index is outside 0..source.Length - 1 + [] let removeAt (index : int) (list : FlatList<'T>) : FlatList<'T> = list.RemoveAt (index) - /// Return a new array with a new item inserted before the given index. - /// The index where the item should be inserted. - /// The value to insert. - /// The input array. - /// The result array. - /// Thrown when index is below 0 or greater than source.Length. + [] let insertAt (index : int) (value : 'T) (list : FlatList<'T>) : FlatList<'T> = list.Insert (index, value) - /// Return a new array with new items inserted before the given index. - /// The index where the items should be inserted. - /// The values to insert. - /// The input array. - /// The result array. - /// Thrown when index is below 0 or greater than source.Length. + [] let insertManyAt (index : int) (values : 'T seq) (list : FlatList<'T>) : FlatList<'T> = list.InsertRange (index, values) - /// Builds a new array that contains the elements of the given array. - /// The input array. - /// A copy of the input array. - let copy (list : FlatList<'T>) : FlatList<'T> = list.ToImmutableArray () + [] + let fill (index : int) (count : int) (value : 'T) (list : FlatList<'T>) : FlatList<'T> = + if index < 0 || count < 0 || index + count > list.Length then + invalidArg (nameof index) ErrorStrings.InputMustBeNonNegative + let builder = list.ToBuilder () + for i = index to index + count - 1 do + builder.[i] <- value + builder.MoveToImmutable () - /// Returns an array that contains no more than N elements in a new array. - /// A function that takes in the current state and returns an option tuple of the next - /// element of the array and the next state value. - /// The initial state value. - /// The result array. - let unfold<'T, 'State> (generator : 'State -> ('T * 'State) voption) (state : 'State) : FlatList<'T> = + [] + let unfold<'T, 'State> (generator : 'State -> struct ('T * 'State) voption) (state : 'State) : FlatList<'T> = let builder = builder () - let rec loop state = - match generator state with - | ValueSome (item, newState) -> - builder.Add item - loop newState - | ValueNone -> () + let mutable currentState = state + let mutable continuing = true + + while continuing do + match generator currentState with + | ValueSome (value, newState) -> + builder.Add (value) + currentState <- newState + | ValueNone -> continuing <- false + + builder.ToImmutable () + + [] + let compareWith (comparer : 'T -> 'T -> int) (list1 : FlatList<'T>) (list2 : FlatList<'T>) : int = + if list1.IsDefault && list2.IsDefault then + 0 + elif list1.IsDefault then + -1 + elif list2.IsDefault then + 1 + else + let len1 = list1.Length + let len2 = list2.Length + let minLength = min len1 len2 - loop state - moveFromBuilder builder - - /// Compares two arrays using the given comparison function, element by element. - /// A function that takes an element from each array and returns an int. - /// If it evaluates to a non-zero value iteration is stopped and that value is returned. - /// The first input array. - /// The second input array. - /// Returns the first non-zero result from the comparison function. If the first array has - /// a larger element, the return value is always positive. If the second array has a larger - /// element, the return value is always negative. When the elements are equal in the two - /// arrays, 1 is returned if the first array is longer, 0 is returned if they are equal in - /// length, and -1 is returned when the second array is longer. - let inline compareWith (comparer : 'T -> 'T -> int) (array1 : FlatList<'T>) (array2 : FlatList<'T>) : int = - let mutable result = 0 - let mutable i = 0 - let len1 = array1.Length - let len2 = array2.Length - let minLength = min len1 len2 + let mutable i = 0 + let mutable result = 0 + let mutable continueComparing = true - while i < minLength && result = 0 do - result <- comparer array1.[i] array2.[i] - i <- i + 1 + while i < minLength && continueComparing do + result <- comparer list1.[i] list2.[i] + if result <> 0 then + continueComparing <- false + else + i <- i + 1 - if result <> 0 then result - elif len1 > len2 then 1 - elif len1 < len2 then -1 - else 0 + if result = 0 then compare len1 len2 else result - /// Returns the greatest of all elements of the array, compared via Operators.max. - /// The input array. - /// Thrown when the array is empty. - /// The maximum element. - let inline max (list : FlatList<'T>) : 'T when 'T : comparison = + [] + let inline max<'T when 'T : comparison> (list : FlatList<'T>) : 'T = checkNotDefault (nameof list) list + if list.IsEmpty then + invalidArg (nameof list) LanguagePrimitives.ErrorStrings.InputArrayEmptyString let mutable acc = list.[0] - for i = 1 to list.Length - 1 do let curr = list.[i] - if curr > acc then acc <- curr - acc - /// Returns the greatest of all elements of the array, compared via Operators.max on the function result. - /// The function to transform the elements into a type supporting comparison. - /// The input array. - /// Thrown when the array is empty. - /// The maximum element. - let inline maxBy (projection : 'T -> 'Key) (list : FlatList<'T>) : 'T when 'Key : comparison = + [] + let inline maxBy<'T, 'Key when 'Key : comparison> (projection : 'T -> 'Key) (list : FlatList<'T>) : 'T = checkNotDefault (nameof list) list + if list.IsEmpty then + invalidArg (nameof list) LanguagePrimitives.ErrorStrings.InputArrayEmptyString let mutable maxVal = list.[0] let mutable maxKey = projection maxVal for i = 1 to list.Length - 1 do @@ -1327,29 +1256,23 @@ module FlatList = maxKey <- currKey maxVal - /// Returns the smallest of all elements of the array, compared via Operators.min. - /// The input array. - /// Thrown when the array is empty. - /// The minimum element. - let inline min (list : FlatList<'T>) : 'T when 'T : comparison = + [] + let inline min<'T when 'T : comparison> (list : FlatList<'T>) : 'T = checkNotDefault (nameof list) list + if list.IsEmpty then + invalidArg (nameof list) LanguagePrimitives.ErrorStrings.InputArrayEmptyString let mutable acc = list.[0] - for i = 1 to list.Length - 1 do let curr = list.[i] - if curr < acc then acc <- curr - acc - /// Returns the smallest of all elements of the array, compared via Operators.min on the function result. - /// The function to transform the elements into a type supporting comparison. - /// The input array. - /// Thrown when the array is empty. - /// The minimum element. - let inline minBy (projection : 'T -> 'Key) (list : FlatList<'T>) : 'T when 'Key : comparison = + [] + let inline minBy<'T, 'Key when 'Key : comparison> (projection : 'T -> 'Key) (list : FlatList<'T>) : 'T = checkNotDefault (nameof list) list + if list.IsEmpty then + invalidArg (nameof list) LanguagePrimitives.ErrorStrings.InputArrayEmptyString let mutable minVal = list.[0] let mutable minKey = projection minVal for i = 1 to list.Length - 1 do diff --git a/src/FSharp.Collections.Immutable/FlatList.fsi b/src/FSharp.Collections.Immutable/FlatList.fsi new file mode 100644 index 0000000..d560cb1 --- /dev/null +++ b/src/FSharp.Collections.Immutable/FlatList.fsi @@ -0,0 +1,1446 @@ +// This file contains F# bindings to ImmutableArray from System.Collections.Immutable. +// It provides a flat list implementation that is optimized for performance and memory usage. +// The FlatList type is a wrapper around ImmutableArray, providing a more convenient API for working with immutable lists. +// FlatList code is designed to perform operations without allocating new arrays unnecessarily, making it suitable for high-performance applications. +#if INTERACTIVE +namespace global +#else +namespace FSharp.Collections.Immutable +#endif + +open System +open System.Collections.Generic +open System.Collections.Immutable + +// The FlatList name comes from a similar construct seen in the official F# source code +type FlatList<'T> = System.Collections.Immutable.ImmutableArray<'T> + +// based on the F# Array module source +[] +module FlatList = + + ////////// Creating ////////// + + /// Creates a new builder with the specified capacity + /// The initial capacity of the builder + /// An empty builder with the specified capacity + [] + val inline builderWith<'T> : capacity : int -> FlatList<'T>.Builder + + /// Builds a from a builder, moving the elements and leaving the builder empty + /// The builder to build from + /// A containing the elements from the builder + /// Thrown when builder is null + [] + val moveFromBuilder<'T> : builder : FlatList<'T>.Builder -> FlatList<'T> + + /// Returns an empty + /// An empty + /// + /// + /// let emptyList = FlatList.empty<int> + /// printfn "Is empty? %b" (FlatList.isEmpty emptyList) // true + /// + /// + [] + val inline empty<'T> : FlatList<'T> + + /// Builds a from the given + /// The to build the from + /// A containing the elements of the array + [] + val inline ofArray<'T> : source : 'T array -> FlatList<'T> + + /// Builds a from the given + /// The to build the from + /// A containing the elements of the sequence + [] + val inline ofSeq<'T> : source : seq<'T> -> FlatList<'T> + + /// Builds a from the given + /// The to build the from + /// A containing the elements of the sequence + [] + val inline ofList<'T> : source : 'T list -> FlatList<'T> + + /// Creates a list from a value option. + /// The input option. + /// A list of one element if the option is Some, and an empty list if the option is None. + [] + val ofOption<'T> : option : 'T option -> FlatList<'T> + + /// Creates a list from a value option. + /// The input value option. + /// A list of one element if the option is ValueSome, and an empty list if the option is ValueNone. + [] + val ofValueOption<'T> : voption : 'T voption -> FlatList<'T> + + /// Returns a with a single element + /// The item to put into the + /// A containing only the given item + [] + val inline singleton<'T> : item : 'T -> FlatList<'T> + + /// Creates a by initializing each element with the given function + /// The number of elements to create + /// The function to initialize each element + /// A new with the initialized elements + /// Thrown when count is negative + [] + val init<'T> : count : int -> initializer : (int -> 'T) -> FlatList<'T> + + /// Creates a of a given length with all elements set to the given value + /// The length of the to create + /// The value to replicate + /// A of the specified length with all elements equal to the given value + [] + val create<'T> : count : int -> value : 'T -> FlatList<'T> + + /// Replicates a value into a of a given length + /// The length of the to create + /// The value to replicate + /// A of the specified length with all elements equal to the given value + [] + val replicate<'T> : count : int -> initial : 'T -> FlatList<'T> + + /// Creates an of a specified length, with all the elements initialized to the default zero value for the type. + /// The length of the to create. + /// The created . + [] + val zeroCreate<'T> : count : int -> FlatList<'T> + + /// Views the as a + /// The input + /// The containing the elements of the + [] + val inline toSeq<'T> : flatList : FlatList<'T> -> seq<'T> + + /// Builds an from the given + /// The to build the from + /// An containing the elements of the + [] + val inline toArray<'T> : list : FlatList<'T> -> 'T array + + /// Builds an from the given + /// The to build the from + /// An containing the elements of the + [] + val toList<'T> : list : FlatList<'T> -> 'T list + + /// Converts a list to an option. If the list has one element, it returns Some of that element. + /// Otherwise, it returns None. + /// The input list. + /// An option representing the list's single element, or None. + [] + val toOption<'T> : list : FlatList<'T> -> 'T option + + /// Converts a list to a value option. If the list has one element, it returns ValueSome of that element. + /// Otherwise, it returns ValueNone. + /// The input list. + /// A value option representing the list's single element, or ValueNone. + [] + val toValueOption<'T> : list : FlatList<'T> -> 'T voption + + /// Builds a new that contains the elements of the given . + /// The input . + [] + val copy<'T> : list : FlatList<'T> -> FlatList<'T> + + ////////// Building ////////// + + /// Builds a from a builder, copying the elements + /// The builder to build from + /// A containing the elements from the builder + /// Thrown when builder is null + [] + val ofBuilder<'T> : builder : FlatList<'T>.Builder -> FlatList<'T> + + /// Creates a new builder + /// An empty builder + [] + val inline builder<'T> : unit -> FlatList<'T>.Builder + + /// Creates a builder containing the elements of the input + /// The to create the builder from + /// A builder containing the elements of the + [] + val toBuilder<'T> : list : FlatList<'T> -> FlatList<'T>.Builder + + module Builder = + + /// Adds an item to the builder + /// The item to add + /// The builder to add to + [] + val add<'T> : item : 'T -> builder : FlatList<'T>.Builder -> FlatList<'T>.Builder + + /// Checks if the is empty + /// The to check + /// True if the is empty, false otherwise + [] + val isEmpty<'T> : list : FlatList<'T> -> bool + + /// Checks if the is uninstantiated + /// The to check + /// True if the is uninstantiated, false otherwise + [] + val isDefault<'T> : list : FlatList<'T> -> bool + + /// Checks if the is uninstantiated or empty + /// The to check + /// True if the is uninstantiated or empty, false otherwise + [] + val isDefaultOrEmpty<'T> : list : FlatList<'T> -> bool + + ////////// IReadOnly* ////////// + + /// Returns the number of elements in the + /// The input + /// The number of elements in the + [] + val length<'T> : list : FlatList<'T> -> int + + /// Gets the element at the specified index in the + /// The index to retrieve + /// The input + /// The element at the specified index + /// Thrown when the index is out of range + [] + val item<'T> : index : int -> list : FlatList<'T> -> 'T + + /// Appends two s to create a new containing all elements from both s + /// The first + /// The second + /// A new containing all elements from both input s + [] + val append<'T> : list1 : FlatList<'T> -> list2 : FlatList<'T> -> FlatList<'T> + + /// Searches for the specified object and returns the zero-based index of the first occurrence within the range + /// of elements in the that starts at the specified index and + /// contains the specified number of elements. + /// The equality comparer to use + /// The starting index + /// The number of elements to search + /// The item to search for + /// The input + /// The zero-based index of the first occurrence of the item + [] + val indexRangeWith<'T> : + comparer : IEqualityComparer<'T> -> index : int -> count : int -> item : 'T -> list : FlatList<'T> -> int + + /// Searches for the specified object and returns the zero-based index of the first occurrence within the range + /// of elements in the that starts at the specified index and + /// contains the specified number of elements. + /// The starting index + /// The number of elements to search + /// The item to search for + /// The input + /// The zero-based index of the first occurrence of the item + [] + val indexRange<'T when 'T : equality> : index : int -> count : int -> item : 'T -> list : FlatList<'T> -> int + + /// Searches for the specified object and returns the zero-based index of the first occurrence within the range + /// of elements in the that starts at the specified index and + /// contains the specified number of elements. + /// The equality comparer to use + /// The starting index + /// The item to search for + /// The input + /// The zero-based index of the first occurrence of the item + [] + val indexFromWith<'T> : comparer : IEqualityComparer<'T> -> index : int -> item : 'T -> list : FlatList<'T> -> int + + /// Searches for the specified object and returns the zero-based index of the first occurrence within the range + /// of elements in the that starts at the specified index and + /// contains the specified number of elements. + /// The starting index + /// The item to search for + /// The input + /// The zero-based index of the first occurrence of the item + [] + val indexFrom<'T when 'T : equality> : index : int -> item : 'T -> list : FlatList<'T> -> int + + /// Searches for the specified object and returns the zero-based index of the first occurrence within the range + /// of elements in the that starts at the specified index and + /// contains the specified number of elements. + /// The equality comparer to use + /// The item to search for + /// The input + /// The zero-based index of the first occurrence of the item + [] + val indexWith<'T> : comparer : IEqualityComparer<'T> -> item : 'T -> list : FlatList<'T> -> int + + /// Searches for the specified object and returns the zero-based index of the first occurrence within the range + /// of elements in the that starts at the specified index and + /// contains the specified number of elements. + /// The item to search for + /// The input + /// The zero-based index of the first occurrence of the item + [] + val index<'T when 'T : equality> : item : 'T -> list : FlatList<'T> -> int + + /// Searches for the specified object and returns the zero-based index of the last occurrence within the + /// range of elements in the that contains the specified number + /// of elements and ends at the specified index. + /// The equality comparer to use + /// The ending index + /// The number of elements to search + /// The item to search for + /// The input + /// The zero-based index of the last occurrence of the item + [] + val lastIndexRangeWith<'T> : + comparer : IEqualityComparer<'T> -> index : int -> count : int -> item : 'T -> list : FlatList<'T> -> int + + /// Searches for the specified object and returns the zero-based index of the last occurrence within the + /// range of elements in the that contains the specified number + /// of elements and ends at the specified index. + /// The ending index + /// The number of elements to search + /// The item to search for + /// The input + /// The zero-based index of the last occurrence of the item + [] + val lastIndexRange<'T when 'T : equality> : index : int -> count : int -> item : 'T -> list : FlatList<'T> -> int + + /// Searches for the specified object and returns the zero-based index of the last occurrence within the + /// range of elements in the that contains the specified number + /// of elements and ends at the specified index. + /// The equality comparer to use + /// The ending index + /// The item to search for + /// The input + /// The zero-based index of the last occurrence of the item + [] + val lastIndexFromWith<'T> : comparer : IEqualityComparer<'T> -> index : int -> item : 'T -> list : FlatList<'T> -> int + + /// Searches for the specified object and returns the zero-based index of the last occurrence within the + /// range of elements in the that contains the specified number + /// of elements and ends at the specified index. + /// The ending index + /// The item to search for + /// The input + /// The zero-based index of the last occurrence of the item + [] + val lastIndexFrom<'T when 'T : equality> : index : int -> item : 'T -> list : FlatList<'T> -> int + + /// Searches for the specified object and returns the zero-based index of the last occurrence within the + /// range of elements in the that contains the specified number + /// of elements and ends at the specified index. + /// The equality comparer to use + /// The item to search for + /// The input + /// The zero-based index of the last occurrence of the item + [] + val lastIndexWith<'T> : comparer : IEqualityComparer<'T> -> item : 'T -> list : FlatList<'T> -> int + + /// Searches for the specified object and returns the zero-based index of the last occurrence within the + /// range of elements in the that contains the specified number + /// of elements and ends at the specified index. + /// The item to search for + /// The input + /// The zero-based index of the last occurrence of the item + [] + val lastIndex<'T when 'T : equality> : item : 'T -> list : FlatList<'T> -> int + + /// Removes the specified objects from the with the given comparer. + /// The equality comparer to use + /// The items to remove + /// The input + /// A new with the specified items removed + [] + val removeAllWith<'T> : comparer : IEqualityComparer<'T> -> items : 'T seq -> list : FlatList<'T> -> FlatList<'T> + + /// Removes the specified objects from the . + /// The items to remove + /// The input + /// A new with the specified items removed + [] + val removeAll<'T when 'T : equality> : items : 'T seq -> list : FlatList<'T> -> FlatList<'T> + + /// Removes all the elements that do not match the conditions defined by the specified predicate. + /// The predicate to test elements + /// The input + /// A new with elements that match the predicate + /// + /// + /// let numbers = FlatList.ofArray [|1; 2; 3; 4; 5; 6|] + /// let evens = FlatList.filter (fun x -> x % 2 = 0) numbers + /// // evens is [|2; 4; 6|] + /// + /// + [] + val filter<'T> : predicate : ('T -> bool) -> list : FlatList<'T> -> FlatList<'T> + + /// Removes all the elements that do not match the conditions defined by the specified predicate. + /// The predicate to test elements + /// The input + /// A new with elements that match the predicate + [] + val where<'T> : predicate : ('T -> bool) -> list : FlatList<'T> -> FlatList<'T> + + /// Removes a range of elements from the . + /// The starting index + /// The number of elements to remove + /// The input + /// A new with the specified range of elements removed + [] + val removeRange<'T> : index : int -> count : int -> list : FlatList<'T> -> FlatList<'T> + + /// Fills the elements of a list with a specified value. + /// The starting index in the target list. + /// The number of elements to fill. + /// The value to fill with. + /// The input list. + /// A new list with the specified range filled with the value. + [] + val fill<'T> : index : int -> count : int -> value : 'T -> list : FlatList<'T> -> FlatList<'T> + + /// Copies a range of elements from the source to the destination array + /// The source + /// The starting index in the source + /// The destination array + /// The starting index in the destination array + /// The number of elements to copy + /// Thrown when the range is invalid + [] + val blit<'T> : + source : FlatList<'T> -> sourceIndex : int -> destination : 'T[] -> destinationIndex : int -> count : int -> unit + + /// Sorts a range of elements in the using the specified comparer + /// The comparer to use + /// The starting index + /// The number of elements to sort + /// The input + /// A new with the specified range of elements sorted + [] + val sortRangeWithComparer<'T> : comparer : IComparer<'T> -> index : int -> count : int -> list : FlatList<'T> -> FlatList<'T> + + /// Sorts a range of elements in the using the specified comparison function + /// The comparison function to use + /// The starting index + /// The number of elements to sort + /// The input + /// A new with the specified range of elements sorted + [] + val sortRangeWith<'T> : comparer : ('T -> 'T -> int) -> index : int -> count : int -> list : FlatList<'T> -> FlatList<'T> + + /// Sorts a range of elements in the using the default comparer + /// The starting index + /// The number of elements to sort + /// The input + /// A new with the specified range of elements sorted + [] + val sortRange<'T when 'T : comparison> : index : int -> count : int -> list : FlatList<'T> -> FlatList<'T> + + /// Sorts the elements in the using the specified comparer + /// The comparer to use + /// The input + /// A new with the elements sorted + [] + val sortWithComparer<'T> : comparer : IComparer<'T> -> list : FlatList<'T> -> FlatList<'T> + + /// Sorts the elements in the using the specified comparison function + /// The comparison function to use + /// The input + /// A new with the elements sorted + [] + val sortWith<'T> : comparer : ('T -> 'T -> int) -> list : FlatList<'T> -> FlatList<'T> + + /// Sorts the elements in the using the default comparer + /// The input + /// A new with the elements sorted + [] + val sort<'T> : list : FlatList<'T> -> FlatList<'T> + + /// Returns a new with the elements in reverse order. + /// The input . + /// The reversed . + [] + val rev<'T> : list : FlatList<'T> -> FlatList<'T> + + /// Returns a new that contains elements of the original sorted in descending order. + /// The input . + /// The sorted . + [] + val inline sortDescending<'T when 'T : comparison> : list : FlatList<'T> -> FlatList<'T> + + /// Returns a new that contains elements of the original sorted in descending order using the specified projection. + /// The function to transform the elements into a type that supports comparison. + /// The input . + /// The sorted . + [] + val inline sortByDescending<'T, 'Key when 'Key : comparison> : + projection : ('T -> 'Key) -> list : FlatList<'T> -> FlatList<'T> + + /// Sorts the using keys given by the given projection. Keys are compared using Operators.compare. + /// The function to transform the elements into a type supporting comparison. + /// The input . + /// The sorted . + [] + val sortBy<'T, 'Key when 'Key : comparison> : projection : ('T -> 'Key) -> list : FlatList<'T> -> FlatList<'T> + + ////////// Loop-based (now LINQ-based where applicable) ////////// + + /// Concatenates a of s into a single + /// The of s to concatenate + /// A new containing all elements from the input s + [] + val concat<'T> : arrs : FlatList> -> FlatList<'T> + + /// Builds a new from the elements of a by applying a mapping function to each element + /// A function to transform elements from the input + /// The input + /// A containing the transformed elements + /// + /// + /// let numbers = FlatList.ofArray [|1; 2; 3; 4; 5|] + /// let squares = FlatList.map (fun x -> x * x) numbers + /// // squares is [|1; 4; 9; 16; 25|] + /// + /// + [] + val inline map<'T, 'U> : mapping : ('T -> 'U) -> list : FlatList<'T> -> FlatList<'U> + + /// Builds a new whose elements are the results of applying the given function + /// to each of the elements of the . The integer index passed to the + /// function indicates the index of element being transformed. + /// A function to transform an element and its index into a result element. + /// The input . + /// The of transformed elements. + [] + val mapi<'T, 'U> : mapping : (int -> 'T -> 'U) -> list : FlatList<'T> -> FlatList<'U> + + /// Builds a new whose elements are the results of applying the given function + /// to the corresponding elements of the two collections pairwise, also passing the index of + /// the elements. The two input s must have the same lengths. + /// The function to transform pairs of input elements and their indices. + /// The first input . + /// The second input . + /// The of transformed elements. + [] + val mapi2<'T1, 'T2, 'U> : + mapping : (int -> 'T1 -> 'T2 -> 'U) -> list1 : FlatList<'T1> -> list2 : FlatList<'T2> -> FlatList<'U> + + /// Builds a new whose elements are the results of applying the given function + /// to the corresponding elements of the three collections pairwise, also passing the index of + /// the elements. The three input s must have the same lengths. + /// The function to transform triples of input elements and their indices. + /// The first input . + /// The second input . + /// The third input . + /// The of transformed elements. + [] + val mapi3<'T1, 'T2, 'T3, 'U> : + mapping : (int -> 'T1 -> 'T2 -> 'T3 -> 'U) -> + list1 : FlatList<'T1> -> + list2 : FlatList<'T2> -> + list3 : FlatList<'T3> -> + FlatList<'U> + + /// Builds a new collection whose elements are the results of applying the given function + /// to the corresponding elements of the two collections pairwise. The two input + /// s must have the same lengths. + /// The function to transform the pairs of the input elements. + /// The first input . + /// The second input . + /// The of transformed elements. + [] + val map2<'T1, 'T2, 'U> : mapping : ('T1 -> 'T2 -> 'U) -> list1 : FlatList<'T1> -> list2 : FlatList<'T2> -> FlatList<'U> + + /// Builds a new collection whose elements are the results of applying the given function + /// to the corresponding elements of the three collections pairwise. The three input + /// s must have the same lengths. + /// The function to transform the triples of the input elements. + /// The first input . + /// The second input . + /// The third input . + /// The of transformed elements. + [] + val map3<'T1, 'T2, 'T3, 'U> : + mapping : ('T1 -> 'T2 -> 'T3 -> 'U) -> + list1 : FlatList<'T1> -> + list2 : FlatList<'T2> -> + list3 : FlatList<'T3> -> + FlatList<'U> + + /// Builds a new whose elements are the results of applying the given function + /// to each of the elements of the while threading an accumulator argument + /// through the computation. + /// The function to transform elements from the input and + /// thread an accumulator state. + /// The initial state of the accumulator. + /// The input . + /// A of transformed elements, and the final accumulator value. + /// + /// + /// // Calculate a running sum while squaring each element + /// let numbers = FlatList.ofArray [|1; 2; 3; 4|] + /// let squares, sum = FlatList.mapFold (fun state x -> x * x, state + x) 0 numbers + /// // squares is [|1; 4; 9; 16|] + /// // sum is 10 + /// + /// + [] + val mapFold<'T, 'State, 'Result> : + mapping : ('State -> 'T -> 'Result * 'State) -> state : 'State -> list : FlatList<'T> -> FlatList<'Result> * 'State + + /// Builds a new whose elements are the results of applying the given function + /// to each of the elements of the while threading an accumulator argument + /// through the computation, starting from the end of the list. + /// The function to transform elements from the input and + /// thread an accumulator state, starting from the end. + /// The input . + /// The initial state of the accumulator. + /// A of transformed elements, and the final accumulator value. + /// + /// + /// // Create a reverse-order list of indices while computing sum + /// let chars = FlatList.ofArray [|'a'; 'b'; 'c'|] + /// let indices, sum = FlatList.mapFoldBack (fun x state -> state, state + 1) chars 0 + /// // indices is [|2; 1; 0|] + /// // sum is 3 + /// + /// + [] + val mapFoldBack<'T, 'State, 'Result> : + mapping : ('T -> 'State -> 'Result * 'State) -> list : FlatList<'T> -> state : 'State -> FlatList<'Result> * 'State + + /// Counts the number of elements in the that satisfy the given predicate + /// A function to project elements from the input + /// The input + /// A of key-value pairs where the key is the projected value and the value is the count + [] + val countBy<'T, 'Key> : projection : ('T -> 'Key) -> list : FlatList<'T> -> FlatList + + /// Creates a containing the elements of the original paired with their indices + /// The input + /// A containing pairs of indices and elements + [] + val indexed<'T> : list : FlatList<'T> -> FlatList + + /// Applies the given function to each element of the + /// A function to apply to each element + /// The input + [] + val inline iter<'T> : action : ('T -> unit) -> list : FlatList<'T> -> unit + + /// Applies the given function to each element of the and its index + /// A function to apply to each element and its index + /// The input + [] + val iteri<'T> : action : (int -> 'T -> unit) -> list : FlatList<'T> -> unit + + /// Applies the given function to pair of elements at the same position in the two s + /// A function to apply to pairs of elements + /// The first input + /// The second input + /// Thrown when the s have different lengths + [] + val iter2<'T1, 'T2> : action : ('T1 -> 'T2 -> unit) -> list1 : FlatList<'T1> -> list2 : FlatList<'T2> -> unit + + /// Applies the given function to the trio of elements at the same position in the three s. + /// A function to apply to trios of elements. + /// The first input + /// The second input + /// The third input + /// Thrown when the s have different lengths + [] + val iter3<'T1, 'T2, 'T3> : + action : ('T1 -> 'T2 -> 'T3 -> unit) -> list1 : FlatList<'T1> -> list2 : FlatList<'T2> -> list3 : FlatList<'T3> -> unit + + /// Applies the given function to the pair of elements at the same position in the two s along with their index + /// A function to apply to pairs of elements and their index + /// The first input + /// The second input + /// Thrown when the s have different lengths + [] + val iteri2<'T1, 'T2> : action : (int -> 'T1 -> 'T2 -> unit) -> list1 : FlatList<'T1> -> list2 : FlatList<'T2> -> unit + + /// Applies the given function to the trio of elements at the same position in the three s along with their index. + /// A function to apply to trios of elements and their index. + /// The first input + /// The second input + /// The third input + /// Thrown when the s have different lengths + [] + val iteri3<'T1, 'T2, 'T3> : + action : (int -> 'T1 -> 'T2 -> 'T3 -> unit) -> + list1 : FlatList<'T1> -> + list2 : FlatList<'T2> -> + list3 : FlatList<'T3> -> + unit + + /// Tests if any element of the satisfies the given predicate + /// A function to test each element + /// The input + /// True if any element satisfies the predicate, false otherwise + [] + val exists<'T> : predicate : ('T -> bool) -> list : FlatList<'T> -> bool + + /// Tests if any corresponding pair of elements from the two s satisfies the given predicate + /// A function to test pairs of elements + /// The first input + /// The second input + /// True if any pair of elements satisfies the predicate, false otherwise + /// Thrown when the s have different lengths + [] + val exists2<'T1, 'T2> : predicate : ('T1 -> 'T2 -> bool) -> list1 : FlatList<'T1> -> list2 : FlatList<'T2> -> bool + + /// Tests if any corresponding trio of elements from the three s satisfies the given predicate. + /// A function to test trios of elements. + /// The first input + /// The second input + /// The third input + /// True if any trio of elements satisfies the predicate, false otherwise. + /// Thrown when the s have different lengths + [] + val exists3<'T1, 'T2, 'T3> : + predicate : ('T1 -> 'T2 -> 'T3 -> bool) -> list1 : FlatList<'T1> -> list2 : FlatList<'T2> -> list3 : FlatList<'T3> -> bool + + /// Tests if all elements of the satisfy the given predicate + /// A function to test each element + /// The input + /// True if all elements satisfy the predicate, false otherwise + [] + val forall<'T> : predicate : ('T -> bool) -> list : FlatList<'T> -> bool + + /// Tests if all corresponding pairs of elements from the two s satisfy the given predicate + /// A function to test pairs of elements + /// The first input + /// The second input + /// True if all pairs of elements satisfy the predicate, false otherwise + /// Thrown when the s have different lengths + [] + val forall2<'T1, 'T2> : predicate : ('T1 -> 'T2 -> bool) -> list1 : FlatList<'T1> -> list2 : FlatList<'T2> -> bool + + /// Tests if all corresponding trios of elements from the three s satisfy the given predicate. + /// A function to test trios of elements. + /// The first input + /// The second input + /// The third input + /// True if all trios of elements satisfy the predicate, false otherwise. + /// Thrown when the s have different lengths + [] + val forall3<'T1, 'T2, 'T3> : + predicate : ('T1 -> 'T2 -> 'T3 -> bool) -> list1 : FlatList<'T1> -> list2 : FlatList<'T2> -> list3 : FlatList<'T3> -> bool + + /// Tests if the given element exists in the + /// The element to find + /// The input + /// True if the element exists in the , false otherwise + [] + val inline contains<'T> : item : 'T -> list : FlatList<'T> -> bool + + /// Splits the into two s, containing the elements for which the given predicate returns true and false respectively + /// A function to test each element + /// The input + /// A tuple of two s, containing the elements for which the predicate returns true and false respectively + [] + val partition<'T> : predicate : ('T -> bool) -> list : FlatList<'T> -> FlatList<'T> * FlatList<'T> + + /// Returns the first element for which the given predicate returns true + /// A function to test elements + /// The input + /// The first element for which the predicate returns true + /// Thrown if no element satisfies the predicate + [] + val find<'T> : predicate : ('T -> bool) -> list : FlatList<'T> -> 'T + + /// Returns the first element for which the given predicate returns true, or ValueNone if no such element exists + /// A function to test elements + /// The input + /// ValueSome value if an element satisfies the predicate, ValueNone otherwise + [] + val tryFind<'T> : predicate : ('T -> bool) -> list : FlatList<'T> -> 'T voption + + /// Returns the last element for which the given predicate returns true + /// A function to test elements + /// The input + /// The last element for which the predicate returns true + /// Thrown if no element satisfies the predicate + [] + val findBack<'T> : predicate : ('T -> bool) -> list : FlatList<'T> -> 'T + + /// Returns the last element for which the given predicate returns true, or ValueNone if no such element exists + /// A function to test elements + /// The input + /// ValueSome value if an element satisfies the predicate, ValueNone otherwise + [] + val tryFindBack<'T> : predicate : ('T -> bool) -> list : FlatList<'T> -> 'T voption + + /// Returns the last element for which the given function returns true. + /// The function to test elements. + /// The input list. + /// The last element for which the predicate returns true. + /// Thrown if no element satisfies the predicate. + [] + val findLast<'T> : predicate : ('T -> bool) -> list : FlatList<'T> -> 'T + + /// Returns the last element for which the given function returns true, or ValueNone if no such element exists. + /// The function to test elements. + /// The input list. + /// ValueSome value if an element satisfies the predicate, ValueNone otherwise + [] + val tryFindLast<'T> : predicate : ('T -> bool) -> list : FlatList<'T> -> 'T voption + + /// Returns the last index for which the given predicate returns true + /// A function to test elements + /// The input + /// The last index for which the predicate returns true + /// Thrown if no element satisfies the predicate + [] + val findIndexBack<'T> : predicate : ('T -> bool) -> list : FlatList<'T> -> int + + /// Returns the last index for which the given predicate returns true, or ValueNone if no such element exists + /// A function to test elements + /// The input + /// ValueSome index if an element satisfies the predicate, ValueNone otherwise + [] + val tryFindIndexBack<'T> : predicate : ('T -> bool) -> list : FlatList<'T> -> int voption + + /// Returns the index of the last element in the that satisfies the given predicate. + /// The function to test elements. + /// The input list. + /// The index of the last element that satisfies the predicate. + /// Thrown if no element satisfies the predicate. + [] + val findLastIndex<'T> : predicate : ('T -> bool) -> list : FlatList<'T> -> int + + /// Returns the index of the last element in the that satisfies the given predicate, or ValueNone if no such element exists. + /// The function to test elements. + /// The input list. + /// ValueSome index if an element satisfies the predicate, ValueNone otherwise + [] + val tryFindLastIndex<'T> : predicate : ('T -> bool) -> list : FlatList<'T> -> int voption + + /// Returns the first value for which the given function returns ValueSome value + /// A function to generate options from the elements + /// The input + /// The first value for which the chooser returns ValueSome value + /// Thrown if the chooser returns ValueNone for all elements + [] + val pick<'T, 'U> : chooser : ('T -> 'U voption) -> list : FlatList<'T> -> 'U + + /// Returns the first value for which the given function returns ValueSome value, or ValueNone + /// A function to generate options from the elements + /// The input + /// The first value for which the chooser returns ValueSome value, or ValueNone + [] + val tryPick<'T, 'U> : chooser : ('T -> 'U voption) -> list : FlatList<'T> -> 'U voption + + /// Returns the last value for which the given function returns ValueSome value. + /// A function to generate options from the elements. + /// The input list. + /// The last value for which the chooser returns ValueSome value. + /// Thrown if the chooser returns ValueNone for all elements. + [] + val pickBack<'T, 'U> : chooser : ('T -> 'U voption) -> list : FlatList<'T> -> 'U + + /// Returns the last value for which the given function returns ValueSome value. + /// A function to generate options from the elements. + /// The input list. + /// The last value for which the chooser returns ValueSome value, or ValueNone. + [] + val tryPickBack<'T, 'U> : chooser : ('T -> 'U voption) -> list : FlatList<'T> -> 'U voption + + /// Builds a new containing only the elements for which the given function returns ValueSome value + /// A function to generate options from the elements + /// The input + /// A containing the values wrapped in ValueSome by the chooser + [] + val choose<'T, 'U> : chooser : ('T -> 'U voption) -> list : FlatList<'T> -> FlatList<'U> + + /// Builds a new collection from the elements of the input collection for which the given function returns a ValueSome value. + /// The elements are processed in reverse order. + /// A function to generate options from the elements. + /// The input list. + /// A new list containing the values from the successful choices, in reverse order of processing. + [] + val chooseBack<'T, 'U> : chooser : ('T -> 'U voption) -> list : FlatList<'T> -> FlatList<'U> + + /// Creates a by applying a key-generating function to each element of the and grouping the elements by the resulting keys + /// A function to transform elements into keys + /// The input + /// A of tuples where each tuple contains a key and a of all elements that match the key + [] + val groupBy<'T, 'Key> : projection : ('T -> 'Key) -> list : FlatList<'T> -> FlatList)> + + /// Returns a new that contains the elements of the original but with duplicates eliminated by using the supplied projection function + /// A function to transform elements before comparing them + /// The input + /// A with distinct elements as determined by the projection function + [] + val distinctBy<'T, 'Key> : projection : ('T -> 'Key) -> list : FlatList<'T> -> FlatList<'T> + + /// Finds the first duplicate element in the . + /// The input . + /// The first duplicate element. + /// Thrown if no duplicate is found. + [] + val findDup<'T> : list : FlatList<'T> -> 'T + + /// Finds the first element in the that is a duplicate of a preceding element according to the given projection function. + /// The function to transform the elements into a type supporting comparison. + /// The input . + /// The first duplicate element. + /// Thrown if no duplicate is found. + [] + val findDupBy<'T, 'Key> : projection : ('T -> 'Key) -> list : FlatList<'T> -> 'T + + /// Creates a new by applying a mapping function to each element of the input and concatenating the results + /// A function to transform elements of the input into s + /// The input + /// A containing the concatenation of all the s generated by the mapping function + [] + val collect<'T, 'U> : mapping : ('T -> 'U seq) -> list : FlatList<'T> -> FlatList<'U> + + /// Gets an element in the at the specified index + /// The index of the element to retrieve + /// The input + /// ValueSome value containing the element, or ValueNone if the index is out of range + [] + val tryItem<'T> : index : int -> list : FlatList<'T> -> 'T voption + + /// Returns the first element of the + /// The input + /// The first element of the + /// Thrown when the is empty + [] + val head<'T> : list : FlatList<'T> -> 'T + + /// Returns the first element of the , or ValueNone if the is empty + /// The input + /// ValueSome value containing the first element, or ValueNone if the is empty + [] + val tryHead<'T> : list : FlatList<'T> -> 'T voption + + /// Returns the first element and the rest of the , or ValueNone if the is empty. + /// The input list. + /// An option containing the first element and the rest of the , or ValueNone. + [] + val tryHeadAndTail<'T> : list : FlatList<'T> -> ('T * FlatList<'T>) voption + + /// Returns the last element of the + /// The input + /// The last element of the + /// Thrown when the is empty + [] + val last<'T> : list : FlatList<'T> -> 'T + + /// Returns the last element of the , or ValueNone if the is empty + /// The input + /// ValueSome value containing the last element, or ValueNone if the is empty + [] + val tryLast<'T> : list : FlatList<'T> -> 'T voption + + /// Returns the last element and all but the last element of the , or ValueNone if the is empty. + /// The input list. + /// An option containing the last element and all but the last element of the , or ValueNone. + [] + val tryLastAndInit<'T> : list : FlatList<'T> -> (FlatList<'T> * 'T) voption + + /// Returns the without its first element + /// The input + /// A containing all elements of the input except the first one + [] + val tail<'T> : list : FlatList<'T> -> FlatList<'T> + + /// Returns the without its first element, or ValueNone if the is empty + /// The input + /// ValueSome value containing the without its first element, or ValueNone if the is empty + [] + val tryTail<'T> : list : FlatList<'T> -> FlatList<'T> voption + + /// Returns the first N elements of the + /// The number of elements to take + /// The input + /// A containing the first N elements + /// Thrown when is negative or greater than the length of the list. + [] + val take<'T> : count : int -> list : FlatList<'T> -> FlatList<'T> + + /// Returns the last elements of the . + /// The number of elements to take from the end of the . + /// The input list. + /// A new list containing the last elements. + /// Thrown when is negative or greater than the length of the list. + [] + val takeEnd<'T> : count : int -> list : FlatList<'T> -> FlatList<'T> + + /// Returns a containing the first elements of the input for which the given predicate returns true + /// A function to test each element + /// The input + /// A containing the first elements for which the predicate returns true + [] + val takeWhile<'T> : predicate : ('T -> bool) -> list : FlatList<'T> -> FlatList<'T> + + /// Returns the without its first N elements + /// The number of elements to skip + /// The input + /// A containing all except the first N elements + /// Thrown when is negative or greater than the length of the list. + [] + val skip<'T> : index : int -> list : FlatList<'T> -> FlatList<'T> + + /// Returns a new that does not contain the last elements of the original . + /// The number of elements to skip from the end of the . + /// The input list. + /// A new list without the last elements. + /// Thrown when is negative or greater than the length of the list. + [] + val skipEnd<'T> : count : int -> list : FlatList<'T> -> FlatList<'T> + + /// Returns a that skips the elements of the input while the given predicate returns true, then returns the rest + /// A function to test each element + /// The input + /// A that skips the elements while the predicate returns true, then contains the rest + [] + val skipWhile<'T> : predicate : ('T -> bool) -> list : FlatList<'T> -> FlatList<'T> + + /// Gets a sublist of the input + /// The index of the first element to include + /// The number of elements in the sublist + /// The input + /// A containing the elements from start index for the given count + /// Thrown when is negative, is negative, or the sum of and exceeds the length of the list. + [] + val sub<'T> : start : int -> count : int -> list : FlatList<'T> -> FlatList<'T> + + /// Returns a that contains no more than N elements of the input + /// The maximum number of elements to include + /// The input + /// A containing at most N elements + /// Thrown when is negative. + [] + val truncate<'T> : count : int -> list : FlatList<'T> -> FlatList<'T> + + /// Splits the into two s at the specified index + /// The index at which to split the + /// The input + /// A tuple of two s, the first containing the elements up to the index, the second containing the rest + [] + val splitAt<'T> : index : int -> list : FlatList<'T> -> FlatList<'T> * FlatList<'T> + + /// Splits the into chunks of size at most 'chunkSize' + /// The maximum size of each chunk + /// The input + /// The split into chunks + /// Thrown when chunkSize is not positive or when is default + [] + val chunkBySize<'T> : chunkSize : int -> list : FlatList<'T> -> FlatList> + + /// Applies a function to the builder and returns the resulting + /// The function to apply to the builder + /// The created from the builder after applying the function + [] + val inline build<'T> : f : (FlatList<'T>.Builder -> unit) -> FlatList<'T> + + /// Updates the by applying a function to a builder initialized with the 's elements + /// The function to apply to the builder + /// The input + /// The updated + [] + val inline update<'T> : f : (FlatList<'T>.Builder -> unit) -> list : FlatList<'T> -> FlatList<'T> + + /// Returns the index of the first element in the that satisfies the given predicate + /// The function to test the input elements + /// The input + /// The index of the first element that satisfies the predicate + /// Thrown if no element satisfies the predicate + [] + val findIndex<'T> : predicate : ('T -> bool) -> list : FlatList<'T> -> int + + /// Returns the index of the first element in the that satisfies the given predicate, or ValueNone if no such element exists + /// The function to test the input elements + /// The input + /// The index of the first element that satisfies the predicate, or ValueNone + [] + val tryFindIndex<'T> : predicate : ('T -> bool) -> list : FlatList<'T> -> int voption + + /// Returns a new containing elements corresponding to a sliding window of elements from the input + /// The size of the window + /// The input + /// The resulting of sliding windows + /// Thrown when windowSize is not positive or when is default + /// + /// + /// let numbers = FlatList.ofArray [|1; 2; 3; 4; 5|] + /// let windows = FlatList.windowed 3 numbers + /// // windows is [|[|1; 2; 3|]; [|2; 3; 4|]; [|3; 4; 5|]|] + /// + /// // Calculate moving averages + /// let movingAverages = + /// windows + /// |> FlatList.map (fun window -> + /// FlatList.average window) + /// // movingAverages is [|2.0; 3.0; 4.0|] + /// + /// + [] + val windowed<'T> : windowSize : int -> list : FlatList<'T> -> FlatList> + + /// Returns a new containing pairs of adjacent elements from the input + /// The input + /// The resulting of pairs + [] + val pairwise<'T> : list : FlatList<'T> -> FlatList + + /// Splits the input into at most count chunks. + /// The maximum number of chunks. + /// The input . + /// The split into chunks. + /// Thrown when count is not positive. + [] + val splitInto<'T> : count : int -> list : FlatList<'T> -> FlatList> + + /// Returns a new that contains the elements of the original but with duplicates removed + /// The input + /// The with distinct elements + [] + val distinct<'T> : list : FlatList<'T> -> FlatList<'T> + + /// Returns a new that contains all pairwise combinations of elements from the first and second s + /// The first input + /// The second input + /// The of all pairwise combinations + [] + val allPairs<'T, 'U> : xs : FlatList<'T> -> ys : FlatList<'U> -> FlatList<('T * 'U)> + + /// Returns a new with the elements permuted according to the specified permutation + /// The function that maps input indices to output indices + /// The input + /// The permuted + /// Thrown when the permutation function returns an out-of-range index + [] + val permute<'T> : indexMap : (int -> int) -> list : FlatList<'T> -> FlatList<'T> + + /// Combines the two s into a of pairs. The two s must have equal lengths + /// The first input + /// The second input + /// The of pairs + /// Thrown when the s have different lengths + [] + val zip<'T, 'U> : list1 : FlatList<'T> -> list2 : FlatList<'U> -> FlatList + + /// Combines the three s into a of triples. The three s must have equal lengths + /// The first input + /// The second input + /// The third input + /// The of triples + /// Thrown when the s have different lengths + [] + val zip3<'T, 'U, 'V> : list1 : FlatList<'T> -> list2 : FlatList<'U> -> list3 : FlatList<'V> -> FlatList + + /// Splits a of pairs into two s + /// The input + /// The two s unzipped from the input + [] + val unzip<'T, 'U> : list : FlatList -> struct (FlatList<'T> * FlatList<'U>) + + /// Splits a of triples into three s + /// The input + /// The three s unzipped from the input + [] + val unzip3<'T, 'U, 'V> : list : FlatList -> struct (FlatList<'T> * FlatList<'U> * FlatList<'V>) + + /// Returns the average of the elements in the + /// The input + /// The average of the elements + /// Thrown when the is empty + [] + val inline average<'T + when 'T : (static member (+) : 'T * 'T -> 'T) + and 'T : (static member DivideByInt : 'T * int -> 'T) + and 'T : (static member Zero : 'T)> : list : FlatList<'T> -> 'T + + /// Returns the average of the results of applying the function to each element of the + /// The function to transform the elements before averaging + /// The input + /// The average of the projected elements + /// Thrown when the is empty + [] + val inline averageBy<'T, 'U + when 'U : (static member (+) : 'U * 'U -> 'U) + and 'U : (static member DivideByInt : 'U * int -> 'U) + and 'U : (static member Zero : 'U)> : projection : ('T -> 'U) -> list : FlatList<'T> -> 'U + + /// Applies a function to each element of the , threading an accumulator argument through the computation + /// The function to update the state given the input elements + /// The initial state + /// The input + /// The final state + /// + /// + /// let numbers = FlatList.ofArray [|1; 2; 3; 4; 5|] + /// let sum = FlatList.fold (fun acc x -> acc + x) 0 numbers + /// // sum is 15 + /// + /// // Computing the average + /// let count = FlatList.length numbers + /// let total = FlatList.fold (fun sum x -> sum + x) 0 numbers + /// let avg = float total / float count + /// + /// + [] + val fold<'T, 'State> : folder : ('State -> 'T -> 'State) -> state : 'State -> list : FlatList<'T> -> 'State + + /// Applies a function to each element of the collection, threading an accumulator argument + /// through the computation. The integer index passed to the function indicates the index of the + /// element. The seed is used as the initial accumulator value. + /// The function to update the state given the index, the input elements, and the previous state. + /// The initial state. + /// The input list. + /// The final state. + [] + val foldi<'T, 'State> : folder : (int -> 'State -> 'T -> 'State) -> state : 'State -> list : FlatList<'T> -> 'State + + /// Applies a function to corresponding elements of two s, threading an accumulator argument through the computation + /// The function to update the state given the input elements from both s + /// The initial state + /// The first input + /// The second input + /// The final state + /// Thrown when the s have different lengths + [] + val fold2<'T1, 'T2, 'State> : + folder : ('State -> 'T1 -> 'T2 -> 'State) -> state : 'State -> list1 : FlatList<'T1> -> list2 : FlatList<'T2> -> 'State + + /// Applies a function to corresponding elements of two s, threading an accumulator argument + /// through the computation. The integer index passed to the function indicates the index of the + /// elements. The seed is used as the initial accumulator value. + /// The function to update the state given the index, the input elements from both s, and the previous state. + /// The initial state. + /// The first input list. + /// The second input list. + /// The final state. + /// Thrown when the lists have different lengths. + [] + val foldi2<'T1, 'T2, 'State> : + folder : (int -> 'State -> 'T1 -> 'T2 -> 'State) -> + state : 'State -> + list1 : FlatList<'T1> -> + list2 : FlatList<'T2> -> + 'State + + /// Applies a function to each element of the , threading an accumulator argument through the computation, starting from the end. + /// The function to update the state given the input elements, starting from the end. + /// The input . + /// The initial state. + /// The final state. + /// Thrown when the is default or empty + [] + val foldBack<'T, 'State> : folder : ('T -> 'State -> 'State) -> list : FlatList<'T> -> state : 'State -> 'State + + /// Applies a function to each element of the collection, starting from the end, threading an + /// accumulator argument through the computation. The integer index passed to the function indicates + /// the index of the element. The seed is used as the initial accumulator value. + /// The function to update the state given the index, the input elements, and the previous state. + /// The input list. + /// The initial state. + /// The final state. + [] + val foldBacki<'T, 'State> : folder : (int -> 'T -> 'State -> 'State) -> list : FlatList<'T> -> state : 'State -> 'State + + /// Applies a function to corresponding elements of two s, threading an accumulator argument through the computation, starting from the end. + /// The function to update the state given the input elements from both s, starting from the end. + /// The first input + /// The second input + /// The initial state. + /// The final state. + /// Thrown when the s have different lengths + [] + val foldBack2<'T1, 'T2, 'State> : + folder : ('T1 -> 'T2 -> 'State -> 'State) -> list1 : FlatList<'T1> -> list2 : FlatList<'T2> -> state : 'State -> 'State + + /// Applies a function to corresponding elements of two s, starting from the end, threading + /// an accumulator argument through the computation. The integer index passed to the function indicates + /// the index of the elements. The seed is used as the initial accumulator value. + /// The function to update the state given the index, the input elements from both s, and the previous state. + /// The first input list. + /// The second input list. + /// The initial state. + /// The final state. + /// Thrown when the lists have different lengths. + [] + val foldBacki2<'T1, 'T2, 'State> : + folder : (int -> 'T1 -> 'T2 -> 'State -> 'State) -> + list1 : FlatList<'T1> -> + list2 : FlatList<'T2> -> + state : 'State -> + 'State + + /// Applies a function to corresponding elements of three s, threading an accumulator argument through the computation, starting from the end. + /// The function to update the state given the input elements from all three s, starting from the end. + /// The first input + /// The second input + /// The third input + /// The initial state. + /// The final state. + /// Thrown when the s have different lengths + [] + val foldBack3<'T1, 'T2, 'T3, 'State> : + folder : ('T1 -> 'T2 -> 'T3 -> 'State -> 'State) -> + list1 : FlatList<'T1> -> + list2 : FlatList<'T2> -> + list3 : FlatList<'T3> -> + state : 'State -> + 'State + + /// Applies a function to each element of the , threading an accumulator argument through the computation. + /// This function takes the second argument, and applies the function to it and the first element of the . + /// Then, it passes this result into the function along with the second element, and so on. + /// Finally, it returns the final result. If the is empty, an exception is raised. + /// The function to reduce the with + /// The input + /// The final accumulated value + /// Thrown when the is empty + [] + val reduce<'T> : reduction : ('T -> 'T -> 'T) -> list : FlatList<'T> -> 'T + + /// Applies a function to each element of the , threading an accumulator argument through the computation, starting from the end. + /// This function takes the last element of the and the second-to-last element, and applies the function to them. + /// Then, it passes this result into the function along with the third-to-last element, and so on. + /// Finally, it returns the final result. If the is empty, an exception is raised. + /// The function to reduce the with, starting from the end + /// The input + /// The final accumulated value + /// Thrown when the is empty + [] + val reduceBack<'T> : reduction : ('T -> 'T -> 'T) -> list : FlatList<'T> -> 'T + + /// Like fold, but returns both the intermediate and final results + /// The function to update the state given the input elements + /// The initial state + /// The input + /// The of all intermediate and final states + [] + val scan<'T, 'State> : folder : ('State -> 'T -> 'State) -> state : 'State -> list : FlatList<'T> -> FlatList<'State> + + /// Like foldBack, but returns both the intermediate and final results + /// The function to update the state given the input elements, starting from the end + /// The input + /// The initial state + /// The of all intermediate and final states, in reverse order of computation + [] + val scanBack<'T, 'State> : folder : ('T -> 'State -> 'State) -> list : FlatList<'T> -> state : 'State -> FlatList<'State> + + /// Returns the only element of the . + /// The input . + /// The only element of the . + /// Thrown when the input does not have precisely one element. + [] + val exactlyOne<'T> : list : FlatList<'T> -> 'T + + /// Returns the only element of the or None if the is empty or contains more than one element. + /// The input . + /// The only element of the or None. + [] + val tryExactlyOne<'T> : list : FlatList<'T> -> 'T voption + + /// Returns a new list with the distinct elements of the input which do not appear in the itemsToExclude sequence + /// A sequence whose elements that also occur in the input will cause those elements to be removed + /// The input + /// A new that contains the distinct elements of list that do not appear in itemsToExclude + [] + val except<'T> : itemsToExclude : 'T seq -> list : FlatList<'T> -> FlatList<'T> + + /// Returns the sum of the elements in the . + /// The input . + /// The resulting sum. + /// Thrown when the input is default or empty. + [] + val inline sum<'T when 'T : (static member (+) : 'T * 'T -> 'T) and 'T : (static member Zero : 'T)> : + list : FlatList<'T> -> 'T + + /// Returns the sum of the results generated by applying the function to each element of the . + /// The function to transform the elements into the type to be summed. + /// The input . + /// The resulting sum. + /// Thrown when the input is default or empty. + [] + val inline sumBy<'T, 'U when 'U : (static member (+) : 'U * 'U -> 'U) and 'U : (static member Zero : 'U)> : + projection : ('T -> 'U) -> list : FlatList<'T> -> 'U + + /// Returns the transpose of the given sequence of s. + /// The input of s. + [] + val transpose<'T> : lists : FlatList> -> FlatList> + + /// Updates the element at the specified index in the array. + /// The index of the element to update. + /// The new value for the element. + /// The input array. + /// The updated array. + [] + val updateAt<'T> : index : int -> value : 'T -> list : FlatList<'T> -> FlatList<'T> + + /// Removes the element at the specified index from the array. + /// The index of the element to remove. + /// The input array. + /// The array with the element removed. + [] + val removeAt<'T> : index : int -> list : FlatList<'T> -> FlatList<'T> + + /// Inserts an element at the specified index in the array. + /// The index at which to insert the element. + /// The element to insert. + /// The input array. + /// The array with the element inserted. + [] + val insertAt<'T> : index : int -> value : 'T -> list : FlatList<'T> -> FlatList<'T> + + /// Inserts multiple elements at the specified index in the array. + /// The index at which to insert the elements. + /// The elements to insert. + /// The input array. + /// The array with the elements inserted. + [] + val insertManyAt<'T> : index : int -> values : 'T seq -> list : FlatList<'T> -> FlatList<'T> + + /// Generates a by repeatedly applying a function to a state. + /// The function to generate the next element and state. + /// The initial state. + /// The generated sequence. + [] + val unfold<'T, 'State> : generator : ('State -> struct ('T * 'State) voption) -> state : 'State -> FlatList<'T> + + /// Compares two arrays using a custom comparison function. + /// The function to compare elements. + /// The first input array. + /// The second input array. + /// The comparison result. + [] + val compareWith<'T> : comparer : ('T -> 'T -> int) -> list1 : FlatList<'T> -> list2 : FlatList<'T> -> int + + /// Returns the maximum element in the array. + /// The input array. + /// The maximum element. + /// Thrown when the input array is empty. + [] + val inline max<'T when 'T : comparison> : list : FlatList<'T> -> 'T + + /// Returns the maximum element in the array according to a projection function. + /// The function to transform the elements into a type supporting comparison. + /// The input array. + /// The maximum element. + /// Thrown when the input array is empty. + [] + val inline maxBy<'T, 'Key when 'Key : comparison> : projection : ('T -> 'Key) -> list : FlatList<'T> -> 'T + + /// Returns the minimum element in the . + /// The input . + /// The minimum element. + /// Thrown when the input is default or empty. + [] + val inline min<'T when 'T : comparison> : list : FlatList<'T> -> 'T + + /// Returns the minimum element in the according to a projection function. + /// The function to transform the elements into a type supporting comparison. + /// The input . + /// The minimum element. + /// Thrown when the input is default or empty. + [] + val inline minBy<'T, 'Key when 'Key : comparison> : projection : ('T -> 'Key) -> list : FlatList<'T> -> 'T diff --git a/src/FSharp.Collections.Immutable/Helper.fs b/src/FSharp.Collections.Immutable/Helper.fs index 1777e1a..43d3bdb 100644 --- a/src/FSharp.Collections.Immutable/Helper.fs +++ b/src/FSharp.Collections.Immutable/Helper.fs @@ -19,6 +19,12 @@ module internal ErrorStrings = [] let ListsHaveDifferentLengths = "The lists have different lengths." +[] +module internal Struct = + + let fstv tuple = let struct (a, _) = tuple in a + let sndv tuple = let struct (_, b) = tuple in b + [] module internal ValueOption = diff --git a/tests/FSharp.Collections.Immutable.Tests/FSharp.Collections.Immutable.Tests.fsproj b/tests/FSharp.Collections.Immutable.Tests/FSharp.Collections.Immutable.Tests.fsproj index eaa1313..afe226e 100644 --- a/tests/FSharp.Collections.Immutable.Tests/FSharp.Collections.Immutable.Tests.fsproj +++ b/tests/FSharp.Collections.Immutable.Tests/FSharp.Collections.Immutable.Tests.fsproj @@ -19,6 +19,7 @@ + diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/BasicOperationsTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/BasicOperationsTests.fs index b6db069..5d6b3de 100644 --- a/tests/FSharp.Collections.Immutable.Tests/FlatList/BasicOperationsTests.fs +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/BasicOperationsTests.fs @@ -135,3 +135,41 @@ type BasicOperationsTests () = Assert.AreEqual (ValueSome 42, FlatList.tryExactlyOne singletonList) Assert.AreEqual (ValueNone, FlatList.tryExactlyOne emptyList) Assert.AreEqual (ValueNone, FlatList.tryExactlyOne multipleList) + + [] + member _.``tryHeadAndTail returns head and tail for non-empty list`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + + match FlatList.tryHeadAndTail flatList with + | ValueSome (head, tail) -> + Assert.AreEqual (1, head) + Assert.AreEqual (2, tail.Length) + Assert.AreEqual (2, tail.[0]) + Assert.AreEqual (3, tail.[1]) + | ValueNone -> Assert.Fail ("Should return ValueSome for non-empty list") + + [] + member _.``tryHeadAndTail returns ValueNone for empty list`` () = + let emptyList = FlatList.empty + let result = FlatList.tryHeadAndTail emptyList + + Assert.AreEqual<(int * FlatList) voption> (ValueNone, result) + + [] + member _.``tryLastAndInit returns init and last for non-empty list`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + + match FlatList.tryLastAndInit flatList with + | ValueSome (init, last) -> + Assert.AreEqual (3, last) + Assert.AreEqual (2, init.Length) + Assert.AreEqual (1, init.[0]) + Assert.AreEqual (2, init.[1]) + | ValueNone -> Assert.Fail ("Should return ValueSome for non-empty list") + + [] + member _.``tryLastAndInit returns ValueNone for empty list`` () = + let emptyList = FlatList.empty + let result = FlatList.tryLastAndInit emptyList + + Assert.AreEqual<(FlatList * int) voption> (ValueNone, result) diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/BuilderTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/BuilderTests.fs index 2d4beeb..ae75e79 100644 --- a/tests/FSharp.Collections.Immutable.Tests/FlatList/BuilderTests.fs +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/BuilderTests.fs @@ -37,7 +37,7 @@ type BuilderTests () = [] member _.``moveFromBuilder builds FlatList and empties builder`` () = - let b = FlatList.builder () + let b = FlatList.builderWith 3 // Set exact capacity b.Add (1) b.Add (2) b.Add (3) @@ -59,10 +59,11 @@ type BuilderTests () = [] member _.``Builder.add adds to builder`` () = let b = FlatList.builder () - FlatList.Builder.add 42 b + let returned = FlatList.Builder.add 42 b Assert.AreEqual (1, b.Count) Assert.AreEqual (42, b.[0]) + Assert.AreSame (b, returned) // Check that same builder is returned [] member _.``build applies function to builder and returns FlatList`` () = diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/CreationTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/CreationTests.fs index b2b6c98..7227bb9 100644 --- a/tests/FSharp.Collections.Immutable.Tests/FlatList/CreationTests.fs +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/CreationTests.fs @@ -119,5 +119,91 @@ type CreationTests () = for i = 0 to original.Length - 1 do Assert.AreEqual (original.[i], copy.[i]) - // Verify copy is independent (this is true for immutable collections) - Assert.AreNotEqual (original.GetHashCode (), copy.GetHashCode ()) + // Verify both reference the same array under the hood + Assert.AreEqual (original.GetHashCode (), copy.GetHashCode ()) + + [] + member _.``zeroCreate creates FlatList with default values`` () = + let flatList = FlatList.zeroCreate 3 + + Assert.AreEqual (3, flatList.Length) + for i = 0 to flatList.Length - 1 do + Assert.AreEqual (0, flatList.[i]) + + [] + member _.``zeroCreate creates FlatList with reference type defaults`` () = + let flatList = FlatList.zeroCreate 2 + + Assert.AreEqual (2, flatList.Length) + for i = 0 to flatList.Length - 1 do + Assert.AreEqual (null, flatList.[i]) + + [] + member _.``ofOption creates singleton for Some`` () = + let result = FlatList.ofOption (Some 42) + + Assert.AreEqual (1, result.Length) + Assert.AreEqual (42, result.[0]) + + [] + member _.``ofOption creates empty for None`` () = + let result = FlatList.ofOption None + + Assert.AreEqual (0, result.Length) + Assert.IsTrue (FlatList.isEmpty result) + + [] + member _.``ofValueOption creates singleton for ValueSome`` () = + let result = FlatList.ofValueOption (ValueSome 42) + + Assert.AreEqual (1, result.Length) + Assert.AreEqual (42, result.[0]) + + [] + member _.``ofValueOption creates empty for ValueNone`` () = + let result = FlatList.ofValueOption ValueNone + + Assert.AreEqual (0, result.Length) + Assert.IsTrue (FlatList.isEmpty result) + + [] + member _.``toOption returns Some for singleton`` () = + let flatList = FlatList.singleton 42 + let result = FlatList.toOption flatList + + Assert.AreEqual (Some 42, result) + + [] + member _.``toOption returns None for empty`` () = + let flatList = FlatList.empty + let result = FlatList.toOption flatList + + Assert.AreEqual (None, result) + + [] + member _.``toOption returns None for multiple elements`` () = + let flatList = FlatList.ofArray [| 1; 2 |] + let result = FlatList.toOption flatList + + Assert.AreEqual (None, result) + + [] + member _.``toValueOption returns ValueSome for singleton`` () = + let flatList = FlatList.singleton 42 + let result = FlatList.toValueOption flatList + + Assert.AreEqual (ValueSome 42, result) + + [] + member _.``toValueOption returns ValueNone for empty`` () = + let flatList = FlatList.empty + let result = FlatList.toValueOption flatList + + Assert.AreEqual (ValueNone, result) + + [] + member _.``toValueOption returns ValueNone for multiple elements`` () = + let flatList = FlatList.ofArray [| 1; 2 |] + let result = FlatList.toValueOption flatList + + Assert.AreEqual (ValueNone, result) diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/FilterMapTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/FilterMapTests.fs index 9c16fbd..ae97b8d 100644 --- a/tests/FSharp.Collections.Immutable.Tests/FlatList/FilterMapTests.fs +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/FilterMapTests.fs @@ -79,6 +79,16 @@ type FilterMapTests () = Assert.AreEqual (2, result.[0]) Assert.AreEqual (4, result.[1]) + [] + member _.``filter removes elements that do not match predicate`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4; 5; 6 |] + let result = FlatList.filter (fun x -> x % 2 = 0) flatList + + Assert.AreEqual (3, result.Length) + Assert.AreEqual (2, result.[0]) + Assert.AreEqual (4, result.[1]) + Assert.AreEqual (6, result.[2]) + [] member _.``where is alias for filter`` () = let flatList = FlatList.ofArray [| 1; 2; 3; 4; 5 |] @@ -89,6 +99,16 @@ type FilterMapTests () = for i = 0 to filtered.Length - 1 do Assert.AreEqual (filtered.[i], wheered.[i]) + [] + member _.``where removes elements that do not match predicate`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4; 5; 6 |] + let result = FlatList.where (fun x -> x % 2 = 0) flatList + + Assert.AreEqual (3, result.Length) + Assert.AreEqual (2, result.[0]) + Assert.AreEqual (4, result.[1]) + Assert.AreEqual (6, result.[2]) + [] member _.``choose selects and maps elements`` () = let flatList = FlatList.ofArray [| 1; 2; 3; 4; 5 |] @@ -96,6 +116,24 @@ type FilterMapTests () = CollectionAssert.AreEqual ([| 20; 40 |], FlatList.toArray result) + [] + member _.``choose transforms and filters elements`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4; 5 |] + let result = FlatList.choose (fun x -> if x % 2 = 0 then ValueSome (x * 10) else ValueNone) flatList + + Assert.AreEqual (2, result.Length) + Assert.AreEqual (20, result.[0]) + Assert.AreEqual (40, result.[1]) + + [] + member _.``chooseBack transforms and filters elements in reverse`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4; 5 |] + let result = FlatList.chooseBack (fun x -> if x % 2 = 0 then ValueSome (x * 10) else ValueNone) flatList + + Assert.AreEqual (2, result.Length) + Assert.AreEqual (40, result.[0]) + Assert.AreEqual (20, result.[1]) + [] member _.``collect maps and concatenates`` () = let flatList = FlatList.ofArray [| 1; 2; 3 |] @@ -109,6 +147,19 @@ type FilterMapTests () = Assert.AreEqual (3, result.[4]) Assert.AreEqual (30, result.[5]) + [] + member _.``collect transforms and concatenates results`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + let result = FlatList.collect (fun x -> [| x; x * 10 |]) flatList + + Assert.AreEqual (6, result.Length) + Assert.AreEqual (1, result.[0]) + Assert.AreEqual (10, result.[1]) + Assert.AreEqual (2, result.[2]) + Assert.AreEqual (20, result.[3]) + Assert.AreEqual (3, result.[4]) + Assert.AreEqual (30, result.[5]) + [] member _.``partition splits elements based on predicate`` () = let flatList = FlatList.ofArray [| 1; 2; 3; 4; 5 |] @@ -117,6 +168,21 @@ type FilterMapTests () = CollectionAssert.AreEqual ([| 2; 4 |], FlatList.toArray evens) CollectionAssert.AreEqual ([| 1; 3; 5 |], FlatList.toArray odds) + [] + member _.``partition splits list by predicate`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4; 5; 6 |] + let (evens, odds) = FlatList.partition (fun x -> x % 2 = 0) flatList + + Assert.AreEqual (3, evens.Length) + Assert.AreEqual (2, evens.[0]) + Assert.AreEqual (4, evens.[1]) + Assert.AreEqual (6, evens.[2]) + + Assert.AreEqual (3, odds.Length) + Assert.AreEqual (1, odds.[0]) + Assert.AreEqual (3, odds.[1]) + Assert.AreEqual (5, odds.[2]) + [] member _.``distinct removes duplicates`` () = let flatList = FlatList.ofArray [| 1; 2; 3; 1; 2; 5 |] diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/FoldTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/FoldTests.fs index 8d3fbb8..c19178b 100644 --- a/tests/FSharp.Collections.Immutable.Tests/FlatList/FoldTests.fs +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/FoldTests.fs @@ -85,7 +85,7 @@ type FoldTests () = Assert.AreEqual (10, result) [] - [)>] + [)>] member _.``reduce throws on empty list`` () = FlatList.reduce (fun acc x -> acc + x) FlatList.empty |> ignore @@ -99,7 +99,7 @@ type FoldTests () = Assert.AreEqual (-2, result) [] - [)>] + [)>] member _.``reduceBack throws on empty list`` () = FlatList.reduceBack (fun x acc -> x + acc) FlatList.empty |> ignore diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/GroupingTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/GroupingTests.fs index 6f4a6ac..995b1d2 100644 --- a/tests/FSharp.Collections.Immutable.Tests/FlatList/GroupingTests.fs +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/GroupingTests.fs @@ -61,12 +61,17 @@ type GroupingTests () = Assert.AreEqual (3, result.[1].Length) Assert.AreEqual (1, result.[2].Length) - for i = 0 to 2 do - Assert.AreEqual (i * 3 + 1, result.[0].[i]) + // Check first chunk + Assert.AreEqual (1, result.[0].[0]) + Assert.AreEqual (2, result.[0].[1]) + Assert.AreEqual (3, result.[0].[2]) - for i = 0 to 2 do - Assert.AreEqual (i * 3 + 4, result.[1].[i]) + // Check second chunk + Assert.AreEqual (4, result.[1].[0]) + Assert.AreEqual (5, result.[1].[1]) + Assert.AreEqual (6, result.[1].[2]) + // Check third chunk Assert.AreEqual (7, result.[2].[0]) [] @@ -141,9 +146,9 @@ type GroupingTests () = let result = FlatList.pairwise flatList Assert.AreEqual (3, result.Length) - Assert.AreEqual ((10, 20), result.[0]) - Assert.AreEqual ((20, 30), result.[1]) - Assert.AreEqual ((30, 40), result.[2]) + Assert.AreEqual (struct (10, 20), result.[0]) + Assert.AreEqual (struct (20, 30), result.[1]) + Assert.AreEqual (struct (30, 40), result.[2]) [] member _.``pairwise returns empty for singleton or empty`` () = diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/ManipulationTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/ManipulationTests.fs index e8e56e9..dee0bee 100644 --- a/tests/FSharp.Collections.Immutable.Tests/FlatList/ManipulationTests.fs +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/ManipulationTests.fs @@ -213,10 +213,9 @@ type ManipulationTests () = let result = FlatList.removeAll itemsToRemove flatList - Assert.AreEqual (3, result.Length) + Assert.AreEqual (2, result.Length) Assert.AreEqual (10, result.[0]) - Assert.AreEqual (20, result.[1]) // Only removes first occurrence by default - Assert.AreEqual (40, result.[2]) + Assert.AreEqual (40, result.[1]) [] member _.``except removes elements from another collection`` () = @@ -229,3 +228,134 @@ type ManipulationTests () = Assert.IsTrue (FlatList.contains 2 result) Assert.IsTrue (FlatList.contains 4 result) Assert.IsTrue (FlatList.contains 5 result) + + [] + member _.``takeEnd returns last N elements`` () = + let flatList = FlatList.ofArray [| 10; 20; 30; 40; 50 |] + + let result1 = FlatList.takeEnd 3 flatList + Assert.AreEqual (3, result1.Length) + Assert.AreEqual (30, result1.[0]) + Assert.AreEqual (40, result1.[1]) + Assert.AreEqual (50, result1.[2]) + + let result2 = FlatList.takeEnd 0 flatList + Assert.AreEqual (0, result2.Length) + + [] + [)>] + member _.``takeEnd throws for negative count`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + FlatList.takeEnd -1 flatList |> ignore + + [] + member _.``skipEnd skips last N elements`` () = + let flatList = FlatList.ofArray [| 10; 20; 30; 40; 50 |] + + let result1 = FlatList.skipEnd 2 flatList + Assert.AreEqual (3, result1.Length) + Assert.AreEqual (10, result1.[0]) + Assert.AreEqual (20, result1.[1]) + Assert.AreEqual (30, result1.[2]) + + let result2 = FlatList.skipEnd 0 flatList + Assert.AreEqual (5, result2.Length) + + [] + [)>] + member _.``skipEnd throws for negative count`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + FlatList.skipEnd -1 flatList |> ignore + + [] + member _.``findDup finds first duplicate element`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 2; 4 |] + let result = FlatList.findDup flatList + + Assert.AreEqual (2, result) + + [] + [)>] + member _.``findDup throws when no duplicates`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + FlatList.findDup flatList |> ignore + + [] + member _.``findDupBy finds first duplicate element by projection`` () = + let flatList = FlatList.ofArray [| "apple"; "banana"; "avocado"; "cherry" |] + let result = FlatList.findDupBy (fun (s : string) -> s.[0]) flatList + + Assert.AreEqual ("avocado", result) + + [] + [)>] + member _.``findDupBy throws when no duplicates`` () = + let flatList = FlatList.ofArray [| "apple"; "banana"; "cherry" |] + FlatList.findDupBy (fun (s : string) -> s.[0]) flatList + |> ignore + + [] + member _.``unfold generates FlatList from state`` () = + let result = + FlatList.unfold + (fun state -> + if state > 5 then + ValueNone + else + ValueSome (struct (state, state + 1)) + ) + 1 + + Assert.AreEqual (5, result.Length) + Assert.AreEqual (1, result.[0]) + Assert.AreEqual (2, result.[1]) + Assert.AreEqual (3, result.[2]) + Assert.AreEqual (4, result.[3]) + Assert.AreEqual (5, result.[4]) + + [] + member _.``unfold handles empty generation`` () = + let result = FlatList.unfold (fun _ -> ValueNone) 1 + + Assert.AreEqual (0, result.Length) + Assert.IsTrue (FlatList.isEmpty result) + + [] + member _.``compareWith compares two FlatLists`` () = + let list1 = FlatList.ofArray [| 1; 2; 3 |] + let list2 = FlatList.ofArray [| 1; 2; 3 |] + let list3 = FlatList.ofArray [| 1; 2; 4 |] + let list4 = FlatList.ofArray [| 1; 2 |] + + Assert.AreEqual (0, FlatList.compareWith compare list1 list2) + Assert.IsTrue (FlatList.compareWith compare list1 list3 < 0) + Assert.IsTrue (FlatList.compareWith compare list3 list1 > 0) + Assert.IsTrue (FlatList.compareWith compare list4 list1 < 0) + Assert.IsTrue (FlatList.compareWith compare list1 list4 > 0) + + [] + member _.``transpose transposes matrix of lists`` () = + let matrix = + FlatList.ofArray [| FlatList.ofArray [| 1; 2; 3 |]; FlatList.ofArray [| 4; 5; 6 |]; FlatList.ofArray [| 7; 8; 9 |] |] + + let result = FlatList.transpose matrix + + Assert.AreEqual (3, result.Length) + + let col1 = result.[0] + Assert.AreEqual (3, col1.Length) + Assert.AreEqual (1, col1.[0]) + Assert.AreEqual (4, col1.[1]) + Assert.AreEqual (7, col1.[2]) + + let col2 = result.[1] + Assert.AreEqual (3, col2.Length) + Assert.AreEqual (2, col2.[0]) + Assert.AreEqual (5, col2.[1]) + Assert.AreEqual (8, col2.[2]) + + let col3 = result.[2] + Assert.AreEqual (3, col3.Length) + Assert.AreEqual (3, col3.[0]) + Assert.AreEqual (6, col3.[1]) + Assert.AreEqual (9, col3.[2]) diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/MapFoldTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/MapFoldTests.fs new file mode 100644 index 0000000..8e345c4 --- /dev/null +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/MapFoldTests.fs @@ -0,0 +1,83 @@ +namespace FSharp.Collections.Immutable.Tests.FlatList + +open System +open System.Collections.Immutable +open System.Collections.Generic +open Microsoft.VisualStudio.TestTools.UnitTesting +open FSharp.Collections.Immutable + +[] +type MapFoldTests () = + + [] + member _.``mapFold transforms elements and accumulates state`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4 |] + let mapped, total = FlatList.mapFold (fun state x -> (x * 2, state + x)) 0 flatList + + // Mapped elements are doubled: [2; 4; 6; 8] + // State accumulates the sum of original elements: 0 + 1 + 2 + 3 + 4 = 10 + Assert.AreEqual (4, mapped.Length) + Assert.AreEqual (2, mapped.[0]) + Assert.AreEqual (4, mapped.[1]) + Assert.AreEqual (6, mapped.[2]) + Assert.AreEqual (8, mapped.[3]) + Assert.AreEqual (10, total) + + [] + member _.``mapFold works with empty list`` () = + let emptyList = FlatList.empty + let mapped, state = FlatList.mapFold (fun state x -> (x * 2, state + x)) 42 emptyList + + // Should return an empty list and the initial state unchanged + Assert.IsTrue (mapped.IsEmpty) + Assert.AreEqual (42, state) + + [] + member _.``mapFold with string concatenation`` () = + let flatList = FlatList.ofArray [| "a"; "b"; "c" |] + let mapped, concatenated = + FlatList.mapFold (fun state x -> (x.ToUpper (), state + x)) "" flatList + + // Mapped elements are uppercase: ["A"; "B"; "C"] + // State concatenates the original strings: "abc" + Assert.AreEqual (3, mapped.Length) + Assert.AreEqual ("A", mapped.[0]) + Assert.AreEqual ("B", mapped.[1]) + Assert.AreEqual ("C", mapped.[2]) + Assert.AreEqual ("abc", concatenated) + + [] + member _.``mapFoldBack transforms elements and accumulates state in reverse`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4 |] + let mapped, total = FlatList.mapFoldBack (fun x state -> (x * 2, state + x)) flatList 0 + + // Mapped elements are doubled: [2; 4; 6; 8] + // State accumulates in reverse: 0 + 4 + 3 + 2 + 1 = 10 + Assert.AreEqual (4, mapped.Length) + Assert.AreEqual (2, mapped.[0]) + Assert.AreEqual (4, mapped.[1]) + Assert.AreEqual (6, mapped.[2]) + Assert.AreEqual (8, mapped.[3]) + Assert.AreEqual (10, total) + + [] + member _.``mapFoldBack works with empty list`` () = + let emptyList = FlatList.empty + let mapped, state = FlatList.mapFoldBack (fun x state -> (x * 2, state + x)) emptyList 42 + + // Should return an empty list and the initial state unchanged + Assert.IsTrue (mapped.IsEmpty) + Assert.AreEqual (42, state) + + [] + member _.``mapFoldBack creates indices in reversed order`` () = + let chars = FlatList.ofArray [| 'a'; 'b'; 'c' |] + let indices, count = FlatList.mapFoldBack (fun _ state -> (state, state + 1)) chars 0 + + // Creates indices in reversed order: [2; 1; 0] + // Final state is the count of elements: 3 + Assert.AreEqual (3, indices.Length) + Assert.AreEqual (2, indices.[0]) + Assert.AreEqual (1, indices.[1]) + Assert.AreEqual (0, indices.[2]) + Assert.AreEqual (3, count) diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/PairOperationTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/PairOperationTests.fs index 3b23c9a..25c32c7 100644 --- a/tests/FSharp.Collections.Immutable.Tests/FlatList/PairOperationTests.fs +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/PairOperationTests.fs @@ -47,9 +47,9 @@ type PairOperationTests () = let result = FlatList.zip list1 list2 Assert.AreEqual (3, result.Length) - Assert.AreEqual ((1, "a"), result.[0]) - Assert.AreEqual ((2, "b"), result.[1]) - Assert.AreEqual ((3, "c"), result.[2]) + Assert.AreEqual (struct (1, "a"), result.[0]) + Assert.AreEqual (struct (2, "b"), result.[1]) + Assert.AreEqual (struct (3, "c"), result.[2]) [] [)>] @@ -61,9 +61,9 @@ type PairOperationTests () = [] member _.``unzip splits pairs into two lists`` () = - let flatList = FlatList.ofArray [| (1, "a"); (2, "b"); (3, "c") |] + let flatList = FlatList.ofArray [| struct (1, "a"); (2, "b"); (3, "c") |] - let list1, list2 = FlatList.unzip flatList + let struct (list1, list2) = FlatList.unzip flatList Assert.AreEqual (3, list1.Length) Assert.AreEqual (3, list2.Length) @@ -85,9 +85,9 @@ type PairOperationTests () = let result = FlatList.zip3 list1 list2 list3 Assert.AreEqual (3, result.Length) - Assert.AreEqual ((1, "a", true), result.[0]) - Assert.AreEqual ((2, "b", false), result.[1]) - Assert.AreEqual ((3, "c", true), result.[2]) + Assert.AreEqual (struct (1, "a", true), result.[0]) + Assert.AreEqual (struct (2, "b", false), result.[1]) + Assert.AreEqual (struct (3, "c", true), result.[2]) [] [)>] @@ -109,9 +109,9 @@ type PairOperationTests () = [] member _.``unzip3 splits triples into three lists`` () = - let flatList = FlatList.ofArray [| (1, "a", true); (2, "b", false); (3, "c", true) |] + let flatList = FlatList.ofArray [| struct (1, "a", true); (2, "b", false); (3, "c", true) |] - let list1, list2, list3 = FlatList.unzip3 flatList + let struct (list1, list2, list3) = FlatList.unzip3 flatList Assert.AreEqual (3, list1.Length) Assert.AreEqual (3, list2.Length) diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/SearchTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/SearchTests.fs index b537c17..04677ac 100644 --- a/tests/FSharp.Collections.Immutable.Tests/FlatList/SearchTests.fs +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/SearchTests.fs @@ -15,7 +15,7 @@ type SearchTests () = Assert.AreEqual (2, FlatList.find (fun x -> x % 2 = 0) flatList) [] - [)>] + [)>] member _.``find throws when no element satisfies predicate`` () = let flatList = FlatList.ofArray [| 1; 3; 5 |] FlatList.find (fun x -> x % 2 = 0) flatList |> ignore @@ -30,10 +30,10 @@ type SearchTests () = [] member _.``findBack returns last element matching predicate`` () = let flatList = FlatList.ofArray [| 1; 2; 3; 4; 2 |] - Assert.AreEqual (4, FlatList.findBack (fun x -> x % 2 = 0) flatList) + Assert.AreEqual (2, FlatList.findBack (fun x -> x % 2 = 0) flatList) [] - [)>] + [)>] member _.``findBack throws when no element satisfies predicate`` () = let flatList = FlatList.ofArray [| 1; 3; 5 |] FlatList.findBack (fun x -> x % 2 = 0) flatList |> ignore @@ -42,7 +42,7 @@ type SearchTests () = member _.``tryFindBack returns element or ValueNone`` () = let flatList = FlatList.ofArray [| 1; 2; 3; 4; 2 |] - Assert.AreEqual (ValueSome 4, FlatList.tryFindBack (fun x -> x % 2 = 0) flatList) + Assert.AreEqual (ValueSome 2, FlatList.tryFindBack (fun x -> x % 2 = 0) flatList) Assert.AreEqual (ValueNone, FlatList.tryFindBack (fun x -> x > 10) flatList) [] @@ -51,7 +51,7 @@ type SearchTests () = Assert.AreEqual (1, FlatList.findIndex (fun x -> x % 2 = 0) flatList) [] - [)>] + [)>] member _.``findIndex throws when no element satisfies predicate`` () = let flatList = FlatList.ofArray [| 1; 3; 5 |] FlatList.findIndex (fun x -> x % 2 = 0) flatList |> ignore @@ -66,10 +66,10 @@ type SearchTests () = [] member _.``findIndexBack returns index of last element matching predicate`` () = let flatList = FlatList.ofArray [| 1; 2; 3; 4; 2 |] - Assert.AreEqual (3, FlatList.findIndexBack (fun x -> x % 2 = 0) flatList) + Assert.AreEqual (4, FlatList.findIndexBack (fun x -> x % 2 = 0) flatList) [] - [)>] + [)>] member _.``findIndexBack throws when no element satisfies predicate`` () = let flatList = FlatList.ofArray [| 1; 3; 5 |] FlatList.findIndexBack (fun x -> x % 2 = 0) flatList @@ -79,7 +79,7 @@ type SearchTests () = member _.``tryFindIndexBack returns index or ValueNone`` () = let flatList = FlatList.ofArray [| 1; 2; 3; 4; 2 |] - Assert.AreEqual (ValueSome 3, FlatList.tryFindIndexBack (fun x -> x % 2 = 0) flatList) + Assert.AreEqual (ValueSome 4, FlatList.tryFindIndexBack (fun x -> x % 2 = 0) flatList) Assert.AreEqual (ValueNone, FlatList.tryFindIndexBack (fun x -> x > 10) flatList) [] @@ -90,7 +90,7 @@ type SearchTests () = Assert.AreEqual (30, result) [] - [)>] + [)>] member _.``pick throws when chooser returns ValueNone for all elements`` () = let flatList = FlatList.ofArray [| 1; 2; 3 |] FlatList.pick (fun x -> if x > 10 then ValueSome x else ValueNone) flatList @@ -134,5 +134,66 @@ type SearchTests () = member _.``tryFindBack returns last element matching predicate`` () = let flatList = FlatList.ofArray [| 1; 2; 3; 4; 2 |] - Assert.AreEqual (ValueSome 4, FlatList.tryFindBack (fun x -> x % 2 = 0) flatList) + Assert.AreEqual (ValueSome 2, FlatList.tryFindBack (fun x -> x % 2 = 0) flatList) Assert.AreEqual (ValueNone, FlatList.tryFindBack (fun x -> x > 10) flatList) + + [] + member _.``findLast returns last element matching predicate`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4; 2 |] + Assert.AreEqual (2, FlatList.findLast (fun x -> x % 2 = 0) flatList) + + [] + [)>] + member _.``findLast throws when no element satisfies predicate`` () = + let flatList = FlatList.ofArray [| 1; 3; 5 |] + FlatList.findLast (fun x -> x % 2 = 0) flatList |> ignore + + [] + member _.``tryFindLast returns last element matching predicate or ValueNone`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4; 2 |] + + Assert.AreEqual (ValueSome 2, FlatList.tryFindLast (fun x -> x % 2 = 0) flatList) + Assert.AreEqual (ValueNone, FlatList.tryFindLast (fun x -> x > 10) flatList) + + [] + member _.``findLastIndex returns index of last element matching predicate`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4; 2 |] + Assert.AreEqual (4, FlatList.findLastIndex (fun x -> x % 2 = 0) flatList) + + [] + [)>] + member _.``findLastIndex throws when no element satisfies predicate`` () = + let flatList = FlatList.ofArray [| 1; 3; 5 |] + FlatList.findLastIndex (fun x -> x % 2 = 0) flatList + |> ignore + + [] + member _.``tryFindLastIndex returns index of last element matching predicate or ValueNone`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4; 2 |] + + Assert.AreEqual (ValueSome 4, FlatList.tryFindLastIndex (fun x -> x % 2 = 0) flatList) + Assert.AreEqual (ValueNone, FlatList.tryFindLastIndex (fun x -> x > 10) flatList) + + [] + member _.``pickBack returns last value from chooser that returns ValueSome`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4; 3 |] + let result = FlatList.pickBack (fun x -> if x = 3 then ValueSome (x * 10) else ValueNone) flatList + + Assert.AreEqual (30, result) + + [] + [)>] + member _.``pickBack throws when chooser returns ValueNone for all elements`` () = + let flatList = FlatList.ofArray [| 1; 2; 3 |] + FlatList.pickBack (fun x -> if x > 10 then ValueSome x else ValueNone) flatList + |> ignore + + [] + member _.``tryPickBack returns last value from chooser that returns ValueSome, or ValueNone`` () = + let flatList = FlatList.ofArray [| 1; 2; 3; 4; 3 |] + + let result1 = FlatList.tryPickBack (fun x -> if x = 3 then ValueSome (x * 10) else ValueNone) flatList + Assert.AreEqual (ValueSome 30, result1) + + let result2 = FlatList.tryPickBack (fun x -> if x > 10 then ValueSome x else ValueNone) flatList + Assert.AreEqual (ValueNone, result2) diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/SortTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/SortTests.fs index 4fc1407..d843649 100644 --- a/tests/FSharp.Collections.Immutable.Tests/FlatList/SortTests.fs +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/SortTests.fs @@ -28,8 +28,8 @@ type SortTests () = let flatList = FlatList.ofArray [| "apple"; "banana"; "cherry"; "date"; "fig" |] let result = FlatList.sortBy String.length flatList - // Should be sorted by length: "fig", "date", "apple", "cherry", "banana" - CollectionAssert.AreEqual ([| "fig"; "date"; "apple"; "cherry"; "banana" |], FlatList.toArray result) + // Should be sorted by length: "fig", "date", "apple", "banana", "cherry" + CollectionAssert.AreEqual ([| "fig"; "date"; "apple"; "banana"; "cherry" |], FlatList.toArray result) [] member _.``sortByDescending orders using key selector in reverse`` () = @@ -112,7 +112,7 @@ type SortTests () = else 1 // list1 and list2 differ at position 2, where 3 and 4 have same parity - Assert.AreEqual (0, FlatList.compareWith comparer list1 list2) + Assert.AreEqual (1, FlatList.compareWith comparer list1 list2) // list1 is shorter than list3 (where all elements match) Assert.AreEqual (-1, FlatList.compareWith comparer list1 list3) From 3baa8f750aaf65013f6d11674ce2baaf4d1c9aca Mon Sep 17 00:00:00 2001 From: Andrii Chebukin Date: Sun, 29 Jun 2025 04:27:53 +0300 Subject: [PATCH 8/8] Copilot suggestion Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../FSharp.Collections.Immutable.Tests/FlatList/UtilityTests.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/FSharp.Collections.Immutable.Tests/FlatList/UtilityTests.fs b/tests/FSharp.Collections.Immutable.Tests/FlatList/UtilityTests.fs index c2345ff..5023b21 100644 --- a/tests/FSharp.Collections.Immutable.Tests/FlatList/UtilityTests.fs +++ b/tests/FSharp.Collections.Immutable.Tests/FlatList/UtilityTests.fs @@ -38,7 +38,7 @@ type UtilityTests () = CollectionAssert.AreEqual ([| 1; 2; 4; 8; 16; 32 |], FlatList.toArray result) [] - member _.``build creats list with builder function`` () = + member _.``build creates list with builder function`` () = let result = FlatList.build (fun builder -> builder.Add (1)