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

时间:2010-07-30 12:48:19

标签: ruby

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

  1. 查找与某个谓词匹配的所有元素
  2. 通过转换运行匹配元素
  3. 将结果作为数组返回
  4. 现在我的解决方案如下:

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

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

13 个答案:

答案 0 :(得分:98)

我通常将mapcompact以及我的选择标准一起用作后缀ifcompact摆脱了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] 

答案 1 :(得分:51)

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

[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

答案 2 :(得分:15)

另一种接近此方法的不同方法是使用新的(相对于此问题)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方法会更快一点,但我认为这更具可读性。

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

答案 3 :(得分:10)

如果您有select可以使用case运算符(===),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"]

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

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***"]

答案 4 :(得分:8)

我不确定有没有。添加selectmap的{​​{3}}未显示一个。{3}}。

你需要传递两个块到select_and_transform方法,这有点不直观恕我直言。

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

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

答案 5 :(得分:2)

不,但你可以这样做:

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

甚至更好:

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

答案 6 :(得分:2)

我认为这种方式更具可读性,因为拆分过滤条件和映射值,同时清楚表明操作已连接:

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

并且,在您的具体情况下,一起消除result变量:

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

答案 7 :(得分:1)

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

在Ruby 1.9和1.8.7中,您也可以通过简单地不将块传递给它来链接和包装迭代器:

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

但在这种情况下实际上并不可能,因为selectmap的块返回值的类型不匹配。对于这样的事情更有意义:

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

AFAICS,你能做的最好的就是第一个例子。

这是一个小例子:

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

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

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

答案 8 :(得分:1)

简单回答:

如果您有n条记录,并且您希望根据条件selectmap

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

在这里,属性是您想要的记录和条件,您可以进行任何检查。

compact是为了清除那些因条件

而产生的不必要的nil

答案 9 :(得分:1)

您应该尝试使用我已添加方法Enumerable#select_map的库Rearmed Ruby。下面是一个例子:

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]}

答案 10 :(得分:1)

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]

这里是good read on the subject

希望对某人有用!

答案 11 :(得分:0)

如果您不想创建两个不同的数组,可以使用compact!但请注意它。

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

有趣的是,compact!会删除nil。如果有更改,compact!的返回值是相同的数组,但如果没有nils则返回nil。

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

将是一个班轮。

答案 12 :(得分:-1)

这是一个例子。它与您的问题不同,但可能是您想要的,或者可以为您的解决方案提供线索:

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