Related
I have a list of Map of Strings like this
List<Map<String, String>> dataListMap = new ArrayList<>();
Map<String, String> dataMap = new HashMap<String, String>() {
{
put("Charged fare", "3");
put("Trip ID", "1");
put("Account", "220");
}
};
dataListMap.add(dataMap);
dataMap = new HashMap<String, String>() {
{
put("Charged fare", "5");
put("Trip ID", "2");
put("Account", "220");
}
};
dataListMap.add(dataMap);
dataMap = new HashMap<String, String>() {
{
put("Charged fare", "7");
put("Trip ID", "3");
put("Account", "230");
}
};
dataListMap.add(dataMap);
dataMap = new HashMap<String, String>() {
{
put("Charged fare", "8");
put("Trip ID", "4");
put("Account", "230");
}
};
dataListMap.add(dataMap);
I want to separate this list by the account number and convert this to two list in side a List<List<Map<String,String>>> Is there an easy way to do that?Please help
List<List<Map<String, String>>> groupedByIds =
new ArrayList<>(dataListMap.stream()
.collect(Collectors.groupingBy(i -> i.get("Account")))
.values());
System.out.println(groupedByIds);
I believe that this is what you asked for but you should try specifying your use case, it can probably be better designed.
Collection<List<Map<String, String>>> accountString = dataListMap.stream().collect(groupingBy(m -> m.get("Account"))).values(); for(List<Map<String, String>> account: accountString){ calculateDailyCap(account); } is it possible to convert this back to only one list?
List<Map<String,String>> regroupedList = groupedByIds.stream()
.flatMap(Collection::stream)
.collect(Collectors.toList());
System.out.println(regroupedList);
Yes you can do it with flatmap, but you already have the list at the start why would you do that?
Stream never affects the result of your initial Collection, it always returns new result, so it is safe to reuse the list you had at the start.
I think you need to iterate over the list and push the Maps in two new Lists of Maps according to their Account number.
However without knowing the precise task you're trying to achieve, I would consider creating a new class that holds the three attributes you currently contain in your map.
That would result it way more robust code, that is also easier to extend in the future.
I have an instances of Student class.
class Student {
String name;
String addr;
String type;
public Student(String name, String addr, String type) {
super();
this.name = name;
this.addr = addr;
this.type = type;
}
#Override
public String toString() {
return "Student [name=" + name + ", addr=" + addr + "]";
}
public String getName() {
return name;
}
public String getAddr() {
return addr;
}
}
And I have a code to create a map , where it store the student name as the key and some processed addr values (a List since we have multiple addr values for the same student) as the value.
public class FilterId {
public static String getNum(String s) {
// should do some complex stuff, just for testing
return s.split(" ")[1];
}
public static void main(String[] args) {
List<Student> list = new ArrayList<Student>();
list.add(new Student("a", "test 1", "type 1"));
list.add(new Student("a", "test 1", "type 2"));
list.add(new Student("b", "test 1", "type 1"));
list.add(new Student("c", "test 1", "type 1"));
list.add(new Student("b", "test 1", "type 1"));
list.add(new Student("a", "test 1", "type 1"));
list.add(new Student("c", "test 3", "type 2"));
list.add(new Student("a", "test 2", "type 1"));
list.add(new Student("b", "test 2", "type 1"));
list.add(new Student("a", "test 3", "type 1"));
Map<String, List<String>> map = new HashMap<>();
// This will create a Map with Student names (distinct) and the test numbers (distinct List of tests numbers) associated with them.
for (Student student : list) {
if (map.containsKey(student.getName())) {
List<String> numList = map.get(student.getName());
String value = getNum(student.getAddr());
if (!numList.contains(value)) {
numList.add(value);
map.put(student.getName(), numList);
}
} else {
map.put(student.getName(), new ArrayList<>(Arrays.asList(getNum(student.getAddr()))));
}
}
System.out.println(map.toString());
}
}
Output would be :
{a=[1, 2, 3], b=[1, 2], c=[1, 3]}
How can I just do the same in java8 in a much more elegant way, may be using the streams ?
Found this Collectors.toMap in java 8 but could't find a way to actually do the same with this.
I was trying to map the elements as CSVs but that it didn't work since I couldn't figure out a way to remove the duplicates easily and the output is not what I need at the moment.
Map<String, String> map2 = new HashMap<>();
map2 = list.stream().collect(Collectors.toMap(Student::getName, Student::getAddr, (a, b) -> a + " , " + b));
System.out.println(map2.toString());
// {a=test 1 , test 1 , test 1 , test 2 , test 3, b=test 1 , test 1 , test 2, c=test 1 , test 3}
With streams, you could use Collectors.groupingBy along with Collectors.mapping:
Map<String, Set<String>> map = list.stream()
.collect(Collectors.groupingBy(
Student::getName,
Collectors.mapping(student -> getNum(student.getAddr()),
Collectors.toSet())));
I've chosen to create a map of sets instead of a map of lists, as it seems that you don't want duplicates in the lists.
If you do need lists instead of sets, it's more efficient to first collect to sets and then convert the sets to lists:
Map<String, List<String>> map = list.stream()
.collect(Collectors.groupingBy(
Student::getName,
Collectors.mapping(s -> getNum(s.getAddr()),
Collectors.collectingAndThen(Collectors.toSet(), ArrayList::new))));
This uses Collectors.collectingAndThen, which first collects and then transforms the result.
Another more compact way, without streams:
Map<String, Set<String>> map = new HashMap<>(); // or LinkedHashMap
list.forEach(s ->
map.computeIfAbsent(s.getName(), k -> new HashSet<>()) // or LinkedHashSet
.add(getNum(s.getAddr())));
This variant uses Iterable.forEach to iterate the list and Map.computeIfAbsent to group transformed addresses by student name.
First of all, the current solution is not really elegant, regardless of any streaming solution.
The pattern of
if (map.containsKey(k)) {
Value value = map.get(k);
...
} else {
map.put(k, new Value());
}
can often be simplified with Map#computeIfAbsent. In your example, this would be
// This will create a Map with Student names (distinct) and the test
// numbers (distinct List of tests numbers) associated with them.
for (Student student : list)
{
List<String> numList = map.computeIfAbsent(
student.getName(), s -> new ArrayList<String>());
String value = getNum(student.getAddr());
if (!numList.contains(value))
{
numList.add(value);
}
}
(This is a Java 8 function, but it is still unrelated to streams).
Next, the data structure that you want to build there does not seem to be the most appropriate one. In general, the pattern of
if (!list.contains(someValue)) {
list.add(someValue);
}
is a strong sign that you should not use a List, but a Set. The set will contain each element only once, and you will avoid the contains calls on the list, which are O(n) and thus may be expensive for larger lists.
Even if you really need a List in the end, it is often more elegant and efficient to first collect the elements in a Set, and afterwards convert this Set into a List in one dedicated step.
So the first part could be solved like this:
// This will create a Map with Student names (distinct) and the test
// numbers (distinct List of tests numbers) associated with them.
Map<String, Collection<String>> map = new HashMap<>();
for (Student student : list)
{
String value = getNum(student.getAddr());
map.computeIfAbsent(student.getName(), s -> new LinkedHashSet<String>())
.add(value);
}
It will create a Map<String, Collection<String>>. This can then be converted into a Map<String, List<String>> :
// Convert the 'Collection' values of the map into 'List' values
Map<String, List<String>> result =
map.entrySet().stream().collect(Collectors.toMap(
Entry::getKey, e -> new ArrayList<String>(e.getValue())));
Or, more generically, using a utility method for this:
private static <K, V> Map<K, List<V>> convertValuesToLists(
Map<K, ? extends Collection<? extends V>> map)
{
return map.entrySet().stream().collect(Collectors.toMap(
Entry::getKey, e -> new ArrayList<V>(e.getValue())));
}
I do not recommend this, but you also could convert the for loop into a stream operation:
Map<String, Set<String>> map =
list.stream().collect(Collectors.groupingBy(
Student::getName, LinkedHashMap::new,
Collectors.mapping(
s -> getNum(s.getAddr()), Collectors.toSet())));
Alternatively, you could do the "grouping by" and the conversion from Set to List in one step:
Map<String, List<String>> result =
list.stream().collect(Collectors.groupingBy(
Student::getName, LinkedHashMap::new,
Collectors.mapping(
s -> getNum(s.getAddr()),
Collectors.collectingAndThen(
Collectors.toSet(), ArrayList<String>::new))));
Or you could introduce an own collector, that does the List#contains call, but all this tends to be far less readable than the other solutions...
I think you are looking for something like below
Map<String,Set<String>> map = list.stream().
collect(Collectors.groupingBy(
Student::getName,
Collectors.mapping(e->getNum(e.getAddr()), Collectors.toSet())
));
System.out.println("Map : "+map);
Here is a version that collects everything in sets, and converts the final result to array lists:
/*
import java.util.*;
import java.util.stream.*;
import static java.util.stream.Collectors.*;
import java.util.function.*;
*/
Map<String, List<String>> map2 = list.stream().collect(groupingBy(
Student::getName, // we will group the students by name
Collector.of(
HashSet::new, // for each student name, we will collect result in a hash set
(arr, student) -> arr.add(getNum(student.getAddr())), // which we fill with processed addresses
(left, right) -> { left.addAll(right); return left; }, // we merge subresults like this
(Function<HashSet<String>, List<String>>) ArrayList::new // finish by converting to List
)
));
System.out.println(map2);
// Output:
// {a=[1, 2, 3], b=[1, 2], c=[1, 3]}
EDIT: made the finisher shorter using Marco13's hint.
Let's say I have a HashMap with String keys and Integer values:
map = {cat=1, kid=3, girl=3, adult=2, human=5, dog=2, boy=2}
I want to switch the keys and values by putting this information into another HashMap. I know that a HashMap cannot have duplicate keys, so I tried to put the information into a HashMap with the Integer for the keys that would map to a String ArrayList so that I could potentially have one Integer mapping to multiple Strings:
swap = {1=[cat], 2=[adult, dog, boy], 3=[kid, girl], 5=[human]}
I tried the following code:
HashMap<Integer, ArrayList<String>> swap = new HashMap<Integer, ArrayList<String>>();
for (String x : map.keySet()) {
for (int i = 0; i <= 5; i++) {
ArrayList<String> list = new ArrayList<String>();
if (i == map.get(x)) {
list.add(x);
swap.put(i, list);
}
}
}
The only difference in my code is that I didn't hard code the number 5 into my index; I have a method that finds the highest integer value in the original HashMap and used that. I know it works correctly because I get the same output even if I hard code the 5 in there, I just didn't include it to save space.
My goal here is to be able to do this 'reversal' with any set of data, otherwise I could just hard code the value. The output I get from the above code is this:
swap = {1=[cat], 2=[boy], 3=[girl], 5=[human]}
As you can see, my problem is that the value ArrayList is only keeping the last String that was put into it, instead of collecting all of them. How can I make the ArrayList store each String, rather than just the last String?
With Java 8, you can do the following:
Map<String, Integer> map = new HashMap<>();
map.put("cat", 1);
map.put("kid", 3);
map.put("girl", 3);
map.put("adult", 2);
map.put("human", 5);
map.put("dog", 2);
map.put("boy", 2);
Map<Integer, List<String>> newMap = map.keySet()
.stream()
.collect(Collectors.groupingBy(map::get));
System.out.println(newMap);
The output will be:
{1=[cat], 2=[adult, dog, boy], 3=[kid, girl], 5=[human]}
you are recreating the arrayList for every iteration and i can't figure out a way to do it with that logic, here is a good way though and without the need to check for the max integer:
for (Map.Entry<String, Integer> entry : map.entrySet()) {
String key = entry.getKey();
Integer value = entry.getValue();
List<String> get = swap.get(value);
if (get == null) {
get = new ArrayList<>();
swap.put(value, get);
}
get.add(key);
}
Best way is to iterate over the key set of the original map.
Also you have to asure that the List is present for any key in the target map:
for (Map.Entry<String,Integer> inputEntry : map.entrySet())
swap.computeIfAbsent(inputEntry.getValue(),()->new ArrayList<>()).add(inputEntry.getKey());
This is obviously not the best solution, but approaches the problem the same way you did by interchanging inner and outer loops as shown below.
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("cat", 1);
map.put("kid", 3);
map.put("girl", 3);
map.put("adult", 2);
map.put("human", 5);
map.put("dog", 2);
map.put("boy", 2);
HashMap<Integer, ArrayList<String>> swap = new HashMap<Integer, ArrayList<String>>();
for (Integer value = 0; value <= 5; value++) {
ArrayList<String> list = new ArrayList<String>();
for (String key : map.keySet()) {
if (map.get(key) == value) {
list.add(key);
}
}
if (map.containsValue(value)) {
swap.put(value, list);
}
}
Output
{1=[cat], 2=[adult, dog, boy], 3=[kid, girl], 5=[human]}
Best way I can think of is using Map.forEach method on existing map and Map.computeIfAbsent method on new map:
Map<Integer, List<String>> swap = new HashMap<>();
map.forEach((k, v) -> swap.computeIfAbsent(v, k -> new ArrayList<>()).add(k));
As a side note, you can use the diamond operator <> to create your new map (there's no need to repeat the type of the key and value when invoking the map's constructor, as the compiler will infer them).
As a second side note, it's good practice to use interface types instead of concrete types, both for generic parameter types and for actual types. This is why I've used List and Map instead of ArrayList and HashMap, respectively.
Using groupingBy like in Jacob's answer but with Map.entrySet for better performance, as suggested by Boris:
// import static java.util.stream.Collectors.*
Map<Integer, List<String>> swap = map.entrySet()
.stream()
.collect(groupingBy(Entry::getValue, mapping(Entry::getKey, toList())));
This uses two more methods of Collectors: mapping and toList.
If it wasn't for these two helper functions, the solution could look like this:
Map<Integer, List<String>> swap = map.entrySet()
.stream()
.collect(
groupingBy(
Entry::getValue,
Collector.of(
ArrayList::new,
(list, e) -> {
list.add(e.getKey());
},
(left, right) -> { // only needed for parallel streams
left.addAll(right);
return left;
}
)
)
);
Or, using toMap instead of groupingBy:
Map<Integer, List<String>> swap = map.entrySet()
.stream()
.collect(
toMap(
Entry::getValue,
(e) -> new ArrayList<>(Arrays.asList(e.getKey())),
(left, right) -> {
left.addAll(right);
return left;
}
)
);
It seams you override the values instrad of adding them to the already creared arraylist. Try this:
HashMap<Integer, ArrayList<String>> swapedMap = new HashMap<Integer, ArrayList<String>>();
for (String key : map.keySet()) {
Integer swappedKey = map.get(key);
ArrayList<String> a = swapedMap.get(swappedKey);
if (a == null) {
a = new ArrayList<String>();
swapedMap.put(swappedKey, a)
}
a.add(key);
}
I didn't have time to run it (sorry, don't have Java compiler now), but should be almost ok :)
You could use the new merge method in java-8 from Map:
Map<Integer, List<String>> newMap = new HashMap<>();
map.forEach((key, value) -> {
List<String> values = new ArrayList<>();
values.add(key);
newMap.merge(value, values, (left, right) -> {
left.addAll(right);
return left;
});
});
Is it possible to map key to Multi Dimensional Array List. Some thing like following example..
Map<K,V>
Where K is key for list of alphabet and V is a multi dimensional array list or normal array list that stores list of word. Some thing like a application that reads a dictionary file. I want to see an example. Example can be anything related to Map and Multi Dimensional Array-list. Or is there any other efficient way to implement collection? I have never used such implementations so if there is already a thread related to mine QA please post the link.
You can always do Map<String, <List<String>>. e.g.
Map<String, List<String>> multimap = new HashMap<String, List<String>>();
String key = "asdf";
List<String> values = Arrays.asList("foo", "bar");
multimap.put(key, values);
You can also use the Multimap<String, String> interface in Google Guava - might be a better fit for your needs. It simplifies the coding somewhat -
Multimap<String, String> multimap = new ArrayListMultimap<String, String>();
String key = "asdf";
multimap.put(key, "foo");
multimap.put(key, "bar");
You can use Guava's Multimap's if you want to associate multiple values with a single key. You need ArrayListMultimap for your specific case.
Example
ListMultimap<String, String> dict= new ArrayListMultimap<String, String>();
dict.put("key 1", "value 1");
dict.put("key 1", "value 2");
dict.put("key 2", "value 1");
Following code without Google's Guava library. It is used for double value as key and sorted order
Map<Double,List<Object>> multiMap = new TreeMap<Double,List<Object>>();
for( int i= 0;i<15;i++)
{
List<Object> myClassList = multiMap.get((double)i);
if(myClassList == null)
{
myClassList = new ArrayList<Object>();
multiMap.put((double) i,myClassList);
}
myClassList.add("Value "+ i);
}
List<Object> myClassList = multiMap.get((double)0);
if(myClassList == null)
{
myClassList = new ArrayList<Object>();
multiMap.put( (double) 0,myClassList);
}
myClassList.add("Value Duplicate");
for (Map.Entry entry : multiMap.entrySet())
{
System.out.println("Key = " + entry.getKey() + ", Value = " +entry.getValue());
}
I'm just starting to learn to use HashMap and reading the java tutorial, but I'm having trouble.
I'm trying to update the List inside a HashMap but I want to get the List of that key, is there a way to update a specific List of the key instead of having to make...5 different Lists and updating those?
HashMap<String, ArrayList<String>> mMap = new HashMap<String, ArrayList<String>>();
ArrayList<String> list = new ArrayList<String>();
mMap.put("A", list);
mMap.put("B", list);
mMap.put("C", list);
mMap.put("D", list);
Iterator iter = mMap.entrySet().iterator();
if (mMap.containsKey("A"))
{
Map.Entry mEntry = (Map.Entry) iter.next();
list.add("test");
mMap.put("A",list);
System.out.println(mEntry.getKey() + " : " + mEntry.getValue());
}
else if (mMap.containsKey("B"))
{
Map.Entry mEntry = (Map.Entry) iter.next();
list.add("entry");
mMap.put("B",list);
System.out.println(mEntry.getKey() + " : " + mEntry.getValue());
}
You could use something like:
mMap.get("A").add("test");
mMap.get("B").add("entry");
Like #Tudor said, use mMap.get("A").add("foo");
You put the same exact list into each map entry. You initial lines should be
mMap.put("A", new ArrayList());
mMap.put("B", new ArrayList());
...
mMap.put("Z", new ArrayList());
Alternatvely, write a method that checks on the fly
public synchronized void myAdd(String key, String value) {
List<String> there = mMap.get(key);
if (there == null) {
there = new ArrayList<String>();
mMap.put(key, there);
}
there.add(value);
}
you probably mean:
HashMap<String, List<String>> mMap = new HashMap<String, List<String>>();
mMap.put("A", new ArrayList<String>());
mMap.put("B", new ArrayList<String>());
mMap.put("C", new ArrayList<String>());
mMap.put("D", new ArrayList<String>());
if (mMap.containsKey("A"))
{
mMap.get("A").add("test");
System.out.println(mEntry.getKey() + " : " + mEntry.getValue());
}
else if (mMap.containsKey("B"))
{
mMap.get("B").add("entry");
System.out.println(mEntry.getKey() + " : " + mEntry.getValue());
}
...
I wonder if you really need those containsKey checks either!
HTH!
I think that in your case you can use Google Guava's Multimap and ListMultimap interfaces and ArrayListMultimap implementation.
Choosing right collection (in link there are only standard collections but Multimap is right in this case) makes code more readable:
ListMultimap<String, String> myMultimap = ArrayListMultimap.create();
myMultimap.put("A", "test");
myMultimap.put("B", "entry");
then myMultimap.get("A") gives you list (ArrayList instance in fact) with one element: "test", while myMultimap.get("C") gives you empty list.
Comparing to Map<String, List<String>> approach:
you don't have to initialize "C" key with empty list for it,
you don't have nulls checks (no NullPointerExceptions),
you don't have to do other checks such as myMap.containsKey("A"),
you write less code,
so your code is less bug-prone,
etc., etc.
P.S. Instead of:
HashMap<String, ArrayList<String>> mMap = new HashMap<String, ArrayList<String>>();
use interfaces, not classes when using collections i.e.:
Map<String, List<String>> mMap = new HashMap<String, List<String>>();
and even better Guava's "static constructors" (you don't repeat code):
Map<String, List<String>> mMap = Maps.newHashMap()
when knowledge about implementation is not necessary.
If you add the same list as a value with different keys, in your case keys A,B,C, and D all point to the same list, and access and update the list through one key the changes will then be visible in all the lists. Each key points to the same list structure.
If you want the lists to be different you need to use different for different keys you need to use a different list.
You could automate the process, say by making your own insert method that clones the given list.
I would do something like this.
private final ReentrantLock lock = new ReentrantLock();
Map<String ,List<String>> mMap=new HashMap<>();
public void myAdd(String key, String value) {
try {
lock.lock();
List<String> there =mMap.get(key)==null?new ArrayList<>():mMap.get(key);
there.add(value);
mMap.put(key, there);
}finally {
lock.unlock();
}
}