diff --git a/src/Fable.Python.fsproj b/src/Fable.Python.fsproj index e7eb9a7..1a155e9 100644 --- a/src/Fable.Python.fsproj +++ b/src/Fable.Python.fsproj @@ -24,6 +24,7 @@ + diff --git a/src/stdlib/Itertools.fs b/src/stdlib/Itertools.fs new file mode 100644 index 0000000..bdb2aaf --- /dev/null +++ b/src/stdlib/Itertools.fs @@ -0,0 +1,189 @@ +/// Type bindings for Python itertools module: https://docs.python.org/3/library/itertools.html +module Fable.Python.Itertools + +open Fable.Core + +// fsharplint:disable MemberNames + +[] +type IExports = + // ======================================================================== + // Infinite iterators + // ======================================================================== + + /// Make an iterator that returns evenly spaced values starting with number start + /// See https://docs.python.org/3/library/itertools.html#itertools.count + [] + abstract count: start: int -> seq + + /// Make an iterator that returns evenly spaced values starting with number start, + /// incrementing by step + /// See https://docs.python.org/3/library/itertools.html#itertools.count + [] + abstract count: start: int * step: int -> seq + + /// Make an iterator that returns evenly spaced float values + /// See https://docs.python.org/3/library/itertools.html#itertools.count + [] + abstract count: start: float -> seq + + /// Make an iterator that returns evenly spaced float values starting with start, + /// incrementing by step + /// See https://docs.python.org/3/library/itertools.html#itertools.count + [] + abstract count: start: float * step: float -> seq + + /// Make an iterator returning elements from the iterable and saving a copy of each. + /// When the iterable is exhausted, return elements from the saved copy. Repeats indefinitely. + /// See https://docs.python.org/3/library/itertools.html#itertools.cycle + abstract cycle: iterable: 'T seq -> seq<'T> + + /// Make an iterator that returns object elem over and over again. Runs indefinitely. + /// See https://docs.python.org/3/library/itertools.html#itertools.repeat + abstract repeat: elem: 'T -> seq<'T> + + /// Make an iterator that returns object elem, times times. + /// See https://docs.python.org/3/library/itertools.html#itertools.repeat + [] + abstract repeat: elem: 'T * times: int -> seq<'T> + + // ======================================================================== + // Finite iterators + // ======================================================================== + + /// Make an iterator that returns accumulated sums + /// See https://docs.python.org/3/library/itertools.html#itertools.accumulate + abstract accumulate: iterable: 'T seq -> seq<'T> + + /// Make an iterator that returns accumulated results of a binary function. + /// See https://docs.python.org/3/library/itertools.html#itertools.accumulate + abstract accumulate: iterable: 'T seq * func: System.Func<'T, 'T, 'T> -> seq<'T> + + /// Make an iterator that returns accumulated results of a binary function, + /// with an initial value. + /// See https://docs.python.org/3/library/itertools.html#itertools.accumulate + [] + abstract accumulate: iterable: 'T seq * func: System.Func<'T, 'T, 'T> * initial: 'T -> seq<'T> + + /// Make an iterator that returns elements from the first iterable until it is exhausted, + /// then proceeds to the next iterable, until all of the iterables are exhausted. + /// See https://docs.python.org/3/library/itertools.html#itertools.chain + abstract chain: a: 'T seq * b: 'T seq -> seq<'T> + + /// Make an iterator that chains three iterables together. + /// See https://docs.python.org/3/library/itertools.html#itertools.chain + abstract chain: a: 'T seq * b: 'T seq * c: 'T seq -> seq<'T> + + /// Make an iterator that chains four iterables together. + /// See https://docs.python.org/3/library/itertools.html#itertools.chain + abstract chain: a: 'T seq * b: 'T seq * c: 'T seq * d: 'T seq -> seq<'T> + + /// Make an iterator that chains iterables from a single iterable of iterables. + /// Equivalent to Python's itertools.chain.from_iterable. + /// See https://docs.python.org/3/library/itertools.html#itertools.chain.from_iterable + [] + abstract chainFromIterable: iterable: 'T seq seq -> seq<'T> + + /// Make an iterator that filters elements from data returning only those that have + /// a corresponding element in selectors that evaluates to True. + /// See https://docs.python.org/3/library/itertools.html#itertools.compress + abstract compress: data: 'T seq * selectors: bool seq -> seq<'T> + + /// Make an iterator that drops elements from the iterable as long as the predicate is true; + /// afterwards, returns every element. + /// See https://docs.python.org/3/library/itertools.html#itertools.dropwhile + abstract dropwhile: predicate: ('T -> bool) * iterable: 'T seq -> seq<'T> + + /// Make an iterator that filters elements from iterable returning only those for which + /// the predicate returns False. + /// See https://docs.python.org/3/library/itertools.html#itertools.filterfalse + abstract filterfalse: predicate: ('T -> bool) * iterable: 'T seq -> seq<'T> + + /// Make an iterator that returns consecutive keys and groups from the iterable + /// (using identity as the key function). + /// Note: The group iterators share the underlying iterable — consume each group before advancing. + /// See https://docs.python.org/3/library/itertools.html#itertools.groupby + abstract groupby: iterable: 'T seq -> seq<'T * seq<'T>> + + /// Make an iterator that returns consecutive keys and groups from the iterable. + /// The key is computed for each element; elements with equal consecutive keys are grouped. + /// Note: The group iterators share the underlying iterable — consume each group before advancing. + /// See https://docs.python.org/3/library/itertools.html#itertools.groupby + abstract groupby: iterable: 'T seq * key: ('T -> 'K) -> seq<'K * seq<'T>> + + /// Make an iterator that returns selected elements from the iterable. Stops at position stop. + /// See https://docs.python.org/3/library/itertools.html#itertools.islice + [] + abstract islice: iterable: 'T seq * stop: int -> seq<'T> + + /// Make an iterator that returns selected elements from the iterable, starting at start. + /// See https://docs.python.org/3/library/itertools.html#itertools.islice + [] + abstract islice: iterable: 'T seq * start: int * stop: int -> seq<'T> + + /// Make an iterator that returns selected elements from the iterable, with a step. + /// See https://docs.python.org/3/library/itertools.html#itertools.islice + [] + abstract islice: iterable: 'T seq * start: int * stop: int * step: int -> seq<'T> + + /// Return successive overlapping pairs taken from the iterable. + /// Requires Python 3.10+. + /// See https://docs.python.org/3/library/itertools.html#itertools.pairwise + abstract pairwise: iterable: 'T seq -> seq<'T * 'T> + + /// Make an iterator that returns elements from the iterable as long as the predicate is true. + /// See https://docs.python.org/3/library/itertools.html#itertools.takewhile + abstract takewhile: predicate: ('T -> bool) * iterable: 'T seq -> seq<'T> + + /// Make an iterator that aggregates elements from each of the iterables. + /// If the iterables are of uneven length, missing values are filled with None (option). + /// See https://docs.python.org/3/library/itertools.html#itertools.zip_longest + abstract zip_longest: a: 'T seq * b: 'U seq -> seq<'T option * 'U option> + + /// Make an iterator that aggregates elements from each of the iterables. + /// If the iterables are of uneven length, missing values are filled with fillvalue. + /// See https://docs.python.org/3/library/itertools.html#itertools.zip_longest + [] + abstract zip_longest: a: 'T seq * b: 'T seq * fillvalue: 'T -> seq<'T * 'T> + + // ======================================================================== + // Combinatoric iterators + // ======================================================================== + + /// Return successive r-length permutations of elements in the iterable. + /// All elements are used (no r limit). Each group is a Python tuple, exposed as seq<'T>. + /// See https://docs.python.org/3/library/itertools.html#itertools.permutations + [] + abstract permutations: iterable: 'T seq -> seq<'T seq> + + /// Return successive r-length permutations of elements in the iterable. + /// Each group is a Python tuple, exposed as seq<'T>. + /// See https://docs.python.org/3/library/itertools.html#itertools.permutations + [] + abstract permutations: iterable: 'T seq * r: int -> seq<'T seq> + + /// Return successive r-length combinations of elements in the iterable (no repeated elements). + /// Each group is a Python tuple, exposed as seq<'T>. + /// See https://docs.python.org/3/library/itertools.html#itertools.combinations + [] + abstract combinations: iterable: 'T seq * r: int -> seq<'T seq> + + /// Return successive r-length combinations of elements in the iterable, allowing individual + /// elements to be repeated. Each group is a Python tuple, exposed as seq<'T>. + /// See https://docs.python.org/3/library/itertools.html#itertools.combinations_with_replacement + [] + abstract combinationsWithReplacement: iterable: 'T seq * r: int -> seq<'T seq> + + /// Return the Cartesian product of the two input iterables. + /// See https://docs.python.org/3/library/itertools.html#itertools.product + abstract product: a: 'T seq * b: 'U seq -> seq<'T * 'U> + + /// Return the Cartesian product of the iterable with itself, repeated r times. + /// Each group is a Python tuple, exposed as seq<'T>. + /// See https://docs.python.org/3/library/itertools.html#itertools.product + [] + abstract product: iterable: 'T seq * repeat: int -> seq<'T seq> + +/// Functions creating iterators for efficient looping +[] +let itertools: IExports = nativeOnly diff --git a/test/Fable.Python.Test.fsproj b/test/Fable.Python.Test.fsproj index 188b590..7b0d3b9 100644 --- a/test/Fable.Python.Test.fsproj +++ b/test/Fable.Python.Test.fsproj @@ -17,6 +17,7 @@ + diff --git a/test/TestItertools.fs b/test/TestItertools.fs new file mode 100644 index 0000000..1f9a413 --- /dev/null +++ b/test/TestItertools.fs @@ -0,0 +1,122 @@ +module Fable.Python.Tests.Itertools + +open Fable.Python.Testing +open Fable.Python.Itertools + +[] +let ``test count from start works`` () = + itertools.count 1 + |> Seq.take 4 + |> Seq.toList + |> equal [ 1; 2; 3; 4 ] + +[] +let ``test count with step works`` () = + itertools.count (0, 2) + |> Seq.take 4 + |> Seq.toList + |> equal [ 0; 2; 4; 6 ] + +[] +let ``test cycle works`` () = + itertools.cycle [ 1; 2; 3 ] + |> Seq.take 7 + |> Seq.toList + |> equal [ 1; 2; 3; 1; 2; 3; 1 ] + +[] +let ``test repeat with times works`` () = + itertools.repeat ("x", 3) + |> Seq.toList + |> equal [ "x"; "x"; "x" ] + +[] +let ``test accumulate works`` () = + itertools.accumulate [ 1; 2; 3; 4; 5 ] + |> Seq.toList + |> equal [ 1; 3; 6; 10; 15 ] + +[] +let ``test accumulate with func works`` () = + itertools.accumulate ([ 1; 2; 3; 4 ], fun a b -> a * b) + |> Seq.toList + |> equal [ 1; 2; 6; 24 ] + +[] +let ``test chain two sequences works`` () = + itertools.chain ([ 1; 2 ], [ 3; 4 ]) + |> Seq.toList + |> equal [ 1; 2; 3; 4 ] + +[] +let ``test chain three sequences works`` () = + itertools.chain ([ 1 ], [ 2; 3 ], [ 4; 5 ]) + |> Seq.toList + |> equal [ 1; 2; 3; 4; 5 ] + +[] +let ``test chainFromIterable works`` () = + itertools.chainFromIterable [ [ 1; 2 ]; [ 3; 4 ]; [ 5 ] ] + |> Seq.toList + |> equal [ 1; 2; 3; 4; 5 ] + +[] +let ``test compress works`` () = + itertools.compress ([ 1; 2; 3; 4; 5 ], [ true; false; true; false; true ]) + |> Seq.toList + |> equal [ 1; 3; 5 ] + +[] +let ``test dropwhile works`` () = + itertools.dropwhile ((fun x -> x < 3), [ 1; 2; 3; 4; 5 ]) + |> Seq.toList + |> equal [ 3; 4; 5 ] + +[] +let ``test filterfalse works`` () = + itertools.filterfalse ((fun x -> x % 2 = 0), [ 1; 2; 3; 4; 5 ]) + |> Seq.toList + |> equal [ 1; 3; 5 ] + +[] +let ``test islice with stop works`` () = + itertools.islice ([ 1; 2; 3; 4; 5 ], 3) + |> Seq.toList + |> equal [ 1; 2; 3 ] + +[] +let ``test islice with start and stop works`` () = + itertools.islice ([ 1; 2; 3; 4; 5 ], 1, 4) + |> Seq.toList + |> equal [ 2; 3; 4 ] + +[] +let ``test pairwise works`` () = + itertools.pairwise [ 1; 2; 3; 4 ] + |> Seq.toList + |> equal [ (1, 2); (2, 3); (3, 4) ] + +[] +let ``test takewhile works`` () = + itertools.takewhile ((fun x -> x < 4), [ 1; 2; 3; 4; 5 ]) + |> Seq.toList + |> equal [ 1; 2; 3 ] + +[] +let ``test combinations works`` () = + itertools.combinations ([ 1; 2; 3 ], 2) + |> Seq.map Seq.toList + |> Seq.toList + |> equal [ [ 1; 2 ]; [ 1; 3 ]; [ 2; 3 ] ] + +[] +let ``test permutations with r works`` () = + itertools.permutations ([ 1; 2; 3 ], 2) + |> Seq.length + |> equal 6 + +[] +let ``test product two sequences works`` () = + itertools.product ([ 1; 2 ], [ "a"; "b" ]) + |> Seq.toList + |> equal [ (1, "a"); (1, "b"); (2, "a"); (2, "b") ]