Как мне разобрать плоский хэш Ruby во вложенный хэш? - PullRequest
1 голос
/ 19 июня 2019

У меня есть следующий плоский хэш Ruby:

{ 
  "builder_rule_0_filter"=>"artist_name", 
  "builder_rule_0_operator"=>"contains", 
  "builder_rule_0_value_0"=>"New Found Glory", 

  "builder_rule_1_filter"=>"bpm", 
  "builder_rule_1_operator"=>"less", 
  "builder_rule_1_value_0"=>"150", 

  "builder_rule_2_filter"=>"days_ago", 
  "builder_rule_2_operator"=>"less", 
  "builder_rule_2_value_0"=>"40", 

  "builder_rule_3_filter"=>"release_date_start", 
  "builder_rule_3_operator"=>"between", 
  "builder_rule_3_value_0"=>"2019-01-01", 
  "builder_rule_3_value_1"=>"2019-12-31", 
}

Я бы хотел преобразовать его во вложенный хэш, который немного более организован / полезен:

{
  "0"=>
    {
      "filter"=>"artist_name",
      "operator"=>"contains",
      "values"=>
      {
        "0"=>"New Found Glory"
      }
    },
  "1"=>
    {
      "filter"=>"bpm",
      "operator"=>"less",
      "values"=>
      {
        "0"=>"150"
      }
    }
  "2"=>
    {
      "filter"=>"days_ago",
      "operator"=>"less",
      "values"=>
      {
        "0"=>"40"
      }
    }
  "3"=>
    {
      "filter"=>"release_date_start",
      "operator"=>"between",
      "values"=>
      {
        "0"=>"2019-01-01"
        "1"=>"2019-12-31"
      }
    }
}

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

Ответы [ 3 ]

5 голосов
/ 19 июня 2019

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

groups = input.
  group_by { |k,v| k.match(/builder_rule_(\d+)/)[1] }.
  transform_values(&:to_h)

на данный момент создание внутренних объектов проще, если вы просто используете некоторое жесткое кодирование для построения key-valls:

result = groups.each_with_object({}) do |(prefix, hash), memo|
  memo[prefix] = {
    "filter" => hash["builder_rule_#{prefix}_filter"],
    "operator" => hash["builder_rule_#{prefix}_operator"],
    "values" => hash.select do |key, val|
      key =~ /builder_rule_#{prefix}_value/
    end.sort_by { |key, val| key }.map { |(key, val)| val }
  }
end

Возможно, это путает, что означает .sort_by { |key, val| key }.map { |(key, val)| val }. Я могу разобрать это:

  • hash.select { |key, val| key =~ /builder_rule_#{prefix}_value/ } получает значения ключей, которые будут использоваться для массива "values". Возвращает хеш.
  • .sort_by { |key, val| key } превращает хеш в массив [key, val] кортежей, отсортированных по ключу. Это делается для того, чтобы значения отображались в правильном порядке.
  • .map { |(key, val)| val } превращает вложенный массив в одноуровневый массив, отбрасывая ключи

Вы также можете использовать sort_by(&:first).map(&:second), хотя вам нужна активная поддержка, чтобы использовать Array#second

2 голосов
/ 19 июня 2019

Предполагая, что глубина установлена ​​равной 3, а правила согласованы (как показано в примере), вы можете сделать это однопроходным преобразованием следующим образом:

h.each_with_object(Hash.new {|h,k| h[k]= {}}) do |(k,v),obj|
  new_k, k2, k3 = k[/(?<=builder_rule_).*/].split('_')
  k2 == 'value' ? (obj[new_k]["values"] ||= {}).merge!({k3 => v}) :  obj[new_k][k2] = v
end

Здесь мы создаем новый Hash где ключи первого уровня по умолчанию пустые Hash (h.each_with_object(Hash.new {|h,k| h[k]= {}}) do |(k,v),obj|).

Затем мы разделяем ключ на основе подчеркивания после 'builder_rule_' new_k, k2, k3 = k[/(?<=builder_rule_).*/].split('_')

Тогда, еслиКлюч второго уровня (k2) равен значению, нам нужно сделать его значениями и объединить ключ третьего уровня (k3) и значение в новый хеш, в противном случае мы присваиваем значение k2.k2 == 'value' ? (obj[new_k]["values"] ||= {}).merge!({k3 => v}) : obj[new_k][k2] = v

Это фактически вернет запрошенное требование, где values - это Hash с ключевыми индексами

{
  "0"=>{
      "filter"=>"artist_name",
      "operator"=>"contains",
      "values"=>
      {
        "0"=>"New Found Glory"
      }
    },
  "1"=>{
      "filter"=>"bpm",
      "operator"=>"less",
      "values"=>
      {
        "0"=>"150"
      }
    },
  "2"=>{
      "filter"=>"days_ago",
      "operator"=>"less",
      "values"=>
      {
        "0"=>"40"
      }
    },
  "3"=>{
      "filter"=>"release_date_start",
      "operator"=>"between",
      "values"=>
      {
        "0"=>"2019-01-01",
        "1"=>"2019-12-31"
      }
    }
}
1 голос
/ 19 июня 2019

Принятый ответ хорош, но выполняет немного больше работы, чем это строго необходимо.Я бы сделал всю работу с each_with_object:

input.each_with_object({}}) do |(key, val), hsh|
  _, num, name = *key.match(/^builder_rule_(\d+)_(.*)/)

  hsh[num] = {} unless hsh.key?(num)

  if name.start_with?(/value_\d/)
    hsh[num]["values"] = [] unless hsh[num].key?("values")
    hsh[num]["values"] << val
  else
    hsh[num][name] = val
  end
end
# => {
#      "0" => {
#        "filter" => "artist_name",
#        "operator" => "contains",
#        "values" => ["New Found Glory"]
#      },
#      "1" => {
#        "filter" => "bpm",
#        "operator" => "less",
#        "values" => ["150"]
#      },
#      "2" => {
#        "filter" => "days_ago",
#        "operator" => "less",
#        "values" => ["40"]
#      },
#      "3" => {
#        "filter" => "release_date_start",
#        "operator" => "between",
#        "values" => ["2019-01-01", "2019-12-31"]
#      }
#    }

См. Это в действии на repl.it: https://repl.it/@jrunning/GiddyEnragedLinks

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