大文字と小文字を無視してコレクションを削除しますか?
-
12-09-2019 - |
質問
さて、ここが私の問題です。私はしなければならない HashSet
さん、私が使っているのは、 removeAll
一方のセットに存在する値をもう一方のセットから削除するメソッド。
メソッドを呼び出す前に、明らかに値を Set
s.電話する .toUpperCase()
それぞれの String
両方のリストで値の大文字と小文字が異なるため、追加する前に。この事件には何の韻も理由もありません。
電話したら removeAll
, に残っている値については、元のケースに戻す必要があります。 Set
. 。元のリストを調べて使用せずにこれを行う効率的な方法はありますか? CompareToIgnoreCase
?
例:
リスト1:
"BOB"
"Joe"
"john"
"MARK"
"dave"
"Bill"
リスト2:
"JOE"
"MARK"
"DAVE"
この後、別途作成します HashSet
各リストに対して使用する toUpperCase()
の上 String
s.それから電話してください removeAll
.
Set1.removeAll(set2);
Set1:
"BOB"
"JOHN"
"BILL"
リストを再度次のようにする必要があります。
"BOB"
"john"
"Bill"
アイデアがあれば大歓迎です。それが貧弱であることは承知しています。元のリストには基準があるべきですが、それは私が決めることではありません。
解決
私の最初の回答では、無意識のうちに、 Comparator
, 、しかしこれにより、 TreeSet
に違反する equals
契約 そしてそれは起こるのを待っているバグです:
// Don't do this:
Set<String> setA = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
setA.add("hello");
setA.add("Hello");
System.out.println(setA);
Set<String> setB = new HashSet<String>();
setB.add("HELLO");
// Bad code; violates symmetry requirement
System.out.println(setB.equals(setA) == setA.equals(setB));
専用のタイプを使用することをお勧めします。
public final class CaselessString {
private final String string;
private final String normalized;
private CaselessString(String string, Locale locale) {
this.string = string;
normalized = string.toUpperCase(locale);
}
@Override public String toString() { return string; }
@Override public int hashCode() { return normalized.hashCode(); }
@Override public boolean equals(Object obj) {
if (obj instanceof CaselessString) {
return ((CaselessString) obj).normalized.equals(normalized);
}
return false;
}
public static CaselessString as(String s, Locale locale) {
return new CaselessString(s, locale);
}
public static CaselessString as(String s) {
return as(s, Locale.ENGLISH);
}
// TODO: probably best to implement CharSequence for convenience
}
このコードはバグを引き起こす可能性が低くなります。
Set<CaselessString> set1 = new HashSet<CaselessString>();
set1.add(CaselessString.as("Hello"));
set1.add(CaselessString.as("HELLO"));
Set<CaselessString> set2 = new HashSet<CaselessString>();
set2.add(CaselessString.as("hello"));
System.out.println("1: " + set1);
System.out.println("2: " + set2);
System.out.println("equals: " + set1.equals(set2));
残念ながら、これはさらに冗長です。
他のヒント
それは次のようにして行うことができます。
- リストの内容を大文字と小文字を区別しないように移動する
TreeSet
さん、 - 次に、共通のものをすべて削除します
String
大文字と小文字を区別せずに感謝しますTreeSet#removeAll(Collection<?> c)
- そして最終的には、
ArrayList#retainAll(Collection<?> c)
リストの要素を反復処理し、要素ごとに呼び出します。contains(Object o)
提供されたコレクションに対して、値を保持する必要があるかどうかを確認します。ここでは、コレクションでは大文字と小文字が区別されないため、String
提供されたものと大文字小文字を区別せずに一致するTreeSet
実例。
対応するコード:
List<String> list1 = new ArrayList<>(
Arrays.asList("BOB", "Joe", "john", "MARK", "dave", "Bill")
);
List<String> list2 = Arrays.asList("JOE", "MARK", "DAVE");
// Add all values of list1 in a case insensitive collection
Set<String> set1 = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
set1.addAll(list1);
// Add all values of list2 in a case insensitive collection
Set<String> set2 = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
set2.addAll(list2);
// Remove all common Strings ignoring case
set1.removeAll(set2);
// Keep in list1 only the remaining Strings ignoring case
list1.retainAll(set1);
for (String s : list1) {
System.out.println(s);
}
出力:
BOB
john
Bill
注意1: 2 番目のリストの内容を TreeSet
特にそのサイズがわからない場合、 TreeSet#removeAll(Collection<?> c)
両方のコレクションのサイズに依存します。現在のコレクションのサイズが提供されたコレクションのサイズよりも厳密に大きい場合は、直接呼び出します。 remove(Object o)
現在のコレクションに対して各要素を削除します。この場合、提供されたコレクションはリストになります。しかし、それが逆の場合は、呼び出します contains(Object o)
提供されたコレクションに対して、特定の要素を削除する必要があるかどうかを判断するため、大文字と小文字を区別しないコレクションではない場合、予期した結果が得られません。
注意2: メソッドの動作 ArrayList#retainAll(Collection<?> c)
上で説明した方法は、メソッドのデフォルト実装の動作と同じです。 retainAll(Collection<?> c)
で見つけることができます AbstractCollection
そのため、このアプローチは、実装が以下のコレクションに対して実際に機能します。 retainAll(Collection<?> c)
同じ動作をします。
を使用できます ハッシュマップ そして、大文字セットを大文字小文字混合セットにマップするキーとして使用します。
ハッシュマップのキーは一意であり、HashMap.keyset(); を使用してそれらのセットを取得できます。
元の大文字と小文字を取得するには、HashMap.get("UPPERCASENAME") と同じくらい簡単です。
そして、によると、 ドキュメンテーション:
このマップに含まれるキーの設定ビューを返します。 セットはマップに裏付けられているため、マップの変更はセットに反映され、その逆も同様です。 このセットは、iterator.remove、set.remove、removeall、rethinal、およびクリア操作を介して、このマップから対応するマッピングを削除する要素削除をサポートします。追加または追加操作をサポートしていません。
したがって、HashMap.keyset().removeAll はハッシュマップに影響します:)
編集:マクダウェルの解決策を使用します。実際には文字を大文字にする必要がないという事実を見落としていました:P
これは、次を使用して解決するのが興味深いでしょう グーグルコレクション. 。次のように定数 Predicate を指定できます。
private static final Function<String, String> TO_UPPER = new Function<String, String>() {
public String apply(String input) {
return input.toUpperCase();
}
そして、あなたが望んでいることは次のように行うことができます:
Collection<String> toRemove = Collections2.transform(list2, TO_UPPER);
Set<String> kept = Sets.filter(list1, new Predicate<String>() {
public boolean apply(String input) {
return !toRemove.contains(input.toUpperCase());
}
}
あれは:
- 「破棄する」リストの大文字のみのバージョンを作成する
- 元のリストにフィルターを適用して、 のみ 大文字の値が ない 大文字のみのリスト。
の出力に注意してください。 Collections2.transform
効率的ではありません Set
したがって、大量のデータを扱っていて、そのリストを調査するコストが負担になる場合は、代わりに次のように使用できます。
Set<String> toRemove = Sets.newHashSet(Collections2.transform(list2, TO_UPPER));
これにより、効率的な検索が復元され、フィルタリングが O(n^2) ではなく O(n) に戻ります。
私の知る限りでは、互いにそれらの個別のオブジェクトのハッシュコード・メソッドを使用するのをHashSetの。 あなたはそのための個別のケースにするために、あなたのオブジェクトでこのメソッドをオーバーライドする必要があります。
あなたが本当に文字列を使用している場合は、文字列クラスを拡張することができないとしては、あなたがこのメソッドをオーバーライドすることはできません。
そのため、あなたはあなたのコンテンツで埋める属性として文字列を含む独自のクラスを作成する必要があります。あなたは、文字列を変更するためにのgetValue()とのsetValue(String)メソッドを持っている場合があります。
あなたはハッシュマップに独自のクラスを追加することができます。
これはあなたの問題を解決する必要があります。
について