Ruby의 목록 이해


93

Python 목록 이해와 동등한 작업을 수행하기 위해 다음을 수행합니다.

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

이 작업을 수행하는 더 좋은 방법이 있습니까? 아마 하나의 메서드 호출로?


3
너와 글렌 맥도날드의 대답은 나에게 괜찮아 보인다. 둘 중 하나보다 더 간결하게 시도함으로써 얻을 수있는 게 뭔지 모르겠어.
Pistos

1
이 솔루션은 목록을 두 번 횡단합니다. 주사는 그렇지 않습니다.
Pedro Rolo

2
여기에 몇 가지 멋진 답변이 있지만 여러 컬렉션에 대한 목록 이해에 대한 아이디어를 보는 것도 멋질 것입니다.
Bo Jeanes 2012

답변:


55

정말로 원한다면 다음과 같이 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

나는 아마 당신이했던 방식대로 할 것입니다.


2
컴팩트하게 사용할 수 있습니다! 최적화
Alexey 2011

9
이 실제로 올바르지 않습니다, 고려해야 [nil, nil, nil].comprehend {|x| x }하는 반환 [].
Ted Kaplan

문서에 따르면 alexey compact!는 항목이 변경되지 않으면 배열 대신 nil을 반환하므로 작동하지 않는다고 생각합니다.
Binary Phile 2014-08-26

89

어때?

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

적어도 내 취향에는 약간 깨끗하고 빠른 벤치 마크 테스트에 따르면 귀하의 버전보다 약 15 % 더 빠릅니다.


4
뿐만 아니라 some_array.map{|x| x * 3 unless x % 2}.compact, 아마도 더 읽기 쉽고 루비 풍입니다.
2015 년

5
unless x%20은 루비에서 진실이기 때문에 @nightpool 은 효과가 없습니다. 참조 : gist.github.com/jfarmer/2647362
Abhinav Srivastava

30

세 가지 대안을 비교하는 빠른 벤치 마크를 만들었고 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

1
reduce이 벤치 마크 에서도 흥미로울 것입니다 ( stackoverflow.com/a/17703276 참조 ).
Adam Lindberg

3
inject==reduce
ben.snape

map_compact가 더 빠를 수도 있지만 새 배열을 만들고 있습니다. inject는 map.compact 및 select.map보다 공간 효율적입니다.
bibstha

11

목록 이해력이 무엇인지에 대해이 스레드에서 Ruby 프로그래머 사이에 약간의 혼란이있는 것 같습니다. 모든 단일 응답은 변환 할 기존 배열을 가정합니다. 그러나 목록 이해력의 힘은 다음 구문으로 즉석에서 생성 된 배열에 있습니다.

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

다음은 Ruby의 아날로그입니다 (이 스레드의 유일한 적절한 대답 인 AFAIC).

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

위의 경우 임의의 정수 배열을 생성하고 있지만 블록에는 모든 것이 포함될 수 있습니다. 그러나 이것은 Ruby 목록 이해입니다.


1
OP가하려는 일을 어떻게 하시겠습니까?
Andrew Grimm

2
실제로 나는 이제 OP 자체에 저자가 변형하기를 원하는 기존 목록이 있음을 알 수 있습니다. 그러나 목록 이해의 전형적인 개념은 일부 반복을 참조하여 이전에 존재하지 않았던 배열 / 목록을 만드는 것과 관련이 있습니다. 그러나 실제로 일부 공식적인 정의에서는 목록 이해가지도를 전혀 사용할 수 없다고 말하고 있으므로 내 버전도 정결하지 않지만 Ruby에서 얻을 수있는만큼 가깝습니다.
Mark

5
Ruby 예제가 Python 예제와 어떻게 유사해야하는지 이해하지 못합니다. Ruby 코드는 다음과 같아야합니다. squares = (0..9) .map {| x | x ** 2}
michau 2011

4
@michau가 옳지 만 목록 이해의 요점 (Mark가 무시 했음)은 목록 이해 자체가 배열을 생성하지 않는다는 것입니다. 생성기와 공동 루틴을 사용하여 스토리지를 전혀 할당하지 않고 스트리밍 방식으로 모든 계산을 수행합니다 (예외 임시 변수) (iff) 결과가 배열 변수에 도착할 때까지-이것은 이해력을 결과 집합으로 축소하기 위해 파이썬 예제에서 대괄호의 목적입니다. Ruby에는 생성기와 유사한 기능이 없습니다.
Guss 2014 년

4
네, (Ruby 2.0부터) : squares_of_all_natural_numbers = (0..Float :: INFINITY) .lazy.map {| x | x ** 2}; P squares_of_all_natural_numbers.take (10) .to_a
michau

11

Rein Henrichs와이 주제에 대해 논의했습니다. Rein Henrichs는 최고의 성능을내는 솔루션은

map { ... }.compact

이는의 변경 불가능한 사용과 같이 중간 배열을 빌드 Enumerable#inject하는 것을 피하고 할당을 유발하는 배열의 증가를 방지하기 때문에 합리적 입니다. 컬렉션에 nil 요소가 포함될 수없는 경우가 아니면 다른 것만 큼 일반적입니다.

나는 이것을 비교하지 않았다

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

Ruby의 C 구현 Enumerable#select도 매우 훌륭 할 수 있습니다.


9

모든 구현에서 작동하고 O (2n) 시간 대신 O (n)에서 실행되는 대체 솔루션은 다음과 같습니다.

some_array.inject([]){|res,x| x % 2 == 0 ? res << 3*x : res}

11
목록을 한 번만 순회한다는 뜻입니다. 공식적인 정의로 가면 O (n)은 O (2n)과 같습니다. 그냥 nitpicking :)
Daniel Hepper

1
@Daniel Harper :) 당신이 옳을뿐만 아니라 평균적인 경우에도 목록을 한 번 넘겨 일부 항목을 삭제 한 다음 다시 작업을 수행하는 것이 평균적인 경우에 더 좋을 수 있습니다. :)
Pedro Rolo

즉, 2n시간 대신 1일을 n하고 다른 1n시간을합니다. inject/의 중요한 장점 중 하나 는 입력 시퀀스에서 더 많은 목록을 이해하는 동작 인 reduce모든 nil값을 보존한다는 것입니다.
John La Rooy

8

방금 Comprehend gem 을 RubyGems에 게시 했습니다. 이렇게하면 다음과 같이 할 수 있습니다.

require 'comprehend'

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

C로 작성되었습니다. 어레이는 한 번만 순회됩니다.


7

Enumerable에는 grep첫 번째 인수가 술어 proc이 될 수 있고 선택적 두 번째 인수가 매핑 함수 인 메서드가 있습니다. 따라서 다음이 작동합니다.

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

이것은 몇 가지 다른 제안 (나는 anoiaque의 simple select.map또는 histocrat의 comprehend gem을 좋아함)만큼 읽기 쉽지는 않지만, 그것의 강점은 그것이 이미 표준 라이브러리의 일부이고 단일 패스이고 임시 중간 배열을 만드는 것을 포함하지 않는다는 것입니다. 이며 -using 제안에 nil사용 된 것과 같은 범위를 벗어난 값이 필요하지 않습니다 compact.


4

이것은 더 간결합니다.

[1,2,3,4,5,6].select(&:even?).map{|x| x*3}

2
또는, 더 많은 포인트 무료 awesomeness에 대한[1,2,3,4,5,6].select(&:even?).map(&3.method(:*))
요 르그 W MITTAG

4
[1, 2, 3, 4, 5, 6].collect{|x| x * 3 if x % 2 == 0}.compact
=> [6, 12, 18]

그것은 나를 위해 작동합니다. 또한 깨끗합니다. 예,와 동일 map하지만 collect코드를 더 이해하기 쉽게 만듭니다.


select(&:even?).map()

아래에서 본 후에 실제로 더 좋아 보입니다.


2

Pedro가 언급했듯이 선택한 요소에 대한 순회를 방지하면서 Enumerable#select및에 대한 연결 호출을 통합 할 수 있습니다 Enumerable#map. 때문에 마찬가지입니다 Enumerable#select배 또는 전문화이다 inject. 서둘러 소개를 올렸습니다Ruby subreddit에 주제에 를 .

배열 변환을 수동으로 융합하는 것은 지루할 수 있으므로 누군가 Robert Gamble의 comprehend구현을 사용하여이 select/ map패턴을 더 예쁘게 만들 수 있습니다.


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]

lazyArray 에서 정의하는 것이 잘못된 이유 :(1..6).lazy{|x|x*3 if x.even?}
Guss

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 }

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 

그래서 기본적으로 우리는 문자열을 루프를위한 적절한 루비 구문으로 변환하고 있습니다. 그러면 우리는 문자열에서 파이썬 구문을 사용하여 할 수 있습니다 :

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]

또는 문자열의 모양이 마음에 들지 않거나 람다를 사용해야하는 경우 파이썬 구문을 미러링하려는 시도를 포기하고 다음과 같이 할 수 있습니다.

S = [for x in 0...9 do $* << x*2 if x.even? end, $*][1]
# [0, 4, 8, 12, 16]

0

Ruby 2.7에서는 filter_map원하는 것을 거의 달성 할 수 있습니다 (map + compact) :

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

여기에서 자세한 내용을 읽을 수 있습니다 .



-4

가장 많은 목록 이해력이 다음과 같을 것이라고 생각합니다.

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

Ruby를 사용하면 표현식 뒤에 조건문을 배치 할 수 있으므로 목록 이해의 Python 버전과 유사한 구문을 얻을 수 있습니다. 또한 select메서드에와 동일한 항목이 포함되어 있지 않기 false때문에 결과 목록에서 모든 nil 값이 제거되고 사용 map했거나 collect대신 사용한 경우처럼 compact에 대한 호출이 필요하지 않습니다 .


7
작동하지 않는 것 같습니다. 적어도 Ruby 1.8.6, [1,2,3,4,5,6] .select {| x | x * 3 if x % 2 == 0}이 [2, 4, 6]으로 평가됩니다. Enumerable # select는 블록이 출력하는 값 AFAIK가 아니라 블록이 참인지 거짓인지 여부 만 고려합니다.
Greg Campbell
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.