jtrain17
jtrain17

Reputation: 161

Overwriting values in a HashMap that are in an ArrayList<String>

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?

Upvotes: 16

Views: 3779

Answers (8)

Jacob G.
Jacob G.

Reputation: 29730

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]}

Upvotes: 23

xehpuk
xehpuk

Reputation: 8250

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;
            }
        )
    );

Upvotes: 1

Eugene
Eugene

Reputation: 121048

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;
        });
    });

Upvotes: 0

fps
fps

Reputation: 34470

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.

Upvotes: 2

Timothy Truckle
Timothy Truckle

Reputation: 15634

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());

Upvotes: 4

Jagan N
Jagan N

Reputation: 2065

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]}

Upvotes: 2

Stefano Liboni
Stefano Liboni

Reputation: 149

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 :)

Upvotes: 0

Zeromus
Zeromus

Reputation: 4542

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);
}

Upvotes: 6

Related Questions