Related
I am trying to use streams in order to iterate over two lists of lists in order to verify if the inner lists sizes are the same for the same index. I have managed to achieve this using streams, but I have to rewrite using an IntStream and mapToObj.
My current approach is:
List<List<String>> a = config.getStrips();
List<List<Integer>> b = slotMachineConfig.getWeights();
a.stream()
.filter(first ->
b.stream()
.allMatch(second -> second.size() == first.size())
)
.findFirst()
.orElseThrow(InvalidConfigException::new);
The problem is that I cannot be sure that the sizes will correspond for the big lists, so I have to rewrite this using IntStream and also using indexes for each list.
What I have so far, but does not work looks like this, I am trying to write a "validate" function in order to verify the inner lists, but it seems like I get an error there saying "no instance of type variable U exist so that void conforms to U".
IntStream.range(0, a.size())
.mapToObj(i -> validate(i, a.get(i), b.get(i)))
.findFirst()
.orElseThrow(SlotMachineInvalidConfigException::new);
public void validate(int index, List<String> firstList, List<Integer> secondList) {
How can I rewrite my method using IntStream and mapToObj, can anyone help me?
You have the right idea but you don't really need a separate validation function if you are just comparing sizes. Here's a working example that supports any list types:
public class ListSizeMatcher {
public <T,S> boolean sizeMatches(List<List<T>> list1, List<List<S>> list2) {
return list1.size() == list2.size()
&& IntStream.range(0, list1.size())
.allMatch(i -> list1.get(i).size() == list2.get(i).size());
}
public static void main(String[] args) {
ListSizeMatcher matcher = new ListSizeMatcher();
System.out.println(matcher.sizeMatches(List.of(List.of(1)), List.of(List.of("a"), List.of("b"))));
System.out.println(matcher.sizeMatches(List.of(List.of(1)), List.of(List.of("a", "b"))));
System.out.println(matcher.sizeMatches(List.of(List.of(1, 2)), List.of(List.of("a", "b"))));
}
}
Note that from a design perspective if each item in the list matches the corresponding item in a separate list you'd be better off creating a single class that contains both items.
If I understand correctly, I think something like this would work:
List<List<String>> a = config.getStrips();
List<List<Integer>> b = slotMachineConfig.getWeights();
if (a.size() != b.size()) throw new InvalidConfigException();
boolean allTheSame = IntStream.range(0, a.size())
.map(i -> a.get(i).size() - b.get(i).size())
.allMatch(diff -> diff == 0);
if (!allTheSame) throw new InvalidConfigException();
For the record, your validate function returns void but I'll assume it was meant to return a boolean
here is a more compact version
List<List<String>> a = new LinkedList<>();
List<List<Integer>> b = new LinkedList<>();
boolean match = IntStream.range(0, a.size())
.mapToObj(i -> a.get(i).size() == b.get(i).size())
.reduce(Boolean::logicalAnd).orElseThrow(InvalidConfigException::new);
if (!match) {
throw new InvalidConfigException();
}
Alternative:
List<List<String>> a = new LinkedList<>();
List<List<Integer>> b = new LinkedList<>();
if (IntStream.range(0, a.size()).filter(i -> a.get(i).size() != b.get(i).size()).count() > 0){
throw new InvalidConfigException();
};
At the end of the day it only takes 1 to be different and fail.
The error means that the validate method cannot be void and it is expected to return some valid value (possibly boolean).
If the inner lists are supposed to have the equal sizes to be valid, the check may look as follows:
// assuming the sizes of outer lists are equal
boolean allSizesEqual = IntStream.range(0, a.size())
.allMatch(i -> a.get(i).size() == b.get(i).size());
if (!allSizesEqual) {
throw new InvalidConfigException("Not all sizes are valid");
}
If there's a need to find specific indexes where a discrepancy is detected:
List<Integer> badIndexes = IntStream.range(0, a.size())
.filter(i -> a.get(i).size() != b.get(i).size()) // IntStream
.boxed() // Stream<Integer>
.collect(Collectors.toList());
if (!badIndexes.isEmpty()) {
throw new InvalidConfigException("Different indexes found: " + badIndexes);
}
Or validate method could be fixed to return appropriate value for the filter:
boolean allItemsValid = IntStream.range(0, a.size())
.allMatch(i -> listsAreValid(a.get(i), b.get(i)));
if (!allItemsValid) {
throw new InvalidConfigException("Not all entries are valid");
}
public boolean listsAreValid(List<String> innerA, List<Integer> innerB) {
// any advanced logic
return innerA.size() == innerB.size();
}
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);
}
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 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()));
}
}
);
Are there any methods to do so? I was looking but couldn't find any.
Another question: I need these methods so I can filter files.
Some are AND filters and some are OR filters (like in set theory), so I need to filter according to all files and the unite/intersects ArrayLists that holds those files.
Should I use a different data structure to hold the files? Is there anything else that would offer a better runtime?
Here's a plain implementation without using any third-party library. Main advantage over retainAll, removeAll and addAll is that these methods don't modify the original lists input to the methods.
public class Test {
public static void main(String... args) throws Exception {
List<String> list1 = new ArrayList<String>(Arrays.asList("A", "B", "C"));
List<String> list2 = new ArrayList<String>(Arrays.asList("B", "C", "D", "E", "F"));
System.out.println(new Test().intersection(list1, list2));
System.out.println(new Test().union(list1, list2));
}
public <T> List<T> union(List<T> list1, List<T> list2) {
Set<T> set = new HashSet<T>();
set.addAll(list1);
set.addAll(list2);
return new ArrayList<T>(set);
}
public <T> List<T> intersection(List<T> list1, List<T> list2) {
List<T> list = new ArrayList<T>();
for (T t : list1) {
if(list2.contains(t)) {
list.add(t);
}
}
return list;
}
}
Collection (so ArrayList also) have:
col.retainAll(otherCol) // for intersection
col.addAll(otherCol) // for union
Use a List implementation if you accept repetitions, a Set implementation if you don't:
Collection<String> col1 = new ArrayList<String>(); // {a, b, c}
// Collection<String> col1 = new TreeSet<String>();
col1.add("a");
col1.add("b");
col1.add("c");
Collection<String> col2 = new ArrayList<String>(); // {b, c, d, e}
// Collection<String> col2 = new TreeSet<String>();
col2.add("b");
col2.add("c");
col2.add("d");
col2.add("e");
col1.addAll(col2);
System.out.println(col1);
//output for ArrayList: [a, b, c, b, c, d, e]
//output for TreeSet: [a, b, c, d, e]
This post is fairly old, but nevertheless it was the first one popping up on google when looking for that topic.
I want to give an update using Java 8 streams doing (basically) the same thing in a single line:
List<T> intersect = list1.stream()
.filter(list2::contains)
.collect(Collectors.toList());
List<T> union = Stream.concat(list1.stream(), list2.stream())
.distinct()
.collect(Collectors.toList());
If anyone has a better/faster solution let me know, but this solution is a nice one liner that can be easily included in a method without adding a unnecessary helper class/method and still keep the readability.
list1.retainAll(list2) - is intersection
union will be removeAll and then addAll.
Find more in the documentation of collection(ArrayList is a collection)
http://download.oracle.com/javase/1.5.0/docs/api/java/util/Collection.html
Unions and intersections defined only for sets, not lists. As you mentioned.
Check guava library for filters. Also guava provides real intersections and unions
static <E> Sets.SetView<E >union(Set<? extends E> set1, Set<? extends E> set2)
static <E> Sets.SetView<E> intersection(Set<E> set1, Set<?> set2)
You can use CollectionUtils from apache commons.
The solution marked is not efficient. It has a O(n^2) time complexity. What we can do is to sort both lists, and the execute an intersection algorithm as the one below.
private static ArrayList<Integer> interesect(ArrayList<Integer> f, ArrayList<Integer> s) {
ArrayList<Integer> res = new ArrayList<Integer>();
int i = 0, j = 0;
while (i != f.size() && j != s.size()) {
if (f.get(i) < s.get(j)) {
i ++;
} else if (f.get(i) > s.get(j)) {
j ++;
} else {
res.add(f.get(i));
i ++; j ++;
}
}
return res;
}
This one has a complexity of O(n log n + n) which is in O(n log n).
The union is done in a similar manner. Just make sure you make the suitable modifications on the if-elseif-else statements.
You can also use iterators if you want (I know they are more efficient in C++, I dont know if this is true in Java as well).
One-liners since JAVA 8
Union
if there are no duplicates:
return concat(a.stream(), b.stream()).collect(toList());
union and distinct:
return concat(a.stream(), b.stream()).distinct().collect(toList());
union and distinct if Collection/Set return type:
return concat(a.stream(), b.stream()).collect(toSet());
Intersect
if no duplicates:
return a.stream().filter(b::contains).collect(toList());
PERFORMANCE: If collection b is huge and not O(1), then pre-optimize the filter performance by adding 1 line before return: Copy to HasSet (import java.util.Set;):
... b = Set.copyOf(b);
intersect and distinct:
return a.stream().distinct().filter(b::contains).collect(toList());
- imports
import static java.util.stream.Stream.concat;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
I think you should use a Set to hold the files if you want to do intersection and union on them. Then you can use Guava's Sets class to do union, intersection and filtering by a Predicate as well. The difference between these methods and the other suggestions is that all of these methods create lazy views of the union, intersection, etc. of the two sets. Apache Commons creates a new collection and copies data to it. retainAll changes one of your collections by removing elements from it.
Here is a way how you can do an intersection with streams (remember that you have to use java 8 for streams):
List<foo> fooList1 = new ArrayList<>(Arrays.asList(new foo(), new foo()));
List<foo> fooList2 = new ArrayList<>(Arrays.asList(new foo(), new foo()));
fooList1.stream().filter(f -> fooList2.contains(f)).collect(Collectors.toList());
An example for lists with different types. If you have a realtion between foo and bar and you can get a bar-object from foo than you can modify your stream:
List<foo> fooList = new ArrayList<>(Arrays.asList(new foo(), new foo()));
List<bar> barList = new ArrayList<>(Arrays.asList(new bar(), new bar()));
fooList.stream().filter(f -> barList.contains(f.getBar()).collect(Collectors.toList());
You can use commons-collections4 CollectionUtils
Collection<Integer> collection1 = Arrays.asList(1, 2, 4, 5, 7, 8);
Collection<Integer> collection2 = Arrays.asList(2, 3, 4, 6, 8);
Collection<Integer> intersection = CollectionUtils.intersection(collection1, collection2);
System.out.println(intersection); // [2, 4, 8]
Collection<Integer> union = CollectionUtils.union(collection1, collection2);
System.out.println(union); // [1, 2, 3, 4, 5, 6, 7, 8]
Collection<Integer> subtract = CollectionUtils.subtract(collection1, collection2);
System.out.println(subtract); // [1, 5, 7]
retainAll will modify your list
Guava doesn't have APIs for List (only for set)
I found ListUtils very useful for this use case.
Use ListUtils from org.apache.commons.collections if you do not want to modify existing list.
ListUtils.intersection(list1, list2)
In Java 8, I use simple helper methods like this:
public static <T> Collection<T> getIntersection(Collection<T> coll1, Collection<T> coll2){
return Stream.concat(coll1.stream(), coll2.stream())
.filter(coll1::contains)
.filter(coll2::contains)
.collect(Collectors.toSet());
}
public static <T> Collection<T> getMinus(Collection<T> coll1, Collection<T> coll2){
return coll1.stream().filter(not(coll2::contains)).collect(Collectors.toSet());
}
public static <T> Predicate<T> not(Predicate<T> t) {
return t.negate();
}
If the objects in the list are hashable (i.e. have a decent hashCode and equals function), the fastest approach between tables approx. size > 20 is to construct a HashSet for the larger of the two lists.
public static <T> ArrayList<T> intersection(Collection<T> a, Collection<T> b) {
if (b.size() > a.size()) {
return intersection(b, a);
} else {
if (b.size() > 20 && !(a instanceof HashSet)) {
a = new HashSet(a);
}
ArrayList<T> result = new ArrayList();
for (T objb : b) {
if (a.contains(objb)) {
result.add(objb);
}
}
return result;
}
}
I was also working on the similar situation and reached here searching for help. Ended up finding my own solution for Arrays.
ArrayList AbsentDates = new ArrayList(); // Will Store Array1-Array2
Note : Posting this if it can help someone reaching this page for help.
ArrayList<String> AbsentDates = new ArrayList<String>();//This Array will store difference
public void AbsentDays() {
findDates("April", "2017");//Array one with dates in Month April 2017
findPresentDays();//Array two carrying some dates which are subset of Dates in Month April 2017
for (int i = 0; i < Dates.size(); i++) {
for (int j = 0; j < PresentDates.size(); j++) {
if (Dates.get(i).equals(PresentDates.get(j))) {
Dates.remove(i);
}
}
AbsentDates = Dates;
}
System.out.println(AbsentDates );
}
Intersection of two list of different object based on common key - Java 8
private List<User> intersection(List<User> users, List<OtherUser> list) {
return list.stream()
.flatMap(OtherUser -> users.stream()
.filter(user -> user.getId()
.equalsIgnoreCase(OtherUser.getId())))
.collect(Collectors.toList());
}
public static <T> Set<T> intersectCollections(Collection<T> col1, Collection<T> col2) {
Set<T> set1, set2;
if (col1 instanceof Set) {
set1 = (Set) col1;
} else {
set1 = new HashSet<>(col1);
}
if (col2 instanceof Set) {
set2 = (Set) col2;
} else {
set2 = new HashSet<>(col2);
}
Set<T> intersection = new HashSet<>(Math.min(set1.size(), set2.size()));
for (T t : set1) {
if (set2.contains(t)) {
intersection.add(t);
}
}
return intersection;
}
JDK8+ (Probably Best Performance)
public static <T> Set<T> intersectCollections(Collection<T> col1, Collection<T> col2) {
boolean isCol1Larger = col1.size() > col2.size();
Set<T> largerSet;
Collection<T> smallerCol;
if (isCol1Larger) {
if (col1 instanceof Set) {
largerSet = (Set<T>) col1;
} else {
largerSet = new HashSet<>(col1);
}
smallerCol = col2;
} else {
if (col2 instanceof Set) {
largerSet = (Set<T>) col2;
} else {
largerSet = new HashSet<>(col2);
}
smallerCol = col1;
}
return smallerCol.stream()
.filter(largerSet::contains)
.collect(Collectors.toSet());
}
If you don't care about performance and prefer smaller code just use:
col1.stream().filter(col2::contains).collect(Collectors.toList());
First, I am copying all values of arrays into a single array then I am removing duplicates values into the array. Line 12, explaining if same number occur more than time then put some extra garbage value into "j" position. At the end, traverse from start-end and check if same garbage value occur then discard.
public class Union {
public static void main(String[] args){
int arr1[]={1,3,3,2,4,2,3,3,5,2,1,99};
int arr2[]={1,3,2,1,3,2,4,6,3,4};
int arr3[]=new int[arr1.length+arr2.length];
for(int i=0;i<arr1.length;i++)
arr3[i]=arr1[i];
for(int i=0;i<arr2.length;i++)
arr3[arr1.length+i]=arr2[i];
System.out.println(Arrays.toString(arr3));
for(int i=0;i<arr3.length;i++)
{
for(int j=i+1;j<arr3.length;j++)
{
if(arr3[i]==arr3[j])
arr3[j]=99999999; //line 12
}
}
for(int i=0;i<arr3.length;i++)
{
if(arr3[i]!=99999999)
System.out.print(arr3[i]+" ");
}
}
}
After testing, here is my best intersection approach.
Faster speed compared to pure HashSet Approach. HashSet and HashMap below has similar performance for arrays with more than 1 million records.
As for Java 8 Stream approach, speed is quite slow for array size larger then 10k.
Hope this can help.
public static List<String> hashMapIntersection(List<String> target, List<String> support) {
List<String> r = new ArrayList<String>();
Map<String, Integer> map = new HashMap<String, Integer>();
for (String s : support) {
map.put(s, 0);
}
for (String s : target) {
if (map.containsKey(s)) {
r.add(s);
}
}
return r;
}
public static List<String> hashSetIntersection(List<String> a, List<String> b) {
Long start = System.currentTimeMillis();
List<String> r = new ArrayList<String>();
Set<String> set = new HashSet<String>(b);
for (String s : a) {
if (set.contains(s)) {
r.add(s);
}
}
print("intersection:" + r.size() + "-" + String.valueOf(System.currentTimeMillis() - start));
return r;
}
public static void union(List<String> a, List<String> b) {
Long start = System.currentTimeMillis();
Set<String> r= new HashSet<String>(a);
r.addAll(b);
print("union:" + r.size() + "-" + String.valueOf(System.currentTimeMillis() - start));
}
retainAll() method use for finding common element..i.e;intersection
list1.retainAll(list2)
You can use the methods:
CollectionUtils.containsAny and CollectionUtils.containsAll
from Apache Commons.
Final solution:
//all sorted items from both
public <T> List<T> getListReunion(List<T> list1, List<T> list2) {
Set<T> set = new HashSet<T>();
set.addAll(list1);
set.addAll(list2);
return new ArrayList<T>(set);
}
//common items from both
public <T> List<T> getListIntersection(List<T> list1, List<T> list2) {
list1.retainAll(list2);
return list1;
}
//common items from list1 not present in list2
public <T> List<T> getListDifference(List<T> list1, List<T> list2) {
list1.removeAll(list2);
return list1;
}
If you had your data in Sets you could use Guava's Sets class.
If the number matches than I am checking it's occur first time or not with help of "indexOf()" if the number matches first time then print and save into in a string so, that when the next time same number matches then it's won't print because due to "indexOf()" condition will be false.
class Intersection
{
public static void main(String[] args)
{
String s="";
int[] array1 = {1, 2, 5, 5, 8, 9, 7,2,3512451,4,4,5 ,10};
int[] array2 = {1, 0, 6, 15, 6, 5,4, 1,7, 0,5,4,5,2,3,8,5,3512451};
for (int i = 0; i < array1.length; i++)
{
for (int j = 0; j < array2.length; j++)
{
char c=(char)(array1[i]);
if(array1[i] == (array2[j])&&s.indexOf(c)==-1)
{
System.out.println("Common element is : "+(array1[i]));
s+=c;
}
}
}
}
}