Bir karmadaki anahtarlar ve değerler nasıl değiştirilir?


154

Bir Kare'deki anahtarları ve değerleri nasıl değiştirebilirim?

Aşağıdaki karma var:

{:a=>:one, :b=>:two, :c=>:three}

dönüştürmek istiyorum ki:

{:one=>:a, :two=>:b, :three=>:c}

Kullanımı mapoldukça sıkıcı görünüyor. Daha kısa bir çözüm var mı?

Yanıtlar:


280

Ruby, Hash için, ters çevrilmiş gibi bir karma işlemesi yapmanıza olanak tanıyan bir yardımcı yönteme sahiptir (özünde, değerlere anahtarlara erişmenize izin vererek):

{a: 1, b: 2, c: 3}.key(1)
=> :a

Ters çevrilmiş karmayı korumak istiyorsanız, Hash # invert çoğu durumda çalışmalıdır:

{a: 1, b: 2, c: 3}.invert
=> {1=>:a, 2=>:b, 3=>:c}

FAKAT...

Yinelenen değerleriniz varsa, değerlerinizin invertson gerçekleşmesi dışında tümünü yok eder (çünkü yineleme sırasında bu anahtarın yeni değerini değiştirmeye devam eder). Benzer şekilde, keyyalnızca ilk maçı döndürür:

{a: 1, b: 2, c: 2}.key(2)
=> :b

{a: 1, b: 2, c: 2}.invert
=> {1=>:a, 2=>:c}

Yani, değerleriniz benzersizse kullanabilirsiniz Hash#invert. Değilse, tüm değerleri bir dizi olarak tutabilirsiniz, örneğin:

class Hash
  # like invert but not lossy
  # {"one"=>1,"two"=>2, "1"=>1, "2"=>2}.inverse => {1=>["one", "1"], 2=>["two", "2"]} 
  def safe_invert
    each_with_object({}) do |(key,value),out| 
      out[value] ||= []
      out[value] << key
    end
  end
end

Not: Bu test kodları artık GitHub'da .

Veya:

class Hash
  def safe_invert
    self.each_with_object({}){|(k,v),o|(o[v]||=[])<<k}
  end
end

4
each_with_objectburada daha mantıklı inject.
Andrew Marshall

böylece olur each_with_object({}){ |i,o|k,v = *i; o[v] ||=[]; o[v] << k}... güzel
Nigel Thorne

3
Aman Tanrım. yapabileceğini bilmiyordum | (anahtar, değer), dışarı |. Bu harika, anahtar ve değer yerine o diziden nefret ediyorum. Çok teşekkürler
Iuri G.

63

Eminim bir tane vardır! Ruby'de işleri yapmanın her zaman daha kısa bir yolu vardır!

Oldukça basit, sadece kullanın Hash#invert:

{a: :one, b: :two, c: :three}.invert
=> {:one=>:a, :two=>:b, :three=>:c}

Et voilà!


4
Aynı değerler karma değerinizde birden fazla görünüyorsa Karma # invert çalışmaz.
Tilo

2
files = {
  'Input.txt' => 'Randy',
  'Code.py' => 'Stan',
  'Output.txt' => 'Randy'
}

h = Hash.new{|h,k| h[k] = []} # Create hash that defaults unknown keys to empty an empty list
files.map {|k,v| h[v]<< k} #append each key to the list at a known value
puts h

Bu, yinelenen değerleri de işleyecektir.


1
Her adımda neler olduğunu açıklayabilir misiniz?
Sajjad Murtaza

Şahsen hash varsayılan değer davranışını ayarlamaktan kaçının. Bu karma ne olursa olsun kod vermek ne olursa olsun bir karma bu şekilde davranmasını beklemeyeceğini endişe ve daha sonra bazı sinsi hataya neden olabilir. Bu endişeyi gerçekten haklı çıkaramıyorum. Bu görmezden gelemeyeceğim bir şüphe. En az sürpriz ilkesi?
Nigel Thorne

Kodla yanıt verirken, kodun nasıl çalıştığını ve neden uygun çözüm olduğunu açıklamak gerçekten önemlidir. Amaç sadece acil sorunu çözmekle kalmayıp eğitmektir.
Tin Man

1
# this doesn't looks quite as elegant as the other solutions here,
# but if you call inverse twice, it will preserve the elements of the original hash

# true inversion of Ruby Hash / preserves all elements in original hash
# e.g. hash.inverse.inverse ~ h

class Hash

  def inverse
    i = Hash.new
    self.each_pair{ |k,v|
      if (v.class == Array)
        v.each{ |x|
          i[x] = i.has_key?(x) ? [k,i[x]].flatten : k
        }
      else
        i[v] = i.has_key?(v) ? [k,i[v]].flatten : k
      end
    }
    return i
  end

end

Hash#inverse sana verir:

 h = {a: 1, b: 2, c: 2}
 h.inverse
  => {1=>:a, 2=>[:c, :b]}
 h.inverse.inverse
  => {:a=>1, :c=>2, :b=>2}  # order might not be preserved
 h.inverse.inverse == h
  => true                   # true-ish because order might change

yerleşik invertyöntem kırık ise:

 h.invert
  => {1=>:a, 2=>:c}    # FAIL
 h.invert.invert == h 
  => false             # FAIL

1

Dizi Kullanımı

input = {:key1=>"value1", :key2=>"value2", :key3=>"value3", :key4=>"value4", :key5=>"value5"}
output = Hash[input.to_a.map{|m| m.reverse}]

Karma Kullanımı

input = {:key1=>"value1", :key2=>"value2", :key3=>"value3", :key4=>"value4", :key5=>"value5"}
output = input.invert

1

Anahtarların benzersiz olduğu bir karma varsa, Hash # invert'i kullanabilirsiniz :

> {a: 1, b: 2, c: 3}.invert
=> {1=>:a, 2=>:b, 3=>:c} 

Ancak, benzersiz olmayan anahtarlarınız varsa, yalnızca son görülen anahtarların tutulacağı durumlarda çalışmaz:

> {a: 1, b: 2, c: 3, d: 3, e: 2, f: 1}.invert
=> {1=>:f, 2=>:e, 3=>:d}

Benzersiz olmayan anahtarlara sahip bir karma varsa, şunları yapabilirsiniz:

> hash={a: 1, b: 2, c: 3, d: 3, e: 2, f: 1}
> hash.each_with_object(Hash.new { |h,k| h[k]=[] }) {|(k,v), h| 
            h[v] << k
            }     
=> {1=>[:a, :f], 2=>[:b, :e], 3=>[:c, :d]}

Karma değerleri zaten dizilerse şunları yapabilirsiniz:

> hash={ "A" => [14, 15, 16], "B" => [17, 15], "C" => [35, 15] }
> hash.each_with_object(Hash.new { |h,k| h[k]=[] }) {|(k,v), h| 
            v.map {|t| h[t] << k}
            }   
=> {14=>["A"], 15=>["A", "B", "C"], 16=>["A"], 17=>["B"], 35=>["C"]}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.