Нарезать на куски из упорядоченного хэша в ruby

#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