I got 3 classes.
Angestellte (simply contains some stuff like names etc.)
Department (only contains a String)
and ttest (for testing obviously)
I want to put all the workers "Angestellte" into their Departments. So basically the output should be:
Personalabteilung: 4
Buchhaltung: 3
Fertigung: 3
I am trying to put the Map as Map with Department and Long
but ultimately I would like to have the Map with String and Long.
I also think my Collectors.counting() doesn't work that way I put it.
I don't really know how to address my Stream of Strings after I have already mapped it. Thats why I put three ? in the code.
import java.util.*;
import java.util.function.Supplier;
import java.util.stream.Collectors;
public class ttest {
public static void main(String[] args){
Department d1 = new Department("Personalabteilung");
Department d2 = new Department("Buchhaltung");
Department d3 = new Department("Fertigung");
List<Angestellte> AlleAng = Arrays.asList(
new Angestellte("Sandra","Bullock",d3,3450, "Female"),
new Angestellte("Yutta","Knie",d1,2800, "Female"),
new Angestellte("Ludwig","Herr",d3,3850, "Male"),
new Angestellte("Peter","Pan",d2,1850, "Male"),
new Angestellte("Nicky","Smith",d3,2100, "Female"),
new Angestellte("Herbert","Rotwein",d2,2450, "Male"),
new Angestellte("Sandra","Siech",d1,1100, "Female"),
new Angestellte("Florian","Schwarzpeter",d2,2800, "Male"),
new Angestellte("Herrietta","Glas",d1,2100, "Female"),
new Angestellte("Brock","Builder",d1,6000, "Male"));
Map<Department, Long> DepAnz = AlleAng.stream()
.map(a -> a.getDep())
.collect(Collectors.toMap(a.getDep???, Collectors.counting()));
}
}
If you want to group by department and your getter is called getDep() You can do
Map<Department, Long> DepAnz = AlleAng.stream()
.collect(Collectors.groupingBy(a -> a.getDep(), Collectors.counting()));
You need to use a group by:
Map<Department, Long> DepAnz =
AlleAng.stream()
.collect(Collectors.groupingBy(Angestellte::getDep, Collectors.counting()));
The groupingBy(classifier, downstream) collector is a collector that collects the Stream element into a Map where the keys are returned by the classifier and the values are the result of applying the downstream collector to all Stream elements having an equal key. In this case, what we want is to count the values so we use Collectors.counting() as the downstream collector.
If you want to group by the the name of the department instead, you could have, assmuming the getter is called .getName() to retrieve the name:
Map<String, Long> DepAnz =
AlleAng.stream()
.collect(Collectors.groupingBy(a -> a.getDep().getName(), Collectors.counting()));
This will return a count of all the different name of departements.
Map<String, Long> map = AlleAng.stream().collect(Collectors.toMap(a -> a.getDept().getName(), a -> 1L, (Long acc, Long newValue) -> acc + newValue));
This is such a verbose usage of lambdas to achieve what you need.
Use a toMap Collector in order to map a specific key and value based on the actual Angestellte references you have got.
Of course, the groupers functions are best suited, but this works as a generic example for map grouping by some criterion
Related
This question is about Java Streams' groupingBy capability.
Suppose I have a class, WorldCup:
public class WorldCup {
int year;
Country champion;
// all-arg constructor, getter/setters, etc
}
and an enum, Country:
public enum Country {
Brazil, France, USA
}
and the following code snippet:
WorldCup wc94 = new WorldCup(1994, Country.Brazil);
WorldCup wc98 = new WorldCup(1998, Country.France);
List<WorldCup> wcList = new ArrayList<WorldCup>();
wcList.add(wc94);
wcList.add(wc98);
Map<Country, List<Integer>> championsMap = wcList.stream()
.collect(Collectors.groupingBy(WorldCup::getCountry, Collectors.mapping(WorldCup::getYear));
After running this code, championsMap will contain:
Brazil: [1994]
France: [1998]
Is there a succinct way to have this list include an entry for all of the values of the enum? What I'm looking for is:
Brazil: [1994]
France: [1998]
USA: []
There are several approaches you can take.
The map which would be used for accumulating the stream data can be prepopulated with entries corresponding to every enum-member. To access all existing enum-members you can use values() method or EnumSet.allOf().
It can be achieved using three-args version of collect() or through a custom collector created via Collector.of().
Map<Country, List<Integer>> championsMap = wcList.stream()
.collect(
() -> EnumSet.allOf(Country.class).stream() // supplier
.collect(Collectors.toMap(
Function.identity(),
c -> new ArrayList<>()
)),
(Map<Country, List<Integer>> map, WorldCup next) -> // accumulator
map.get(next.getCountry()).add(next.getYear()),
(left, right) -> // combiner
right.forEach((k, v) -> left.get(k).addAll(v))
);
Another option is to add missing entries to the map after reduction of the stream has been finished.
For that we can use built-in collector collectingAndThen().
Map<Country, List<Integer>> championsMap = wcList.stream()
.collect(Collectors.collectingAndThen(
Collectors.groupingBy(WorldCup::getCountry,
Collectors.mapping(WorldCup::getYear,
Collectors.toList())),
map -> {
EnumSet.allOf(Country.class)
.forEach(country -> map.computeIfAbsent(country, k -> new ArrayList<>())); // if you're not going to mutate these lists - use Collections.emptyList()
return map;
}
));
I have a database object that has a field that contains a list of strings. I retrieve all these objects and then use the flatMap and distinct stream methods on the resulting list to get a new list that holds all possible unique values that a database object string list can contain.
Next i want to make a map where the keys are the unique values list of the stringlist that i made earlier, and the values of the map are a list of database objects whose stringlist contains the value of the respective string mapkey.
So what I want is groupingBy the following:
if(object.stringList().contains(respectiveMapKeyFromUniqeStringCollection) put object in object values list of that respective keymap.
Is something like this possible using the groupingBy method?
Edit: I will explain further
class VegetableMaker{
#ElementCollection
private List<String> vegetableList;
}
Lets assume the possible values that a vegetableList can contain are: "Lettuce, Tomato, spinache, rubarbe, onion"
Set<String> produceNames = vegetableMakers.stream().flatMap(vegetableMaker -> vegetableMaker.getVegetableList().stream())
.distinct().collect(Collectors.toSet());
Now we have the list that contains all the possible values mentioned before.
We want to use the values in this list as the keys in the map.
So the Map will look like:
Map<uniqueStringsAsKeys, List<VegetableMaker>> map
The list value contains all the VegetableMaker instances of which the vegetableList contains the key of the map. So the list of key Onion will contain all the VegetableMaker instances whose list includes "Onion".
Is it possible to achieve such a map using the groupingBy method of a java stream?
EDIT 2:
This is the solution i have now, that doesn't use groupingBy but clarifies even more what I want.
EDIT: changed variable in code to match variables used in previous examples.
Set<VegetableMaker> vegetableMakers = vegetableMakerDao.findAll();
Set<String> uniqueVegetableList = vegetableMakers.stream().flatMap(vegetableMaker -> affiliateLink.getKeywords().stream()).distinct().collect(Collectors.toSet());
Map<String,Set<VegetableMaker>> vegetableMakersContainingKeywordInTheirList = new HashMap<>();
uniqueVegetableList.forEach(produceName ->{
Set<VegetableMaker> vegetableMakerSet = new HashSet<>();
vegetableMakers.forEach( vegetableMaker -> {
if(vegetableMaker.getVegetableList().contains(produceName))
vegetableMakerSet.add(vegetableMaker);
});
vegetableMakersContainingKeywordInTheirList.put(produceName, vegetableMakerSet);
});
If I understood you correctly:
List<VegetableMaker> dbObjects = List.of(
new VegetableMaker("Salad", List.of("Onion", "Cucumber")),
new VegetableMaker("Italian Salad", List.of("Cheese")),
new VegetableMaker("Greek Salad", List.of("Onion")));
Map<String, List<VegetableMaker>> map = dbObjects.stream()
.flatMap(x -> x.getVegetableList().stream().map(y -> new SimpleEntry<>(x, y)))
.collect(Collectors.groupingBy(
Entry::getValue,
Collectors.mapping(Entry::getKey, Collectors.toList())));
System.out.println(map);
Resulting being something like:
{Onion=[Salad, Greek Salad], Cheese=[Italian Salad], Cucumber=[Salad]}
EDIT
This is not much different than what I posted above:
Map<String, Set<VegetableMaker>> result = vegetableMakerList.stream()
.flatMap(x -> x.getKeywords().stream().distinct().map(y -> new SimpleEntry<>(x, y)))
.collect(Collectors.groupingBy(
Entry::getValue,
Collectors.mapping(Entry::getKey, Collectors.toSet())));
final Set<VegetableMaker> vegetableMakers = vegetableMakerDao.findAll();
final Map<String, Set<VegetableMaker>> vegetableMakersContainingKeywordInTheirList = vegetableMakers.stream()
.map(VegetableMaker::getKeywords)
.flatMap(Collection::stream)
.distinct()
.collect(Collectors.toMap(
Function.identity(),
vegetable -> vegetableMakers.stream()
.filter(vegetableMaker -> vegetableMaker.getKeywords().contains(vegetable))
.collect(Collectors.toSet())
));
Is there any way to create this hashmap inside the lambda function?
Map<SaleStatus, Long> sales = new HashMap<>();
saleStatusCounters.forEach(saleStatusCounter -> sales.put(saleStatusCounter.getStatus(), saleStatusCounter.getMatches()));
Something like this:
saleStatusCounters.stream()
.map(obj -> new HashMap<SaleStatus, Long>().put(obj.getStatus(), obj.getMatches()))
.collect(Collectors.toMap(???)));
Your code is fine as is. You can, nonetheless, use streams and Collectors.toMap to get the result you want:
Map<SaleStatus, Long> sales = saleStatusCounters.stream()
.collect(Collectors.toMap(obj -> obj.getStatus(), obj -> obj.getMatches()));
Note: this works as long as there are no collisions in the map, i.e. as long as you don't have two or more sale status counter objects with the same status.
In case you have more than one element in your list with the same status, you should use the overloaded version of Collectors.toMap that expects a merge function:
Map<SaleStatus, Long> sales = saleStatusCounters.stream()
.collect(Collectors.toMap(
obj -> obj.getStatus(),
obj -> obj.getMatches(),
Long::sum));
Here Long::sum is a BinaryOperator<Long> that merges two values that are mapped to the same key.
Lets assume I have a list List<Foo> mylist = parseFromCSV();
Where
parseFromCSV()
returns a list of object of Foo class. Foo class has her private attributes:
private mailId;
private status;
... getters and setters...
A Possibly pair of objects in
mylist: [{MailId=100, Status=BOUNCED, EMAIL=test#test.com},{MailId=100, Status=OPENED, EMAIL=test2#test.com}, {MailId=200, Status=CLICKED, EMAIL = test3#test.com}, {MailId=100, Status=HARDBOUNCED, EMAIL = test4#test.com}]
I can indeed have duplicates in my list because the objects listed represents the CSV lines parsed into Java Objects.
Each MailId has multiple different possible status of feedback.
What I am interested in, is to retrieve only the "important" status in my set of possible statuses for one MailId and persist into my database.
OPENED > CLICKED > HARDBOUNCED > BOUNCED (priority of the statuses).
In my example: for MailId= 100 I will only persist the OPENED status into my status column in database.
What will be a good way of achieving this? Should I create a new List (Set?) containing each pair filtered of {MailId, Status(Prioritized)}, ... {MailIdN, StatusN(Prioritized)...} ? Can we add priority on String using Enumerations or making a new comparator for my objects of class Foo?
The persisting operations into mysql database using hibernate should look like this:
persistStatusIntoTable(String MailId, String Status(Taken only status based on priority))
Thanks to all of your help !
If you don't want to use 3rd party libraries and don't want to use an enum class:
// initialization which you will have to do once somewhere in your code
List<String> ordering = Arrays.asList("OPENED", "CLICKED", "HARDBOUNCED", "BOUNCED");
Map<String, Integer> priorityMap = IntStream.range(0, ordering.size()).boxed()
.collect(Collectors.toMap(ordering::get, i -> i));
// example input
List<Foo> myList = Arrays.asList(
new Foo("100", "BOUNCED"), new Foo("100", "OPENED"),
new Foo("101", "BOUNCED"), new Foo("101", "HARDBOUNCED"), new Foo("101", "BOUNCED"));
// determine foo objects that should be persisted
Collection<Foo> toBePersisted = myList.stream()
.filter(t -> priorityMap.containsKey(t.getStatus()))
.collect(Collectors.toMap(Foo::getMailId, t -> t,
BinaryOperator.minBy(Comparator.comparing(t -> priorityMap.get(t.getStatus())))))
.values();
What this code does is:
set up a map which maps status to priority (lower int = higher priority)
filter Foo instances that have an invalid status
group Foo instances by mailId
keep Foo instance with minimum priority integer (= highest priority) per group
get values of resulting map
I assume a couple of things here (that could be easily changed). First Status is an enum (but you could do the same for a String for example).
Then I'm using google guava Ordering to simplify building the Comparator (you could do this without it).
Comparator<Status> c = Ordering.explicit(
ImmutableList.of(Status.OPENED, Status.CLICKED, Status.HARDBOUNCED, Status.BOUNCED));
List<Foo> list = Stream.of(new Foo(Status.BOUNCED, 57), new Foo(Status.OPENED, 57),
new Foo(Status.CLICKED, 58), new Foo(Status.BOUNCED, 57),
new Foo(Status.HARDBOUNCED, 58))
.collect(Collectors.groupingBy(Foo::getId,
Collectors.mapping(Foo::getStatus, Collectors.toCollection(() -> new PriorityQueue<>(c)))))
.entrySet()
.stream()
.map(e -> new Foo(e.getValue().remove(), e.getKey()))
.collect(Collectors.toList());
System.out.println(list); // [id = 57 status = OPENED, id = 58 status = CLICKED]
The idea is to create a Comparator that defines the order of your Statuses; then use that comparator to populate a PriorityQueue (for some id); ultimately I am only interested in the first one - the most important as you call it (and that is the first according to the Comparator).
And here is the version that uses a String versus an enum for Status:
Comparator<String> c = Ordering.explicit(
ImmutableList.of("OPENED", "CLICKED", "HARDBOUNCED", "BOUNCED"));
List<Foo> list = Stream.of(new Foo("BOUNCED", 57), new Foo("OPENED", 57),
new Foo("CLICKED", 58), new Foo("BOUNCED", 57),
new Foo("HARDBOUNCED", 58))
.collect(Collectors.groupingBy(Foo::getId,
Collectors.mapping(Foo::getStatus, Collectors.toCollection(() -> new PriorityQueue<>(c)))))
.entrySet()
.stream()
.map(e -> new Foo(e.getValue().remove(), e.getKey()))
.collect(Collectors.toList());
Ok, so I have a List<Person>. and each Person has a List<String> that is a list of phone numbers that that person owns.
So this is the basic structure:
public class Person {
private String name;
private List<String> phoneNumbers;
// constructors and accessor methods
}
I would like to create a Map<String, Person> where the key is each phone number that the person owns, and the value is the actual person.
So to explain better. If I had this List<Person>:
Person bob = new Person("Bob");
bob.getPhoneNumbers().add("555-1738");
bob.getPhoneNumbers().add("555-1218");
Person john = new Person("John");
john.getPhoneNumbers().add("518-3718");
john.getPhoneNumbers().add("518-3115");
john.getPhoneNumbers().add("519-1987");
List<Person> list = new ArrayList<>();
list.add(bob);
list.add(john);
and I invoked this method. It would give me the following Map<String, Person>
Map<String, Person> map = new HashMap<>();
map.put("555-1738", bob);
map.put("555-1218", bob);
map.put("518-3718", john);
map.put("518-3115", john);
map.put("519-1987", john);
I would like to know how to achieve this using the stream API, as I already know how I would do it using for loops and such.
If you have list of persons called persons and a class called PhoneNumberAndPerson (you could use a generic Tuple or Pair instead)
These are the steps:
For each person, take each phone number of that person. For each of those phone numbers, create a new instance of PhoneNumberAndPerson and add that to a list. Use flatMap to make one single list of all these smaller lists. To make a Map out of this list you supply one function to extract a key from a PhoneNumberAndPerson and another function to extract a Person from that same PhoneNumberAndPerson.
persons.stream()
.flatMap(person -> person.getPhoneNumbers().stream().map(phoneNumber -> new PhoneNumberAndPerson(phoneNumber, person)))
.collect(Collectors.toMap(pp -> pp.getPhoneNumber(), pp -> pp.getPerson()));
Without additional class and pre-creating map:
Map<String, Person> result = list.stream().collect(HashMap::new,
(map, p) -> p.getPhoneNumbers().forEach(phone -> map.put(phone, p)), HashMap::putAll);
Without creating other classes i'd go for something like this:
Map<String, String> map = new HashMap<>();
list.stream().forEach(p -> p.getPhoneNumbers().forEach(n -> map.put(n, p.getName())));
Edit: As suggested by Simon, you can use the collect method, but it can be tricky if you want to create a map using the class that you provided, with a simpler class ( without using List but just a plain String in order to store the number)
you can simply call the code below that returns a Map
list.stream().collect(Collectors.toMap(Person::getPhoneNumber, Person::getName));