4
votes

I have two list of objects and I want to merge them into one. the objects have two fields, "name" and "value". For a given obj2 in list2 , if we find a match of "name" field of obj1 in list1 (obj1 from list1 and obj2 from list2), then we use the "value" of the obj2 to overwrite obj1. if there is no match found, then we add obj2 to list1. The final output will be updated list1.

Is there any fast way to do this? All I can think of is to use two for loops to compare all the objects in two lists

class NameValueObj{
String name;
String value;
}

List<NameValueObj> merge(List<NameValueObj> list1, List<NameValueObj> list2){
// I want to merge two list here
}

NameValueObj is a given so I can;t modify the object source.

Here is my way of doing it.

private List<Header> mergeHeaders(List<Header> defHeaders, List<Header> ovrdHeaders) {
        List<Header> lFinal = defHeaders;
        boolean foundMatch = false;
        for (Header ovrdHeader : ovrdHeaders) {
            foundMatch = false;
            for (Header defHeader : defHeaders) {
                if (defHeader.getName().equalsIgnoreCase(ovrdHeader.getName())) {
                    defHeader.setValue(ovrdHeader.getValue());
                    foundMatch = true;
                    break;
                }
            }
            if(!foundMatch) {
                lFinal.add(ovrdHeader);
            }

        }

        return lFinal;
    }

Header has name and value field. Headers has unique names in a given list.

3

3 Answers

10
votes

Your algorithm is O(n*n) (quadratic).

You can do it in O(n) (linear) using a temporary LinkedHashMap:

private List<Header> mergeHeaders(final List<Header> defHeaders, final List<Header> ovrdHeaders) {
    final Map<String, Header> headersMap = new LinkedHashMap<String, Header>();

    for (final Header defHeader : defHeaders) {
        headersMap.put(defHeader.getName().toLowerCase(), defHeader);
    }

    for (final Header ovrdHeader : ovrdHeaders) {
        headersMap.put(ovrdHeader.getName().toLowerCase(), ovrdHeader);
    }

    return new ArrayList<Header>(headersMap.values());
}

Note that the behavior is NOT exactly the same as the behavior of your implementation. The differences are:

  • This implementation returns a new list instance instead of modifying the first list. IMO, it's a benefit, but it may depend. If needed, you can modify the first list as follows (though I'd not recommend that):

    defHeaders.clear();
    defHeaders.addAll(headersMap.values());
    return defHeaders;
    
  • This implementation assumes that header names are already unique (case INsensitively) in both lists, while your one doesn't make this assumption.
    If header names are not unique, this implementation would preserve the last header from list 1 or list 2.

2
votes

So if you see the major efficiency overhead your way is searching for a particular element in another list. So you should ask how can you efficiently search for the object in another list?

  • Array(if you know the index)
  • HashMap(if you kow the key)

HashMap looks like an easy way to implement your problem. So what you can do is iterate through your first list and add name as key and value as value in the map. Similarly for the second list.

Next iterate through the keyset of 1st map and search for corresponding name key in 2nd list. If found add as value in 1st list.

The major drawback here is using extra data structures.

0
votes

I had to solve a similar problem. In my case, I had to create custom decision rules when Hash Map already have an item or not. Here is my final solution:

Auxiliar classes:

public interface MergeRules<T> {
    T olderListContains(T olderObj, T newObj);

    T olderListNotContains(T newObj);
}

public abstract class HashAbstract {
    public String getKey() {
       return null;
    }
}

My final merge method:

public static <T> List<T> merge(final List<T> olderList,
                                final List<T> newList,
                                MergeRules<T> mergeRules) {
    final Map<String, T> listMap = new LinkedHashMap<>();

    for (final T olderObj : olderList) {
        listMap.put(((HashAbstract) olderObj).getKey(), olderObj);
    }

    for (final T newObj : newList) {
        String newObjKey = ((HashAbstract) newObj).getKey();
        T returnObj;

        if (listMap.containsKey(newObjKey)) {
            returnObj = mergeRules.olderListContains(listMap.get(newObjKey), newObj);
        } else {
            returnObj = mergeRules.olderListNotContains(newObj);
        }

        listMap.put(newObjKey, returnObj);
    }

    return new ArrayList<>(listMap.values());
}

Example:

class NameValueObj extends HashAbstract {
    public String name;
    public String value;

    @Override
    public String getKey() {
        return name.trim().toUpperCase();
    }
}

NameValueObj obj1 = new NameValueObj();
obj1.name = "test";
obj1.value = "1234";

NameValueObj obj2 = new NameValueObj();
obj2.name = "testSomething";
obj2.value = "4321";

NameValueObj obj3 = new NameValueObj(); // obj that will be updated
obj3.name = "test";
obj3.value = "1212";

NameValueObj obj4 = new NameValueObj(); // new object
obj4.name = "testObj";
obj4.value = "1313";

List<NameValueObj> list1 = new ArrayList<>();
list1.add(obj1);
list2.add(obj2);

List<NameValueObj> list2 = new ArrayList<>();
list2.add(obj3);
list2.add(obj4);

List<NameValueObj> mergedList = merge(list1, list2, 
               new Compare.MergeRules<NameValueObj>() {
                    @Override
                    public NameValueObj olderListContains(NameValueObj olderObj, NameValueObj newObj) {
                        return newObj; // if obj already exists, return the new
                    }

                    @Override
                    public NameValueObj olderListNotContains(NameValueObj newObj) {                       
                        return newObj; // if not exists, return the new obj
                    }
                });

The mergedList of the example is:

name: test - value: 1212
name: testSomething - value: 4321
name: testObj - value: 1313