I need to iterate a Map with Key and List to provide another type of object. I have tried to explain in the code level.
I have tried with for loop which works fine. But I like to have in Java8 streaming
public Map<String, List<TestClassResult>> getComputed(
Map<String, SourceClass[]> sourceMapObject) {
Map<String, List<TestClassResult>> response = new HashMap<>();
// Here goes the functionality
List<TestClassResult> result;
for (Map.Entry<String, SourceClass[]> entry : sourceMapObject.entrySet()) {
result = new ArrayList<>();
String key = entry.getKey();
for (SourceClass value : entry.getValue()) {
result.add(someMethod(value.id, value.empCode));
}
response.put(key, result);
}
return response;
}
public class SourceClass{
private String id;
private String empCode;
}
public class TestClassResult{
private String empName;
private String empMartial;
private int empAge;
}
I need this to be implemented with Java 8 streams and lambdas
sourceMapObject.entrySet()
.stream()
.collect(Collectors.toMap(
Entry::getKey,
entry -> Arrays.stream(entry.getValue())
.map(value -> someMethod(value.id, value.empCode))
.collect(Collectors.toList()),
(left, right) -> right
))
If you know for sure you will not have duplicates you can omit the (left, right) -> right part. But since in your existing code, you had response.put(key, result); I'd kept it to conform to that.
The point here is that Map::put will override the previous value that you already had in the Map, while a Collectors::toMap without a merger will throw an Exception. On the other hand with (left, right) -> right, it will behave just like the put.
Related
I have a Map<String, MyData> and theMyDataobject contains a properties attribute that is also of Map<String, MyData> type.
I need to traverse this model, looking for a specific MyData object and return its key.
I've tried to construct a flatmap like the one below, but it is not working... somehow the result of the flatmap is a Stream<Object> instead of Stream<Entry<String, MyData>>
Could someone point me what I'm doing wrong, please?
public class MyData<S> {
private String name;
private Map<String, MyData> properties;
...
}
MyData target = ...;
String key = getAllData().entrySet().stream()
.flatMap(s -> s.getValue().getProperties().entrySet().stream())
.filter(e-> target == e.getValue())
.findFirst()
.map(o-> o.getKey())
.orElse("not found");
I have a JSON file containing data in the form:
{
"type":"type1",
"value":"value1",
"param": "param1"
}
{
"type":"type2",
"value":"value2",
"param": "param2"
}
I also have an object like this:
public class TestObject {
private final String value;
private final String param;
public TestObject(String value, String param) {
this.value = value;
this.param = param;
}
}
What I want is to create a Map<String, List<TestObject>> that contains a list of TestObjects for each type.
This is what I coded:
Map<String, List<TestObject>> result = jsonFileStream
.map(this::buildTestObject)
.collect(Collectors.groupingBy(line -> JsonPath.read(line, "$.type")));
Where the method buildTestObject is:
private TestObject buildTestObject(String line) {
return new TestObject(
JsonPath.read(line, "$.value"),
JsonPath.read(line, "$.param"));
}
This does not work because the map() function returns a TestObject, so that the collect function does not work on the JSON String line anymore.
In real life, I cannot add the "type" variable to the TestObjectfile, as it is a file from an external library.
How can I group my TestObjects by the type in the JSON file?
You can move the mapping operation to a down stream collector of groupingBy:
Map<String, List<TestObject>> result = jsonFileStream
.collect(Collectors.groupingBy(line -> JsonPath.read(line, "$.type"),
Collectors.mapping(this::buildTestObject, Collectors.toList())));
This will preserve the string so you can extract the type as a classifier, and applies the mapping to the elements of the resulting groups.
You can also use the toMap collector to accomplish the task at hand.
Map<String, List<TestObject>> resultSet = jsonFileStream
.collect(Collectors.toMap(line -> JsonPath.read(line, "$.type"),
line -> new ArrayList<>(Collections.singletonList(buildTestObject(line))),
(left, right) -> {
left.addAll(right);
return left;
}
));
In addition to the Stream solution, it's worth pointing out that Java 8 also significantly improved the Map interface, making this kind of thing
much less painful to achieve with a for loop than had previously been the case. I am not familiar with the library you are using, but something like this will work (you can always convert a Stream to an Iterable).
Map<String, List<TestObject>> map = new HashMap<>();
for (String line : lines) {
map.computeIfAbsent(JsonPath.read(line, "$.type"), k -> new ArrayList<>())
.add(buildTestObject(line));
}
I have the following class:
class A {
private String id;
private String name;
private String systemid;
}
I'm getting a set of A and want to convert it to a map where the key is the system id, and the value is set of A. (Map<String, Set<A>)
There can be multiple A instances with the same systemid.
Can't seem to figure out how to do it..
got till here but the identity is clearly not right
Map<String, Set<A>> sysUidToAMap = mySet.stream().collect(Collectors.toMap(A::getSystemID, Function.identity()));
can you please assist?
You can use groupingBy instead of toMap:
Map<String, Set<A>> sysUidToAMap =
mySet.stream()
.collect(Collectors.groupingBy(A::getSystemID,
Collectors.toSet()));
my 2ยข: you can do it with Collectors.toMap but it's slightly more verbose:
yourSet
.stream()
.collect(Collectors.toMap(
A::getSystemId,
a -> {
Set<A> set = new HashSet<>();
set.add(a);
return set;
}, (left, right) -> {
left.addAll(right);
return left;
}));
As streams were not specifically requested, here's a way to do it only with the Map API:
Map<String, Set<A>> map = new HashMap<>();
mySet.forEach(a -> map.computeIfAbsent(a.getSystemId(), k -> new HashSet<>()).add(a));
I am using Java 8 streams to group a list of entries by a certain key and then sorting the groups by date. What I would like to do in addition is to "collapse" any two entries within a group that have the same date and sum them up. I have a class like this (stripped down for example purposes)
class Thing {
private String key;
private Date activityDate;
private float value;
...
}
Then I'm grouping them like so:
Map<String, List<Thing>> thingsByKey = thingList.stream().collect(
Collectors.groupingBy(
Thing::getKey,
TreeMap::new,
Collectors.mapping(Function.identity(), toSortedList())
));
private static Collector<Thing,?,List<Thing>> toSortedList() {
return Collectors.collectingAndThen(toList(),
l -> l.stream().sorted(Comparator.comparing(Thing::getActivityDate)).collect(toList()));
}
What I would like to do is, if any two Thing entries have the exact same date, sum up the values for those and collapse them down such that,
Thing1
Date=1/1/2017
Value=10
Thing2
Date=1/1/2017
Value=20
Turns into 30 for 1/1/2017.
What's the best way to accomplish something like that?
I have slightly change your Thing class to use LocalData and added a very simple toString:
#Override
public String toString() {
return " value = " + value;
}
If I understood correctly, than this is what you need:
Map<String, TreeMap<LocalDate, Thing>> result = Arrays
.asList(new Thing("a", LocalDate.now().minusDays(1), 12f), new Thing("a", LocalDate.now(), 12f), new Thing("a", LocalDate.now(), 13f))
.stream()
.collect(Collectors.groupingBy(Thing::getKey,
Collectors.toMap(Thing::getActivityDate, Function.identity(),
(Thing left, Thing right) -> new Thing(left.getKey(), left.getActivityDate(), left.getValue() + right.getValue()),
TreeMap::new)));
System.out.println(result); // {a={2017-06-24= value = 12.0, 2017-06-25= value = 25.0}}
This can be accomplished using the toMap collector:
Map<Date, Thing> thingsByDate = things.stream().collect(Collectors.toMap(
Thing::getActivityDate,
Function.identity(),
(thing1, thing2) -> new Thing(null, thing1.getActivityDate(), thing1.getValue()+thing2.getValue())
);
You may then do with this map as you wish.
I have two classes that are structured like this:
public class Company {
private List<Person> person;
...
public List<Person> getPerson() {
return person;
}
...
}
public class Person {
private String tag;
...
public String getTag() {
return tag;
}
...
}
Basically the Company class has a List of Person objects, and each Person object can get a Tag value.
If I get the List of the Person objects, is there a way to use Stream from Java 8 to find the one Tag value that is the most common among all the Person objects (in case of a tie, maybe just a random of the most common)?
String mostCommonTag;
if(!company.getPerson().isEmpty) {
mostCommonTag = company.getPerson().stream() //How to do this in Stream?
}
String mostCommonTag = getPerson().stream()
// filter some person without a tag out
.filter(it -> Objects.nonNull(it.getTag()))
// summarize tags
.collect(Collectors.groupingBy(Person::getTag, Collectors.counting()))
// fetch the max entry
.entrySet().stream().max(Map.Entry.comparingByValue())
// map to tag
.map(Map.Entry::getKey).orElse(null);
AND the getTag method appeared twice, you can simplify the code as further:
String mostCommonTag = getPerson().stream()
// map person to tag & filter null tag out
.map(Person::getTag).filter(Objects::nonNull)
// summarize tags
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
// fetch the max entry
.entrySet().stream().max(Map.Entry.comparingByValue())
// map to tag
.map(Map.Entry::getKey).orElse(null);
You could collect the counts to a Map, then get the key with the highest value
List<String> foo = Arrays.asList("a","b","c","d","e","e","e","f","f","f","g");
Map<String, Long> f = foo
.stream()
.collect(Collectors.groupingBy(v -> v, Collectors.counting()));
String maxOccurence =
Collections.max(f.entrySet(), Comparator.comparing(Map.Entry::getValue)).getKey();
System.out.println(maxOccurence);
This should work for you:
private void run() {
List<Person> list = Arrays.asList(() -> "foo", () -> "foo", () -> "foo",
() -> "bar", () -> "bar");
Map<String, Long> commonness = list.stream()
.collect(Collectors.groupingBy(Person::getTag, Collectors.counting()));
Optional<String> mostCommon = commonness.entrySet().stream()
.max(Map.Entry.comparingByValue())
.map(Map.Entry::getKey);
System.out.println(mostCommon.orElse("no elements in list"));
}
public interface Person {
String getTag();
}
The commonness map contains the information which tag was found how often. The variable mostCommon contains the tag that was found most often. Also, mostCommon is empty, if the original list was empty.
If you are open to using a third-party library, you can use Collectors2 from Eclipse Collections with a Java 8 Stream to create a Bag and request the topOccurrences, which will return a MutableList of ObjectIntPair which is the tag value and the count of the number of occurrences.
MutableList<ObjectIntPair<String>> topOccurrences = company.getPerson()
.stream()
.map(Person::getTag)
.collect(Collectors2.toBag())
.topOccurrences(1);
String mostCommonTag = topOccurrences.getFirst().getOne();
In the case of a tie, the MutableList will have more than one result.
Note: I am a committer for Eclipse Collections.
This is helpful for you,
Map<String, Long> count = persons.stream().collect(
Collectors.groupingBy(Person::getTag, Collectors.counting()));
Optional<Entry<String, Long>> maxValue = count .entrySet()
.stream().max((entry1, entry2) -> entry1.getValue() > entry2.getValue() ? 1 : -1).get().getKey();
maxValue.get().getValue();
One More solution by abacus-common
// Comparing the solution by jdk stream,
// there is no "collect(Collectors.groupingBy(Person::getTag, Collectors.counting())).entrySet().stream"
Stream.of(company.getPerson()).map(Person::getTag).skipNull() //
.groupBy(Fn.identity(), Collectors.counting()) //
.max(Comparators.comparingByValue()).map(e -> e.getKey()).orNull();
// Or by multiset
Stream.of(company.getPerson()).map(Person::getTag).skipNull() //
.toMultiset().maxOccurrences().map(e -> e.getKey()).orNull();