只对某些属性使用group_by



我有一个具有许多属性的对象数组roads。我想做的是找到哪个district_code(一个属性)有多个state(另一个属性)

为简单起见省略其他属性-例如:

roads = [['1004', 'VIC'], ['1004', 'BRI'], ['1011', 'VIC'], ['1010', 'ACT'], ['1000', 'ACT'], ['1019', 'VIC'], ['1004', 'VIC']]

如果我使用roads.group_by { |ro| ro[0] },我得到的结果:

=> {"1004"=>[["1004", "VIC"], ["1004", "BRI"], ["1004", "VIC"]], "1011"=>[["1011", "VIC"]], "1010"=>[["1010", "ACT"]], "1000"=>[["1000", "ACT"]], "1019"=>[["1019", "VIC"]]}

我想要的是哈希值只显示state有多个唯一值的地方,像这样:

=> {"1004"=>["VIC", "BRI"]}

关于如何根据值的数量或值中的特定属性进行group_by或映射,有什么想法吗?

谢谢!

让我添加一个额外的元素roads这样所需的散列将有多个关键。

roads = [['1004', 'VIC'], ['1004', 'BRI'], ['1011', 'VIC'],
['1010', 'ACT'], ['1000', 'ACT'], ['1019', 'VIC'],
['1004', 'VIC'], ['1000', 'VIC]]
require 'set'
roads.each_with_object({}) do |(dc,st),h|     
h[dc] = Set.new unless h.key?(dc)
h[dc] << st
end.select {|k,v| v.size > 1}
.transform_values(&:to_a)
#=> {"1004"=>["VIC", "BRI"], "1000"=>["ACT", "VIC"]}

参见hash# select和hash# transform_values。

让我们看一下这些步骤。

f = roads.each_with_object({}) do |(dc,st),h|     
h[dc] = Set.new unless h.key?(dc)
h[dc] << st
end
#=> {"1004"=>#<Set: {"VIC", "BRI"}>, "1011"=>#<Set: {"VIC"}>,
#    "1010"=>#<Set: {"ACT"}>, "1000"=>#<Set: {"ACT", "VIC"}>,
#    "1019"=>#<Set: {"VIC"}>}

将块变量写入|(dc,st),h|使用了数组分解。这是一项强大的技术,可用于许多应用。

这里,假设我们写:

enum = roads.each_with_object({})
#=> #<Enumerator: [["1004", "VIC"], ["1004", "BRI"],...,
#                  ["1000", "VIC"]]:each_with_object({})>

当生成第一个元素并传递给块时,执行以下命令为块变量赋值。

(dc,st),h = enum.next
#=> [["1004", "VIC"], {}]

导致:

dc #=> "1004"
st #=>"VIC"
h  #=> {}

看到枚举器#。本文是提供数组分解更全面解释的几篇文章之一。

在继续,

g = f.select {|k,v| v.size > 1}
#=> {"1004"=>#<Set: {"VIC", "BRI"}>, "1000"=>#<Set: {"ACT", "VIC"}>}
g.transform_values(&:to_a)
#=> {"1004"=>["VIC", "BRI"], "1000"=>["ACT", "VIC"]}

计算f有两种常见的变体。第一个是

f = roads.each_with_object({}) do |(dc,st),h|
(h[dc] ||= Set.new) << st
end

h[dc] ||= Set.new扩展为h[dc] = h[dc] || Set.new。如果h有一个密钥dc,它就变成了h[dc] = h[dc];否则变成h[dc] = nil || Set.new = Set.new。返回s集合,然后执行s << st。只要h没有等于nil的值,此操作就有效。

第二个变体是

roads.each_with_object(Hash.new {|h,k| h[k]=Set.new}) do |(dc,st),h|
h[dc] << st
end

这使用了Hash::new的形式,它接受一个块,不带参数。如果定义了h

h = Hash.new {|h,k| h[k]=Set.new}

则,可能在键值对添加之后,如果h没有键k

h[k] = Set.new

执行。在这种情况下,当遇到h[dc] << st,而h没有dc键时,h[k] = Set.newh[dc] << st之前执行。


初始解的变体如下:

roads.each_with_object(Hash.new {|h,k| h[k]=[]}) {|(dc,st),h| h[dc] << st}
.transform_values(&:uniq)
.select {|k,v| v.size > 1}

输入

roads = [['1004', 'VIC'], ['1004', 'BRI'], ['1011', 'VIC'], ['1010', 'ACT'], ['1000', 'ACT'], ['1019', 'VIC'], ['1004', 'VIC']]

代码
p Hash[roads.group_by(&:first)
.transform_values(&:uniq)
.filter_map { |k, v| [k, v.map(&:last)] if v.length > 1 }]

输出
{"1004"=>["VIC", "BRI"]}

如果你已经可以得到:

{"1004"=>[["1004", "VIC"], ["1004", "BRI"], ["1004", "VIC"]], "1011"=>[["1011", "VIC"]], "1010"=>[["1010", "ACT"]], "1000"=>[["1000", "ACT"]], "1019"=>[["1019", "VIC"]]}

:

roads.group_by { |ro| ro[0] }

那么您只需要选择长度大于1的条目。

roads.group_by { |ro| ro[0] }.select { |k, v| v.length > 1 }

得到:

{"1004"=>[["1004", "VIC"], ["1004", "BRI"], ["1004", "VIC"]]}

然后我们可以把它映射到名字。可以是一行,但为了演示而分开。

roads.group_by { |r| r[0] }                  
.select { |k, v| v.length > 1 }         
.map { |k, v| [k, v.map { |x| x[1] }] } 
.to_h

结果是:

{"1004"=>["VIC", "BRI", "VIC"]}

最新更新