Понимание списка в Ruby - PullRequest
       67

Понимание списка в Ruby

85 голосов
/ 22 ноября 2008

Чтобы сделать эквивалентность понимания списка Python, я делаю следующее:

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

Есть ли лучший способ сделать это ... возможно, с помощью одного вызова метода?

Ответы [ 14 ]

83 голосов
/ 22 ноября 2008

Как насчет:

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

Чуть чище, по крайней мере, на мой вкус, и, согласно быстрому тесту, примерно на 15% быстрее, чем ваша версия ...

53 голосов
/ 22 ноября 2008

Если вы действительно хотите, вы можете создать метод 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

Я бы, наверное, просто сделал это так, как ты.

27 голосов
/ 18 февраля 2011

Я сделал быстрый тест, сравнивая три варианта, и map-compact действительно кажется лучшим вариантом.

Тест производительности (Rails)

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
11 голосов
/ 04 мая 2010

Я обсуждал эту тему с Рейном Хенриксом, который говорит мне, что самое эффективное решение -

map { ... }.compact`

Это имеет смысл, поскольку позволяет избежать создания промежуточных массивов, как при неизменном использовании Enumerable#inject, и избегает увеличения массива, что приводит к выделению. Он такой же общий, как и любой другой, если ваша коллекция не может содержать ноль элементов.

Я не сравнивал это с

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

Вполне возможно, что реализация Ruby на C Enumerable#select также очень хороша.

10 голосов
/ 27 марта 2012

Кажется, что некоторые программисты Ruby в этой теме не понимают, что такое понимание списков. Каждый ответ предполагает преобразование какого-либо существующего массива. Но сила понимания списка заключается в массиве, созданном на лету со следующим синтаксисом:

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

Следующее будет аналогом в Ruby (единственный адекватный ответ в этой теме, AFAIC):

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

В приведенном выше случае я создаю массив случайных целых чисел, но блок может содержать что угодно. Но это будет понимание списка Ruby.

9 голосов
/ 02 мая 2010

Альтернативное решение, которое будет работать в каждой реализации и работать в O (n) вместо O (2n) времени:

some_array.inject([]){|res,x| x % 2 == 0 ? res << 3*x : res}
8 голосов
/ 15 февраля 2013

Я только что опубликовал постигнуть гем в RubyGems, что позволяет вам сделать это:

require 'comprehend'

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

Это написано на C; массив только один раз пройден.

7 голосов
/ 02 октября 2013

Enumerable имеет метод grep, первым аргументом которого может быть предикат proc, а необязательным вторым аргументом является функция отображения; так работает следующее:

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

Это не так читабельно, как пара других предложений (мне нравится простой select.map от anoiaque или постижимый камень гистократа), но его сильные стороны в том, что он уже является частью стандартной библиотеки, однопроходным и t включает создание временных промежуточных массивов и не требует значения за пределами допустимого диапазона, такого как nil, используемого в compact -использующих предложениях.

4 голосов
/ 04 мая 2010

Это более кратко:

[1,2,3,4,5,6].select(&:even?).map{|x| x*3}
4 голосов
/ 03 мая 2010
[1, 2, 3, 4, 5, 6].collect{|x| x * 3 if x % 2 == 0}.compact
=> [6, 12, 18]

Это работает для меня. Это также чисто. Да, это то же самое, что и map, но я думаю, что collect делает код более понятным.


select(&:even?).map()

на самом деле выглядит лучше, увидев его ниже.

...