Switch direction of association between objects

Viewed 94

Say I have a class foo:

class foo {
    String someString;
    List<String> someList;
}

If I have a list of foo's, is there a way for me to create a new list/array/whatever of foo's with essentially the someString and someList values remapped? For example:

arr1: [
    foo {
        someString: 'test1',
        someList: ['a', 'b']
    },
    foo {
        someString: 'test2',
        someList: ['b', 'c']
    }
]

Would become

arr2: [
    foo {
        someString: 'a',
        someList: ['test1']
    },
    foo {
        someString: 'b',
        someList: ['test1', 'test2']
    },
    foo {
        someString: 'c',
        someList: ['test2']
    },
]

Right now, I have a nested loop that iterates over each element of someList for each foo and compiles them into a map where the key is the someList value and the value is a set of the someString values that have come from foos where the someList value was present. Then do a map of the entry set to convert it to new foos with the key and value passed as constructor params.

Map<String, Set<String>> myMap;

for(foo f : arr1) {
    for(String s : f.someList) {
        Set<String> mySet = myMap.get(s);
        if(mySet == null) {
            myMap.put(s, new HashSet<String>(Arrays.asList(f.someString)));
        } else {
            mySet.add(f.someString);
        }
    }
}

List<String> myNewList = myMap.entrySet()
                              .stream()
                              .map(e -> 
                                    new foo(e.key, new ArrayList<String>(e.value)))
                              .collect(Collectors.toList());

It seems to work fine, but it's not the prettiest in the world, so I was wondering if there's another approach to doing this.

3 Answers

First flip your objects by using a map:

Map<String, Set<String>> map = new LinkedHashMap<>();
arr1.forEach(foo -> 
        foo.someList.forEach(s -> 
                map.computeIfAbsent(
                    s, 
                    k -> new LinkedHashSet<>())
                .add(foo.someString)));

Then create the foo objects from the map:

List<foo> result = new ArrayList<>();
map.forEach((k, v) -> list.add(new foo(k, new ArrayList<>(v))));

This assumes you have the appropriate constructor.

Why not create the foo objects in your nested loop like this?

Map<String, foo> myMap;

 for(foo f : arr1) {
  for(String s : f.someList) {
    foo myFoo= myMap.get(s);
    if(mySet == null) {
        myFoo= new foo(s, new ArrayList<String>);
        myMap.put(s, myFoo);

    } 
    myFoo.somelist.add(f.someString);
  }
}

Here is the sample on java 8 with Guava library:

private static Function<Foo,Set<Foo>> fooStream(final Map<String,Foo> map){
        return foo -> foo.getSomeList().stream()
                .map(createNewFoo(foo.getSomeString(),map)).collect(Collectors.toSet());

    }

    private static Function<String,Foo> createNewFoo(final String someString,final Map<String,Foo> map){
        return s->{
            if(Objects.nonNull(map.get(s))){
                map.get(s).getSomeList().add(someString);
                return map.get(s);
            }
            return createNewFoo(s,someString,map);
        };
    }

    private static Foo createNewFoo(final String someListValue , final String someString,final Map<String,Foo> map){
        final Foo foo = new Foo(someListValue,Lists.newArrayList(someString));
        map.put(someListValue,foo);
        return foo;
    }

And here how you can call it in main method :

final Map<String, Foo> myMap = Maps.newHashMap(); 
final Foo foo1 = new Foo("test1",Arrays.asList("A","B")); 
final Foo foo2 = new Foo("test2",Arrays.asList("B","C")); 
final List<Foo> foos = Arrays.asList(foo1,foo2); 
final Set<Foo> fooSet = foos.stream()
.map(fooStream(myMap)) 
.flatMap(Set::stream).collect(Collectors.toSet());

Where Foo class is :

public class Foo {
    private String someString;
    private List<String> someList;

    public Foo(String someString, List<String> someList) {
        this.someString = someString;
        this.someList = someList;
    }
//getters and setters
}
Related