From 84a0285cc7b4d9a90565a5b30fe0fcdd4e356217 Mon Sep 17 00:00:00 2001 From: corem Date: Mon, 16 Dec 2024 16:13:43 -0500 Subject: [PATCH 1/5] FlowOps: debounce operator --- core/src/main/scala/ox/flow/FlowOps.scala | 10 ++++++++ .../scala/ox/flow/FlowOpsDebounceTest.scala | 25 +++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 core/src/test/scala/ox/flow/FlowOpsDebounceTest.scala diff --git a/core/src/main/scala/ox/flow/FlowOps.scala b/core/src/main/scala/ox/flow/FlowOps.scala index 64b5b6c8..99bb61f8 100644 --- a/core/src/main/scala/ox/flow/FlowOps.scala +++ b/core/src/main/scala/ox/flow/FlowOps.scala @@ -86,6 +86,16 @@ class FlowOps[+T]: if n != 0 && sampleCounter % n == 0 then emit(t) ) + /** Remove subsequent, repeating elements + */ + def debounce: Flow[T] = Flow.usingEmitInline: emit => + var previousElement: Option[T] = None + last.run( + FlowEmit.fromInline: t => + if !previousElement.contains(t) then emit(t) + previousElement = Some(t) + ) + /** Applies the given mapping function `f` to each element emitted by this flow, for which the function is defined, and emits the result. * If `f` is not defined at an element, the element will be skipped. * diff --git a/core/src/test/scala/ox/flow/FlowOpsDebounceTest.scala b/core/src/test/scala/ox/flow/FlowOpsDebounceTest.scala new file mode 100644 index 00000000..b5eb75b3 --- /dev/null +++ b/core/src/test/scala/ox/flow/FlowOpsDebounceTest.scala @@ -0,0 +1,25 @@ +package ox.flow + +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers +import ox.* + +class FlowOpsDebounceTest extends AnyFlatSpec with Matchers: + behavior of "debounce" + + it should "not debounce if applied on an empty flow" in: + val c = Flow.empty[Int] + val s = c.debounce + s.runToList() shouldBe List.empty + + it should "not debounce if applied on a flow containing only distinct values" in: + val c = Flow.fromValues(1 to 10: _*) + val s = c.debounce + s.runToList() shouldBe (1 to 10) + + it should "debounce if applied on a flow containing repeating elements" in: + val c = Flow.fromValues(1, 1, 2, 3, 4, 4, 5) + val s = c.debounce + s.runToList() shouldBe (1 to 5) + +end FlowOpsDebounceTest From 6e9ce3d53e7e17f9a3d172ca593a99a099224b8a Mon Sep 17 00:00:00 2001 From: corem Date: Mon, 16 Dec 2024 17:02:13 -0500 Subject: [PATCH 2/5] FlowOps: debounceBy operator --- core/src/main/scala/ox/flow/FlowOps.scala | 17 ++++++++--- .../scala/ox/flow/FlowOpsDebounceByTest.scala | 30 +++++++++++++++++++ .../scala/ox/flow/FlowOpsDebounceTest.scala | 5 ++++ 3 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 core/src/test/scala/ox/flow/FlowOpsDebounceByTest.scala diff --git a/core/src/main/scala/ox/flow/FlowOps.scala b/core/src/main/scala/ox/flow/FlowOps.scala index 99bb61f8..a3d8ea8d 100644 --- a/core/src/main/scala/ox/flow/FlowOps.scala +++ b/core/src/main/scala/ox/flow/FlowOps.scala @@ -88,12 +88,21 @@ class FlowOps[+T]: /** Remove subsequent, repeating elements */ - def debounce: Flow[T] = Flow.usingEmitInline: emit => - var previousElement: Option[T] = None + def debounce: Flow[T] = + debounceBy(T => T) + + /** Remove subsequent, repeating elements matching 'f' + * + * @param f + * The function used to compare the previous and current element + */ + def debounceBy[U](f: T => U): Flow[T] = Flow.usingEmitInline: emit => + var previousElement: Option[U] = None last.run( FlowEmit.fromInline: t => - if !previousElement.contains(t) then emit(t) - previousElement = Some(t) + val currentElement = f(t) + if !previousElement.contains(currentElement) then emit(t) + previousElement = Some(currentElement) ) /** Applies the given mapping function `f` to each element emitted by this flow, for which the function is defined, and emits the result. diff --git a/core/src/test/scala/ox/flow/FlowOpsDebounceByTest.scala b/core/src/test/scala/ox/flow/FlowOpsDebounceByTest.scala new file mode 100644 index 00000000..b03f5397 --- /dev/null +++ b/core/src/test/scala/ox/flow/FlowOpsDebounceByTest.scala @@ -0,0 +1,30 @@ +package ox.flow + +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers +import ox.* + +class FlowOpsDebounceByTest extends AnyFlatSpec with Matchers: + behavior of "debounceBy" + + it should "not debounce if applied on an empty flow" in: + val c = Flow.empty[Int] + val s = c.debounceBy(_ * 2) + s.runToList() shouldBe List.empty + + it should "not debounce if applied on a flow containing only distinct values" in: + val c = Flow.fromValues(1 to 10: _*) + val s = c.debounceBy(_ * 2) + s.runToList() shouldBe (1 to 10) + + it should "debounce if applied on a flow containing repeating elements" in: + val c = Flow.fromValues(1, 1, 2, 3, 4, 4, 5) + val s = c.debounceBy(_ * 2) + s.runToList() shouldBe (1 to 5) + + it should "debounce subsequent odd/prime numbers" in: + val c = Flow.fromValues(1, 1, 1, 2, 4, 3, 7, 4, 5) + val s = c.debounceBy(_ % 2 == 0) + s.runToList() shouldBe List(1, 2, 3, 4, 5) + +end FlowOpsDebounceByTest diff --git a/core/src/test/scala/ox/flow/FlowOpsDebounceTest.scala b/core/src/test/scala/ox/flow/FlowOpsDebounceTest.scala index b5eb75b3..b80939c6 100644 --- a/core/src/test/scala/ox/flow/FlowOpsDebounceTest.scala +++ b/core/src/test/scala/ox/flow/FlowOpsDebounceTest.scala @@ -17,6 +17,11 @@ class FlowOpsDebounceTest extends AnyFlatSpec with Matchers: val s = c.debounce s.runToList() shouldBe (1 to 10) + it should "debounce if applied on a flow containing only repeating values" in: + val c = Flow.fromValues(1, 1, 1, 1, 1) + val s = c.debounce + s.runToList() shouldBe List(1) + it should "debounce if applied on a flow containing repeating elements" in: val c = Flow.fromValues(1, 1, 2, 3, 4, 4, 5) val s = c.debounce From 3c719b5836fe99cb860c0a52b5e6dc8436fe443f Mon Sep 17 00:00:00 2001 From: corem Date: Mon, 16 Dec 2024 17:04:26 -0500 Subject: [PATCH 3/5] FlowOps: debounceBy operator --- core/src/test/scala/ox/flow/FlowOpsDebounceByTest.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/test/scala/ox/flow/FlowOpsDebounceByTest.scala b/core/src/test/scala/ox/flow/FlowOpsDebounceByTest.scala index b03f5397..e6f6695d 100644 --- a/core/src/test/scala/ox/flow/FlowOpsDebounceByTest.scala +++ b/core/src/test/scala/ox/flow/FlowOpsDebounceByTest.scala @@ -12,12 +12,12 @@ class FlowOpsDebounceByTest extends AnyFlatSpec with Matchers: val s = c.debounceBy(_ * 2) s.runToList() shouldBe List.empty - it should "not debounce if applied on a flow containing only distinct values" in: + it should "not debounce if applied on a flow containing only distinct f(value)" in: val c = Flow.fromValues(1 to 10: _*) val s = c.debounceBy(_ * 2) s.runToList() shouldBe (1 to 10) - it should "debounce if applied on a flow containing repeating elements" in: + it should "debounce if applied on a flow containing repeating f(value)" in: val c = Flow.fromValues(1, 1, 2, 3, 4, 4, 5) val s = c.debounceBy(_ * 2) s.runToList() shouldBe (1 to 5) From 0a65538d294ae32a8585cf8329e0ed96d4b1c8af Mon Sep 17 00:00:00 2001 From: corem Date: Mon, 16 Dec 2024 17:12:11 -0500 Subject: [PATCH 4/5] FlowOps: debounceBy operator --- core/src/main/scala/ox/flow/FlowOps.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/ox/flow/FlowOps.scala b/core/src/main/scala/ox/flow/FlowOps.scala index a3d8ea8d..b9230ce0 100644 --- a/core/src/main/scala/ox/flow/FlowOps.scala +++ b/core/src/main/scala/ox/flow/FlowOps.scala @@ -89,7 +89,7 @@ class FlowOps[+T]: /** Remove subsequent, repeating elements */ def debounce: Flow[T] = - debounceBy(T => T) + debounceBy(identity) /** Remove subsequent, repeating elements matching 'f' * From 33cd9fb44bc624db00ebc365ac5c128c82d3bef7 Mon Sep 17 00:00:00 2001 From: corem Date: Mon, 16 Dec 2024 18:00:07 -0500 Subject: [PATCH 5/5] FlowOps: debounceBy operator --- core/src/main/scala/ox/flow/FlowOps.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/ox/flow/FlowOps.scala b/core/src/main/scala/ox/flow/FlowOps.scala index b9230ce0..e677d3ae 100644 --- a/core/src/main/scala/ox/flow/FlowOps.scala +++ b/core/src/main/scala/ox/flow/FlowOps.scala @@ -94,7 +94,7 @@ class FlowOps[+T]: /** Remove subsequent, repeating elements matching 'f' * * @param f - * The function used to compare the previous and current element + * The function used to compare the previous and current elements */ def debounceBy[U](f: T => U): Flow[T] = Flow.usingEmitInline: emit => var previousElement: Option[U] = None