#ruby-on-rails #ruby
#ruby-on-rails #ruby
Вопрос:
У меня есть хэш, ключи которого расположены в отсортированном порядке, а размер хэша превышает 1000. Как я могу разделить хэш на куски на основе диапазона.
Пример :-
h_main = {"1" => "a", "2" => "b", "9" => "c", .............. "880" => "xx", "996" => "xyz", "998" => "lll", "1050" => "mnx"}
Я должен разделить приведенный выше хэш на фрагменты хэша сортировщика на основе диапазона :-
h_result = {"1-100" => {"1" => "a", "2" => "b", "9" => "c" ..... "99" => "re"},
"101-200" => {}
....
....
"900-1000" => {"996" => "xyz", "998" => "lll"},
"1000-1100" => {"1050" => "mnx"}
}
Я могу сделать это, применяя каждый цикл, а затем могу добавить условие для объединения пары ключ-значение в соответствующем хэше, но это длительный процесс.
Пожалуйста, помогите предоставить оптимальное решение, заранее спасибо.
Ответ №1:
def doit(h, group_size)
h.keys.
slice_when { |k1,k2| k2.to_i/group_size > k1.to_i/group_size }.
each_with_object({}) do |key_group,g|
start_range = group_size * (key_group.first.to_i/group_size)
g["%d-%d" % [start_range, start_range group_size-1]] = h.slice(*key_group)
end
end
h = {"11"=>"a", "12"=>"b", "19"=>"c", "28"=>"xx", "29"=> "xyz",
"42"=>"lll", "47"=>"mnx"}
doit(h, 10)
#=> {"10-19"=>{"11"=>"a", "12"=>"b", "19"=>"c"},
# "20-29"=>{"28"=>"xx", "29"=>"xyz"},
# "40-49"=>{"42"=>"lll", "47"=>"mnx"}}
doit(h, 15)
#=> {"0-14"=>{"11"=>"a", "12"=>"b"},
# "15-29"=>{"19"=>"c", "28"=>"xx", "29"=>"xyz"},
# "30-44"=>{"42"=>"lll"}, "45-59"=>{"47"=>"mnx"}}
doit(h, 20)
#=> {"0-19"=>{"11"=>"a", "12"=>"b", "19"=>"c"},
# "20-39"=>{"28"=>"xx", "29"=>"xyz"},
# "40-59"=>{"42"=>"lll", "47"=>"mnx"}}
Смотрите Перечисляемый#срез_когда и Хэш#нарезать.
Шаги следующие.
group_size = 10
a = h.keys
#=> ["11", "12", "19", "28", "29", "42", "47", "74", "76"]
b = a.slice_when { |k1,k2| k2.to_i/group_size > k1.to_i/group_size }
#=> #<Enumerator: #<Enumerator::Generator:0x000056fa312199b8>:each>
Мы можем видеть элементы, которые будут сгенерированы этим счетчиком и переданы в блок путем преобразования его в массив.
b.to_a
#=> [["11", "12", "19"], ["28", "29"], ["42", "47"]]
Наконец,
b.each_with_object({}) do |key_group,g|
start_range = group_size * (key_group.first.to_i/group_size)
g["%d-%d" % [start_range, start_range group_size-1]] =
h.slice(*key_group)
end
#=> {"10-19"=>{"11"=>"a", "12"=>"b", "19"=>"c"},
# "20-29"=>{"28"=>"xx", "29"=>"xyz"},
# "40-49"=>{"42"=>"lll", "47"=>"mnx"}}
Обратите внимание, что:
e = b.each_with_object({})
#=> #<Enumerator: #<Enumerator:
# #<Enumerator::Generator:0x0000560a0fc12658>:each>:
# each_with_object({})>
e.to_a
#=> [[["11", "12", "19"], {}], [["28", "29"], {}], [["42", "47"], {}]]
Последний шаг начинается с того, что перечислитель e
генерирует значение и передает его в блок, после чего переменным блока присваиваются значения с использованием декомпозиции массива.
key_group,g = e.next
#=> [["11", "12", "19"], {}]
key_group
#=> ["11", "12", "19"]
g #=> {}
Затем выполняются вычисления блоков.
start_range = group_size * (key_group.first.to_i/group_size)
#=> 10 * (11/10) => 10
g["%d-%d" % [start_range, start_range group_size-1]] =
h.slice(*key_group)
#=> g["%d-%d" % [10, 10 10-1]] = h.slice("11", "12", "19")
#=> g["10-19"] = {"11"=>"a", "12"=>"b", "19"=>"c"}
#=> {"11"=>"a", "12"=>"b", "19"=>"c"}
Теперь,
g #=> {"10-19"=>{"11"=>"a", "12"=>"b", "19"=>"c"}}
Затем перечислитель e
генерирует другой элемент, передает его в блок, и присваиваются переменные блока.
key_group,g = e.next
#=> [["28", "29"], {"10-19"=>{"11"=>"a", "12"=>"b", "19"=>"c"}}]
key_group
#=> ["28", "29"]
g #=> {"10-19"=>{"11"=>"a", "12"=>"b", "19"=>"c"}}
Обратите внимание, что значение g
было обновлено. Вычисления блоков теперь выполняются, как и раньше, после чего:
g #=> {"10-19"=>{"11"=>"a", "12"=>"b", "19"=>"c"},
# "20-29"=>{"28"=>"xx", "29"=>"xyz"}}
Затем
key_group,g = e.next
#=> [["42", "47"], {"10-19"=>{"11"=>"a", "12"=>"b", "19"=>"c"},
# "20-29"=>{"28"=>"xx", "29"=>"xyz"}}]
key_group
#=> ["42", "47"]
g #=> {"10-19"=>{"11"=>"a", "12"=>"b", "19"=>"c"},
# "20-29"=>{"28"=>"xx", "29"=>"xyz"}}
После выполнения вычислений блока:
g #=> {"10-19"=>{"11"=>"a", "12"=>"b", "19"=>"c"},
# "20-29"=>{"28"=>"xx", "29"=>"xyz"},
# "40-49"=>{"42"=>"lll", "47"=>"mnx"}}
Затем возникает исключение:
key_group,g = e.next
#=> StopIteration (iteration reached an end)
вызывающий возврат перечислителя g
.
Комментарии:
1. Это привело к массиву хэшей, можете ли вы сделать это как хэши хэшей..
2. @code_aks, я только что заметил это и исправил. Спасибо.
3. Вы можете удалить
keys
и вычислить как(k1,_),(k2,_)
, затем удалить первыйmap
и добавитьto_h
кf
(вeach_with_object
), что позволяет избежать второй итерации ключей и дополнительного выброса,Array
вызванного map. Хотя оба решения требуютh
сортировки4. @engineersmnky, спасибо за предложение. Я удалил первый
map
, как вы и предлагали, но затем пошел другим путем, пытаясь сбалансировать эффективность и удобочитаемость.5. @CarySwoveland то же самое, то же самое. Я бы никогда не стал придираться к тому, что в данном случае сводится к стилистическим предпочтениям.
Ответ №2:
Поскольку ваш хэш уже отсортирован по ключам, такие вещи, как slice_when
предложенные @CarySwoveland, вероятно, имели бы преимущество в эффективности; однако, если бы хэш был или стал несортированным, следующие решения не были бы затронуты с точки зрения группировки.
Использование лямбда-выражения для группировки ключей:
def group_numeric_range(h, group_size)
groups = ->(n) do
g = n.to_i / group_size
"#{g * group_size 1}-#{g * group_size group_size}"
end
h.group_by do |k,_|
groups.(k)
end.transform_values(amp;:to_h)
end
Пример:
h = {"11"=>"a", "12"=>"b", "19"=>"c", "28"=>"xx", "29"=> "xyz",
"42"=>"lll", "47"=>"mnx"}
group_numeric_range(h,10)
#=> {"11-20"=>{"11"=>"a", "12"=>"b", "19"=>"c"}, "21-30"=>{"28"=>"xx", "29"=>"xyz"}, "41-50"=>{"42"=>"lll", "47"=>"mnx"}}
Альтернатива:
def group_numeric_range(h, group_size)
groups = ->(n) do
g = n.to_i / group_size
"#{g * group_size 1}-#{g * group_size group_size}"
end
h.each_with_object(Hash.new{|h,k| h[k] = {}}) do |(k,v),obj|
obj[groups.(k)].merge!(k=>v)
end
end
Обновить
Другим вариантом было бы создать Array
группы, а затем выбрать индекс для группировки (я также добавил вывод пустых диапазонов), например
def group_numeric_range(h, group_size)
groups = ((h.keys.max.to_i / group_size) 1).times.map do |g|
["#{g * group_size 1}-#{g * group_size group_size}",{}]
end
h.each_with_object(groups) do |(k,v),obj|
obj[k.to_i / group_size].last.merge!(k=>v)
end.to_h
end
h = {"11"=>"a", "12"=>"b", "19"=>"c", "28"=>"xx", "29"=> "xyz",
"42"=>"lll", "47"=>"mnx"}
group_numeric_range(h,10)
#=> {"1-10"=>{}, "11-20"=>{"11"=>"a", "12"=>"b", "19"=>"c"}, "21-30"=>{"28"=>"xx", "29"=>"xyz"}, "31-40"=>{}, "41-50"=>{"42"=>"lll", "47"=>"mnx"}}
Ответ №3:
Вот как я бы это сделал, но не уверен, что вы уже сделали.
Создание большого хэша:
hash = {}
1000.times do |x|
hash[x] = "hi!"
end
нарезка по диапазону:
hash.slice(*(1 .. 100))
=> # keys from 1 .. 100
создаем желаемый хэш:
def split_hash(range, hash)
end_result = {}
(hash.count / range).times do |x|
range_start = (range * x) 1
range_end = range_start range
end_result["#{range_start}-#{range_end}"] = hash.slice(*(range_start .. range_end)) # slice returns a hash which was desired. If you can convert to an array you gain range access as slice still iterates but is performative. if you are OK with an array: hash.to_a[range_start .. range_end]
end
end_result
end