I have started working with Java 8 and trying to convert some loops and old syntax in my code to lambdas and streams.
So for example, I'm trying to convert this while and for loop to stream, but I'm not getting it right:
List<String> list = new ArrayList<>();
if (!oldList.isEmpty()) {// old is a List<String>
Iterator<String> itr = oldList.iterator();
while (itr.hasNext()) {
String line = (String) itr.next();
for (Map.Entry<String, String> entry : map.entrySet()) {
if (line.startsWith(entry.getKey())) {
String newline = line.replace(entry.getKey(),entry.getValue());
list.add(newline);
}
}
}
}
I wanted to know if it's possible to convert the above example to a single stream where there is a while loop inside of a for loop.
As was stated above, using streams here doesn't really add value since it makes the code harder to read/understand. I get that you're doing it more as a learning exercise. That being said, doing something like this is a slightly more functional-style approach as it doesn't have a side effect of adding to the list from within the stream itself:
list = oldList.stream().flatMap(line->
map.entrySet().stream()
.filter(e->line.startsWith(e.getKey()))
.map(filteredEntry->line.replace(filteredEntry.getKey(),filteredEntry.getValue()))
).collect(Collectors.toList());
I don't see why you would want to use streams here, but it is possible.
Create some test input:
List<String> oldList = Arrays.asList("adda","bddb","cddc");
Map<String,String> map = new HashMap<>();
map.put("a", "x");
map.put("b", "y");
map.put("c", "z");
List<String> list = new ArrayList<>();
The actual code:
oldList.stream()
.forEach(line -> map.entrySet().stream()
.filter(entry -> line.startsWith(entry.getKey()))
.forEach(entry -> list.add(line.replace(entry.getKey(),entry.getValue()))));
Print the outcome:
list.forEach(System.out::println);
Which is:
xddx
yddy
zddz
To answer your question, it's a 1-liner:
List<String> list = oldList.stream()
.filter(line -> map.keySet().stream().anyMatch(line::startsWith))
.map(line -> map.entrySet().stream()
.filter(entry -> line.startsWith(entry.getKey()))
.map(entry -> line.replace(entry.getKey(), entry.getValue()))
.findFirst().get())
.collect(Collectors.toList());
You can achieve it with a stream nested in a stream created from oldList list. Nested stream plays role of mapping current value from oldList with a mapper defined in map, e.g.
public static void main(String[] args) {
final List<String> oldList = Arrays.asList("asd-qwe", "zxc", "123");
final Map<String, String> map = new HashMap<String, String>() {{
put("asd", "zcx");
put("12", "09");
put("qq", "aa");
}};
List<String> result = oldList.stream()
.map(line -> map.entrySet()
.stream()
.filter(entry -> line.startsWith(entry.getKey()))
.map(entry -> line.replace(entry.getKey(), entry.getValue()))
.collect(Collectors.toList())
)
.flatMap(Collection::stream)
.collect(Collectors.toList());
System.out.println(result);
}
Following example produces output like:
[zcx-qwe, 093]
Suggested solution can be easily parallelized if needed. Functional approach with no side effects.
Related
I've a Map<KeyString, List<MyVO>>.
MyVO.java contains:
String name;
int id;
I want to map it into Map<KeyString, List<names from MyVO>.
How can achieve this using java 8 streams?
You can use something like this:
Map<String, List<String>> response =
map.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
e -> e.getValue().stream()
.map(MyVO::getName)
.collect(Collectors.toList())));
I'm using a record to demo. A class would also work here.
record VO(String getStr)
}
first create some data
Map<String, List<VO>> map =
Map.of("A", List.of(new VO("S1"), new VO("S2")), "B",
List.of(new VO("S3"), new VO("S4")));
map.entrySet().forEach(System.out::println);
prints
A=[VO[str=S1], VO[str=S2]]
B=[VO[str=S3], VO[str=S4]]
now stream the entry set of the original map
and collect using Collectors.toMap.
use the orginal key from the Entry.
and stream the list of VO to pull out the string and create a new list.
Map<String,List<String>> result = map.entrySet().stream()
.collect(Collectors.toMap(
Entry::getKey,
e -> e.getValue().stream().map(VO::getStr).toList()));
prints
A=[S1, S2]
B=[S3, S4]
A solution:
public static void mapNames() {
final Map<String, List<MyVO>> voMap = new HashMap<>();
voMap.put("all", Arrays.asList(
new MyVO(1, "John"),
new MyVO(2, "Bill"),
new MyVO(3, "Johanna")
));
final Map<String, List<String>> nameMap = voMap.entrySet().stream()
.filter(Objects::nonNull)
.collect(
Collectors.toMap(
Map.Entry::getKey,
e -> e.getValue().stream()
.map(MyVO::getName)
.collect(Collectors.toList())
));
System.out.println(nameMap);
}
Output:
{all=[John, Bill, Johanna]}
I have a Map of Map which needs to be filtered based of another Map using lambda expressions
I tried to do filter on the map and find all matches based of another map but it doesnot seem to work. It seems the values are not filtered correctly.
Is there a way I can do streams and map and put the filtering logic there?
Can someone please help
public static void main(String []args){
System.out.println("Hello World");
Map<String,List<String>> items = new HashMap<>();
List<String> ut1=new ArrayList<>();
ut1.add("S");
ut1.add("C");
List<String> ut2=new ArrayList<>();
ut2.add("M");
List<String> ut3=new ArrayList<>();
ut3.add("M");
ut3.add("C");
items .put("1010016",ut1);
items .put("1010019",ut2);
items .put("1010012",ut3);
System.out.println("Map"+items);
Map<String,Map<String,String>> sKey = new HashMap<>();
Map<String,String> utKey1 = new HashMap<>();
utKey1.put("S","1001");
utKey1.put("M","1002");
utKey1.put("C","1003");
Map<String,String> utKey2 = new HashMap<>();
utKey2.put("S","1004");
Map<String,String> utKey3 = new HashMap<>();
utKey3.put("S","1005");
utKey3.put("M","1006");
Map<String,String> utKey4 = new HashMap<>();
utKey4.put("S","1007");
utKey4.put("M","1008");
utKey4.put("C","1009");
sKey.put("1010016",utKey1);
sKey.put("1010019",utKey2);
sKey.put("1010012",utKey3);
sKey.put("1010011",utKey4);
System.out.println("Map2"+sKey);
Map<String,Map<String,String>> map3 =
sKey.entrySet().stream()
.filter(x ->
items.containsKey(x.getKey())
&& x.getValue().entrySet().stream().allMatch(y ->
items.entrySet().stream().anyMatch(list ->
list.getValue().contains(y.getKey()))))
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
System.out.println("Map3"+map3);
}
the filtered map is returning as:
Map3{1010012={S=1005, M=1006}, 1010016={S=1001, C=1003, M=1002}, 1010019={S=1004}}
But the actual result should be:
Map3{1010012={M=1006}, 1010016={S=1001, C=1003}}
I will rather say this is a work around to achieve your expected output using stream.
Map<String, Map<String, String>> result =
sKey.entrySet().stream()
.filter(detail -> items.keySet().contains(detail.getKey()) &&
!Collections.disjoint(detail.getValue().keySet(), items.get(detail.getKey())))
.collect(HashMap::new,
(m,v) -> m.put(v.getKey(), v.getValue().entrySet().stream()
.filter(detail -> items.get(v.getKey()).contains(detail.getKey()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))),
HashMap::putAll);
Output
{1010012={M=1006}, 1010016={S=1001, C=1003}}
I inherited some code and there is a map which is defined as Map<String, Collection<Map<String, String>>>
Here is a sample set of values stored in this map:
{
A1: [
{Item Number: "1234",Tax Code: "1"},
{Item Number: "2345",Tax Code: "2"},
{Item Number: "1234",Tax Code: "1"}
],
B2: [
{Store Number: "111",Status: "2"},
{Store Number: "222",Status: "3"}
]
}
How can I get, say, A1.Item Number as a list of strings? I was planning to covert this to json and use json path. But since the map itself is available to me, is there an easier way?
I am using java 8.
Thanks!
You can achieve the required result like so:
String itemNumber = "1234";
List<String> result =
myMap.values()
.stream()
.flatMap(Collection::stream)
.map(Map::entrySet)
.flatMap(Collection::stream)
.filter(e -> e.getKey().equals(itemNumber))
.map(Map.Entry::getValue)
.collect(Collectors.toList());
This is most likely to be used numerous times, in which case it would be wise to encapsulate the logic into a method as such:
static List<String> getListOfValuesByItemNumber(
Map<String, Collection<Map<String, String>>> map,
String itemNumber){
return map.values()
.stream()
.flatMap(Collection::stream)
.map(Map::entrySet)
.flatMap(Collection::stream)
.filter(e -> e.getKey().equals(itemNumber))
.map(Map.Entry::getValue)
.collect(Collectors.toList());
}
Then simply pass a reference to the map and the item number as the second parameter and retrieve back a list of values where the map entry key is equal to the item number.
Further, a non-stream approach would be:
static List<String> getListOfValuesByItemNumber(
Map<String, Collection<Map<String, String>>> map,
String itemNumber){
List<String> accumulator = new ArrayList<>();
map.values()
.forEach(collection ->
collection.forEach(m -> m.forEach((k, v) -> {
if(k.equals(itemNumber))
accumulator.add(v);
})));
return accumulator;
}
If I understand you correctly, this is what you want:
public static List<String> extractFieldValues(
Map<String, Collection<Map<String, String>>> horribleMess,
String fieldName) {
return horribleMess.values()
.stream()
.flatMap(Collection::stream)
.map(map -> map.get(fieldName))
.collect(Collectors.toList());
}
But as indicated in a comment above, a Map/Collection/Map nesting is a maintenance nightmare you should get rid of through refactoring.
Solution is,
List<String> ItemNumberLists = ( map.values() ).stream()
.flatMap( Collection::stream )
.map( s -> s.get("a") )
.collect( Collectors.toList() );
If you still doubt ask in a comment.
I have a "," separated String array like this
a b c d,
f b h j,
l p o i,
I would like this to be converted to a Hashmap like
HashMap<String, List<String>> such that second element in list (delimited by space becomes key and the 3rd element becomes value)
So,
This should become
b -> c,h
p -> o
I want to use Streams API and I think this is the way to go:
List<String> entries = new ArrayList<>();
HashMap<String, List<String>> map = new HashMap<>();
HashMap<String, List<String>> newMap = entries.stream()
.collect(line -> {
if (map.contains(line.split(" ")[1])) {
// Get existing list and add the element
map.get(line.split(" ")[1].add(line.split(" ")[1]));
} else {
// Create a new list and add
List<String> values = new ArrayList<>();
values.add(line.split(" ")[1]);
map.put(line.split(" ")[0], values);
}
});
Is there any better way? How exactly should I return Hashmap from collect function?
You can use the Collectors.groupingBy as shown below to group the inputs (follow the inline comments):
String[] inputs = {"a b c d,", "f b h j,", "l p o i,"};
Map<String, List<String>> results =
Arrays.stream(inputs).map(s -> s.split(" ")).//splt with space
collect(Collectors.groupingBy(arr -> arr[1], // Make second element as the key
Collectors.mapping(arr -> arr[2], // Make third element as the value
Collectors.toList())));//collect the values to List
System.out.println(results);
Output:
{p=[o], b=[c, h]}
I suggest you read the API here to understand how Collectors.groupingBy along with Collectors.mappingworks.
You can achieve the task at hand using a groupingBy collector along with Collectors.mapping as a downstream collector.
Map<String, List<String>> collect =
myList.stream()
.map(s -> s.split(" "))
.collect(Collectors.groupingBy(a -> a[1],
Collectors.mapping(a -> a[2], Collectors.toList())));
output:
{p=[o], b=[c, h]}
if you want to maintain insertion order then you can specify a LinkedHashMap like this:
Map<String, List<String>> collect =
myList.stream()
.map(s -> s.split(" "))
.collect(Collectors.groupingBy(s -> s[1],
LinkedHashMap::new,
Collectors.mapping(s -> s[2], Collectors.toList())));
output:
{b=[c, h], p=[o]}
If you want HashMap , not just any Map
HashMap<String, List<String>> output =myList.stream().map(s -> s.split(" "))
.collect(Collectors.groupingBy((s) -> s[1],
HashMap::new,
Collectors.mapping(
(s) -> s[2],
Collectors.toList())));
Let consider a hashmap
Map<Integer, List> id1 = new HashMap<Integer,List>();
I inserted some values into both hashmap.
For Example,
List<String> list1 = new ArrayList<String>();
list1.add("r1");
list1.add("r4");
List<String> list2 = new ArrayList<String>();
list2.add("r2");
list2.add("r5");
List<String> list3 = new ArrayList<String>();
list3.add("r3");
list3.add("r6");
id1.put(1,list1);
id1.put(2,list2);
id1.put(3,list3);
id1.put(10,list2);
id1.put(15,list3);
Q1) Now I want to apply a filter condition on the key in hashmap and retrieve the corresponding value(List).
Eg: Here My query is key=1, and output should be 'list1'
I wrote
id1.entrySet().stream().filter( e -> e.getKey() == 1);
But I don't know how to retrieve as a list as output of this stream operation.
Q2) Again I want to apply a filter condition on the key in hashmap and retrieve the corresponding list of lists.
Eg: Here My query is key=1%(i.e key can be 1,10,15), and output should be 'list1','list2','list3'(list of lists).
If you are sure you are going to get at most a single element that passed the filter (which is guaranteed by your filter), you can use findFirst :
Optional<List> o = id1.entrySet()
.stream()
.filter( e -> e.getKey() == 1)
.map(Map.Entry::getValue)
.findFirst();
In the general case, if the filter may match multiple Lists, you can collect them to a List of Lists :
List<List> list = id1.entrySet()
.stream()
.filter(.. some predicate...)
.map(Map.Entry::getValue)
.collect(Collectors.toList());
What you need to do is create a Stream out of the Map's .entrySet():
// Map<K, V> --> Set<Map.Entry<K, V>> --> Stream<Map.Entry<K, V>>
map.entrySet().stream()
From the on, you can .filter() over these entries. For instance:
// Stream<Map.Entry<K, V>> --> Stream<Map.Entry<K, V>>
.filter(entry -> entry.getKey() == 1)
And to obtain the values from it you .map():
// Stream<Map.Entry<K, V>> --> Stream<V>
.map(Map.Entry::getValue)
Finally, you need to collect into a List:
// Stream<V> --> List<V>
.collect(Collectors.toList())
If you have only one entry, use this instead (NOTE: this code assumes that there is a value; otherwise, use .orElse(); see the javadoc of Optional for more details):
// Stream<V> --> Optional<V> --> V
.findFirst().get()
For your Q2, there are already answers to your question. For your Q1, and more generally when you know that the key's filtering should give a unique value, there's no need to use Streams at all.
Just use get or getOrDefault, i.e:
List<String> list1 = id1.getOrDefault(1, Collections.emptyList());
You can also do it like this
public Map<Boolean, List<Student>> getpartitionMap(List<Student> studentsList) {
List<Predicate<Student>> allPredicates = getAllPredicates();
Predicate<Student> compositePredicate = allPredicates.stream()
.reduce(w -> true, Predicate::and)
Map<Boolean, List<Student>> studentsMap= studentsList
.stream()
.collect(Collectors.partitioningBy(compositePredicate));
return studentsMap;
}
public List<Student> getValidStudentsList(Map<Boolean, List<Student>> studentsMap) throws Exception {
List<Student> validStudentsList = studentsMap.entrySet()
.stream()
.filter(p -> p.getKey() == Boolean.TRUE)
.flatMap(p -> p.getValue().stream())
.collect(Collectors.toList());
return validStudentsList;
}
public List<Student> getInValidStudentsList(Map<Boolean, List<Student>> studentsMap) throws Exception {
List<Student> invalidStudentsList =
partionedByPredicate.entrySet()
.stream()
.filter(p -> p.getKey() == Boolean.FALSE)
.flatMap(p -> p.getValue().stream())
.collect(Collectors.toList());
return invalidStudentsList;
}
With flatMap you will get just List<Student> instead of List<List<Student>>.
Thanks
Using keySet-
id1.keySet().stream()
.filter(x -> x == 1)
.map(x -> id1.get(x))
.collect(Collectors.toList())
why all that !!!
List list1 = id1.get(1);
HashMap<Integer, String> hashmap = new HashMap<>();
hashmap.put(1, "a");
hashmap.put(2, "b");
List<String> collect = hashmap.keySet().stream()
.map(k -> "key=" + k + " value=" + hashmap.get(k))
.collect(Collectors.toList());
System.out.println(collect);
Maybe the sample is oversimplified, but you don't need the Java stream API here. Just use the Map directly.
List<String> list1 = id1.get(1); // this will return the list from your map