I'm trying to figure out how to use Java8 streams for the following case:
Suppose I have the following Map:
Map<String,String[]> map = { { "01": {"H01","H02","H03"}, {"11": {"B11","B12","B13"}} };
And the desired output would be:
map = { {"01": {"H02"}, {"11": {"B11"}};
My attempt:
map.entrySet().stream() //
.flatMap(entry -> Arrays.stream(entry.getValue()) //
.filter(channel -> Channel.isValid(channel))
.collect(Collectors.toMap()));
There are a couple issues with your current approach.
There is no toMap method with the signature toMap() so that will render a compilation error.
flatMap expects a function taking a type T and returning a Stream<R> whereas you're attempting to pass a map as a return value so that will also render a compilation error.
Instead it seems like you want something like this:
Map<String, List<String>> resultSet = map.entrySet()
.stream() //
.flatMap(entry -> Arrays.stream(entry.getValue())
.filter(Channel::isValid)
.map(e -> new AbstractMap.SimpleEntry<>(entry.getKey(), e)))
.collect(Collectors.groupingBy(AbstractMap.SimpleEntry::getKey,
Collectors.mapping(AbstractMap.SimpleEntry::getValue,
Collectors.toList())));
or much simpler solution:
map.entrySet()
.stream() //
.collect(Collectors.toMap(Map.Entry::getKey,
a -> Arrays.stream(a.getValue())
.filter(Channel::isValid)
.collect(Collectors.toList())));
Heres another way of solving your problem:
public static void stack2() {
Map<String, String[]> map = new HashMap<>();
map.put("01", new String[]{"H01", "H02", "H03"});
map.put("11", new String[]{"B11", "B12", "B13"});
Map<String, String[]> newMap = map.entrySet()
.stream()
.peek(e -> e.setValue(
Arrays.stream(e.getValue())
.filter(Main::stack2Filter)
.toArray(String[]::new)))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
newMap.forEach((key, value) ->
System.out.println("key: " + key + " value " + Arrays.toString(value)));
}
public static boolean stack2Filter(String entry) {
return entry.equals("H02") || entry.equals("B11");
}
Related
Here is the piece of code which was written in Java7 and I wants to convert into Java8 by using Streams and Lambdas.
public static Map<String, List<Employee>> getEmployees(List<Person> personList) {
Map<String, List<Employee>> result = new HashMap<>();
for (Person person : personList) {
String[] perArr = person.getName().split("-");
List<Employee> employeeList = result.get(perArr[0]);
if (employeeList == null) {
employeeList = new ArrayList<>();
}
employeeList.add(new Employee(person.getPersonId(), perArr[1]));
result.put(perArr[0], employeeList);
}
return result;
}
Well you were somehow close I would say, problem being that you would need to pass along to the next stage of the stream pipeline 3 things actually: first token and second token (from split("-")) and also person::getPersonId; I've used a List here and some casting for this purpose (you could use a Triple for example, I've heard apache has it):
personList.stream()
.map(person -> {
String[] tokens = person.getName().split("-");
return Arrays.asList(tokens[0], tokens[1], person.getPersonId());
})
.collect(Collectors.groupingBy(
list -> (String) list.get(0),
Collectors.mapping(
list -> new Employee((Integer) list.get(2), (String) list.get(1)),
Collectors.toList())));
A straightforward way to improve your loop is to use Map.computeIfAbsent to manage creation of new map entries:
for (Person person : personList) {
String[] perArr = person.getName().split("-");
List<Employee> employeeList = result.computeIfAbsent(perArr[0], x -> new ArrayList<>());
employeeList.add(new Employee(person.getPersonId(), perArr[1]));
}
Doing this with streams is somewhat awkward because you cannot conveniently carry the result of an intermediate computation and so you would have to either complicate matters with intermediate objects or just split the string again:
import static java.util.stream.Collectors.*;
personList.stream()
.collect(groupingBy(
p -> p.getName().split("-")[0],
mapping(
p -> new Employee(p.getPersonId(), p.getName().split("-")[1]),
toList()
)
));
I can create with enhanced for loop and with map's computeIfAbsent as below.
String [][] students = {{"David","50"},{"Sherif","70"},{"Bhavya","85"},{"Bhavya","95"}};
Map<String, List<Integer>> map = new HashMap<String, List<Integer>>();
for(String student[] : students) {
map.computeIfAbsent(student[0], (k)->new ArrayList<Integer>()).add(Integer.parseInt(student[1]));
}
Is there any way I can use stream with collectors api to build map as above?
Map<String, List<Integer>> m = Arrays.stream(students)
.collect(Collectors.?);
Try this out.
String[][] students = { { "David", "50" }, { "Sherif", "70" }, { "Bhavya", "85" }, { "Bhavya", "95" } };
Map<String, List<Integer>> studentsByName = Stream.of(students).collect(Collectors.groupingBy(kv -> kv[0],
Collectors.mapping(kv -> Integer.valueOf(kv[1]), Collectors.toList())));
System.out.println(studentsByName);
You can try like this
map = Arrays.stream(students)
.map(array->new Pair<String,Integer>(array[0],Integer.valueOf(array[1])))
.collect(Collectors.groupingBy(p->p.getKey(), Collectors.mapping(p->p.getValue(),
Collectors.toList())));
Using groupingBy:
Arrays.stream(students)
.map(a -> new AbstractMap.SimpleEntry<>(a[0], Integer.valueOf(a[1])))
.collect(groupingBy(AbstractMap.SimpleEntry::getKey,
mapping(AbstractMap.SimpleEntry::getValue,
toList())));
Using toMap:
Arrays.stream(students)
.map(a -> new AbstractMap.SimpleEntry<>(a[0], Integer.valueOf(a[1])))
.collect(toMap(AbstractMap.SimpleEntry::getKey,
k -> new ArrayList<>(Collections.singletonList(k.getValue())),
(left, right) -> {left.addAll(right);return left;}));
I have a LinkedHashMap which contains multiple entries. I'd like to reduce the multiple entries to a single one in the first step, and than map that to a single String.
For example:
I'm starting with a Map like this:
{"<a>"="</a>", "<b>"="</b>", "<c>"="</c>", "<d>"="</d>"}
And finally I want to get a String like this:
<a><b><c><d></d></c></b></a>
(In that case the String contains the keys in order, than the values in reverse order. But that doesn't really matter, I'd like an general solution)
I think I need map.entrySet().stream().reduce(), but I have no idea what to write in the reduce method, and how to continue.
Since you're reducing entries by concatenating keys with keys and values with values, the identity you're looking for is an entry with empty strings for both key and value.
String reduceEntries(LinkedHashMap<String, String> map) {
Entry<String, String> entry =
map.entrySet()
.stream()
.reduce(
new SimpleImmutableEntry<>("", ""),
(left, right) ->
new SimpleImmutableEntry<>(
left.getKey() + right.getKey(),
right.getValue() + left.getValue()
)
);
return entry.getKey() + entry.getValue();
}
Java 9 adds a static method Map.entry(key, value) for creating immutable entries.
here is an example about how I would do it :
import java.util.LinkedHashMap;
public class Main {
static String result = "";
public static void main(String [] args)
{
LinkedHashMap<String, String> map = new LinkedHashMap<String, String>();
map.put("<a>", "</a>");
map.put("<b>", "</b>");
map.put("<c>", "</c>");
map.put("<d>", "</d>");
map.keySet().forEach(s -> result += s);
map.values().forEach(s -> result += s);
System.out.println(result);
}
}
note: you can reverse values() to get d first with ArrayUtils.reverse()
We are trying to refactor below code to java 8:
List<String> list = new ArrayList<>();
Iterator<Obj> i = x.iterator();
while (i.hasNext()) {
String y = m(i.next().getKey());
if (y != null) {
list.add(y);
}
}
return list;
So far we have come up with:
return x.stream()
.filter(s -> m(s.getKey()) != null)
.map(t -> m(t.getKey()))
.collect(Collectors.toList());
But the method m() is being invoked twice here. Is there any way around?
Well you can do the filtering after the mapping step:
x.stream()
.map(s -> m(s.getKey()))
.filter(Objects::nonNull)
.collect(Collectors.toList());
I m using Java 8 for grouping by data. But results obtained are not in order formed.
Map<GroupingKey, List<Object>> groupedResult = null;
if (!CollectionUtils.isEmpty(groupByColumns)) {
Map<String, Object> mapArr[] = new LinkedHashMap[mapList.size()];
if (!CollectionUtils.isEmpty(mapList)) {
int count = 0;
for (LinkedHashMap<String, Object> map : mapList) {
mapArr[count++] = map;
}
}
Stream<Map<String, Object>> people = Stream.of(mapArr);
groupedResult = people
.collect(Collectors.groupingBy(p -> new GroupingKey(p, groupByColumns), Collectors.mapping((Map<String, Object> p) -> p, toList())));
public static class GroupingKey
public GroupingKey(Map<String, Object> map, List<String> cols) {
keys = new ArrayList<>();
for (String col : cols) {
keys.add(map.get(col));
}
}
// Add appropriate isEqual() ... you IDE should generate this
#Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final GroupingKey other = (GroupingKey) obj;
if (!Objects.equals(this.keys, other.keys)) {
return false;
}
return true;
}
#Override
public int hashCode() {
int hash = 7;
hash = 37 * hash + Objects.hashCode(this.keys);
return hash;
}
#Override
public String toString() {
return keys + "";
}
public ArrayList<Object> getKeys() {
return keys;
}
public void setKeys(ArrayList<Object> keys) {
this.keys = keys;
}
}
Here i am using my class groupingKey by which i m dynamically passing from ux. How can get this groupByColumns in sorted form?
Not maintaining the order is a property of the Map that stores the result. If you need a specific Map behavior, you need to request a particular Map implementation. E.g. LinkedHashMap maintains the insertion order:
groupedResult = people.collect(Collectors.groupingBy(
p -> new GroupingKey(p, groupByColumns),
LinkedHashMap::new,
Collectors.mapping((Map<String, Object> p) -> p, toList())));
By the way, there is no reason to copy the contents of mapList into an array before creating the Stream. You may simply call mapList.stream() to get an appropriate Stream.
Further, Collectors.mapping((Map<String, Object> p) -> p, toList()) is obsolete. p->p is an identity mapping, so there’s no reason to request mapping at all:
groupedResult = mapList.stream().collect(Collectors.groupingBy(
p -> new GroupingKey(p, groupByColumns), LinkedHashMap::new, toList()));
But even the GroupingKey is obsolete. It basically wraps a List of values, so you could just use a List as key in the first place. Lists implement hashCode and equals appropriately (but you must not modify these key Lists afterwards).
Map<List<Object>, List<Object>> groupedResult=
mapList.stream().collect(Collectors.groupingBy(
p -> groupByColumns.stream().map(p::get).collect(toList()),
LinkedHashMap::new, toList()));
Based on #Holger's great answer. I post this to help those who want to keep the order after grouping as well as changing the mapping.
Let's simplify and suppose we have a list of persons (int age, String name, String adresss...etc) and we want the names grouped by age while keeping ages in order:
final LinkedHashMap<Integer, List<String> map = myList
.stream()
.sorted(Comparator.comparing(p -> p.getAge())) //sort list by ages
.collect(Collectors.groupingBy(p -> p.getAge()),
LinkedHashMap::new, //keeps the order
Collectors.mapping(p -> p.getName(), //map name
Collectors.toList())));