From 545e66b573994ba64a5644cd17bb729ec1039417 Mon Sep 17 00:00:00 2001 From: jmagaram Date: Sun, 23 Apr 2023 17:27:12 -0700 Subject: [PATCH] Seq.sortBy --- CHANGELOG.md | 4 +++- SEQ_README.md | 1 + package.json | 2 +- src/Extras__Seq.res | 7 ++++++ src/Extras__Seq.resi | 8 +++++++ tests/Extras__SeqTests.res | 45 +++++++++++++++++++++++++++++++++++++- 6 files changed, 64 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ed495e..4f3bda5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,9 @@ -## Version 1.0.1 +## Version 1.1.0 - Fix bug in `allPairs` where sequences are not cached. - `Seq.reverse` +- `Seq.sortBy` +- `Seq.delay` ## Version 1.0.0 diff --git a/SEQ_README.md b/SEQ_README.md index b2c4d16..137be38 100644 --- a/SEQ_README.md +++ b/SEQ_README.md @@ -78,6 +78,7 @@ let pairwise: t<'a> => t<('a, 'a)> let reverse: t<'a> => t<'a> let scan: (t<'a>, 'b, ('b, 'a) => 'b) => t<'b> let scani: (t<'a>, ~zero: 'b, (~sum: 'b, ~val: 'a, ~inx: int) => 'b) => t<'b> +let sortBy: (t<'a>, ('a, 'a) => int) => t<'a> let takeAtMost: (t<'a>, int) => t<'a> let takeUntil: (t<'a>, 'a => bool) => t<'a> let takeWhile: (t<'a>, 'a => bool) => t<'a> diff --git a/package.json b/package.json index f3dadf5..5df87c3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@jmagaram/rescript-extras", - "version": "1.0.1", + "version": "1.1.0", "module": "", "description": "Useful general-purpose utility functions and modules for ReScript projects.", "keywords": [ diff --git a/src/Extras__Seq.res b/src/Extras__Seq.res index aa43784..98809f6 100644 --- a/src/Extras__Seq.res +++ b/src/Extras__Seq.res @@ -706,3 +706,10 @@ let reverse = xx => | _ => xx->fromArray(~start=xx->Js.Array2.length - 1, ~end=0) } }) + +let sortBy = (xx, compare) => + delay(() => { + let xx = xx->toArray + xx->Js.Array2.sortInPlaceWith(compare)->ignore + xx->fromArray + }) diff --git a/src/Extras__Seq.resi b/src/Extras__Seq.resi index 14c32bf..e166b8d 100644 --- a/src/Extras__Seq.resi +++ b/src/Extras__Seq.resi @@ -646,3 +646,11 @@ let reverse: t<'a> => t<'a> sequence. */ let delay: (unit => t<'a>) => t<'a> + +/** +`sortBy(source, compare)` consumes the entire `source` sequence as soon as that +sequence is iterated. As such it should not be used with large or infinite +sequences. The function uses a stable sort (the original order of equal elements +is preserved) based on the provided `compare` function. +*/ +let sortBy: (t<'a>, ('a, 'a) => int) => t<'a> diff --git a/tests/Extras__SeqTests.res b/tests/Extras__SeqTests.res index afa5ff3..5ffbb84 100644 --- a/tests/Extras__SeqTests.res +++ b/tests/Extras__SeqTests.res @@ -6,6 +6,7 @@ module Option = Belt.Option module Result = Belt.Result let intToString = Belt.Int.toString +let intCompare = Ex.Cmp.int let concatInts = xs => xs->Js.Array2.length == 0 ? "_" : xs->Js.Array2.map(intToString)->Js.Array2.joinWith("") @@ -1578,13 +1579,54 @@ let orElseTests = makeSeqEqualsTests( ], ) +let sortByTests = makeSeqEqualsTests( + ~title="sortBy", + [ + (S.empty->S.sortBy(intCompare), [], ""), + (S.singleton(1)->S.sortBy(intCompare), [1], ""), + ([1, 5, 2, 9, 7, 3]->S.fromArray->S.sortBy(intCompare), [1, 2, 3, 5, 7, 9], ""), + ], +)->Js.Array2.concat([ + T.make(~category="Seq", ~title="sortBy", ~expectation="completely lazy", ~predicate=() => { + S.repeatWith(3, () => {Js.Exn.raiseError("boom!")}) + ->S.sortBy(intCompare) + ->S.takeAtMost(0) + ->S.consume + ->ignore + true + }), + T.make(~category="Seq", ~title="sortBy", ~expectation="stable", ~predicate=() => { + let sortByFirst = (a, b) => { + let (afst, _) = a + let (bfst, _) = b + intCompare(afst, bfst) + } + let data = [ + (2, "x"), + (1, "x"), + (2, "y"), + (4, "x"), + (3, "x"), + (1, "y"), + (3, "y"), + (2, "z"), + (4, "y"), + (1, "z"), + (3, "z"), + (4, "z"), + ] + let sorted = data->S.fromArray->S.sortBy(sortByFirst)->S.map(((_, letter)) => letter)->S.toArray + sorted == "xyzxyzxyzxyz"->Js.String2.split("") + }), +]) + let delayTests = makeSeqEqualsTests( ~title="delay", [ (S.delay(() => S.empty), [], ""), (S.delay(() => S.singleton(1)), [1], ""), (S.delay(() => oneToFive), [1, 2, 3, 4, 5], ""), - (S.delay(() => Js.Exn.raiseError("Boom!"))->S.takeAtMost(0), [], ""), + (S.delay(() => Js.Exn.raiseError("boom!"))->S.takeAtMost(0), [], ""), ], ) @@ -1739,6 +1781,7 @@ let tests = sampleZipLongest, scanTests, someTests, + sortByTests, sortedMergeTests, takeAtMostTests, takeUntilTests,