6

I want to search through an ArrayLst and delete any entries that are the same.

For example if my list was: apple, orange, banana, pear, peach, orange,

then "orange" would be deleted (both occurances).

Naively, I tried:

for(String word : userlist){

for(String otherword : userlist){

...
}
}

where I wrote how to .remove(lastIndexOf(userword)) if it equals word and their indexes are different.

This led to exception after exception, and I quickly realized I was manipulating a list while iterating through it which was making it all go wrong.

So decided to make a copy of the list

ArrayList<String> copylist = userlist;

for(String word : copylist){

    for(String otherword : copylist){

    if(word.equalsIgnoreCase(otherword) 
            && copylist.lastIndexOf(word)!=copylist.lastIndexOf(otherword)){

userlist.remove(userlist.lastIndexOf(word));
userlist.remove(userlist.lastIndexOf(otherword));
    }

    }
    }

SO I tried this, and it had similar problems. Notably ConcurrentModificationException. After tweaking it I can't get, what in my head should be a fairly easy process, to work in Java. Please help.

7 Answers 7

10

You're currently not making a copy of the list at all. You're declaring a new variable which has a reference to the same list. To make a copy of the list, use:

ArrayList<String> copyList = new ArrayList<String>(userList);

However, I'd suggest a different approach:

ArrayList<String> wordsToRemove = new ArrayList<String>();
Set<String> seenWords = new HashSet<String>();

for (String word : userList)
{
    if (!seenWords.add(word))
    {
        wordsToRemove.add(word);
    }
}

for (String word : wordsToRemove)
{
    // Keep removing it until it doesn't exist any more
    while (userList.remove(word)) {}
}

This doesn't ignore case, however. To do that, you need to be a bit smarter:

Set<String> wordsToRemove = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
Set<String> seenWords = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);

for (String word : userList)
{
    if (!seenWords.add(word))
    {
        wordsToRemove.add(word);
    }
}

// Now we know the words we don't want, step through the list again and
// remove them (case-insensitively, as wordsToRemove is case-insensitive)
for (Iterator<String> iterator = userList.iterator(); it.hasNext() ;)
{
    if (wordsToRemove.contains(word))
    {
        iterator.remove();
    }
}
Sign up to request clarification or add additional context in comments.

5 Comments

Actually, it is imperative that it is case sensitive. Sorry.
By the way, do I need a Set for this? Can't I just add the word I want to remove into a new ArrayList, rejectlist, say? And then later, outside the loop, delete every element of reject list from user list?
@JJG: A set makes it quicker to find the words we've already seen.
Well I could create a method that changes all words into lower case just before they are dumped into userlist, and then it would work, right?
@JJG: That would certainly be simpler, yes. Although if your app is international, there can be oddities around lowercasing (e.g. in Turkish "i" doesn't do what you might expect)
4
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;


public class Test {
    public static void main(String[] args) {
        List<String> a = new ArrayList<String>();
        a.add("apple");
        a.add("orange");
        a.add("banana");
        a.add("pear");
        a.add("peach");
        a.add("orange");
        System.out.println(a);
        System.out.println(getSingleWordList(a));
    }
    private static List<String> getSingleWordList(List<String> list)
    {
        Set<String> uniques = new HashSet<String>();
        Set<String> dups    = new HashSet<String>();

        for (String a : list)
            if (!uniques.add(a))
                dups.add(a);


        uniques.removeAll(dups);

        return new ArrayList<String>(uniques);
    }
}

OUTPUT

Input = [apple, orange, banana, pear, peach, orange]

Output = [pear, apple, banana, peach]

2 Comments

That's nice - I like it. Use a LinkedHashSet if order preservation is required.
as I understand, he want's the old list to be altered, not a new one. Thus I'd replace your return line with list.clear(); list.addAll(uniques);
2

If you wish to remove element during iteration through collection you have to use Iterator and Iterator.remove()

But I'd like to suggest you easier solution: put your list into set. It automatically removes duplicates:

List<String> mylist = new ArrayList<String>();
// some initialization: there are duplicates

Set<String> myset = new HashSet<String>(mylist); // no duplicates here.

You can continue using set because it is collection and you can iterate over it. But if you need random access create list again:

newlist = new ArrayList<String>(myset);

The elements order in set will be "random" as a result of hashing. If you wish to preserve the original order use LinkedHashSet instead of HashSet.

2 Comments

That will end up with one instance of "orange" instead of removing both of them. The idea is to remove all copies of any value which is duplicated.
As I understand, he want's both occurrences of a duplicate to be removed. Additionally, you could use a LinkedHashSet to preserve order and reuse the existing list (list.clear(); list.addAll(set);)
1
ArrayList<String> copylist = userlist; 

This line assigns the reference userlist to copylist and it does not create new arraylist. It points to same arraylist. To make a new list, simple way is to create entirly new list and keep adding items in this new list after checkin whether item is already present in new list or not.

ArrayList<String> newList = new ArrayList<String> ();

foreach(String item in userList) {
    if(newList.contains(item)==false)
    {
          newList.add(item);
    }
}

4 Comments

That will end up with a list with no duplicates - but it will still contain one instance of "orange" for example.
foreach(String item in userList) this is not Java. You mean for(String item : userList)
@S.P.Floyd, sorry bout foreach. I left Java year ago.. now working in C# so used to foreach..
which is probably why Jon Skeet didn't notice :-)
1

It is well known problem - you cannot modify container that is iterated. The trick to resolve is usage of method remove of Iterator:

    Iterator<String> tblIter = array.iterator();
    while (tblIter.hasNext()) {
        String entry = tblIter.next();
        if( entry.equals(.....) )
        {
            ...
            tblIter.remove();
        }
    }

1 Comment

But remember both instances must be removed. I'm not sure this will work. Thanks for the answer, I'll give it a try.
0

If you have control on how data is added, you might create your own "add" method, something like this:

add(element)
{
    if arrayList.contains(element)
        arrayList.remove(arrayList.indexOf(element))

    else
        arrayList.add(element)
}

on the other hand, if you do not have control on how/when the data is added, you can do a loop and in it have a similar logic as above. Take a look here for the appropriate methods.

1 Comment

Thanks! Actually I had a go at trying that. my current solution is very similar, which feels very awkward, and might not work well, when I add more things to the program. I wanted to know the best way to do it.
0

If the objective is a collection without duplicate elements, consider whether a Set would be more appropriate than a List.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.