Skip to content

Commit

Permalink
Add List.chunkBy (#561)
Browse files Browse the repository at this point in the history
  • Loading branch information
gusty authored Nov 19, 2023
1 parent a5a7c84 commit bcf934e
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 2 deletions.
6 changes: 5 additions & 1 deletion src/FSharpPlus/Control/Collection.fs
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,11 @@ type GroupBy =
type ChunkBy =
static member ChunkBy (x: Id<'T> , f: 'T->'Key, _: Id<'Key*Id<'T>> , [<Optional>]_impl: ChunkBy) = let a = Id.run x in Id.create (f a, x)
static member ChunkBy (x: seq<'T> , f: 'T->'Key, _: seq<'Key*seq<'T>> , [<Optional>]_impl: ChunkBy) = Seq.chunkBy f x |> Seq.map (fun (x,y) -> x, y :> _ seq)
static member ChunkBy (x: list<'T>, f: 'T->'Key, _: list<'Key*list<'T>>, [<Optional>]_impl: ChunkBy) = Seq.chunkBy f x |> Seq.map (fun (x,y) -> x, Seq.toList y) |> Seq.toList
static member ChunkBy (x: list<'T>, f: 'T->'Key, _: list<'Key*list<'T>>, [<Optional>]_impl: ChunkBy) =
#if TEST_TRACE
Traces.add "ChunkBy, list<'T>"
#endif
List.chunkBy f x
static member ChunkBy (x: 'T [] , f: 'T->'Key, _: ('Key*('T [])) [] , [<Optional>]_impl: ChunkBy) = Seq.chunkBy f x |> Seq.map (fun (x,y) -> x, Seq.toArray y) |> Seq.toArray

static member inline Invoke (projection: 'T->'Key) (source: '``Collection<'T>``) : '``Collection<'Key * 'Collection<'T>>`` =
Expand Down
42 changes: 42 additions & 0 deletions src/FSharpPlus/Extensions/List.fs
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,48 @@ module List =
loop (ls,rs)
loop (list1, list2)
#endif

/// <summary>
/// Chunks the list up into groups with the same projected key by applying
/// the key-generating projection function to each element and yielding a list of
/// keys tupled with values.
/// </summary>
///
/// <remarks>
/// Each key is tupled with an array of all adjacent elements that match
/// to the key, therefore keys are not unique but can't be adjacent
/// as each time the key changes a new group is yield.
///
/// The ordering of the original list is respected.
/// </remarks>
///
/// <param name="projection">A function that transforms an element of the list into a comparable key.</param>
/// <param name="source">The input list.</param>
///
/// <returns>The resulting list of keys tupled with a list of matching values</returns>
let chunkBy (projection: 'T -> 'Key) (source: _ list) =
#if FABLE_COMPILER
Seq.chunkBy projection source |> Seq.map (fun (x, y) -> x, Seq.toList y) |> Seq.toList
#else
match source with
| [] -> []
| x::xs ->
let mutable acc = new ListCollector<_> ()
let mutable members = new ListCollector<_> ()
let rec loop source g =
match source with
| [] -> acc.Add (g, members.Close ())
| x::xs ->
let key = projection x
if g <> key then
acc.Add (g, members.Close ())
members <- new ListCollector<_> ()
members.Add x
loop xs key
members.Add x
loop xs (projection x)
acc.Close ()
#endif

/// <summary>Same as choose but with access to the index.</summary>
/// <param name="mapping">The mapping function, taking index and element as parameters.</param>
Expand Down
24 changes: 24 additions & 0 deletions tests/FSharpPlus.Tests/Collections.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
namespace FSharpPlus.Tests

open FSharpPlus
open FSharpPlus.Data
open NUnit.Framework

#if TEST_TRACE
open FSharpPlus.Internals
#endif

module Collections =

[<Test>]
let chunkBy () =
#if TEST_TRACE
Traces.reset()
#endif
let source = [1; 2; 3; 5; 7; 9]
let expected = [(1, [1]); (0, [2]); (1, [3; 5; 7; 9])]
let actual = chunkBy (flip (%) 2) source
CollectionAssert.AreEqual(expected, actual)
#if TEST_TRACE
CollectionAssert.AreEqual (["ChunkBy, list<'T>"], Traces.get())
#endif
1 change: 1 addition & 0 deletions tests/FSharpPlus.Tests/FSharpPlus.Tests.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
<Compile Include="Parsing.fs" />
<Compile Include="Traversals.fs" />
<Compile Include="Indexables.fs" />
<Compile Include="Collections.fs" />
<Compile Include="Validations.fs" />
<Compile Include="Task.fs" />
<Compile Include="ValueTask.fs" />
Expand Down
2 changes: 1 addition & 1 deletion tests/FSharpPlus.Tests/General.fs
Original file line number Diff line number Diff line change
Expand Up @@ -472,7 +472,7 @@ module Functor =
let nel = zip (NonEmptyList.ofList [1; 2]) (NonEmptyList.ofList ["a"; "b"; "c"])
CollectionAssert.AreEqual (NonEmptyList.ofList [1,"a"; 2,"b"], nel)

module Collections =
module Collections2 =

open System.Collections.Concurrent

Expand Down

0 comments on commit bcf934e

Please sign in to comment.