Ruby에는 'select'와 'map'을 결합한 Array 메서드가 있습니까?


96

문자열 값을 포함하는 Ruby 배열이 있습니다. 다음을 수행해야합니다.

  1. 일부 술어와 일치하는 모든 요소 찾기
  2. 변환을 통해 일치하는 요소 실행
  3. 결과를 배열로 반환

지금 내 솔루션은 다음과 같습니다.

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

선택 및 매핑을 단일 논리 문으로 결합하는 Array 또는 Enumerable 메서드가 있습니까?


5
현재로서는 방법이 없지만 Ruby에 하나를 추가하는 제안 : bugs.ruby-lang.org/issues/5663
stefankolb

Enumerable#grep방법은 루비에서 10 년 넘게 요청 된 것과 정확히 일치합니다. 조건 자 인수와 변환 블록을 사용합니다. @hirolau는이 질문에 대한 유일한 정답을 제공합니다.
inopinatus

2
filter_map이 정확한 목적을 위해 Ruby 2.7이 도입 되었습니다. 여기에 더 많은 정보가 있습니다 .
SRack

답변:


115

나는 보통 mapcompact내 선택 기준과 함께 접미사로 사용 if합니다. compactnil을 제거합니다.

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] 

1
아하, 맵 블록에서 반환 된 nil을 무시하는 방법을 알아 내려고했습니다. 감사!
Seth Petry-Johnson

문제 없습니다. 저는 컴팩트를 좋아합니다. 눈에 띄지 않게 밖에 앉아 제 역할을합니다. 또한 매우 선언 적이기 때문에 간단한 선택 기준에 대해 열거 가능한 함수를 연결하는 것보다이 방법을 선호합니다.
Jed Schneider

4
map+ compact가 실제로 더 나은 성능을 inject
발휘

3
이렇게하면 원래 nil과 기준에 실패한 모든 nil이 제거됩니다. 그러니 조심하세요
user1143669

1
그것은하지 않습니다 전체 체인을 제거 map하고 select그냥 그것을이다, compact특별한 경우가 reject인해 C.에 직접 구현 된 다소 나은 NILS 및 수행에 작품
조 Atzberger

53

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

20
하지 않습니다 each_with_object좀 더 이해가? 블록의 각 반복이 끝날 때 배열을 반환 할 필요가 없습니다. 간단히 할 수 있습니다 my_array.each_with_object([]) { |i, a| a << i if i.condition }.
henrebotha

@henrebotha 아마도 그렇습니다. 저는 기능적 배경에서 reduce왔습니다. 그것이 제가 처음 발견 한 이유입니다 😊
Adam Lindberg

35

Ruby 2.7 이상

이제 있습니다!

filter_map이 정확한 목적을 위해 Ruby 2.7이 도입 되었습니다. 관용적이고 성능이 뛰어나며 곧 표준이 될 것으로 기대합니다.

예를 들면 :

numbers = [1, 2, 5, 8, 10, 13]
enum.filter_map { |i| i * 2 if i.even? }
# => [4, 16, 20]

여기 에 주제에 대한 좋은 읽기가 있습니다.

누군가에게 유용하기를 바랍니다!


1
아무리 자주 업그레이드하더라도 멋진 기능은 항상 다음 버전에 있습니다.
mlt

좋은. 하나의 문제는 그 이후 수 filter, selectfind_all마찬가지로, 동의어 map하고 collect있습니다,이 메소드의 이름을 기억하기 어려울 수 있습니다. 그것은이다 filter_map, select_collect, find_all_map또는 filter_collect?
Eric Duminil

19

이것에 접근하는 또 다른 다른 방법은 new (이 질문과 관련하여)를 사용하는 것입니다 Enumerator::Lazy.

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

.lazy메서드는 lazy 열거자를 반환합니다. 호출 .select또는 .map게으른 열거에 또 다른 게으른 열거를 돌려줍니다. 한 번만 호출 .uniq하면 실제로 열거자를 강제 적용하고 배열을 반환합니다. 따라서 효과적으로 발생하는 것은 귀하 .select.map통화가 하나로 결합되는 것입니다. @lines한 번만 반복 .select하여.map .

내 본능은 Adam의 reduce방법이 조금 더 빠를 것이지만 이것이 훨씬 더 읽기 쉽다고 생각합니다.


이것의 주요 결과는 각 후속 메서드 호출에 대해 중간 배열 개체가 생성되지 않는다는 것입니다. 정상적인 @lines.select.map상황 에서는에 select의해 수정 된 배열을 반환하고 map다시 배열을 반환합니다. 이에 비해 지연 평가는 배열을 한 번만 생성합니다. 이것은 초기 컬렉션 개체가 클 때 유용합니다. 또한 무한 열거 자로 작업 할 수 있습니다 random_number_generator.lazy.select(&:odd?).take(10).


4
각자에게. 내 종류의 솔루션을 사용하면 메서드 이름을 한 눈에 볼 수 있고 입력 데이터의 하위 집합을 변환하고 고유하게 만들고 정렬 할 것임을 즉시 알 수 있습니다. reduce"모든 것을 수행"하는 변화는 항상 나에게 상당히 지저분하게 느껴집니다.
henrebotha의

2
@henrebotha : 난 당신이 무슨 뜻인지 오해를했습니다, 그러나 이것은 매우 중요한 포인트입니다 경우에 저를 용서 : 그것은 정확하지 "만 반복 처리가 이상 그 말을 @lines한 번에 모두 수행 .select.map". 사용 .lazy한다고해서 지연 열거 자에 대한 연결 작업 작업이 단일 반복으로 "축소"되는 것은 아닙니다. 이것은 컬렉션에 대한 지연 평가 wrt 체인 작업에 대한 일반적인 오해입니다. ( 첫 번째 예에서 및 블록 puts의 시작 부분에 문을 추가하여이를 테스트 할 수 있습니다 . 동일한 수의 줄을 인쇄하는 것을 알 수 있습니다.)selectmap
pje

1
@henrebotha : 제거 .lazy하면 동일한 횟수로 인쇄됩니다. 그것이 내 요점입니다. 귀하의 map블록과 귀하의 select블록은 게으른 버전과 열성 버전에서 동일한 횟수로 실행됩니다. 게으른 버전은 "당신 .select.map전화를 결합"하지 않습니다
pje

1
@pje : 효과에 lazy 결합 실패 요소 그들 때문에 select조건은 전달되지 않습니다 map. 즉, prepending lazy은 대체 selectmap단일 으로 대체하는 것과 거의 동일 reduce([])하며 "지능적으로" select의 블록을 reduce의 결과 에 포함하기위한 전제 조건으로 만듭니다.
henrebotha 2008

1
@henrebotha : 게으름이이 알고리즘의 시간 복잡성을 변경하지 않기 때문에 일반적으로 게으른 평가에 대한 잘못된 비유라고 생각합니다. 이것이 내 요점입니다. 모든 경우에 게으른 선택 후 맵은 항상 열망 버전과 동일한 수의 계산을 수행합니다. 속도를 높이는 것이 아니라 각 반복의 실행 순서 만 변경합니다. 체인의 마지막 함수는 필요에 따라 이전 함수의 값을 역순으로 "풀링"합니다.
pje

13

당신이있는 경우 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"]

더 복잡한 논리가 필요한 경우 람다를 만들 수 있습니다.

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

대안이 아닙니다. 질문에 대한 유일한 정답입니다.
inopinatus

@inopinatus : 더 이상은 아닙니다 . 그래도 여전히 좋은 대답입니다. 그렇지 않으면 블록으로 grep을 본 기억이 없습니다.
Eric Duminil

8

하나가 있는지 잘 모르겠습니다. 및 을 추가 하는 Enumerable 모듈 은 하나를 표시하지 않습니다.selectmap

두 블록을 select_and_transform메서드 에 전달해야하는데 , 이는 약간 직관적이지 않은 IMHO입니다.

분명히, 더 읽기 쉽게 함께 연결할 수 있습니다.

transformed_list = lines.select{|line| ...}.map{|line| ... }

3

간단한 대답 :

당신은 n 개의 기록을 가지고 있고, 당신이 원하는 경우 selectmap다음 조건에 따라

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

여기에서 속성은 레코드에서 원하는 모든 항목과 조건을 확인할 수 있습니다.

compact는 if 조건에서 나온 불필요한 nil을 플러시하는 것입니다.


1
조건이없는 경우에도 동일하게 사용할 수 있습니다. 내 친구가 물었다.
Sk. Irfan jul.

2

아니요,하지만 다음과 같이 할 수 있습니다.

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

또는 더 나은 방법 :

lines.inject([]) { |all, line| all << line if check_some_property; all }

14
reject(&:nil?)기본적으로 compact.
Jörg W Mittag

네, 주입 방법이 더 좋습니다.
Daniel O'Hara

2

필터 조건과 매핑 된 값을 분할하고 작업이 연결되어 있음을 명확하게 유지하기 때문에이 방법이 더 읽기 쉽다고 생각합니다.

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

1
def example
  @lines.select {|line| ... }.map {|line| ... }.uniq.sort
end

Ruby 1.9 및 1.8.7에서는 단순히 블록을 전달하지 않고 반복자를 연결하고 래핑 할 수도 있습니다.

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


어젯밤에 "Ruby의 메소드 체인"에 대해 아주 간단한 웹 검색을했는데 잘 지원되지 않는 것 같았습니다. Tho, 아마 시도 했어야했는데 ... 또한 왜 블록 인수의 유형이 일치하지 않는다고 말합니까? 내 예에서 두 블록 모두 내 배열에서 텍스트 한 줄을 가져옵니다.
Seth Petry-Johnson

@Seth Petry-Johnson : 예, 죄송합니다. 반환 값을 의미했습니다. select요소를 유지할지 여부를 결정하는 부울 값을 map반환하고 변환 된 값을 반환합니다. 변환 된 값 자체는 아마도 진실 일 것이므로 모든 요소가 선택됩니다.
Jörg W Mittag

1

메서드를 추가 한 내 라이브러리 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]}

select_map이 라이브러리에서는 select { |i| ... }.map { |i| ... }위의 많은 답변에서 동일한 전략을 구현합니다 .
Jordan Sitkin

1

두 개의 다른 배열을 만들지 않으려면 사용할 수 compact!있지만주의해야합니다.

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

흥미롭게도 compact!nil을 제자리에서 제거합니다. 의 반환 값 compact!은 변경 사항이 있으면 동일한 배열이지만 nil이 없으면 nil입니다.

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

하나의 라이너가 될 것입니다.


0

버전 :

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


-1

여기에 예가 있습니다. 문제와 같지는 않지만 원하는 것이거나 솔루션에 대한 단서를 제공 할 수 있습니다.

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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.