Benjamin M
Benjamin M

Reputation: 24557

Create/Sort List<X> with values ordered the same as in List<Y>

I have a List of Objects A, which is used to retrieve another List of Objects B. But the second List is randomly sorted. Both Object types have the id property in common.

What I'm doing at the moment:

List<A> input = ...;
List<B> output = new ArrayList<>();
for(A a : input) {
    output.add(getOutputObjectById(a.getId()));
}

The problem is, that getOutputObjectById is quite expensive. There's another method getOutputObjectsByIds which takes a Collection of ids and returns List<B>. But the order of elements can be different. Though I need a way to ensure that both Lists have the same sorting at the end.

I first thought about using a LinkedHashMap and do something like this:

List<A> input = ...;
List<B> output = new ArrayList<>();
LinkedHashMap<String, Object> intermediate = new LinkedHashMap<String, Object>();
for(A a : input) {
    intermediate.put(a.getId(), a);
}
for(B b : getOutputObjectsByIds(intermediate.keySet())) {
    intermediate.put(b.getId(), b);
}
for(Object o : intermediate.values()) {
    output.add((B) o);
}

That's so much code, copying objects around multiple collections, etc.

I really hope, there's a shorter and more elegant way to do this.

Do you have any idea?

Upvotes: 2

Views: 88

Answers (2)

Benjamin M
Benjamin M

Reputation: 24557

Based on hexafraction's work, I created a quite similar solution, which doesn't create a new List, but instead sorts the given List:

/*
 * converts a Collection<T> to Map<ID, T>
 * ID is specified by the keyFunction
 */
public static <T, ID> Map<ID, T> collectionToMap(Collection<T> collection, Function<T, ID> keyFunction) {
    return collection.stream().collect(Collectors.toMap(keyFunction, Function.identity()));
}

/*
 * sorts List<T> to have the same ordering as List<O>
 * two functions have to be provided, that are used to get the List's ID
 */
public static <T, O, ID> void sortListByObjects(List<T> list, Function<T, ID> listIdFunction, List<O> objects, Function<O, ID> objectIdFunction) {
    Map<ID, T> map = collectionToMap(list, listIdFunction);
    for(int i = 0; i < objects.size(); i++) {
        list.set(i, map.get(objectIdFunction.apply(objects.get(i))));
    }
}

/*
 * abstraction of the method above, which sorts List<T> by a List of IDs
 */
public static <T, ID> void sortListByIds(List<T> list, Function<T, ID> listIdFunction, List<ID> ids) {
    sortListByObjects(list, listIdFunction, ids, Function.identity());
}

It's pretty simple to use:

List<String> ids = Arrays.asList("4", "1", "9");
List<Foo> unsortedList = Arrays.asList(new Foo("1"), new Foo("9"), new Foo("4"));
sortListByIds(unsortedList, foo -> foo.getId(), ids);

Or:

List<Foo> unsortedList = Arrays.asList(new Foo("1"), new Foo("9"), new Foo("4"));
List<Bar> sortedList = Arrays.asList(new Bar("4"), new Bar("1"), new Bar("4"));
sortListByObjects(unsortedList, foo -> foo.getId(), sortedList, bar -> bar.getId());

Upvotes: 0

nanofarad
nanofarad

Reputation: 41281

It looks like you tagged this java-8 so I'll offer use of streams:

List<A> input = ...;
HashMap<IdClass, B> bsById = new HashMap<>();
for(B b : getOutputObjectsByIds(input.stream().map(A::getId).collect(Collectors.toList()))){
    bsById.put(b.getId(), b);
}
return input.stream().map((A a)->bsById.get(a.getId())).collect(Collectors.toList());

This uses an intermediate HashMap, to avoid a O(n^2) situation with a parallel scan over the list for each member of input.

Upvotes: 3

Related Questions