Elements in second list matched with elements in first list - java

I have 2 lists as the following
List<String> firstList = Arrays.asList("E","B","A","C");
List<String> secondList = Arrays.asList("Alex","Bob","Chris","Antony","Ram","Shyam");
I want the output in the form of a map having values in the second list mapped to elements in the first list based on first character.
For example I want the output as
Map<String,List<String>> outputMap;
and it has the following content
key -> B, value -> a list having single element Bob
key -> A, value -> a list having elements Alex and Antony
key -> C, value -> a list having single element Chris
I did something like this
firstList.stream()
.map(first->
secondList.stream().filter(second-> second.startsWith(first))
.collect(Collectors.toList())
);
and can see the elements of the second list group by first character. However I am unsure as to how to store the same in a map .
Please note that my question is more from the perspective of using the streaming API to get the job done.

I'm pretty sure that instead of nesting streaming of both lists you should just group the second one by first letter and filter values by testing whether the first letter is in the first list
final Map<String, List<String>> result = secondList.stream()
.filter(s -> firstList.contains(s.substring(0, 1)))
.collect(Collectors.groupingBy(s -> s.substring(0, 1)));
You can also extract s.substring(0, 1) to some
String firstLetter(String string)
method to make code a little bit more readable

Just move that filter to a toMap() collector:
Map<String, List<String>> grouped = firstList.stream()
.collect(Collectors.toMap(first -> first, first -> secondList.stream()
.filter(second -> second.startsWith(first))
.collect(Collectors.toList())));
If you want only keys that have matching names, you can
.filter(first -> secondList.stream().anyMatch(second -> second.startsWith(first)))

Related

Extract keys from Map for which there is no duplicate values

Map is defined as:
Map<Integer,String> map = new HashMap<>();
map.put(2,"ram");
map.put(3,"ram");
map.put(4,"gopal");
map.put(5,"madan");
map.put(6,"shyam");
map.put(7,"gopal");
map.put(8,"ram");
My expected output is List which contains only keys for which there is no duplicate values.
5
6
My approach and thought process:
Thought process 1 :
I would take map.entrySet().stream().map(....) and then take another stream inside map and filter the values for which duplicate values are present.
The approach soon got wasted as the first indexed value would be again compared in the nested stream and i would happen to filter out all elements thus.
Thought process 2
I kept values in different List by :
List<String> subList = map.entrySet().stream()
.map((k)->k.getValue())
.collect(Collectors.toList());
and then:
map.entrySet().stream()
.filter(s ->
subList.contains(s.getValue()) )
.map(Map.Entry::getKey)
.collect(Collectors.toList());
But I am getting the output as
2
3
4
5
6
7
8
The output is obvious as the value that i am picking as s from stream i am comparing it in the pool where the value will be always present at-least one time.
I again thought then that if i could have a counter that count count and if the value is present then it would increment, but again all seems very much vague now.
Any ways in which i can iterate by index using stream, so that i can always leave the key value which i am taking and just comparing with rest of values.
will love to get a brief explanation.
You can break the task into two step. first count value repeating by groupingBy() and counting() collectors.
Map<String,Long> valueCount = map.values()
.stream()
.collect(Collectors.groupingBy(Function.identity(),Collectors.counting()));
Its result is:
{madan=1, shyam=1, gopal=2, ram=3}
second step is to find only keys which their values are not duplicate. so to attain this you can use filter() and filtering the map by previous step result.
map.entrySet()
.stream()
.filter(entry -> valueCount.get(entry.getValue())==1).map(Map.Entry::getKey)
.collect(Collectors.toList())
You can filter the values with frequency 1 while creating the subList such as:
Set<String> uniqueSet = map.values().stream()
.collect(Collectors.groupingBy(a -> a, Collectors.counting()))
.entrySet().stream()
.filter(a -> a.getValue() == 1)
.map((Map.Entry::getKey))
.collect(Collectors.toSet());
and then perform the same operation as:
Set<Integer> result = map.entrySet().stream()
.filter(e -> uniqueSet.contains(e.getValue()))
.map(Map.Entry::getKey)
.collect(Collectors.toSet());
Or as Holger pointed out in the comments, instead of counting, you could do away with a Boolean value to filter unique values as:
Set<String> uniqueSet = map.values().stream()
.collect(Collectors.toMap(Function.identity(), v -> true, (a,b) -> false))
.entrySet().stream()
.filter(Map.Entry::getValue)
.map((Map.Entry::getKey))
.collect(Collectors.toSet());

How to check if a value exists in an ArrayList that contains an ArrayList

So, I have an ArrayList that contains another ArrayList, and I'm trying to see if the inner Arraylist contains a value that I'm looking for. If it does, I want to return the index in which that array exists.
List<List<String>> messages = parseMessages(extract.getPath());
String needle = "test";
messages.stream() // Stream<List<String>>
.filter(message -> message.contains(needle))
.flatMap(List::stream) // Stream<String>
.forEach(System.out::println);
So I wrote this code after I captured an ArrayList within an ArrayList.
The array that I am trying to access is "Messages" which contains 2 other arraylist.
I want to use the contains method to check if the inner arraylist contains a value and return the index of that arrayList.
Thanks
Try this
messages.stream()
.filter(message -> message.contains(needle))
.map(message -> message.indexOf(needle))
.forEach(System.out::println);
The map stage returns the index of the value. This will continue for other lists even after matching inner list containing needle is found. To stop after the first match, you can use findFirst.
messages.stream()
.filter(message -> message.contains(needle))
.map(message -> message.indexOf(needle))
.findFirst()
.ifPresent(System.out::println);
If you want to get the index of the outer list,
IntStream.range(0, messages.size())
.filter(index -> messages.get(index).contains(needle))
.forEach(System.out::println);
Again, to stop after one match,
IntStream.range(0, messages.size())
.filter(index -> messages.get(index).contains(needle))
.findFirst()
.ifPresent(System.out::println);

Partitioning an arraylist into Map buckets

I have two ArrayLists of type String ArrayList<String> xList and ArrayList<String> yList and I want to partition one list into buckets, according to someAttribute (for the below example lets say it uses .contains().
Map<String, List<String>> yBuckets = new HashMap<>();
yList.forEach(y -> yBuckets.compute(y.contains("I'm here"), (k,v) ->
(v == null ? new ArrayList<>() : v).add(y)));
However I'm getting an incompatible types error (String can't be converted to Boolean). I've never really used Java 8 to date but it seems to be useful for what I want so I'm wondering if anyone would be able to tell me how i'd go about fixing this code.
Thanks
If you want to use lambda, you could use one of the toMap Collectors:
https://docs.oracle.com/javase/8/docs/api/java/util/stream/Collectors.html#mapping-java.util.function.Function-java.util.stream.Collector-
For Example, partitioning a list of names into buckets by last name:
List<String> names = Arrays.asList("Steve Ray", "Bobby Ray", "Billy Bob", "Timmy Bob");
Map<String, List<String>> nameBuckets = names.stream().collect(
Collectors.groupingBy(str -> {return str.split(" ")[1];}, Collectors.toList()));
For the example that you provided there is Collectors.partitioningBy but this will partition in two parts only - based on a Predicate.
yList.stream()
.collect(Collectors.partitioningBy(y -> y.contains("I'm here")));
As such that is going to be a result in the form of Map<Boolean, List<String>>.
If you have more groups, you would want to use Collectors.groupingBy that will partition your List base on a Function
There are 2 mistakes in your code. The first one is that if you want to partition based on .contains which returns a boolean your map needs to be Map<Boolean, List<String>>.
The second mistake is with parenthesis. The (k,v) ->... lambda needs to return List<String> because you put .add(y) inside the lambda it return a boolean you need to add to the list returned by compute like so:
yList.forEach(y -> yBuckets.compute(y.contains("I'm here"), (k,v) ->
(v == null ? new ArrayList<>() : v)).add(y));

How to use Map filter in list by Java 8 lambda

Map is Map<String, List<User>> and List is List<User>. I want to use
Map<String,List<User>> newMap = oldMap.stream()
.filter(u ->userList.stream()
.filter(ul ->ul.getName().equalsIgnoreCase(u.getKey()).count()>0))
.collect(Collectors.toMap(u.getKey, u.getVaule()));
can't change to new Map. Why?
There are several problems with your code:
Map does not have a stream(): its entry set does, so you need to call entrySet() first.
There are a couple of misplaced parentheses
Collectors.toMap code is incorrect: you need to use the lambda u -> u.getKey() (or the method-reference Map.Entry::getKey) and not just the expression u.getKey(). Also, you mispelled getValue().
This would be a corrected code:
Map<String, List<User>> newMap =
oldMap.entrySet()
.stream()
.filter(u -> userList.stream()
.filter(ul ->ul.getName().equalsIgnoreCase(u.getKey())).count() > 0
).collect(Collectors.toMap(u -> u.getKey(), u -> u.getValue()));
But a couple of notes here:
You are filtering only to see if the count is greater than 0: instead you could just use anyMatch(predicate). This is a short-cuiting terminal operation that returns true if the predicate is true for at least one of the elements in the Stream. This has also the advantage that this operation might not process all the elements in the Stream (when filtering does)
It is inefficient since you are traversing the userList every time you need to filter a Stream element. It would be better to use a Set which has O(1) lookup (so first you would convert your userList into a userSet, transforming the username in lower-case, and then you would search this set for a lower-case value username).
This would be a more performant code:
Set<String> userNameSet = userList.stream().map(u -> u.getName().toLowerCase(Locale.ROOT)).collect(toSet());
Map<String,List<User>> newMap =
oldMap.entrySet()
.stream()
.filter(u -> userNameSet.contains(u.getKey().toLowerCase(Locale.ROOT)))
.collect(Collectors.toMap(u -> u.getKey(), u -> u.getValue()));
Perhaps you intended to create a Stream of the entry Set of the input Map.
Map<String,List<User>> newMap =
oldMap.entrySet().stream()
.filter(u ->userList.stream().filter(ul ->ul.getName().equalsIgnoreCase(u.getKey())).count()>0)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
This would create a Map that retains the entries of the original Map whose keys equal the name of at least one of the members of userList (ignoring case).

How to get certain information out of arraylist grouped into other lists in Java

I wrote a program, that reads multiple (similar) textfiles out of a Folder. Im splitting the information by space and store everything in one arraylist which contains data kind of this:
key1=hello
key2=good
key3=1234
...
key15=repetition
key1=morning
key2=night
key3=5678
...
Now I'm looking for a way to get those information out of this list and somehow grouped by their keys into other lists. So im looking for a way to get a result like this:
keyList1 = {hello,morning}
keyList2 = {good,night}
and so on.
So I have to check very line for a keyword such as "key1" and split the value at the "=" and go on and on.
I think, the datastructure that suits your (described) needs best is a MultiMap. It is like a map, but with the possibility to store more than one value for a key.
For example the implementation from the guava project.
http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/collect/Multimap.html
First, you have to iterate over the arraylist:
final Multimap<String, String> multimap = ArrayListMultimap.create();
for ( String element : arrayList ) {
String[] splitted = element.split( "=" );
multimap.put( splitted[0], splitted[1] );
}
You get a List of values the following way:
for (String key : multimap.keySet()) {
List<String> values = multimap.get(key);
}
You might want to add some sanity checks for the splitting of your Strings.
(Code is untested)
It looks like you may be looking something like this kind of grouping (assuming you have access to Java 8)
List<String> pairs = Files.readAllLines(Paths.get("input.txt"));
Map<String, List<String>> map = pairs
.stream()
.map(s -> s.split("="))
.collect(
Collectors.groupingBy(
arr -> arr[0],
LinkedHashMap::new,//to preserve order of keys
Collectors.mapping(arr -> arr[1],
Collectors.toList())));
System.out.println(pairs);
System.out.println("---");
System.out.println(map);
Output:
[key1=hello, key2=good, key3=1234, key15=repetition, key1=morning, key2=night, key3=5678]
---
{key1=[hello, morning], key2=[good, night], key3=[1234, 5678], key15=[repetition]}

Categories