[ruby] Ruby에서 nil 값을 매핑하고 제거하는 방법

나는이 map중 하나 값 또는 전무로 설정을 변경한다. 그런 다음 목록에서 nil 항목을 제거하고 싶습니다. 목록을 유지할 필요는 없습니다.

이것이 내가 현재 가지고있는 것입니다 :

# A simple example function, which returns a value or nil
def transform(n)
  rand > 0.5 ? n * 10 : nil }
end

items.map! { |x| transform(x) } # [1, 2, 3, 4, 5] => [10, nil, 30, 40, nil]
items.reject! { |x| x.nil? } # [10, nil, 30, 40, nil] => [10, 30, 40]

루프를 수행하고 조건부로 다음과 같은 다른 배열로 수집 할 수 있다는 것을 알고 있습니다.

new_items = []
items.each do |x|
    x = transform(x)
    new_items.append(x) unless x.nil?
end
items = new_items

그러나 그것은 관용적 인 것처럼 보이지 않습니다. 목록에 함수를 매핑하고 nil을 제거 / 제외하는 좋은 방법이 있습니까?



답변

루비 2.7+

지금 있습니다!

Ruby 2.7 filter_map은이 정확한 목적을 위해 소개 하고 있습니다. 관용적이고 성능이 뛰어나며 곧 표준이 될 것으로 기대합니다.

예를 들면 다음과 같습니다.

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

귀하의 경우 블록이 거짓으로 평가되면 간단히 다음과 같이하십시오.

items.filter_map { |x| process_x url }

Ruby 2.7은 Enumerable # filter_map을 추가합니다 “는이 문제에 대한 초기 접근 방식 중 일부에 대한 일부 성능 벤치 마크와 함께 주제에 대해 잘 읽었습니다.

N = 1_00_000
enum = 1.upto(1_000)
Benchmark.bmbm do |x|
  x.report("select + map")  { N.times { enum.select { |i| i.even? }.map{|i| i + 1} } }
  x.report("map + compact") { N.times { enum.map { |i| i + 1 if i.even? }.compact } }
  x.report("filter_map")    { N.times { enum.filter_map { |i| i + 1 if i.even? } } }
end

# Rehearsal -------------------------------------------------
# select + map    8.569651   0.051319   8.620970 (  8.632449)
# map + compact   7.392666   0.133964   7.526630 (  7.538013)
# filter_map      6.923772   0.022314   6.946086 (  6.956135)
# --------------------------------------- total: 23.093686sec
# 
#                     user     system      total        real
# select + map    8.550637   0.033190   8.583827 (  8.597627)
# map + compact   7.263667   0.131180   7.394847 (  7.405570)
# filter_map      6.761388   0.018223   6.779611 (  6.790559)


답변

당신은 사용할 수 있습니다 compact:

[1, nil, 3, nil, nil].compact
=> [1, 3] 

사람들에게 map블록 의 출력으로 nil을 포함하는 배열을 얻고 그 블록이 조건부로 값을 반환하려고하면 코드 냄새가 나고 논리를 다시 생각해야한다고 사람들에게 상기시키고 싶습니다 .

예를 들어, 이렇게하는 일을하는 경우 :

[1,2,3].map{ |i|
  if i % 2 == 0
    i
  end
}
# => [nil, 2, nil]

그럼 하지마 대신, 이전에 map, reject당신이 원하는하지 않거나 물건 select당신은 무엇을 하시겠습니까? :

[1,2,3].select{ |i| i % 2 == 0 }.map{ |i|
  i
}
# => [2]

나는 compact우리가 올바르게 다룰 수없는 것들을 제거하기위한 마지막 도랑 노력으로 혼란을 정리하는 것을 고려한다 . 보통 우리는 무엇이 우리에게 올 것인지 알지 못했기 때문이다. 우리는 항상 프로그램에서 어떤 종류의 데이터가 던져 지는지 알아야합니다. 예기치 않은 / 알 수없는 데이터가 잘못되었습니다. 내가 일하고있는 배열에서 nil을 볼 때마다 왜 존재하는지 파헤쳐 서 루비가 nil을 생성하는 시간을 낭비하고 배열을 통해 제거하기 위해 시간을 낭비하지 않고 배열을 생성하는 코드를 개선 할 수 있는지 확인합니다. 나중에.

'Just my $%0.2f.' % [2.to_f/100]


답변

사용해보십시오 reduce또는 inject.

[1, 2, 3].reduce([]) { |memo, i|
  if i % 2 == 0
    memo << i
  end

  memo
}

내가 허용 대답에 동의 우리가 안된다고 map하고 compact있지만 같은 이유.

나는 그 깊은 느끼는 map다음 compact에 해당 select다음을 map. 고려하십시오 : map일대일 기능입니다. 당신을 일부 값 세트에서 매핑하고있는 경우 map에, 당신은 원하는 입력 세트의 각 값에 대한 출력 설정 한 값입니다. 사전에해야한다면 select아마 map세트를 원하지 않을 것 입니다. select나중에 (또는 compact) 해야하는 경우 map세트에서 원하지 않을 것 입니다. 두 경우 모두 reduce한 번만 이동하면 전체 세트에서 두 번 반복됩니다 .

또한 영어에서는 “정수 세트를 짝수 정수 세트로 줄이기”를 시도하고 있습니다.


답변

귀하의 예에서 :

items.map! { |x| process_x url } # [1, 2, 3, 4, 5] => [1, nil, 3, nil, nil]

로 바뀌지 않고 값이 변경된 것처럼 보이지 않습니다 nil. 이 경우 다음과 같습니다.

items.select{|x| process_x url}

충분할 것입니다.


답변

빈 문자열과 nil을 거부하는 것과 같이 거부에 대한 느슨한 기준을 원한다면 다음을 사용할 수 있습니다.

[1, nil, 3, 0, ''].reject(&:blank?)
 => [1, 3, 0] 

더 나아가 0 값을 거부하거나 프로세스에 더 복잡한 논리를 적용하려면 블록을 전달하여 거부하십시오.

[1, nil, 3, 0, ''].reject do |value| value.blank? || value==0 end
 => [1, 3]

[1, nil, 3, 0, '', 1000].reject do |value| value.blank? || value==0 || value>10 end
 => [1, 3]


답변

compact이 작업을 해결하는 가장 좋은 방법은 확실 합니다. 그러나 간단한 빼기만으로 동일한 결과를 얻을 수 있습니다.

[1, nil, 3, nil, nil] - [nil]
 => [1, 3]


답변

each_with_object 아마도 가장 깨끗한 방법입니다.

new_items = items.each_with_object([]) do |x, memo|
    ret = process_x(x)
    memo << ret unless ret.nil?
end

제 생각에는, each_with_object더 나은보다 inject/ reduce당신이 블록의 반환 값에 대해 걱정할 필요가 없기 때문에 조건의 경우입니다.