web-dev-qa-db-ja.com

セットから要素を取得する

Setが他の要素と等しい要素を取得するための操作を提供しないのはなぜですか?

Set<Foo> set = ...;
...
Foo foo = new Foo(1, 2, 3);
Foo bar = set.get(foo);   // get the Foo element from the Set that equals foo

Setbarと等しい要素が含まれているかどうかを尋ねることができますが、その要素を取得できないのはなぜですか? :(

明確にするために、equalsメソッドはオーバーライドされていますが、すべてではなくフィールドの1つだけをチェックします。したがって、等しいと見なされる2つのFooオブジェクトは実際には異なる値を持つことができます。そのため、fooを使用することはできません。

270
foobar

等しい場合、要素を取得する意味はありません。 Mapは、このユースケースに適しています。


それでも要素を見つけたい場合は、反復子を使用する以外に選択肢はありません。

public static void main(String[] args) {

    Set<Foo> set = new HashSet<Foo>();
    set.add(new Foo("Hello"));

    for (Iterator<Foo> it = set.iterator(); it.hasNext(); ) {
        Foo f = it.next();
        if (f.equals(new Foo("Hello")))
            System.out.println("foo found");
    }
}

static class Foo {
    String string;
    Foo(String string) {
        this.string = string;
    }
    @Override
    public int hashCode() { 
        return string.hashCode(); 
    }
    @Override
    public boolean equals(Object obj) {
        return string.equals(((Foo) obj).string);
    }
}
94
dacwe

WhySetが別の要素に等しい要素を取得する操作を提供しない」という正確な質問に答えるには、答えは次のとおりです。コレクションフレームワークの設計者は非常に楽しみです。彼らはあなたの非常に正当なユースケースを予期せず、単純に「javadocからの」数学セット抽象化のモデル化を試み、有用なget()メソッドを追加するのを忘れました。

暗黙の質問「howでは要素を取得しますか?」:最善の解決策は、Map<E,E>の代わりにSet<E>を使用して要素をマッピングすることです自分自身。そのようにして、効率的に「set」から要素を取得できます。これは、Mapのget()メソッドが効率的なハッシュテーブルまたはツリーアルゴリズムを使用する要素。必要に応じて、Setをカプセル化して、追加のget()メソッドを提供するMapの独自の実装を作成できます。

私の意見では、以下の答えは悪いか間違っています:

「すでに同じオブジェクトを持っているので、要素を取得する必要はありません」:質問ですでに示したように、アサーションは間違っています。等しい2つのオブジェクトは、オブジェクトの等価性に関係のない異なる状態を保持できます。目標は、「クエリ」として使用されるオブジェクトの状態ではなく、Setに含まれる要素のこの状態にアクセスすることです。

「イテレータを使用する以外に選択肢はありません」:これは、大きなセットに対して完全に非効率的なコレクションに対する線形検索です(皮肉なことに、Setは効率的に照会できるハッシュマップまたはツリーとして編成されます)。しないでください!このアプローチを使用することで、実際のシステムで深刻なパフォーマンスの問題を見てきました。私の意見では、欠落しているget()メソッドについて恐ろしいことは、それを回避するのは少し面倒ではありませんが、ほとんどのプログラマーはその意味を考えずに線形検索アプローチを使用します。

341
jschreiner

集合をリストに変換してから、リストのgetメソッドを使用する

Set<Foo> set = ...;
List<Foo> list = new ArrayList<Foo>(set);
Foo obj = list.get(0);
21
To Kra

あなたが同じ物を持っているなら、なぜあなたはセットからの物が必要なのですか?それがキーだけで「等しい」場合、Mapがより良い選択です。

とにかく、次のようにします。

Foo getEqual(Foo sample, Set<Foo> all) {
  for (Foo one : all) {
    if (one.equals(sample)) {
      return one;
    }
  } 
  return null;
}

Java 8では、これはワンライナーになることができます。

return all.stream().filter(sample::equals).findAny().orElse(null);
12
Arne Burmeister

Javaのデフォルト設定は、残念ながら、 jschreiner が正確に説明されているように、「get」操作を提供するようには設計されていません。

dacwe によって推奨される)関心のある要素を見つけるために反復子を使用したり、( KyleM によって推奨される)要素を削除して再追加する解決策しかし、非常に非効率的になる可能性があります。

David Ogren で正しく記述されているように、等しくないオブジェクトが「等しい」ようにequalsの実装をオーバーライドすると、メンテナンスの問題が発生しやすくなります。

そして、(多くの人が示唆しているように)明示的な置き換えとしてMapを使用すると、コードが洗練されなくなります。

目的がセットに含まれている要素の元のインスタンスにアクセスすることである場合(私が正しくユースケースを理解していることを願ってください)、これは別の可能な解決策です。


Javaを使ってクライアントサーバー型のビデオゲームを開発している間も、私は個人的に同じニーズを持っていました。私の場合、各クライアントにはサーバーに格納されているコンポーネントのコピーがあり、問題はクライアントがサーバーのオブジェクトを変更する必要があるたびに発生していました。

インターネットを介してオブジェクトを渡すことは、クライアントがとにかくそのオブジェクトの異なるインスタンスを持っていたことを意味しました。この「コピーされた」インスタンスを元のインスタンスと一致させるために、Java UUIDを使用することにしました。

そこで私は抽象クラスUniqueItemを作成しました。これはそのサブクラスの各インスタンスに自動的にランダムな一意のIDを与えます。

このUUIDはクライアントとサーバーインスタンスの間で共有されているので、このように単純にMapを使用することでそれらを一致させることができます。

しかし、同様のユースケースでMapを直接使用するのは、まだ洗練されていません。誰かが、マップを使うことは保守と取り扱いがより複雑かもしれないと主張するかもしれません。

これらの理由で、私はMagicSetと呼ばれるライブラリを実装しました。これはMapの使用を開発者にとって「透明」にします。

https://github.com/ricpacca/magicset


元のJava HashSetと同様に、MagicHashSet(ライブラリで提供されているMagicSetの実装の1つ)は、バッキングHashMapを使用します。そして値としての要素それ自身。これは通常のHashSetと比較してメモリ使用量のオーバーヘッドを引き起こしません。

さらに、MagicSetはSetとまったく同じように使用できますが、getFromId()、popFromId()、removeFromId()など、追加の機能を提供するメソッドがいくつかあります。

これを使用する唯一の要件は、MagicSetに格納したい要素はすべて抽象クラスUniqueItemを拡張する必要があるということです。


これは、同じUUID(またはそのUUIDさえも)を持つその都市の別のインスタンスを指定して、MagicSetからその都市の元のインスタンスを取得することを想像したコード例です。

class City extends UniqueItem {

    // Somewhere in this class

    public void doSomething() {
        // Whatever
    }
}

public class GameMap {
    private MagicSet<City> cities;

    public GameMap(Collection<City> cities) {
        cities = new MagicHashSet<>(cities);
    }

    /*
     * cityId is the UUID of the city you want to retrieve.
     * If you have a copied instance of that city, you can simply 
     * call copiedCity.getId() and pass the return value to this method.
     */
    public void doSomethingInCity(UUID cityId) {
        City city = cities.getFromId(cityId);
        city.doSomething();
    }

    // Other methods can be called on a MagicSet too
}
11
ricpacca

あなたのセットが実際にNavigableSet<Foo>TreeSetなど)、そしてFoo implements Comparable<Foo>であるなら、あなたは使うことができます。

Foo bar = set.floor(foo); // or .ceiling
if (foo.equals(bar)) {
    // use bar…
}

(ヒントについての@ eliran-malkaのコメントに感謝します。)

10
Jesse Glick

Java 8では、次のことが可能です。

Foo foo = set.stream().filter(item->item.equals(theItemYouAreLookingFor)).findFirst().get();

しかし、注意してください、.get()はNoSuchElementExceptionを投げます、あるいはOptional項目を操作することができます。

7
daniele
Object objectToGet = ...
Map<Object, Object> map = new HashMap<Object, Object>(set.size());
for (Object o : set) {
    map.put(o, o);
}
Object objectFromSet = map.get(objectToGet);

あなたが1つだけ取得するならば、あなたはすべてのあなたの要素をループするのであなたは違いを気づくでしょうがあなたがすべてのあなたの要素をループするのでそれほどパフォーマンスが良くないでしょう。

4
Xymon

なぜ:

Setは比較の手段を提供するのに役立つ役割を果たしているようです。重複要素を格納しないように設計されています。

この意図/設計のために、格納されたオブジェクトへの参照をget()してから変更すると、Setの設計意図が妨げられ、予期しない動作が発生する可能性があります。

からJavaDocs

可変オブジェクトを集合要素として使用する場合は、細心の注意を払う必要があります。オブジェクトがセット内の要素である間に、オブジェクトの値が等号比較に影響を与えるような方法で変更された場合、セットの動作は指定されません。

どうやって:

Streamsが導入されたので、次のことができます。

mySet.stream()
.filter(object -> object.property.equals(myProperty))
.findFirst().get();
2
Jason M

そのためにはJava HashMapオブジェクトを使用することをお勧めします http://download.Oracle.com/javase/1,5.0/docs/api/Java/util/HashMap.html

1
nurxyz

Setのどんな特定の実装もそうではないかもしれないので ランダムアクセス

イテレータのnext()メソッドを使って、等しい要素が見つかったら、いつでも イテレータ を取得してSetをステップスルーすることができます。これは実装に関係なく機能します。実装がランダムアクセスではない場合(リンクリスト付きのSetセットの画像)、返す要素を見つけるためにコレクションを繰り返す必要があり、get(E element)はこれを暗示しているように思われるので、インターフェースのget(E element)メソッドは欺くでしょうSetが取得する要素に直接ジャンプできることが必要です。

contains()は、もちろん実装によっては同じことをする必要があるかもしれませんし、そうでないかもしれませんが、その名前が同じ種類の誤解に役立つとは思えません。

0
Tom Tresansky

あなたがHashSetからn番目の要素が欲しいなら、あなたは以下の解決策で進むことができます、ここで私はHashSetでModelClassのオブジェクトを加えました。

ModelClass m1 = null;
int nth=scanner.nextInt();
for(int index=0;index<hashset1.size();index++){
    m1 = (ModelClass) itr.next();
    if(nth == index) {
        System.out.println(m1);
        break;
    }
}
0
Hardik Patel

適切なオブジェクトはguavaの Interner です。

他の不変型についてString.intern()と同等の動作を提供します。一般的な実装は Interners クラスから入手できます。

ConcurrencyLevelや使用される参照の種類など、非常に興味深いレバーもいくつかあります(WeakInternerよりも便利であると思われるSoftInternerが提供されていないことは注目に値します)。

0
molyss

iteratorクラスを使用できます

import Java.util.Iterator;
import Java.util.HashSet;

public class MyClass {
 public static void main(String[ ] args) {
 HashSet<String> animals = new HashSet<String>();
animals.add("fox");
animals.add("cat");
animals.add("dog");
animals.add("rabbit");

Iterator<String> it = animals.iterator();
while(it.hasNext()) {
  String value = it.next();
  System.out.println(value);   
 }
 }
}
0
chiha asma

Java.util.HashSetの実装の最初の数行を見ると、次のようになります。

public class HashSet<E>
    ....
    private transient HashMap<E,Object> map;

そのため、HashSetHashMapを内部的に使用します。つまり、HashMapを直接使用し、キーと値として同じ値を使用すると、必要な効果が得られ、メモリを節約できます。

0
rghome

私は知っている、これはずっと前に質問されて答えられた、しかし誰かが興味を持っているならば、これが私の解決策である - HashMapによって支持されるカスタムセットクラス

http://Pastebin.com/Qv6S91n9

他のすべてのSetメソッドを簡単に実装できます。

0
Lukas Z.

はい、HashMapを使用してください。ただし、特殊な方法で使用します。HashMapを疑似-Setとして使用しようとしたときに予想されるトラップは、Map/Setの「実際の」要素と「候補」要素の間の混乱です。 equal要素が既に存在するかどうかをテストします。これは絶対確実というわけではありませんが、罠からあなたを遠ざけます。

class SelfMappingHashMap<V> extends HashMap<V, V>{
    @Override
    public String toString(){
        // otherwise you get lots of "... object1=object1, object2=object2..." stuff
        return keySet().toString();
    }

    @Override
    public V get( Object key ){
        throw new UnsupportedOperationException( "use tryToGetRealFromCandidate()");
    }

    @Override
    public V put( V key, V value ){
       // thorny issue here: if you were indavertently to `put`
       // a "candidate instance" with the element already in the `Map/Set`: 
       // these will obviously be considered equivalent 
       assert key.equals( value );
       return super.put( key, value );
    }

    public V tryToGetRealFromCandidate( V key ){
        return super.get(key);
    }
}

それからこれをしなさい:

SelfMappingHashMap<SomeClass> selfMap = new SelfMappingHashMap<SomeClass>();
...
SomeClass candidate = new SomeClass();
if( selfMap.contains( candidate ) ){
    SomeClass realThing = selfMap.tryToGetRealFromCandidate( candidate );
    ...
    realThing.useInSomeWay()...
}

しかし、プログラマが実際にすぐにMap/Set..に入れない限り、candidateを何らかの形で自己消滅させる必要があります。containsを結合しない限り、candidateを「汚染」させたいのです。 Mapはそれを "アナテマ"にします。おそらくSomeClassに新しいTaintableインターフェースを実装させることができます。

以下のように、より満足のいく解決策はGettableSetです。しかし、これをうまく機能させるには、すべてのコンストラクタを見えないようにするためにSomeClassの設計を担当する必要があります(または...可能であり、ラッパークラスを設計して使用する意欲があります)。

public interface NoVisibleConstructor {
    // again, this is a "Nudge" technique, in the sense that there is no known method of 
    // making an interface enforce "no visible constructor" in its implementing classes 
    // - of course when Java finally implements full multiple inheritance some reflection 
    // technique might be used...
    NoVisibleConstructor addOrGetExisting( GettableSet<? extends NoVisibleConstructor> gettableSet );
};

public interface GettableSet<V extends NoVisibleConstructor> extends Set<V> {
    V getGenuineFromImpostor( V impostor ); // see below for naming
}

実装:

public class GettableHashSet<V extends NoVisibleConstructor> implements GettableSet<V> {
    private Map<V, V> map = new HashMap<V, V>();

    @Override
    public V getGenuineFromImpostor(V impostor ) {
        return map.get( impostor );
    }

    @Override
    public int size() {
        return map.size();
    }

    @Override
    public boolean contains(Object o) {
        return map.containsKey( o );
    }

    @Override
    public boolean add(V e) {
        assert e != null;
        V result = map.put( e,  e );
        return result != null;
    }

    @Override
    public boolean remove(Object o) {
        V result = map.remove( o );
        return result != null;
    }

    @Override
    public boolean addAll(Collection<? extends V> c) {
        // for example:
        throw new UnsupportedOperationException();
    }

    @Override
    public void clear() {
        map.clear();
    }

    // implement the other methods from Set ...
}

あなたのNoVisibleConstructorクラスはこのようになります:

class SomeClass implements NoVisibleConstructor {

    private SomeClass( Object param1, Object param2 ){
        // ...
    }

    static SomeClass getOrCreate( GettableSet<SomeClass> gettableSet, Object param1, Object param2 ) {
        SomeClass candidate = new SomeClass( param1, param2 );
        if (gettableSet.contains(candidate)) {
            // obviously this then means that the candidate "fails" (or is revealed
            // to be an "impostor" if you will).  Return the existing element:
            return gettableSet.getGenuineFromImpostor(candidate);
        }
        gettableSet.add( candidate );
        return candidate;
    }

    @Override
    public NoVisibleConstructor addOrGetExisting( GettableSet<? extends NoVisibleConstructor> gettableSet ){
       // more elegant implementation-hiding: see below
    }
}

PSそのようなNoVisibleConstructorクラスに関する1つの技術的な問題:そのようなクラスが本質的にfinalであることは反対されるかもしれませんが、それは望ましくないかもしれません。実際には、いつでもダミーのパラメータなしのprotectedコンストラクタを追加できます。

protected SomeClass(){
    throw new UnsupportedOperationException();
}

...これは少なくともサブクラスをコンパイルさせるでしょう。その後、サブクラスに別のgetOrCreate()ファクトリメソッドを含める必要があるかどうかを検討する必要があります。

最後のステップ はあなたのセットメンバーのためのこのような抽象基底クラス(リストの場合は "element"、セットの場合は "member")です(可能であれば、wrapper classの使用範囲最大限の実装を隠蔽するための、クラスがあなたの管理下にない、または既に基本クラスを持っているなどの場合。

public abstract class AbstractSetMember implements NoVisibleConstructor {
    @Override
    public NoVisibleConstructor
            addOrGetExisting(GettableSet<? extends NoVisibleConstructor> gettableSet) {
        AbstractSetMember member = this;
        @SuppressWarnings("unchecked") // unavoidable!
        GettableSet<AbstractSetMembers> set = (GettableSet<AbstractSetMember>) gettableSet;
        if (gettableSet.contains( member )) {
            member = set.getGenuineFromImpostor( member );
            cleanUpAfterFindingGenuine( set );
        } else {
            addNewToSet( set );
        }
        return member;
    }

    abstract public void addNewToSet(GettableSet<? extends AbstractSetMember> gettableSet );
    abstract public void cleanUpAfterFindingGenuine(GettableSet<? extends AbstractSetMember> gettableSet );
}

...使い方はかなり明白です(あなたのSomeClassstaticファクトリメソッドの内側):

SomeClass setMember = new SomeClass( param1, param2 ).addOrGetExisting( set );
0
mike rodent

やったことある!あなたがGuavaを使っているならば、それを地図に変換する簡単な方法は:

Map<Integer,Foo> map = Maps.uniqueIndex(fooSet, Foo::getKey);
0
Heisenberg