Как реализовать эффективные хэш-минусы с помощью Java HashSet

1

Я пытаюсь реализовать хеш-минусы в java, сопоставимые с тем, что String.intern делает для строк. Т.е. я хочу, чтобы класс сохранял все различные значения типа данных T в наборе и предоставлял метод T intern(T t) который проверяет, находится ли t в наборе. Если это так, возвращается экземпляр в наборе, иначе t добавляется в набор и возвращается. Причина в том, что результирующие значения можно сравнить с помощью ссылочного равенства, так как, конечно, оба одинаковых значения возвращаются из intern.

Конечно, наиболее очевидной структурой данных кандидатов для хэш-минусов является java.util.HashSet<T>. Тем не менее, кажется, что его интерфейс несовершенен и не позволяет эффективно вставлять, потому что нет способа получить элемент, который уже находится в наборе, или вставить его, если он там отсутствует.

Алгоритм, использующий HashSet будет выглядеть так:

class HashCons<T>{
    HashSet<T> set = new HashSet<>();

    public T intern(T t){
        if(set.contains(t)) {
           return ???;  // <----- PROBLEM
        } else {
           set.add(t); // <--- Inefficient, second hash lookup
           return t;
    }
}

Как вы видите, проблема двоякая:

  1. Это решение было бы неэффективным, так как я дважды получал доступ к хеш-таблице, один раз для contains и один раз для add. Но все в порядке, это может быть не слишком большой удар производительности, поскольку правильный кусок будет в кеше после contains, поэтому add не приведет к провалу кеша и, таким образом, будет довольно быстрым.
  2. Я не могу получить элемент уже в наборе (см. Строку с PROBLEM). Нет способа получить элемент в наборе. Поэтому реализовать это невозможно.

Я что-то упустил? Или действительно невозможно построить обычный хеш-минус с java.util.HashSet?

Теги:
hashset
hashtable

2 ответа

1

Well HashSet реализуется как оболочка HashMap в OpenJDK, поэтому вы не выиграете в использовании памяти по сравнению с решением, предложенным aRestless.

10-минутный эскиз

class HashCons<T> {
    T[] table;
    int size;
    int sizeLimit;
    HashCons(int expectedSize) {
        init(Math.max(Integer.highestOneBit(expectedSize * 2) * 2, 16));
    }

    private void init(int capacity) {
        table = (T[]) new Object[capacity];
        size = 0;
        sizeLimit = (int) (capacity * 2L / 3);
    }

    T cons(@Nonnull T key) {
        int mask = table.length - 1;
        int i = key.hashCode() & mask;
        do {
            if (table[i] == null) break;
            if (key.equals(table[i])) return table[i];
            i = (i + 1) & mask;
        } while (true);
        table[i] = key;
        if (++size > sizeLimit) rehash();
        return key;
    }

    private void rehash() {
        T[] table = this.table;
        if (table.length == (1 << 30))
            throw new IllegalStateException("HashCons is full");
        init(table.length << 1);
        for (T key : table) {
            if (key != null) cons(key);
        }
    }
}
1

Я не думаю, что это возможно с помощью HashSet. Вместо этого вы можете использовать какую-то Map и использовать свое значение как ключ и как значение. java.util.concurrent.ConcurrentMap имеет довольно удобный метод

putIfAbsent(K key, V value)

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

Вот как вы это сделаете, используя HashMap:

class HashCons<T>{
    Map<T,T> map = new HashMap<T,T>();

    public T intern(T t){
        if (!map.containsKey(t))
            map.put(t,t);
        return map.get(t);
    }
}

Я думаю, что причина, по которой это невозможно с HashSet, довольно проста: для множества, если contains(t), это означает, что данный t также равен одному из t 'в наборе. Нет причин для возможности вернуть его (поскольку у вас уже есть).

  • 0
    Да, я тоже уже думал об этом. Но это немного глупо, потому что это не карта, это набор. Конечно, я мог бы злоупотреблять картой, у которой нет значимого типа значения (как реализация HashSet делает сама внутри себя), но было бы обидно, что в обычной реализации набора отсутствует такая возможность ... Кроме того, мне все равно придется получить доступ к HashMap два раза, это позор :(
  • 0
    Посмотрите здесь stackoverflow.com/questions/23877776/… вам нужна карта для замены любого кандидата его первым созданным идентичным экземпляром. Также stackoverflow.com/questions/23744201/…
Показать ещё 6 комментариев

Ещё вопросы

Сообщество Overcoder
Наверх
Меню