99

为了做相当于 Python 列表推导,我正在做以下事情:

some_array.select{|x| x % 2 == 0 }.collect{|x| x * 3}

有没有更好的方法来做到这一点......也许用一个方法调用?

4

17 回答 17

94

怎么样:

some_array.map {|x| x % 2 == 0 ? x * 3 : nil}.compact

稍微干净一点,至少在我看来,根据快速基准测试,比你的版本快 15%...

于 2008-11-22T01:39:59.730 回答
56

如果你真的想要,你可以像这样创建一个 Array#comprehend 方法:

class Array
  def comprehend(&block)
    return self if block.nil?
    self.collect(&block).compact
  end
end

some_array = [1, 2, 3, 4, 5, 6]
new_array = some_array.comprehend {|x| x * 3 if x % 2 == 0}
puts new_array

印刷:

6
12
18

我可能会按照你的方式去做。

于 2008-11-21T22:47:16.003 回答
31

我做了一个快速的基准测试,比较了这三种选择,map-compact似乎确实是最好的选择。

性能测试(导轨)

require 'test_helper'
require 'performance_test_help'

class ListComprehensionTest < ActionController::PerformanceTest

  TEST_ARRAY = (1..100).to_a

  def test_map_compact
    1000.times do
      TEST_ARRAY.map{|x| x % 2 == 0 ? x * 3 : nil}.compact
    end
  end

  def test_select_map
    1000.times do
      TEST_ARRAY.select{|x| x % 2 == 0 }.map{|x| x * 3}
    end
  end

  def test_inject
    1000.times do
      TEST_ARRAY.inject([]) {|all, x| all << x*3 if x % 2 == 0; all }
    end
  end

end

结果

/usr/bin/ruby1.8 -I"lib:test" "/usr/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake/rake_test_loader.rb" "test/performance/list_comprehension_test.rb" -- --benchmark
Loaded suite /usr/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake/rake_test_loader
Started
ListComprehensionTest#test_inject (1230 ms warmup)
           wall_time: 1221 ms
              memory: 0.00 KB
             objects: 0
             gc_runs: 0
             gc_time: 0 ms
.ListComprehensionTest#test_map_compact (860 ms warmup)
           wall_time: 855 ms
              memory: 0.00 KB
             objects: 0
             gc_runs: 0
             gc_time: 0 ms
.ListComprehensionTest#test_select_map (961 ms warmup)
           wall_time: 955 ms
              memory: 0.00 KB
             objects: 0
             gc_runs: 0
             gc_time: 0 ms
.
Finished in 66.683039 seconds.

15 tests, 0 assertions, 0 failures, 0 errors
于 2011-02-18T20:13:35.490 回答
12

在这个线程中,Ruby 程序员似乎对列表理解是什么感到有些困惑。每个响应都假定一些预先存在的数组进行转换。但是列表推导的强大之处在于使用以下语法动态创建的数组:

squares = [x**2 for x in range(10)]

以下将是 Ruby 中的模拟(此线程中唯一合适的答案,AFAIC):

a = Array.new(4).map{rand(2**49..2**50)} 

在上述情况下,我正在创建一个随机整数数组,但该块可以包含任何内容。但这将是一个 Ruby 列表理解。

于 2012-03-26T23:26:47.297 回答
11

我与 Rein Henrichs 讨论了这个话题,他告诉我性能最好的解决方案是

map { ... }.compact

这很有意义,因为它避免了构建中间数组,就像不可变的用法一样Enumerable#inject,它避免了增长数组,这会导致分配。除非您的集合可以包含 nil 元素,否则它与其他任何一个一样通用。

我没有和这个比较过

select {...}.map{...}

可能 Ruby 的 C 实现Enumerable#select也非常好。

于 2010-05-04T01:51:24.893 回答
9

可以在每个实现中运行并在 O(n) 而不是 O(2n) 时间内运行的替代解决方案是:

some_array.inject([]){|res,x| x % 2 == 0 ? res << 3*x : res}
于 2010-05-02T16:49:30.427 回答
8

我刚刚将comprehend gem发布到 RubyGems,它可以让你这样做:

require 'comprehend'

some_array.comprehend{ |x| x * 3 if x % 2 == 0 }

它是用 C 语言编写的;数组只遍历一次。

于 2013-02-15T19:23:01.337 回答
7

Enumerable 有一个grep方法,其第一个参数可以是谓词 proc,其可选的第二个参数是映射函数;所以以下工作:

some_array.grep(proc {|x| x % 2 == 0}) {|x| x*3}

这不像其他几个建议那样可读(我喜欢 anoiaque 的 simpleselect.map或 histocrat 的 comprehend gem),但它的优点是它已经是标准库的一部分,并且是单通道的,不涉及创建临时中间数组,并且不需要像nilcompact-using 建议中使用的越界值。

于 2013-10-02T02:10:37.050 回答
4
[1, 2, 3, 4, 5, 6].collect{|x| x * 3 if x % 2 == 0}.compact
=> [6, 12, 18]

这对我行得通。它也很干净。是的,它与 相同map,但我认为collect使代码更易于理解。


select(&:even?).map()

实际上看起来更好,在下面看到它之后。

于 2010-05-03T16:37:45.280 回答
4

这更简洁:

[1,2,3,4,5,6].select(&:even?).map{|x| x*3}
于 2010-05-03T22:09:20.260 回答
2

就像 Pedro 提到的那样,您可以将链式调用Enumerable#select和融合在一起Enumerable#map,避免遍历选定的元素。这是真的,因为Enumerable#select它是 fold 或 的特化inject。我在 Ruby subreddit 上发布了对该主题的仓促介绍。

手动融合数组转换可能很乏味,所以也许有人可以使用 Robert Gamble 的comprehend实现来使这个select/map模式更漂亮。

于 2010-05-03T18:32:39.933 回答
2

像这样的东西:

def lazy(collection, &blk)
   collection.map{|x| blk.call(x)}.compact
end

称它为:

lazy (1..6){|x| x * 3 if x.even?}

返回:

=> [6, 12, 18]
于 2013-04-17T04:41:43.563 回答
2

Ruby 2.7 引入了filter_map它几乎可以实现您想要的(地图 + 紧凑):

some_array.filter_map { |x| x * 3 if x % 2 == 0 }

你可以在这里阅读更多关于它的信息。

于 2020-07-07T16:13:08.430 回答
1

另一种解决方案,但可能不是最好的

some_array.flat_map {|x| x % 2 == 0 ? [x * 3] : [] }

或者

some_array.each_with_object([]) {|x, list| x % 2 == 0 ? list.push(x * 3) : nil }
于 2018-10-30T16:27:42.980 回答
0

这是解决此问题的一种方法:

c = -> x do $*.clear             
  if x['if'] && x[0] != 'f' .  
    y = x[0...x.index('for')]    
    x = x[x.index('for')..-1]
    (x.insert(x.index(x.split[3]) + x.split[3].length, " do $* << #{y}")
    x.insert(x.length, "end; $*")
    eval(x)
    $*)
  elsif x['if'] && x[0] == 'f'
    (x.insert(x.index(x.split[3]) + x.split[3].length, " do $* << x")
    x.insert(x.length, "end; $*")
    eval(x)
    $*)
  elsif !x['if'] && x[0] != 'f'
    y = x[0...x.index('for')]
    x = x[x.index('for')..-1]
    (x.insert(x.index(x.split[3]) + x.split[3].length, " do $* << #{y}")
    x.insert(x.length, "end; $*")
    eval(x)
    $*)
  else
    eval(x.split[3]).to_a
  end
end 

所以基本上我们将字符串转换为正确的 ruby​​ 语法 for loop 然后我们可以在字符串中使用 python 语法来执行:

c['for x in 1..10']
c['for x in 1..10 if x.even?']
c['x**2 for x in 1..10 if x.even?']
c['x**2 for x in 1..10']

# [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# [2, 4, 6, 8, 10]
# [4, 16, 36, 64, 100]
# [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

或者,如果您不喜欢字符串的外观或必须使用 lambda,我们可以放弃尝试镜像 python 语法并执行以下操作:

S = [for x in 0...9 do $* << x*2 if x.even? end, $*][1]
# [0, 4, 8, 12, 16]
于 2019-09-06T09:26:58.707 回答
-1

https://rubygems.org/gems/ruby_list_comprehension

我的 Ruby List Comprehension gem 的无耻插件,允许惯用的 Ruby 列表理解

$l[for x in 1..10 do x + 2 end] #=> [3, 4, 5 ...]
于 2019-12-24T16:35:23.950 回答
-3

我认为最能理解列表的方式如下:

some_array.select{ |x| x * 3 if x % 2 == 0 }

由于 Ruby 允许我们将条件放在表达式之后,我们得到的语法类似于 Python 版本的列表推导。此外,由于该select方法不包含任何等同于 的内容false,所有 nil 值都将从结果列表中删除,并且不需要调用 compact ,如果我们使用maporcollect代替。

于 2009-10-24T01:51:18.387 回答