diff --git a/src/FSharp.Core/array.fs b/src/FSharp.Core/array.fs index c52f3864225..3d4ffa4474f 100644 --- a/src/FSharp.Core/array.fs +++ b/src/FSharp.Core/array.fs @@ -2296,3 +2296,252 @@ module Array = iFalse <- iFalse + 1 res1, res2 + + let private createPartitions (array: 'T[]) = + createPartitionsUpTo array.Length array + + let inline pickPivot + ([] cmpAtIndex: int -> int -> int) + ([] swapAtIndex: int -> int -> unit) + (orig: ArraySegment<'T>) + = + let inline swapIfGreater (i: int) (j: int) = + if cmpAtIndex i j > 0 then + swapAtIndex i j + + // Set pivot to be a median of {first,mid,last} + + let firstIdx = orig.Offset + let lastIDx = orig.Offset + orig.Count - 1 + let midIdx = orig.Offset + orig.Count / 2 + + swapIfGreater firstIdx midIdx + swapIfGreater firstIdx lastIDx + swapIfGreater midIdx lastIDx + midIdx + + let inline partitionIntoTwo + ([] cmpWithPivot: int -> int) + ([] swapAtIndex: int -> int -> unit) + (orig: ArraySegment<'T>) + = + let mutable leftIdx = orig.Offset + 1 // Leftmost is already < pivot + let mutable rightIdx = orig.Offset + orig.Count - 2 // Rightmost is already > pivot + + while leftIdx < rightIdx do + while cmpWithPivot leftIdx < 0 do + leftIdx <- leftIdx + 1 + + while cmpWithPivot rightIdx > 0 do + rightIdx <- rightIdx - 1 + + if leftIdx < rightIdx then + swapAtIndex leftIdx rightIdx + leftIdx <- leftIdx + 1 + rightIdx <- rightIdx - 1 + + let lastIdx = orig.Offset + orig.Count - 1 + // There might be more elements being (=)pivot. Exclude them from further work + while cmpWithPivot leftIdx >= 0 && leftIdx > orig.Offset do + leftIdx <- leftIdx - 1 + + while cmpWithPivot rightIdx <= 0 && rightIdx < lastIdx do + rightIdx <- rightIdx + 1 + + new ArraySegment<_>(orig.Array, offset = orig.Offset, count = leftIdx - orig.Offset + 1), + new ArraySegment<_>(orig.Array, offset = rightIdx, count = lastIdx - rightIdx + 1) + + let partitionIntoTwoUsingComparer + (cmp: 'T -> 'T -> int) + (orig: ArraySegment<'T>) + : ArraySegment<'T> * ArraySegment<'T> = + let array = orig.Array + + let inline swap i j = + let tmp = array[i] + array[i] <- array[j] + array[j] <- tmp + + let pivotIdx = + pickPivot (fun i j -> cmp array[i] array[j]) (fun i j -> swap i j) orig + + let pivotItem = array[pivotIdx] + partitionIntoTwo (fun idx -> cmp array[idx] pivotItem) (fun i j -> swap i j) orig + + let partitionIntoTwoUsingKeys (keys: 'A[]) (orig: ArraySegment<'T>) : ArraySegment<'T> * ArraySegment<'T> = + let array = orig.Array + + let inline swap i j = + let tmpKey = keys[i] + keys[i] <- keys[j] + keys[j] <- tmpKey + + let tmp = array.[i] + array.[i] <- array.[j] + array.[j] <- tmp + + let pivotIdx = + pickPivot (fun i j -> compare keys[i] keys[j]) (fun i j -> swap i j) orig + + let pivotKey = keys[pivotIdx] + partitionIntoTwo (fun idx -> compare keys[idx] pivotKey) (fun i j -> swap i j) orig + + let inline sortInPlaceHelper + (array: 'T[]) + ([] partitioningFunc: ArraySegment<'T> -> ArraySegment<'T> * ArraySegment<'T>) + ([] sortingFunc: ArraySegment<'T> -> unit) + = + let rec sortChunk (segment: ArraySegment<_>) freeWorkers = + match freeWorkers with + // Really small arrays are not worth creating a Task for, sort them immediately as well + | 0 + | 1 -> sortingFunc segment + | _ when segment.Count <= minChunkSize -> sortingFunc segment + | _ -> + let left, right = partitioningFunc segment + // If either of the two is too small, sort small segments straight away. + // If the other happens to be big, leave it with all workes in it's recursive step + if left.Count <= minChunkSize || right.Count <= minChunkSize then + sortChunk left freeWorkers + sortChunk right freeWorkers + else + // Pivot-based partitions might be inbalanced. Split free workers for left/right proportional to their size + let itemsPerWorker = Operators.max ((left.Count + right.Count) / freeWorkers) 1 + + let workersForLeftTask = + (left.Count / itemsPerWorker) + |> Operators.max 1 + |> Operators.min (freeWorkers - 1) + + let leftTask = Task.Run(fun () -> sortChunk left workersForLeftTask) + sortChunk right (freeWorkers - workersForLeftTask) + leftTask.Wait() + + let bigSegment = new ArraySegment<_>(array, 0, array.Length) + sortChunk bigSegment maxPartitions + + let sortInPlaceWithHelper + (partitioningComparer: 'T -> 'T -> int) + (sortingComparer: IComparer<'T>) + (inputArray: 'T[]) + = + let partitioningFunc = partitionIntoTwoUsingComparer partitioningComparer + + let sortingFunc = + fun (s: ArraySegment<'T>) -> Array.Sort<'T>(inputArray, s.Offset, s.Count, sortingComparer) + + sortInPlaceHelper inputArray partitioningFunc sortingFunc + + let sortKeysAndValuesInPlace (inputKeys: 'TKey[]) (values: 'TValue[]) = + let partitioningFunc = partitionIntoTwoUsingKeys inputKeys + let sortingComparer = LanguagePrimitives.FastGenericComparerCanBeNull<'TKey> + + let sortingFunc = + fun (s: ArraySegment<'T>) -> + Array.Sort<'TKey, 'TValue>(inputKeys, values, s.Offset, s.Count, sortingComparer) + + sortInPlaceHelper values partitioningFunc sortingFunc + + [] + let sortInPlaceWith comparer (array: 'T[]) = + checkNonNull "array" array + let sortingComparer = ComparisonIdentity.FromFunction(comparer) + sortInPlaceWithHelper comparer sortingComparer array + + [] + let sortInPlaceBy (projection: 'T -> 'U) (array: 'T[]) = + checkNonNull "array" array + + let inputKeys: 'U[] = + Microsoft.FSharp.Primitives.Basics.Array.zeroCreateUnchecked array.Length + + let partitions = createPartitions array + + Parallel.For( + 0, + partitions.Length, + fun i -> + let segment = partitions.[i] + + for idx = segment.Offset to (segment.Offset + segment.Count - 1) do + inputKeys[idx] <- projection array[idx] + ) + |> ignore + + sortKeysAndValuesInPlace inputKeys array + + [] + let sortInPlace (array: 'T[]) = + checkNonNull "array" array + + let sortingComparer: IComparer<'T> = + LanguagePrimitives.FastGenericComparerCanBeNull<'T> + + let partioningFunc = compare + sortInPlaceWithHelper partioningFunc sortingComparer array + + [] + let sortWith (comparer: 'T -> 'T -> int) (array: 'T[]) = + let result = copy array + sortInPlaceWith comparer result + result + + [] + let sortBy projection (array: 'T[]) = + checkNonNull "array" array + + let inputKeys = + Microsoft.FSharp.Primitives.Basics.Array.zeroCreateUnchecked array.Length + + let clone = + Microsoft.FSharp.Primitives.Basics.Array.zeroCreateUnchecked array.Length + + let partitions = createPartitions clone + + Parallel.For( + 0, + partitions.Length, + fun i -> + let segment = partitions.[i] + + for idx = segment.Offset to (segment.Offset + segment.Count - 1) do + clone[idx] <- array[idx] + inputKeys.[idx] <- projection array[idx] + ) + |> ignore + + sortKeysAndValuesInPlace inputKeys clone + clone + + [] + let sort array = + let result = copy array + sortInPlace result + result + + let reverseInPlace (array: 'T[]) = + let segments = createPartitionsUpTo (array.Length / 2) array + let lastIdx = array.Length - 1 + + Parallel.For( + 0, + segments.Length, + fun idx -> + let s = segments[idx] + + for i = s.Offset to (s.Offset + s.Count - 1) do + let tmp = array[i] + array[i] <- array[lastIdx - i] + array[lastIdx - i] <- tmp + ) + |> ignore + + array + + [] + let sortByDescending projection array = + array |> sortBy projection |> reverseInPlace + + [] + let sortDescending array = + array |> sort |> reverseInPlace diff --git a/src/FSharp.Core/array.fsi b/src/FSharp.Core/array.fsi index ea978be8f9c..2e34d39c263 100644 --- a/src/FSharp.Core/array.fsi +++ b/src/FSharp.Core/array.fsi @@ -3492,6 +3492,194 @@ module Array = [] val partition: predicate:('T -> bool) -> array:'T[] -> 'T[] * 'T[] + /// Sorts the elements of an array in parallel, returning a new array. Elements are compared using . + /// + /// This is not a stable sort, i.e. the original order of equal elements is not necessarily preserved. + /// For a stable sort, consider using . + /// + /// The input array. + /// + /// The sorted array. + /// + /// Thrown when the input array is null. + /// + /// + /// + /// let input = [| 8; 4; 3; 1; 6; 1 |] + /// + /// Array.Parallel.sort input + /// + /// Evaluates to [| 1; 1 3; 4; 6; 8 |]. + /// + [] + [] + val sort: array:'T[] -> 'T[] when 'T : comparison + + /// Sorts the elements of an array in parallel, using the given projection for the keys and returning a new array. + /// Elements are compared using . + /// + /// This is not a stable sort, i.e. the original order of equal elements is not necessarily preserved. + /// For a stable sort, consider using . + /// + /// The function to transform array elements into the type that is compared. + /// The input array. + /// + /// The sorted array. + /// + /// Thrown when the input array is null. + /// + /// + /// + /// let input = [| "a"; "bbb"; "cccc"; "dd" |] + /// + /// input |> Array.Parallel.sortBy (fun s -> s.Length) + /// + /// Evaluates to [|"a"; "dd"; "bbb"; "cccc"|]. + /// + + [] + [] + val sortBy: projection:('T -> 'Key) -> array:'T[] -> 'T[] when 'Key : comparison + + /// Sorts the elements of an array in parallel, using the given comparison function as the order, returning a new array. + /// + /// This is not a stable sort, i.e. the original order of equal elements is not necessarily preserved. + /// For a stable sort, consider using . + /// + /// The function to compare pairs of array elements. + /// The input array. + /// + /// The sorted array. + /// + /// Thrown when the input array is null. + /// + /// Sort an array of pairs using a comparison function that compares string lengths then index numbers: + /// + /// let compareEntries (n1: int, s1: string) (n2: int, s2: string) = + /// let c = compare s1.Length s2.Length + /// if c <> 0 then c else + /// compare n1 n2 + /// + /// let input = [| (0,"aa"); (1,"bbb"); (2,"cc"); (3,"dd") |] + /// + /// input |> Array.Parallel.sortWith compareEntries + /// + /// Evaluates to [|(0, "aa"); (2, "cc"); (3, "dd"); (1, "bbb")|]. + /// + [] + [] + val sortWith: comparer:('T -> 'T -> int) -> array:'T[] -> 'T[] + + /// Sorts the elements of an array by mutating the array in-place in parallel, using the given projection for the keys. + /// Elements are compared using . + /// + /// This is not a stable sort, i.e. the original order of equal elements is not necessarily preserved. + /// For a stable sort, consider using . + /// + /// The function to transform array elements into the type that is compared. + /// The input array. + /// + /// Thrown when the input array is null. + /// + /// + /// + /// let array = [| "a"; "bbb"; "cccc"; "dd" |] + /// + /// array |> Array.Parallel.sortInPlaceBy (fun s -> s.Length) + /// + /// After evaluation array contains [|"a"; "dd"; "bbb"; "cccc"|]. + /// + [] + [] + val sortInPlaceBy: projection:('T -> 'Key) -> array:'T[] -> unit when 'Key : comparison + + /// Sorts the elements of an array by mutating the array in-place in parallel, using the given comparison function as the order. + /// + /// The function to compare pairs of array elements. + /// The input array. + /// + /// Thrown when the input array is null. + /// + /// The following sorts entries using a comparison function that compares string lengths then index numbers: + /// + /// let compareEntries (n1: int, s1: string) (n2: int, s2: string) = + /// let c = compare s1.Length s2.Length + /// if c <> 0 then c else + /// compare n1 n2 + /// + /// let array = [| (0,"aa"); (1,"bbb"); (2,"cc"); (3,"dd") |] + /// + /// array |> Array.Parallel.sortInPlaceWith compareEntries + /// + /// After evaluation array contains [|(0, "aa"); (2, "cc"); (3, "dd"); (1, "bbb")|]. + /// + [] + [] + val sortInPlaceWith: comparer:('T -> 'T -> int) -> array:'T[] -> unit + + /// Sorts the elements of an array by mutating the array in-place in parallel, using the given comparison function. + /// Elements are compared using . + /// + /// The input array. + /// + /// Thrown when the input array is null. + /// + /// + /// + /// let array = [| 8; 4; 3; 1; 6; 1 |] + /// + /// Array.sortInPlace array + /// + /// After evaluation array contains [| 1; 1; 3; 4; 6; 8 |]. + /// + [] + [] + val sortInPlace: array:'T[] -> unit when 'T : comparison + + /// Sorts the elements of an array in parallel, in descending order, returning a new array. Elements are compared using . + /// + /// This is not a stable sort, i.e. the original order of equal elements is not necessarily preserved. + /// For a stable sort, consider using . + /// + /// The input array. + /// + /// The sorted array. + /// + /// + /// + /// let input = [| 8; 4; 3; 1; 6; 1 |] + /// + /// input |> Array.Parallel.sortDescending + /// + /// Evaluates to [| 8; 6; 4; 3; 1; 1 |]. + /// + [] + [] + val sortDescending: array:'T[] -> 'T[] when 'T : comparison + + /// Sorts the elements of an array in parallel, in descending order, using the given projection for the keys and returning a new array. + /// Elements are compared using . + /// + /// This is not a stable sort, i.e. the original order of equal elements is not necessarily preserved. + /// For a stable sort, consider using . + /// + /// The function to transform array elements into the type that is compared. + /// The input array. + /// + /// The sorted array. + /// + /// + /// + /// let input = [| "a"; "bbb"; "cccc"; "dd" |] + /// + /// input |> Array.Parallel.sortByDescending (fun s -> s.Length) + /// + /// Evaluates to [|"cccc"; "bbb"; "dd"; "a"|]. + /// + [] + [] + val sortByDescending: projection:('T -> 'Key) -> array:'T[] -> 'T[] when 'Key : comparison + /// Combines the two arrays into an array of pairs. The two arrays must have equal lengths, otherwise an ArgumentException is /// raised. /// diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard20.debug.bsl b/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard20.debug.bsl index ccac95ae185..1924e0d7067 100644 --- a/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard20.debug.bsl +++ b/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard20.debug.bsl @@ -55,8 +55,16 @@ Microsoft.FSharp.Collections.ArrayModule+Parallel: TResult[] MapIndexed[T,TResul Microsoft.FSharp.Collections.ArrayModule+Parallel: TResult[] Map[T,TResult](Microsoft.FSharp.Core.FSharpFunc`2[T,TResult], T[]) Microsoft.FSharp.Collections.ArrayModule+Parallel: T[] Filter[T](Microsoft.FSharp.Core.FSharpFunc`2[T,System.Boolean], T[]) Microsoft.FSharp.Collections.ArrayModule+Parallel: T[] Initialize[T](Int32, Microsoft.FSharp.Core.FSharpFunc`2[System.Int32,T]) +Microsoft.FSharp.Collections.ArrayModule+Parallel: T[] SortByDescending[T,TKey](Microsoft.FSharp.Core.FSharpFunc`2[T,TKey], T[]) +Microsoft.FSharp.Collections.ArrayModule+Parallel: T[] SortBy[T,TKey](Microsoft.FSharp.Core.FSharpFunc`2[T,TKey], T[]) +Microsoft.FSharp.Collections.ArrayModule+Parallel: T[] SortDescending[T](T[]) +Microsoft.FSharp.Collections.ArrayModule+Parallel: T[] SortWith[T](Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.FSharpFunc`2[T,System.Int32]], T[]) +Microsoft.FSharp.Collections.ArrayModule+Parallel: T[] Sort[T](T[]) Microsoft.FSharp.Collections.ArrayModule+Parallel: Void IterateIndexed[T](Microsoft.FSharp.Core.FSharpFunc`2[System.Int32,Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.Unit]], T[]) Microsoft.FSharp.Collections.ArrayModule+Parallel: Void Iterate[T](Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.Unit], T[]) +Microsoft.FSharp.Collections.ArrayModule+Parallel: Void SortInPlaceBy[T,TKey](Microsoft.FSharp.Core.FSharpFunc`2[T,TKey], T[]) +Microsoft.FSharp.Collections.ArrayModule+Parallel: Void SortInPlaceWith[T](Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.FSharpFunc`2[T,System.Int32]], T[]) +Microsoft.FSharp.Collections.ArrayModule+Parallel: Void SortInPlace[T](T[]) Microsoft.FSharp.Collections.ArrayModule: Boolean Contains[T](T, T[]) Microsoft.FSharp.Collections.ArrayModule: Boolean Exists2[T1,T2](Microsoft.FSharp.Core.FSharpFunc`2[T1,Microsoft.FSharp.Core.FSharpFunc`2[T2,System.Boolean]], T1[], T2[]) Microsoft.FSharp.Collections.ArrayModule: Boolean Exists[T](Microsoft.FSharp.Core.FSharpFunc`2[T,System.Boolean], T[]) diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard20.release.bsl b/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard20.release.bsl index 1ceae1e15a2..4458119e921 100644 --- a/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard20.release.bsl +++ b/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard20.release.bsl @@ -54,8 +54,16 @@ Microsoft.FSharp.Collections.ArrayModule+Parallel: TResult[] MapIndexed[T,TResul Microsoft.FSharp.Collections.ArrayModule+Parallel: TResult[] Map[T,TResult](Microsoft.FSharp.Core.FSharpFunc`2[T,TResult], T[]) Microsoft.FSharp.Collections.ArrayModule+Parallel: T[] Filter[T](Microsoft.FSharp.Core.FSharpFunc`2[T,System.Boolean], T[]) Microsoft.FSharp.Collections.ArrayModule+Parallel: T[] Initialize[T](Int32, Microsoft.FSharp.Core.FSharpFunc`2[System.Int32,T]) +Microsoft.FSharp.Collections.ArrayModule+Parallel: T[] SortByDescending[T,TKey](Microsoft.FSharp.Core.FSharpFunc`2[T,TKey], T[]) +Microsoft.FSharp.Collections.ArrayModule+Parallel: T[] SortBy[T,TKey](Microsoft.FSharp.Core.FSharpFunc`2[T,TKey], T[]) +Microsoft.FSharp.Collections.ArrayModule+Parallel: T[] SortDescending[T](T[]) +Microsoft.FSharp.Collections.ArrayModule+Parallel: T[] SortWith[T](Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.FSharpFunc`2[T,System.Int32]], T[]) +Microsoft.FSharp.Collections.ArrayModule+Parallel: T[] Sort[T](T[]) Microsoft.FSharp.Collections.ArrayModule+Parallel: Void IterateIndexed[T](Microsoft.FSharp.Core.FSharpFunc`2[System.Int32,Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.Unit]], T[]) Microsoft.FSharp.Collections.ArrayModule+Parallel: Void Iterate[T](Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.Unit], T[]) +Microsoft.FSharp.Collections.ArrayModule+Parallel: Void SortInPlaceBy[T,TKey](Microsoft.FSharp.Core.FSharpFunc`2[T,TKey], T[]) +Microsoft.FSharp.Collections.ArrayModule+Parallel: Void SortInPlaceWith[T](Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.FSharpFunc`2[T,System.Int32]], T[]) +Microsoft.FSharp.Collections.ArrayModule+Parallel: Void SortInPlace[T](T[]) Microsoft.FSharp.Collections.ArrayModule: Boolean Contains[T](T, T[]) Microsoft.FSharp.Collections.ArrayModule: Boolean Exists2[T1,T2](Microsoft.FSharp.Core.FSharpFunc`2[T1,Microsoft.FSharp.Core.FSharpFunc`2[T2,System.Boolean]], T1[], T2[]) Microsoft.FSharp.Collections.ArrayModule: Boolean Exists[T](Microsoft.FSharp.Core.FSharpFunc`2[T,System.Boolean], T[]) diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard21.debug.bsl b/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard21.debug.bsl index 2863239fd15..efa567e9d49 100644 --- a/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard21.debug.bsl +++ b/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard21.debug.bsl @@ -54,8 +54,16 @@ Microsoft.FSharp.Collections.ArrayModule+Parallel: TResult[] MapIndexed[T,TResul Microsoft.FSharp.Collections.ArrayModule+Parallel: TResult[] Map[T,TResult](Microsoft.FSharp.Core.FSharpFunc`2[T,TResult], T[]) Microsoft.FSharp.Collections.ArrayModule+Parallel: T[] Filter[T](Microsoft.FSharp.Core.FSharpFunc`2[T,System.Boolean], T[]) Microsoft.FSharp.Collections.ArrayModule+Parallel: T[] Initialize[T](Int32, Microsoft.FSharp.Core.FSharpFunc`2[System.Int32,T]) +Microsoft.FSharp.Collections.ArrayModule+Parallel: T[] SortByDescending[T,TKey](Microsoft.FSharp.Core.FSharpFunc`2[T,TKey], T[]) +Microsoft.FSharp.Collections.ArrayModule+Parallel: T[] SortBy[T,TKey](Microsoft.FSharp.Core.FSharpFunc`2[T,TKey], T[]) +Microsoft.FSharp.Collections.ArrayModule+Parallel: T[] SortDescending[T](T[]) +Microsoft.FSharp.Collections.ArrayModule+Parallel: T[] SortWith[T](Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.FSharpFunc`2[T,System.Int32]], T[]) +Microsoft.FSharp.Collections.ArrayModule+Parallel: T[] Sort[T](T[]) Microsoft.FSharp.Collections.ArrayModule+Parallel: Void IterateIndexed[T](Microsoft.FSharp.Core.FSharpFunc`2[System.Int32,Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.Unit]], T[]) Microsoft.FSharp.Collections.ArrayModule+Parallel: Void Iterate[T](Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.Unit], T[]) +Microsoft.FSharp.Collections.ArrayModule+Parallel: Void SortInPlaceBy[T,TKey](Microsoft.FSharp.Core.FSharpFunc`2[T,TKey], T[]) +Microsoft.FSharp.Collections.ArrayModule+Parallel: Void SortInPlaceWith[T](Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.FSharpFunc`2[T,System.Int32]], T[]) +Microsoft.FSharp.Collections.ArrayModule+Parallel: Void SortInPlace[T](T[]) Microsoft.FSharp.Collections.ArrayModule: Boolean Contains[T](T, T[]) Microsoft.FSharp.Collections.ArrayModule: Boolean Exists2[T1,T2](Microsoft.FSharp.Core.FSharpFunc`2[T1,Microsoft.FSharp.Core.FSharpFunc`2[T2,System.Boolean]], T1[], T2[]) Microsoft.FSharp.Collections.ArrayModule: Boolean Exists[T](Microsoft.FSharp.Core.FSharpFunc`2[T,System.Boolean], T[]) diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard21.release.bsl b/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard21.release.bsl index 9d39de05657..3f24d58bbc1 100644 --- a/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard21.release.bsl +++ b/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard21.release.bsl @@ -54,8 +54,16 @@ Microsoft.FSharp.Collections.ArrayModule+Parallel: TResult[] MapIndexed[T,TResul Microsoft.FSharp.Collections.ArrayModule+Parallel: TResult[] Map[T,TResult](Microsoft.FSharp.Core.FSharpFunc`2[T,TResult], T[]) Microsoft.FSharp.Collections.ArrayModule+Parallel: T[] Filter[T](Microsoft.FSharp.Core.FSharpFunc`2[T,System.Boolean], T[]) Microsoft.FSharp.Collections.ArrayModule+Parallel: T[] Initialize[T](Int32, Microsoft.FSharp.Core.FSharpFunc`2[System.Int32,T]) +Microsoft.FSharp.Collections.ArrayModule+Parallel: T[] SortByDescending[T,TKey](Microsoft.FSharp.Core.FSharpFunc`2[T,TKey], T[]) +Microsoft.FSharp.Collections.ArrayModule+Parallel: T[] SortBy[T,TKey](Microsoft.FSharp.Core.FSharpFunc`2[T,TKey], T[]) +Microsoft.FSharp.Collections.ArrayModule+Parallel: T[] SortDescending[T](T[]) +Microsoft.FSharp.Collections.ArrayModule+Parallel: T[] SortWith[T](Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.FSharpFunc`2[T,System.Int32]], T[]) +Microsoft.FSharp.Collections.ArrayModule+Parallel: T[] Sort[T](T[]) Microsoft.FSharp.Collections.ArrayModule+Parallel: Void IterateIndexed[T](Microsoft.FSharp.Core.FSharpFunc`2[System.Int32,Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.Unit]], T[]) Microsoft.FSharp.Collections.ArrayModule+Parallel: Void Iterate[T](Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.Unit], T[]) +Microsoft.FSharp.Collections.ArrayModule+Parallel: Void SortInPlaceBy[T,TKey](Microsoft.FSharp.Core.FSharpFunc`2[T,TKey], T[]) +Microsoft.FSharp.Collections.ArrayModule+Parallel: Void SortInPlaceWith[T](Microsoft.FSharp.Core.FSharpFunc`2[T,Microsoft.FSharp.Core.FSharpFunc`2[T,System.Int32]], T[]) +Microsoft.FSharp.Collections.ArrayModule+Parallel: Void SortInPlace[T](T[]) Microsoft.FSharp.Collections.ArrayModule: Boolean Contains[T](T, T[]) Microsoft.FSharp.Collections.ArrayModule: Boolean Exists2[T1,T2](Microsoft.FSharp.Core.FSharpFunc`2[T1,Microsoft.FSharp.Core.FSharpFunc`2[T2,System.Boolean]], T1[], T2[]) Microsoft.FSharp.Collections.ArrayModule: Boolean Exists[T](Microsoft.FSharp.Core.FSharpFunc`2[T,System.Boolean], T[]) diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/ArrayModule2.fs b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/ArrayModule2.fs index 496b560d72e..a402c396765 100644 --- a/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/ArrayModule2.fs +++ b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/ArrayModule2.fs @@ -644,7 +644,57 @@ type ArrayModule2() = Assert.AreEqual([|8;8;8|], eights) () + + + member private _.MultiplyArray(template:_[],repetitions:int) = + Array.zeroCreate repetitions |> Array.collect (fun _ -> template) + + member private _.CompareTwoMethods<'TIn,'TOut when 'TOut: equality> (regularArrayFunc:'TIn[]->'TOut[]) (arrayParaFunc:'TIn[]->'TOut[]) (initialData:'TIn[]) = + let first,second = initialData, Array.copy initialData + let whenSequential = regularArrayFunc second + let whenParallel = arrayParaFunc first + + if(whenSequential <> whenParallel) then + Assert.AreEqual(whenSequential.Length, whenParallel.Length, "Lengths are different") + let diffsAt = + Array.zip whenSequential whenParallel + |> Array.mapi (fun idx (a,b) -> if(a <> b) then Some(idx,(a,b)) else None) + |> Array.choose id + |> dict + + Assert.Empty(diffsAt) + Assert.Equal<'TOut>(whenSequential, whenParallel) + + [] + member this.sortInPlaceWithParallel() = + + let tee f x = f x; x + // integer array + this.MultiplyArray([|3;5;7;2;4;8|],1_000) + |> this.CompareTwoMethods (tee (Array.sortInPlaceWith compare)) (tee (Array.Parallel.sortInPlaceWith compare)) + + // Sort backwards + this.MultiplyArray([|3;5;7;2;4;8|],1_000) + |> this.CompareTwoMethods (tee (Array.sortInPlaceWith (fun a b -> -1 * compare a b))) (tee (Array.Parallel.sortInPlaceWith (fun a b -> -1 * compare a b))) + // string array + let strArr = [|"Lists"; "are"; "a"; "commonly"; "used"; "data"; "structure"|] + this.MultiplyArray(strArr,1_000) + |> this.CompareTwoMethods (tee (Array.sortInPlaceWith compare)) (tee (Array.Parallel.sortInPlaceWith compare)) + + // empty array + [| |] + |> this.CompareTwoMethods (tee (Array.sortInPlaceWith compare)) (tee (Array.Parallel.sortInPlaceWith compare)) + + // null array + let nullArr = null:string[] + CheckThrowsArgumentNullException (fun () -> Array.Parallel.sortInPlaceWith compare nullArr |> ignore) + + // Equal elements + this.MultiplyArray([|8; 8;8|],1_000) + |> this.CompareTwoMethods (tee (Array.sortInPlaceWith compare)) (tee (Array.Parallel.sortInPlaceWith compare)) + + () [] member this.sortInPlaceBy() = @@ -675,6 +725,31 @@ type ArrayModule2() = Assert.AreEqual([|3;8|],len2Arr) () + + [] + member this.sortInPlaceByParallel() = + let tee f x = f x; x + + // integer array + this.MultiplyArray([|3;5;7;2;4;8|],50) + |> this.CompareTwoMethods (tee (Array.sortInPlaceBy int)) (tee (Array.Parallel.sortInPlaceBy int)) + + // string array + let strArr = [|"Lists"; "are"; "a"; "commonly"; "used"; "datastructure"|] + this.MultiplyArray(strArr,1_000) + |> this.CompareTwoMethods (tee (Array.sortInPlaceBy (fun (x:string) -> x.Length))) (tee (Array.Parallel.sortInPlaceBy (fun (x:string) -> x.Length))) + + + // empty array + let emptyArr:int[] = [| |] + Array.Parallel.sortInPlaceBy int emptyArr + if emptyArr <> [||] then Assert.Fail() + + // null array + let nullArr = null:string[] + CheckThrowsArgumentNullException (fun () -> Array.Parallel.sortInPlaceBy (fun (x:string) -> x.Length) nullArr |> ignore) + + () [] member this.SortDescending() = @@ -711,6 +786,40 @@ type ArrayModule2() = Assert.AreEqual([| maxFloat; 2.0; 1.5; 1.0; 0.5; epsilon; 0.0; -epsilon; minFloat; |], resultFloat) () + + [] + member this.SortDescendingParallel() = + // integer array + this.MultiplyArray([|3;5;7;2;4;8|],1_000) + |> this.CompareTwoMethods (Array.sortDescending) (Array.Parallel.sortDescending) + + // string array + this.MultiplyArray([|"Z";"a";"d"; ""; "Y"; null; "c";"b";"X"|] ,1_000) + |> this.CompareTwoMethods (Array.sortDescending) (Array.Parallel.sortDescending) + + // empty array + let emptyArr:int[] = [| |] + let resultEmpty = Array.Parallel.sortDescending emptyArr + if resultEmpty <> [||] then Assert.Fail() + + // tuple array + let tupArr = [|(2,"a");(1,"d");(1,"b");(1,"a");(2,"x");(2,"b");(1,"x")|] + this.MultiplyArray(tupArr,1_000) + |> this.CompareTwoMethods (Array.sortDescending) (Array.Parallel.sortDescending) + + // date array + let dateArr = [|DateTime(2014,12,31);DateTime(2014,1,1);DateTime(2015,1,1);DateTime(2013,12,31);DateTime(2014,1,1)|] + this.MultiplyArray(dateArr,1_000) + |> this.CompareTwoMethods (Array.sortDescending) (Array.Parallel.sortDescending) + Assert.AreEqual([|DateTime(2014,12,31);DateTime(2014,1,1);DateTime(2015,1,1);DateTime(2013,12,31);DateTime(2014,1,1)|], dateArr) + + // float array + let minFloat,maxFloat,epsilon = System.Double.MinValue,System.Double.MaxValue,System.Double.Epsilon + let floatArr = [| 0.0; 0.5; 2.0; 1.5; 1.0; minFloat; maxFloat; epsilon; -epsilon |] + this.MultiplyArray(floatArr,1_000) + |> this.CompareTwoMethods (Array.sortDescending) (Array.Parallel.sortDescending) + + () [] member this.SortByDescending() = @@ -750,6 +859,46 @@ type ArrayModule2() = Assert.AreEqual([| maxFloat; 2.0; 1.5; 1.0; 0.5; epsilon; 0.0; -epsilon; minFloat; |], resultFloat) () + + [] + member this.SortByDescendingParallel() = + // integer array + let intArr = [|3;5;7;2;4;8|] + this.MultiplyArray(intArr,1_000) + |> this.CompareTwoMethods(Array.sortByDescending int) (Array.Parallel.sortByDescending int) + Assert.AreEqual([|3;5;7;2;4;8|], intArr) + + + // string array + let strArr = [|".."; ""; "..."; "."; "...."|] + this.MultiplyArray(strArr,1_000) + |> this.CompareTwoMethods(Array.sortByDescending (fun (x:string) -> x.Length)) (Array.Parallel.sortByDescending (fun (x:string) -> x.Length)) + Assert.AreEqual([|".."; ""; "..."; "."; "...."|], strArr) + + // empty array + let emptyArr:int[] = [| |] + let resultEmpty = Array.Parallel.sortByDescending int emptyArr + if resultEmpty <> [||] then Assert.Fail() + + // tuple array + let tupArr = [|(2,"a");(1,"d");(1,"b");(2,"x")|] + this.MultiplyArray(tupArr,1_000) + |> this.CompareTwoMethods(Array.sortByDescending snd) (Array.Parallel.sortByDescending snd) + Assert.AreEqual( [|(2,"a");(1,"d");(1,"b");(2,"x")|] , tupArr) + + // date array + let dateArr = [|DateTime(2013,12,31);DateTime(2014,2,1);DateTime(2015,1,1);DateTime(2014,3,1)|] + this.MultiplyArray(dateArr,1_000) + |> this.CompareTwoMethods(Array.sortByDescending (fun (d:DateTime) -> d.Month)) (Array.Parallel.sortByDescending (fun (d:DateTime) -> d.Month)) + Assert.AreEqual([|DateTime(2013,12,31);DateTime(2014,2,1);DateTime(2015,1,1);DateTime(2014,3,1)|], dateArr) + + // float array + let minFloat,maxFloat,epsilon = System.Double.MinValue,System.Double.MaxValue,System.Double.Epsilon + let floatArr = [| 0.0; 0.5; 2.0; 1.5; 1.0; minFloat; maxFloat; epsilon; -epsilon |] + this.MultiplyArray(floatArr,1_000) + |> this.CompareTwoMethods(Array.sortByDescending id) (Array.Parallel.sortByDescending id) + + () [] member this.Sub() = diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/CollectionModulesConsistency.fs b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/CollectionModulesConsistency.fs index c8730cd26a2..e22e3d019ba 100644 --- a/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/CollectionModulesConsistency.fs +++ b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/CollectionModulesConsistency.fs @@ -8,6 +8,7 @@ open FsCheck open Utils let smallerSizeCheck testable = Check.One({ Config.QuickThrowOnFailure with EndSize = 25 }, testable) +let bigSizeCheck testable = Check.One({ Config.QuickThrowOnFailure with StartSize = 222;EndSize = 999; MaxTest = 8 }, testable) /// helper function that creates labeled FsCheck properties for equality comparisons let consistency name sqs ls arr = @@ -998,6 +999,7 @@ let ``sortByDescending actually sorts (but is inconsistent in regards of stabili smallerSizeCheck sortByDescending smallerSizeCheck sortByDescending + let sum (xs : int []) = let s = run (fun () -> xs |> Seq.sum) let l = run (fun () -> xs |> Array.toList |> List.sum) @@ -1314,3 +1316,87 @@ let ``zip3 is consistent for collections with equal length`` () = smallerSizeCheck zip3 smallerSizeCheck zip3 smallerSizeCheck zip3 + + +module ArrayParallelVsArray = + let sort<'a when 'a : comparison> (xs : 'a []) = + let a = xs |> Array.sort + let pa = xs |> Array.Parallel.sort + + let opName = "sort" + (a = pa) |@ (sprintf "Array.%s = '%A', Array.Parallel.%s = '%A'" opName a opName pa) + + [] + let ``sort is consistent`` () = + bigSizeCheck sort + bigSizeCheck sort + bigSizeCheck sort + + let sortBy<'a,'b when 'a : comparison and 'b : comparison> (xs : 'a []) (f:'a -> 'b) = + let a = xs |> Array.sortBy f + let pa = xs |> Array.Parallel.sortBy f + + isSorted (Array.map f a) && isSorted (Array.map f pa) && + haveSameElements pa xs && haveSameElements a xs && + a.Length = pa.Length && a.Length = xs.Length + + [] + let ``sortBy actually sorts (but is inconsistent in regards of stability)`` () = + bigSizeCheck sortBy + bigSizeCheck sortBy + bigSizeCheck sortBy + bigSizeCheck sortBy + bigSizeCheck sortBy + + let sortWith<'a,'b when 'a : comparison and 'b : comparison> (xs : 'a []) = + let f x y = + if x = y then 0 else + if x = Unchecked.defaultof<_> && y <> Unchecked.defaultof<_> then -1 else + if y = Unchecked.defaultof<_> && x <> Unchecked.defaultof<_> then 1 else + if x < y then -1 else 1 + + let a = xs |> Array.sortWith f + let pa = xs |> Array.Parallel.sortWith f + let isSorted sorted = sorted |> Array.pairwise |> Array.forall (fun (a,b) -> f a b <= 0 || a = b) + + isSorted a && isSorted pa && + haveSameElements pa xs && haveSameElements a xs && + a.Length = pa.Length && a.Length = xs.Length + + [] + let ``sortWith actually sorts (but is inconsistent in regards of stability)`` () = + bigSizeCheck sortWith + bigSizeCheck sortWith + bigSizeCheck sortWith + bigSizeCheck sortWith + bigSizeCheck sortWith + + let sortDescending<'a when 'a : comparison> (xs : 'a []) = + let a = xs |> Array.sortDescending + let pa = xs |> Array.Parallel.sortDescending + let opName = "sortDescending" + (a = pa) |@ (sprintf "Array.%s = '%A', Array.Parallel.%s = '%A'" opName a opName pa) + + [] + let ``sortDescending is consistent`` () = + bigSizeCheck sortDescending + bigSizeCheck sortDescending + bigSizeCheck sortDescending + + let sortByDescending<'a,'b when 'a : comparison and 'b : comparison> (xs : 'a []) (f:'a -> 'b) = + let a = xs |> Array.sortByDescending f + let pa = xs |> Array.Parallel.sortByDescending f + + let isDescSorted arr = arr |> Array.pairwise |> Array.forall (fun (a,b) -> f a >= f b || a = b) + + isDescSorted a && isDescSorted pa && + haveSameElements pa xs && haveSameElements a xs && + a.Length = pa.Length && a.Length = xs.Length + + [] + let ``sortByDescending actually sorts (but is inconsistent in regards of stability)`` () = + bigSizeCheck sortByDescending + bigSizeCheck sortByDescending + bigSizeCheck sortByDescending + bigSizeCheck sortByDescending + bigSizeCheck sortByDescending \ No newline at end of file