Skip to content

Commit 8c4e0b4

Browse files
Rafalwallymathieu
authored andcommitted
+ zipShortest function and make generic zip safe (#370)
1 parent 4d2811b commit 8c4e0b4

File tree

8 files changed

+82
-14
lines changed

8 files changed

+82
-14
lines changed

src/FSharpPlus/Control/Functor.fs

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -170,20 +170,21 @@ type Unzip =
170170
type Zip =
171171
inherit Default1
172172

173-
static member Zip ((x: IEnumerator<'T> , y: IEnumerator<'U> , _output: IEnumerator<'T*'U> ), _mthd: Zip) = Enumerator.zip x y
174-
static member Zip ((x: seq<'T> , y: seq<'U> , _output: seq<'T*'U> ), _mthd: Zip) = Seq.zip x y
175-
static member Zip ((x: NonEmptySeq<'T> , y: NonEmptySeq<'U> , _output: NonEmptySeq<'T*'U> ), _mthd: Zip) = NonEmptySeq.zip x y
176-
static member Zip ((x: IDictionary<'K, 'T> , y: IDictionary<'K,'U> , _output: IDictionary<'K,'T*'U> ), _mthd: Zip) = Dict.zip x y
173+
static member Zip ((x: IEnumerator<'T> , y: IEnumerator<'U> , _output: IEnumerator<'T*'U> ), _mthd: Zip) = Enumerator.zip x y
174+
static member Zip ((x: seq<'T> , y: seq<'U> , _output: seq<'T*'U> ), _mthd: Zip) = Seq.zip x y
175+
static member Zip ((x: NonEmptySeq<'T> , y: NonEmptySeq<'U> , _output: NonEmptySeq<'T*'U> ), _mthd: Zip) = NonEmptySeq.zip x y
176+
static member Zip ((x: IDictionary<'K, 'T> , y: IDictionary<'K,'U> , _output: IDictionary<'K,'T*'U> ), _mthd: Zip) = Dict.zip x y
177177
static member Zip ((x: IReadOnlyDictionary<'K, 'T>, y: IReadOnlyDictionary<'K,'U>, _output: IReadOnlyDictionary<'K,'T*'U>), _mthd: Zip) = IReadOnlyDictionary.zip x y
178178
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>
179179
static member Zip ((x: Map<'K, 'T> , y: Map<'K,'U> , _output: Map<'K,'T*'U> ), _mthd: Zip) = Map.zip x y
180180
static member Zip ((f: 'R -> 'T , g: 'R -> 'U , _output: 'R -> 'T * 'U ), _mthd: Zip) = fun x -> (f x, g x)
181181
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))
182-
static member Zip ((x: list<'T> , y: list<'U> , _output: list<'T*'U> ), _mthd: Zip) = List.zip x y
183-
static member Zip ((x: 'T [] , y: 'U [] , _output: ('T*'U) [] ), _mthd: Zip) = Array.zip x y
184-
static member Zip ((x: option<'T> , y: option<'U> , _output: option<'T*'U> ), _mthd: Zip) = Option.zip x y
185-
static member Zip ((x: Async<'T> , y: Async<'U> , _output: Async<'T*'U> ), _mthd: Zip) = Async.zip x y
186-
static member Zip ((x: Task<'T> , y: Task<'U> , _output: Task<'T*'U> ), _mthd: Zip) = Task.zip x y
182+
static member Zip ((x: list<'T> , y: list<'U> , _output: list<'T*'U> ), _mthd: Zip) = List.zipShortest x y
183+
static member Zip ((x: 'T [] , y: 'U [] , _output: ('T*'U) [] ), _mthd: Zip) = Array.zipShortest x y
184+
static member Zip ((x: ResizeArray<'T> , y: ResizeArray<'U> , _output: ResizeArray<'T*'U> ), _mthd: Zip) = ResizeArray.zipShortest x y
185+
static member Zip ((x: option<'T> , y: option<'U> , _output: option<'T*'U> ), _mthd: Zip) = Option.zip x y
186+
static member Zip ((x: Async<'T> , y: Async<'U> , _output: Async<'T*'U> ), _mthd: Zip) = Async.zip x y
187+
static member Zip ((x: Task<'T> , y: Task<'U> , _output: Task<'T*'U> ), _mthd: Zip) = Task.zip x y
187188

188189
static member inline Invoke (source1: '``ZipFunctor<'T1>``) (source2: '``ZipFunctor<'T2>``) =
189190
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)

src/FSharpPlus/Data/NonEmptyList.fs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,17 @@ module NonEmptyList =
9898
/// <param name="list2">The second input list.</param>
9999
/// <returns>A single list containing pairs of matching elements from the input lists.</returns>
100100
let zip (list1: NonEmptyList<'T>) (list2: NonEmptyList<'U>) = {Head = (list1.Head, list2.Head); Tail = List.zip list1.Tail list2.Tail}
101+
102+
/// <summary>
103+
/// Zip safely two lists. If one list is shorter, excess elements are discarded from the right end of the longer list.
104+
/// </summary>
105+
/// <param name="a1">First input list.</param>
106+
/// <param name="a2">Second input list.</param>
107+
/// <returns>List with corresponding pairs of input lists.</returns>
108+
let zipShortest (list1: NonEmptyList<'T>) (list2: NonEmptyList<'U>) =
109+
{ Head = (list1.Head, list2.Head); Tail = List.zipShortest list1.Tail list2.Tail }
110+
111+
101112
/// Returns a new NonEmptyList with the element added to the beginning.
102113
let cons e {Head = x; Tail = xs} = {Head = e ; Tail = x::xs}
103114
/// Returns the first element of a new non empty list. You can also use property nel.Head.
@@ -200,8 +211,8 @@ type NonEmptyList<'t> with
200211
static member Unzip s = NonEmptyList.unzip s
201212

202213
[<EditorBrowsable(EditorBrowsableState.Never)>]
203-
static member Zip (x, y) = NonEmptyList.zip x y
204-
214+
static member Zip (x, y) = NonEmptyList.zipShortest x y
215+
205216
static member (>>=) ({Head = x; Tail = xs}, f: _->NonEmptyList<'b>) =
206217
let {Head = y; Tail = ys} = f x
207218
let ys' = List.collect (NonEmptyList.toList << f) xs

src/FSharpPlus/Extensions/Array.fs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,12 @@ module Array =
8686
let (x, y) = ResizeArray (), ResizeArray ()
8787
Array.iter (mapper >> function Choice1Of2 e -> x.Add e | Choice2Of2 e -> y.Add e) source
8888
x.ToArray (), y.ToArray ()
89+
90+
/// <summary>
91+
/// Zip safely two arrays. If one array is shorter, excess elements are discarded from the right end of the longer array.
92+
/// </summary>
93+
/// <param name="a1">First input array.</param>
94+
/// <param name="a2">Second input array.</param>
95+
/// <returns>Array with corresponding pairs of input arrays.</returns>
96+
let zipShortest (a1: array<'T1>) (a2: array<'T2>) =
97+
Array.init (min a1.Length a2.Length) (fun i -> a1.[i], a2.[i])

src/FSharpPlus/Extensions/List.fs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,3 +125,17 @@ module List =
125125
| Choice1Of2 x -> loop (x::acc1, acc2) xs
126126
| Choice2Of2 x -> loop (acc1, x::acc2) xs
127127
loop ([], []) (List.rev source)
128+
129+
/// <summary>
130+
/// Zip safely two lists. If one list is shorter, excess elements are discarded from the right end of the longer list.
131+
/// </summary>
132+
/// <param name="a1">First input list.</param>
133+
/// <param name="a2">Second input list.</param>
134+
/// <returns>List with corresponding pairs of input lists.</returns>
135+
let zipShortest (l1: list<'T1>) (l2: list<'T2>) =
136+
let rec loop acc = function
137+
| (l::ls,r::rs) -> loop ((l,r)::acc) (ls,rs)
138+
| (_,_) -> acc
139+
loop [] (l1,l2) |> List.rev
140+
141+

src/FSharpPlus/Extensions/ResizeArray.fs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,17 @@ module ResizeArray =
8686
let (x, y) = ResizeArray (), ResizeArray ()
8787
Array.iter (mapper >> function Choice1Of2 e -> x.Add e | Choice2Of2 e -> y.Add e) source
8888
x.ToArray (), y.ToArray ()
89+
90+
91+
/// <summary>
92+
/// Zip safely two ResizeArrays. If one ResizeArray is shorter, excess elements are discarded from the right end of the longer ResizeArray.
93+
/// </summary>
94+
/// <param name="a1">First input ResizeArray.</param>
95+
/// <param name="a2">Second input ResizeArray.</param>
96+
/// <returns>ResizeArray with corresponding pairs of input ResizeArrays.</returns>
97+
let zipShortest (a1: ResizeArray<'T1>) (a2: ResizeArray<'T2>) =
98+
let len = min a1.Count a2.Count
99+
let ra = ResizeArray(len)
100+
for i in 0..(len-1) do
101+
ra.Add (a1.[i], a2.[i])
102+
ra

src/FSharpPlus/Operators.fs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,13 +90,14 @@ module Operators =
9090
/// Like map but ignoring the results.
9191
let inline iter (action: 'T->unit) (source: '``Functor<'T>``) : unit = Iterate.Invoke action source
9292

93-
// Un-zips (un-tuple) two functors.
93+
/// Un-zips (un-tuple) two functors.
9494
let inline unzip (source: '``Functor<'T1 * 'T2>``) = Unzip.Invoke source : '``Functor<'T1>`` * '``Functor<'T2>``
9595

96-
// Zips (tuple) two functors.
96+
/// Zips (tuple) two functors.
97+
/// For collections, if one collection is shorter, excess elements are discarded from the right end of the longer collection.
9798
let inline zip (source1: '``ZipFunctor<'T1>``) (source2: '``ZipFunctor<'T2>``) : '``ZipFunctor<'T1 * 'T2>`` = Zip.Invoke source1 source2
98-
9999

100+
100101
// Applicative ------------------------------------------------------------
101102

102103

tests/FSharpPlus.Tests/Data.fs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,11 @@ module NonEmptyList =
352352
[<Test>]
353353
let ``zip `` () =
354354
nonEmptyList |> NonEmptyList.zip nonEmptyList |> NonEmptyList.toList |> shoulSeqEqual [(1,1)]
355+
356+
[<Test>]
357+
let zipShortest () =
358+
let nonEmptyList' = nonEmptyList |> NonEmptyList.cons 2
359+
nonEmptyList |> NonEmptyList.zipShortest nonEmptyList' |> NonEmptyList.toList |> shoulSeqEqual [(2,1)]
355360

356361
[<Test>]
357362
let ``get head from NonEmptyList`` () =

tests/FSharpPlus.Tests/General.fs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,19 @@ module Functor =
489489

490490
()
491491

492+
[<Test>]
493+
let genericZipShortest () =
494+
let a = zip [|1; 2; 3|] [|"a"; "b"|]
495+
CollectionAssert.AreEqual ([|1,"a"; 2,"b"|], a)
496+
497+
let l = zip [1; 2] ["a"; "b"; "c"]
498+
CollectionAssert.AreEqual ([1,"a"; 2,"b"], l)
499+
500+
let e = zip (ResizeArray [1; 2]) (ResizeArray ["a"; "b"; "c"])
501+
CollectionAssert.AreEqual (ResizeArray [1,"a"; 2,"b"], e)
502+
503+
let nel = zip (NonEmptyList.ofList [1; 2]) (NonEmptyList.ofList ["a"; "b"; "c"])
504+
CollectionAssert.AreEqual (NonEmptyList.ofList [1,"a"; 2,"b"], nel)
492505

493506
module Collections =
494507

0 commit comments

Comments
 (0)