I have the following setup:
Map<Instant, String> items;
...
String renderTags(String text) {
// Renders markup tags in a string to human readable form
}
...
<?> getItems() {
// Here is where I need help
}
My problems is, the strings that are the values of the items map are marked up with tags. I want getItems() to return all the items, but with the strings parsed using the renderTags(String) method. Something like:
// Doesn't work
items.entrySet().stream().map(e -> e.setValue(renderTags(e.getValue())));
What is the most effective way to do this?
If you want a Map as result:
Map<Instant, String> getItems() {
return items.entrySet()
.stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
e -> renderTags(e.getValue())));
}
If you want to modify an existing map instead of generating the new one (as in your example), there's no need to use the stream at all. Use Map.replaceAll:
items.replaceAll((k, v) -> renderTags(v));
return items;
If you want to keep the original map unchanged, consult other answers.
You can try it this way with Collectors.toMap():
Map<Instant, String> getItems() {
return items.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
entry -> renderTags(entry.getValue())
));
}
By the way, if the name says simply "get", you shouldn't generally transform it in there. One expects a getter to be simple and not costy at all.
Since Java 9 you can also do:
Entry<String, String> entry = Map.entry("a", "b");
In your Map it would be used like this:
Map<Instant, String> getItems() {
return items.entrySet()
.stream()
.map(entry -> Map.entry(entry.getKey(), renderTags(entry.getValue())))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue);
}
An alternative could be
Map<Instant, String> getItems() {
return items.entrySet().stream()
.peek(entry -> entry.setValue(renderTags(entry.getKey())))
.collect(Collectors.toMap(Map.Entry::getKey,e -> e.getValue()));
}
see Baeldung
Related
I have a nested String HashMap and a List of object. The object has a String property to be matched against the values of the inner HashMap.
I'm trying to find a single liner using stream() and Collectors for the below java code
HashMap<String, HashMap<String, String>>PartDetailsHMap=new HashMap<String, HashMap<String, String>>()
List<Part> partList=new ArrayList<Part>();
for(int i=0;i<partList.size();i++)
String partId = partList.get(i).getPropertyValue("part_id");
for(HashMap< String, String> PartPropsHMap:PartDetailsHMap.values())
{
if(PartPropsHMap.containsValue(itemId))
{
collectingPartMap.put(partList.get(i), PartPropsHMap);
break;
}
}
}
If needed I can extract String property in a List<String>.
Looking for a one liner using stream().
Something like this should work:
Map<String, Map<String, String>> PartDetailsHMap = new HashMap<>();
List<Part> partList = new ArrayList<>();
Map<Part, Map<String, String>> collectingPartMap = partList.stream()
.map(part -> PartDetailsHMap.values()
.stream()
.filter(partPropsHMap -> partPropsHMap.containsValue(part.getPropertyValue("part_id")))
.findFirst()
.map(partPropsHMap -> new SimpleEntry<Part, Map>(part, partPropsHMap))
.get()
)
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
I've used SimpleEntry class in AbstractMap to carry the context of Part along with the map that we've found to the next operation - collect.
Caveat: I feel if the option without streams is cleaner and does the job, I would go with that. Given that the manipulation you need here is fairly involved, it would benefit in the long run to keep it readable, than something clever.
An alternate approach slightly improving the current answer could be to not perform a get without an isPresent check. This could be achieved by using filter and map
Map<Part, Map<String, String>> collectingPartMap = partList.stream()
.map(part -> partDetailsHMap.values().stream()
.filter(innerMap -> innerMap.containsValue(part.getPartId())) // notice 'getPartId' for the access
.findFirst()
.map(firstInnerMap -> new AbstractMap.SimpleEntry<>(part, firstInnerMap)))
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
I have a nested HashMap<String,Object> and I want to create a HashMap<String,String> by flattening the Hashmap. I have tried the solution from Recursively Flatten values of nested maps in Java 8. But I am unable to use the class FlatMap as mentioned in the answer.
I have also tried the solution in the question itself, still I am missing something. Then I found a similar use case and came up with the following solution. But it seems like I am missing something as a parameter for the lambda function flatMap .
public static void main(String[] args) {
Map<String,Object> stringObjectMap= new HashMap<String,Object>();
stringObjectMap.put("key1","value1");
stringObjectMap.put("key2","value2");
Map<String,Object> innerStringObjectMap = new HashMap<>();
innerStringObjectMap.put("i1key3","value3");
innerStringObjectMap.put("i1key4","value4");
innerStringObjectMap.put("i1key5","value5");
stringObjectMap.put("map1",innerStringObjectMap);
Map<String,Object> innerStringObjectMap2 = new HashMap<>();
innerStringObjectMap.put("i2key6","value6");
innerStringObjectMap2.put("i2key7","value7");
innerStringObjectMap.put("i1map2",innerStringObjectMap2);
Map<String,Object> collect =
stringObjectMap.entrySet().stream()
.map(x -> x.getValue())
.flatMap(x -> x) //I aint sure what should be give here
.distinct(); //there was a collect as List which i removed.
//collect.forEach(x -> System.out.println(x));
}
What is a better solution for flattening a nested map? I am not just interested in the values, but also the keys in the map. That is the reason why I decided to flatten the map to get another map (I am not sure if this is even possible)
EDIT - Expected Output
key1 - value1
key2-value2
map1 ="" //this is something i will get later for my purpose
i1key3=value3
.
.
i1map2=""
.
.
i2key7=value7
I modified the class from the mentioned answer according to your needs:
public class FlatMap {
public static Stream<Map.Entry<?, ?>> flatten(Map.Entry<?, ?> e) {
if (e.getValue() instanceof Map<?, ?>) {
return Stream.concat(Stream.of(new AbstractMap.SimpleEntry<>(e.getKey(), "")),
((Map<?, ?>) e.getValue()).entrySet().stream().flatMap(FlatMap::flatten));
}
return Stream.of(e);
}
}
Usage:
Map<?, ?> collect = stringObjectMap.entrySet()
.stream()
.flatMap(FlatMap::flatten)
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(u, v) -> throw new IllegalStateException(String.format("Duplicate key %s", u)),
LinkedHashMap::new));
Attention:
Be sure to use the provided collect with a LinkedHashMap, otherwise the order will be screwed up.
I have used the function from https://stackoverflow.com/a/48578105/5243291. But I used the function in a different way.
Map<Object, Object> collect = new HashMap<>();
stringObjectMap.entrySet()
.stream()
.flatMap(FlatMap::flatten).forEach(it -> {
collect.put(it.getKey(), it.getValue());
});
Function again
public static Stream<Map.Entry<?, ?>> flatten(Map.Entry<?, ?> e) {
if (e.getValue() instanceof Map<?, ?>) {
return Stream.concat(Stream.of(new AbstractMap.SimpleEntry<>(e.getKey(), "")),
((Map<?, ?>) e.getValue()).entrySet().stream().flatMap(FlatMap::flatten));
}
return Stream.of(e);
}
I want to convert a Map<String, List<MyObject>> to List<Map<String, MyObject>>
{<key1,[myObject1, myObject2]>, <key2,[myObject3, myObject4]>}
will be converted to
[{<key1,myObject1>, <key2,myObject3>}, {<key1,myObject2>, <key2, myObject4>}]
where myObject1 and myObject3 have a same unique id and so do myObject2 and myObject4.
my implementation is below but is there a more optimal way of doing this.
private List<Map<String, MyObject>> getObjectMapList( Map<String, List<MyObject>> objectMap)
{
List<Map<String, MyObject>> objectMapList = new ArrayList<Map<String,MyObject>>();
for(MyObject myObject : objectMap.get("key1")) {// there will be default key1 whose value is known
Map<String, MyObject> newMap= new HashMap<String, MyObject>();
for (String key : objectMap.keySet()) {
newMap.put(key, objectMap.get(key).stream()
.filter(thisObject -> thisObject.getId().equals(myObject.getId()))
.collect(Collectors.toList()).get(0));
}
objectMapList.add(newMap);
}
return objectMapList;
}
Here's a 1-liner without any curly brackets:
private List<Map<String, MyObject>> getObjectMapList( Map<String, List<MyObject>> objectMap) {
return map.entrySet().stream()
.map(e -> e.getValue().stream()
.map(o -> Collections.singletonMap(e.getKey(), o))
.collect(Collections.toList())
.flatMap(List::stream)
.collect(Collections.toList());
}
The main "trick" here is the use of Collections.singletonMap() to allow a blockless in-line create-and-populate of a map.
Disclaimer: Code may not compile or work as it was thumbed in on my phone (but there's a reasonable chance it will work)
This stream should return you the desired result. With my old Eclipse version, I had some trouble with types. You might have to break it up into single steps, or add some types in the lambdas, but I wanted to keep it short.
Map<String, List<MyObject>> objectMap = new HashMap<>();
objectMap.keySet()
.stream()
.flatMap(key -> objectMap.get(key)
.stream()
.map(obj -> new AbstractMap.SimpleEntry<>(key, obj)))
.collect(groupingBy(pair -> pair.getValue().getId()))
.values()
.stream()
.map(listOfSameIds -> listOfSameIds.stream()
.collect(toMap(SimpleEntry::getKey, SimpleEntry::getValue)))
.collect(toList());
What I do is:
Pair all the objects in all your input's values with their keys and put them in one long list (flatMap(key -> streamOfKeyObjectPairs)).
Separate those pairs by the IDs of the objects (collect(groupingBy)).
Take each of those groups and convert the lists of pairs into maps (map(list -> toMap))
Put all those maps into a list
How to process a list of string and collec it into Map or Immutable map only for those whose value is present
String anotherParam = "xyz";
Map.Builder<String,String> resultMap = ImmutableMap.builder(..)
listOfItems.stream()
.filter(Objects::nonNull)
.distinct()
.forEach(
item -> {
final Optional<String> result =
getProcessedItem(item,anotherParam);
if (result.isPresent()) {
resultMap.put(item, result.get());
}
});
return resultMap.build();
Please tell, is there a better way to achieve this via collect?
If you have access to Apache Commons library you can make use of Pair.class
Map<String, String> resultMap = ImmutableMap.copyof(listOfItems()
.stream()
.filter(Objects::nonNull)
.distinct()
.map(it -> Pair.of(it, getProcessedItem(it,anotherParam))
.filter(pair -> pair.getValue().isPresent())
.collect(toMap(Pair::getKey, pair -> pair.getValue().get())))
But it's a good practice to make special data classes which describes your mapping item->result more specificly
Here is an example, create class like this:
static class ItemResult(){
public final String item;
public final Optional<String> result;
public ItemResult(String item, Optional<String> result){
this.item = item;
this.result = result;
}
public boolean isPresent(){
return this.result.isPresent();
}
public String getResult(){
return result.get();
}
}
And use it like that:
Map<String, String> resultMap = ImmutableMap.copyOf(listOfItems()
.stream()
.filter(Objects::nonNull)
.distinct()
.map(it -> new ItemResult(it, getProcessedItem(it,anotherParam))
.filter(ItemResult::isPresent)
.collect(toMap(ItemResult::item, ItemResult::getResult)))
You can read here why Google gave up the idea of tuples and pairs and don't use them in most cases
If after all you don't want to use any other class you can leverage api of the Optional:
Map.Builder<String,String> resultMap = ImmutableMap.builder(..)
listOfItems.stream()
.filter(Objects::nonNull)
.distinct()
.forEach(item -> getProcessedItem(item,anotherParam)
.ifPresent(result -> resultMap.put(item result));
return resultMap.build();
I have a class like this:
class MultiDataPoint {
private DateTime timestamp;
private Map<String, Number> keyToData;
}
and i want to produce , for each MultiDataPoint
class DataSet {
public String key;
List<DataPoint> dataPoints;
}
class DataPoint{
DateTime timeStamp;
Number data;
}
of course a 'key' can be the same across multiple MultiDataPoints.
So given a List<MultiDataPoint>, how do I use Java 8 streams to convert to List<DataSet>?
This is how I am currently doing the conversion without streams:
Collection<DataSet> convertMultiDataPointToDataSet(List<MultiDataPoint> multiDataPoints)
{
Map<String, DataSet> setMap = new HashMap<>();
multiDataPoints.forEach(pt -> {
Map<String, Number> data = pt.getData();
data.entrySet().forEach(e -> {
String seriesKey = e.getKey();
DataSet dataSet = setMap.get(seriesKey);
if (dataSet == null)
{
dataSet = new DataSet(seriesKey);
setMap.put(seriesKey, dataSet);
}
dataSet.dataPoints.add(new DataPoint(pt.getTimestamp(), e.getValue()));
});
});
return setMap.values();
}
It's an interesting question, because it shows that there are a lot of different approaches to achieve the same result. Below I show three different implementations.
Default methods in Collection Framework: Java 8 added some methods to the collections classes, that are not directly related to the Stream API. Using these methods, you can significantly simplify the implementation of the non-stream implementation:
Collection<DataSet> convert(List<MultiDataPoint> multiDataPoints) {
Map<String, DataSet> result = new HashMap<>();
multiDataPoints.forEach(pt ->
pt.keyToData.forEach((key, value) ->
result.computeIfAbsent(
key, k -> new DataSet(k, new ArrayList<>()))
.dataPoints.add(new DataPoint(pt.timestamp, value))));
return result.values();
}
Stream API with flatten and intermediate data structure: The following implementation is almost identical to the solution provided by Stuart Marks. In contrast to his solution, the following implementation uses an anonymous inner class as intermediate data structure.
Collection<DataSet> convert(List<MultiDataPoint> multiDataPoints) {
return multiDataPoints.stream()
.flatMap(mdp -> mdp.keyToData.entrySet().stream().map(e ->
new Object() {
String key = e.getKey();
DataPoint dataPoint = new DataPoint(mdp.timestamp, e.getValue());
}))
.collect(
collectingAndThen(
groupingBy(t -> t.key, mapping(t -> t.dataPoint, toList())),
m -> m.entrySet().stream().map(e -> new DataSet(e.getKey(), e.getValue())).collect(toList())));
}
Stream API with map merging: Instead of flattening the original data structures, you can also create a Map for each MultiDataPoint, and then merge all maps into a single map with a reduce operation. The code is a bit simpler than the above solution:
Collection<DataSet> convert(List<MultiDataPoint> multiDataPoints) {
return multiDataPoints.stream()
.map(mdp -> mdp.keyToData.entrySet().stream()
.collect(toMap(e -> e.getKey(), e -> asList(new DataPoint(mdp.timestamp, e.getValue())))))
.reduce(new HashMap<>(), mapMerger())
.entrySet().stream()
.map(e -> new DataSet(e.getKey(), e.getValue()))
.collect(toList());
}
You can find an implementation of the map merger within the Collectors class. Unfortunately, it is a bit tricky to access it from the outside. Following is an alternative implementation of the map merger:
<K, V> BinaryOperator<Map<K, List<V>>> mapMerger() {
return (lhs, rhs) -> {
Map<K, List<V>> result = new HashMap<>();
lhs.forEach((key, value) -> result.computeIfAbsent(key, k -> new ArrayList<>()).addAll(value));
rhs.forEach((key, value) -> result.computeIfAbsent(key, k -> new ArrayList<>()).addAll(value));
return result;
};
}
To do this, I had to come up with an intermediate data structure:
class KeyDataPoint {
String key;
DateTime timestamp;
Number data;
// obvious constructor and getters
}
With this in place, the approach is to "flatten" each MultiDataPoint into a list of (timestamp, key, data) triples and stream together all such triples from the list of MultiDataPoint.
Then, we apply a groupingBy operation on the string key in order to gather the data for each key together. Note that a simple groupingBy would result in a map from each string key to a list of the corresponding KeyDataPoint triples. We don't want the triples; we want DataPoint instances, which are (timestamp, data) pairs. To do this we apply a "downstream" collector of the groupingBy which is a mapping operation that constructs a new DataPoint by getting the right values from the KeyDataPoint triple. The downstream collector of the mapping operation is simply toList which collects the DataPoint objects of the same group into a list.
Now we have a Map<String, List<DataPoint>> and we want to convert it to a collection of DataSet objects. We simply stream out the map entries and construct DataSet objects, collect them into a list, and return it.
The code ends up looking like this:
Collection<DataSet> convertMultiDataPointToDataSet(List<MultiDataPoint> multiDataPoints) {
return multiDataPoints.stream()
.flatMap(mdp -> mdp.getData().entrySet().stream()
.map(e -> new KeyDataPoint(e.getKey(), mdp.getTimestamp(), e.getValue())))
.collect(groupingBy(KeyDataPoint::getKey,
mapping(kdp -> new DataPoint(kdp.getTimestamp(), kdp.getData()), toList())))
.entrySet().stream()
.map(e -> new DataSet(e.getKey(), e.getValue()))
.collect(toList());
}
I took some liberties with constructors and getters, but I think they should be obvious.