I have an array of values
String [] str = { ONE, TWO, THREE };
and some key-value pairs stored in a map
Map<String, String> map;
How can I use the Stream API to get all entries from the map whose key is contained in the array?
I assume that when you say you want the entries from this map, you mean the values
so here is how I would do it
String[] str = {"ONE", "TWO", "THREE"};
Map<String, String> map = <Your map's here>;
List<String> values = Stream.of(str)
.filter(map::containsKey)
.map(map::get)
.collect(Collectors.toList());
If the map and the set of keys to remove are small, start with a copy of the map, and then modify it.
Map<String, String> filtered = new HashMap<>(map);
filtered.keySet().retainAll(Arrays.asList(str));
If the map is so much larger than the set of keys to remove that making a copy is undesirable, you can build a new map with only the correct entries:
Set<String> filter = new HashSet<>(Arrays.asList(str));
Map<String, String> filtered = map.entrySet().stream()
.filter(e -> filter.contains(e.getKey))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
You didn’t specify the form of the result.
If you want the same map but only containing the specified keys, you can use
Map<String, String> map;
Map<String, String> result = new HashMap<>(map);
result.keySet().retainAll(Arrays.asList(str));
If the old state is not needed anymore and the map is mutable, you may apply the second statement to the map without creating a copy.
A solution always creating a new Map would be
Map<String, String> result = Arrays.stream(str)
.collect(HashMap::new, (m, k) -> m.computeIfAbsent(k, map::get), Map::putAll);
If you just want an array containing values corresponding to the array containing the keys, you could use
String[] result = new String[str.length];
Arrays.setAll(result, ix -> map.get(str[ix]));
or
String[] result = Arrays.stream(str).map(map::get).toArray(String[]::new);
One way is to stream the array and collect the elements into a new map, with the keys being the elements and the values taken from your map for each key:
Map<String, String> result = Arrays.stream(str)
.collect(Collectors.toMap(e -> e, map::get));
This assumes all the elements of the array are keys in the map, and no duplicate elements on the array.
If you want to filter out elements that aren't keys of the map, use filter on the stream:
Map<String, String> result = Arrays.stream(str)
.filter(map::containsKey)
.collect(Collectors.toMap(e -> e, map::get));
And if you have duplicate elements in the array:
Map<String, String> result = Arrays.stream(str)
.filter(map::containsKey)
.collect(Collectors.toMap(e -> e, map::get, (o, n) -> o));
The last parameter (o, n) -> o is a merge function that is applied to values when there are duplicates keys on the map. In this case, the merge function keeps the old value and discards the new one.
Related
I am using Java8 to achieve the below things,
Map<String, String> m0 = new HashMap<>();
m0.put("x", "123");
m0.put("y", "456");
m0.put("z", "789");
Map<String, String> m1 = new HashMap<>();
m1.put("x", "000");
m1.put("y", "111");
m1.put("z", "222");
List<Map<String, String>> l = new ArrayList<>(Arrays.asList(m0, m1));
List<String> desiredKeys = Lists.newArrayList("x");
List<Map<String, String>> transformed = l.stream().map(map -> map.entrySet().stream()
.filter(e -> desiredKeys.stream().anyMatch(k -> k.equals(e.getKey())))
.collect(Collectors.toMap(e -> e.getKey(), p -> p.getValue())))
.filter(m -> !m.isEmpty())
.collect(Collectors.toList());
System.err.println(l);
System.err.println(transformed);
List<String> values = new ArrayList<>();
for (Map<String,String> map : transformed) {
values.add(map.values().toString());
System.out.println("Values inside map::"+map.values());
}
System.out.println("values::"+values); //values::[[123], [000]]
Here, I would like to fetch only the x-values from the list. I have achieved it but it is not in a proper format.
Expected output:
values::[123, 000]
Actual output:
values::[[123], [000]]
I know how to fix the actual output. But is there any easy way to achieve this issue? Any help would be appreciable.
You do not need to iterate over the entire map to find an entry by its key. That's what Map.get is for. To flatten the list of list of values, use flatMap:
import static java.util.stream.Collectors.toList;
.....
List<String> values = l.stream()
.flatMap(x -> desiredKeys.stream()
.filter(x::containsKey)
.map(x::get)
).collect(toList());
On a side note, avoid using l (lower case L) as a variable name. It looks too much like the number 1.
I’m not sure Streams will help, here. It’s easier to just loop through the Maps:
Collection<String> values = new ArrayList<>();
for (Map<String, String> map : l) {
Map<String, String> copy = new HashMap<>(map);
copy.keySet().retainAll(desiredKeys);
values.addAll(copy.values());
}
Flat map over the stream of maps to get a single stream representing the map entries of all your input maps. From there, you can filter out each entry whose key is not contained in the desired keys. Finally, extract the equivalent value of each entry to collect them into a list.
final List<String> desiredValues = l.stream()
.map(Map::entrySet)
.flatMap(Collection::stream)
.filter(entry -> desiredKeys.contains(entry.getKey()))
.map(Map.Entry::getValue)
.collect(Collectors.toList());
EDIT
This assumes that if a map has the key "x" it must also has the key "y" so to fetch the corredponding value.
final List<String> desiredValues = l.stream()
.filter(map -> map.containsKey("x"))
.map(map -> map.get("y"))
.collect(Collectors.toList());
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}
I am trying to sort a map to show in a dropdown. But I am not able to get any sorting done. This will return a new map. But not with the map sorted by the key as I would expect.
private Map<String, String> mapInstrumentIDs = new TreeMap<>();
Map<Object, Object> val = mapInstrumentIDs
.entrySet()
.stream()
.sorted(Map.Entry.comparingByKey())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
I of course didn't think about that the key is actually an integer. This means sorting it as a string does not give me the expected result (as integer sort). Changing the key to Integer and converting the value will yield the expected result.
By default a TreeMap guarantees that its elements will be sorted in ascending key order.
You should collect the results into a Map implementation that retains the order of its entries. LinkedHashMap will do:
Map<String, String> sorted = mapInstrumentIDs.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.collect(toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(x,y)-> {throw new AssertionError();},
LinkedHashMap::new
));
Normally one would make a copy as:
private SortedMap<String, String> mapInstrumentIDs = new TreeMap<>();
SortedMap<String, String> val = new TreeMap(mapInstrumentIDs);
If you want a copy with key type Comparable<?> and value type Object, you want to specify the initial map as TreeMap and cannot use the standard collectors:
SortedMap<Comparable, Object> val = mapInstrumentIDs
.entrySet()
.collect(TreeMap<Comparable, Object>::new,
(m, e) -> { m.put(e.getKey(), e.getValue()); return m; },
(m, m2) -> m.addAll(m2)
);
I have the following Object and a Map:
MyObject
String name;
Long priority;
foo bar;
Map<String, List<MyObject>> anotherHashMap;
I want to convert the Map in another Map. The Key of the result map is the key of the input map. The value of the result map ist the Property "name" of My object, ordered by priority.
The ordering and extracting the name is not the problem, but I could not put it into the result map. I do it the old Java 7 way, but it would be nice it is possible to use the streaming API.
Map<String, List<String>> result = new HashMap<>();
for (String identifier : anotherHashMap.keySet()) {
List<String> generatedList = anotherHashMap.get(identifier).stream()...;
teaserPerPage.put(identifier, generatedList);
}
Has anyone an idea? I tried this, but got stuck:
anotherHashMap.entrySet().stream().collect(Collectors.asMap(..., ...));
Map<String, List<String>> result = anotherHashMap
.entrySet().stream() // Stream over entry set
.collect(Collectors.toMap( // Collect final result map
Map.Entry::getKey, // Key mapping is the same
e -> e.getValue().stream() // Stream over list
.sorted(Comparator.comparingLong(MyObject::getPriority)) // Sort by priority
.map(MyObject::getName) // Apply mapping to MyObject
.collect(Collectors.toList())) // Collect mapping into list
);
Essentially, you stream over each entry set and collect it into a new map. To compute the value in the new map, you stream over the List<MyOjbect> from the old map, sort, and apply a mapping and collection function to it. In this case I used MyObject::getName as the mapping and collected the resulting names into a list.
For generating another map, we can have something like following:
HashMap<String, List<String>> result = anotherHashMap.entrySet().stream().collect(Collectors.toMap(elem -> elem.getKey(), elem -> elem.getValue() // can further process it);
Above I am recreating the map again, but you can process the key or the value according to your needs.
Map<String, List<String>> result = anotherHashMap.entrySet().stream().collect(Collectors.toMap(
Map.Entry::getKey,
e -> e.getValue().stream()
.sorted(comparing(MyObject::getPriority))
.map(MyObject::getName)
.collect(Collectors.toList())));
Similar to answer of Mike Kobit, but sorting is applied in the correct place (i.e. value is sorted, not map entries) and more concise static method Comparator.comparing is used to get Comparator for sorting.
I have a list of maps.
List<Map<Integer, String>>
The values in the list are, for example
<1, String1>
<2, String2>
<1, String3>
<2, String4>
As an end result, I want a Map>, like
<1, <String1, String3>>
<2, <String2, String4>>
How can I achieve this in Java.
CODE :
List<Map<Integer, String>> genericList = new ArrayList<Map<Integer,String>>();
for(TrackActivity activity : activityMajor){
Map<Integer, String> mapIdResponse = activity.getMapIdResponse();
genericList.add(mapIdResponse);
}
Now this genericList is the input and from this list, based on the same ids I want a
Map<Integer, List<String>> mapIdResponseList
Basically, to club the responses which are String based on the ids, grouping the responses with same id in a list and then creating a new map with that id as the key and the list as its value.
You can do it the following with Java 8:
private void init() {
List<Map<Integer, String>> mapList = new ArrayList<>();
Map<Integer, String> map1 = new HashMap<>();
map1.put(1, "String1");
mapList.add(map1);
Map<Integer, String> map2 = new HashMap<>();
map2.put(2, "String2");
mapList.add(map2);
Map<Integer, String> map3 = new HashMap<>();
map3.put(1, "String3");
mapList.add(map3);
Map<Integer, String> map4 = new HashMap<>();
map4.put(2, "String4");
mapList.add(map4);
Map<Integer, List<String>> response = mapList.stream()
.flatMap(map -> map.entrySet().stream())
.collect(
Collectors.groupingBy(
Map.Entry::getKey,
Collectors.mapping(
Map.Entry::getValue,
Collectors.toList()
)
)
);
response.forEach((i, l) -> {
System.out.println("Integer: " + i + " / List: " + l);
});
}
This will print:
Integer: 1 / List: [String1, String3]
Integer: 2 / List: [String2, String4]
Explanation (heavily warranted), I am afraid I cannot explain every single detail, you need to understand the basics of the Stream and Collectors API introduced in Java 8 first:
Obtain a Stream<Map<Integer, String>> from the mapList.
Apply the flatMap operator, which roughly maps a stream into an already existing stream.
Here: I convert all Map<Integer, String> to Stream<Map.Entry<Integer, String>> and add them to the existing stream, thus now it is also of type Stream<Map.Entry<Integer, String>>.
I intend to collect the Stream<Map.Entry<Integer, String>> into a Map<Integer, List<String>>.
For this I will use a Collectors.groupingBy, which produces a Map<K, List<V>> based on a grouping function, a Function that maps the Map.Entry<Integer, String> to an Integer in this case.
For this I use a method reference, which exactly does what I want, namely Map.Entry::getKey, it operates on a Map.Entry and returns an Integer.
At this point I would have had a Map<Integer, List<Map.Entry<Integer, String>>> if I had not done any extra processing.
To ensure that I get the correct signature, I must add a downstream to the Collectors.groupingBy, which has to provide a collector.
For this downstream I use a collector that maps my Map.Entry entries to their String values via the reference Map.Entry::getValue.
I also need to specify how they are being collected, which is just a Collectors.toList() here, as I want to add them to a list.
And this is how we get a Map<Integer, List,String>>.
Have a look at guavas MultiMap. Should be exactly what you are looking for:
http://code.google.com/p/guava-libraries/wiki/NewCollectionTypesExplained#Multimap