提问人:sb813322 提问时间:7/19/2023 最后编辑:sb813322 更新时间:7/19/2023 访问量:65
如何在 Ruby 中高效地链接数组方法
How do you efficiently chain array methods in Ruby
问:
因此,假设我有这个 MWE 代码:
# see below for full code - not important here
class RealEstate; attr_accessor :name; attr_accessor :living_space; attr_accessor :devices; def initialize(name, living_space, devices); @name = name; @living_space = living_space; @devices = devices; end; end
real_estates = [ RealEstate.new("ceiling", 30, [1]), # name, living_space, devices
RealEstate.new("1st floor", 50, [2,3]),
RealEstate.new("Ground floor", 70, [4,5]) ]
(A) 现在我想使用数组方法,尤其是椒盐卷饼冒号,例如:
real_estates.map(&:living_space).inject(:+) # get the sum of all the available living space
real_estates.map(&:devices).map!(&:first) # get the first device of each floor
(B) 在我的理解中,这似乎是低效的。数组被处理两次(或多次),这在一个巨大的实际示例中具有意义。但是,我可以将每个内容写在自己的(单个)循环中:
real_estate.inject(0) do |sum, o|
sum + o.living_space
end
real_estate.map {|o| o.devices.first}
我真的更喜欢像 A 块而不是 B 块那样的语法,但 YMMV。我知道filter_map
或flat_map
,在某些情况下已经有所帮助,据称将性能提高了 4.5 倍左右
特别是,当这些语句做很多事情并且变得很大时,(菊花?)将它们链接在一起似乎是一种使代码可读的模式。参考:方法链(惯用语):“火车残骸是干净的代码”
最后,我的问题:如何防止在同一数组上出现中间结果(数组)和多次迭代?或者:如何有效地对数组方法进行链接?
Rails 在这里适用,但我认为纯红宝石也可能有一个变体。我想象这样的事情:
real_estates.map_inject(&:living_space,:+) # tbh you would need a combination for each of map, select, reject, each, etc.
real_estates.map(&:devices.first)
real_estates.map([&:devices,&:first])
我不仅使用 map 和 inject,还使用 filter、uniq、select、reject(所有 Enumerable)、flatten(Array)等,也经常轰轰烈烈
整个 MWE 类代码:
class RealEstate
attr_accessor :name
attr_accessor :living_space
attr_accessor :devices
def initialize(name, living_space, devices)
@name = name
@living_space = living_space
@devices = devices
end
end
答:
1赞
spickermann
7/19/2023
#1
我建议在你的类中添加一个辅助方法:
class RealEstate
attr_accessor :name, :living_space, :devices
def initialize(name, living_space, devices)
@name = name
@living_space = living_space
@devices = devices
end
def first_device
devices.first
end
end
然后,您可以使用以下方法:
real_estates.sum(&:living_space) # using `sum` as Sergio Tulentsev suggested
real_estates.map(&:first_device) # using the helper method for readability
评论
0赞
sb813322
7/19/2023
谢谢你的回答。在我的实际应用程序中,您不会有确切的设备数组和调用,而是通过多个不同的类进行链接。但我会研究这个想法,也许我可以在我的模型中利用一些委托!另一个常见的情况是 - 我应该把它添加到问题中吗?.first
.reject(&:nil).map(...)
4赞
spickermann
7/19/2023
当你不分享你的实际问题而只是简化版本时,很难给出很好的答案......并可替换为.reject(&:nil).map(...)
filter_map { |x| x&.do_something }
0赞
sb813322
8/11/2023
本文给出了一个容易分解的例子。它很容易理解,基本上以(摘录)和 .虽然这很容易阅读,但它在同一个数组中迭代了三次,我预计它会产生很多开销。rock_hits = [["Queen", "Bohemian Rhapsody"],["Queen", "Don't Stop Me Now"]]
rock_hits.group_by(&:shift).transform_values(&:flatten)
评论
real_estates.map(&:living_space).inject(:+)
可以替换为 。但对于你的其余问题:如果你的数组很大,迭代它们对性能有明显的影响,我建议 a) 重构你的逻辑以使用较小的数组(批处理/切片等),b) 不要链接这样的方法并手动滚动你的循环或 c) 放弃 ruby 以获得更快的语言。real_estates.sum(&:living_space)
Enumerator::Lazy
,它“允许对长序列或无限序列进行惯用计算,以及在不构造中间数组的情况下链接计算”#to_proc
。然而,在我看来,这种方法应该只是句法糖。