def values
@store.values.flatten
end
def each
@store.each {|k,v| v.each {|y| yield k,y}}
end
alias each_pair each
def each_key
self.keys.each {|k| yield k}
end
def each_value
self.values.each {|v| yield v}
end
def has_key? k
self.keys.include? k
end
def has_value? v
self.values.include? v
end
def length
self.values.size
end
alias size length
def delete k
val = @store[k]
@store.delete k
val
end
def delete k,v
@store[k] -= [v] if @store[k]
v
end
# Остальные методы опущены...
end
# He будет работать... для повторяющихся ключей
# актуально только последнее значение.
h = {1=>1, 2=>4, 3=>9, 4=>16, 2=>0}
# А так будет...
h = HashDup.new(1,1, 2,4, 3,9, 4,16, 2,0)
k = h.keys # [4, 1, 2, 2, 3]
v = h.values # [16, 1, 4, 0, 9]
n = h.size # 5
h.each {|k,v| puts '#{k} => #{v}'}
# Печатается:
# 4 => 16
# 1 => 1
# 2 => 4
# 2 => 0
# 3 => 9
Но если не пользоваться литеральными хэшами, то задача решаема. В листинге 8.1 реализован класс, содержащий атрибут @store
, который является обычным хэшем; каждое значение этого хэша представляет собой массив. Доступ к хэшу организован так, что при необходимости добавить ключ, который уже существует, мы на самом деле добавляем новое значение в массив, ассоциированный с этим ключом.
Что должен возвращать метод size
? Очевидно, «истинное» число пар ключ-значение, включая и дубликаты. Аналогично метод keys
возвращает массив, который может содержать дубликаты. Итераторы ведут себя естественно; как и в случае обычного хэша, порядок обхода непредсказуем.
Помимо стандартного метода delete
мы реализовали метод delete_pair
. Первый удаляет все значения, ассоциированные с данным ключом, второй — только конкретную пару ключ-значение. (Отметим, что было бы затруднительно реализовать единственный метод вида delete(k, v=nil)
, так как nil
— допустимое значение в любом хэше.)
Для краткости мы не стали реализовывать весь класс целиком и, честно говоря, для некоторых методов, например invert
, пришлось бы принимать небанальные решения по поводу желательного поведения. Интересующийся читатель может восполнить пробелы.
8.3. Перечисляемые структуры в общем
Что делает набор перечисляемым? Вообще-то сам тот факт, что это набор. Модуль Enumerable
требует, чтобы был определен стандартный итератор each
. Последовательность обхода не имеет значения, так как даже неупорядоченные наборы, например хэш, могут обладать итераторами.
Кроме того, если предполагается пользоваться методами min
, max
и sort
, то для набора должен быть определен метод сравнения (<=>
). Все это достаточно очевидно.
Итак,