From d988bee576671c60b0f11e1060b870f9bc850c96 Mon Sep 17 00:00:00 2001 From: Shimon Sharafi Date: Thu, 27 Aug 2026 15:38:34 +0300 Subject: [PATCH] Implement RichIterator and Extra APIs. Complete the Scala-style iterator methods so all training tests pass. Co-authored-by: Cursor --- src/main/java/iterator/Extra.java | 13 +- src/main/java/iterator/RichIterator.java | 476 ++++++++++++++++++++--- 2 files changed, 438 insertions(+), 51 deletions(-) diff --git a/src/main/java/iterator/Extra.java b/src/main/java/iterator/Extra.java index 8d4ae23..c83d8e0 100644 --- a/src/main/java/iterator/Extra.java +++ b/src/main/java/iterator/Extra.java @@ -13,7 +13,7 @@ private Extra() { * @implNote try implement it with only one line of code ! */ public static int sumAges(RichIterator itr) { - throw new NotImplementedException(); + return itr.foldLeft(0, (sum, person) -> sum + person.age); } /** @@ -22,7 +22,7 @@ public static int sumAges(RichIterator itr) { * @implNote try implement it with only one line of code ! */ public static Optional avgAge(RichIterator itr) { - throw new NotImplementedException(); + return Optional.of(itr.foldLeft(Pair.apply(0, 0), (acc, p) -> Pair.apply(acc._1 + p.age, acc._2 + 1))).filter(acc -> acc._2 > 0).map(acc -> acc._1 / acc._2); } /** @@ -40,8 +40,7 @@ public static Optional avgAge(RichIterator itr) { * @implNote try implement it with only one line of code ! */ public static RichIterator> product(Iterable a, Iterable b) { - // TODO: implement this method - throw new NotImplementedException(); + return RichIterator.from(a).flatMap(x -> RichIterator.from(b).map(y -> Pair.apply(x, y))); } /** @@ -50,7 +49,8 @@ public static RichIterator> product(Iterable a, Iterable * @implNote try implement it with only 2 lines of code ! */ public static RichIterator multiplicationBoard() { - throw new NotImplementedException(); + Iterable range = () -> RichIterator.iterate(1, i -> i + 1).take(10); + return product(range, range).map(p -> p._1 * p._2); } /** @@ -62,8 +62,7 @@ public static RichIterator multiplicationBoard() { * @implNote try implement it with only one line of code ! */ public static RichIterator factorial() { - // TODO: implement this method - throw new NotImplementedException(); + return RichIterator.iterate(1, x -> x + 1).scanLeft(1, (acc, n) -> acc * n); } public static class Person { diff --git a/src/main/java/iterator/RichIterator.java b/src/main/java/iterator/RichIterator.java index dfc87f2..36857e2 100644 --- a/src/main/java/iterator/RichIterator.java +++ b/src/main/java/iterator/RichIterator.java @@ -10,14 +10,19 @@ public interface RichIterator extends Iterator { * @return true if there are no more elements, false otherwise */ default boolean isEmpty() { - throw new NotImplementedException(); + return !hasNext(); } /** * @return number of elements */ default int length() { - throw new NotImplementedException(); + int n = 0; + while (hasNext()) { + next(); + n++; + } + return n; } /** @@ -25,14 +30,24 @@ default int length() { * @throws NoSuchElementException if the iterator is empty */ default A last() { - throw new NotImplementedException(); + if (!hasNext()) { + throw new NoSuchElementException(); + } + A last = next(); + while (hasNext()) { + last = next(); + } + return last; } /** * @return the last element if not empty. */ default Optional lastOptional() { - throw new NotImplementedException(); + if (!hasNext()) { + return Optional.empty(); + } + return Optional.of(last()); } /** @@ -40,7 +55,14 @@ default Optional lastOptional() { * @return the index of the element if exists, -1 otherwise */ default int indexOf(A elem) { - throw new NotImplementedException(); + int i = 0; + while (hasNext()) { + if (Objects.equals(next(), elem)) { + return i; + } + i++; + } + return -1; } /** @@ -50,14 +72,23 @@ default int indexOf(A elem) { * @return Optional.of(A) if exists, empty() otherwise. */ default Optional find(Predicate f) { - throw new NotImplementedException(); + while (hasNext()) { + A a = next(); + if (f.test(a)) { + return Optional.of(a); + } + } + return Optional.empty(); } /** * @return Optional.of(next) if exists or empty() otherwise */ default Optional nextOptional() { - throw new NotImplementedException(); + if (!hasNext()) { + return Optional.empty(); + } + return Optional.of(next()); } /** @@ -66,7 +97,9 @@ default Optional nextOptional() { * @param f the Consumer */ default void foreach(Consumer f) { - throw new NotImplementedException(); + while (hasNext()) { + f.accept(next()); + } } /** @@ -74,7 +107,7 @@ default void foreach(Consumer f) { * @return true if exists, false otherwise */ default boolean contains(A elem) { - throw new NotImplementedException(); + return indexOf(elem) != -1; } /** @@ -88,21 +121,23 @@ default boolean contains(A elem) { * @return the collection */ default > C toCollection(Supplier collectionFactory) { - throw new NotImplementedException(); + C collection = collectionFactory.get(); + foreach(collection::add); + return collection; } /** * @return a list built from the iterator's elements */ default List toList() { - throw new NotImplementedException(); + return toCollection(ArrayList::new); } /** * @return a set built from the iterator's elements */ default Set toSet() { - throw new NotImplementedException(); + return toCollection(HashSet::new); } /** @@ -110,8 +145,12 @@ default Set toSet() { * @return true if this and that have the same elements in the same order */ default boolean sameElements(Iterator that) { - // TODO: implement this method - throw new NotImplementedException(); + while (hasNext() && that.hasNext()) { + if (!Objects.equals(next(), that.next())) { + return false; + } + } + return !hasNext() && !that.hasNext(); } // hard @@ -124,8 +163,7 @@ default boolean sameElements(Iterator that) { * @return an iterator that contains only this element */ static RichIterator pure(A elem) { - // TODO: implement this method - throw new NotImplementedException(); + return from(Collections.singletonList(elem)); } /** @@ -136,7 +174,17 @@ static RichIterator pure(A elem) { * @throws NoSuchElementException if the iterator is empty */ default A max(Comparator comparator) { - throw new NotImplementedException(); + if (!hasNext()) { + throw new NoSuchElementException(); + } + A max = next(); + while (hasNext()) { + A a = next(); + if (comparator.compare(a, max) > 0) { + max = a; + } + } + return max; } /** @@ -147,7 +195,17 @@ default A max(Comparator comparator) { * @throws NoSuchElementException if the iterator is empty */ default A min(Comparator comparator) { - throw new NotImplementedException(); + if (!hasNext()) { + throw new NoSuchElementException(); + } + A min = next(); + while (hasNext()) { + A a = next(); + if (comparator.compare(a, min) < 0) { + min = a; + } + } + return min; } /** @@ -158,7 +216,18 @@ default A min(Comparator comparator) { * @return a new Iterator. */ default RichIterator map(Function f) { - throw new NotImplementedException(); + final Iterator self = this; + return from(new Iterator() { + @Override + public boolean hasNext() { + return self.hasNext(); + } + + @Override + public B next() { + return f.apply(self.next()); + } + }); } /** @@ -169,7 +238,20 @@ default RichIterator map(Function f) { * output (printed): 112233 */ default RichIterator tapEach(Consumer f) { - throw new NotImplementedException(); + final Iterator self = this; + return from(new Iterator() { + @Override + public boolean hasNext() { + return self.hasNext(); + } + + @Override + public A next() { + A a = self.next(); + f.accept(a); + return a; + } + }); } /** @@ -184,7 +266,10 @@ default RichIterator tapEach(Consumer f) { * RichIterator.apply(1).reduce((total, a) -> total + a) // 1 */ default A reduce(BiFunction acc) { - throw new NotImplementedException(); + if (!hasNext()) { + throw new NoSuchElementException(); + } + return foldLeft(next(), acc); } /** @@ -194,7 +279,10 @@ default A reduce(BiFunction acc) { * @return // see reduce */ default Optional reduceOptional(BiFunction acc) { - throw new NotImplementedException(); + if (!hasNext()) { + return Optional.empty(); + } + return Optional.of(reduce(acc)); } /** @@ -211,7 +299,7 @@ default Optional reduceOptional(BiFunction * RichIterator().mkString("[", ";","]") // [] */ default String mkString(String prefix, String delimiter, String suffix) { - throw new NotImplementedException(); + return prefix + map(String::valueOf).reduceOptional((a, b) -> a + delimiter + b).orElse("") + suffix; } /** @@ -224,7 +312,7 @@ default String mkString(String prefix, String delimiter, String suffix) { * @return a String */ default String mkString() { - throw new NotImplementedException(); + return mkString("RichIterator(", ",", ")"); } /** @@ -236,7 +324,7 @@ default String mkString() { * RichIterator.apply(1,2,3,4).append(5) // 1,2,3,4,5 */ default RichIterator append(A elem) { - throw new NotImplementedException(); + return appendAll(pure(elem)); } /** @@ -248,7 +336,21 @@ default RichIterator append(A elem) { * RichIterator.apply(1,2,3,4).appendAll(Arrays.asList(5,6,7,8)) // 1,2,3,4,5,6,7,8 */ default RichIterator appendAll(Iterator elems) { - throw new NotImplementedException(); + final Iterator self = this; + return from(new Iterator() { + @Override + public boolean hasNext() { + return self.hasNext() || elems.hasNext(); + } + + @Override + public A next() { + if (self.hasNext()) { + return self.next(); + } + return elems.next(); + } + }); } /** @@ -260,7 +362,7 @@ default RichIterator appendAll(Iterator elems) { * RichIterator.apply(1,2,3,4).prepend(0) // 0,1,2,3,4 */ default RichIterator prepend(A elem) { - throw new NotImplementedException(); + return prependAll(pure(elem)); } /** @@ -272,7 +374,21 @@ default RichIterator prepend(A elem) { * RichIterator.apply(4,5,6,7).prependAll(Arrays.asList(1,2,3)) // 1,2,3,4,5,6,7 */ default RichIterator prependAll(Iterator elems) { - throw new NotImplementedException(); + final Iterator self = this; + return from(new Iterator() { + @Override + public boolean hasNext() { + return elems.hasNext() || self.hasNext(); + } + + @Override + public A next() { + if (elems.hasNext()) { + return elems.next(); + } + return self.next(); + } + }); } /** @@ -285,7 +401,29 @@ default RichIterator prependAll(Iterator elems) { * RichIterator.apply(1,2,3,4,5,1,2,3,4,5).drop(20) // empty() */ default RichIterator drop(int n) { - throw new NotImplementedException(); + final Iterator self = this; + return from(new Iterator() { + int remaining = n; + + private void skip() { + while (remaining > 0 && self.hasNext()) { + self.next(); + remaining--; + } + } + + @Override + public boolean hasNext() { + skip(); + return self.hasNext(); + } + + @Override + public A next() { + skip(); + return self.next(); + } + }); } /** @@ -298,7 +436,24 @@ default RichIterator drop(int n) { * RichIterator.apply(1,2,3,4,5).take(20) // 1,2,3,4,5 */ default RichIterator take(int n) { - throw new NotImplementedException(); + final Iterator self = this; + return from(new Iterator() { + int left = n; + + @Override + public boolean hasNext() { + return left > 0 && self.hasNext(); + } + + @Override + public A next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + left--; + return self.next(); + } + }); } /** @@ -314,7 +469,11 @@ default RichIterator take(int n) { * empty().foldLeft(0, (x,y) -> x + y) // 0 */ default B foldLeft(B zero, BiFunction acc) { - throw new NotImplementedException(); + B result = zero; + while (hasNext()) { + result = acc.apply(result, next()); + } + return result; } /** @@ -326,7 +485,29 @@ default B foldLeft(B zero, BiFunction acc * Iterator(1,2,3,4,5).scanLeft(0, Integer::sum) // Iterator(0,1,3,6,10,15) */ default RichIterator scanLeft(B zero, BiFunction acc) { - throw new NotImplementedException(); + final Iterator self = this; + return from(new Iterator() { + B current = zero; + boolean started = false; + + @Override + public boolean hasNext() { + return !started || self.hasNext(); + } + + @Override + public B next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + if (!started) { + started = true; + return current; + } + current = acc.apply(current, self.next()); + return current; + } + }); } /** @@ -345,7 +526,25 @@ default RichIterator scanLeft(B zero, BiFunction RichIterator iterate(A first, Function progress) { - throw new NotImplementedException(); + return from(new Iterator() { + A current = first; + boolean started = false; + + @Override + public boolean hasNext() { + return true; + } + + @Override + public A next() { + if (!started) { + started = true; + return current; + } + current = progress.apply(current); + return current; + } + }); } /** @@ -359,7 +558,21 @@ static RichIterator iterate(A first, Function pro * @return zipped iterator */ default RichIterator> zip(Iterator that) { - throw new NotImplementedException(); + final Iterator self = this; + return from(new Iterator>() { + @Override + public boolean hasNext() { + return self.hasNext() && that.hasNext(); + } + + @Override + public Pair next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + return Pair.apply(self.next(), that.next()); + } + }); } /** @@ -371,7 +584,7 @@ default RichIterator> zip(Iterator that) { * @return the zipped iterator */ default RichIterator> zipWithIndex() { - throw new NotImplementedException(); + return zip(iterate(0, i -> i + 1)); } /** @@ -383,7 +596,12 @@ default RichIterator> zipWithIndex() { * @return a map */ default Map toMap(Function> asPair) { - throw new NotImplementedException(); + Map map = new HashMap<>(); + foreach(a -> { + Pair pair = asPair.apply(a); + map.put(pair._1, pair._2); + }); + return map; } // very hard @@ -397,7 +615,43 @@ default Map toMap(Function> asPair) * RichIterator.apply(1,2,3,4,5,1,2,3,4,5).takeWhile(x -> x <= 3) // 1,2,3 */ default RichIterator takeWhile(Predicate predicate) { - throw new NotImplementedException(); + final Iterator self = this; + return from(new Iterator() { + A buffered; + boolean hasBuffered = false; + boolean done = false; + + @Override + public boolean hasNext() { + if (done) { + return false; + } + if (hasBuffered) { + return true; + } + if (!self.hasNext()) { + done = true; + return false; + } + A a = self.next(); + if (predicate.test(a)) { + buffered = a; + hasBuffered = true; + return true; + } + done = true; + return false; + } + + @Override + public A next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + hasBuffered = false; + return buffered; + } + }); } /** @@ -409,7 +663,7 @@ default RichIterator takeWhile(Predicate predicate) { * RichIterator.apply(1,2,3,4,5,1,2,3,4,5).takeUntil(x -> x > 3) // 1,2,3 */ default RichIterator takeUntil(Predicate predicate) { - throw new NotImplementedException(); + return takeWhile(predicate.negate()); } /** @@ -421,7 +675,45 @@ default RichIterator takeUntil(Predicate predicate) { * RichIterator.apply(1,2,3,4,5,1,2,3,4,5).dropWhile(x -> x <= 3) // 4,5,1,2,3,4,5 */ default RichIterator dropWhile(Predicate predicate) { - throw new NotImplementedException(); + final Iterator self = this; + return from(new Iterator() { + boolean skipped = false; + boolean hasBuffered = false; + A buffered; + + private void skip() { + if (skipped) { + return; + } + skipped = true; + while (self.hasNext()) { + A a = self.next(); + if (!predicate.test(a)) { + buffered = a; + hasBuffered = true; + return; + } + } + } + + @Override + public boolean hasNext() { + skip(); + return hasBuffered || self.hasNext(); + } + + @Override + public A next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + if (hasBuffered) { + hasBuffered = false; + return buffered; + } + return self.next(); + } + }); } /** @@ -433,7 +725,7 @@ default RichIterator dropWhile(Predicate predicate) { * RichIterator.apply(1,2,3,4,5,1,2,3,4,5).dropUntil(x -> x >= 3) // 4,5,1,2,3,4,5 */ default RichIterator dropUntil(Predicate predicate) { - throw new NotImplementedException(); + return dropWhile(predicate.negate()); } /** @@ -452,7 +744,51 @@ default RichIterator dropUntil(Predicate predicate) { * @return the iterator */ default BufferedIterator buffered() { - throw new NotImplementedException(); + final Iterator self = this; + return new BufferedIterator() { + private A head; + private boolean hasHead = false; + + private void fill() { + if (!hasHead && self.hasNext()) { + head = self.next(); + hasHead = true; + } + } + + @Override + public A head() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + return head; + } + + @Override + public Optional headOptional() { + if (!hasNext()) { + return Optional.empty(); + } + return Optional.of(head()); + } + + @Override + public boolean hasNext() { + fill(); + return hasHead; + } + + @Override + public A next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + hasHead = false; + A result = head; + head = null; + return result; + } + }; } /** @@ -462,7 +798,8 @@ default BufferedIterator buffered() { * e.g. Iterator(1,2,3,3,2,1,4).distinct() // Iterator(1,2,3,4) */ default RichIterator distinct() { - throw new NotImplementedException(); + Set seen = new HashSet<>(); + return filter(seen::add); } /** @@ -477,7 +814,29 @@ default RichIterator distinct() { * @return the iterator */ default RichIterator flatMap(Function> f) { - throw new NotImplementedException(); + final Iterator self = this; + return from(new Iterator() { + Iterator current = Collections.emptyIterator(); + + @Override + public boolean hasNext() { + while (!current.hasNext() && self.hasNext()) { + current = f.apply(self.next()); + if (current == null) { + current = Collections.emptyIterator(); + } + } + return current.hasNext(); + } + + @Override + public B next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + return current.next(); + } + }); } /** @@ -492,7 +851,36 @@ default RichIterator flatMap(Function> f * @return an iterator */ default RichIterator filter(Predicate f) { - throw new NotImplementedException(); + final Iterator self = this; + return from(new Iterator() { + A buffered; + boolean hasBuffered = false; + + @Override + public boolean hasNext() { + if (hasBuffered) { + return true; + } + while (self.hasNext()) { + A a = self.next(); + if (f.test(a)) { + buffered = a; + hasBuffered = true; + return true; + } + } + return false; + } + + @Override + public A next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + hasBuffered = false; + return buffered; + } + }); } // end