Skip to content

Commit

Permalink
Speed up Array extensions (#507)
Browse files Browse the repository at this point in the history
  • Loading branch information
fcallejon authored Oct 3, 2022
1 parent 866c19d commit c56866c
Showing 1 changed file with 74 additions and 8 deletions.
82 changes: 74 additions & 8 deletions src/FSharpPlus/Extensions/Array.fs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ namespace FSharpPlus
module Array =

open System
open FSharp.Core.CompilerServices

/// <summary>Applies an array of functions to an array of values and concatenates them.</summary>
/// <param name="f">The array of functions.</param>
Expand All @@ -19,12 +20,12 @@ module Array =
/// </example>
let apply f x =
let lenf, lenx = Array.length f, Array.length x
Array.init (lenf * lenx) (fun i -> f.[i / lenx] x.[i % lenx])
Array.init (lenf * lenx) (fun i -> let (d, r) = Math.DivRem (i, lenx) in f.[d] x.[r])

/// Combines all values from the first array with the second, using the supplied mapping function.
let lift2 f x y =
let lenx, leny = Array.length x, Array.length y
Array.init (lenx * leny) (fun i -> f x.[i / leny] y.[i % leny])
Array.init (lenx * leny) (fun i -> let (d, r) = Math.DivRem (i, leny) in f x.[d] y.[r])


/// <summary>Combines all values from three arrays and calls a mapping function on this combination.</summary>
Expand All @@ -36,22 +37,87 @@ module Array =
/// <returns>Array with values returned from mapping function.</returns>
let lift3 mapping list1 list2 list3 =
let lenx, leny, lenz = Array.length list1, Array.length list2, Array.length list3
let combinedFirstTwo = Array.init (lenx * leny) (fun i -> (list1.[i / leny], list2.[i % leny]))
let combinedFirstTwo = Array.init (lenx * leny) (fun i -> let (d, r) = Math.DivRem (i, leny) in (list1.[d], list2.[r]))

Array.init (lenx * leny * lenz) (fun i -> combinedFirstTwo.[i/leny], list3.[i%leny])
Array.init (lenx * leny * lenz) (fun i -> let (d, r) = Math.DivRem (i, leny) in combinedFirstTwo.[d], list3.[r])
|> Array.map (fun x -> mapping (fst (fst x)) (snd (fst x)) (snd x))

/// Concatenates all elements, using the specified separator between each element.
let intercalate (separator: _ []) (source: seq<_ []>) = source |> Seq.intercalate separator |> Seq.toArray
let intercalate (separator: 'T []) (source: seq<'T []>) =
#if FABLE_COMPILER
source |> Seq.intercalate separator |> Seq.toArray
#else
let mutable coll = new ArrayCollector<'T> ()
let mutable notFirst = false
source |> Seq.iter (fun element ->
if notFirst then coll.AddMany separator
coll.AddMany element
notFirst <- true)
coll.Close ()
#endif

/// Inserts a separator element between each element in the source array.
let intersperse element source = source |> Array.toSeq |> Seq.intersperse element |> Seq.toArray : 'T []
let intersperse element (source: 'T []) =
match source with
| [||] -> [||]
| _ ->
let finalLength = Array.length source * 2 - 1
Array.init finalLength (fun i ->
match Math.DivRem (i, 2) with
| i, 0 -> source.[i]
| _ -> element)

/// Creates a sequence of arrays by splitting the source array on any of the given separators.
let split (separators: seq<_ []>) (source: _ []) = source |> Array.toSeq |> Seq.split separators |> Seq.map Seq.toArray

/// Replaces a subsequence of the source array with the given replacement array.
let replace (oldValue: _ []) (newValue: _ []) source = source |> Array.toSeq |> Seq.replace oldValue newValue |> Seq.toArray : 'T []
let replace (oldValue: 'T []) (newValue: 'T []) (source: 'T[]) : 'T[] =
#if FABLE_COMPILER
source |> Array.toSeq |> Seq.replace oldValue newValue |> Seq.toArray: 'T []
#else
match source with
| [||] -> [||]
| _ ->
let mutable candidate = new ArrayCollector<'T>()
let mutable sourceIndex = 0

while sourceIndex < source.Length do
let sourceItem = source.[sourceIndex]

if sourceItem = oldValue.[0]
&& sourceIndex + newValue.Length <= source.Length then
let middleIndex = (oldValue.Length - 1) / 2
let mutable oldValueIndexLeft = 0

let mutable oldValueIndexRight =
oldValue.Length - 1

let mutable matchingElements =
source.[sourceIndex + oldValueIndexLeft] = oldValue.[oldValueIndexLeft]
&& source.[sourceIndex + oldValueIndexRight] = oldValue.[oldValueIndexRight]

while oldValueIndexLeft <= middleIndex
&& oldValueIndexRight >= middleIndex
&& matchingElements do
matchingElements <-
source.[sourceIndex + oldValueIndexLeft] = oldValue.[oldValueIndexLeft]
&& source.[sourceIndex + oldValueIndexRight] = oldValue.[oldValueIndexRight]

oldValueIndexLeft <- oldValueIndexLeft + 1
oldValueIndexRight <- oldValueIndexRight - 1

if matchingElements then
candidate.AddMany newValue
sourceIndex <- sourceIndex + oldValue.Length
else
candidate.Add sourceItem
sourceIndex <- sourceIndex + 1
else
sourceIndex <- sourceIndex + 1
candidate.Add sourceItem

candidate.Close()
#endif

/// <summary>
/// Returns the index of the first occurrence of the specified slice in the source.
Expand Down Expand Up @@ -127,4 +193,4 @@ module Array =
let fi x =
i.Value <- i.Value + 1
mapping i.Value x
Array.choose fi source
Array.choose fi source

0 comments on commit c56866c

Please sign in to comment.