Ruby에서 배열을 해시로 변환하는 가장 좋은 방법은 무엇입니까

StackOverflow https://stackoverflow.com/questions/39567

  •  09-06-2019
  •  | 
  •  

문제

Ruby에서는 다음 형식 중 하나로 배열이 제공됩니다.

[apple, 1, banana, 2]
[[apple, 1], [banana, 2]]

...이것을 해시 형식으로 변환하는 가장 좋은 방법은 무엇입니까?

{apple => 1, banana => 2}
도움이 되었습니까?

해결책

메모:간결하고 효율적인 솔루션을 보려면 다음을 참조하세요. Marc-André Lafortune의 답변 아래에.

이 답변은 원래 글을 쓰는 시점에서 가장 높은 지지를 받았던 flatten을 사용하는 접근 방식에 대한 대안으로 제공되었습니다.이 예를 모범 사례나 효율적인 접근 방식으로 제시할 의도가 없음을 분명히 했어야 했습니다.원래 답변은 다음과 같습니다.


경고! 다음을 사용하는 솔루션 단조롭게 하다 배열 키나 값은 보존되지 않습니다!

@John Topley의 인기 답변을 바탕으로 다음을 시도해 보겠습니다.

a3 = [ ['apple', 1], ['banana', 2], [['orange','seedless'], 3] ]
h3 = Hash[*a3.flatten]

오류가 발생합니다.

ArgumentError: odd number of arguments for Hash
        from (irb):10:in `[]'
        from (irb):10

생성자는 길이가 짝수인 배열을 기대했습니다(예:['k1','v1,'k2','v2']).더 나쁜 것은 짝수 길이로 병합된 다른 배열이 우리에게 잘못된 값을 가진 해시를 자동으로 제공한다는 것입니다.

배열 키나 값을 사용하려면 다음을 사용할 수 있습니다. 지도:

h3 = Hash[a3.map {|key, value| [key, value]}]
puts "h3: #{h3.inspect}"

이렇게 하면 배열 키가 유지됩니다.

h3: {["orange", "seedless"]=>3, "apple"=>1, "banana"=>2}

다른 팁

간단히 사용 Hash[*array_variable.flatten]

예를 들어:

a1 = ['apple', 1, 'banana', 2]
h1 = Hash[*a1.flatten(1)]
puts "h1: #{h1.inspect}"

a2 = [['apple', 1], ['banana', 2]]
h2 = Hash[*a2.flatten(1)]
puts "h2: #{h2.inspect}"

사용 Array#flatten(1) 재귀를 제한하므로 Array 키와 값이 예상대로 작동합니다.

가장 좋은 방법은 다음과 같습니다. Array#to_h:

[ [:apple,1],[:banana,2] ].to_h  #=> {apple: 1, banana: 2}

참고하세요 to_h 또한 블록을 허용합니다:

[:apple, :banana].to_h { |fruit| [fruit, "I like #{fruit}s"] } 
  # => {apple: "I like apples", banana: "I like bananas"}

메모: to_h Ruby 2.6.0+에서 블록을 허용합니다.초기 루비의 경우 내 backports 보석과 require 'backports/2.6.0/enumerable/to_h'

to_h 블록 없는 기능은 Ruby 2.1.0에서 도입되었습니다.

Ruby 2.1 이전에는 읽기 어려운 문자를 사용할 수 있었습니다. Hash[]:

array = [ [:apple,1],[:banana,2] ]
Hash[ array ]  #= > {:apple => 1, :banana => 2}

마지막으로 다음을 사용하는 솔루션에 주의하세요. flatten, 이는 배열 자체인 값에 문제를 일으킬 수 있습니다.

업데이트

루비 2.1.0이 오늘 출시되었습니다.그리고 나는 함께 온다 Array#to_h (릴리즈 노트 그리고 루비 문서), 이는 변환 문제를 해결합니다. ArrayHash.

Ruby 문서 예:

[[:foo, :bar], [1, 2]].to_h    # => {:foo => :bar, 1 => 2}

편집하다:글을 쓰는 동안 게시된 응답을 보니 Hash[a.Flatten]이 좋은 것 같습니다.응답을 통해 생각할 때 문서에서 해당 부분을 놓쳤음에 틀림없습니다.필요한 경우 제가 작성한 솔루션을 대안으로 사용할 수 있다고 생각했습니다.

두 번째 형식은 더 간단합니다.

a = [[:apple, 1], [:banana, 2]]
h = a.inject({}) { |r, i| r[i.first] = i.last; r }

a = 배열, h = 해시, r = 반환 값 해시(누적된 해시), i = 배열의 항목

첫 번째 양식을 작성하는 가장 깔끔한 방법은 다음과 같습니다.

a = [:apple, 1, :banana, 2]
h = {}
a.each_slice(2) { |i| h[i.first] = i.last }

다음을 사용하여 2D 배열을 해시로 간단히 변환할 수도 있습니다.

1.9.3p362 :005 > a= [[1,2],[3,4]]

 => [[1, 2], [3, 4]]

1.9.3p362 :006 > h = Hash[a]

 => {1=>2, 3=>4} 

답변에 추가하지만 익명 배열을 사용하고 주석을 달았습니다.

Hash[*("a,b,c,d".split(',').zip([1,2,3,4]).flatten)]

내부부터 시작하여 답변을 분석해 보겠습니다.

  • "a,b,c,d" 실제로는 문자열입니다.
  • split 쉼표로 배열을 만듭니다.
  • zip 다음 배열과 함께 사용됩니다.
  • [1,2,3,4] 실제 배열입니다.

중간 결과는 다음과 같습니다.

[[a,1],[b,2],[c,3],[d,4]]

flatten은 다음과 같이 변환합니다.

["a",1,"b",2,"c",3,"d",4]

그런 다음:

*["a",1,"b",2,"c",3,"d",4] 그것을 펼칩니다."a",1,"b",2,"c",3,"d",4

우리는 이에 대한 인수로 사용할 수 있습니다. Hash[] 방법:

Hash[*("a,b,c,d".split(',').zip([1,2,3,4]).flatten)]

결과는 다음과 같습니다.

{"a"=>1, "b"=>2, "c"=>3, "d"=>4}

요약 및 요약:

이 답변은 다른 답변의 정보를 포괄적으로 요약하기를 바랍니다.

질문의 데이터와 몇 가지 추가 사항을 고려하면 매우 짧은 버전입니다.

flat_array   = [  apple, 1,   banana, 2  ] # count=4
nested_array = [ [apple, 1], [banana, 2] ] # count=2 of count=2 k,v arrays
incomplete_f = [  apple, 1,   banana     ] # count=3 - missing last value
incomplete_n = [ [apple, 1], [banana   ] ] # count=2 of either k or k,v arrays


# there's one option for flat_array:
h1  = Hash[*flat_array]                     # => {apple=>1, banana=>2}

# two options for nested_array:
h2a = nested_array.to_h # since ruby 2.1.0    => {apple=>1, banana=>2}
h2b = Hash[nested_array]                    # => {apple=>1, banana=>2}

# ok if *only* the last value is missing:
h3  = Hash[incomplete_f.each_slice(2).to_a] # => {apple=>1, banana=>nil}
# always ok for k without v in nested array:
h4  = Hash[incomplete_n] # or .to_h           => {apple=>1, banana=>nil}

# as one might expect:
h1 == h2a # => true
h1 == h2b # => true
h1 == h3  # => false
h3 == h4  # => true

토론 및 세부 사항은 다음과 같습니다.


설정:변수

우리가 사용할 데이터를 미리 보여주기 위해 데이터의 다양한 가능성을 나타내는 몇 가지 변수를 만들겠습니다.이는 다음 범주에 속합니다.

질문에 직접적으로 나온 내용을 바탕으로 a1 그리고 a2:

(메모:나는 apple 그리고 banana 변수를 표현하기 위한 것이었습니다.다른 사람들이 그랬듯이 여기서도 입력과 결과가 일치할 수 있도록 문자열을 사용하겠습니다.)

a1 = [  'apple', 1 ,  'banana', 2  ] # flat input
a2 = [ ['apple', 1], ['banana', 2] ] # key/value paired input

다중 값 키 및/또는 값 a3:

다른 답변에서는 또 다른 가능성이 제시되었습니다(여기서 확장). 키 및/또는 값은 그 자체로 배열일 수 있습니다.

a3 = [ [ 'apple',                   1   ],
       [ 'banana',                  2   ],
       [ ['orange','seedless'],     3   ],
       [ 'pear',                 [4, 5] ],
     ]

불균형 배열, a4:

좋은 측정을 위해 입력이 불완전할 수 있는 경우를 위해 하나를 추가하겠다고 생각했습니다.

a4 = [ [ 'apple',                   1],
       [ 'banana',                  2],
       [ ['orange','seedless'],     3],
       [ 'durian'                    ], # a spiky fruit pricks us: no value!
     ]

이제 작업하려면:

처음에는 평평한 배열로 시작하여, a1:

일부는 다음을 사용하도록 제안했습니다. #to_h (Ruby 2.1.0에 등장했으며 백포트됨 이전 버전으로).처음에 플랫 배열인 경우에는 작동하지 않습니다.

a1.to_h   # => TypeError: wrong element type String at 0 (expected array)

사용 Hash::[] 와 결합 표시 연산자 하다:

Hash[*a1] # => {"apple"=>1, "banana"=>2}

이것이 다음과 같은 간단한 경우에 대한 해결책입니다. a1.

키/값 쌍 배열의 배열을 사용하면 a2:

배열로 [key,value] 유형 배열에는 두 가지 방법이 있습니다.

첫 번째, Hash::[] 여전히 작동합니다(예: *a1):

Hash[a2] # => {"apple"=>1, "banana"=>2}

그리고 또한 #to_h 지금 작동합니다:

a2.to_h  # => {"apple"=>1, "banana"=>2}

따라서 간단한 중첩 배열 사례에 대한 두 가지 쉬운 대답이 있습니다.

이는 다음과 같이 하위 배열을 키나 값으로 사용하는 경우에도 마찬가지입니다. a3:

Hash[a3] # => {"apple"=>1, "banana"=>2, ["orange", "seedless"]=>3, "pear"=>[4, 5]} 
a3.to_h  # => {"apple"=>1, "banana"=>2, ["orange", "seedless"]=>3, "pear"=>[4, 5]}

그러나 두리안에는 스파이크가 있습니다(변칙적인 구조로 인해 문제가 발생함).

균형이 맞지 않은 입력 데이터를 얻은 경우 문제가 발생합니다. #to_h:

a4.to_h  # => ArgumentError: wrong array length at 3 (expected 2, was 1)

하지만 Hash::[] 여전히 작동합니다. 설정만 하면 됩니다. nil 에 대한 값으로 durian (그리고 단지 1값 배열인 a4의 다른 배열 요소):

Hash[a4] # => {"apple"=>1, "banana"=>2, ["orange", "seedless"]=>3, "durian"=>nil}

평면화 - 새 변수 사용 a5 그리고 a6

언급 된 몇 가지 다른 답변 flatten, 유무에 관계없이 1 인수이므로 몇 가지 새로운 변수를 만들어 보겠습니다.

a5 = a4.flatten
# => ["apple", 1, "banana", 2,  "orange", "seedless" , 3, "durian"] 
a6 = a4.flatten(1)
# => ["apple", 1, "banana", 2, ["orange", "seedless"], 3, "durian"] 

나는 사용하기로 선택했다 a4 우리가 겪었던 밸런스 문제로 인해 기본 데이터로 사용되었습니다. a4.to_h.전화할 것 같아 flatten 누군가가 이 문제를 해결하기 위해 사용할 수 있는 접근 방식 중 하나는 다음과 같을 수 있습니다.

flatten 인수 없이 (a5):

Hash[*a5]       # => {"apple"=>1, "banana"=>2, "orange"=>"seedless", 3=>"durian"}
# (This is the same as calling `Hash[*a4.flatten]`.)

순진하게 보면 이것이 효과가 있는 것처럼 보입니다. 그러나 씨 없는 오렌지로 인해 우리가 잘못된 발을 디디게 되었고, 3열쇠 그리고 durian.

그리고 이것은 a1, 작동하지 않습니다.

a5.to_h # => TypeError: wrong element type String at 0 (expected array)

그래서 a4.flatten 우리에게 유용하지 않습니다. 우리는 단지 사용하고 싶습니다 Hash[a4]

그만큼 flatten(1) 사례 (a6):

하지만 부분적으로만 평탄화하는 것은 어떻습니까?전화하는 것이 주목할 가치가 있습니다. Hash::[] 사용하여 splat 부분적으로 평면화된 배열(a6) 이다 ~ 아니다 전화하는 것과 똑같다 Hash[a4]:

Hash[*a6] # => ArgumentError: odd number of arguments for Hash

미리 평면화된 배열, 여전히 중첩됨(가져오는 대체 방법) a6):

하지만 이것이 우리가 처음에 배열을 얻은 방법이라면 어떨까요?(즉, 그에 비해 a1, 그것은 우리의 입력 데이터였습니다. 이번에는 일부 데이터가 배열이나 다른 객체일 수 있습니다.) 우리는 다음을 보았습니다. Hash[*a6] 작동하지 않지만, 여전히 동작을 얻고 싶다면 어떻게 해야 할까요? 마지막 요소 (중요한!아래 참조)는 nil 값?

이러한 상황에서도 다음을 사용하여 이를 수행할 수 있는 방법이 있습니다. Enumerable#each_slice 키/값으로 돌아가려면 한 쌍 외부 배열의 요소로:

a7 = a6.each_slice(2).to_a
# => [["apple", 1], ["banana", 2], [["orange", "seedless"], 3], ["durian"]] 

이는 결국 우리에게 "동일한" 에게 a4, 하지만 동일한 값:

a4.equal?(a7) # => false
a4 == a7      # => true

따라서 우리는 다시 사용할 수 있습니다 Hash::[]:

Hash[a7] # => {"apple"=>1, "banana"=>2, ["orange", "seedless"]=>3, "durian"=>nil}
# or Hash[a6.each_slice(2).to_a]

하지만 문제가 있습니다!

중요한 점은 each_slice(2) 솔루션은 다음과 같은 경우에만 상황을 정상으로 되돌립니다. 마지막 키에는 값이 누락되었습니다.나중에 추가 키/값 쌍을 추가한 경우:

a4_plus = a4.dup # just to have a new-but-related variable name
a4_plus.push(['lychee', 4])
# => [["apple",                1],
#     ["banana",               2],
#     [["orange", "seedless"], 3], # multi-value key
#     ["durian"],              # missing value
#     ["lychee", 4]]           # new well-formed item

a6_plus = a4_plus.flatten(1)
# => ["apple", 1, "banana", 2, ["orange", "seedless"], 3, "durian", "lychee", 4]

a7_plus = a6_plus.each_slice(2).to_a
# => [["apple",                1],
#     ["banana",               2],
#     [["orange", "seedless"], 3], # so far so good
#     ["durian",               "lychee"], # oops! key became value!
#     [4]]                     # and we still have a key without a value

a4_plus == a7_plus # => false, unlike a4 == a7

그리고 여기서 얻을 수 있는 두 해시는 중요한 면에서 다릅니다.

ap Hash[a4_plus] # prints:
{
                     "apple" => 1,
                    "banana" => 2,
    [ "orange", "seedless" ] => 3,
                    "durian" => nil, # correct
                    "lychee" => 4    # correct
}

ap Hash[a7_plus] # prints:
{
                     "apple" => 1,
                    "banana" => 2,
    [ "orange", "seedless" ] => 3,
                    "durian" => "lychee", # incorrect
                           4 => nil       # incorrect
}

(메모:나는 사용하고있다 awesome_print'에스 ap 여기에 구조를 더 쉽게 표시하기 위해;이에 대한 개념적 요구 사항은 없습니다.)

그래서 each_slice 불균형 플랫 입력에 대한 솔루션은 불균형 비트가 맨 끝에 있는 경우에만 작동합니다.


시사점:

  1. 가능할 때마다 이러한 항목에 대한 입력을 다음과 같이 설정하십시오. [key, value] 쌍(외부 배열의 각 항목에 대한 하위 배열).
  2. 실제로 그렇게 할 수 있을 때, #to_h 또는 Hash::[] 둘 다 작동합니다.
  3. 당신이 할 수 없다면, Hash::[] 스플랫(*) 작동할 것입니다. 입력이 균형을 이루는 한.
  4. 불안정한 그리고 평평한 배열을 입력으로 사용하는 경우 이것이 합리적으로 작동하는 유일한 방법은 마지막 value 항목이 누락된 유일한 항목입니다.

참고 사항:추가할 가치가 있다고 느끼기 때문에 이 답변을 게시합니다. 기존 답변 중 일부에는 잘못된 정보가 있고 (내가 읽은) 여기에서 하려고 노력하는 것만큼 완전한 답변을 제공한 답변은 없습니다.도움이 되었기를 바랍니다.그럼에도 불구하고 나는 나보다 앞서 이 답변의 일부에 영감을 준 여러 사람들에게 감사를 표합니다.

다음과 같은 배열이 있다면 -

data = [["foo",1,2,3,4],["bar",1,2],["foobar",1,"*",3,5,:foo]]

각 배열의 첫 번째 요소가 해시의 키가 되고 나머지 요소가 값 배열이 되도록 하려면 다음과 같이 할 수 있습니다.

data_hash = Hash[data.map { |key| [key.shift, key] }]

#=>{"foo"=>[1, 2, 3, 4], "bar"=>[1, 2], "foobar"=>[1, "*", 3, 5, :foo]}

이것이 최선의 방법인지는 확실하지 않지만 다음과 같이 작동합니다.

a = ["apple", 1, "banana", 2]
m1 = {}
for x in (a.length / 2).times
  m1[a[x*2]] = a[x*2 + 1]
end

b = [["apple", 1], ["banana", 2]]
m2 = {}
for x,y in b
  m2[x] = y
end

숫자 값이 seq 인덱스인 경우 더 간단한 방법을 사용할 수 있습니다.내 코드 제출은 다음과 같습니다. 내 Ruby는 약간 녹슬었습니다.

   input = ["cat", 1, "dog", 2, "wombat", 3]
   hash = Hash.new
   input.each_with_index {|item, index|
     if (index%2 == 0) hash[item] = input[index+1]
   }
   hash   #=> {"cat"=>1, "wombat"=>3, "dog"=>2}
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top