diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..b26cc9a --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,27 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "shell", + "args": [ + "build", + // Ask dotnet build to generate full paths for file names. + "/property:GenerateFullPaths=true", + // Do not generate summary otherwise it leads to duplicate errors in Problems panel + "/consoleloggerparameters:NoSummary" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "presentation": { + "reveal": "silent" + }, + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/FSharp.Collections.Immutable.Tests/FSharp.Collections.Immutable.Tests.fsproj b/FSharp.Collections.Immutable.Tests/FSharp.Collections.Immutable.Tests.fsproj new file mode 100644 index 0000000..3401609 --- /dev/null +++ b/FSharp.Collections.Immutable.Tests/FSharp.Collections.Immutable.Tests.fsproj @@ -0,0 +1,26 @@ + + + + net5.0 + + false + false + + + + + + + + + + + + + + + + + + + diff --git a/FSharp.Collections.Immutable.Tests/flat-list-tests.fs b/FSharp.Collections.Immutable.Tests/flat-list-tests.fs new file mode 100644 index 0000000..9951fb1 --- /dev/null +++ b/FSharp.Collections.Immutable.Tests/flat-list-tests.fs @@ -0,0 +1,156 @@ +namespace FSharp.Collections.Immutable.Tests + +open NUnit.Framework +open FSharp.Collections.Immutable + +[] +module FlatListTestsUtils = + let nullList<'a> () = new FlatList<'a>() + let emptyList<'a> () = FlatList<'a>.Empty + let list0to9 () = FlatList.init 10 id + let list9to0 () = FlatList.init 10 ((-) 9) + let listOf count list = + let builder = FlatList.builderWith count + for i = 1 to count do + builder.Add list + FlatList.ofBuilder builder + let throwsOnlyOnNullLists category func = [ + (func |> appliedToFunc nullList |> throws) |> testCase "throws on null list" category + (func |> appliedToFunc emptyList |> doesNotThrow) |> testCase "does not throw on empty list" category + (func |> appliedToFunc list0to9 |> doesNotThrow) |> testCase "does not throw on non-empty list" category + ] + let doesNotThrowOnlyOnFilledList category func = [ + (func |> appliedToFunc nullList |> throws) |> testCase "throws on null list" category + (func |> appliedToFunc emptyList |> throws) |> testCase "throws on empty list" category + (func |> appliedToFunc list0to9 |> doesNotThrow) |> testCase "does not throw on non-empty list" category + ] + +type FlatListFixture () = + static member validationCases = Seq.concat [ + FlatList.toSeq |> throwsOnlyOnNullLists (nameof(FlatList.toSeq)) + FlatList.toArray |> throwsOnlyOnNullLists (nameof(FlatList.toArray)) + FlatList.toList |> throwsOnlyOnNullLists (nameof(FlatList.toList)) + FlatList.length |> throwsOnlyOnNullLists (nameof(FlatList.length)) + (FlatList.append |> withSecondArgFrom emptyList) |> throwsOnlyOnNullLists (nameof(FlatList.append) + " (first arg)") + (FlatList.append <| emptyList ()) |> throwsOnlyOnNullLists (nameof(FlatList.append) + " (second arg)") + (FlatList.indexFromWith LanguagePrimitives.FastGenericEqualityComparer 0 1) |> throwsOnlyOnNullLists (nameof(FlatList.indexFromWith)) + (FlatList.indexFrom 0 1) |> throwsOnlyOnNullLists (nameof(FlatList.indexFrom)) + (FlatList.indexWith LanguagePrimitives.FastGenericEqualityComparer 1) |> throwsOnlyOnNullLists (nameof(FlatList.indexWith)) + (FlatList.index 1) |> throwsOnlyOnNullLists (nameof(FlatList.index)) + (FlatList.removeAllWith LanguagePrimitives.FastGenericEqualityComparer <| emptyList ()) |> throwsOnlyOnNullLists (nameof(FlatList.removeAllWith)) + (FlatList.removeAll <| emptyList ()) |> throwsOnlyOnNullLists (nameof(FlatList.removeAll)) + (FlatList.filter noopPredicate) |> throwsOnlyOnNullLists (nameof(FlatList.filter)) + (FlatList.where noopPredicate) |> throwsOnlyOnNullLists (nameof(FlatList.where)) + (FlatList.sortWithComparer LanguagePrimitives.FastGenericComparer) |> throwsOnlyOnNullLists (nameof(FlatList.sortWithComparer)) + (FlatList.sortWith LanguagePrimitives.GenericComparison) |> throwsOnlyOnNullLists (nameof(FlatList.sortWith)) + FlatList.sort |> throwsOnlyOnNullLists (nameof(FlatList.sort)) + FlatList.map noopPredicate |> throwsOnlyOnNullLists (nameof(FlatList.map)) + FlatList.countBy noopPredicate |> throwsOnlyOnNullLists (nameof(FlatList.countBy)) + FlatList.indexed |> throwsOnlyOnNullLists (nameof(FlatList.indexed)) + FlatList.iter ignore |> throwsOnlyOnNullLists (nameof(FlatList.iter)) + (FlatList.iter2 ignore2 |> withSecondArgFrom emptyList) |> throwsOnlyOnNullLists (nameof(FlatList.iter2) + " (first arg)") + (FlatList.iter2 ignore2 (emptyList ())) |> throwsOnlyOnNullLists (nameof(FlatList.iter2) + " (second arg)") + FlatList.distinct |> throwsOnlyOnNullLists (nameof(FlatList.distinct)) + FlatList.distinctBy noopPredicate |> throwsOnlyOnNullLists (nameof(FlatList.distinctBy)) + (FlatList.map2 ignore2 |> withSecondArgFrom emptyList) |> throwsOnlyOnNullLists (nameof(FlatList.map2) + " (first arg)") + (FlatList.map2 ignore2 (emptyList ())) |> throwsOnlyOnNullLists (nameof(FlatList.map2) + " (second arg)") + (fun a -> FlatList.map3 ignore3 a (emptyList ()) (emptyList ())) |> throwsOnlyOnNullLists (nameof(FlatList.map3) + " (first arg)") + (fun a -> FlatList.map3 ignore3 (emptyList ()) a (emptyList ())) |> throwsOnlyOnNullLists (nameof(FlatList.map3) + " (second arg)") + (FlatList.map3 ignore3 (emptyList ()) (emptyList ())) |> throwsOnlyOnNullLists (nameof(FlatList.map3) + " (third arg)") + (FlatList.mapi2 ignore3 |> withSecondArgFrom emptyList) |> throwsOnlyOnNullLists (nameof(FlatList.mapi2) + " (first arg)") + (FlatList.mapi2 ignore3 (emptyList ())) |> throwsOnlyOnNullLists (nameof(FlatList.mapi2) + " (second arg)") + (FlatList.iteri ignore2) |> throwsOnlyOnNullLists (nameof(FlatList.iteri)) + (FlatList.iteri2 ignore3 |> withSecondArgFrom emptyList) |> throwsOnlyOnNullLists (nameof(FlatList.iteri2) + " (first arg)") + (FlatList.iteri2 ignore3 (emptyList ())) |> throwsOnlyOnNullLists (nameof(FlatList.iteri2) + " (second arg)") + (FlatList.mapi ignore2) |> throwsOnlyOnNullLists (nameof(FlatList.mapi)) + + (FlatList.item 0) |> doesNotThrowOnlyOnFilledList (nameof(FlatList.item)) + (FlatList.indexRangeWith LanguagePrimitives.FastGenericEqualityComparer 0 1 1) |> doesNotThrowOnlyOnFilledList (nameof(FlatList.indexRangeWith)) + (FlatList.indexRange 0 1 1) |> doesNotThrowOnlyOnFilledList (nameof(FlatList.indexRange)) + (FlatList.lastIndexRangeWith LanguagePrimitives.FastGenericEqualityComparer 0 1 1) |> doesNotThrowOnlyOnFilledList (nameof(FlatList.lastIndexRangeWith)) + (FlatList.lastIndexRange 0 1 1) |> doesNotThrowOnlyOnFilledList (nameof(FlatList.lastIndexRange)) + (FlatList.lastIndexFromWith LanguagePrimitives.FastGenericEqualityComparer 0 1) |> doesNotThrowOnlyOnFilledList (nameof(FlatList.lastIndexFromWith)) + (FlatList.lastIndexFrom 0 1) |> doesNotThrowOnlyOnFilledList (nameof(FlatList.lastIndexFrom)) + (FlatList.lastIndexWith LanguagePrimitives.FastGenericEqualityComparer 1) |> doesNotThrowOnlyOnFilledList (nameof(FlatList.lastIndexWith)) + (FlatList.lastIndex 1) |> doesNotThrowOnlyOnFilledList (nameof(FlatList.lastIndex)) + (FlatList.removeRange 0 1) |> doesNotThrowOnlyOnFilledList (nameof(FlatList.removeRange)) + (fun a -> FlatList.blit a 0 [|10;11;12|] 0 1) |> doesNotThrowOnlyOnFilledList (nameof(FlatList.blit)) + (FlatList.sortRangeWithComparer LanguagePrimitives.FastGenericComparer 0 1) |> doesNotThrowOnlyOnFilledList (nameof(FlatList.sortRangeWithComparer)) + (FlatList.sortRangeWith LanguagePrimitives.GenericComparison 0 1) |> doesNotThrowOnlyOnFilledList (nameof(FlatList.sortRangeWith)) + (FlatList.sortRange 0 1) |> doesNotThrowOnlyOnFilledList (nameof(FlatList.sortRange)) + ] + + static member operationCases = [ + (FlatList.init 5 |> appliedTo (id) |> producesEquivalentOf [0..4]) |> testCase "for 5 elements yield valid list" (nameof(FlatList.init)) + (FlatList.init 0 |> appliedTo (id) |> producesEquivalentOf []) |> testCase "for 0 elements yields empty list" (nameof(FlatList.init)) + (FlatList.isEmpty |> appliedToFunc emptyList |> produces true) |> testCase "empty list is" (nameof(FlatList.isEmpty)) + (FlatList.isEmpty |> appliedToFunc list0to9 |> produces false) |> testCase "non-empty list is not" (nameof(FlatList.isEmpty)) + (FlatList.isDefault |> appliedToFunc nullList |> produces true) |> testCase "null list is" (nameof(FlatList.isDefault)) + (FlatList.isDefault |> appliedToFunc emptyList |> produces false) |> testCase "empty list is not" (nameof(FlatList.isDefault)) + (FlatList.isDefault |> appliedToFunc list0to9 |> produces false) |> testCase "non-empty list is not" (nameof(FlatList.isDefault)) + (FlatList.isDefaultOrEmpty |> appliedToFunc nullList |> produces true) |> testCase "null list is" (nameof(FlatList.isDefaultOrEmpty)) + (FlatList.isDefaultOrEmpty |> appliedToFunc emptyList |> produces true) |> testCase "empty list is" (nameof(FlatList.isDefaultOrEmpty)) + (FlatList.isDefaultOrEmpty |> appliedToFunc list0to9 |> produces false) |> testCase "non-empty list is not" (nameof(FlatList.isDefaultOrEmpty)) + (FlatList.length |> appliedToFunc emptyList |> produces 0) |> testCase "for empty list is 0" (nameof(FlatList.length)) + (FlatList.length |> appliedToFunc list0to9 |> produces 10) |> testCase "for non-empty list is .length" (nameof(FlatList.length)) + (FlatList.item 0 |> appliedToFunc emptyList |> throws) |> testCase "throws for empty list" (nameof(FlatList.item)) + (FlatList.item 0 |> appliedToFunc list0to9 |> produces 0) |> testCase "[0] for non-empty list equals to [0]" (nameof(FlatList.item)) + (FlatList.item 5 |> appliedToFunc list0to9 |> produces 5) |> testCase "[5] for non-empty list equals to [5]" (nameof(FlatList.item)) + (FlatList.item -1 |> appliedToFunc list0to9 |> throws) |> testCase "[-1] for non-empty list throws" (nameof(FlatList.item)) + (FlatList.item 25 |> appliedToFunc list0to9 |> throws) |> testCase "[out of bounds] for non-empty list throws" (nameof(FlatList.item)) + (FlatList.append (emptyList ()) |> appliedToFunc list0to9 |> producesEquivalentOf [0..9]) |> testCase "empty to non-empty" (nameof(FlatList.append)) + (FlatList.append (list0to9 ()) |> appliedToFunc emptyList |> producesEquivalentOf [0..9]) |> testCase "non-empty to empty" (nameof(FlatList.append)) + (FlatList.append (list0to9 ()) |> appliedToFunc list0to9 |> producesEquivalentOf (List.append [0..9] [0..9])) |> testCase "non-empty to non-empty" (nameof(FlatList.append)) + (FlatList.indexRangeWith HashIdentity.Structural 3 5 6 |> appliedToFunc list0to9 |> produces 6) |> testCase "returns index for valid args" (nameof(FlatList.indexRangeWith)) + (FlatList.indexRangeWith HashIdentity.Structural 3 15 6 |> appliedToFunc list0to9 |> throws) |> testCase "throws for invalid args (count)" (nameof(FlatList.indexRangeWith)) + (FlatList.indexRangeWith HashIdentity.Structural -2 5 6 |> appliedToFunc list0to9 |> throws) |> testCase "throws for invalid args (index)" (nameof(FlatList.indexRangeWith)) + (FlatList.indexRangeWith HashIdentity.Structural 3 5 0 |> appliedToFunc list0to9 |> produces -1) |> testCase "returns -1 for non-present item" (nameof(FlatList.indexRangeWith)) + (FlatList.removeAll ([] |> FlatList.ofList) |> appliedToFunc list0to9 |> producesEquivalentOf [0..9]) |> testCase "with empty list - returns source" (nameof(FlatList.removeAll)) + (FlatList.removeAll ([] |> FlatList.ofList) |> appliedToFunc emptyList |> producesEquivalentOf []) |> testCase "with empty list - returns source (even if empty)" (nameof(FlatList.removeAll)) + (FlatList.removeAll (list0to9 ()) |> appliedToFunc emptyList |> producesEquivalentOf []) |> testCase "takes no action for not present values" (nameof(FlatList.removeAll)) + (FlatList.removeAll (list0to9 ()) |> appliedToFunc list0to9 |> producesEquivalentOf []) |> testCase "removes all present items" (nameof(FlatList.removeAll)) + (FlatList.filter ((>) 4) |> appliedToFunc list0to9 |> producesEquivalentOf [0..3]) |> testCase "yields only valid items" (nameof(FlatList.filter)) + (FlatList.filter ((<) 100) |> appliedToFunc list0to9 |> producesEquivalentOf []) |> testCase "returns empty list if predicate matches no items" (nameof(FlatList.filter)) + (FlatList.removeRange 0 0 |> appliedToFunc emptyList |> producesEquivalentOf []) |> testCase "empty list - 0 items from 0" (nameof(FlatList.removeRange)) + (FlatList.removeRange 0 -1 |> appliedToFunc emptyList |> throws) |> testCase "empty list - -1 items from 0" (nameof(FlatList.removeRange)) + (FlatList.removeRange -1 0 |> appliedToFunc emptyList |> throws) |> testCase "empty list - 0 items from -1" (nameof(FlatList.removeRange)) + (FlatList.removeRange 0 3 |> appliedToFunc emptyList |> throws) |> testCase "empty list - 3 items from 0" (nameof(FlatList.removeRange)) + (FlatList.removeRange 0 3 |> appliedToFunc list0to9 |> producesEquivalentOf [3..9]) |> testCase "non-empty list - 3 items from 0" (nameof(FlatList.removeRange)) + (FlatList.sortRangeWith LanguagePrimitives.GenericComparison 0 3 |> appliedToFunc emptyList |> throws) |> testCase "empty list - 3 items from 0" (nameof(FlatList.sortRangeWith)) + (FlatList.sortRangeWith LanguagePrimitives.GenericComparison 0 -1 |> appliedToFunc emptyList |> throws) |> testCase "empty list - -1 items from 0" (nameof(FlatList.sortRangeWith)) + (FlatList.sortRangeWith LanguagePrimitives.GenericComparison -1 0 |> appliedToFunc emptyList |> throws) |> testCase "empty list - 0 items from -1" (nameof(FlatList.sortRangeWith)) + (FlatList.sortRangeWith LanguagePrimitives.GenericComparison 0 0 |> appliedToFunc emptyList |> producesEquivalentOf []) |> testCase "empty list - 3 items from 0" (nameof(FlatList.sortRangeWith)) + (FlatList.sortRangeWith LanguagePrimitives.GenericComparison 0 3 |> appliedToFunc list9to0 |> producesSameAs [7;8;9;6;5;4;3;2;1;0]) |> testCase "9 to 0 list - 3 items from 0" (nameof(FlatList.sortRangeWith)) + (FlatList.sortRangeWith (fun x y -> y - x) 0 3 |> appliedToFunc list0to9 |> producesSameAs [2;1;0;3;4;5;6;7;8;9]) |> testCase "0 to 9 list - 3 items from 0" (nameof(FlatList.sortRangeWith)) + (FlatList.initWithValue -1 |> appliedTo 1 |> throws) |> testCase "-1 items" (nameof(FlatList.initWithValue)) + (FlatList.initWithValue 0 |> appliedTo 1 |> producesEquivalentOf []) |> testCase "0 items" (nameof(FlatList.initWithValue)) + (FlatList.initWithValue 3 |> appliedTo 1 |> producesEquivalentOf [1;1;1]) |> testCase "3 items" (nameof(FlatList.initWithValue)) + (FlatList.concat |> appliedToFunc emptyList |> producesEquivalentOf []) |> testCase "no items" (nameof(FlatList.concat)) + (FlatList.concat |> appliedTo (listOf 4 <| emptyList ()) |> producesEquivalentOf []) |> testCase "no items 4 times" (nameof(FlatList.concat)) + (FlatList.concat |> appliedTo ([emptyList ();list0to9 ()] |> FlatList.ofList) |> producesEquivalentOf [0..9]) |> testCase "no items & 0 to 9" (nameof(FlatList.concat)) + (FlatList.concat |> appliedTo ([list0to9 ();list0to9 ()] |> FlatList.ofList) |> producesEquivalentOf [0;1;2;3;4;5;6;7;8;9;0;1;2;3;4;5;6;7;8;9]) |> testCase "0 to 9 times 2" (nameof(FlatList.concat)) + (FlatList.map ((+) 3) |> appliedToFunc emptyList |> producesEquivalentOf []) |> testCase "empty list" (nameof(FlatList.map)) + (FlatList.map ((+) 3) |> appliedToFunc list0to9 |> producesEquivalentOf [3..12]) |> testCase "non-empty list" (nameof(FlatList.map)) + (FlatList.countBy (fun x -> x % 2 = 0) |> appliedToFunc list0to9 |> producesEquivalentOf [true,5;false,5]) |> testCase "non-empty list" (nameof(FlatList.countBy)) + (FlatList.countBy (fun x -> x % 2 = 0) |> appliedToFunc emptyList |> producesEquivalentOf []) |> testCase "empty list" (nameof(FlatList.countBy)) + (FlatList.distinct |> appliedToFunc emptyList |> producesEquivalentOf []) |> testCase "empty list" (nameof(FlatList.distinct)) + (FlatList.distinct |> appliedTo ([1;1;1;1] |> FlatList.ofList) |> producesEquivalentOf [1]) |> testCase "duplicates list" (nameof(FlatList.distinct)) + (FlatList.distinctBy (fun x -> x % 2 = 0) |> appliedToFunc emptyList |> producesEquivalentOf []) |> testCase "empty list" (nameof(FlatList.distinctBy)) + (FlatList.distinctBy (fun x -> x % 2 = 0) |> appliedToFunc list0to9 |> producesEquivalentOf [0;1]) |> testCase "0 to 9 list, oddity check" (nameof(FlatList.distinctBy)) + (FlatList.map2 (+) |> withSecondArgFrom emptyList |> appliedToFunc emptyList |> producesEquivalentOf []) |> testCase "empty list - both" (nameof(FlatList.map2)) + (FlatList.map2 (+) |> withSecondArgFrom emptyList |> appliedToFunc list0to9 |> producesEquivalentOf []) |> testCase "empty list - 1st" (nameof(FlatList.map2)) + (FlatList.map2 (+) |> withSecondArgFrom list0to9 |> appliedToFunc emptyList |> producesEquivalentOf []) |> testCase "empty list - 2nd" (nameof(FlatList.map2)) + (FlatList.map2 (+) |> withSecondArgFrom list0to9 |> appliedToFunc list9to0 |> producesEquivalentOf (listOf 10 9)) |> testCase "non-empty" (nameof(FlatList.map2)) + (FlatList.exists ((>) 5) |> appliedToFunc emptyList |> produces false) |> testCase "empty list" (nameof(FlatList.exists)) + (FlatList.exists ((>) 5) |> appliedToFunc list0to9 |> produces true) |> testCase "non-empty list" (nameof(FlatList.exists)) + (FlatList.exists ((>) -1) |> appliedToFunc list0to9 |> produces false) |> testCase "non-empty list, invalid condition" (nameof(FlatList.exists)) + (FlatList.contains 5 |> appliedToFunc emptyList |> produces false) |> testCase "empty list" (nameof(FlatList.contains)) + (FlatList.contains 5 |> appliedToFunc list0to9 |> produces true) |> testCase "non-empty list" (nameof(FlatList.contains)) + (FlatList.contains -1 |> appliedToFunc list0to9 |> produces false) |> testCase "non-empty list, invalid element" (nameof(FlatList.contains)) + ] + + [] + member this.testParameterValidation (code:unit->unit) = code () + + [] + member this.testOperationResult (code:unit->unit) = code () diff --git a/FSharp.Collections.Immutable.Tests/test-helpers.fs b/FSharp.Collections.Immutable.Tests/test-helpers.fs new file mode 100644 index 0000000..f0a6754 --- /dev/null +++ b/FSharp.Collections.Immutable.Tests/test-helpers.fs @@ -0,0 +1,20 @@ +namespace FSharp.Collections.Immutable.Tests + +open FsUnit +open NUnit.Framework + +[] +module TestHelpers = + let appliedTo value func = fun () -> func value + let appliedToFunc value func = fun () -> func (value ()) + let produces value func = fun () -> () |> func |> should equal value + let producesEquivalentOf value func = fun () -> () |> func |> should equivalent value + let producesSameAs value func = fun () -> () |> func |> Seq.fold2 (fun acc x y -> acc && (x = y)) true value |> should equal true + let throws func = fun () -> Assert.Catch (func >> ignore) |> ignore + let doesNotThrow func = fun () -> func () |> ignore |> should equal () + let noopPredicate _ = true + let ignore2 _ _ = () + let ignore3 _ _ _ = () + let testCase name category (code:unit->unit) = ((TestCaseData code).SetName (category + " - " + name)).SetCategory category + let withSecondArg a f = fun b -> f b a + let withSecondArgFrom a f = fun b -> f b (a ()) diff --git a/FSharp.Collections.Immutable.sln b/FSharp.Collections.Immutable.sln index a9324bb..f053b66 100644 --- a/FSharp.Collections.Immutable.sln +++ b/FSharp.Collections.Immutable.sln @@ -11,6 +11,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .github\workflows\publish_release.yml = .github\workflows\publish_release.yml EndProjectSection EndProject +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharp.Collections.Immutable.Tests", "FSharp.Collections.Immutable.Tests\FSharp.Collections.Immutable.Tests.fsproj", "{2272CACE-DBCE-4BC8-BADD-11802BAF30EC}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -21,6 +23,10 @@ Global {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 + {2272CACE-DBCE-4BC8-BADD-11802BAF30EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2272CACE-DBCE-4BC8-BADD-11802BAF30EC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2272CACE-DBCE-4BC8-BADD-11802BAF30EC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2272CACE-DBCE-4BC8-BADD-11802BAF30EC}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj b/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj index 2b3fbfb..a9177f1 100644 --- a/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj +++ b/src/FSharp.Collections.Immutable/FSharp.Collections.Immutable.fsproj @@ -1,45 +1,41 @@ - - - - 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 - - - - - - - - - - - - - - - - true - - - - - - - - + + + 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 + + + + + + + + + + + + + + + true + + + + + + \ No newline at end of file diff --git a/src/FSharp.Collections.Immutable/flat-list.fs b/src/FSharp.Collections.Immutable/flat-list.fs index 5f86c40..6739494 100644 --- a/src/FSharp.Collections.Immutable/flat-list.fs +++ b/src/FSharp.Collections.Immutable/flat-list.fs @@ -2,6 +2,9 @@ namespace global #else namespace FSharp.Collections.Immutable + +open FSharp.Collections.Immutable + #endif // The FlatList name comes from a similar construct seen in the official F# source code @@ -16,49 +19,75 @@ module FlatList = let inline internal checkNotDefault argName (list : FlatList<'T>) = if list.IsDefault then invalidArg argName "Uninstantiated ImmutableArray/FlatList" let inline internal check (list : FlatList<'T>) = checkNotDefault (nameof list) list + let inline internal checkEmpty (list : FlatList<_>) = check list; if list.Length = 0 then invalidArg (nameof list) "Source is empty" else () + let inline internal raiseOrReturn list = check list; list ////////// Creating ////////// + [] let inline empty<'T> : FlatList<_> = FlatListFactory.Create<'T>() + [] let inline singleton<'T> (item : 'T) : FlatList<'T> = FlatListFactory.Create<'T> (item) + [] + let copy (list:FlatList<_>) = FlatListFactory.CreateRange list + [] let inline ofSeq source = FlatListFactory.CreateRange source + [] let inline ofArray (source : _ array) = FlatListFactory.CreateRange source + [] + let inline ofList (source: _ list) = FlatListFactory.CreateRange source - let inline toSeq (flatList: FlatList<_>) = flatList :> seq<_> + [] + let inline toSeq (flatList: FlatList<_>) = check flatList; flatList :> seq<_> + [] let inline toArray (list : FlatList<_>) = check list; Seq.toArray list + [] + let inline toList list = check list; Seq.toList list ////////// Building ////////// + [] let moveFromBuilder (builder : FlatList<_>.Builder) : FlatList<_> = checkNotNull (nameof builder) builder builder.MoveToImmutable() + [] let ofBuilder (builder : FlatList<_>.Builder) : FlatList<_> = checkNotNull (nameof builder) builder builder.ToImmutable() + [] 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() module Builder = let inline private check (builder: FlatList<'T>.Builder) = checkNotNull (nameof builder) builder + [] let add item builder = check builder; builder.Add(item) 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 ////////// IReadOnly* ////////// + [] let length list = check list; list.Length + [] let item index list = check list; list.[index] + [] let append list1 list2 : FlatList<'T> = checkNotDefault (nameof list1) list1 checkNotDefault (nameof list2) list2 @@ -67,74 +96,99 @@ module 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 + [] 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. + [] 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 + [] 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<_> = check list 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<_> = check list 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 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 sortRangeWithComparer comparer index count list = check list 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 sortWith comparer list = sortWithComparer (ComparisonIdentity.FromFunction comparer) list + [] 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 let builder = builderWith count @@ -142,230 +196,115 @@ module FlatList = builder.Add <| initializer i moveFromBuilder builder + [] + let initWithValue count value = + if count < 0 then invalidArg (nameof count) ErrorStrings.InputMustBeNonNegative + let builder = builderWith count + for i = 0 to count - 1 do + builder.Add value + ofBuilder 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 - 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 concat (seqs:FlatList>) = + let builder = builderWith <| concatAddLengths seqs 0 0 + for seq in seqs do + builder.AddRange seq + ofBuilder builder - let inline map mapping list = - check list - let builder = builderWithLengthOf list - for i = 0 to length list - 1 do - builder.Add(mapping list.[i]) - moveFromBuilder builder + [] + let map mapping = raiseOrReturn >> Seq.map mapping >> ofSeq - 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<_> - 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 indexed list = - check list - let builder = builderWithLengthOf list - for i = 0 to length list - 1 do - builder.Add(i, list.[i]) - moveFromBuilder builder + [] + let countBy projection = raiseOrReturn >> Seq.countBy projection >> ofSeq - let inline iter action list = - check list - for i = 0 to length list - 1 do - action list.[i] + [] + let indexed list = list |> raiseOrReturn |> Seq.indexed |> ofSeq + [] + let iter action = raiseOrReturn >> Seq.iter action + + [] 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 invalidArg (nameof list2) ErrorStrings.ListsHaveDifferentLengths - 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 + Seq.iter2 action list1 list2 - ofBuilder builder + [] + let distinct (list: FlatList<'T>) = list |> Seq.distinct |> ofSeq + [] + let distinctBy projection = raiseOrReturn >> Seq.distinctBy projection >> ofSeq + + [] 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 + Seq.map2 mapping list1 list2 |> ofSeq + [] 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 + Seq.map3 mapping list1 list2 list3 |> ofSeq + + + [] 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]) + Seq.mapi2 mapping list1 list2 |> ofSeq + + [] + let iteri action = raiseOrReturn >> Seq.iteri action + [] let iteri2 action list1 list2 = checkNotDefault (nameof list1) list1 checkNotDefault (nameof list2) list2 - let f = OptimizedClosures.FSharpFunc<_,_,_,_>.Adapt(action) - 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]) + Seq.iteri2 action list1 list2 - 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 - - 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 mapi mapping = raiseOrReturn >> Seq.mapi mapping >> ofSeq - 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 exists predicate = raiseOrReturn >> Seq.exists predicate + [] + let contains e = raiseOrReturn >> Seq.contains e + + [] let exists2 predicate list1 list2 = checkNotDefault (nameof list1) list1 checkNotDefault (nameof list2) list2 - 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)) - loop 0 + Seq.exists2 predicate list1 list2 - 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 = raiseOrReturn >> Seq.forall predicate + [] let forall2 predicate list1 list2 = checkNotDefault (nameof list1) list1 checkNotDefault (nameof list2) list2 - 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)) - loop 0 + Seq.forall2 predicate list1 list2 - 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 list-of-lists. - 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 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 + [] + let groupBy projection = raiseOrReturn >> Seq.groupBy projection >> Seq.map (fun (k, i) -> k, ofSeq i) >> ofSeq + + [] + let pick chooser = raiseOrReturn >> Seq.pick chooser + [] + let tryPick chooser = raiseOrReturn >> Seq.tryPick chooser + + [] + let choose chooser = raiseOrReturn >> Seq.choose chooser >> ofSeq + + [] let partition predicate list = check list let res1 = builderWith list.Length @@ -375,48 +314,126 @@ module FlatList = 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) - loop 0 - let tryFind predicate list = + let rec private tryFindWithCustomStride (list:FlatList<_>) index predicate indexPredicate indexTransform = + if indexPredicate index then + if predicate list.[index] then + Some (index, list.[index]) + else tryFindWithCustomStride list (indexTransform index) predicate indexPredicate indexTransform + else None + + [] + let tryFindItem predicate direction 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 - let findBack predicate list = + let startIndex = if direction then 0 else length list - 1 + let indexPredicate = if direction then ((>) (length list)) else ((<=) 0) + let transform = if direction then ((+) 1) else ((+) -1) // because section is not available + tryFindWithCustomStride list startIndex predicate indexPredicate transform + + [] + let find predicate = raiseOrReturn >> Seq.find predicate + [] + let tryFind predicate = raiseOrReturn >> Seq.tryFind predicate + [] + let findBack predicate = raiseOrReturn >> Seq.findBack predicate + [] + let tryFindBack predicate = raiseOrReturn >> Seq.tryFindBack predicate + [] + let findIndex predicate = raiseOrReturn >> Seq.findIndex predicate + [] + let findIndexBack predicate = raiseOrReturn >> Seq.findIndexBack predicate + [] + let tryFindIndex predicate = raiseOrReturn >> Seq.tryFindIndex predicate + [] + let tryFindIndexBack predicate = raiseOrReturn >> Seq.tryFindIndexBack predicate + + [] + let fold folder (state: 'state) = raiseOrReturn >> Seq.fold folder state + + [] + let scan folder (state: 'state) = raiseOrReturn >> Seq.scan folder state >> ofSeq + + [] + let fold2 folder (state: 'state) (left:FlatList<'a>) (right:FlatList<'b>) = + check left; check right + Seq.fold2 folder state left right + + [] + let foldBack2 folder (left:FlatList<'a>) (right:FlatList<'b>) (state:'state) = + check left; check right + Seq.foldBack2 folder left right state + + [] + let foldBack folder (list:FlatList<'a>) (state: 'state) = check list - let rec loop i = - 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 = + Seq.foldBack folder list state + + [] + let scanBack folder (list:FlatList<'a>) (state:'state) = 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 + Seq.scanBack folder list state |> ofSeq - let findIndexBack predicate list = + [] + let unfold (generator: 'state -> ('a * 'state) option) state = + Seq.unfold generator state |> ofSeq + + [] + let reduce reduction = raiseOrReturn >> Seq.reduce reduction + + [] + let reduceBack reduction = raiseOrReturn >> Seq.reduceBack reduction + + [] + let mapFold mapping (state:'State) (list:FlatList<'T>) = 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 + let (items, s) = Seq.mapFold mapping state list + ofSeq items, s - let tryFindIndexBack predicate list = + [] + let mapFoldBack mapping (list:FlatList<'T>) (state:'State) = 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 - // TODO: windowed + let (i, s) = Seq.mapFoldBack mapping list state + ofSeq i, s + + [] + let zip (left:FlatList<_>) (right:FlatList<_>) = + check left; check right + Seq.zip left right |> ofSeq + + [] + let zip3 (left:FlatList<_>) (middle:FlatList<_>) (right:FlatList<_>) = + check left; check middle; check right + Seq.zip3 left middle right |> ofSeq + + [] + let unzip list = + let left = builderWithLengthOf list + let right = builderWithLengthOf list + for item in list do + left.Add <| fst item + right.Add <| snd item + ofBuilder left, ofBuilder right + + [] + let unzip3 list = + let left = builderWithLengthOf list + let right = builderWithLengthOf list + let middle = builderWithLengthOf list + for item in list do + left.Add <| fst3 item + middle.Add <| snd3 item + right.Add <| thd3 item + ofBuilder left, ofBuilder middle, ofBuilder right + + [] + let windowed windowSize = raiseOrReturn >> Seq.windowed windowSize >> Seq.map ofSeq >> ofSeq + + [] + let fill target targetIndex count value = + mapi (fun i a -> if targetIndex <= i && i < targetIndex + count then value else a) target ////////// Based on other operations ////////// + [] let take count list = removeRange count (length list - count) list let inline private lengthWhile predicate list = @@ -425,50 +442,133 @@ module FlatList = 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 + [] let skipWhile predicate list = skip (lengthWhile predicate list) list + [] let sub start stop list = skip start list |> take (stop - start - 1) + [] let truncate count list = if count < length list then take count list else list + [] let splitAt index list = take index list, skip index list + [] let head list = item 0 list + [] let tryItem index list = if index >= length list || index < 0 then None else Some(list.[index]) + [] let tryHead list = tryItem 0 list + [] let last (list : FlatList<_>) = list.[length list - 1] + [] let tryLast list = tryItem (length list - 1) list - let tail list = removeRange 1 (length list - 1) list + [] + let tail list = skip 1 list + [] let tryTail list = if isEmpty list then None else Some <| tail list - let create count item = init count <| fun _ -> item // optimize + [] + let create = initWithValue - let replicate count item = create item count + [] + let replicate item = item |> flip initWithValue + [] let collect mapping list = concat <| map mapping list + [] let inline build f = let builder = builder() f builder moveFromBuilder builder + [] let inline update f list = let builder = toBuilder list f builder moveFromBuilder builder + [] + let inline sum ( list:FlatList< ^T > when ^T : (static member (+) : ^T * ^T -> ^T) and ^T : (static member Zero : ^T) ) = + list |> raiseOrReturn |> reduce (+) + + [] + let inline sumBy projection ( list:FlatList< ^T > when ^T : (static member (+) : ^T * ^T -> ^T) and ^T : (static member Zero : ^T) ) = + list |> raiseOrReturn |> map projection |> reduce (+) + + [] + let inline average ( list:FlatList< ^T > when ^T : (static member (+) : ^T * ^T -> ^T) and ^T : (static member DivideByInt : ^T*int -> ^T) and ^T : (static member Zero : ^T) ) = + list |> raiseOrReturn |> applyOverFuncs LanguagePrimitives.DivideByInt sum length + + [] + let inline averageBy projection ( list:FlatList< ^T > when ^T : (static member (+) : ^T * ^T -> ^T) and ^T : (static member DivideByInt : ^T*int -> ^T) and ^T : (static member Zero : ^T) ) = + list |> raiseOrReturn |> applyOverFuncs LanguagePrimitives.DivideByInt ((map projection) >> sum) length + + let private minMaxReduction projection comparison a b = + let pa = projection a + let pb = projection b + if comparison pa pb then a else b + + [] + let maxBy projection (list:FlatList<'a>) = list |> raiseOrReturn |> reduce (minMaxReduction projection (>)) + + [] + let minBy projection (list:FlatList<'a> when 'a : comparison) = list |> raiseOrReturn |> reduce (minMaxReduction projection (<)) + + [] + let max (list:FlatList<'a> when 'a : comparison) = list |> raiseOrReturn |> reduce max + [] + let min (list:FlatList<'a> when 'a : comparison) = list |> raiseOrReturn |> reduce min + + [] + let sortBy projection = sortWith (applyOverArgs LanguagePrimitives.GenericComparison projection) + [] + let sortDescending (list:FlatList<'a>) = sortWith (flip LanguagePrimitives.GenericComparison) list + [] + let sortByDescending projection = sortWith (flip (applyOverArgs LanguagePrimitives.GenericComparison projection)) + + [] + let compareWith comparer (left:FlatList<'a>) (right:FlatList<'b>) = zip left right |> Seq.skipWhile ((uncurry comparer) >> ((=) 0)) |> Seq.head |> (uncurry comparer) + + [] + let tryExactlyOne (list:FlatList<_>) = Seq.tryExactlyOne list + [] + let exactlyOne (list:FlatList<_>) = Seq.exactlyOne list + + [] + let rev (list:FlatList<_>) = list |> raiseOrReturn |> Seq.rev |> ofSeq + [] + let transpose (list:FlatList<_>) = list |> raiseOrReturn |> Seq.transpose |> Seq.map ofSeq |> ofSeq + [] + let permute indexMap (list:FlatList<_>) = list |> raiseOrReturn |> Seq.permute indexMap |> ofSeq + [] + let pairwise (list:FlatList<_>) = list |> raiseOrReturn |> Seq.pairwise |> ofSeq + [] + let except itemsToExclude (list:FlatList<_>) = list |> raiseOrReturn |> Seq.except itemsToExclude |> ofSeq + [] + let splitInto count (list:FlatList<_>) = list |> raiseOrReturn |> Seq.splitInto count |> Seq.map ofSeq |> ofSeq + [] + let chunkBySize chunkSize (list:FlatList<_>) = list |> raiseOrReturn |> Seq.chunkBySize chunkSize |> Seq.map ofSeq |> ofSeq + [] + let allPairs (left:FlatList<'a>) (right:FlatList<'b>) = Seq.allPairs (raiseOrReturn left) (raiseOrReturn right) |> ofSeq + ////////// module ImmutableArray = FlatList diff --git a/src/FSharp.Collections.Immutable/functional-utils.fs b/src/FSharp.Collections.Immutable/functional-utils.fs new file mode 100644 index 0000000..e05821b --- /dev/null +++ b/src/FSharp.Collections.Immutable/functional-utils.fs @@ -0,0 +1,17 @@ +#if INTERACTIVE +namespace global +#else +namespace FSharp.Collections.Immutable +#endif + +[] +module FunctionalUtils = + let inline flip f a b = f b a + let inline uncurry f (a, b) = f a b + + let inline applyOverFuncs f g h x = f (g x) (h x) + let inline applyOverArgs f g x y = f (g x) (g y) + + let inline fst3 (a, _, _) = a + let inline snd3 (_, a, _) = a + let inline thd3 (_, _, a) = a