Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add .map4 functions for modules Seq, List, and Array #28

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
25 changes: 24 additions & 1 deletion src/FSharpAux/Array.fs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,30 @@ module Array =
// else
// i <- i + 1
// found


/// <summary>
/// Builds a new collection whose elements are the results of applying the given function
/// to the corresponding quadruples from the four collections. The four input
/// arrays must have the same length, otherwise an <c>ArgumentException</c> is
/// raised.
/// </summary>
/// <param name="mapping">The function to transform the quadruples of the input elements.</param>
/// <param name="array1">The first input array.</param>
/// <param name="array2">The second input array.</param>
/// <param name="array3">The third input array.</param>
/// <param name="array4">The fourth input array.</param>
/// <exception cref="T:System.ArgumentException">Thrown when the input arrays differ in length.</exception>
/// <exception cref="T:System.ArgumentNullException">Thrown when any of the input arrays is null.</exception>
/// <returns>The array of transformed elements.</returns>
let map4 (mapping : 'T -> 'T -> 'T -> 'T -> 'U) (array1 : 'T []) (array2 : 'T []) (array3 : 'T []) (array4 : 'T []) =
checkNonNull "array1" array1
checkNonNull "array2" array2
checkNonNull "array3" array3
checkNonNull "array4" array4
if array1.Length <> array2.Length || array1.Length <> array3.Length || array1.Length <> array4.Length then
failwithf "The input lists have different lengths.\n\tarray1.Length = %i; array2.Length = %i; array3.Length = %i; array4.Length = %i" array1.Length array2.Length array3.Length array4.Length
[|for i = 0 to array1.Length - 1 do yield mapping array1[i] array2[i] array3[i] array4[i]|]

/// Builds a new array that contains every element of the input array except for that on position index.
let removeIndex index (arr : 'T []) =
if index < arr.Length then
Expand Down
22 changes: 21 additions & 1 deletion src/FSharpAux/List.fs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,27 @@ open Microsoft.FSharp.Core.OptimizedClosures

[<AutoOpen>]
module List =


/// <summary>
/// Builds a new collection whose elements are the results of applying the given function
/// to the corresponding elements of the four collections simultaneously.
/// </summary>
/// <param name="mapping">The function to transform quadruples of elements from the input lists.</param>
/// <param name="list1">The first input list.</param>
/// <param name="list2">The second input list.</param>
/// <param name="list3">The third input list.</param>
/// <param name="list4">The fourth input list.</param>
/// <returns>The list of transformed elements.</returns>
let map4 (mapping : 'T -> 'T -> 'T -> 'T -> 'U) (list1 : 'T list) (list2 : 'T list) (list3 : 'T list) (list4 : 'T list) =
if list1.Length <> list2.Length || list1.Length <> list3.Length || list1.Length <> list4.Length then
failwithf "The input lists have different lengths.\n\tlist1.Length = %i; list2.Length = %i; list3.Length = %i; list4.Length = %i" list1.Length list2.Length list3.Length list4.Length
let rec loop acc nl1 nl2 nl3 nl4 =
match nl1, nl2, nl3, nl4 with
| h1 :: t1, h2 :: t2, h3 :: t3, h4 :: t4 ->
loop (mapping h1 h2 h3 h4 :: acc) t1 t2 t3 t4
| _ -> List.rev acc
loop [] list1 list2 list3 list4

/// Applies a function to each element of the list, threading an accumulator argument through the computation. If the input function is f and the elements are i0...iN then computes f (... (f i0 i1)...) iN and returns the intermediary and final results. Raises ArgumentException if the list has size zero.
let scanReduce f l =
match l with
Expand Down
43 changes: 36 additions & 7 deletions src/FSharpAux/Seq.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@
open System.Collections.Generic

[<AutoOpen>]
module Seq =

///Adds a value to the back of a sequence.
module Seq =

let inline internal checkNonNull argName arg =
if isNull arg then
nullArg argName

/// Adds a value to the back of a sequence.
let appendSingleton (s : seq<'T>) (value : 'T) =
Seq.append s (Seq.singleton value)

///Adds a value to the front of a sequence.
/// Adds a value to the front of a sequence.
let consSingleton (s : seq<'T>) (value : 'T) =
Seq.append (Seq.singleton value) s

Expand Down Expand Up @@ -222,17 +226,42 @@ module Seq =






/// Returns head of a seq as option or None if seq is empty.
let tryHead s = Seq.tryPick Some s

/// Returns head of a seq or default value if seq is empty.
let headOrDefault defaultValue s =
match (tryHead s) with
| Some x -> x
| None -> defaultValue

/// <summary>
/// Builds a new collection whose elements are the results of applying the given function
/// to the corresponding quadruples of elements from the four sequences. If one input sequence if shorter than
/// the others then the remaining elements of the longer sequences are ignored.
/// </summary>
/// <param name="mapping">The function to transform quadruples of elements from the input sequences.</param>
/// <param name="source1">The first input sequence.</param>
/// <param name="source2">The second input sequence.</param>
/// <param name="source3">The third input sequence.</param>
/// <param name="source4">The fourth input sequence.</param>
/// <returns>The result sequence.</returns>
/// <exception cref="System.ArgumentNullException">Thrown when any of the input sequences is null.</exception>
let map4 (mapping : 'T -> 'T -> 'T -> 'T -> 'U) (source1 : seq<'T>) (source2 : seq<'T>) (source3 : seq<'T>) (source4 : seq<'T>) =
checkNonNull "source1" source1
checkNonNull "source2" source2
checkNonNull "source3" source3
checkNonNull "source4" source4
let e1 = source1.GetEnumerator()
let e2 = source2.GetEnumerator()
let e3 = source3.GetEnumerator()
let e4 = source4.GetEnumerator()
seq {
while e1.MoveNext() && e2.MoveNext() && e3.MoveNext() && e4.MoveNext() do
yield mapping e1.Current e2.Current e3.Current e4.Current
}

/// Splits a sequence of pairs into two sequences.
let unzip (input : seq<_>) =
let (lstA, lstB) =
Expand Down
12 changes: 12 additions & 0 deletions tests/FSharpAux.Tests/ArrayTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ open Expecto
let testArray1 = [|1337; 14; 23; 23; 69; 1; 2; 3; 1000; 9001; 23|]
let testArray2 = [|3; 3; 2; 4; 2; 1|]
let testArray3 = [|6; 6; 2; 4; 2; 8|]
let testArray4 = [|6; 6; 2; 4; 2; 9|]
let testArray5 = [|6; 6; 2; 4; 2; 5|]

let testArray1_filteri_Equal = [|14; 23; 23; 69|]
let testArray1_filteri_NotEqual = [|1337; 14; 23;|]
Expand All @@ -21,10 +23,20 @@ let testArray1_skipNth_Equal = [|1337; 14; 23; 69; 2; 3; 9001; 23|]
let testArray1_skipNth_NotEqual = [|5; 6; 7; 10|]
let testArray1_groupWhen_Equal = [|[|1337; 14|]; [|23|]; [|23|]; [|69|]; [|1; 2|]; [|3; 1000|]; [|9001|]; [|23|]|]
let testArray1_groupWhen_NotEqual = [|[|1337; 14|]; [|23|]; [|23|]; [|69|]; [|1; 2|]; [|3; 1000|]; [|9001; 23|]|]
let testArray_map4 = [|(3, 6, 6, 6); (3, 6, 6, 6); (2, 2, 2, 2); (4, 4, 4, 4); (2, 2, 2, 2); (1, 8, 9, 5)|]

[<Tests>]
let arrayTests =
testList "ArrayTests" [
testList "Array.map4" [
testCase "Throws when any array is null" <| fun _ ->
Expect.throws (fun _ -> Array.map4 (fun _ _ _ _ -> ()) testArray2 testArray3 null null |> ignore) "Array.map4 did not throw when an input array was null"
testCase "Throws when arrays have unequal lengths" <| fun _ ->
Expect.throws (fun _ -> Array.map4 (fun _ _ _ _ -> ()) [|1|] [||] [|3|] [|4|] |> ignore) "Array.map4 did not throw when input arrays have unequal length"
testCase "Maps correctly" <| fun _ ->
let res = Array.map4 (fun a b c d -> a, b, c, d) testArray2 testArray3 testArray4 testArray5
Expect.sequenceEqual res testArray_map4 "Array.map4 did not map correctly"
]
testList "Array.filteri" [
testCase "returns correct array" (fun _ ->
Expect.equal (testArray1 |> Array.filteri (fun i t -> i < 5 && t < 100)) testArray1_filteri_Equal "Array.filteri did return correct array"
Expand Down
8 changes: 8 additions & 0 deletions tests/FSharpAux.Tests/ListTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,18 @@ let testList4_groupWhen_Equal = [[3]; [3; 2; 4; 2; 2]]
let testList4_groupWhen_NotEqual = [[3]; [3; 2; 4; 2]; [2]]
let testList5_groupWhen_Equal = [[3]; [3; 2; 4; 2]; [1]]
let testList5_groupWhen_NotEqual = [[3]; [3; 2; 4; 2; 1]]
let testList_map4 = [(3, 3, 3, 3); (3, 3, 3, 3); (2, 2, 2, 2); (4, 4, 4, 4); (1, 1, 2, 2); (2, 1, 2, 1)]

[<Tests>]
let listTests =
testList "ListTests" [
testList "List.map4" [
testCase "throws when lists have different lengths" <| fun _ ->
Expect.throws (fun _ -> List.map4 (fun _ _ _ _ -> ()) [1] [2] [3; 3] [4] |> ignore) "List.map4 did not throw when lists had different lengths"
testCase "maps correctly" <| fun _ ->
let res = List.map4 (fun a b c d -> a, b, c, d) testList2 testList3 testList4 testList5
Expect.sequenceEqual res testList_map4 "List.map4 did not map lists correctly"
]
testList "List.filteri" [
testCase "returns correct list" (fun _ ->
Expect.equal (testList1 |> List.filteri (fun i t -> i < 5 && t < 100)) testList1_filteri_Equal "List.filteri did return correct List"
Expand Down
12 changes: 12 additions & 0 deletions tests/FSharpAux.Tests/SeqTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ let testSeq4_groupWhen_Equal = seq {seq {3}; seq {3; 2; 4; 2; 2}}
let testSeq4_groupWhen_NotEqual = seq {seq {3}; seq {3; 2; 4; 2}; seq {2}}
let testSeq5_groupWhen_Equal = seq {seq {3}; seq {3; 2; 4; 2}; seq {1}}
let testSeq5_groupWhen_NotEqual = seq {seq {3}; seq {3; 2; 4; 2; 1}}
let testSeq_map4_1 = seq {(3, 3, 3, 3); (3, 3, 3, 3); (2, 2, 2, 2); (4, 4, 4, 4); (1, 1, 2, 2); (2, 1, 2, 1)}
let testSeq_map4_2 = seq {(3, 3, 3, 1337); (3, 3, 3, 14); (2, 2, 2, 23); (4, 4, 4, 23); (1, 1, 2, 69); (2, 1, 2, 1)}

// helper functions
let list s = Seq.toList s
Expand All @@ -26,6 +28,16 @@ let list2 s = Seq.map (Seq.toList) s |> Seq.toList
[<Tests>]
let seqTests =
testList "SeqTests" [
testList "Seq.map4" [
testCase "throws when any seq is null" <| fun _ ->
Expect.throws (fun _ -> Seq.map4 (fun _ _ _ _ -> ()) testSeq2 testSeq2 null testSeq2 |> ignore) "Seq.map4 did not throw when any seq is null"
testCase "maps correctly" <| fun _ ->
let res = Seq.map4 (fun a b c d -> a, b, c, d) testSeq2 testSeq3 testSeq4 testSeq5 |> list
Expect.sequenceEqual res (list testSeq_map4_1) "Seq.map4 did not map seqs correctly"
testCase "maps correctly with unequal seq lengths" <| fun _ ->
let res = Seq.map4 (fun a b c d -> a, b, c, d) testSeq2 testSeq3 testSeq4 testSeq1 |> list
Expect.sequenceEqual res (list testSeq_map4_2) "Seq.map4 did not map correctly when seqs have unequal lengths"
]
let isOdd = fun n -> n % 2 <> 0
testList "Seq.groupWhen" [
testCase "returns correct jagged list, case1: [3; 3; 2; 4; 1; 2]" (fun _ ->
Expand Down