In my entrySet I have the next values:
name -> "someName"
nameSpace -> "someNameSpace"
version -> "someVersion"
I'm iterating over this entrySet and adding its values to a final object, my problem is that I need to add the values in the order:
nameSpace -> "someNameSpace"
name -> "someName"
version -> "someVersion"
With nameSpace first. Not pretty sure if it's possible to achieve that using a stream or something more sophisticated. I'm doing the process manually.
public static void main(String [] args){
SortedMap<String, String> coordinates = new TreeMap<>();
coordinates.put("name", "nameValue");
coordinates.put("nameSpace", "nameSpaceValue");
coordinates.put("version", "versionValue");
String name = coordinates.get("name");
String nameSpace = coordinates.get("nameSpace");
String version = coordinates.get("version");
/*Object.add("name", name);
Object.add("nameSpace", nameSpace);
Object.add("version", version);*/
}
Thanks!
It seems natural sorting is not applicable to the keys in this specific order: nameSpace, name, version, therefore SortedMap / TreeMap would require some very custom/hardcoded comparator:
SortedMap<String, String> coordinates = new TreeMap<>(
(a, b) -> b.startsWith(a) && !a.startsWith(b) ? 1 : a.compareTo(b)
);
coordinates.put("version", "versionValue");
coordinates.put("nameSpace", "nameSpaceValue");
coordinates.put("name", "nameValue");
// iterating the map
// coordinates.forEach((k, v) -> Object.add(k, v));
coordinates.forEach((k, v) -> System.out.println(k + " -> " + v));
Output:
nameSpace -> nameSpaceValue
name -> nameValue
version -> versionValue
Another approach would be to use LinkedHashMap capable of maintaining the insertion order and fix this order as necessary:
Map<String, String> coordinates = new LinkedHashMap<>();
// add entries in expected order
coordinates.put("nameSpace", "nameSpaceValue");
coordinates.put("name", "nameValue");
coordinates.put("version", "versionValue");
Similar approach would be to use an unsorted map and a list of keys in the desired order.
Map<String, String> coordinates = new HashMap<>();
coordinates.put("name", "nameValue");
coordinates.put("nameSpace", "nameSpaceValue");
coordinates.put("version", "versionValue");
List<String> keyOrder = Arrays.asList("nameSpace", "name", "version");
keyOrder.forEach(k -> System.out.println(k + " -> " + coordinates.get(k)));
However, it seems that method add of the custom object requires both key and value anyway, so the order of populating the fields should not be relevant.
Related
I have a list of map of string string(List<Map<String,String>>) like bellow,
[{key = "car", value = "bmw"},{key = "car", value = "suzuki"},{key = "car", value = "hyundai"},{key = "bike", value = "honda"},{key = "bike", value = "tvs"}]
I want to convert to below list of map string list string(List<Map<String, List<String>>>) or something equal
[{key = "car", value = ["bmw","suzuki","hyundai"]},{key = "bike", value = ["honda","tvs"]}]
Thanks in advance.
Java 8+
For singleton maps
Use groupingBy to separate by key and then mapping to get the value of your singleton map
Map<String, List<String>> res =
list.stream()
.map(map -> map.entrySet().iterator().next())
.collect(Collectors.groupingBy(
entry -> entry.getKey(),
Collectors.mapping(
entry -> entry.getValue(),
Collectors.toList()
)
)
);
For maps with multiple entries
You do the same as above, but flatMap to get all the entries in a single 1-D stream
Map<String, List<String>> res =
list.stream()
.flatMap(map -> map.entrySet().stream())
.collect(Collectors.groupingBy(
entry -> entry.getKey(),
Collectors.mapping(
entry -> entry.getValue(),
Collectors.toList()
)
)
);
Pre-Java 8
For maps with multiple entries
You'll have to iterate through each map in the list, and then through the entries of each map. For each entry, check if the key's already there, and if so, add the value to the list associated with that key. If the key's not in the result map, then create a new list there.
Map<String, List<String>> map = new HashMap<>();
for (Map<String, String> m : list) {
for (Map.Entry<String, String> e : m.entrySet()) {
String key = e.getKey();
if (!map.containsKey(key)) {
map.put(key, new ArrayList<String>());
}
map.get(key).add(e.getValue());
}
}
For singleton maps
For each map in your original list, you find the first entry, and then you do the same as above: add that entry's value to the list that the entry's key maps to, and before that, add a new List to the map if the key doesn't exist there already.
Map<String, List<String>> map = new HashMap<>();
for (Map<String, String> m : list) {
Map.Entry<String, String> e = m.entrySet().iterator().next();
String key = e.getKey();
if (!map.containsKey(key)) {
map.put(key, new ArrayList<String>());
}
map.get(key).add(e.getValue());
}
Based on your example, you have a list of maps like this:
List<Map<String,String>> myListOfMaps = List.of(
Map.of("car", "bmw"),
Map.of("car", "suzuki"),
Map.of("car", "hyundai"),
Map.of("bike", "honda"),
Map.of("bike", "tvs")
);
Then you can use the groupingBy function of streams to do something similar to what you see below.
Stream your list, flatten the stream by streaming over the entrysets, group the entrysets by their keys, map to a list of values
Map<String,List<String>> myResultMap = myListOfMaps.stream()
.flatMap(m -> m.entrySet().stream())
.collect(Collectors.groupingBy(
Map.Entry::getKey, Collectors.mapping(Map.Entry::getValue, Collectors.toList())));
use result/print ..
myResultMap.forEach((k,v)-> {
System.out.println(k + " : " + v);
});
// car : [bmw, suzuki, hyundai]
// bike : [honda, tvs]
I have a Map<String, List<StartingMaterial>>
I want to convert the Object in the List to another Object.
ie. Map<String, List<StartingMaterialResponse>>
Can I do this using java stream Collectors.toMap()?
I tried something like the below code.
Map<String, List<StartingMaterial>> startingMaterialMap = xxxx;
startingMaterialMap.entrySet().stream().collect(Collectors.toMap( Map.Entry::getKey, Function.identity(), (k, v) -> convertStartingMaterialToDto(v.getValue())));
And my conversion code to change the Object is like below,
private StartingMaterialResponse convertStartingMaterialToDto(StartingMaterial sm) {
final StartingMaterialMatrix smm = sm.getStartingMaterialMatrix();
final StartingMaterial blending1Matrix = smm.getBlending1Matrix();
final StartingMaterial blending2Matrix = smm.getBlending2Matrix();
return new StartingMaterialResponse(
sm.getId(),
sm.getComponent().getCasNumber(),
sm.getDescription(),
sm.getPriority(),
String.join(" : ",
Arrays.asList(smm.getCarryInMatrix().getComponent().getMolecularFormula(),
blending1Matrix != null ? blending1Matrix.getComponent().getMolecularFormula() : "",
blending2Matrix != null ? blending2Matrix.getComponent().getMolecularFormula() : ""
).stream().distinct().filter(m -> !m.equals("")).collect(Collectors.toList())),
smm.getFamily(),
smm.getSplitGroup());
}
You can use the toMap collector since your source is a map. However you have to iterate over all the values and convert each of them into the DTO format inside the valueMapper.
Map<String, List<StartingMaterialResponse>> result = startingMaterialMap.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().stream()
.map(s -> convertStartingMaterialToDto(s)).collect(Collectors.toList())));
I think you mean to do :
startingMaterialMap.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey,
e -> e.getValue().stream()
.map(this::convertStartingMaterialToDto)
.collect(Collectors.toList()))
);
Here is my approach to this problem :
Map<String, List<Integer>> deposits = new HashMap<>();
deposits.put("first", Arrays.asList(1, 2, 3));
deposits.forEach((depositName, products) -> {
products.stream()
.map(myIntegerProduct -> myIntegerProduct.toString())
.collect(Collectors.toList());
});
The above example convert the List<Integer> to a list of Strings.
In your example, instead of myIntegerProduct.toString() is the convertStartingMaterialToDto method.
The forEach method iterates through every Key-Value pair in the map and you set some names for the key and the value parameters to be more specific and keep an understandable code for everyone who reads it. In my example : forEach( (depositName, products)) -> the depositName is the Key ( in my case a String ) and the products is the Value of the key ( in my case is a List of integers ).
Finally you iterate through the list too and map every item to a new type
products.stream()
.map(myIntegerProduct -> myIntegerProduct.toString())
I have two lists. I want to create a map which will have true for the matching element and false for the unique one in java 8.
Eg.
Input-
List 1 = [A,B,C,D]
List 2 = [B,C,Y,Z]
Output-
Map:
A,false
B,true
C,true
D,false
My code:
Map<String,Boolean> map = new HashMap<>();
for(String var1 : list1) {
boolean value;
if (CollectionUtils.isNotEmpty(list2)) {
Optional<String> valueOptional = list2.stream()
.filter(e1 -> e1.equalsIgnoreCase(var1))
.findAny();
value = valueOptional.isPresent();
map.put(var1, value);
}
}
First, create a Set using the second list. Then use the toMap collector to create the map which has the string as it's key, and it's existence in the setTwo as the value. Here's how it looks.
Set<String> setTwo = new HashSet<>(listTwo);
Map<String, Boolean> existenceMap = listOne.stream()
.collect(Collectors.toMap(s -> s, setTwo::contains, (a, b) -> a));
I would recommend map with boolean key and values as list
Map<Boolean, List<String>> map = list1.stream()
.collect(Collectors.partitioningBy(list2::contains)); // or set::contains
My current attempt:
Map<Integer, Map<String, Integer>> collect = shopping.entrySet()
.stream()
.collect(Collectors.toMap/*groupingBy? */(e -> e.getKey().getAge(),
e -> e.getValue().entrySet().stream().collect(Collectors.groupingBy(b -> b.getKey().getCategory(), Collectors.summingInt(Map.Entry::getValue)))));
shopping is basically a map: Map<Client, Map<Product,Integer>>,
The problem comes from the fact that the provided data contains multiple values by key - there are Clients with same ages, and the code works only for a single value by key.
How could I make this code work also for multiple keys?
I suppose it should be somehow changed to use collect collect(Collectors.groupingBy) ->
in the resulting map Map<Integer, Map<String, Integer>>:
The outer key (Integer) represents the client age.
The inner key (String) - represents product category
The inner maps value (Integer) - represents the number of products
which belong to a specific category.
My attempt using groupingBy:
Map<Integer, Map<String, Integer>> collect = shopping.entrySet()
.stream()
.collect(Collectors.groupingBy(/*...*/))
Simply I want to refactor that code into one using streams:
Map<Integer, Map<String, Integer>> counts = new HashMap<>();
for (Map.Entry<Client, Map<Product, Integer>> iData : shopping.entrySet()) {
int age = iData.getKey().getAge();
for (Map.Entry<Product, Integer> iEntry : iData.getValue().entrySet()) {
String productCategory = iEntry.getKey().getCategory();
counts.computeIfAbsent(age, (agekey) -> new HashMap<>()).compute(productCategory, (productkey, value) -> value == null ? 1 : value + 1);
}
}
A non-stream(forEach) way to convert your for loop could be :
Map<Integer, Map<String, Integer>> counts = new HashMap<>();
shopping.forEach((key, value1) -> value1.keySet().forEach(product ->
counts.computeIfAbsent(key.getAge(),
(ageKey) -> new HashMap<>())
.merge(product.getCategory(), 1, Integer::sum)));
This would be more appropriate via a groupingBy collector instead of toMap.
Map<Integer, Map<String, Integer>> result = shopping.entrySet()
.stream()
.collect(groupingBy(e -> e.getKey().getAge(),
flatMapping(e -> e.getValue().keySet().stream(),
groupingBy(Product::getCategory,
summingInt(e -> 1)))));
note this uses flatMapping which is only available in the standard library as of jdk9.
I have a map like below:
Map<String, String> map1 = new HashMap<String, String>();
and the contents are:
ID_1 -> ID_2
------------
100 -> 10
200 -> 20
300 -> 30
Based on the value of ID_2 I have to query an oracle table and get a code value corresponding to each entry:
ID_1 -> ID_2 -> code
--------------------
100 -> 10 -> 8
200 -> 20 -> 2
300 -> 30 -> 9
and then I will have to get the map1 sorted in ascending way by the code value i.e the result should be:
200 -> 20
100 -> 10
300 -> 30
I have thought of creating an intermediary map with <ID_1, List<ID_2,code>> as <K,V> and then sort using the code value and then get the final output.
Is there any shorter way to do so, like without using an intermediary map?
You try this code below: I used int[] array instead of List
public class Test {
public static void main(String[] args) throws Exception {
Map<String, int[]> map = new HashMap<>();
map.put("100", new int[]{10, 8});
map.put("200", new int[]{20, 2});
map.put("300", new int[]{30, 9});
Map<String, int[]> sortByValue = sortByValue(map);
for (Map.Entry<String, int[]> entry : sortByValue.entrySet()) {
System.out.println(entry.getKey() +" -> "+ entry.getValue()[0]);
}
}
private static Map<String, int[]> sortByValue( Map<String, int[]> map ) {
List<Map.Entry<String, int[]>> list = new LinkedList<>(map.entrySet());
Collections.sort(list, (o1, o2) -> Integer.compare(o1.getValue()[1], o2.getValue()[1]));
Map<String, int[]> result = new LinkedHashMap<>();
for (Map.Entry<String, int[]> entry : list) {
result.put(entry.getKey(), entry.getValue());
}
return result;
}
}
And it is the result:
200 -> 20
100 -> 10
300 -> 30
Based on map1 you can build new map:
Map<String, Pair<String, String> map2
where key is id from oracle db.
As you need to have ordering you can use TreeMap and method
Map.Entry<K,V> firstEntry();
I would express you logic as follow :
Get all entries in the map
Affect to each one its score (through the database call)
Order the entries in a final map according to step 2
It is important to notice that few maps have ordering constraints. The base implementation that comes to mind is LinkedHashMap. Furthermore "reordering an existing map" seems like a strange idea that is not backed by any methods in the Map interface. So in a nutshell, saying you need to return a Map that has an ordering constraint seems like a bad/incomplete idea - but it is certainly doable.
I would also adivse against using a TreeMap which is a Map ordered by a Comparator because I see no constraint that your ordering values are unique. Plus, your ordering is on the values, not the keys, of the map. Such a comparator would not be straightforward at all.
So, in short, what I would do is
LinkedHashMap<String, String> sorted = map.entrySet().stream()
// This is your DB call
.sorted(Comparator.comparing(entry -> getDBValue(entry)))
// Now we have an ordered stream of key/value entries from the original map
.collect(
// We flush back to a Map
Collectors.toMap(
// Keeping the original keys as is
Map.Entry::getKey,
// Keeping the original values as is
Map.Entry::getValue,
// This merge step should never be called, as keys are unique. Maybe you could assert that and fail if this is called
(v1, v2) -> v1,
// This is where you instanciate the Map that respects ordering of keys. Here, LinkedHashMap is iterable in the order of insertion, which is what we want.
LinkedHashMap::new
)
);
With Java streams you can achieve this without using any additional collections, here is an implementation.
To maintain order have used LinkedHashMap in the collector
For simplicity I have taken one more map to hold the db values [you need to change this to get from DB]
Map<String, String> map1 = new HashMap<String, String>();
map1.put("100", "10");
map1.put("200", "20");
map1.put("300", "30");
Map<String, String> dbmap = new HashMap<String, String>();
dbmap.put("10", "8");
dbmap.put("20", "2");
dbmap.put("30", "9");
Comparator<String> comp = (k1, k2) -> dbmap.get(map1.get(k1)).compareTo(dbmap.get(map1.get(k2)));
Map<String, String> queryMap = map1.keySet().stream().sorted(comp)
.collect(toMap((String key) -> key, value -> (String) map1.get(value), (u, v) -> {
throw new IllegalStateException(String.format("Duplicate key %s", u));
}, LinkedHashMap::new));
System.out.println(queryMap);
Ouput
{200=20, 100=10, 300=30}