在Ruby中,是否有一个Array方法结合了“select”和“map”?

我有一个包含一些string值的Ruby数组。 我需要:

  1. find匹配某个谓词的所有元素
  2. 通过转换运行匹配的元素
  3. 以数组forms返回结果

现在我的解决scheme如下所示:

def example matchingLines = @lines.select{ |line| ... } results = matchingLines.map{ |line| ... } return results.uniq.sort end 

是否有一个数组或枚举方法将select和map组合成一个逻辑语句?

我通常使用mapcompact在一起作为一个后缀, if我的select标准。 compact摆脱nils。

 jruby-1.5.0 > [1,1,1,2,3,4].map{|n| n*3 if n==1} => [3, 3, 3, nil, nil, nil] jruby-1.5.0 > [1,1,1,2,3,4].map{|n| n*3 if n==1}.compact => [3, 3, 3] 

你可以使用reduce ,这只需要一个pass:

 [1,1,1,2,3,4].reduce([]) { |a, n| a.push(n*3) if n==1; a } => [3, 3, 3] 

换句话说,初始化状态是你想要的(在我们的例子中是一个空的列表来填充: [] ),然后总是确保返回这个值,修改原始列表中的每个元素(在我们的例子中,修改元素推送到列表)。

这是最有效率的,因为它只能通过一个循环遍历列表( map + selectcompact需要两遍)。

在你的情况下:

 def example results = @lines.reduce([]) do |lines, line| lines.push( ...(line) ) if ... lines end return results.uniq.sort end 

另一个不同的方式来处理这个是使用新的(相对于这个问题) Enumerator::Lazy

 def example @lines.lazy .select { |line| line.property == requirement } .map { |line| transforming_method(line) } .uniq .sort end 

.lazy方法返回一个懒惰的枚举器。 在惰性枚举器上调用.select.map返回另一个惰性枚举器。 只有你调用.uniq它实际上强制枚举器并返回一个数组。 那么有效的做法是将你的.select.map调用合并为一个 – 你只需迭代@lines就可以同时执行.select.map

我的直觉是Adam的reduce方法会快一点,但是我认为这样更具可读性。


这样做的主要结果是没有为每个后续的方法调用创build中间数组对象。 在正常的@lines.select.map情况下, select返回一个数组,然后由map修改,再次返回一个数组。 相比之下,懒惰评估只创build一个数组一次。 当您的初始集合对象很大时,这非常有用。 它也授权你使用无限枚举器 – 例如random_number_generator.lazy.select(&:odd?).take(10)

我不确定是否有一个。 Enumerable模块 ,添加selectmap ,不显示一个。

你将被要求通过两个块到select_and_transform方法,这将是一个有点不直观的恕我直言。

显然,你可以把它们链接在一起,这样更加可读:

 transformed_list = lines.select{|line| ...}.map{|line| ... } 

如果你有一个select可以使用case运算符( === ), grep是一个好的select:

 p [1,2,'not_a_number',3].grep(Integer){|x| -x } #=> [-1, -2, -3] p ['1','2','not_a_number','3'].grep(/\D/, &:upcase) #=> ["NOT_A_NUMBER"] 

如果我们需要更复杂的逻辑,我们可以创buildlambdas:

 my_favourite_numbers = [1,4,6] is_a_favourite_number = -> x { my_favourite_numbers.include? x } make_awesome = -> x { "***#{x}***" } my_data = [1,2,3,4] p my_data.grep(is_a_favourite_number, &make_awesome) #=> ["***1***", "***4***"] 
 def example @lines.select {|line| ... }.map {|line| ... }.uniq.sort end 

在Ruby 1.9和1.8.7中,你也可以链接和包装迭代器,只需不传递一个块:

 enum.select.map {|bla| ... } 

但在这种情况下,这是不可能的,因为selectmap的块返回值的types不匹配。 这样做更有意义:

 enum.inject.with_index {|(acc, el), idx| ... } 

AFAICS,你可以做的最好的是第一个例子。

这是一个小例子:

 %w[ab 1 2 cd].map.select {|e| if /[0-9]/ =~ e then false else e.upcase end } # => ["a", "b", "c", "d"] %w[ab 1 2 cd].select.map {|e| if /[0-9]/ =~ e then false else e.upcase end } # => ["A", "B", false, false, "C", "D"] 

但是你真正想要的是["A", "B", "C", "D"]

我认为这种方式更具可读性,因为它将过滤条件和映射值分开,同时保持连接的动作不变:

 results = @lines.select { |line| line.should_include? }.map do |line| line.value_to_map end 

而且,在你的具体情况下,一起消除resultvariables:

 def example @lines.select { |line| line.should_include? }.map { |line| line.value_to_map }.uniq.sort end 

不,但你可以这样做:

 lines.map { |line| do_some_action if check_some_property }.reject(&:nil?) 

甚至更好:

 lines.inject([]) { |all, line| all << line if check_some_property; all } 

简单的答案:

如果您有n条logging,并且您想根据条件selectmap

 records.map { |record| record.attribute if condition }.compact 

在这里,属性是任何你想从logging和条件你可以把任何检查。

紧凑是冲洗如果条件出来的不必要的零

你应该尝试使用我已经添加了Enumerable#select_map方法的Enumerable#select_map 。 下面是一个例子:

 items = [{version: "1.1"}, {version: nil}, {version: false}] items.select_map{|x| x[:version]} #=> [{version: "1.1"}] # or without enumerable monkey patch Rearmed.select_map(items){|x| x[:version]} 

如果你不想创build两个不同的数组,你可以使用compact! 但要小心。

 array = [1,1,1,2,3,4] new_array = map{|n| n*3 if n==1} new_array.compact! 

有趣的是, compact! 做一个到位删除零。 compact!的返回值compact! 如果有更改,则是相同的数组,如果没有nil,则为零。

 array = [1,1,1,2,3,4] new_array = map{|n| n*3 if n==1}.tap { |array| array.compact! } 

将是一个class轮。

这是一个例子。 这与你的问题不一样,但可能是你想要的,或者可以给你的解决scheme提供线索:

 def example lines.each do |x| new_value = do_transform(x) if new_value == some_thing return new_value # here jump out example method directly. else next # continue next iterate. end end end