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

Set*_*son 88 ruby

我有一个包含一些字符串值的Ruby数组.我需要:

  1. 找到与某个谓词匹配的所有元素
  2. 通过转换运行匹配元素
  3. 将结果作为数组返回

现在我的解决方案看起来像这样:

def example
  matchingLines = @lines.select{ |line| ... }
  results = matchingLines.map{ |line| ... }
  return results.uniq.sort
end
Run Code Online (Sandbox Code Playgroud)

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

Jed*_*der 103

我通常使用mapcompact我的选择标准一起作为后缀if.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] 
Run Code Online (Sandbox Code Playgroud)

  • 我不确定`map` +`compact`是否真的比`inject`表现更好,并将我的基准测试结果发布到相关主题:http://stackoverflow.com/questions/310426/list-comprehension-in-ruby/5046057 #5046057 (4认同)
  • 这将删除所有nils,包括原始nils和那些不符合标准的nils.所以小心 (3认同)

Ada*_*erg 50

你可以使用reduce它,只需要一次传递:

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

换句话说,将状态初始化为您想要的(在我们的例子中,填充空列表:) [],然后始终确保返回此值并修改原始列表中的每个元素(在我们的示例中,修改后的元素)推到列表中).

这是最有效的,因为它只通过一次传递(map+ selectcompact需要两次传递)在列表上循环.

在你的情况下:

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

  • 不是'each_with_object`更有意义吗?您不必在块的每次迭代结束时返回数组.你可以简单地做`my_array.each_with_object([]){| i,a | 一个<< i if i.condition}`. (14认同)

hen*_*tha 17

接近这个的另一种不同方式是使用新的(相对于这个问题)Enumerator::Lazy:

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

.lazy方法返回一个惰性枚举器.调用.select.map在惰性枚举器上返回另一个惰性枚举器.只有在调用.uniq它之后才会强制执行枚举器并返回一个数组.所以真正发生的事是你.select.map呼叫合并成一个-你只遍历@lines一次做两.select.map.

我的直觉是亚当的reduce方法会快一点,但我认为这更具可读性.


这样做的主要结果是没有为每个后续方法调用创建中间数组对象.在正常@lines.select.map情况下,select返回一个数组,然后map再次返回一个数组.相比之下,延迟评估仅创建一次数组.当您的初始集合对象很大时,这很有用.它还使您能够使用无限的枚举器 - 例如random_number_generator.lazy.select(&:odd?).take(10).

  • 给每个人自己.通过我的解决方案,我可以浏览方法名称,并立即知道我将转换输入数据的子集,使其唯一,并对其进行排序."减少"作为"做一切"的转变对我来说总是让人感觉很混乱. (4认同)
  • “更易读”。不同意,没有冒犯。 (2认同)
  • @henrebotha:如果我误解了您的意思,请原谅我,但这是非常重要的一点:说“您只对`@ lines`进行一次遍历以同时执行'.select'和' `.map`”。使用`.lazy`并不意味着对惰性枚举器的链接操作将“折叠”成一个迭代。这是对集合的惰性评估和链接操作的普遍误解。(在第一个示例中,您可以通过在select和map块的开头添加puts语句来进行测试。您会发现它们打印的行数相同) (2认同)
  • *时间*复杂度是相同的,因为惰性/急切版本都进行了相同数量的操作,但*空间*复杂度更低,因为惰性版本只初始化了一个额外的数组。 (2认同)
  • 我同意“你只在@lines 上迭代一次才能同时执行 .select 和 .map”的说法是不正确的。`#lazy`、`#select` 和 `#map` 都在此处创建枚举器,因此由于额外的惰性枚举器,实际上使用此解决方案执行了更多枚举。答案显示 `reduce` 预计会更快,但我预计这也比大多数/所有其他答案慢。不过,我确实喜欢这个答案的其余部分,并且它向我介绍了(让我偶然发现)ruby 中的懒惰枚举。谢谢! (2认同)

hir*_*lau 10

如果你有一个select可以使用caseoperator(===),grep是一个很好的选择:

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"]
Run Code Online (Sandbox Code Playgroud)

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

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***"]
Run Code Online (Sandbox Code Playgroud)


SRa*_*ack 10

Ruby 2.7以上

现在有!

Ruby 2.7正是filter_map为此目的而引入的。它是惯用语言和高性能,我希望它很快会成为标准。

例如:

numbers = [1, 2, 5, 8, 10, 13]
enum.filter_map { |i| i * 2 if i.even? }
# => [4, 16, 20]
Run Code Online (Sandbox Code Playgroud)

这是关于这个主题好读物

希望对某人有用!

  • 无论我升级多少次,下一版本总会有一个很酷的功能。 (4认同)
  • 这绝对应该是 2021 年 Ruby 2.7 公认的答案!工作完美。 (4认同)

Gis*_*shu 8

我不确定有没有.该可枚举模块,增加了selectmap,不显示的.

你需要传递两个块到该select_and_transform方法,这将是一个有点不直观的恕我直言.

显然,您可以将它们链接在一起,这更具可读性:

transformed_list = lines.select{|line| ...}.map{|line| ... }
Run Code Online (Sandbox Code Playgroud)