代码之家  ›  专栏  ›  技术社区  ›  Seth Petry-Johnson

在Ruby中,是否有一个数组方法结合了select和map?

  •  82
  • Seth Petry-Johnson  · 技术社区  · 14 年前

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

    1. 通过转换运行匹配元素
    2. 以数组形式返回结果

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

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

    13 回复  |  直到 14 年前
        1
  •  117
  •   the Tin Man    8 年前

    我通常使用 map compact 加上我的选择标准作为后缀 if

    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] 
    
        2
  •  55
  •   the Tin Man    8 年前

    你可以用 reduce 为此,只需一次通过:

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

    换句话说,将状态初始化为您想要的状态(在本例中,填写一个空列表: [] ),然后始终确保返回该值,并对原始列表中的每个元素进行修改(在本例中,修改后的元素被推送到列表中)。

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

    就你而言:

    def example
      results = @lines.reduce([]) do |lines, line|
        lines.push( ...(line) ) if ...
        lines
      end
      return results.uniq.sort
    end
    
        3
  •  48
  •   SRack    5 年前

    红宝石2.7+

    现在有了!

    Ruby2.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 .

    希望对别人有用!

        4
  •  20
  •   henrebotha    8 年前

    另一种不同的方法是使用new(相对于这个问题) Enumerator::Lazy :

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

    这个 .lazy .select .map 在惰性枚举器上返回另一个惰性枚举器。只有一次你打电话 .uniq 它实际上是否强制枚举器并返回数组。所以实际上发生的是 .选择 .地图 调用被合并成一个-您只需迭代 @lines 两个都做一次 .选择 .地图

    我的直觉是亚当 reduce


    @lines.select.map 情况, select 返回一个数组,然后由 map ,再次返回数组。相比之下,延迟求值只创建一次数组。当初始集合对象很大时,这很有用。它还允许您使用无限枚举数-例如。 random_number_generator.lazy.select(&:odd?).take(10)

        5
  •  13
  •   the Tin Man    8 年前

    如果你有 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"]
    

    如果我们需要更复杂的逻辑,我们可以创建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***"]
    
        6
  •  9
  •   the Tin Man    8 年前

    我不确定有没有。这个 Enumerable module ,这增加了 select map

    你必须在两个街区内通过 select_and_transform 方法,这将有点不直观。

    很明显,你可以把它们连在一起,这样可读性更好:

    transformed_list = lines.select{|line| ...}.map{|line| ... }
    
        7
  •  3
  •   Sk. Irfan    7 年前

    简单回答:

    如果你有n个记录 select map 根据当时的情况

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

    紧凑是冲掉不必要的零的条件下,如果出来

        8
  •  2
  •   Daniel O'Hara    14 年前

    不,但你可以这样做:

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

    或者更好:

    lines.inject([]) { |all, line| all << line if check_some_property; all }
    
        9
  •  2
  •   the Tin Man    8 年前

    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
    
        10
  •  1
  •   Jörg W Mittag    14 年前
    def example
      @lines.select {|line| ... }.map {|line| ... }.uniq.sort
    end
    

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

    但在这种情况下不太可能,因为块的类型返回 select map 不匹配。这对这样的事情更有意义:

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

        11
  •  1
  •   Weston Ganger    7 年前

    Rearmed Ruby 其中我添加了 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]}
    
        12
  •  1
  •   Bibek Shrestha    7 年前

    如果不想创建两个不同的数组,可以使用 compact!

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

    有趣的是, 契约! 就地清除零。的返回值 契约! 如果有更改,则为同一数组;如果没有nil,则为nil。

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

        13
  •  0
  •   Jordan Michael Rushing    6 年前

    您的版本:

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

    我的版本:

    def example
      results = {}
      @lines.each{ |line| results[line] = true if ... }
      return results.keys.sort
    end
    

    这将进行1次迭代(排序除外),并具有保持唯一性的额外好处(如果您不关心uniq,那么只需将结果作为数组并 results.push(line) if ...

        14
  •  -1
  •   the Tin Man    8 年前

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

    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