There is a condition that I need values in the following Set
Set<String> name = studentResponse
.stream()
.map(StudentResponse::getDetails)
.flatMap(List::stream)
.map(StudentDetail::getName())
.filter(s -> s.contains("A"))
.collect(Collectors.toSet());
I want to filter student names containing "A" if List<StudentDetail> details in StudentResponse contains more than 5 elements. If not, I want to take all names in StudentDetail. Is there any way to handle this condition?
You can use
Set<String> name = studentResponse
.stream()
.map(StudentResponse::getDetails)
.flatMap(l -> l.stream()
.map(StudentDetail::getName)
.filter(s -> l.size() <= 5 || s.contains("A")))
.collect(Collectors.toSet());
but it has the disadvantage of re-checking a list condition for every element, despite it shouldn't change during the entire traversal. A better solution is not to perform a filter operation when it is not necessary, like
Set<String> name = studentResponse
.stream()
.map(StudentResponse::getDetails)
.flatMap(l -> l.size() <= 5?
l.stream().map(StudentDetail::getName):
l.stream().map(StudentDetail::getName).filter(s -> s.contains("A")))
.collect(Collectors.toSet());
or, to avoid the code duplication:
Set<String> name = studentResponse
.stream()
.map(StudentResponse::getDetails)
.flatMap(l -> {
Stream<String> names = l.stream().map(StudentDetail::getName);
return l.size() <= 5? names: names.filter(s -> s.contains("A"));
})
.collect(Collectors.toSet());
Other way is using Supplier<T>
Supplier<Stream<List<StudentDetail>>> stuSup = ()-> studentResponse
.stream()
.map(StudentResponse::getDetails);
then perform a filter on it.
Stream<String> gtFive = stuSup.get()
.filter(d->d.size()>5)
.flatMap(List::stream)
.map(StudentDetail::getName())
.filter(s -> s.contains("A"));
and for less than five:
Stream<String> lteFive = stuSup.get()
.filter(d->d.size()<=5)
.flatMap(List::stream)
.map(StudentDetail::getName());
and finally, combine both of them.
Stream.concat(gtFive,lteFive).collect(toSet());
You can try out processing the two halves based on the conditions using partitioningBy.
Map<Boolean, List<StudentResponse>> greaterThanFive = studentResponse.stream()
.collect(Collectors.partitioningBy(sr -> sr.getDetails().size() > 5));
Set<String> names = Stream.concat(
greaterThanFive.get(Boolean.FALSE).stream()
.flatMap(sr -> sr.getDetails().stream())
.map(StudentDetail::getName),
greaterThanFive.get(Boolean.TRUE).stream()
.flatMap(sr -> sr.getDetails().stream())
.map(StudentDetail::getName)
.filter(name -> name.contains("A"))) // for details size more than 5
.collect(Collectors.toSet());
But there is no reason to choose it over a solution that can perform the partitioning on-the-fly.
You can use Map.Entry as a bag to collect all informations that are needed in the last filtering.
Set<String> name = studentResponse
.stream()
.flatMap(sr -> sr.getDetails().stream().map(
d -> Map.entry(sr.getDetails().size(), d.getName())))
.filter(e -> (e.getKey() <= 5) || (e.getKey() > 5 && e.getValue().contains("A")))
.map(Map.Entry::getValue)
.collect(Collectors.toSet());
Related
I have Map<String,Integer> map. I want to filter and sort the map by key and then get 5 percent of their number , I have such a function:
public List<String> getValuableSubStr(){
List<String> result = new ArrayList<>();
long size = map.entrySet().stream().filter(e -> e.getKey().length() ==3).count();
map.entrySet().stream()
.filter(e -> e.getKey().length() ==3)
.sorted(Map.Entry.<String,Integer>comparingByKey().reversed())
.limit((size*5)/100)
.peek(e -> result.add(e.getKey()));
return result;
}
But after calling the function , I get an empty list , although the map is not empty and forEach are printed normally .What did I do wrong?
peek is not a terminal operation, so it doesn't cause the stream to be evaluated.
Use forEach instead of peek.
Or, better, collect directly into a list.
return map.entrySet().stream()
.filter(e -> e.getKey().length() ==3)
.sorted(Map.Entry.<String,Integer>comparingByKey().reversed())
.limit((size*5)/100)
.map(Map.Entry::getKey)
.collect(Collectors.toList());
Actually, since you're dealing only with keys:
return map.keySet().stream()
.filter(e -> e.length() ==3)
.sorted(Comparator.reverseOrder())
.limit((size*5)/100)
.collect(Collectors.toList());
peek is more useful for debugging. See here:
it's an intermediate operation and we didn't apply a terminal operation
from https://www.baeldung.com/java-streams-peek-api
I would do
public List<String> getValuableSubStr(){
List<String> result = new ArrayList<>();
long size = map.entrySet().stream().filter(e -> e.getKey().length() ==3).count();
return map.entrySet().stream()
.filter(e -> e.getKey().length() ==3)
.sorted(Map.Entry.<String,Integer>comparingByKey().reversed())
.limit((size*5)/100)
.map(a -> a.getKey())
.collect(Collectors.toList());
}
How can I use a stream from two lists to get a list of unique entities?
Match only by username
public class Entity {
private String username;
private String password;
}
var first = Arrays.asList(
new Entity("user1", ""),
new Entity("user2", "")
new Entity("user3", "pass3"),
new Entity("user5", "pass5")
);
var second = Arrays.asList(
new Entity("user1", "pass1"),
new Entity("user2", "pass2"),
);
public static void foo(List<Entity> first, List<Entity> second) {
List<Entity>result = Stream.of(first, second)
.flatMap(List::stream)
?
?
.collect(Collectors.toList());
}
result must be list with Entity("user3", "pass3") and Entity("user5", "pass5")
you can make grouping by username:
var groupedData = Stream.concat(list1.stream(), list2.stream())
.collect(Collectors.groupingBy(Entity::getUsername));
and then filtered entity which size > 1:
groupedData.values().stream()
.filter(s -> s.size() == 1)
.flatMap(Collection::stream)
.collect(Collectors.toList());
or only one a big stream:
Stream.concat(list1.stream(), list2.stream())
.collect(Collectors.groupingBy(Entity::getUsername)).values().stream()
.filter(s -> s.size() == 1)
.flatMap(Collection::stream)
.collect(Collectors.toList());
Along with using groupingBy you can also use Collectors.toMap with merging (val1, val2) -> null to exclude elements getting to merge thus leaving only single elements:
List<Entity> result = Stream.concat(first.stream(), second.stream())
.collect(Collectors.toMap(Entity::getUsername,
val -> val, (val1, val2) -> null))
.values().stream()
.collect(Collectors.toList());
Probably it's not the best way
public static List<Entity> foo(List<Entity> first, List<Entity> second) {
List<Entity> arr = new ArrayList<>();
arr.addAll(first);
arr.addAll(second);
return arr
.stream()
.filter(entity -> (first.stream().map(Entity::getUsername).anyMatch(username -> username.equals(entity.getUsername())) &&
second.stream().map(Entity::getUsername).noneMatch(username -> username.equals(entity.getUsername()))) ||
(second.stream().map(Entity::getUsername).anyMatch(username -> username.equals(entity.getUsername())) &&
first.stream().map(Entity::getUsername).noneMatch(username -> username.equals(entity.getUsername()))))
.collect(Collectors.toList());
}
The logic in the filter is "Exclusive OR", as we don't have a straight way of doing that, we need to make the logic of (Condition1 and not Condition2) or (Condition2 and not Condition1).
lambda as a straight forward solution
concat the streams of first-list-entities not contained in second-list and vice versa
List<Entity> unique = Stream.concat(
first.stream().filter(e -> ! second.contains( e )),
second.stream().filter(e -> ! first.contains( e )) ).collect( toList() );
Function should return optional of most frequent last name (if it encountered at least two times) or optional empty if number of last names is the same or list of users is empty
This is what i came up with, but it doesnt return Optional.empty
#Override
public Optional<String> getMostFrequentLastName(final List<User> users) {
return users.stream()
.map(User::getLastName)
.distinct()
.collect
(Collectors.groupingBy(
Function.identity(),
Collectors.summingInt(w -> 1)
))
.entrySet()
.stream()
.filter(stringIntegerEntry -> stringIntegerEntry.getValue() >= 2)
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
.map(Map.Entry::getKey)
.findFirst();
}
This is my test class
public static void main(String[] args) {
Optional<String> optionalS = Stream.of(new User("name1"),
new User("name1"), new User("name2"), new User("name2"))
.map(User::getLastName)
.collect
(Collectors.groupingBy(
Function.identity(),
Collectors.counting()
))
.entrySet()
.stream()
.filter(stringIntegerEntry -> stringIntegerEntry.getValue() >= 2)
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
.map(Map.Entry::getKey)
.findFirst();
System.out.println(optionalS.toString());
}
Here is the awnser
Optional[name2]
But should be
Optional[empty]
You may use
Optional<String> optionalS =
Stream.of(new User("name1"), new User("name1"), new User("name2"), new User("name2"))
.collect(Collectors.groupingBy(User::getLastName, Collectors.counting()))
.entrySet()
.stream()
.filter(entry -> entry.getValue() >= 2)
.reduce((e1, e2) -> e1.getValue() < e2.getValue()? e2:
e1.getValue() > e2.getValue()? e1:
new AbstractMap.SimpleImmutableEntry<>(null, e1.getValue()))
.map(Map.Entry::getKey);
System.out.println(optionalS.toString());
Getting the maximum value is a form of Reduction. Since you want to get an empty optional in case of a tie, the simplest solution is to write the reduction function explicitly, use the Map.Entry with the bigger value if there is one, otherwise construct a new Map.Entry with a null key.
The result of the reduction is already an Optional, which will be empty if there were no elements (with a count >=2). So the last map step is applied on an Optional. If already empty, the map function won’t be evaluated and the resulting Optional stays empty. If the optional is not empty, but Map.Entry::getKey evaluates to null, the resulting optional will be empty.
It seems to me that if you have the same number of maximum of some different lastNames you want to return an Optional::empty, as such:
Map<String, Long> map =
Stream.of(new User("name1"),
new User("name1"),
new User("name2"),
new User("name2"))
.collect(Collectors.groupingBy(User::getLastName, Collectors.counting()));
map.entrySet()
.stream()
.max(Entry.comparingByValue())
.flatMap(en -> {
boolean b = map.entrySet()
.stream()
.filter(x -> !x.getKey().equals(en.getKey()))
.mapToLong(Entry::getValue)
.noneMatch(x -> x == en.getValue());
return b ? Optional.of(en.getKey()) : Optional.empty();
})
.ifPresent(System.out::println);
}
Here my monster for you:
Optional<String> optionalS = Stream.of(
new User("name1"),
new User("name1"),
new User("name2"),
new User("name2"))
.map(User::getLastName)
.collect(
Collectors.groupingBy(
Function.identity(),
Collectors.counting()
))
.entrySet()
.stream()
.filter(stringIntegerEntry -> stringIntegerEntry.getValue() >= 2)
.collect(
Collectors.groupingBy(
Map.Entry::getValue,
Collectors.toList()
))
.entrySet()
.stream()
.sorted(Comparator.comparing(
Map.Entry::getKey,
Comparator.reverseOrder()))
.map(Map.Entry::getValue)
.findFirst()
.filter(x -> x.size() == 1)
.map(x -> x.get(0).getKey());
System.out.println(optionalS);
As far as I undestand your solution in stream you code creates
Map<String(lastname),Integer(number of occurence)>
and then filter that map where number of occurence >=2 and in your test case you have map with entries:
<"name1",2>
<"name2",2>
So ordering by value will still return two values.
You should try create
Map<Integer,List<String>>
which will store number of occurence -> names, then filter map keys, sort them descending and (in map value) you will get most frequently lastname (or lastnames if there were more than once in input).
//edit
Below short snippet with my solution:
Map<Integer, List<String>> map = new HashMap<>();
map.put(2,Arrays.asList("name1","name2"));
Optional<String> optionalS = map
.entrySet()
.stream()
.sorted(Map.Entry.comparingByKey(Comparator.reverseOrder()))
.findFirst() //get max{map's keys}
.filter(x->x.getValue().size() == 1) //get lastname that occured only once
.map(x->x.getValue().get(0)); //get that lastname (above filter check that list has only one element) or Optional.empty if stream didn't find any
System.out.println(optionalS.toString());
I skipped the part of creating map.
P.S. You can replace HashMap with TreeMap with custom comparator to avoid sorting in stream.
Imagine that I have a List<Map<String,Object>>:
[{'id':1,'name':'xyz'},{'id':2,'name':'abc'},{'id':3,'name':'pqr'}]
And I need to generate another list including the name in the above list:
List<String>
Avoiding using a loop, is it possible to achieve this by using java stream api?
List<String> names = list.stream()
.map(i -> i.get("name").toString())
.collect(Collectors.toList());
Since i.get("name").toString() might produce a NPE, it's smart to filter out maps that don't contain the key "name":
List<String> names = list.stream()
.filter(i -> i.containsKey("name"))
.map(i -> i.get("name").toString())
.collect(Collectors.toList());
or
List<String> names = list.stream()
.map(i -> i.get("name"))
.filter(Objects::nonNull)
.map(Object::toString)
.collect(Collectors.toList());
Having a list like the following one
List<Integer> values = new ArrayList<Integer>();
values.add(1);
values.add(0);
values.add(1);
values.add(1);
values.add(0);
I want to print the elements > 0 adding them a value, for example 10, by using Java 8 Stream. For example:
values.stream()
.filter(val -> val > 0)
// HERE add 10
.forEach(System.out::println);
Is it possible to do that? If yes, how?
Use the map operation
values.stream()
.filter(val -> val>0)
.map(x -> x+10)
.forEach(System.out::println);
If you need to keep the values, do
List<Integer> newValues = values.stream()
.filter(val -> val>0)
.map(x -> x+10)
.collect(Collectors.toList());
If you want to print and save new data in one go you can use peek() like below -
List<Integer> newValues = myList.stream().filter(val -> val>0).map(x -> x+10)
.peek(System.out::println).collect(Collectors.toList());
values = StreamSupport.stream(values)
.filter(val -> val > 0)
.forEach(val -> val += 10)
.collect(Collectors.toList());
then you can use:
values.stream()
.forEach(System.out::println);