web-dev-qa-db-ja.com

Java-ArrayListの重複を削除する

ArrayListを使用してStringsを保存するプログラムに取り組んでいます。プログラムはユーザーにメニューを表示し、ユーザーが実行する操作を選択できるようにします。このような操作は、リストへの文字列の追加、エントリの印刷などです。できることは、removeDuplicates()というメソッドを作成することです。このメソッドは、ArrayListを検索し、重複する値を削除します。リスト内に重複した値のインスタンスを1つ残しておきます。また、このメソッドが削除された重複の総数を返すようにします。

私はこれを実現するためにネストされたループを使用しようとしましたが、エントリが削除されると、ArrayListのインデックスが変更され、正常に動作しなくなるため、トラブルに直面しました。私は何をする必要があるかを概念的に知っていますが、このアイデアをコードに実装するのに苦労しています。

疑似コードは次のとおりです。

最初のエントリから始めます。リスト内の後続の各エントリをチェックし、最初のエントリと一致するかどうかを確認します。最初のエントリに一致するリスト内の後続の各エントリを削除します。

すべてのエントリを調べた後、2番目のエントリに進みます。リスト内の各エントリを確認し、2番目のエントリと一致するかどうかを確認します。 2番目のエントリに一致するリスト内の各エントリを削除します。

リストのエントリについて繰り返します

ここに私がこれまでに持っているコードがあります:

public int removeDuplicates()
{
  int duplicates = 0;

  for ( int i = 0; i < strings.size(); i++ )
  {
     for ( int j = 0; j < strings.size(); j++ )
     {
        if ( i == j )
        {
          // i & j refer to same entry so do nothing
        }

        else if ( strings.get( j ).equals( strings.get( i ) ) )
        {
           strings.remove( j );
           duplicates++;
        }
     }
 }

   return duplicates;
}

[〜#〜] update [〜#〜]:Willは、Setsを使用した実用的なソリューションではなく、重複を削除するアルゴリズムの開発を伴う宿題ソリューションを探しているようです。彼のコメントを参照してください。

提案のためのThx。これは課題の一部であり、教師がソリューションにセットを含めないことを意図していたと思います。言い換えると、HashSetを実装せずに重複を検索して削除するソリューションを考え出すことになります。先生は、私がやろうとしているネストされたループの使用を提案しましたが、特定のエントリが削除された後、ArrayListのインデックス付けに問題がありました。

18
Will

自然に重複を防ぐSetなどのコレクション(およびHashSetなどの実装)を使用しないのはなぜですか?

37
matt b

ネストされたループを問題なく使用できます。

public static int removeDuplicates(ArrayList<String> strings) {

    int size = strings.size();
    int duplicates = 0;

    // not using a method in the check also speeds up the execution
    // also i must be less that size-1 so that j doesn't
    // throw IndexOutOfBoundsException
    for (int i = 0; i < size - 1; i++) {
        // start from the next item after strings[i]
        // since the ones before are checked
        for (int j = i + 1; j < size; j++) {
            // no need for if ( i == j ) here
            if (!strings.get(j).equals(strings.get(i)))
                continue;
            duplicates++;
            strings.remove(j);
            // decrease j because the array got re-indexed
            j--;
            // decrease the size of the array
            size--;
        } // for j
    } // for i

    return duplicates;

}
17
Azder

この1つのライナーを試して、文字列保存順序のコピーを取得できます。

List<String> list;
List<String> dedupped = new ArrayList<String>(new LinkedHashSet<String>(list));

このアプローチは、O(n) O(n ^ 2)ではなく償却)

14
Peter Lawrey

マットbの回答に関する私のコメントを明確にするために、削除された重複の数を本当にカウントしたい場合は、次のコードを使用します。

List<String> list = new ArrayList<String>();

// list gets populated from user input...

Set<String> set = new HashSet<String>(list);
int numDuplicates = list.size() - set.size();
8
Peter

私はこれを達成するためにネストされたループを使用しようとしましたが、エントリget deleted、ArrayList gets changedおよび物事が彼らがすべきように動作しません

エントリを削除するたびにカウンタを減らすだけではどうですか。

エントリを削除すると、要素も移動します。

ej:

String [] a = {"a","a","b","c" }

ポジション:

a[0] = "a";
a[1] = "a";    
a[2] = "b";
a[3] = "c";

最初の「a」を削除すると、インデックスは次のようになります。

a[0] = "a";
a[1] = "b";
a[2] = "c";

したがって、これを考慮に入れて、jj--)値の「ジャンプ」を回避します。

このスクリーンショットをご覧ください:

its working

4
OscarRyz
List<String> lst = new ArrayList<String>();

lst.add("one");
lst.add("one");
lst.add("two");
lst.add("three");
lst.add("three");
lst.add("three");
Set se =new HashSet(lst);
lst.clear();
lst = new ArrayList<String>(se);
for (Object ls : lst){
    System.out.println("Resulting output---------" + ls);   
}
4
Psl

Arraylistから重複した文字列を削除する非常に簡単な方法

ArrayList al = new ArrayList();
// add elements to al, including duplicates
HashSet hs = new HashSet();
hs.addAll(al);
al.clear();
al.addAll(hs);
3
Ahmad Arslan
public Collection removeDuplicates(Collection c) {
// Returns a new collection with duplicates removed from passed collection.
    Collection result = new ArrayList();

    for(Object o : c) {
        if (!result.contains(o)) {
            result.add(o);
        }
    }

    return result;
}

または

public void removeDuplicates(List l) {
// Removes duplicates in place from an existing list
    Object last = null;
    Collections.sort(l);

    Iterator i = l.iterator();
    while(i.hasNext()) {
        Object o = i.next();
        if (o.equals(last)) {
            i.remove();
        } else {
            last = o;
        }
    }
}

両方ともテストされていません。

3
Will Hartung

あなたはこのようなことをすることができます、上記の人々が答えたものの1つは選択肢でなければなりませんが、ここに別のものがあります。

for (int i = 0; i < strings.size(); i++) {
    for (int j = j + 1; j > strings.size(); j++) {
      if(strings.get(i) == strings.get(j)) {
            strings.remove(j);
            j--;
       }`
    }
  }

return strings;
1
Tipper Truong

あなたが言ったようにセットを使用できないと仮定すると、問題を解決する最も簡単な方法は、所定の場所にある重複を削除しようとするのではなく、一時的なリストを使用することです。

public class Duplicates {

    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("one");
        list.add("one");
        list.add("two");
        list.add("three");
        list.add("three");
        list.add("three");

        System.out.println("Prior to removal: " +list);
        System.out.println("There were " + removeDuplicates(list) + " duplicates.");
        System.out.println("After removal: " + list);
    }

    public static int removeDuplicates(List<String> list) {
        int removed = 0;
        List<String> temp = new ArrayList<String>();

        for(String s : list) {
            if(!temp.contains(s)) {
                temp.add(s);
            } else {
                //if the string is already in the list, then ignore it and increment the removed counter
                removed++;
            }
        }

        //put the contents of temp back in the main list
        list.clear();
        list.addAll(temp);

        return removed;
    }

}
1
Jared Russell

重複を空の文字列*に置き換えて、インデックス作成をそのまま維持できます。完了したら、空の文字列を削除できます。

*ただし、空の文字列が実装で有効でない場合のみ。

0
Smalltown2k

リストをHashSetに追加し、そのハッシュセットを再度リストに変換して重複を削除できます。

public static int removeDuplicates(List<String> duplicateList){
    List<String> correctedList = new ArrayList<String>();
    Set<String> a = new HashSet<String>();
    a.addAll(duplicateList);
    correctedList.addAll(a);
    return (duplicateList.size()-correctedList.size());
}

ここでは、重複の数を返します。また、すべての一意の値でcorrectListを使用することもできます

0
user2450176

コードに見られる問題は、反復中にエントリを削除するため、反復の場所が無効になることです。

例えば:

{"a", "b", "c", "b", "b", "d"} 
       i         j  

これで、strings [j]を削除しています。

{"a", "b", "c", "b", "d"} 
       i         j  

内側のループが終了し、jが増分されます。

{"a", "b", "c", "b", "d"} 
       i              j

重複した「b」が1つだけ検出されました...おっと。

これらの場合のベストプラクティスは、削除する必要のある場所を保存し、arraylistを反復処理した後に削除することです。 (1つのボーナス、strings.size()呼び出しは、ループの外側でユーザーまたはコンパイラーによって最適化できます)

ヒント、i + 1でjの繰り返しを開始できます。すでに0をチェックしています-i!

0
NomeN

セットを使用するのが最良のオプションです(他の人が提案したように)。

リスト内のすべての要素を互いに比較する場合は、forループを少し調整する必要があります。

for(int i = 0; i < max; i++)
    for(int j = i+1; j < max; j++)

この方法では、各要素を2回ではなく1回だけ比較することはありません。これは、2番目のループが最初のループと比較して次の要素から開始するためです。

また、イテレータの代わりにforループを使用する場合でも、リストを反復するときにリストから削除する場合は、リストのサイズを小さくすることに注意してください。一般的な解決策は、削除するアイテムの別のリストを保持し、削除するアイテムの決定が終了したら、元のリストからそれらを削除することです。

0
Thirler
_public <Foo> Entry<Integer,List<Foo>> uniqueElementList(List<Foo> listWithPossibleDuplicates) {
  List<Foo> result = new ArrayList<Foo>();//...might want to pre-size here, if you have reliable info about the number of dupes
  Set<Foo> found = new HashSet<Foo>(); //...again with the pre-sizing
  for (Foo f : listWithPossibleDuplicates) if (found.add(f)) result.add(f);
  return entryFactory(listWithPossibleDuplicates.size()-found.size(), result);
}
_

そして、いくつかのentryFactory(Integer key, List<Foo> value)メソッド。代わりに元のリストを変更したい場合(おそらく良いアイデアではありませんが、何でも):

_public <Foo> int removeDuplicates(List<Foo> listWithPossibleDuplicates) {
  int original = listWithPossibleDuplicates.size();
  Iterator<Foo> iter = listWithPossibleDuplicates.iterator();
  Set<Foo> found = new HashSet<Foo>();
  while (iter.hasNext()) if (!found.add(iter.next())) iter.remove();
  return original - found.size();
}
_

文字列を使用する特定の場合、追加の等式制約を処理する必要がある場合があります(たとえば、大文字と小文字のバージョンは同じですか?)。

編集:ああ、これは宿題です。 JavaコレクションフレームワークとSetでIterator/Iterableを検索し、私が提供したのと同じ結論に達しないかどうかを確認します。ジェネリック部分はグレービーです。

0
Carl

以下は、リストの順序を変更せずに、リストから重複する要素を削除するコードです。一時リストを使用せず、設定変数も使用しません。このコードはメモリを節約し、パフォーマンスを向上させます。

これは、あらゆる種類のリストで機能する一般的な方法です。

これは、インタビューの1つで尋ねられた質問でした。多くのフォーラムで解決策を検索しましたが、見つけることができなかったため、これがコードを投稿する正しいフォーラムだと考えました。

    public List<?> removeDuplicate(List<?> listWithDuplicates) {
    int[] intArray = new int[listWithDuplicates.size()];
    int dupCount = 1;
    int arrayIndex = 0;
    int prevListIndex = 0; // to save previous listIndex value from intArray
    int listIndex;

    for (int i = 0; i < listWithDuplicates.size(); i++) {
        for (int j = i + 1; j < listWithDuplicates.size(); j++) {
            if (listWithDuplicates.get(j).equals(listWithDuplicates.get(i)))
                dupCount++;

            if (dupCount == 2) {
                intArray[arrayIndex] = j; // Saving duplicate indexes to an array
                arrayIndex++;
                dupCount = 1;
            }
        }
    }

    Arrays.sort(intArray);

    for (int k = intArray.length - 1; k >= 0; k--) {
        listIndex = intArray[k];
        if (listIndex != 0 && prevListIndex != listIndex){
            listWithDuplicates.remove(listIndex);
            prevListIndex = listIndex;
        }
    }
    return listWithDuplicates;
}
0
Avinash U

内部のforループは無効です。要素を削除すると、jはインクリメントできません。これは、jが削除した要素の後の要素を指しているため、検査する必要があるためです。

つまり、whileループの代わりにforループを使用し、jiの要素が一致しない場合にのみjをインクリメントする必要があります。 doが一致する場合、jの要素を削除します。 size()は1ずつ減少し、jは次の要素を指すようになるため、jを増やす必要はありません。

また、iの前の重複は以前の反復ですでに削除されているため、内部変数のall要素、iに続く要素を検査する理由はありません。

0
markusk

重複を削除するには、セットを使用するのが最適なオプションです。

配列のリストがある場合は、重複を削除しても配列リスト機能を保持できます。

 List<String> strings = new ArrayList<String>();
 //populate the array
 ...
 List<String> dedupped = new ArrayList<String>(new HashSet<String>(strings));
 int numdups = strings.size() - dedupped.size();

セットを使用できない場合は、配列(Collections.sort())を並べ替えてリストを反復処理し、現在の要素が前の要素と等しいかどうかを確認し、等しい場合は削除します。

0
Theo

私はこの質問に参加するのに少し遅れていますが、GENERICタイプを使用する同じことに関してより良い解決策があります。上記のすべてのソリューションは単なるソリューションです。これらは、実行時スレッド全体の複雑さへのリードを増やしています。

RemoveDuplicacy.Java

ロード時に必要なを実行する手法を使用して、最小化できます。

例:クラスタイプのarraylistを次のように使用している場合:

ArrayList<User> usersList = new ArrayList<User>();
        usersList.clear();

        User user = new User();
        user.setName("A");
        user.setId("1"); // duplicate
        usersList.add(user);

        user = new User();
        user.setName("A");
        user.setId("1"); // duplicate
        usersList.add(user);

        user = new User();
        user.setName("AB");
        user.setId("2"); // duplicate
        usersList.add(user);

        user = new User();
        user.setName("C");
        user.setId("4");
        usersList.add(user);

        user = new User();
        user.setName("A");
        user.setId("1"); // duplicate
        usersList.add(user);

        user = new User();
        user.setName("A");
        user.setId("2"); // duplicate
        usersList.add(user);


}

上記で使用したarraylistのベースとなるクラス:ユーザークラス

class User {
    private String name;
    private String id;

    /**
     * @param name
     *            the name to set
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * @return the name
     */
    public String getName() {
        return name;
    }

    /**
     * @param id
     *            the id to set
     */
    public void setId(String id) {
        this.id = id;
    }

    /**
     * @return the id
     */
    public String getId() {
        return id;
    }

}

ここでJavaにオブジェクト(親)クラスの2つのオーバーライドされたメソッドがあります。これは、目的をよりよく果たすためにここで役立ちます。

@Override
    public int hashCode() {

        final int prime = 31;
        int result = 1;
        result = prime * result + ((id == null) ? 0 : id.hashCode());
        return result;

    }

    @Override
    public boolean equals(Object obj) {

        if (this == obj)
            return true;

        if (obj == null)
            return false;

        if (getClass() != obj.getClass())
            return false;

        User other = (User) obj;

        if (id == null) {
            if (other.id != null)
                return false;

        } else if (!id.equals(other.id))
            return false;

        return true;

    }

Userクラスでこれらのメソッドをオーバーライドする必要があります

完全なコードは次のとおりです。

https://Gist.github.com/458431

クエリがあれば教えてください。

0
Dwivedi Ji
public ArrayList removeDuplicates(ArrayList <String> inArray)
{
    ArrayList <String> outArray = new ArrayList();
    boolean doAdd = true;
    for (int i = 0; i < inArray.size(); i++)
    {
        String testString = inArray.get(i);
        for (int j = 0; j < inArray.size(); j++)
        {
            if (i == j)
            {
                break;
            }
            else if (inArray.get(j).equals(testString))
            {
                doAdd = false;
                break;
            }

        }
        if (doAdd)
        {
            outArray.add(testString);
        }
        else
        {
            doAdd = true;
        }

    }
    return outArray;

}
0
user232223