Skip to content

Commit bcf934e

Browse files
authored
Add List.chunkBy (#561)
1 parent a5a7c84 commit bcf934e

5 files changed

Lines changed: 73 additions & 2 deletions

File tree

src/FSharpPlus/Control/Collection.fs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,11 @@ type GroupBy =
289289
type ChunkBy =
290290
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)
291291
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)
292-
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
292+
static member ChunkBy (x: list<'T>, f: 'T->'Key, _: list<'Key*list<'T>>, [<Optional>]_impl: ChunkBy) =
293+
#if TEST_TRACE
294+
Traces.add "ChunkBy, list<'T>"
295+
#endif
296+
List.chunkBy f x
293297
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
294298

295299
static member inline Invoke (projection: 'T->'Key) (source: '``Collection<'T>``) : '``Collection<'Key * 'Collection<'T>>`` =

src/FSharpPlus/Extensions/List.fs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,48 @@ module List =
340340
loop (ls,rs)
341341
loop (list1, list2)
342342
#endif
343+
344+
/// <summary>
345+
/// Chunks the list up into groups with the same projected key by applying
346+
/// the key-generating projection function to each element and yielding a list of
347+
/// keys tupled with values.
348+
/// </summary>
349+
///
350+
/// <remarks>
351+
/// Each key is tupled with an array of all adjacent elements that match
352+
/// to the key, therefore keys are not unique but can't be adjacent
353+
/// as each time the key changes a new group is yield.
354+
///
355+
/// The ordering of the original list is respected.
356+
/// </remarks>
357+
///
358+
/// <param name="projection">A function that transforms an element of the list into a comparable key.</param>
359+
/// <param name="source">The input list.</param>
360+
///
361+
/// <returns>The resulting list of keys tupled with a list of matching values</returns>
362+
let chunkBy (projection: 'T -> 'Key) (source: _ list) =
363+
#if FABLE_COMPILER
364+
Seq.chunkBy projection source |> Seq.map (fun (x, y) -> x, Seq.toList y) |> Seq.toList
365+
#else
366+
match source with
367+
| [] -> []
368+
| x::xs ->
369+
let mutable acc = new ListCollector<_> ()
370+
let mutable members = new ListCollector<_> ()
371+
let rec loop source g =
372+
match source with
373+
| [] -> acc.Add (g, members.Close ())
374+
| x::xs ->
375+
let key = projection x
376+
if g <> key then
377+
acc.Add (g, members.Close ())
378+
members <- new ListCollector<_> ()
379+
members.Add x
380+
loop xs key
381+
members.Add x
382+
loop xs (projection x)
383+
acc.Close ()
384+
#endif
343385

344386
/// <summary>Same as choose but with access to the index.</summary>
345387
/// <param name="mapping">The mapping function, taking index and element as parameters.</param>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
namespace FSharpPlus.Tests
2+
3+
open FSharpPlus
4+
open FSharpPlus.Data
5+
open NUnit.Framework
6+
7+
#if TEST_TRACE
8+
open FSharpPlus.Internals
9+
#endif
10+
11+
module Collections =
12+
13+
[<Test>]
14+
let chunkBy () =
15+
#if TEST_TRACE
16+
Traces.reset()
17+
#endif
18+
let source = [1; 2; 3; 5; 7; 9]
19+
let expected = [(1, [1]); (0, [2]); (1, [3; 5; 7; 9])]
20+
let actual = chunkBy (flip (%) 2) source
21+
CollectionAssert.AreEqual(expected, actual)
22+
#if TEST_TRACE
23+
CollectionAssert.AreEqual (["ChunkBy, list<'T>"], Traces.get())
24+
#endif

tests/FSharpPlus.Tests/FSharpPlus.Tests.fsproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
<Compile Include="Parsing.fs" />
2424
<Compile Include="Traversals.fs" />
2525
<Compile Include="Indexables.fs" />
26+
<Compile Include="Collections.fs" />
2627
<Compile Include="Validations.fs" />
2728
<Compile Include="Task.fs" />
2829
<Compile Include="ValueTask.fs" />

tests/FSharpPlus.Tests/General.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -472,7 +472,7 @@ module Functor =
472472
let nel = zip (NonEmptyList.ofList [1; 2]) (NonEmptyList.ofList ["a"; "b"; "c"])
473473
CollectionAssert.AreEqual (NonEmptyList.ofList [1,"a"; 2,"b"], nel)
474474

475-
module Collections =
475+
module Collections2 =
476476

477477
open System.Collections.Concurrent
478478

0 commit comments

Comments
 (0)