【问题标题】:Sort Ruby Hash by order in array of keys按键数组中的顺序对 Ruby 哈希进行排序
【发布时间】:2016-05-24 05:53:41
【问题描述】:

我有一个哈希:

sample = { bar: 200, foo: 100, baz: 100 }

如何使用sort_order 中的键顺序对sample 进行排序:

sort_order = [:foo, :bar, :baz, :qux, :quux]

预期结果:

sample #=> { foo: 100, bar: 200, baz: 100 }

我能想到的只有

new_hash = {}
sort_order.each{|k| new_hash[k] = sample[k] unless sample[k].nil? }
sample = new_hash

一定有更好的方法。提示?

不应该存在没有值的键,即键的数量保持不变,Sort Hash Keys based on order of same keys in array 不是这种情况

【问题讨论】:

  • 你想要什么更好的方式?更短?性能更好?
  • 1.更短 2. 性能更好 按此顺序
  • @Amit 这与我的解决方案有何不同?
  • sample.sort_by { |k,_| sort_order.index(k) }.to_hsort_order.each_with_object({}) { |sym,h| h[sym] = sample[sym] if sample.key?(sym) }
  • sample 中是否存在 sort_order 中不存在的键?

标签: ruby hash


【解决方案1】:

使用键交集的函数式方法:

new_sample = (sort_order & sample.keys).map { |k| [k, sample[k]] }.to_h
#=> {:foo=>100, :bar=>200, :baz=>100}

正如@Stefan 所说,来自 ActiveSupport 的抽象 Hash#slice 几乎可以完成这项工作:

require 'active_support/core_ext/hash'
new_sample = sample.slice(*sort_order)
#=> {:foo=>100, :bar=>200, :baz=>100}

【讨论】:

  • slice 检查密钥是否存在(类似于Keith Bennett's answer),所以你可以写sample.slice(*sort_order)
【解决方案2】:

请看我的this answer:

sort_order = [:foo, :bar, :baz, :qux, :quux, :corge, :grault,
              :garply, :waldo, :fred, :plugh, :xyzzy, :thud]
sample = { bar: 200, foo: 100, baz: 100 }

sample.sort_by {|k, _| sort_order.index(k)}.to_h
=> {:foo=>100, :bar=>200, :baz=>100}

【讨论】:

  • 对于小输入来说不是问题,但请注意这是二次方的。
【解决方案3】:

下面的代码就是这样做的。请注意,我使用has_key? 是因为您希望输出哈希包含输入哈希中的所有键,即使它们的值为nil

#!/usr/bin/env ruby

def sorted_hash(input_hash, key_sort_order)
  new_hash = {}
  key_sort_order.each do |key|
    if input_hash.has_key?(key)
      new_hash[key] = input_hash[key]
    end
  end
  new_hash
end

sort_order = [:foo, :bar, :baz, :qux, :quux]
sample = { bar: 200, foo: 100, baz: 100 }

puts sorted_hash(sample, sort_order)
# Outputs: {:foo=>100, :bar=>200, :baz=>100}

一种简化是使用each_with_object:

def sorted_hash_two(input_hash, key_sort_order)
  key_sort_order.each_with_object({}) do |key, result_hash|
    if input_hash.has_key?(key)
      result_hash[key] = input_hash[key]
    end
  end
end

puts sorted_hash_two(sample, sort_order)
# Outputs: {:foo=>100, :bar=>200, :baz=>100}

我更喜欢 @tokland 的数组交集 (&) 的想法,因为它消除了对 if 条件的需求:

def sorted_hash_ewo_intersection(input_hash, key_sort_order)
  (key_sort_order & input_hash.keys).each_with_object({}) do |key, result_hash|
    result_hash[key] = input_hash[key]
  end
end # produces: {:foo=>100, :bar=>200, :baz=>100}

【讨论】:

  • each_with_object 看起来更简单,但是我看不出它比我的解决方案更好。
  • 正如我在答案中提到的,我认为使用 has_key? 而不是测试 nil 很重要。顺便说一句,我建议将其表达为一种方法;将有问题的逻辑与输入和输出分开;并且您的姓氏sample = new_hash 是不必要的。
  • 我刚刚添加了@tokland 使用数组交集(使用&)但使用each_with_object 的方法,最好使用这种方法。
  • 注意&确实是设置交集;如果有重复项,那么这些重复项将被删除,例如:[1] & [1,1] => [1] 和 [1,1] & [1] => [1]。在这种情况下,两个数组都保证没有重复,因为哈希键总是唯一的,但在其他情况下需要注意。
【解决方案4】:

这里还有另一种方法:

(sort_order & sample.keys).zip([nil]).to_h.merge(sample)
#=> {:foo=>100, :bar=>200, :baz=>100}

说明:

首先,我们创建一个仅包含正确顺序的所需键的哈希。

(sort_order & sample.keys).zip([nil]).to_h
#=> {:foo=>nil, :bar=>nil, :baz=>nil}

然后,我们将此哈希与sample 合并以从sample 中获取值。

【讨论】:

  • 对不起,请忘记我已删除的 cmets。
猜你喜欢
  • 1970-01-01
  • 2015-02-07
  • 2011-12-09
  • 2011-10-25
  • 2014-03-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多