Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 10 additions & 9 deletions src/FSharpPlus/Control/Functor.fs
Original file line number Diff line number Diff line change
Expand Up @@ -169,20 +169,21 @@ type Unzip =
type Zip =
inherit Default1

static member Zip ((x: IEnumerator<'T> , y: IEnumerator<'U> , _output: IEnumerator<'T*'U> ), _mthd: Zip) = Enumerator.zip x y
static member Zip ((x: seq<'T> , y: seq<'U> , _output: seq<'T*'U> ), _mthd: Zip) = Seq.zip x y
static member Zip ((x: NonEmptySeq<'T> , y: NonEmptySeq<'U> , _output: NonEmptySeq<'T*'U> ), _mthd: Zip) = NonEmptySeq.zip x y
static member Zip ((x: IDictionary<'K, 'T> , y: IDictionary<'K,'U> , _output: IDictionary<'K,'T*'U> ), _mthd: Zip) = Dict.zip x y
static member Zip ((x: IEnumerator<'T> , y: IEnumerator<'U> , _output: IEnumerator<'T*'U> ), _mthd: Zip) = Enumerator.zip x y
static member Zip ((x: seq<'T> , y: seq<'U> , _output: seq<'T*'U> ), _mthd: Zip) = Seq.zipShortest x y
static member Zip ((x: NonEmptySeq<'T> , y: NonEmptySeq<'U> , _output: NonEmptySeq<'T*'U> ), _mthd: Zip) = NonEmptySeq.zipShortest x y
static member Zip ((x: IDictionary<'K, 'T> , y: IDictionary<'K,'U> , _output: IDictionary<'K,'T*'U> ), _mthd: Zip) = Dict.zip x y
static member Zip ((x: IReadOnlyDictionary<'K, 'T>, y: IReadOnlyDictionary<'K,'U>, _output: IReadOnlyDictionary<'K,'T*'U>), _mthd: Zip) = IReadOnlyDictionary.zip x y
static member Zip ((x: Dictionary<'K, 'T> , y: Dictionary<'K,'U> , _output: Dictionary<'K,'T*'U> ), _mthd: Zip) = Dict.zip x y :?> Dictionary<'K,'T*'U>
static member Zip ((x: Map<'K, 'T> , y: Map<'K,'U> , _output: Map<'K,'T*'U> ), _mthd: Zip) = Map.zip x y
static member Zip ((f: 'R -> 'T , g: 'R -> 'U , _output: 'R -> 'T * 'U ), _mthd: Zip) = fun x -> (f x, g x)
static member Zip ((f: Func<'R, 'T> , g: Func<'R, 'U> , _output: Func<'R, 'T * 'U> ), _mthd: Zip) = Func<_,_> (fun x -> (f.Invoke x, g.Invoke x))
static member Zip ((x: list<'T> , y: list<'U> , _output: list<'T*'U> ), _mthd: Zip) = List.zip x y
static member Zip ((x: 'T [] , y: 'U [] , _output: ('T*'U) [] ), _mthd: Zip) = Array.zip x y
static member Zip ((x: option<'T> , y: option<'U> , _output: option<'T*'U> ), _mthd: Zip) = Option.zip x y
static member Zip ((x: Async<'T> , y: Async<'U> , _output: Async<'T*'U> ), _mthd: Zip) = Async.zip x y
static member Zip ((x: Task<'T> , y: Task<'U> , _output: Task<'T*'U> ), _mthd: Zip) = Task.zip x y
static member Zip ((x: list<'T> , y: list<'U> , _output: list<'T*'U> ), _mthd: Zip) = List.zipShortest x y
static member Zip ((x: 'T [] , y: 'U [] , _output: ('T*'U) [] ), _mthd: Zip) = Array.zipShortest x y
static member Zip ((x: ResizeArray<'T> , y: ResizeArray<'U> , _output: ResizeArray<'T*'U> ), _mthd: Zip) = ResizeArray.zipShortest x y
static member Zip ((x: option<'T> , y: option<'U> , _output: option<'T*'U> ), _mthd: Zip) = Option.zip x y
static member Zip ((x: Async<'T> , y: Async<'U> , _output: Async<'T*'U> ), _mthd: Zip) = Async.zip x y
static member Zip ((x: Task<'T> , y: Task<'U> , _output: Task<'T*'U> ), _mthd: Zip) = Task.zip x y

static member inline Invoke (source1: '``ZipFunctor<'T1>``) (source2: '``ZipFunctor<'T2>``) =
let inline call_4 (a: ^a, b: ^b, c: ^c, d: ^d) = ((^a or ^b or ^c or ^d) : (static member Zip : (_*_*_)*_ -> _) (b, c, d), a)
Expand Down
15 changes: 13 additions & 2 deletions src/FSharpPlus/Data/NonEmptyList.fs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,17 @@ module NonEmptyList =
/// <param name="list2">The second input list.</param>
/// <returns>A single list containing pairs of matching elements from the input lists.</returns>
let zip (list1: NonEmptyList<'T>) (list2: NonEmptyList<'U>) = {Head = (list1.Head, list2.Head); Tail = List.zip list1.Tail list2.Tail}

/// <summary>
/// Zip safely two lists. If one list is shorter, excess elements are discarded from the right end of the longer list.
/// </summary>
/// <param name="a1">First input list.</param>
/// <param name="a2">Second input list.</param>
/// <returns>List with corresponding pairs of input lists.</returns>
let zipShortest (list1: NonEmptyList<'T>) (list2: NonEmptyList<'U>) =
{ Head = (list1.Head, list2.Head); Tail = List.zipShortest list1.Tail list2.Tail }


/// Returns a new NonEmptyList with the element added to the beginning.
let cons e {Head = x; Tail = xs} = {Head = e ; Tail = x::xs}
/// Returns the first element of a new non empty list. You can also use property nel.Head.
Expand Down Expand Up @@ -200,8 +211,8 @@ type NonEmptyList<'t> with
static member Unzip s = NonEmptyList.unzip s

[<EditorBrowsable(EditorBrowsableState.Never)>]
static member Zip (x, y) = NonEmptyList.zip x y

static member Zip (x, y) = NonEmptyList.zipShortest x y
static member (>>=) ({Head = x; Tail = xs}, f: _->NonEmptyList<'b>) =
let {Head = y; Tail = ys} = f x
let ys' = List.collect (NonEmptyList.toList << f) xs
Expand Down
8 changes: 8 additions & 0 deletions src/FSharpPlus/Data/NonEmptySeq.fs
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,14 @@ module NonEmptySeq =
/// <returns>The result sequence.</returns>
let zip3 (source1: NonEmptySeq<_>) (source2: NonEmptySeq<_>) (source3: NonEmptySeq<_>) = Seq.zip3 source1 source2 source3 |> unsafeOfSeq

/// <summary>
/// Zip safely two sequences. If one sequence is shorter, excess elements are discarded from the right end of the longer sequence.
/// </summary>
/// <param name="a1">First input sequence.</param>
/// <param name="a2">Second input sequence.</param>
/// <returns>Sequence with corresponding pairs of input sequences.</returns>
let zipShortest (source1: NonEmptySeq<_>) (source2: NonEmptySeq<_>) = Seq.zipShortest source1 source2 |> unsafeOfSeq

/// <summary>Applies the given function to each element of the NonEmptySequence and concatenates all the
/// results.</summary>
///
Expand Down
10 changes: 10 additions & 0 deletions src/FSharpPlus/Extensions/Array.fs
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,13 @@ module Array =
let (x, y) = ResizeArray (), ResizeArray ()
Array.iter (mapper >> function Choice1Of2 e -> x.Add e | Choice2Of2 e -> y.Add e) source
x.ToArray (), y.ToArray ()

/// <summary>
/// Zip safely two arrays. If one array is shorter, excess elements are discarded from the right end of the longer array.
/// </summary>
/// <param name="a1">First input array.</param>
/// <param name="a2">Second input array.</param>
/// <returns>Array with corresponding pairs of input arrays.</returns>
let zipShortest (a1: array<'T1>) (a2: array<'T2>) =
let len = min a1.Length a2.Length
[| for i in 0..(len-1) -> a1.[i], a2.[i] |]
14 changes: 14 additions & 0 deletions src/FSharpPlus/Extensions/List.fs
Original file line number Diff line number Diff line change
Expand Up @@ -125,3 +125,17 @@ module List =
| Choice1Of2 x -> loop (x::acc1, acc2) xs
| Choice2Of2 x -> loop (acc1, x::acc2) xs
loop ([], []) (List.rev source)

/// <summary>
/// Zip safely two lists. If one list is shorter, excess elements are discarded from the right end of the longer list.
/// </summary>
/// <param name="a1">First input list.</param>
/// <param name="a2">Second input list.</param>
/// <returns>List with corresponding pairs of input lists.</returns>
let zipShortest (l1: list<'T1>) (l2: list<'T2>) =
let rec loop acc = function
| (l::ls,r::rs) -> loop ((l,r)::acc) (ls,rs)
| (_,_) -> acc
loop [] (l1,l2) |> List.rev


12 changes: 12 additions & 0 deletions src/FSharpPlus/Extensions/ResizeArray.fs
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,15 @@ module ResizeArray =
let (x, y) = ResizeArray (), ResizeArray ()
Array.iter (mapper >> function Choice1Of2 e -> x.Add e | Choice2Of2 e -> y.Add e) source
x.ToArray (), y.ToArray ()


/// <summary>
/// Zip safely two ResizeArrays. If one ResizeArray is shorter, excess elements are discarded from the right end of the longer ResizeArray.
/// </summary>
/// <param name="a1">First input ResizeArray.</param>
/// <param name="a2">Second input ResizeArray.</param>
/// <returns>ResizeArray with corresponding pairs of input ResizeArrays.</returns>
let zipShortest (a1: ResizeArray<'T1>) (a2: ResizeArray<'T2>) =
let len = min a1.Count a2.Count
[| for i in 0..(len-1) -> a1.[i], a2.[i] |]
|> ResizeArray
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will accept it as it is, but not sure if this is the most efficient implementation in terms of allocations.

Copy link
Contributor Author

@3Rafal 3Rafal Oct 10, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed it to efficient version both here and in ResizeArray.
I've checked it with #time and implementations suggested by you are considerably faster. (even 2-3x on my machine)

12 changes: 12 additions & 0 deletions src/FSharpPlus/Extensions/Seq.fs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,18 @@ module Seq =
members <- ResizeArray ()
members.Add e.Current
yield g, members }

/// <summary>
/// Zip safely two sequences. If one seq is shorter, excess elements are discarded from the right end of the longer seq.
/// </summary>
/// <param name="s1">First input seq.</param>
/// <param name="s2">Second input seq.</param>
/// <returns>Sequence with corresponding pairs of input sequences.</returns>
let zipShortest (s1: 'T1 seq) (s2: 'T2 seq) = seq {
use e1 = s1.GetEnumerator ()
use e2 = s2.GetEnumerator ()
while e1.MoveNext () && e2.MoveNext() do
yield e1.Current, e2.Current }

/// Inserts a separator element between each element in the source seq.
///http://codebetter.com/matthewpodwysocki/2009/05/06/functionally-implementing-intersperse/
Expand Down
4 changes: 2 additions & 2 deletions src/FSharpPlus/Operators.fs
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,10 @@ module Operators =
// Un-zips (un-tuple) two functors.
let inline unzip (source: '``Functor<'T1 * 'T2>``) = Unzip.Invoke source : '``Functor<'T1>`` * '``Functor<'T2>``

// Zips (tuple) two functors.
// Zips safely two collections. If one collection is shorter, excess elements are discarded from the right end of the longer collection.
let inline zip (source1: '``ZipFunctor<'T1>``) (source2: '``ZipFunctor<'T2>``) : '``ZipFunctor<'T1 * 'T2>`` = Zip.Invoke source1 source2



// Applicative ------------------------------------------------------------


Expand Down
5 changes: 5 additions & 0 deletions tests/FSharpPlus.Tests/Data.fs
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,11 @@ module NonEmptyList =
[<Test>]
let ``zip `` () =
nonEmptyList |> NonEmptyList.zip nonEmptyList |> NonEmptyList.toList |> shoulSeqEqual [(1,1)]

[<Test>]
let zipShortest () =
let nonEmptyList' = nonEmptyList |> NonEmptyList.cons 2
nonEmptyList |> NonEmptyList.zipShortest nonEmptyList' |> NonEmptyList.toList |> shoulSeqEqual [(2,1)]

[<Test>]
let ``get head from NonEmptyList`` () =
Expand Down
19 changes: 19 additions & 0 deletions tests/FSharpPlus.Tests/General.fs
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,25 @@ module Functor =

()

[<Test>]
let genericZipShortest () =
let s = zip (seq [1; 2]) (seq ["a"; "b"; "c"])
CollectionAssert.AreEqual (seq [1,"a"; 2,"b"], s)

let a = zip [|1; 2; 3|] [|"a"; "b"|]
CollectionAssert.AreEqual ([|1,"a"; 2,"b"|], a)

let l = zip [1; 2] ["a"; "b"; "c"]
CollectionAssert.AreEqual ([1,"a"; 2,"b"], l)

let e = zip (ResizeArray [1; 2]) (ResizeArray ["a"; "b"; "c"])
CollectionAssert.AreEqual (ResizeArray [1,"a"; 2,"b"], e)

let nes = zip (NonEmptySeq.ofList [1; 2]) (NonEmptySeq.ofList ["a"; "b"; "c"])
CollectionAssert.AreEqual (NonEmptySeq.ofList [1,"a"; 2,"b"], nes)

let nel = zip (NonEmptyList.ofList [1; 2]) (NonEmptyList.ofList ["a"; "b"; "c"])
CollectionAssert.AreEqual (NonEmptyList.ofList [1,"a"; 2,"b"], nel)

module Collections =

Expand Down