Skip to content

Commit 48b26b5

Browse files
rishabhdaimRishabh Kumar
andauthored
OAK-11531 : added util method to replace Guava's Iterables.mergeSorted (#2122)
* OAK-11531 : added util method to replace Guava's Iterables.mergeSorted * OAK-11531 : reverted pom.xml change * OAK-11531 : added null checks for IterableUtils.mergeSorted --------- Co-authored-by: Rishabh Kumar <diam@adobe.com>
1 parent 3509f70 commit 48b26b5

File tree

4 files changed

+292
-0
lines changed

4 files changed

+292
-0
lines changed

oak-commons/src/main/java/org/apache/jackrabbit/oak/commons/collections/IterableUtils.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import java.lang.reflect.Array;
2727
import java.util.ArrayList;
2828
import java.util.Collection;
29+
import java.util.Comparator;
2930
import java.util.Iterator;
3031
import java.util.List;
3132
import java.util.NoSuchElementException;
@@ -303,4 +304,20 @@ public static <E> Iterable<E> filter(final Iterable<?> itr, final Class<E> type)
303304
public static <I, O> Iterable<O> transform(final Iterable<I> iterable, final Function<? super I, ? extends O> function) {
304305
return org.apache.commons.collections4.IterableUtils.transformedIterable(iterable, function::apply);
305306
}
307+
308+
/**
309+
* Merges multiple sorted iterables into a single sorted iterable.
310+
*
311+
* @param <T> the type of elements returned by this iterable
312+
* @param iterables the iterables to merge, must not be null
313+
* @param c the c to determine the order of elements, must not be null
314+
* @return an iterable that merges the input iterables in sorted order
315+
* @throws NullPointerException if the iterables or c are null
316+
*/
317+
public static <T> Iterable<T> mergeSorted(final Iterable<? extends Iterable<? extends T>> iterables, final Comparator<? super T> c) {
318+
Objects.requireNonNull(iterables, "Iterables must not be null.");
319+
Objects.requireNonNull(c, "Comparator must not be null.");
320+
final Iterable<T> iterable = () -> IteratorUtils.mergeSorted(org.apache.commons.collections4.IterableUtils.transformedIterable(iterables, Iterable::iterator), c);
321+
return org.apache.commons.collections4.IterableUtils.unmodifiableIterable(iterable);
322+
}
306323
}

oak-commons/src/main/java/org/apache/jackrabbit/oak/commons/collections/IteratorUtils.java

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,15 @@
1818
*/
1919
package org.apache.jackrabbit.oak.commons.collections;
2020

21+
import org.apache.commons.collections4.iterators.PeekingIterator;
2122
import org.jetbrains.annotations.NotNull;
2223

24+
import java.util.Comparator;
2325
import java.util.Iterator;
26+
import java.util.NoSuchElementException;
2427
import java.util.Objects;
28+
import java.util.PriorityQueue;
29+
import java.util.Queue;
2530

2631
/**
2732
* Utility methods for {@link Iterator} conversions.
@@ -61,4 +66,82 @@ public static <T> Iterable<T> toIterable(@NotNull final Iterator<T> iterator) {
6166
}
6267
};
6368
}
69+
70+
/**
71+
* Merges multiple sorted iterators into a single sorted iterator. Equivalent entries will not be de-duplicated.
72+
* <p>
73+
* This method assumes that the input iterators are sorted in increasing order.
74+
*
75+
* @param <T> the type of elements returned by this iterator
76+
* @param itrs the iterators to merge, must not be null
77+
* @param c the comparator to determine the order of elements, must not be null
78+
* @return an iterator that merges the input iterators in sorted order
79+
* @throws NullPointerException if the iterators or comparator are null
80+
*/
81+
public static <T> Iterator<T> mergeSorted(final Iterable<? extends Iterator<? extends T>> itrs, final Comparator<? super T> c) {
82+
Objects.requireNonNull(itrs, "Iterators must not be null");
83+
Objects.requireNonNull(c, "Comparator must not be null");
84+
return org.apache.commons.collections4.IteratorUtils.unmodifiableIterator(new MergingIterator<>(itrs, c));
85+
}
86+
87+
/**
88+
* An iterator that merges multiple sorted iterators into a single sorted iterator.
89+
* <p>This iterator assumes that the input iterators are sorted in increasing order.
90+
* Equivalent entries will not be de-duplicated.
91+
*
92+
* @param <T> the type of elements returned by this iterator
93+
*/
94+
private static class MergingIterator<T> implements Iterator<T> {
95+
final Queue<PeekingIterator<T>> queue;
96+
97+
/**
98+
* Constructs a new MergingIterator.
99+
*
100+
* @param itrs the iterators to merge, must not be null
101+
* @param c the comparator to determine the order of elements, must not be null
102+
* @throws NullPointerException if the iterators or comparator are null
103+
*/
104+
public MergingIterator(final Iterable<? extends Iterator<? extends T>> itrs, final Comparator<? super T> c) {
105+
Comparator<PeekingIterator<T>> heapComparator = Comparator.comparing(PeekingIterator::peek, c);
106+
107+
this.queue = new PriorityQueue<>(heapComparator);
108+
109+
for (Iterator<? extends T> itr : itrs) {
110+
if (itr.hasNext()) {
111+
// only add those iterator which have elements
112+
this.queue.add(PeekingIterator.peekingIterator(itr));
113+
}
114+
}
115+
}
116+
117+
/**
118+
* Returns {@code true} if the iteration has more elements.
119+
*
120+
* @return {@code true} if the iteration has more elements
121+
*/
122+
@Override
123+
public boolean hasNext() {
124+
return !this.queue.isEmpty();
125+
}
126+
127+
/**
128+
* Returns the next element in the iteration.
129+
*
130+
* @return the next element in the iteration
131+
* @throws NoSuchElementException if the iteration has no more elements
132+
*/
133+
@Override
134+
public T next() {
135+
if (!hasNext()) {
136+
throw new NoSuchElementException("No more elements available");
137+
}
138+
139+
final PeekingIterator<T> nextItr = this.queue.remove();
140+
T next = nextItr.next();
141+
if (nextItr.hasNext()) {
142+
this.queue.add(nextItr);
143+
}
144+
return next;
145+
}
146+
}
64147
}

oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/collections/IterableUtilsTest.java

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
import java.util.Arrays;
2626
import java.util.Collections;
27+
import java.util.Comparator;
2728
import java.util.Iterator;
2829
import java.util.List;
2930
import java.util.NoSuchElementException;
@@ -536,4 +537,96 @@ public void testTransformWithComplexFunction() {
536537
List<Integer> result = ListUtils.toList(transformed.iterator());
537538
Assert.assertEquals(Arrays.asList(1, 2, 3), result);
538539
}
540+
541+
@Test
542+
public void testMergeSortedWithNonEmptyIterables() {
543+
List<Integer> list1 = Arrays.asList(1, 4, 9);
544+
List<Integer> list2 = Arrays.asList(2, 5, 8);
545+
List<Integer> list3 = Arrays.asList(3, 6, 7);
546+
547+
Iterable<Iterable<Integer>> iterables = Arrays.asList(list1, list2, list3);
548+
Iterable<Integer> merged = IterableUtils.mergeSorted(iterables, Comparator.naturalOrder());
549+
550+
List<Integer> expected = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
551+
Iterator<Integer> iterator = merged.iterator();
552+
for (Integer value : expected) {
553+
Assert.assertTrue(iterator.hasNext());
554+
Assert.assertEquals(value, iterator.next());
555+
}
556+
Assert.assertFalse(iterator.hasNext());
557+
}
558+
559+
@Test
560+
public void testMergeSortedWithDuplicateElementsIterables() {
561+
List<Integer> list1 = Arrays.asList(1, 4, 9);
562+
List<Integer> list2 = Arrays.asList(2, 5, 8);
563+
List<Integer> list3 = Arrays.asList(3, 6, 9);
564+
565+
Iterable<Iterable<Integer>> iterables = Arrays.asList(list1, list2, list3);
566+
Iterable<Integer> merged = IterableUtils.mergeSorted(iterables, Comparator.naturalOrder());
567+
568+
List<Integer> expected = Arrays.asList(1, 2, 3, 4, 5, 6, 8, 9, 9);
569+
Iterator<Integer> iterator = merged.iterator();
570+
for (Integer value : expected) {
571+
Assert.assertTrue(iterator.hasNext());
572+
Assert.assertEquals(value, iterator.next());
573+
}
574+
Assert.assertFalse(iterator.hasNext());
575+
}
576+
577+
@Test
578+
public void testMergeSortedWithEmptyIterables() {
579+
List<Integer> list1 = Collections.emptyList();
580+
List<Integer> list2 = Collections.emptyList();
581+
List<Integer> list3 = Collections.emptyList();
582+
583+
Iterable<Iterable<Integer>> iterables = Arrays.asList(list1, list2, list3);
584+
Iterable<Integer> merged = IterableUtils.mergeSorted(iterables, Comparator.naturalOrder());
585+
586+
Assert.assertFalse(merged.iterator().hasNext());
587+
}
588+
589+
@Test
590+
public void testMergeSortedWithNullIterables() {
591+
Assert.assertThrows(NullPointerException.class, () -> {
592+
IterableUtils.mergeSorted(null, Comparator.naturalOrder());
593+
});
594+
}
595+
596+
@Test
597+
public void testMergeSortedWithNullComparator() {
598+
List<Integer> list1 = Arrays.asList(1, 4, 7);
599+
List<Integer> list2 = Arrays.asList(2, 5, 8);
600+
List<Integer> list3 = Arrays.asList(3, 6, 9);
601+
602+
Iterable<Iterable<Integer>> iterables = Arrays.asList(list1, list2, list3);
603+
Assert.assertThrows(NullPointerException.class, () -> {
604+
IterableUtils.mergeSorted(iterables, null);
605+
});
606+
}
607+
608+
@Test
609+
public void testMergeSortedWithSingleIterable() {
610+
List<Integer> list1 = Arrays.asList(1, 2, 3);
611+
612+
Iterable<Iterable<Integer>> iterables = Collections.singletonList(list1);
613+
Iterable<Integer> merged = IterableUtils.mergeSorted(iterables, Comparator.naturalOrder());
614+
615+
List<Integer> expected = Arrays.asList(1, 2, 3);
616+
Iterator<Integer> iterator = merged.iterator();
617+
for (Integer value : expected) {
618+
Assert.assertTrue(iterator.hasNext());
619+
Assert.assertEquals(value, iterator.next());
620+
}
621+
Assert.assertFalse(iterator.hasNext());
622+
}
623+
624+
@Test
625+
public void testMergeSortedWithNoIterables() {
626+
Iterable<Iterable<Integer>> iterables = Collections.emptyList();
627+
Iterable<Integer> merged = IterableUtils.mergeSorted(iterables, Comparator.naturalOrder());
628+
629+
Assert.assertFalse(merged.iterator().hasNext());
630+
Assert.assertThrows(NoSuchElementException.class, () -> merged.iterator().next());
631+
}
539632
}

oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/collections/IteratorUtilsTest.java

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,11 @@
2121
import org.junit.Assert;
2222
import org.junit.Test;
2323

24+
import java.util.Arrays;
25+
import java.util.Comparator;
2426
import java.util.Iterator;
2527
import java.util.List;
28+
import java.util.NoSuchElementException;
2629

2730
import static org.junit.Assert.fail;
2831

@@ -44,4 +47,100 @@ public void iteratorToIIterable() {
4447
// that's what we want
4548
}
4649
}
50+
51+
@Test
52+
public void testMergeSortedWithNonEmptyIterators() {
53+
List<Integer> list1 = Arrays.asList(1, 4, 7);
54+
List<Integer> list2 = Arrays.asList(2, 5, 9);
55+
List<Integer> list3 = Arrays.asList(3, 6, 8);
56+
57+
Iterable<Iterator<Integer>> iterators = Arrays.asList(list1.iterator(), list2.iterator(), list3.iterator());
58+
Iterator<Integer> merged = IteratorUtils.mergeSorted(iterators, Comparator.naturalOrder());
59+
60+
List<Integer> expected = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
61+
for (Integer value : expected) {
62+
Assert.assertTrue(merged.hasNext());
63+
Assert.assertEquals(value, merged.next());
64+
}
65+
Assert.assertFalse(merged.hasNext());
66+
}
67+
68+
@Test
69+
public void testMergeSortedWithDuplicateElementsIterators() {
70+
List<Integer> list1 = Arrays.asList(1, 4, 7);
71+
List<Integer> list2 = Arrays.asList(2, 5, 9);
72+
List<Integer> list3 = Arrays.asList(3, 6, 9);
73+
74+
Iterable<Iterator<Integer>> iterators = Arrays.asList(list1.iterator(), list2.iterator(), list3.iterator());
75+
Iterator<Integer> merged = IteratorUtils.mergeSorted(iterators, Comparator.naturalOrder());
76+
77+
List<Integer> expected = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 9, 9);
78+
for (Integer value : expected) {
79+
Assert.assertTrue(merged.hasNext());
80+
Assert.assertEquals(value, merged.next());
81+
}
82+
Assert.assertFalse(merged.hasNext());
83+
}
84+
85+
@Test
86+
public void testMergeSortedWithEmptyIterators() {
87+
List<Integer> list1 = List.of();
88+
List<Integer> list2 = List.of();
89+
List<Integer> list3 = List.of();
90+
91+
Iterable<Iterator<Integer>> iterators = Arrays.asList(list1.iterator(), list2.iterator(), list3.iterator());
92+
Iterator<Integer> merged = IteratorUtils.mergeSorted(iterators, Comparator.naturalOrder());
93+
94+
Assert.assertFalse(merged.hasNext());
95+
}
96+
97+
@Test
98+
public void testMergeSortedWithNullIterators() {
99+
Assert.assertThrows(NullPointerException.class, () -> {
100+
IteratorUtils.mergeSorted(null, Comparator.naturalOrder());
101+
});
102+
}
103+
104+
@Test
105+
public void testMergeSortedWithNullComparator() {
106+
List<Integer> list1 = Arrays.asList(1, 4, 7);
107+
List<Integer> list2 = Arrays.asList(2, 5, 8);
108+
List<Integer> list3 = Arrays.asList(3, 6, 9);
109+
110+
Iterable<Iterator<Integer>> iterators = Arrays.asList(list1.iterator(), list2.iterator(), list3.iterator());
111+
Assert.assertThrows(NullPointerException.class, () -> {
112+
IteratorUtils.mergeSorted(iterators, null);
113+
});
114+
}
115+
116+
@Test
117+
public void testMergeSortedWithSingleIterator() {
118+
List<Integer> list1 = Arrays.asList(1, 2, 3);
119+
120+
Iterable<Iterator<Integer>> iterators = List.of(list1.iterator());
121+
Iterator<Integer> merged = IteratorUtils.mergeSorted(iterators, Comparator.naturalOrder());
122+
123+
List<Integer> expected = Arrays.asList(1, 2, 3);
124+
for (Integer value : expected) {
125+
Assert.assertTrue(merged.hasNext());
126+
Assert.assertEquals(value, merged.next());
127+
}
128+
Assert.assertFalse(merged.hasNext());
129+
}
130+
131+
@Test
132+
public void testMergeSortedWithNoIterators() {
133+
Iterable<Iterator<Integer>> iterators = List.of();
134+
Iterator<Integer> merged = IteratorUtils.mergeSorted(iterators, Comparator.naturalOrder());
135+
136+
Assert.assertFalse(merged.hasNext());
137+
}
138+
139+
@Test
140+
public void testMergeSortedWithEmptyAndOneIterator() {
141+
Iterable<Iterator<Integer>> iterators = List.of();
142+
Iterator<Integer> merged = IteratorUtils.mergeSorted(iterators, Comparator.naturalOrder());
143+
144+
Assert.assertThrows(NoSuchElementException.class, merged::next);
145+
}
47146
}

0 commit comments

Comments
 (0)