I have a list of items. Each item has 3 properties: name, section and amount. The amount can be positive/negative. The same item can be several times in the same section.
I want to group the items in the list by section and name. If the sum of this grouping equals 0 - remove all the items with this section and name.
I got this far
items.stream().collect(Collectors.groupingBy(ItemData::getSection))....
Now I need the second grouping and compare it to 0 in order to add it as a predicate to removeIf
items.removeIf(item -> predicate(item));
You can first group the ItemData based on section and name with sum of amount as value
Map<String,Long> result = details.stream()
.collect(Collectors.groupingBy(det -> det.getSection()+"_"+det.getName(),
Collectors.summingLong(det->det.getAmount())));
And then remove the entries from Map having value !=0
result.entrySet().removeIf(det->det.getValue() != 0);
And then remove the elements from items list having entry in Map with key comparison
items.removeIf(item -> result.containsKey(item.getSection()+"_"+item.getName()));
You can also eliminate the entries having value 0 by using stream
Map<String,Long> result = details.stream()
.collect(Collectors.groupingBy(det -> det.getSection()+"_"+det.getName(),
Collectors.summingLong(det->det.getCount())))
.entrySet()
.stream()
.filter(entry->entry.getValue()==0)
.collect(Collectors.toMap(Map.Entry::getKey,Map.Entry::getValue));
Alternatively you could use a BiPredicate which accepts an Item and List<Item>
BiPredicate<Item,List<Item>> biPred =
(item, list) -> list.stream()
.filter(i -> i.getName().equals(item.getName()) &&
i.getSection().equals(item.getSection()))
.mapToLong(Item::getAmount)
.sum() == 0;
myList.removeIf(e -> biPred.test(e, myList));
Related
I have 2 lists as the following
List<String> firstList = Arrays.asList("E","B","A","C");
List<String> secondList = Arrays.asList("Alex","Bob","Chris","Antony","Ram","Shyam");
I want the output in the form of a map having values in the second list mapped to elements in the first list based on first character.
For example I want the output as
Map<String,List<String>> outputMap;
and it has the following content
key -> B, value -> a list having single element Bob
key -> A, value -> a list having elements Alex and Antony
key -> C, value -> a list having single element Chris
I did something like this
firstList.stream()
.map(first->
secondList.stream().filter(second-> second.startsWith(first))
.collect(Collectors.toList())
);
and can see the elements of the second list group by first character. However I am unsure as to how to store the same in a map .
Please note that my question is more from the perspective of using the streaming API to get the job done.
I'm pretty sure that instead of nesting streaming of both lists you should just group the second one by first letter and filter values by testing whether the first letter is in the first list
final Map<String, List<String>> result = secondList.stream()
.filter(s -> firstList.contains(s.substring(0, 1)))
.collect(Collectors.groupingBy(s -> s.substring(0, 1)));
You can also extract s.substring(0, 1) to some
String firstLetter(String string)
method to make code a little bit more readable
Just move that filter to a toMap() collector:
Map<String, List<String>> grouped = firstList.stream()
.collect(Collectors.toMap(first -> first, first -> secondList.stream()
.filter(second -> second.startsWith(first))
.collect(Collectors.toList())));
If you want only keys that have matching names, you can
.filter(first -> secondList.stream().anyMatch(second -> second.startsWith(first)))
Suppose I have following data :
Question :
I want to find zscore of largest orderId where Pair is 'AB', OrderType is 'Buy' and status is 'InProgress'.
NOTE: I stored this data into HashMap name is orderBook where Key is orderId and Value is OrderModel (PairName, OrderType, Status, zscore).
Solution 1 :
int maxOrderId = 0 ;
getOrderBook().entrySet().stream()
.filter(e -> e.getValue().getPairName().equals("AB")
&& e.getValue().getCompletedStatus().equals("InProgress")
&& e.getValue().getOrderType().equals("Buy"))
.forEach(o -> {
if (maxOrderId < o.getKey()) {
maxOrderId = o.getKey();
}
});
double zscore = getOrderBook().get(maxOrderId).getzScore();
System.out.println("Order ID :"+ maxOrderId +", Zscore :"+zscore);
output : Order ID : 5, Zscore : -2.5
I can find zscore using above code but I want to find in one go.
So How can I find the zscore of largest OrderId using Java 8 / streams in one line ?
Is there any better way than my code ?
What you're looking for is the max method:
Optional<Entry<Long,Order>> maxIdEntry = getOrderBook()
.entrySet()
.stream()
.filter(/* your filter logic */)
.max(Comparator.comparing(Entry::getKey));
This yields an Optional, so either use the isPresent() and get() methods or the ifPresent(Consumer<T> consumer) method for processing the result
You can use max() using Comparator to get largest OrderId and use map of Optional to map zScore .
double zscore = getOrderBook()
.entrySet()
.stream()
.filter(e -> e.getValue().getPairName().equals("AB")
&& e.getValue().getCompletedStatus().equals("InProgress")
&& e.getValue().getOrderType().equals("Buy"))
.max(Comparator.comparing(Entry::getKey))
.map(e -> e.getValue().getzScore())
.orElse(0);
The already existing answer are excellent. There are more ways:
How about using TreeMap which is able to keep the keys sorted? As long as the key is ex. a String, you don't even need to pass a Comparator.
// create a copy of HashMap as a TreeMap
NavigableMap<String, Order> navigableMap = new TreeMap<>(getOrderBook());
// remove unwanted entries (inverted condition)
navigableMap.entrySet().removeIf(e ->
!e.getValue().getPairName().equals("AB") ||
!e.getValue().getCompletedStatus().equals("InProgress") ||
!e.getValue().getOrderType().equals("Buy"));
// NavigableMap::lastEntry gets an entry with the highest key (by the comparator)
double zscore = sortedMap.lastEntry().getValue().getzScore();
Map is defined as:
Map<Integer,String> map = new HashMap<>();
map.put(2,"ram");
map.put(3,"ram");
map.put(4,"gopal");
map.put(5,"madan");
map.put(6,"shyam");
map.put(7,"gopal");
map.put(8,"ram");
My expected output is List which contains only keys for which there is no duplicate values.
5
6
My approach and thought process:
Thought process 1 :
I would take map.entrySet().stream().map(....) and then take another stream inside map and filter the values for which duplicate values are present.
The approach soon got wasted as the first indexed value would be again compared in the nested stream and i would happen to filter out all elements thus.
Thought process 2
I kept values in different List by :
List<String> subList = map.entrySet().stream()
.map((k)->k.getValue())
.collect(Collectors.toList());
and then:
map.entrySet().stream()
.filter(s ->
subList.contains(s.getValue()) )
.map(Map.Entry::getKey)
.collect(Collectors.toList());
But I am getting the output as
2
3
4
5
6
7
8
The output is obvious as the value that i am picking as s from stream i am comparing it in the pool where the value will be always present at-least one time.
I again thought then that if i could have a counter that count count and if the value is present then it would increment, but again all seems very much vague now.
Any ways in which i can iterate by index using stream, so that i can always leave the key value which i am taking and just comparing with rest of values.
will love to get a brief explanation.
You can break the task into two step. first count value repeating by groupingBy() and counting() collectors.
Map<String,Long> valueCount = map.values()
.stream()
.collect(Collectors.groupingBy(Function.identity(),Collectors.counting()));
Its result is:
{madan=1, shyam=1, gopal=2, ram=3}
second step is to find only keys which their values are not duplicate. so to attain this you can use filter() and filtering the map by previous step result.
map.entrySet()
.stream()
.filter(entry -> valueCount.get(entry.getValue())==1).map(Map.Entry::getKey)
.collect(Collectors.toList())
You can filter the values with frequency 1 while creating the subList such as:
Set<String> uniqueSet = map.values().stream()
.collect(Collectors.groupingBy(a -> a, Collectors.counting()))
.entrySet().stream()
.filter(a -> a.getValue() == 1)
.map((Map.Entry::getKey))
.collect(Collectors.toSet());
and then perform the same operation as:
Set<Integer> result = map.entrySet().stream()
.filter(e -> uniqueSet.contains(e.getValue()))
.map(Map.Entry::getKey)
.collect(Collectors.toSet());
Or as Holger pointed out in the comments, instead of counting, you could do away with a Boolean value to filter unique values as:
Set<String> uniqueSet = map.values().stream()
.collect(Collectors.toMap(Function.identity(), v -> true, (a,b) -> false))
.entrySet().stream()
.filter(Map.Entry::getValue)
.map((Map.Entry::getKey))
.collect(Collectors.toSet());
I have two list of objects accounts and salaries and I need to iterate the list of objects. If the id matches I need to update the account object.
I have list1 and list2 these two objects are different object type. we need to update the object(param) in list1 with list2 object(param).
Example
if(accounts !=null && salaries!=null) { // checking for nulls
for (Account obj1 : accounts) {// iterating objects
for (Salary obj2 : salaries) {
String id = ibj2.getId();
if (id.equals(obj1.getId())) {// id checks
obj1.setxxxx(obj2.getxxxx());// set the value
}
}
}
}
I tried:
list1.stream().flatMap(x -> list2 .stream() .filter(y -> x.getId().equals(y.getId())));
Your flatMap (suggested in the comment), will produce a Stream<Salary>, which won't allow you do modify the corresponding Account instances.
You can create a Stream of Accounts and their corresponding Salary and run forEach on that Stream:
accounts.stream()
.flatMap(a->salaries.stream()
.filter(s -> s.getID().equals(a.getID())
.map(s -> new SimpleEntry<Account,Salary)(a,s)))
.forEach(e -> e.getKey().setxxxx(e.getValue().getxxxx()));
The final operation, obj1.setxxxx(obj2.getxxxx()); requires to have both obj1 and obj2. that dictates the item that is streamed from both lists
list1.stream()
.forEach(obj1 ->
list2.stream()
.filter(obj2 -> obj1.getId().equals(obj2.getId()))
.findFirst()
.ifPresent(obj2 -> obj1.setxxxx(obj2.getxxxx()))
);
I would always suggest to create a Map since the lookup cost will decrease and it will become more readable.
Map<String, List<Salary>> salaryById = salaries.stream().collect(Collectors.groupingBy(Salary::getId));
accounts.forEach(a -> CollectionUtils.emptyIfNull(salaryById.get(a.getId())).forEach(s -> s.setxxxx(..)));
In case Account Salary <-> Account is One to One you change grouping to Collectors.toMap(..)
Is there a way to collect both filtered and not filtered value in java 8 filter ?
One way is:
.filter( foo -> {
if(!foo.apply()){
// add to required collection
}
return foo.apply();
}
Is there a better alternative ?
Map<Boolean, List<Foo>> map =
collection.stream().collect(Collectors.partitioningBy(foo -> foo.isBar());
You can use a ternary operator with map, so that the function you apply is either the identity for some condition, In below example I calculating square of even numbers and keeping odd numbers as it is.
List<Integer> temp = arrays.stream()
.map(i -> i % 2 == 0 ? i*i : i)
.collect(Collectors.toList());
In your case it would be like this :
List<Integer> temp = arrays.stream()
.map(!foo.apply() -> ? doSomething: doSomethingElse)
.collect(Collectors.toList());