Хобрук: Ваш путь к мастерству в программировании

Двунаправленная хеш-таблица в Ruby

Мне нужна двунаправленная хэш-таблица в Ruby. Например:

h = {:abc => 123, :xyz => 789, :qaz => 789, :wsx => [888, 999]}
h.fetch(:xyz) # => 789
h.rfetch(123) # => abc
h.rfetch(789) # => [:xyz, :qaz]
h.rfetch(888) # => :wsx

Метод rfetch означает обратную выборку и является только моим предложением.

Обратите внимание на три вещи:

  1. Если несколько ключей сопоставляются с одним и тем же значением, тогда rfetch возвращает их все, упакованные в массив.
  2. Если значение является массивом, то rfetch ищет его параметр среди элементов массива.
  3. Двунаправленный хеш означает, что и fetch, и rfetch должны выполняться за постоянное время.

Существует ли такая структура в Ruby (включая внешние библиотеки)?

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

03.08.2011

  • Для быстрого и грязного вы можете использовать встроенный hash.invert() для создания отдельного обратного хэша (stackoverflow.com/a/3794060 /18706). Как указывает @ken-bloom, более надежная реализация этого находится на raa.ruby-lang. .org/project/invertash 19.02.2013

Ответы:


1

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

class BiHash
    def initialize
        @forward = Hash.new { |h, k| h[k] = [ ] }
        @reverse = Hash.new { |h, k| h[k] = [ ] }
    end

    def insert(k, v)
        @forward[k].push(v)
        @reverse[v].push(k)
        v
    end

    def fetch(k)
        fetch_from(@forward, k)
    end

    def rfetch(v)
        fetch_from(@reverse, v)
    end

    protected

    def fetch_from(h, k)
        return nil if(!h.has_key?(k))
        v = h[k]
        v.length == 1 ? v.first : v.dup
    end
end

Поиск будет вести себя так же, как обычный поиск по хешу (поскольку он является обычным поиском по хэшу). Добавьте несколько операторов и, возможно, достойные реализации to_s и inspect, и все будет хорошо.

Такая штука работает так:

b = BiHash.new
b.insert(:a, 'a')
b.insert(:a, 'b')
b.insert(:a, 'c')
b.insert(:b, 'a')
b.insert(:c, 'x')

puts b.fetch(:a).inspect            # ["a", "b", "c"]
puts b.fetch(:b).inspect            # "a"
puts b.rfetch('a').inspect          # [:a, :b]
puts b.rfetch('x').inspect          # :c
puts b.fetch(:not_there).inspect    # nil
puts b.rfetch('not there').inspect  # nil

Нет ничего плохого в том, чтобы создавать свои инструменты, когда они вам нужны.

03.08.2011
  • Вероятно, было бы неплохо вернуть v.dup вместо v в fetch_from, иначе вы можете получить неприятные побочные эффекты, например. b.rfetch(:a).pop 05.08.2011

  • 2

    В Ruby нет такой встроенной структуры.

    Обратите внимание, что Hash#rassoc делает что-то подобное, но возвращает только первое совпадение и имеет линейное время:

    h = {:abc => 123, :xyz => 789, :qaz => 789, :wsx => [888, 999]}
    h.rassoc(123) # => [:abc, 123]
    

    Кроме того, невозможно выполнить ваши требования в Ruby совершенно безопасным образом, так как вы не сможете обнаружить изменения в значениях, которые являются массивами. Например.:

    h = MyBidirectionalArray.new(:foo => 42, :bar => [:hello, :world])
    h.rfetch(:world) # => :bar
    h[:bar].shift
    h[:bar] # => [:world]
    h.rfetch(:world) # => should be nil, but how to detect this??
    

    Вычисление хеша каждый раз для обнаружения изменения сделает ваш поиск линейным по времени. Однако вы можете продублировать значения массива и заморозить их (как это делает Ruby для ключей Hash, которые являются строками!)

    Кажется, вам нужен класс Graph, который может иметь API, отличный от Hash, не так ли? Вы можете проверить rgl или что-то подобное, но я не знаю, как они реализованы. .

    Удачи.

    03.08.2011

    3

    Существует метод Hash#invert (http://www.ruby-doc.org/core-2.1.0/Hash.html#method-i-invert), чтобы добиться этого. Однако он не будет отображать несколько значений в массив.

    15.03.2014

    4

    Попробуй это:

    class Hash
      def rfetch val
        select { |k,v| v.is_a?(Array) ? v.include?(val) : v == val }.map { |x| x[0] }
      end
    end
    
    03.08.2011
  • Да, это самое простое решение, однако оно требует линейного времени, поэтому нарушает третий пункт, упомянутый в вопросе. 03.08.2011

  • 5

    Если вы редко обновляете этот хэш, вы можете использовать inverthash.

    03.08.2011
    Новые материалы

    Аргументы прогрессивного улучшения почти всегда упускают суть
    В наши дни в кругах веб-разработчиков много болтают о Progressive Enhancement — PE, но на самом деле почти все аргументы с обеих сторон упускают самую фундаментальную причину, по которой PE..

    Введение в Джанго Фреймворк
    Схема «работать умно, а не усердно» В этой и последующих статьях я познакомлю вас с тем, что такое фреймворк Django и как создать свое первое приложение с помощью простых и понятных шагов, а..

    Настольный ПК как «одно кольцо, чтобы править всеми» домашних компьютеров
    Вид после 9 месяцев использования С настольных компьютеров все началось, но в какой-то момент они стали «серверами», и мы все перешли на ноутбуки. В прошлом году я столкнулся с идеей настольных..

    Расширенные методы безопасности для VueJS: реализация аутентификации без пароля
    Руководство, которое поможет вам создавать безопасные приложения в долгосрочной перспективе Безопасность приложений часто упускается из виду в процессе разработки, потому что основная..

    стройный-i18следующий
    Представляем стройную оболочку для i18next. Эта библиотека, основанная на i18next, заключает экземпляр i18next в хранилище svelte и отслеживает события i18next, такие как languageChanged,..

    Обзор 20 основных и современных методов работы с массивами в JavaScript
    Вы знаете их всех? В этом коротком посте я покажу сводку методов, доступных в JavaScript для работы с массивами. Я надеюсь, что вы найдете это полезным! В конце поста вы найдете ссылку на..

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