Получение элемента из набора

Почему 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

Я могу спросить, содержит ли Set элемент, равный bar, поэтому почему я не могу получить этот элемент?: (

Чтобы уточнить, метод equals переопределен, но он проверяет только одно из полей, а не все. Таким образом, два объекта Foo, которые считаются равными, могут иметь разные значения, поэтому я не могу просто использовать Foo.

Ответ 1

Невозможно получить элемент, если он равен. A 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);
    }
}

Ответ 2

Чтобы ответить на точный вопрос " Почему не выполняет Set операцию, чтобы получить элемент, который равен другому элементу?", ответ будет следующим: поскольку разработчики структуры коллекции были не очень перспективный взгляд. Они не ожидали вашего законного варианта использования, наивно пытались "моделировать абстрактную абстракцию математики" (из javadoc) и просто забывали добавить полезный метод get().

Теперь к подразумеваемому вопросу " как вы получите элемент then": я думаю, что лучшим решением является использование Map<E,E> вместо Set<E>, чтобы сопоставить элементы с самим собой, Таким образом, вы можете эффективно извлекать элемент из "set", потому что метод get() Map найдет элемент, используя эффективную хеш-таблицу или алгоритм дерева. Если вы хотите, вы можете написать собственную реализацию Set, которая предлагает дополнительный метод get(), инкапсулируя Map.

Следующие ответы, на мой взгляд, плохие или неправильные:

"Вам не нужно получать элемент, потому что у вас уже есть равный объект": утверждение неверно, как вы уже показали в вопросе. Два объекта, которые все равно равны, могут иметь другое состояние, которое не имеет отношения к равенству объекта. Цель состоит в том, чтобы получить доступ к этому состоянию элемента, содержащегося в Set, а не к состоянию объекта, используемого в качестве "запроса".

"У вас нет другого выбора, кроме использования итератора": это линейный поиск по коллекции, которая совершенно неэффективна для больших наборов (по иронии судьбы, Set внутри) организована как хэш-карта или дерево, которые могут быть запрошены эффективно). Не делай этого! Я видел серьезные проблемы с производительностью в реальных системах, используя этот подход. На мой взгляд, что ужасно насчет отсутствующего метода get() - это не так много, что немного обходиться с ним, но большинство программистов будут использовать линейный подход поиска, не думая о последствиях.

Ответ 3

Преобразовать набор в список, а затем использовать get метод списка

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

Ответ 4

Если у вас есть равный объект, зачем вам нужен один из набора? Если он "равен" только клавишам, лучше выбрать 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);

Ответ 5

Набор по умолчанию в Java, к сожалению, не предназначен для обеспечения операции get, как точно объяснил jschreiner.

Решения использования итератора для нахождения интересующего элемента (предложенного dacwe) или для удаления элемента и его повторного добавления с обновленными значениями (предложенными KyleM) могут работать, но могут быть очень неэффективными.

Переопределение реализации equals, так что неравные объекты "равны", как правильно сказал Дэвид Огрен, может легко вызвать проблемы с обслуживанием.

И использование Map в качестве явной замены (как предлагают многие), imho, делает код менее элегантным.

Если цель состоит в том, чтобы получить доступ к исходному экземпляру элемента, содержащегося в наборе (надеюсь, я правильно понял ваш вариант использования), вот еще одно возможное решение.


Лично у меня была такая же потребность при разработке видеоигры клиент-сервер с Java. В моем случае у каждого клиента были копии компонентов, хранящихся на сервере, и проблема заключалась в том, что клиенту нужно было изменить объект сервера.

Передача объекта через Интернет означала, что у клиента все равно были разные экземпляры этого объекта. Чтобы сопоставить этот "скопированный" экземпляр с исходным, я решил использовать Java UUID.

Поэтому я создал абстрактный класс UniqueItem, который автоматически дает случайный уникальный идентификатор каждому экземпляру его подклассов.

Этот UUID является общим для клиента и экземпляра сервера, поэтому таким образом можно легко сопоставить их, просто используя карту.

Однако непосредственное использование карты в аналогичном сценарии использования было все еще не элегантным. Кто-то может поспорить, что использование карты может быть более сложным в обслуживании и обработке.

По этим причинам я реализовал библиотеку MagicSet, которая делает использование Map "прозрачным" для разработчика.

https://github.com/ricpacca/magicset


Как и исходный Java HashSet, MagicHashSet (который является одной из реализаций MagicSet, представленных в библиотеке) использует вспомогательный HashMap, но вместо того, чтобы использовать элементы в качестве ключей и фиктивное значение в качестве значений, он использует UUID элемента в качестве ключа и сам элемент как ценность. Это не вызывает накладных расходов на использование памяти по сравнению с обычным HashSet.

Более того, MagicSet может использоваться именно как Set, но с некоторыми другими методами, обеспечивающими дополнительные функциональные возможности, такие как getFromId(), popFromId(), removeFromId() и т.д.

Единственное требование для его использования - чтобы любой элемент, который вы хотите сохранить в MagicSet, должен расширять абстрактный класс UniqueItem.


Вот пример кода, представляющий, как извлечь оригинальный экземпляр города из MagicSet, учитывая другой экземпляр этого города с тем же UUID (или даже просто его UUID).

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
}

Ответ 6

Если ваш набор фактически является NavigableSet<Foo> (например, TreeSet) и Foo implements Comparable<Foo>, вы можете использовать

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

(Спасибо за комментарий @eliran-malkas за подсказку.)

Ответ 7

С помощью Java 8 вы можете:

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

Но будьте осторожны .get() генерирует исключение NoSuchElementException или вы можете манипулировать необязательным элементом.

Ответ 8

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);

Если вы только сделаете это, это не будет очень эффективным, потому что вы будете перебирать все свои элементы, но при выполнении нескольких запросов на большом наборе вы заметите разницу.

Ответ 9

Почему:

Кажется, что Set играет полезную роль в обеспечении средств сравнения. Он предназначен не для хранения повторяющихся элементов.

Из-за этого намерения/дизайна, если нужно было() ссылаться на сохраненный объект, а затем мутировать его, возможно, что намерения дизайна Set могут быть сорваны и могут вызвать неожиданное поведение.

Из JavaDocs

Следует проявлять большую осторожность, если изменяемые объекты используются в качестве заданных элементов. Поведение набора не указывается, если значение объекта изменяется таким образом, который влияет на равные сравнения, когда объект является элементом в наборе.

Как

Теперь, когда были введены потоки, можно сделать следующее

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

Ответ 11

Я знаю, об этом просили и ответили давно, однако, если кому-то интересно, вот мое решение - класс настраиваемых наборов, поддерживаемый HashMap:

http://pastebin.com/Qv6S91n9

Вы можете легко реализовать все другие методы Set.

Ответ 12

Было это сделано!! Если вы используете Guava, быстрый способ конвертировать его в карту:

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

Ответ 13

вы можете использовать класс 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);   
 }
 }
}

Ответ 14

Если вы хотите nth Element из HashSet, вы можете пойти ниже, здесь я добавил объект ModelClass в HashSet.

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;
    }
}

Ответ 15

Если вы посмотрите на первые несколько строк реализации java.util.HashSet вы увидите:

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

Таким образом, HashSet любом случае использует HashMap внутренне, что означает, что если вы просто используете HashMap напрямую и используете то же значение, что и ключ и значение, вы получите желаемый эффект и сэкономите себе немного памяти.

Ответ 16

похоже, что правильным объектом для использования является Interner из guava:

Обеспечивает эквивалентное поведение для String.intern() для других неизменяемых типов. Общие реализации доступны из класса Interners.

У него также есть несколько очень интересных рычагов, таких как concurrencyLevel или тип используемых ссылок (возможно, стоит отметить, что он не предлагает SoftInterner, который я считаю более полезным, чем WeakInterner).

Ответ 17

Как насчет использования класса Arrays?

import java.util.Arrays;
import java.util.List;
import java.util.HashSet;
import java.util.Arrays;

public class MyClass {
    public static void main(String args[]) {
        Set mySet = new HashSet();
        mySet.add("one");
        mySet.add("two");
        List list = Arrays.asList(mySet.toArray());
        Object o0 = list.get(0);
        Object o1 = list.get(1);
        System.out.println("items " + o0+","+o1);
    }
}

выход:
пункты один, два

Ответ 18

Поскольку любая конкретная реализация Set может быть или не быть произвольным доступом.

Вы всегда можете получить iterator и пройти через Set, используя метод itaterators next(), чтобы вернуть результат, который вы хотите, как только вы найти равный элемент. Это работает независимо от реализации. Если реализация НЕ является произвольным доступом (изображение связанного списка с установленным набором), метод get(E element) в интерфейсе был бы обманчив, так как ему пришлось бы перебирать коллекцию, чтобы найти возвращаемый элемент, а get(E element) кажется, подразумевает, что это было бы необходимо, чтобы Set мог перейти непосредственно к элементу, чтобы получить.

contains() может или не обязательно делать то же самое, конечно, в зависимости от реализации, но имя, похоже, не поддается такому же недоразумению.

Ответ 19

Да, используйте 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()...
}

Но... теперь вы хотите, чтобы candidate был самоуничтожен каким-то образом, если программист фактически не отправил его в Map/Set... вы бы хотели, чтобы 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: можно возразить, что такой класс по своей природе final, что может быть нежелательным. На самом деле вы всегда можете добавить конструктор protected фиктивный без параметров:

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

... который, по крайней мере, позволит скомпилировать подкласс. Затем вам нужно подумать о необходимости включить в подкласс еще один метод getOrCreate() factory.

Заключительный шаг - это абстрактный базовый класс (элемент "NB" для списка, "член" для набора), как это для ваших членов набора (когда это возможно - снова, область использования обертки класс, в котором класс не находится под вашим контролем или уже имеет базовый класс и т.д.), для максимального скрытия реализации:

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 );
}

... использование довольно очевидно (внутри метода SomeClass static factory):

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

Ответ 20

Быстрый вспомогательный метод, который может решить эту проблему:

<T> T onlyItem(Collection<T> items) {
    if (items.size() != 1)
        throw new IllegalArgumentException("Collection must have single item; instead it has " + items.size());

    return items.iterator().next();
}

Ответ 21

Попробуйте использовать массив:

ObjectClass[] arrayName = SetOfObjects.toArray(new ObjectClass[setOfObjects.size()]);

Ответ 22

Следующим может быть подход

   SharedPreferences se_get = getSharedPreferences("points",MODE_PRIVATE);
   Set<String> main = se_get.getStringSet("mydata",null);
   for(int jk = 0 ; jk < main.size();jk++)
   {
      Log.i("data",String.valueOf(main.toArray()[jk]));
   }