15

I have a set and I want to convert it to map in order to use it later in guava's Maps.difference(). I only care about the keys in the difference.
Came up with this version:

private <T> Map<T, T> toMap(Set<T> set) {
  return set.stream().collect(Collectors.toMap(Function.identity(), Function.identity()));
}

However, I know that usually, a set has a backing field of map. This is the method I use to create the map:

public static <E> Set<E> newConcurrentHashSet() {
  return Collections.newSetFromMap(new ConcurrentHashMap<E, Boolean>());
}

Since I only need the keys I thought maybe I can get a view of this field somehow. any idea?

Nicholas K
  • 15,148
  • 7
  • 31
  • 57
oshai
  • 14,865
  • 26
  • 84
  • 140
  • 2
    why not use https://google.github.io/guava/releases/snapshot/api/docs/com/google/common/collect/Sets.html#difference-java.util.Set-java.util.Set- ? Why are you trying to force a set into a map? – luk2302 Mar 21 '17 at 08:04
  • I would like to know which items only on left, which only on right, which in common (similar to map difference) – oshai Mar 21 '17 at 08:07
  • 1
    If you only need the keys, why are you converting to Map? A Map is basically a Set with values, so what you're saying makes no sense. – Andreas Mar 21 '17 at 08:08

6 Answers6

28

I ended up with a fairly straight-forward one line solution with Java 8 as follows:

Map<String, Foo> map = fooSet.stream().collect(Collectors.toMap(Foo::getKey, e -> e));
  • fooSet is a set of objects of type Foo, i.e. Set<Foo> fooSet
  • Foo has a getter called getKey which returns a String
Shanerk
  • 5,175
  • 2
  • 40
  • 36
4

You can convert a Set to a Map (same key and value taken from elements of Set) as shown below:

private <T> Map<T, T> toMap(Set<T> set) {
    Map<T, T> map = new ConcurrentHashMap<>();
    set.forEach(t -> map.put(t, t));//contains same key and value pair
    return map;
}
Vasu
  • 21,832
  • 11
  • 51
  • 67
4

Improvement of developer's answer:

Map<String, Foo> map = fooSet.stream().collect(Collectors.toMap(Foo::getKey, Function.identity()));

or if you statically import Collectors.toMap and Function.identity:

Map<String, Foo> map = fooSet.stream().collect(toMap(Foo::getKey, identity()));
Björn Kahlert
  • 109
  • 1
  • 5
  • What is `Function.identity()`? – jumping_monkey May 11 '22 at 01:01
  • 1
    @jumping_monkey `Function.identity()` is documented at https://docs.oracle.com/javase/8/docs/api/java/util/function/Function.html#identity-- It returns a function that accepts one argument and which returns that argument when called. In the `collect` example the effect is that the values of the stream of entries are taken as is to construct the map. – Björn Kahlert Jul 04 '22 at 19:46
  • 1
    Oh, f(x) = x. Thanks Björn! – jumping_monkey Jul 05 '22 at 02:50
1

From comment:

I would like to know which items only on left, which only on right, which in common (similar to map difference)

Use removeAll() and [retainAll()][3].

Example:

Set<Integer> set1 = new HashSet<>(Arrays.asList(1,3,5,7,9));
Set<Integer> set2 = new HashSet<>(Arrays.asList(3,4,5,6,7));

Set<Integer> onlyIn1 = new HashSet<>(set1);
onlyIn1.removeAll(set2);

Set<Integer> onlyIn2 = new HashSet<>(set2);
onlyIn2.removeAll(set1);

Set<Integer> inBoth = new HashSet<>(set1);
inBoth.retainAll(set2);

System.out.println("set1: " + set1);
System.out.println("set2: " + set2);
System.out.println("onlyIn1: " + onlyIn1);
System.out.println("onlyIn2: " + onlyIn2);
System.out.println("inBoth : " + inBoth);

Output

set1: [1, 3, 5, 7, 9]
set2: [3, 4, 5, 6, 7]
onlyIn1: [1, 9]
onlyIn2: [4, 6]
inBoth : [3, 5, 7]

Now, if you want to know all values and where they were found, you can do this (Java 8):

Set<Integer> setA = new HashSet<>(Arrays.asList(1,3,5,7,9));
Set<Integer> setB = new HashSet<>(Arrays.asList(3,4,5,6,7));

Map<Integer, String> map = new HashMap<>();
for (Integer i : setA)
    map.put(i, "In A");
for (Integer i : setB)
    map.compute(i, (k, v) -> (v == null ? "In B" : "In Both"));

System.out.println("setA: " + setA);
System.out.println("setB: " + setB);
map.entrySet().stream().forEach(System.out::println);

Output

setA: [1, 3, 5, 7, 9]
setB: [3, 4, 5, 6, 7]
1=In A
3=In Both
4=In B
5=In Both
6=In B
7=In Both
9=In A
Community
  • 1
  • 1
Andreas
  • 154,647
  • 11
  • 152
  • 247
0

See similar answer here.

Assuming that your original set is a set of values (no keys in original data!), you will need to specify keys for the newly created map. Guava's Maps.uniqueIndex might be helpful (see here)

Otherwise, if your original set is a set of keys (no values in original data!) that you want to retain, you will need to specify default or specific values for the newly created map. Guava's Maps.toMap might be helpful here. (See more here)

Community
  • 1
  • 1
Mantas
  • 89
  • 6
0
package com.example;

import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

public class Main {
    public static void main(String[] args) {    
        Set<Foo> s = new HashSet<>();
        s.add(new Foo("cccc"));
        s.add(new Foo("aaaa"));
        s.add(new Foo("bbb"));
        Map<String, Foo> m = s.stream().collect(Collectors.toMap(Foo::getKey, Function.identity()));
        System.out.println(m);
    }
}

class Foo {
    String name;
    Foo(String name){this.name = name;}
    String getKey() {return name;}
}

Important note from the reference:

The returned Collector is not concurrent. For parallel stream pipelines, the combiner function operates by merging the keys from one map into another, which can be an expensive operation. If it is not required that results are inserted into the Map in encounter order, using toConcurrentMap(Function, Function) may offer better parallel performance.

jumping_monkey
  • 5,941
  • 2
  • 43
  • 58