In Ruby, is there an Array method that combines 'select' and 'map'?

后端 未结 14 797
盖世英雄少女心
盖世英雄少女心 2020-12-07 18:34

I have a Ruby array containing some string values. I need to:

  1. Find all elements that match some predicate
  2. Run the matching elements through a transfo
相关标签:
14条回答
  • 2020-12-07 19:18

    Another different way of approaching this is using the new (relative to this question) Enumerator::Lazy:

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

    The .lazy method returns a lazy enumerator. Calling .select or .map on a lazy enumerator returns another lazy enumerator. Only once you call .uniq does it actually force the enumerator and return an array. So what effectively happens is your .select and .map calls are combined into one - you only iterate over @lines once to do both .select and .map.

    My instinct is that Adam's reduce method will be a little faster, but I think this is far more readable.


    The primary consequence of this is that no intermediate array objects are created for each subsequent method call. In a normal @lines.select.map situation, select returns an array which is then modified by map, again returning an array. By comparison, the lazy evaluation only creates an array once. This is useful when your initial collection object is large. It also empowers you to work with infinite enumerators - e.g. random_number_generator.lazy.select(&:odd?).take(10).

    0 讨论(0)
  • 2020-12-07 19:19

    I'm not sure there is one. The Enumerable module, which adds select and map, doesn't show one.

    You'd be required to pass in two blocks to the select_and_transform method, which would be a bit unintuitive IMHO.

    Obviously, you could just chain them together, which is more readable:

    transformed_list = lines.select{|line| ...}.map{|line| ... }
    
    0 讨论(0)
  • 2020-12-07 19:19

    Simple Answer:

    If you have n records, and you want to select and map based on condition then

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

    Here, attribute is whatever you want from the record and condition you can put any check.

    compact is to flush the unnecessary nil's which came out of that if condition

    0 讨论(0)
  • 2020-12-07 19:20

    You can use reduce for this, which requires only one pass:

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

    In other words, initialize the state to be what you want (in our case, an empty list to fill: []), then always make sure to return this value with modifications for each element in the original list (in our case, the modified element pushed to the list).

    This is the most efficient since it only loops over the list with one pass (map + select or compact requires two passes).

    In your case:

    def example
      results = @lines.reduce([]) do |lines, line|
        lines.push( ...(line) ) if ...
        lines
      end
      return results.uniq.sort
    end
    
    0 讨论(0)
  • 2020-12-07 19:27

    I usually use map and compact together along with my selection criteria as a postfix if. compact gets rid of the 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] 
    
    0 讨论(0)
  • 2020-12-07 19:27

    I think that this way is more readable, because splits the filter conditions and mapped value while remaining clear that the actions are connected:

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

    And, in your specific case, eliminate the result variable all together:

    def example
      @lines.select { |line|
        line.should_include?
      }.map { |line|
        line.value_to_map
      }.uniq.sort
    end
    
    0 讨论(0)
提交回复
热议问题