I have a many objects of type Slot in an array list.
Slot class is as shown below-
Slot{
int start;
int end;
}
let the list of type List<Slot> be called slots. The slots are sorted based on start time. End time of one slot may be equal to start time of next slot, but they would never overlap.
Is there any possible way in which I can iterate over this list using Java 8 streams, and combine two slots if end time of one matches start time of next and output them into an ArrayList?
Such scenario is perfectly supported by my free StreamEx library which enhances standard Stream API. There's an intervalMap intermediate operation which is capable to collapse several adjacent stream elements to the single element. Here's complete example:
// Slot class and sample data are taken from #Andreas answer
List<Slot> slots = Arrays.asList(new Slot(3, 5), new Slot(5, 7),
new Slot(8, 10), new Slot(10, 11), new Slot(11, 13));
List<Slot> result = StreamEx.of(slots)
.intervalMap((s1, s2) -> s1.end == s2.start,
(s1, s2) -> new Slot(s1.start, s2.end))
.toList();
System.out.println(result);
// Output: [3-7, 8-13]
The intervalMap method takes two parameters. The first is a BiPredicate accepting two adjacent elements from the input stream and returns true if they must be merged (here the condition is s1.end == s2.start). The second parameter is a BiFunction which takes the first and the last elements from the merged series and produces the resulting element.
Note that if you have, for example 100 adjacent slots which should be combined into one, this solution does not create 100 intermediate objects (like in #Misha's answer, which is nevertheless very interesting), it tracks first and last slot in the series immediately forgetting about intermediate onces. Of course this solution is parallel friendly. If you have many thousands of input slots, using .parallel() may improve the performance.
Note that current implementation will recreate the Slot even if it's not merged with anything. In this case the BinaryOperator receives the same Slot parameter twice. If you want to optimize this case, you can make additional check like s1 == s2 ? s1 : ...:
List<Slot> result = StreamEx.of(slots)
.intervalMap((s1, s2) -> s1.end == s2.start,
(s1, s2) -> s1 == s2 ? s1 : new Slot(s1.start, s2.end))
.toList();
Since these types of questions come up a lot, I thought it might be an interesting exercise to write a collector that would group adjacent elements by a predicate.
Assuming we can add combining logic to the Slot class
boolean canCombine(Slot other) {
return this.end == other.start;
}
Slot combine(Slot other) {
if (!canCombine(other)) {
throw new IllegalArgumentException();
}
return new Slot(this.start, other.end);
}
the groupingAdjacent collector can then be used as follows:
List<Slot> combined = slots.stream()
.collect(groupingAdjacent(
Slot::canCombine, // test to determine if two adjacent elements go together
reducing(Slot::combine), // collector to use for combining the adjacent elements
mapping(Optional::get, toList()) // collector to group up combined elements
));
Alternatively, second parameter can be collectingAndThen(reducing(Slot::combine), Optional::get) and the third argument be toList()
Here's the source for groupingAdjacent. It can handle null elements and is parallel-friendly. With a bit more hassle, a similar thing can be done with a Spliterator.
public static <T, AI, I, AO, R> Collector<T, ?, R> groupingAdjacent(
BiPredicate<? super T, ? super T> keepTogether,
Collector<? super T, AI, ? extends I> inner,
Collector<I, AO, R> outer
) {
AI EMPTY = (AI) new Object();
// Container to accumulate adjacent possibly null elements. Adj can be in one of 3 states:
// - Before first element: curGrp == EMPTY
// - After first element but before first group boundary: firstGrp == EMPTY, curGrp != EMPTY
// - After at least one group boundary: firstGrp != EMPTY, curGrp != EMPTY
class Adj {
T first, last; // first and last elements added to this container
AI firstGrp = EMPTY, curGrp = EMPTY;
AO acc = outer.supplier().get(); // accumlator for completed groups
void add(T t) {
if (curGrp == EMPTY) /* first element */ {
first = t;
curGrp = inner.supplier().get();
} else if (!keepTogether.test(last, t)) /* group boundary */ {
addGroup(curGrp);
curGrp = inner.supplier().get();
}
inner.accumulator().accept(curGrp, last = t);
}
void addGroup(AI group) /* group can be EMPTY, in which case this should do nothing */ {
if (firstGrp == EMPTY) {
firstGrp = group;
} else if (group != EMPTY) {
outer.accumulator().accept(acc, inner.finisher().apply(group));
}
}
Adj merge(Adj other) {
if (other.curGrp == EMPTY) /* other is empty */ {
return this;
} else if (this.curGrp == EMPTY) /* this is empty */ {
return other;
} else if (!keepTogether.test(last, other.first)) /* boundary between this and other*/ {
addGroup(this.curGrp);
addGroup(other.firstGrp);
} else if (other.firstGrp == EMPTY) /* other container is single-group. prepend this.curGrp to other.curGrp*/ {
other.curGrp = inner.combiner().apply(this.curGrp, other.curGrp);
} else /* other Adj contains a boundary. this.curGrp+other.firstGrp form a complete group. */ {
addGroup(inner.combiner().apply(this.curGrp, other.firstGrp));
}
this.acc = outer.combiner().apply(this.acc, other.acc);
this.curGrp = other.curGrp;
this.last = other.last;
return this;
}
R finish() {
AO combined = outer.supplier().get();
if (curGrp != EMPTY) {
addGroup(curGrp);
assert firstGrp != EMPTY;
outer.accumulator().accept(combined, inner.finisher().apply(firstGrp));
}
return outer.finisher().apply(outer.combiner().apply(combined, acc));
}
}
return Collector.of(Adj::new, Adj::add, Adj::merge, Adj::finish);
}
You can do it using the reduce() method with U being another List<Slot>, but it's a lot more convoluted than just doing it in a for loop, unless parallel processing is required.
See end of answer for test setup.
Here is the for loop implementation:
List<Slot> mixed = new ArrayList<>();
Slot last = null;
for (Slot slot : slots)
if (last == null || last.end != slot.start)
mixed.add(last = slot);
else
mixed.set(mixed.size() - 1, last = new Slot(last.start, slot.end));
Output
[3-5, 5-7, 8-10, 10-11, 11-13]
[3-7, 8-13]
Here is the stream reduce implementation:
List<Slot> mixed = slots.stream().reduce((List<Slot>)null, (list, slot) -> {
System.out.println("accumulator.apply(" + list + ", " + slot + ")");
if (list == null) {
List<Slot> newList = new ArrayList<>();
newList.add(slot);
return newList;
}
Slot last = list.get(list.size() - 1);
if (last.end != slot.start)
list.add(slot);
else
list.set(list.size() - 1, new Slot(last.start, slot.end));
return list;
}, (list1, list2) -> {
System.out.println("combiner.apply(" + list1 + ", " + list2 + ")");
if (list1 == null)
return list2;
if (list2 == null)
return list1;
Slot lastOf1 = list1.get(list1.size() - 1);
Slot firstOf2 = list2.get(0);
if (lastOf1.end != firstOf2.start)
list1.addAll(list2);
else {
list1.set(list1.size() - 1, new Slot(lastOf1.start, firstOf2.end));
list1.addAll(list2.subList(1, list2.size()));
}
return list1;
});
Output
accumulator.apply(null, 3-5)
accumulator.apply([3-5], 5-7)
accumulator.apply([3-7], 8-10)
accumulator.apply([3-7, 8-10], 10-11)
accumulator.apply([3-7, 8-11], 11-13)
[3-5, 5-7, 8-10, 10-11, 11-13]
[3-7, 8-13]
Changing it for parallel (multi-threaded) processing:
List<Slot> mixed = slots.stream().parallel().reduce(...
Output
accumulator.apply(null, 8-10)
accumulator.apply(null, 3-5)
accumulator.apply(null, 10-11)
accumulator.apply(null, 11-13)
combiner.apply([10-11], [11-13])
accumulator.apply(null, 5-7)
combiner.apply([3-5], [5-7])
combiner.apply([8-10], [10-13])
combiner.apply([3-7], [8-13])
[3-5, 5-7, 8-10, 10-11, 11-13]
[3-7, 8-13]
Caveat
If slots is an empty list, the for loop version results in an empty list, and the streams version results is a null value.
Test Setup
All the above code used the following Slot class:
class Slot {
int start;
int end;
Slot(int start, int end) {
this.start = start;
this.end = end;
}
#Override
public String toString() {
return this.start + "-" + this.end;
}
}
The slots variable was defined as:
List<Slot> slots = Arrays.asList(new Slot(3, 5), new Slot(5, 7), new Slot(8, 10), new Slot(10, 11), new Slot(11, 13));
Both slots and the result mixed are printed using:
System.out.println(slots);
System.out.println(mixed);
It's a two-liner:
List<Slot> condensed = new LinkedList<>();
slots.stream().reduce((a,b) -> {if (a.end == b.start) return new Slot(a.start, b.end);
condensed.add(a); return b;}).ifPresent(condensed::add);
If the fields of slot are not visible, you will have to change a.end to a.getEnd(), etc
Some test code with some edge cases:
List<List<Slot>> tests = Arrays.asList(
Arrays.asList(new Slot(3, 5), new Slot(5, 7), new Slot(8, 10), new Slot(10, 11), new Slot(11, 13)),
Arrays.asList(new Slot(3, 5), new Slot(5, 7), new Slot(8, 10), new Slot(10, 11), new Slot(12, 13)),
Arrays.asList(new Slot(3, 5), new Slot(5, 7)),
Collections.emptyList());
for (List<Slot> slots : tests) {
List<Slot> condensed = new LinkedList<>();
slots.stream().reduce((a, b) -> {if (a.end == b.start) return new Slot(a.start, b.end);
condensed.add(a); return b;}).ifPresent(condensed::add);
System.out.println(condensed);
}
Output:
[3-7, 8-13]
[3-7, 8-11, 12-13]
[3-7]
[]
A clean (parallel safe) solution that doesn't require any new methods:
List<Slot> condensed = slots.stream().collect(LinkedList::new,
(l, s) -> l.add(l.isEmpty() || l.getLast().end != s.start ?
s : new Slot(l.removeLast().start, s.end)),
(l, l2) -> {if (!l.isEmpty() && !l2.isEmpty() && l.getLast().end == l2.getFirst().start) {
l.add(new Slot(l.removeLast().start, l2.removeFirst().end));} l.addAll(l2);});
This uses the more appropriate list implementation LinkedList to simplify removing and accessing the last element of the list when merging Slots.
List<List<Slot>> tests = Arrays.asList(
Arrays.asList(new Slot(3, 5), new Slot(5, 7), new Slot(8, 10), new Slot(10, 11), new Slot(11, 13)),
Arrays.asList(new Slot(3, 5), new Slot(5, 7), new Slot(8, 10), new Slot(10, 11), new Slot(12, 13)),
Arrays.asList(new Slot(3, 5), new Slot(5, 7)),
Collections.emptyList());
for (List<Slot> slots : tests) {
List<Slot> condensed = slots.stream().collect(LinkedList::new,
(l, s) -> l.add(l.isEmpty() || l.getLast().end != s.start ?
s : new Slot(l.removeLast().start, s.end)),
(l, l2) -> {if (!l.isEmpty() && !l2.isEmpty() && l.getLast().end == l2.getFirst().start) {
l.add(new Slot(l.removeLast().start, l2.removeFirst().end));} l.addAll(l2);});
System.out.println(condensed);
}
Output:
[[3, 7], [8, 13]]
[[3, 7], [8, 11], [12, 13]]
[[3, 7]]
[]
If you add the following method to your Slot class
public boolean join(Slot s) {
if(s.start != end)
return false;
end = s.end;
return true;
}
you can perform the entire operation using the standard API the following way
List<Slot> result = slots.stream().collect(ArrayList::new,
(l, s)-> { if(l.isEmpty() || !l.get(l.size()-1).join(s)) l.add(s); },
(l1, l2)-> l1.addAll(
l1.isEmpty()||l2.isEmpty()||!l1.get(l1.size()-1).join(l2.get(0))?
l2: l2.subList(1, l2.size()))
);
This obeys the contract of the API (unlike abusing reduce) and will therefore works seamlessly with parallel streams (though you need really large source lists to benefit from parallel execution).
However, the solution above uses in-place joining of Slots which is only acceptable if you don’t need the source list/items anymore. Otherwise, or if you use immutable Slot instances only, you have to create new Slot instance representing joint slots.
One possible solution looks like
BiConsumer<List<Slot>,Slot> joinWithList=(l,s) -> {
if(!l.isEmpty()) {
Slot old=l.get(l.size()-1);
if(old.end==s.start) {
l.set(l.size()-1, new Slot(old.start, s.end));
return;
}
}
l.add(s);
};
List<Slot> result = slots.stream().collect(ArrayList::new, joinWithList,
(l1, l2)-> {
if(!l2.isEmpty()) {
joinWithList.accept(l1, l2.get(0));
l1.addAll(l2.subList(1, l2.size()));
}
}
);
Related
I'm attempting to compare two Iterables in Java of same size. I only need to know that the contents are the same. However, something like [1, 2] and [1, 2, 2] should not be equal, while [1, 2, 2, 4] should equal [1, 2, 4, 2].
boolean functionName() {
boolean pvk;
... setup ...
for(Edge e : pMST.edges()) {
pvk = false;
for(Edge f : kMST.edges()) {
if(e == f) {
pvk = true;
System.out.println("True.");
}
}
if(!pvk) return false;
}
return true;
}
There's my initial lousy attempt, but not only does this always return false, it doesn't account for duplicates properly.
You could sort the items and compare the resulting lists, but this is potentially slow O(n lg n) and it relies on the items either being Comparable or having a total order imposed on them by a Comparator. This might be infeasible.
This other answer suggests using a Guava Multiset. This makes sense, as it keeps track of the elements and the count of occurrences, which is significant for your question. It should be O(n) for reasonable implementations such as a HashMultiset. Other libraries such as Apache Commons (MultiSet) and Eclipse Collections (Bag) have collection implementations that are functionally equivalent to Guava’s Multiset.
If you don't want to include a dependency on any of these libraries, you can do this in the JDK by itself. Unfortunately Java doesn't have a Bag implementation, but for this purpose it's easy to emulate it using a Map from your item type to a count, either Integer or Long.
If you have Lists, you can do this:
boolean unorderedEquals(List<Item> list1, List<Item> list2) {
Map<Item, Long> freq1 = list1.stream().collect(groupingBy(i -> i, counting()));
Map<Item, Long> freq2 = list2.stream().collect(groupingBy(i -> i, counting()));
return freq1.equals(freq2);
}
If you have Iterables, you need to build up the maps using forEach instead:
boolean unorderedEquals(Iterable<Item> iter1, Iterable<Item> iter2) {
Map<Item, Integer> freq1 = new HashMap<>();
iter1.forEach(it -> freq1.merge(it, 1, (a, b) -> a + b));
Map<Item, Integer> freq2 = new HashMap<>();
iter2.forEach(it -> freq2.merge(it, 1, (a, b) -> a + b));
return freq1.equals(freq2);
}
Combining this answer with ideas from this thread, notably this answer to create an efficient but readable solution, you may use
static boolean unorderedEquals(Collection<?> coll1, Collection<?> coll2) {
if(coll1.size() != coll2.size()) return false;
Map<Object, Integer> freq = new HashMap<>();
for(Object o: coll1) freq.merge(o, 1, Integer::sum);
for(Object o: coll2)
if(freq.merge(o, -1, Integer::sum) < 0) return false;
return true;
}
The first loop creates a frequency map like in the linked answer, but instead of building a second map, to perform an expensive comparison, the second loop decreases the counts on each occurrence, returning immediately, if a count became negative. The merge method smoothly handles the case of absent keys.
Since it has been checked right at the beginning of the method that both lists have the same size, after increasing and decreasing, the total count must be zero. Since we have proven that there are no negative numbers, as we returned immediately for them, there can’t be positive non-zero values either. So we can return true after the second loop without further checks.
Supporting arbitrary Iterables, which differ from Collection in not necessarily having a size() method, is a bit trickier, as we can’t do the pre-check then and hence, have to maintain the count:
static boolean unorderedEquals(Iterable<?> iter1, Iterable<?> iter2) {
Map<Object, Integer> freq = new HashMap<>();
int size = 0;
for(Object o: iter1) {
freq.merge(o, 1, Integer::sum);
size++;
}
for(Object o: iter2)
if(--size < 0 || freq.merge(o, -1, Integer::sum) < 0) return false;
return size == 0;
}
If we want avoid the boxing overhead, we have to resort to a mutable value for the map, e.g.
static boolean unorderedEquals(Collection<?> coll1, Collection<?> coll2) {
if(coll1.size() != coll2.size()) return false;
Map<Object, int[]> freq = new HashMap<>();
for(Object o: coll1) freq.computeIfAbsent(o, x -> new int[1])[0]++;
int[] absent = { 0 };
for(Object o: coll2) if(freq.getOrDefault(o, absent)[0]-- == 0) return false;
return true;
}
But I don’t think that his will pay off. For small numbers of occurrences, boxing will reuse the Integer instances whereas we need a distinct int[] object for each distinct element when using mutable values.
But using compute might be interesting for the Iterable solution, when using it like
static boolean unorderedEquals(Iterable<?> coll1, Iterable<?> coll2) {
Map<Object, int[]> freq = new HashMap<>();
for(Object o: coll1) freq.computeIfAbsent(o, x -> new int[1])[0]++;
int[] absent = {};
for(Object o: coll2)
if(freq.compute(o, (key,c) -> c == null || c[0] == 0? absent:
--c[0] == 0? null: c) == absent) return false;
return freq.isEmpty();
}
which removes entries from the map when their count reaches zero, so we only have to check the map for emptiness at the end.
I would sort them. But first I would compare the sizes before doing the sort. You would need to provide a Comparator<T> to be used by the sort method. If you're sorting Integers, you could use:
List<Integer> a = new ArrayList<>(List.of(1, 2, 3, 3, 3, 3, 4, 5, 6));
List<Integer> b = new ArrayList<>(List.of(2, 3, 1, 3, 4, 5, 6, 3, 3));
System.out.println(compareLists(a, b, Comparator.naturalOrder()));
public static <T> boolean compareList(List<T> list1, List<T> list2,
Comparator<T> comp) {
if (list1 == list2) {
return true;
}
if (list1.size() != list2.size()) {
return false;
}
Collections.sort(list1, comp);
Collections.sort(list2, comp);
return list1.equals(list2);
}
How to find the first match or the last element in a list using java stream?
Which means if no element matches the condition,then return the last element.
eg:
OptionalInt i = IntStream.rangeClosed(1,5)
.filter(x-> x == 7)
.findFirst();
System.out.print(i.getAsInt());
What should I do to make it return 5;
Given the list
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
You could just do :
int value = list.stream().filter(x -> x == 2)
.findFirst()
.orElse(list.get(list.size() - 1));
Here if the filter evaluates to true the element is retrieved, else the last element in the last is returned.
If the list is empty you could return a default value, for example -1.
int value = list.stream().filter(x -> x == 2)
.findFirst()
.orElse(list.isEmpty() ? -1 : list.get(list.size() - 1));
You can use reduce() function like that:
OptionalInt i = IntStream.rangeClosed(1, 5)
.reduce((first, second) -> first == 7 ? first : second);
System.out.print(i.getAsInt());
Basically I would use one of the following two methods or deviations thereof:
Stream variant:
<T> T getFirstMatchOrLast(List<T> list, Predicate<T> filter, T defaultValue) {
return list.stream()
.filter(filter)
.findFirst()
.orElse(list.isEmpty() ? defaultValue : list.get(list.size() - 1));
}
non-stream variant:
<T> T getFirstMatchOrLast(Iterable<T> iterable, Predicate<T> filter, T defaultValue) {
T relevant = defaultValue;
for (T entry : iterable) {
relevant = entry;
if (filter.test(entry))
break;
}
return relevant;
}
Or as also Ilmari Karonen suggested in the comment with Iterable<T> you are then able to even call stream::iterator in case you really deal with a Stream instead of a List. Calling the shown methods would look as follows:
getFirstMatchOrLast(Arrays.asList(1, 20, 3), i -> i == 20, 1); // returns 20
getFirstMatchOrLast(Collections.emptyList(), i -> i == 3, 20); // returns 20
getFirstMatchOrLast(Arrays.asList(1, 2, 20), i -> i == 7, 30); // returns 20
// only non-stream variant: having a Stream<Integer> stream = Stream.of(1, 2, 20)
getFirstMatchOrLast(stream::iterator, i -> i == 7, 30); // returns 20
I wouldn't use reduce here because it sounds wrong to me in the sense, that it also goes through the whole entries even though the first entry could have matched already, i.e. it doesn't short-circuit anymore. Moreover for me it isn't as readable as filter.findFirst.orElse... (but that's probably just my opinion)
I probably would then even end up with something as follows:
<T> Optional<T> getFirstMatchOrLast(Iterable<T> iterable, Predicate<T> filter) {
T relevant = null;
for (T entry : iterable) {
relevant = entry;
if (filter.test(entry))
break;
}
return Optional.ofNullable(relevant);
}
// or transform the stream variant to somethinng like that... however I think that isn't as readable anymore...
so that calls would rather look like:
getFirstMatchOrLast(Arrays.asList(1, 2, 3, 5), i -> i == 7).orElseThrow(...)
getFirstMatchOrLast(Arrays.asList(1, 2, 3, 5), i -> i == 7).orElse(0);
getFirstMatchOrLast(Arrays.asList(1, 2, 3, 5), i -> i == 7).orElseGet(() -> /* complex formula */);
getFirstMatchOrLast(stream::iterator, i -> i == 5).ifPresent(...)
if you want to do this in one pipeline then you could do:
int startInc = 1;
int endEx = 5;
OptionalInt first =
IntStream.concat(IntStream.range(startInc, endEx)
.filter(x -> x == 7), endEx > 1 ? IntStream.of(endEx) : IntStream.empty())
.findFirst();
but you're probably better off collecting the generated numbers into a list then operate on it as follows:
// first collect the numbers into a list
List<Integer> result = IntStream.rangeClosed(startInc,endEx)
.boxed()
.collect(toList());
// then operate on it
int value = result.stream()
.filter(x -> x == 7)
.findFirst()
.orElse(result.get(result.size() - 1));
Alternatively, if you want to make the latter return an empty Optional in the case of the source being empty (if that's a possible scenario) instead of an exception then you could do:
List<Integer> result = IntStream.rangeClosed(startInc,endEx)
.boxed()
.collect(toList());
Optional<Integer> first =
Stream.concat(result.stream().filter(x -> x == 7), result.isEmpty() ?
Stream.empty() : Stream.of(result.get(result.size() - 1)))
.findFirst();
I am not sure why you really want to use streams for that, a simple for-loop would be enough:
public static <T> T getFirstMatchingOrLast(List<? extends T> source, Predicate<? super T> predicate){
// handle empty case
if(source.isEmpty()){
return null;
}
for(T t : source){
if(predicate.test(t)){
return t;
}
}
return source.get(source.size() -1);
}
Which then can be called like:
Integer match = getFirstMatchingOrLast(ints, i -> i == 7);
I'm trying to split a list into a list of list where each list has a maximum size of 4.
I would like to know how this is possible to do using lambdas.
Currently the way I'm doing it is as follow:
List<List<Object>> listOfList = new ArrayList<>();
final int MAX_ROW_LENGTH = 4;
int startIndex =0;
while(startIndex <= listToSplit.size() )
{
int endIndex = ( ( startIndex+MAX_ROW_LENGTH ) < listToSplit.size() ) ? startIndex+MAX_ROW_LENGTH : listToSplit.size();
listOfList.add(new ArrayList<>(listToSplit.subList(startIndex, endIndex)));
startIndex = startIndex+MAX_ROW_LENGTH;
}
UPDATE
It seems that there isn't a simple way to use lambdas to split lists. While all of the answers are much appreciated, they're also a wonderful example of when lambdas do not simplify things.
Try this approach:
static <T> List<List<T>> listSplitter(List<T> incoming, int size) {
// add validation if needed
return incoming.stream()
.collect(Collector.of(
ArrayList::new,
(accumulator, item) -> {
if(accumulator.isEmpty()) {
accumulator.add(new ArrayList<>(singletonList(item)));
} else {
List<T> last = accumulator.get(accumulator.size() - 1);
if(last.size() == size) {
accumulator.add(new ArrayList<>(singletonList(item)));
} else {
last.add(item);
}
}
},
(li1, li2) -> {
li1.addAll(li2);
return li1;
}
));
}
System.out.println(
listSplitter(
Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9),
4
)
);
Also note that this code could be optimized, instead of:
new ArrayList<>(Collections.singletonList(item))
use this one:
List<List<T>> newList = new ArrayList<>(size);
newList.add(item);
return newList;
If you REALLY need a lambda it can be done like this. Otherwise the previous answers are better.
List<List<Object>> lists = new ArrayList<>();
AtomicInteger counter = new AtomicInteger();
final int MAX_ROW_LENGTH = 4;
listToSplit.forEach(pO -> {
if(counter.getAndIncrement() % MAX_ROW_LENGTH == 0) {
lists.add(new ArrayList<>());
}
lists.get(lists.size()-1).add(pO);
});
Surely the below is sufficient
final List<List<Object>> listOfList = new ArrayList<>(
listToSplit.stream()
.collect(Collectors.groupingBy(el -> listToSplit.indexOf(el) / MAX_ROW_LENGTH))
.values()
);
Stream it, collect with a grouping: this gives a Map of Object -> List, pull the values of the map and pass directly into whatever constructor (map.values() gives a Collection not a List).
Perhaps you can use something like that
BiFunction<List,Integer,List> splitter= (list2, count)->{
//temporary list of lists
List<List> listOfLists=new ArrayList<>();
//helper implicit recursive function
BiConsumer<Integer,BiConsumer> splitterHelper = (offset, func) -> {
if(list2.size()> offset+count){
listOfLists.add(list2.subList(offset,offset+count));
//implicit self call
func.accept(offset+count,func);
}
else if(list2.size()>offset){
listOfLists.add(list2.subList(offset,list2.size()));
//implicit self call
func.accept(offset+count,func);
}
};
//pass self reference
splitterHelper.accept(0,splitterHelper);
return listOfLists;
};
Usage example
List<Integer> list=new ArrayList<Integer>(){{
add(1);
add(2);
add(3);
add(4);
add(5);
add(6);
add(7);
add(8);
add(8);
}};
//calling splitter function
List listOfLists = splitter.apply(list, 3 /*max sublist size*/);
System.out.println(listOfLists);
And as a result we have
[[1, 2, 3], [4, 5, 6], [7, 8, 8]]
The requirement is a bit odd, but you could do:
final int[] counter = new int[] {0};
List<List<Object>> listOfLists = in.stream()
.collect(Collectors.groupingBy( x -> counter[0]++ / MAX_ROW_LENGTH ))
.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.map(Map.Entry::getValue)
.collect(Collectors.toList());
You could probably streamline this by using the variant of groupingBy that takes a mapSupplier lambda, and supplying a SortedMap. This should return an EntrySet that iterates in order. I leave it as an exercise.
What we're doing here is:
Collecting your list items into a Map<Integer,Object> using a counter to group. The counter is held in a single-element array because the lambda can only use local variables if they're final.
Getting the map entries as a stream, and sorting by the Integer key.
Using Stream::map() to convert the stream of Map.Entry<Integer,Object> into a stream of Object values.
Collecting this into a list.
This doesn't benefit from any "free" parallelisation. It has a memory overhead in the intermediate Map. It's not particularly easy to read.
However, I wouldn't do this, just for the sake of using a lambda. I would do something like:
for(int i=0; i<in.size(); i += MAX_ROW_LENGTH) {
listOfList.add(
listToSplit.subList(i, Math.min(i + MAX_ROW_LENGTH, in.size());
}
(Yours had a defensive copy new ArrayList<>(listToSplit.subList(...)). I've not duplicated it because it's not always necessary - for example if the input list is unmodifiable and the output lists aren't intended to be modifiable. But do put it back in if you decide you need it in your case.)
This will be extremely fast on any in-memory list. You're very unlikely to want to parallelise it.
Alternatively, you could write your own (unmodifiable) implementation of List that's a view over the underlying List<Object>:
public class PartitionedList<T> extends AbstractList<List<T>> {
private final List<T> source;
private final int sublistSize;
public PartitionedList(T source, int sublistSize) {
this.source = source;
this.sublistSize = sublistSize;
}
#Override
public int size() {
return source.size() / sublistSize;
}
#Override
public List<T> get(int index) {
int sourceIndex = index * sublistSize
return source.subList(sourceIndex,
Math.min(sourceIndex + sublistSize, source.size());
}
}
Again, it's up to you whether you want to make defensive copies here.
This will be have equivalent big-O access time to the underlying list.
You can use:
ListUtils.partition(List list, int size)
OR
List<List> partition(List list, int size)
Both return consecutive sublists of a list, each of the same size (the final list may be smaller).
I want to merge inner Lists using java8 streams for following like this:
When
List<List<Integer>> mainList = new ArrayList<List<Integer>>();
mainList.add(Arrays.asList(0,1));
mainList.add(Arrays.asList(0,1,2));
mainList.add(Arrays.asList(1,2));
mainList.add(Arrays.asList(3));
should be merged into
[[0,1,2],[3]];
And When
List<List<Integer>> mainList = new ArrayList<List<Integer>>();
mainList.add(Arrays.asList(0,2));
mainList.add(Arrays.asList(1,4));
mainList.add(Arrays.asList(0,2,4));
mainList.add(Arrays.asList(3,4));
mainList.add(Arrays.asList(1,3,4));
should be merged into
[[0,1,2,3,4]];
This is so far what I have done
static void mergeCollections(List<List<Integer>> collectionTomerge) {
boolean isMerge = false;
List<List<Integer>> mergeCollection = new ArrayList<List<Integer>>();
for (List<Integer> listInner : collectionTomerge) {
List<Integer> mergeAny = mergeCollection.stream().map(
lc -> lc.stream().filter(listInner::contains)
).findFirst()
.orElse(null)
.collect(Collectors.toList());
}
}
but I am getting this exception:
Exception in thread "main" java.lang.NullPointerException
at linqArraysOperations.LinqOperations.mergeCollections(LinqOperations.java:87)
Updated with mine version of answer
That's what I want to achieve but great answer of Tagir is without recursion
I change things a bit in Mikhaal answer to achieve this, by using logic from Tagir answer without flat map
public static <T> List<List<T>> combineList(List<List<T>> argList) {
boolean isMerge = false;
List<List<T>> result = new ArrayList<>();
for (List<T> list : argList) {
List<List<T>> mergedFound =
result.stream()
.filter(mt->list.stream().anyMatch(mt::contains))
.map(
t -> Stream.concat(t.stream(),list.stream()).distinct()
.collect(Collectors.toList())
)
.collect(Collectors.toList());
//if(mergedFound !=null && ( mergedFound.size() > 0 && mergedFound.stream().findFirst().get().size() > 0 )){
if(mergedFound !=null && mergedFound.size() > 0 && ){
result = Stream.concat(result.stream().filter(t->list.stream().noneMatch(t::contains)),mergedFound.stream()).distinct().collect(Collectors.toList());
isMerge = true;
}
else
result.add(list);
}
if(isMerge && result.size() > 1)
return combineList(result);
return result;
}
Here's very simple, yet not very efficient solution:
static List<List<Integer>> mergeCollections(List<List<Integer>> input) {
List<List<Integer>> result = Collections.emptyList();
for (List<Integer> listInner : input) {
List<Integer> merged = Stream.concat(
// read current results and select only those which contain
// numbers from current list
result.stream()
.filter(list -> list.stream().anyMatch(listInner::contains))
// flatten them into single stream
.flatMap(List::stream),
// concatenate current list, remove repeating numbers and collect
listInner.stream()).distinct().collect(Collectors.toList());
// Now we need to remove used lists from the result and add the newly created
// merged list
result = Stream.concat(
result.stream()
// filter out used lists
.filter(list -> list.stream().noneMatch(merged::contains)),
Stream.of(merged)).collect(Collectors.toList());
}
return result;
}
The tricky part is that next listInner may merge several lists which were already added. For example, if we had partial result like [[1, 2], [4, 5], [7, 8]], and processing a new listInner which content is [2, 3, 5, 7], then partial result should become [[1, 2, 3, 4, 5, 7, 8]] (that is, all lists are merged together). So on every iteration we are looking for existing partial results which have common numbers with current listInner, flatten them, concatenating with current listInner and dumping into the new merged list. Next we filter out from the current result lists which were used in merged and adding merged there.
You can make the solution somewhat more efficient using partitioningBy collector to perform both filtering steps at once:
static List<List<Integer>> mergeCollections(List<List<Integer>> input) {
List<List<Integer>> result = Collections.emptyList();
for (List<Integer> listInner : input) {
// partition current results by condition: whether they contain
// numbers from listInner
Map<Boolean, List<List<Integer>>> map = result.stream().collect(
Collectors.partitioningBy(
list -> list.stream().anyMatch(listInner::contains)));
// now map.get(true) contains lists which intersect with current
// and should be merged with current
// and map.get(false) contains other lists which should be preserved
// in result as is
List<Integer> merged = Stream.concat(
map.get(true).stream().flatMap(List::stream),
listInner.stream()).distinct().collect(Collectors.toList());
result = Stream.concat(map.get(false).stream(), Stream.of(merged))
.collect(Collectors.toList());
}
return result;
}
Here map.get(true) contains the lists which have elements from listInner and map.get(false) contains other lists which should be preserved from the previous result.
The order of elements is probably not what you would expect, but you could easily sort nested lists or use List<TreeSet<Integer>> as resulting data structure if you want.
For the exception you're getting, I would guess that the List<List<Integer>> that the mergeCollections is being passed contains null values, and that those throw NullPointerException on listInner::contains.
Second, if I understand your problem correctly, you want an algorithm that can merge lists that share a common element. I came up with this to solve your problem:
public class Combiner {
public static void main(String[] args) {
List<List<Integer>> mainList = new ArrayList<>();
mainList.add(Arrays.asList(1, 2));
mainList.add(Arrays.asList(4, 5));
mainList.add(Arrays.asList(7, 8));
mainList.add(Arrays.asList(6, 19));
mainList.add(Arrays.asList(2, 3, 5, 7));
System.out.println(combineList(new ArrayList<>(mainList)));
List<List<Integer>> result = mergeCollections(new ArrayList<>(mainList));
System.out.println(result);
}
public static <T> List<List<T>> combineList(List<List<T>> argList) {
List<List<T>> result = new ArrayList<>();
for (List<T> list : argList) {
//Copy the given list
List<T> addedList = new ArrayList<>(list);
result.add(addedList);
for (List<T> otherList : argList) {
if (list.equals(otherList)) continue;
//If at least one element is shared between the two lists
if (list.stream().anyMatch(otherList::contains)) {
//Add all elements that are exclusive to the second list
addedList.addAll(otherList.stream().map(t -> addedList.contains(t) ? null : t)
.filter(t -> t != null).collect(Collectors.toList()));
}
}
}
List<List<T>> del = new ArrayList<>();
for (int i = 0; i < result.size(); i++) {
for (int j = i + 1; j < result.size(); j++) {
List<T> list = result.get(j);
if (listEqualsUnOrdered(list, result.get(i))) {
//Modified this
del.add(result.get(i));
}
}
//Can't use listIterator here because of iterating starting at j + 1
result.removeAll(del);
}
//Recursion
if (!result.equals(argList)) {
result = combineList(result);
}
return result;
}
private static <T> boolean listEqualsUnOrdered(List<T> list1, List<T> list2) {
if (list1.size() != list2.size()) return false;
List<T> testOne = new ArrayList<>(list1);
testOne.removeAll(list2);
boolean testOnePassed = (testOne.size() == 0);
List<T> testTwo = new ArrayList<>(list2);
testTwo.removeAll(list1);
if (testTwo.size() == 0 && testOnePassed) return true;
return false;
}
}
The algorithm is fairly simple, it uses a simple recursion to run the algorithm until the output is "clean", i.e. until the lists have been completely merged. I haven't done any optimizing, but it does what it's supposed to do.
Note that this method will also merge your existing list.
Use Case
Through some coding Katas posted at work, I stumbled on this problem that I'm not sure how to solve.
Using Java 8 Streams, given a list of positive integers, produce a
list of integers where the integer preceded a larger value.
[10, 1, 15, 30, 2, 6]
The above input would yield:
[1, 15, 2]
since 1 precedes 15, 15 precedes 30, and 2 precedes 6.
Non-Stream Solution
public List<Integer> findSmallPrecedingValues(final List<Integer> values) {
List<Integer> result = new ArrayList<Integer>();
for (int i = 0; i < values.size(); i++) {
Integer next = (i + 1 < values.size() ? values.get(i + 1) : -1);
Integer current = values.get(i);
if (current < next) {
result.push(current);
}
}
return result;
}
What I've Tried
The problem I have is I can't figure out how to access next in the lambda.
return values.stream().filter(v -> v < next).collect(Collectors.toList());
Question
Is it possible to retrieve the next value in a stream?
Should I be using map and mapping to a Pair in order to access next?
Using IntStream.range:
static List<Integer> findSmallPrecedingValues(List<Integer> values) {
return IntStream.range(0, values.size() - 1)
.filter(i -> values.get(i) < values.get(i + 1))
.mapToObj(values::get)
.collect(Collectors.toList());
}
It's certainly nicer than an imperative solution with a large loop, but still a bit meh as far as the goal of "using a stream" in an idiomatic way.
Is it possible to retrieve the next value in a stream?
Nope, not really. The best cite I know of for that is in the java.util.stream package description:
The elements of a stream are only visited once during the life of a stream. Like an Iterator, a new stream must be generated to revisit the same elements of the source.
(Retrieving elements besides the current element being operated on would imply they could be visited more than once.)
We could also technically do it in a couple other ways:
Statefully (very meh).
Using a stream's iterator is technically still using the stream.
That's not a pure Java8, but recently I've published a small library called StreamEx which has a method exactly for this task:
// Find all numbers where the integer preceded a larger value.
Collection<Integer> numbers = Arrays.asList(10, 1, 15, 30, 2, 6);
List<Integer> res = StreamEx.of(numbers).pairMap((a, b) -> a < b ? a : null)
.nonNull().toList();
assertEquals(Arrays.asList(1, 15, 2), res);
The pairMap operation internally implemented using custom spliterator. As a result you have quite clean code which does not depend on whether the source is List or anything else. Of course it works fine with parallel stream as well.
Committed a testcase for this task.
It's not a one-liner (it's a two-liner), but this works:
List<Integer> result = new ArrayList<>();
values.stream().reduce((a,b) -> {if (a < b) result.add(a); return b;});
Rather than solving it by "looking at the next element", this solves it by "looking at the previous element, which reduce() give you for free. I have bent its intended usage by injecting a code fragment that populates the list based on the comparison of previous and current elements, then returns the current so the next iteration will see it as its previous element.
Some test code:
List<Integer> result = new ArrayList<>();
IntStream.of(10, 1, 15, 30, 2, 6).reduce((a,b) -> {if (a < b) result.add(a); return b;});
System.out.println(result);
Output:
[1, 15, 2]
The accepted answer works fine if either the stream is sequential or parallel but can suffer if the underlying List is not random access, due to multiple calls to get.
If your stream is sequential, you might roll this collector:
public static Collector<Integer, ?, List<Integer>> collectPrecedingValues() {
int[] holder = {Integer.MAX_VALUE};
return Collector.of(ArrayList::new,
(l, elem) -> {
if (holder[0] < elem) l.add(holder[0]);
holder[0] = elem;
},
(l1, l2) -> {
throw new UnsupportedOperationException("Don't run in parallel");
});
}
and a usage:
List<Integer> precedingValues = list.stream().collect(collectPrecedingValues());
Nevertheless you could also implement a collector so thats works for sequential and parallel streams. The only thing is that you need to apply a final transformation, but here you have control over the List implementation so you won't suffer from the get performance.
The idea is to generate first a list of pairs (represented by a int[] array of size 2) which contains the values in the stream sliced by a window of size two with a gap of one. When we need to merge two lists, we check the emptiness and merge the gap of the last element of the first list with the first element of the second list. Then we apply a final transformation to filter only desired values and map them to have the desired output.
It might not be as simple as the accepted answer, but well it can be an alternative solution.
public static Collector<Integer, ?, List<Integer>> collectPrecedingValues() {
return Collectors.collectingAndThen(
Collector.of(() -> new ArrayList<int[]>(),
(l, elem) -> {
if (l.isEmpty()) l.add(new int[]{Integer.MAX_VALUE, elem});
else l.add(new int[]{l.get(l.size() - 1)[1], elem});
},
(l1, l2) -> {
if (l1.isEmpty()) return l2;
if (l2.isEmpty()) return l1;
l2.get(0)[0] = l1.get(l1.size() - 1)[1];
l1.addAll(l2);
return l1;
}), l -> l.stream().filter(arr -> arr[0] < arr[1]).map(arr -> arr[0]).collect(Collectors.toList()));
}
You can then wrap these two collectors in a utility collector method, check if the stream is parallel with isParallel an then decide which collector to return.
If you're willing to use a third party library and don't need parallelism, then jOOλ offers SQL-style window functions as follows
System.out.println(
Seq.of(10, 1, 15, 30, 2, 6)
.window()
.filter(w -> w.lead().isPresent() && w.value() < w.lead().get())
.map(w -> w.value())
.toList()
);
Yielding
[1, 15, 2]
The lead() function accesses the next value in traversal order from the window.
Disclaimer: I work for the company behind jOOλ
You can achieve that by using a bounded queue to store elements which flows through the stream (which is basing on the idea which I described in detail here: Is it possible to get next element in the Stream?
Belows example first defines instance of BoundedQueue class which will store elements going through the stream (if you don't like idea of extending the LinkedList, refer to link mentioned above for alternative and more generic approach). Later you just examine the two subsequent elements - thanks to the helper class:
public class Kata {
public static void main(String[] args) {
List<Integer> input = new ArrayList<Integer>(asList(10, 1, 15, 30, 2, 6));
class BoundedQueue<T> extends LinkedList<T> {
public BoundedQueue<T> save(T curElem) {
if (size() == 2) { // we need to know only two subsequent elements
pollLast(); // remove last to keep only requested number of elements
}
offerFirst(curElem);
return this;
}
public T getPrevious() {
return (size() < 2) ? null : getLast();
}
public T getCurrent() {
return (size() == 0) ? null : getFirst();
}
}
BoundedQueue<Integer> streamHistory = new BoundedQueue<Integer>();
final List<Integer> answer = input.stream()
.map(i -> streamHistory.save(i))
.filter(e -> e.getPrevious() != null)
.filter(e -> e.getCurrent() > e.getPrevious())
.map(e -> e.getPrevious())
.collect(Collectors.toList());
answer.forEach(System.out::println);
}
}