Ruby에서 해시의 모든 값 변경


170

값 앞뒤에 '%'를 추가하기 위해 해시의 모든 값을 변경하고 싶습니다.

{ :a=>'a' , :b=>'b' }

로 변경해야합니다

{ :a=>'%a%' , :b=>'%b%' }

가장 좋은 방법은 무엇입니까?


1
원본 문자열 객체를 변경하거나 원본의 객체를 변경하거나 아무것도 변경하지 않으려면 명확히하십시오.
Phrogz

1
그렇다면 당신은 오답을 받아 들였습니다. (개인적으로 객체를 변경하는 대신 함수형 프로그래밍을 옹호하므로 @pst의 의도에 위배되지 않습니다.)
Phrogz

그러나 여전히 접근 방식은 훌륭했습니다
TheReverseFlick

답변:


178

실제 문자열 자체가 제자리에서 변경되도록하려면 (같은 문자열 객체에 대한 다른 참조에 영향을 줄 수 있으며)

# Two ways to achieve the same result (any Ruby version)
my_hash.each{ |_,str| str.gsub! /^|$/, '%' }
my_hash.each{ |_,str| str.replace "%#{str}%" }

해시를 제자리에서 변경하고 싶지만 문자열에 영향을 미치지 않으려면 (새 문자열을 얻으려면) :

# Two ways to achieve the same result (any Ruby version)
my_hash.each{ |key,str| my_hash[key] = "%#{str}%" }
my_hash.inject(my_hash){ |h,(k,str)| h[k]="%#{str}%"; h }

새로운 해시를 원한다면 :

# Ruby 1.8.6+
new_hash = Hash[*my_hash.map{|k,str| [k,"%#{str}%"] }.flatten]

# Ruby 1.8.7+
new_hash = Hash[my_hash.map{|k,str| [k,"%#{str}%"] } ]

1
@Andrew Marshall 맞습니다. 감사합니다. Ruby 1.8에서는 Hash.[]배열 쌍의 배열을 허용하지 않으므로 짝수 개의 직접 인수가 필요합니다 (따라서 앞뒤로).
Phrogz

2
실제로, Hash. [key_value_pairs]는 1.8.7에 도입되었으므로 Ruby 1.8.6 만 스 플랫 앤 플랫 (splat & flatten)이 필요하지 않습니다.
Marc-André Lafortune

2
@Aupajo Hash#each는 키와 값을 모두 블록에 제공합니다. 이 경우 키에 신경 쓰지 않았으므로 유용한 이름은 지정하지 않았습니다. 변수 이름은 밑줄로 시작 할 수 있으며, 실제로있을 수 있습니다 단지 밑줄. 이 작업을 수행하면 성능 이점이 없으며 첫 번째 블록 값으로 아무것도하지 않는다는 미묘한 자체 문서 참고 사항입니다.
Phrogz

1
말인지 생각 my_hash.inject(my_hash){ |h,(k,str)| h[k]="%#{str}%"; h }블록에서 해시를 반환해야
aceofspades

1
또는 사용하지 않는 키 값에 밑줄을 사용하는 것보다 이해하기 쉬운 each_value 메소드를 사용할 수도 있습니다.
Strand McCutchen

266

Ruby 2.1 이상에서는 할 수 있습니다

{ a: 'a', b: 'b' }.map { |k, str| [k, "%#{str}%"] }.to_h

5
고마워, 실제로 내가 찾던 것이었다. 왜 그렇게 많이지지를 모르는지 모르겠습니다.
simperreault

7
그러나 이것은 매우 느리고 RAM이 배고프다. 입력 해시를 반복하여 중간 배열의 중첩 배열을 생성 한 다음 새 해시로 변환합니다. RAM 피크 사용량을 무시하면 런타임이 훨씬 더 나빠집니다. 다른 답변에서이 위치 내 수정 솔루션을 벤치마킹하면 동일한 반복 횟수에서 2.5 초와 1.5 초를 비교합니다. 루비는 비교적 느린 언어이기 때문에 느린 언어의 느린 비트를 피하는 것은 많은 의미가 있습니다 :-)
Andrew Hodgkinson

2
@AndrewHodgkinson은 일반적으로 런타임 성능에주의를 기울이지 않는다고 동의하지만 동의하지는 않지만 이러한 모든 성능 함정을 추적하지 못하면 루비의 "개발자 생산성 우선"철학에 어긋나 기 시작합니까? 나는 이것이 당신에 대한 의견이 아니라, 루비를 사용하여 이것이 우리에게 가져 오는 궁극적 역설에 대한 일반적인 의견이라고 생각합니다.
elsurudo

4
수수께끼는 : 글쎄, 우리는 이미 루비를 사용하기로 한 결정에서 성능을 포기하고 있습니다. "이 작은 것"은 어떤 차이를 만들어 냅니까? 미끄러운 경사지 아닌가요? 레코드의 경우 가독성 측면에서이 솔루션을 허용 된 답변보다 선호합니다.
elsurudo

1
Ruby 2.4 이상을 사용 #transform_values!하는 경우 sschmeck ( stackoverflow.com/a/41508214/6451879 )에서 지적한 것처럼 사용하기가 훨씬 쉽습니다 .
Finn

127

Ruby 2.4에서는 Hash#transform_values!사용할 수 있는 메소드를 도입했습니다 .

{ :a=>'a' , :b=>'b' }.transform_values! { |v| "%#{v}%" }
# => {:a=>"%a%", :b=>"%b%"} 

정확히 내가 찾던 것!
Dolev

4
물론 Hash#transform_values수신기를 수정하지 않는 (뱅없이 )도 있습니다 . 그렇지 않으면 좋은 답변 감사합니다!
iGEL

1
이건 정말 것이다 감소 의 내 사용 reduce- P
이젤

86

해시 값을 수정하는 가장 좋은 방법은

hash.update(hash){ |_,v| "%#{v}%" }

적은 코드와 명확한 의도. 또한 변경해야하는 값 이상의 새 객체가 할당되지 않기 때문에 더 빠릅니다.


정확히 사실이 아닙니다 : 새로운 문자열이 할당됩니다. 여전히 효과적인 솔루션입니다. +1
Phrogz

@Phrogz 좋은 지적; 나는 대답을 업데이트했다. 모든 값 변환이와 같은 뮤 테이터로 표현 될 수있는 것은 아니기 때문에 일반적으로 값 할당을 피할 수 없습니다 gsub!.
Sim

3
내 대답과 동일하지만 다른 동의어와 함께 나는 update그 의도를보다 잘 전달 한다는 데 동의합니다 merge!. 이것이 가장 좋은 대답이라고 생각합니다.

1
사용하지 않는 경우 k, 사용 _대신.
sekrett

28

좀 더 읽기 쉬운 map단일 요소 해시 배열 reducemerge

the_hash.map{ |key,value| {key => "%#{value}%"} }.reduce(:merge)

2
보다 명확하게 사용Hash[the_hash.map { |key,value| [key, "%#{value}%"] }]
meagar

이것은 값을 업데이트하는 매우 비효율적 인 방법입니다. 모든 값 쌍에 대해 먼저 쌍 Array(for map)을 만든 다음 a 를 만듭니다 Hash. 그런 다음 축소 작업의 각 단계에서 "메모리"를 복제 Hash하고 새 키-값 쌍을 추가합니다. 결승전을 수정하려면 최소한 :merge!in reduce을 사용 하십시오 Hash. 그리고 결국, 당신은 기존 객체의 값을 수정하지 않고 새로운 객체를 만듭니다. 이것은 질문이 아닙니다.
Sim

비어있는 nil경우 반환the_hash
DNNX


16

원본에 부작용을 일으키지 않는 한 가지 방법 :

h = {:a => 'a', :b => 'b'}
h2 = Hash[h.map {|k,v| [k, '%' + v + '%']}]

Hash # mapHash.map해시를 반환하지 않는 이유 (결과 [key,value]쌍의 배열이 새 해시로 변환되는 이유 )를 설명하고 동일한 일반 패턴에 대한 대체 접근 방식을 제공 하므로 흥미롭게 읽힐 수 있습니다 .

행복한 코딩.

[면책 조항 : Hash.mapRuby 2.x에서 의미가 변경 되는지 확실하지 않습니다 ]


7
Matz Hash.map는 Ruby 2.x에서 시맨틱이 변경 되는지 알고 있습니까?
앤드류 그림

1
Hash # [] 메소드는 매우 유용하지만 매우 추악합니다. 같은 방식으로 배열을 해시로 변환하는 더 좋은 방법이 있습니까?
Martijn

15
my_hash.each do |key, value|
  my_hash[key] = "%#{value}%"
end

나는 부작용을 좋아하지 않지만 접근 방식에 +1 :) each_with_objectRuby 1.9 (IIRC)에는 이름에 직접 액세스 할 필요 가 없으며 Map#merge작동 할 수도 있습니다. 복잡한 세부 사항이 어떻게 다른지 잘 모르겠습니다.

1
초기 해시가 수정 됩니다 . 동작이 예상되는 경우 에는 문제가 없지만 "잊어 버린 경우 미묘한 문제가 발생할 수 있습니다. 객체의 가변성을 줄이는 것을 선호하지만 항상 실용적이지는 않을 수도 있습니다. (루비는 "부작용이없는"언어가 아니다 ;-)

8

해시 병합! 가장 깨끗한 해결책입니다

o = { a: 'a', b: 'b' }
o.merge!(o) { |key, value| "%#{ value }%" }

puts o.inspect
> { :a => "%a%", :b => "%b%" }

@MichieldeMare 죄송합니다, 이것을 충분히 테스트하지 않았습니다. 맞습니다. 블록에는 두 개의 매개 변수가 필요합니다. 수정했습니다.

5

다음과 같이 RSpec으로 테스트 한 후 :

describe Hash do
  describe :map_values do
    it 'should map the values' do
      expect({:a => 2, :b => 3}.map_values { |x| x ** 2 }).to eq({:a => 4, :b => 9})
    end
  end
end

다음과 같이 Hash # map_values를 구현할 수 있습니다.

class Hash
  def map_values
    Hash[map { |k, v| [k, yield(v)] }]
  end
end

그런 다음이 기능을 다음과 같이 사용할 수 있습니다.

{:a=>'a' , :b=>'b'}.map_values { |v| "%#{v}%" }
# {:a=>"%a%", :b=>"%b%"}

1

어떤 인플레 이스 변형이 가장 빠른지 궁금하다면 다음과 같습니다.

Calculating -------------------------------------
inplace transform_values! 1.265k  0.7%) i/s -      6.426k in   5.080305s
      inplace update      1.300k  2.7%) i/s -      6.579k in   5.065925s
  inplace map reduce    281.367   1.1%) i/s -      1.431k in   5.086477s
      inplace merge!      1.305k  0.4%) i/s -      6.630k in   5.080751s
        inplace each      1.073k  0.7%) i/s -      5.457k in   5.084044s
      inplace inject    697.178   0.9%) i/s -      3.519k in   5.047857s
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.