6

I have a problem that I am sure the solution is so easy but I am not able to find it. I have an ArrayList of smaller ArrayLists. Those lists contain elements of type String. I want to merge the smaller lists to one and then remove the duplicates. Let me be clear.

I have this:

[[USA, Maine], [USA, Maine, Kennebunk], [USA, Maine, North Berwick], 
[USA, New Hampshire], [USA, Keene, New Hampshire], [USA, Keene, New 
Hampshire, Main Street], [USA, New Hampshire, Swanzey]].

This is my main list that has smaller lists inside. I want to have a final ArrayList which is the merge of the smaller ones and get the duplicates deleted.

What I want is:

[USA, Maine, Kennebunk, North Berwick, New Hampshire , Keene, Main Street, Swanzey]

Any help is appreciated. Thank you

Mureinik
  • 297,002
  • 52
  • 306
  • 350
hristoforidisc
  • 81
  • 1
  • 10

6 Answers6

6

This is a concise solution using the Stream class:

listOfLists.stream().flatMap(List::stream).collect(Collectors.toSet())

Note that the result is of type Set. This takes care of removing the duplicates.

If you need a List, you can use this:

listOfLists.stream()
           .flatMap(List::stream)
           .distinct()
           .collect(Collectors.toList())

Note that this even guarantees the order of elements to be stable, i.e. [["foo","bar"],["bar","abc","foo"]] will always result in ["foo","bar","abc"] in this order. Most solutions using Set do not guarantee this because most of them are not sorted.

siegi
  • 5,646
  • 2
  • 30
  • 42
  • My ArrayList of ArratLists is called filmingLocations. Is this how I am supposed to have the code? filmingLocations.stream().flatMap(List::stream).distinct().collect(Collectors.toList(); – hristoforidisc Dec 15 '17 at 17:37
  • @hristoforidisc, yes :-) – siegi Dec 15 '17 at 17:40
  • At this point flatMap(List::stream) I am getting an error. Should I import anything? – hristoforidisc Dec 15 '17 at 17:50
  • @hristoforidisc, for the code above you need to import `java.util.List` and `java.util.stream.Collectors`. – siegi Dec 15 '17 at 17:53
  • I already did that but still doesn't work. – hristoforidisc Dec 15 '17 at 17:56
  • @hristoforidisc, you need at least Java 8 for this to work, because `Stream` does not exist in earlier versions. If you need to use some older version of Java you have to use one of the other solutions. Note that you should update to at least Java 8 because there will be no more public updates to Java 7 and earlier, see the [Support Roadmap](http://www.oracle.com/technetwork/java/javase/eol-135779.html) for details. – siegi Dec 15 '17 at 18:06
  • I have the updated java version but still my code doesn't work mate. I am getting an error at the flapMap section and I can't understand the reason. – hristoforidisc Dec 15 '17 at 18:14
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/161325/discussion-between-siegi-and-hristoforidisc). – siegi Dec 15 '17 at 18:14
3

It's easy to perform with help of Sets (Set does not allow duplicate values)

public List<String> merge(List<List<String>> list) {
    Set<String> uniques = new HashSet<>();
    for(List<String> sublist : list) {
        uniques.addAll(sublist);
    }
    return new ArrayList<>(uniques);
}

p.s. when you want that you merged list will be sorted change HashSet to TreeSet like this: Set<String> uniques = new TreeSet<>();

fxrbfg
  • 1,756
  • 1
  • 11
  • 17
  • Note that `TreeSet` uses the _natural ordering_ of the elements (or some other `Comparator` passed at construction time) and _not_ the _insertion order_. This means the result is sorted by alphabet and not as it was in the original lists. – siegi Dec 15 '17 at 17:46
1

Traditional solution:

Set<String> result = new LinkedHashSet<>();
for (List<String> innerList : filmingLocations) result.addAll(innerList);

As result is a LinkedHashSet, it preserves insertion order, so the order of the elements will be as in the inner lists.

You can also use an equivalent Java 8 solution:

Set<String> result = new LinkedHashSet<>();
filmingLocations.forEach(result::addAll);

Or even a Java 8 stream-based solution:

Set<String> result = filmingLocations.stream()
    .flatMap(List::stream)
    .collect(Collectors.toCollection(LinkedHashSet::new));
fps
  • 33,623
  • 8
  • 55
  • 110
  • 1
    Thank you, I knew there was some `Set` in the JDK preserving insertion order, but I never remember its name (and they don't mention it in the Javadoc of `Collection` or `Set`, while they do so for `HashSet`, `TreeSet` and even `SortedSet`) :-P – siegi Dec 15 '17 at 18:40
0

If you are targeting < Java 8, you could create instance of your final ArrayList, let's call it "resultList". Then iterate over each of your inner ArrayLists and add only these Strings for which contains() method returns false. This is the solution only if you have to use ArrayList as your final Collection. Otherwise, you should consider using HashSet, which automatically holds unique values inside and gets rid of any repeated objects. The following code might help you a bit, if you need to use ArrayList as your result Collection:

ArrayList<ArrayList<String>> sourceList = new ArrayList<>();
        // Adding sample ArrayLists ("a" and "b") of Strings to sourceList:
        ArrayList<String> a = new ArrayList<>();
        a.add("USA");
        a.add("Maine");
        sourceList.add(a);
        ArrayList<String> b = new ArrayList<>();
        b.add("USA");
        b.add("Maine");
        b.add("Kennebunk");
        sourceList.add(b);
        ArrayList<String> resultList = new ArrayList<>();
        for(ArrayList<String> outerList : sourceList) {
            for(String str : outerList) {
                // If resultList doesn't contain currently checked string...
                if(!(resultList.contains(str))) {
                    // Add this string to resultList...
                    resultList.add(str);
                }
            }
        }
        System.out.println(resultList.toString());

Output you get: [USA, Maine, Kennebunk]

Przemysław Moskal
  • 3,551
  • 2
  • 12
  • 21
  • Yes I have to use ArrayList as my final Collection. About the code do you have any ideas? – hristoforidisc Dec 15 '17 at 17:26
  • You could use two nested foreach loops. In the outer one, you iterate over outer ArrayList, in the inner one, you iterate over each of the inner lists. In the inner loop body you check if your final ArrayList already contains the String, if not, you add it to ArrayList – Przemysław Moskal Dec 15 '17 at 17:33
0

Solution:
Loop trough ever String in you ArrayList of ArrayLists and add the string to another ArrayList if it is not already on this list using the .contains() method of ArrayList

Code:

  public ArrayList<String> merge(ArrayList<ArrayList<String>> startArrayList) {
    ArrayList<String> finalArrayList = new ArrayList<String>();
    //Iterate over each element
    for (ArrayList<String> innerList:startArrayList) {
      for (String value:innerList) {
        //add the String if it is missing
        if (!finalArrayList.contains(value))
          finalArrayList.add(value);
      }
    }
    return finalArrayList;
  }
ProfBits
  • 199
  • 1
  • 9
0

I saw this post and had to answer, Berwick/Kennebunk are towns I lived in lol. Are you local?

Anyways The easiest way to do this is with set operations as mentioned above. This gurenttees some O(log n) search .

public List<String> mergeTowns (List<List<String>> list) {
    Set<String> uniques = new HashSet<>();
    for(List<String> sublist : list) {
        uniques.addAll(sublist);
    }
    return new ArrayList<>(uniques);
}

If your looking for a little more dynamic data structure use a map where country is you key and towns are your values. This way if you decide to build up a big data base of towns by who have different countries you can and then search the map by country to show the towns . Maybe use State instead of country as your key.

The resulting data structure will give up a map like this. when printed.

[USA = [berwick, kennebunk, north berwick, wells], CANADA = [berwick, kennebunk, north berwick, wells], MEXICO = [berwick, kennebunk, north berwick, wells]]

The way the data structure is built up prevents duplicate town entries in the same country/state.

public class Merge {


    private static ArrayList<String> mergeMap(HashMap<String, Set> map) {
        ArrayList<String> data = new ArrayList();
        for(Entry<String, Set> entries : map.entrySet()){
            String country = entries.getKey();
            Set<String> towns = entries.getValue();
            data.add(country+" = "+towns);
        }
        return data;
    }



    public static void main(String[] args) {
        //Mock data
        String[] countrys = {"USA", "CANADA", "MEXICO"};

        //Try this way of building your data structure instead of an array list of array list. 
        HashMap<String,Set> map = new HashMap<String,Set>();
        TreeSet<String> towns = new TreeSet<String>();

        // Add a couple towns to your set of towns
        towns.add("berwick");
        towns.add("north berwick");
        towns.add("kennebunk");
        towns.add("kennebunk");
        towns.add("kennebunk");
        towns.add("kennebunk");
        towns.add("wells");
        towns.add("wells");

        //With a map you could push a different set of towns to different countries
        for(String country: countrys){
            map.put(country, towns);
        }

        //Pass in your map<Country, Towns>
        ArrayList<String> mergedValues = mergeMap(map);
    }
}
John Hanewich
  • 167
  • 1
  • 11