Ruby-이미 배열이 아닌 경우 변수를 배열로 우아하게 변환


120

배열, 단일 요소 또는 nil이 주어지면 배열을 얻습니다. 마지막 두 개는 각각 단일 요소 배열과 빈 배열입니다.

루비가 이런 식으로 작동 할 것이라고 잘못 생각했습니다.

[1,2,3].to_a  #= [1,2,3]     # Already an array, so no change
1.to_a        #= [1]         # Creates an array and adds element
nil.to_a      #= []          # Creates empty array

그러나 실제로 얻는 것은 다음과 같습니다.

[1,2,3].to_a  #= [1,2,3]         # Hooray
1.to_a        #= NoMethodError   # Do not want
nil.to_a      #= []              # Hooray

따라서이 문제를 해결하려면 다른 방법을 사용하거나 사용하려는 모든 클래스의 to_a 메서드를 수정하여 메타 프로그램을 수행 할 수 있습니다.

그래서 방법은 다음과 같습니다.

result = nums.class == "Array".constantize ? nums : (nums.class == "NilClass".constantize ? [] : ([]<<nums))

문제는 그것이 약간 엉망이라는 것입니다. 이 작업을 수행하는 우아한 방법이 있습니까? (이 문제를 해결하는 루비 같은 방법이라면 놀라 울 것입니다)


어떤 응용 프로그램이 있습니까? 왜 배열로 변환합니까?

Rails의 ActiveRecord에서 say를 호출 user.posts하면 게시물 배열, 단일 게시물 또는 nil이 반환됩니다. 이 결과에 대해 작동하는 메소드를 작성할 때 메소드가 0, 1 또는 많은 요소를 가질 수있는 배열을 취할 것이라고 가정하는 것이 가장 쉽습니다. 방법 예 :

current_user.posts.inject(true) {|result, element| result and (element.some_boolean_condition)}

2
user.posts단일 게시물을 반환해서는 안됩니다. 적어도 나는 그것을 본 적이 없습니다.
Sergio Tulentsev 2013-08-21

1
처음 두 코드 블록에서 ==대신 의미한다고 생각합니다 =.
Patrick Oscity 2013-08-21


3
Btw, 반환 [1,2,3].to_a하지 않습니다[[1,2,3]] ! 반환합니다 [1,2,3].
Patrick Oscity 2013 년

감사합니다 패들는 질문을 업데이트합니다 ... facepalms을 자기에
xxjjnn

답변:


153

[*foo]또는 Array(foo)대부분의 경우 작동하지만 해시와 같은 경우에는 엉망입니다.

Array([1, 2, 3])    # => [1, 2, 3]
Array(1)            # => [1]
Array(nil)          # => []
Array({a: 1, b: 2}) # => [[:a, 1], [:b, 2]]

[*[1, 2, 3]]    # => [1, 2, 3]
[*1]            # => [1]
[*nil]          # => []
[*{a: 1, b: 2}] # => [[:a, 1], [:b, 2]]

해시에서도 작동한다고 생각할 수있는 유일한 방법은 메서드를 정의하는 것입니다.

class Object; def ensure_array; [self] end end
class Array; def ensure_array; to_a end end
class NilClass; def ensure_array; to_a end end

[1, 2, 3].ensure_array    # => [1, 2, 3]
1.ensure_array            # => [1]
nil.ensure_array          # => []
{a: 1, b: 2}.ensure_array # => [{a: 1, b: 2}]

2
대신 ensure_array확장to_a
Dan Grahn

9
@screenmutt의 원래 사용에 의존하는 메서드에 영향을 미칩니다 to_a. 예를 들어, {a: 1, b: 2}.each ...다르게 작동합니다.
sawa

1
이 구문을 설명 할 수 있습니까? 루비의 수년 동안 저는 이런 종류의 호출을 본 적이 없었습니다. 클래스 이름의 괄호는 무엇을합니까? 문서에서 찾을 수 없습니다.
mastaBlasta 2015 년

1
@mastaBlasta Array (arg)는 to_ary를 호출 한 다음 인수에서 to_a를 호출하여 새 배열을 만들려고합니다. 이것은 공식 루비 문서에 문서화되어 있습니다. 나는 Avdi의 "Confident Ruby"책에서 그것에 대해 배웠습니다.
mambo

2
@mambo 내 질문을 게시 한 후 어느 시점에서 나는 답을 찾았습니다. 어려운 부분은 Array 클래스와 관련이 없지만 Kernel 모듈의 메서드라는 것입니다. ruby-doc.org/core-2.3.1/Kernel.html#method-i-Array
mastaBlasta

119

ActiveSupport (레일) : Array.wrap

Array.wrap([1, 2, 3])     # => [1, 2, 3]
Array.wrap(1)             # => [1]
Array.wrap(nil)           # => []
Array.wrap({a: 1, b: 2})  # => [{:a=>1, :b=>2}]

Rails를 사용하지 않는 경우 rails source 와 유사한 자체 메서드를 정의 할 수 있습니다 .

class Array
  def self.wrap(object)
    if object.nil?
      []
    elsif object.respond_to?(:to_ary)
      object.to_ary || [object]
    else
      [object]
    end
  end
end

12
class Array; singleton_class.send(:alias_method, :hug, :wrap); end더 귀여움을 위해.
rthbound,

21

가장 간단한 해결책은 [foo].flatten(1). 다른 제안 된 솔루션과는 달리 (중첩 된) 배열, 해시 및 nil다음에서 잘 작동합니다 .

def wrap(foo)
  [foo].flatten(1)
end

wrap([1,2,3])         #= [1,2,3]
wrap([[1,2],[3,4]])   #= [[1,2],[3,4]]
wrap(1)               #= [1]
wrap(nil)             #= [nil]
wrap({key: 'value'})  #= [{key: 'value'}]

불행히도 이것은 다른 접근 방식에 비해 심각한 성능 문제가 있습니다. Kernel#ArrayArray(), 그들 중 가장 빠릅니다. Ruby 2.5.1 비교 : Array () : 7936825.7 i / s. Array.wrap : 4199036.2 i / s-1.89 배 느림. 랩 : 644030.4 i / s-12.32 배 더 느림
Wasif Hossain

19

Array(whatever) 트릭을해야한다

Array([1,2,3]) # [1,2,3]
Array(nil) # []
Array(1337)   # [1337]

14
해시에서는 작동하지 않습니다. Array ({a : 1, b : 2})는 [[: a, 1], [: b, 2]]
davispuh

13

ActiveSupport (레일)

ActiveSupport에는 이에 대한 꽤 좋은 방법이 있습니다. Rails가 포함되어 있으므로이를 수행하는 가장 좋은 방법은 다음과 같습니다.

Array.wrap([1, 2, 3]) #=> [1, 2, 3]
Array.wrap(nil) #=> nil

Splat (Ruby 1.9 이상)

표시 연산자 ( *)는 다음과 같은 경우 배열을 배열 해제합니다.

*[1,2,3] #=> 1, 2, 3 (notice how this DOES not have braces)

물론, 배열이 없으면 이상한 일을하므로 "splat"객체를 배열에 넣어야합니다. 다소 이상하지만 다음을 의미합니다.

[*[1,2,3]] #=> [1, 2, 3]
[*5] #=> [5]
[*nil] #=> []
[*{meh: "meh"}] #=> [[:meh, "meh"], [:meh2, "lol"]]

ActiveSupport가없는 경우 방법을 정의 할 수 있습니다.

class Array
    def self.wrap(object)
        [*object]
    end
end

Array.wrap([1, 2, 3]) #=> [1, 2, 3]
Array.wrap(nil) #=> nil

비록 당신이 큰 배열을 가지고 있고 배열이 아닌 것을 덜 계획한다면, 그것을 변경하고 싶을 수도 있습니다. 위의 방법은 큰 배열에서 느리고 스택이 오버플로 될 수도 있습니다 (omg so meta). 어쨌든 대신 이것을 할 수 있습니다.

class Array
    def self.wrap(object)
        object.is_a? Array ? object : [*object]
    end
end

Array.wrap([1, 2, 3]) #=> [1, 2, 3]
Array.wrap(nil) #=> [nil]

또한 테너 레이 연산자가 있거나없는 몇 가지 벤치 마크 가 있습니다.


큰 배열에서는 작동하지 않습니다. SystemStackError: stack level too deep1M 요소 용 (루비 2.2.3).
denis.peplin 2015

@ denis.peplin은 StackOverflow 오류가 발생한 것 같습니다. : D-솔직히, 무슨 일이 일어 났는지 잘 모르겠습니다. 죄송합니다.
Ben Aubin 2015

최근 Hash#values_at에 1M 인수 (사용 splat)를 시도했는데 동일한 오류가 발생합니다.
denis.peplin 2015

@ denis.peplin 함께 작동합니까 object.is_a? Array ? object : [*object]?
Ben Aubin

1
Array.wrap(nil)반환 []하지 않습니다 nil: /
Aeramor

7

어때

[].push(anything).flatten

2
예, 제 경우에는 [anything] .flatten을 사용하게 된 것 같습니다.하지만 일반적인 경우에는 중첩 된 배열 구조도 평평하게됩니다
xxjjnn

1
[].push(anything).flatten(1)작동 할 것이다! 중첩 된 배열을 평면화하지 않습니다!
xxjjnn

2

명백한 것을 말할 위험이 있고 이것이 지구와 주변 지역에서 지금까지 본 것 중 가장 맛있는 구문 설탕이 아니라는 것을 알면이 코드는 당신이 설명하는 것과 정확히 일치하는 것처럼 보입니다.

foo = foo.is_a?(Array) ? foo : foo.nil? ? [] : [foo]

1

Object의 배열 메소드를 덮어 쓸 수 있습니다.

class Object
    def to_a
        [self]
    end
end

모든 것이 Object를 상속하므로 이제 to_a는 태양 아래의 모든 것에 대해 정의됩니다.


3
신성 모독 원숭이 패치! 회개하라!
xxjjnn

1

모든 답변을 검토했으며 대부분 루비 2 이상에서는 작동하지 않습니다.

그러나 elado에는 가장 우아한 솔루션이 있습니다.

ActiveSupport (레일) 사용 : Array.wrap

Array.wrap ([1, 2, 3]) # => [1, 2, 3]

Array.wrap (1) # => [1]

Array.wrap (nil) # => []

Array.wrap ({a : 1, b : 2}) # => [{: a => 1, : b => 2}]

슬프게도 루비 2+에서는 오류가 발생하므로 작동하지 않습니다.

undefined method `wrap' for Array:Class

따라서 수정하려면 필요합니다.

'active_support / deprecation'필요

'active_support / core_ext / array / wrap'필요


0

#to_a두 가지 주요 문제 클래스 ( NilHash)에 대한 메서드가 이미 존재 하므로 다음 을 확장하여 나머지 메서드를 정의하기 만하면됩니다 Object.

class Object
    def to_a
        [self]
    end
end

그런 다음 모든 객체에서 해당 메서드를 쉽게 호출 할 수 있습니다.

"Hello world".to_a
# => ["Hello world"]
123.to_a
# => [123]
{a:1, b:2}.to_a
# => [[:a, 1], [:b, 2]] 
nil.to_a
# => []

5
핵심 Ruby 클래스, 특히 객체를 패치하는 원숭이는 피해야한다고 생각합니다. 나는 ActiveSupport에 패스를 줄 것이므로 위선자라고 생각하십시오. @sawa의 위 솔루션은 이것보다 훨씬 더 실행 가능합니다.
pho3nixf1re jul.
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.