Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add additional methods to itertools #3992

Merged
merged 11 commits into from
Feb 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .release-notes/3992.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
## Add additional methods to `itertools`

Previously there were methods for `Iter` within `itertools` which, while originally planned, where not implemented. These methods have now been implemented.

The added methods are:

+ `dedup` which removes local duplicates from consecutive identical elements via a provided hash function
+ `interleave` which alternates values between two iterators until both run out
+ `interleave_shortest` which alternates values between two iterators until one of them runs out
+ `intersperse` which yields a given value after every `n` elements of the iterator
+ `step_by` which yields every `n`th element of the iterator
+ `unique` which removes global duplicates of identical elements via a provided hash function
84 changes: 84 additions & 0 deletions packages/itertools/_test.pony
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,16 @@ actor \nodoc\ Main is TestList
test(_TestIterCollect)
test(_TestIterCount)
test(_TestIterCycle)
test(_TestIterDedup)
test(_TestIterEnum)
test(_TestIterFilter)
test(_TestIterFilterMap)
test(_TestIterFind)
test(_TestIterFlatMap)
test(_TestIterFold)
test(_TestIterInterleave)
test(_TestIterInterleaveShortest)
test(_TestIterIntersperse)
test(_TestIterLast)
test(_TestIterMap)
test(_TestIterNextOr)
Expand All @@ -28,8 +32,10 @@ actor \nodoc\ Main is TestList
test(_TestIterRun)
test(_TestIterSkip)
test(_TestIterSkipWhile)
test(_TestIterStepBy)
test(_TestIterTake)
test(_TestIterTakeWhile)
test(_TestIterUnique)
test(_TestIterZip)

class \nodoc\ iso _TestIterChain is UnitTest
Expand Down Expand Up @@ -183,6 +189,19 @@ class \nodoc\ iso _TestIterCycle is UnitTest

h.assert_array_eq[String](expected, actual)

class \nodoc\ iso _TestIterDedup is UnitTest
fun name(): String => "itertools/Iter.dedup"

fun apply(h: TestHelper) ? =>
let iter = Iter[USize]([as USize: 1; 1; 2; 3; 3; 2; 2].values()).dedup()
let expected = [as USize: 1; 2; 3; 2]

var i: USize = 0
for v in iter do
h.assert_eq[USize](v, expected(i)?)
i = i + 1
end

class \nodoc\ iso _TestIterEnum is UnitTest
fun name(): String => "itertools/Iter.enum"

Expand Down Expand Up @@ -314,6 +333,45 @@ class \nodoc\ iso _TestIterFold is UnitTest
.fold_partial[I64](0, {(acc, x) ? => error })?
})

class \nodoc\ iso _TestIterInterleave is UnitTest
fun name(): String => "itertools/Iter.interleave"

fun apply(h: TestHelper) ? =>
let iter = Iter[USize](Range(0, 4)).interleave(Range(4, 6))
let expected = [as USize: 0; 4; 1; 5; 2; 3]

var i: USize = 0
for v in iter do
h.assert_eq[USize](v, expected(i)?)
i = i + 1
end

class \nodoc\ iso _TestIterInterleaveShortest is UnitTest
fun name(): String => "itertools/Iter.interleave_shortest"

fun apply(h: TestHelper) ? =>
let iter = Iter[USize](Range(0, 4)).interleave_shortest(Range(4, 6))
let expected = [as USize: 0; 4; 1; 5; 2]

var i: USize = 0
for v in iter do
h.assert_eq[USize](v, expected(i)?)
i = i + 1
end

class \nodoc\ iso _TestIterIntersperse is UnitTest
fun name(): String => "itertools/Iter.intersperse"

fun apply(h: TestHelper) ? =>
let iter = Iter[USize](Range(0, 3)).intersperse(8)
let expected = [as USize: 0; 8; 1; 8; 2]

var i: USize = 0
for v in iter do
h.assert_eq[USize](v, expected(i)?)
i = i + 1
end

class \nodoc\ iso _TestIterLast is UnitTest
fun name(): String => "itertools/Iter.last"

Expand Down Expand Up @@ -438,6 +496,19 @@ class \nodoc\ iso _TestIterSkipWhile is UnitTest
h.assert_eq[I64](-1,
Iter[I64](input.values()).skip_while({(x) => x < -2 }).next()?)

class \nodoc\ iso _TestIterStepBy is UnitTest
fun name(): String => "itertools/Iter.step_by"

fun apply(h: TestHelper) ? =>
let iter = Iter[USize](Range(0, 10)).step_by(2)
let expected = [as USize: 1; 3; 5; 7; 9]

var i: USize = 0
for v in iter do
h.assert_eq[USize](v, expected(i)?)
i = i + 1
end

class \nodoc\ iso _TestIterTake is UnitTest
fun name(): String => "itertools/Iter.take"

Expand Down Expand Up @@ -502,6 +573,19 @@ class \nodoc\ iso _TestIterTakeWhile is UnitTest
h.assert_eq[USize](1, infinite.next()?)
h.assert_false(infinite.has_next())

class \nodoc\ iso _TestIterUnique is UnitTest
fun name(): String => "itertools/Iter.unique"

fun apply(h: TestHelper) ? =>
let iter = Iter[USize]([as USize: 1; 2; 1; 1; 3; 4; 1].values()).unique()
let expected = [as USize: 1; 2; 3; 4]

var i: USize = 0
for v in iter do
h.assert_eq[USize](v, expected(i)?)
i = i + 1
end

class \nodoc\ iso _TestIterZip is UnitTest
fun name(): String => "itertools/Iter.zip"

Expand Down
152 changes: 143 additions & 9 deletions packages/itertools/iter.pony
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,6 @@ class Iter[A] is Iterator[A]
_store_iter.next()?
end)

/*
fun ref dedup[H: HashFunction[A] val = HashIs[A]](): Iter[A!]^ =>
"""
Return an iterator that removes duplicates from consecutive identical
Expand All @@ -369,12 +368,37 @@ class Iter[A] is Iterator[A]
## Example

```pony
Iter[I64]([as I64: 1; 1; 2; 3; 3; 2; 2].values())
Iter[USize]([as USize: 1; 1; 2; 3; 3; 2; 2].values())
.dedup()
```
`1 2 3 2`
"""
*/
Iter[A!](
object is Iterator[A!]
var _prev_value: (A! | None) = None
var _prev_hash: USize = 0

fun ref has_next(): Bool =>
_iter.has_next()

fun ref next(): A! ? =>
let cur_value: A! = _iter.next()?
let cur_hash: USize = H.hash(cur_value)
match _prev_value
| let prev_value: A! =>
if (_prev_hash == cur_hash) and H.eq(prev_value, cur_value) then
this.next()?
else
_prev_value = cur_value
_prev_hash = cur_hash
cur_value
end
| None =>
_prev_value = cur_value
_prev_hash = cur_hash
cur_value
end
end)

fun ref enum[B: (Real[B] val & Number) = USize](): Iter[(B, A)]^ =>
"""
Expand Down Expand Up @@ -530,7 +554,6 @@ class Iter[A] is Iterator[A]
end
acc'

/*
fun ref interleave(other: Iterator[A]): Iter[A!] =>
"""
Return an iterator that alternates the values of the original iterator and
Expand All @@ -544,6 +567,35 @@ class Iter[A] is Iterator[A]
```
`0 4 1 5 2 3`
"""
Iter[A!](
object is Iterator[A!]
var _use_original: Bool = true

fun ref has_next(): Bool =>
_iter.has_next() or other.has_next()

fun ref next(): A! ? =>
// Oscillate between iterators
if _use_original then
_use_original = not _use_original

// _iter might be empty, get next value from other
if _iter.has_next() then
_iter.next()?
else
other.next()?
end
else
_use_original = not _use_original

// other might be empty, get next value from _iter
if other.has_next() then
other.next()?
else
_iter.next()?
end
end
end)

fun ref interleave_shortest(other: Iterator[A]): Iter[A!] =>
"""
Expand All @@ -554,10 +606,34 @@ class Iter[A] is Iterator[A]

```pony
Iter[USize](Range(0, 4))
.interleave(Range(4, 6))
.interleave_shortest(Range(4, 6))
```
`0 4 1 5 2`
"""
Iter[A!](
object is Iterator[A!]
var _use_original: Bool = true

fun ref has_next(): Bool =>
if _use_original then
_iter.has_next()
else
other.has_next()
end

fun ref next(): A! ? =>
if this.has_next() then
if _use_original then
_use_original = not _use_original
_iter.next()?
else
_use_original = not _use_original
other.next()?
end
else
error
end
end)

fun ref intersperse(value: A, n: USize = 1): Iter[A!] =>
"""
Expand All @@ -571,7 +647,23 @@ class Iter[A] is Iterator[A]
```
`0 8 1 8 2`
"""
*/
Iter[A!](
object is Iterator[A!]
var _count: USize = 0
let _v: A = consume value

fun ref has_next(): Bool =>
_iter.has_next()

fun ref next(): A! ? =>
if (_count == n) then
_count = 0
_v
else
_count = _count + 1
_iter.next()?
end
end)

fun ref last(): A ? =>
"""
Expand Down Expand Up @@ -708,6 +800,34 @@ class Iter[A] is Iterator[A]
end
end)

fun ref step_by(n: USize = 1): Iter[A!] =>
"""
Return an iterator that yields every `n`th element of the
original iterator. n == 0 is treated like n == 1 rather than an error.

## Example
```pony
Iter[USize](Range(0, 10))
.step_by(2)
```
`1 3 5 7 9`
"""
Iter[A!](
object is Iterator[A!]
let _step: USize = if n == 0 then 1 else n end

fun ref has_next(): Bool =>
_iter.has_next()

fun ref next(): A! ? =>
var steps_to_burn: USize = _step - 1
while steps_to_burn != 0 do
try _iter.next()? end
steps_to_burn = steps_to_burn - 1
end
_iter.next()?
end)

fun ref take(n: USize): Iter[A]^ =>
"""
Return an iterator for the first n elements.
Expand Down Expand Up @@ -783,7 +903,6 @@ class Iter[A] is Iterator[A]
end
end)

/*
fun ref unique[H: HashFunction[A] val = HashIs[A]](): Iter[A!]^ =>
"""
Return an iterator that filters out elements that have already been
Expand All @@ -792,12 +911,27 @@ class Iter[A] is Iterator[A]
## Example

```pony
Iter[I64]([as I64: 1; 2; 1; 1; 3; 4; 1].values())
Iter[USize]([as USize: 1; 2; 1; 1; 3; 4; 1].values())
.unique()
```
`1 2 3 4`
"""
*/
Iter[A!](
object
let _set: HashSet[A!, H] = HashSet[A!, H]()

fun ref has_next(): Bool =>
_iter.has_next()

fun ref next(): A! ? =>
let v = _iter.next()?
if _set.contains(v) then
next()?
else
_set.set(v)
v
end
end)

fun ref zip[B](i2: Iterator[B]): Iter[(A, B)]^ =>
"""
Expand Down