Cách tốt nhất để chuyển đổi chuỗi thành biểu tượng trong hàm băm


250

Cách (nhanh nhất / sạch nhất / đơn giản) để chuyển đổi tất cả các khóa trong hàm băm từ chuỗi sang biểu tượng trong Ruby là gì?

Điều này sẽ có ích khi phân tích YAML.

my_hash = YAML.load_file('yml')

Tôi muốn có thể sử dụng:

my_hash[:key] 

Thay vì:

my_hash['key']


80
hash.symbolize_keyshash.deep_symbolize_keysthực hiện công việc nếu bạn đang sử dụng Rails.
Zaz

Josh nếu bạn đã đưa ra nhận xét của bạn vào một câu trả lời, tôi sẽ bỏ phiếu cho bạn. yêu cầu 'rails'; hash.deep_symbolize_keys hoạt động khá tốt trong irb hoặc pry. : D
Douglas G. Allen

Câu trả lời:


239

Trong Ruby> = 2.5 ( tài liệu ) bạn có thể sử dụng:

my_hash.transform_keys(&:to_sym)

Sử dụng phiên bản Ruby cũ hơn? Dưới đây là một lớp lót sẽ sao chép hàm băm thành một lớp mới với các phím được ký hiệu:

my_hash = my_hash.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}

Với Rails bạn có thể sử dụng:

my_hash.symbolize_keys
my_hash.deep_symbolize_keys 

5
À, xin lỗi vì không rõ ràng - tiêm không sửa đổi người gọi. Bạn cần làm my_hash = my_hash.inject ({}) {| memo, (k, v) | ghi nhớ [k.to_sym] = v; ghi nhớ}
Sarah Mei

4
Đó chính xác là những gì tôi đang tìm kiếm. Tôi đã sửa đổi nó một chút và thêm một số dòng để thậm chí tạo các biểu tượng trong băm lồng nhau. Hãy xem ở đây, nếu bạn quan tâm: any-where.de/blog/ruby-hash-convert-opes-keys-to-symbols
Matt

37
Trong Ruby 1.9, bạn có thể sử dụng each_with_objectnhư vậy:my_hash.each_with_object({}){|(k,v), h| h[k.to_sym] = v}
sgtFloyd

8
điều này không xử lý các hàm băm đệ quy ... Tìm một lần nhưng không cho DRY.
baash05

8
@BryanM. Tôi đã tham gia cuộc thảo luận này rất muộn :-) nhưng bạn cũng có thể sử dụng .tapphương pháp để loại bỏ sự cần thiết phải vượt qua memovào cuối. Tôi đã tạo một phiên bản đã được dọn sạch của tất cả các giải pháp (cả các giải pháp đệ quy) gist.github.com/Integralist/9503099
tích hợp

307

Đây là một phương pháp tốt hơn, nếu bạn đang sử dụng Rails:

thông số tượng trưng_key

Kết thúc.

Nếu bạn không, chỉ cần tách mã của họ (nó cũng nằm trong liên kết):

myhash.keys.each do |key|
  myhash[(key.to_sym rescue key) || key] = myhash.delete(key)
end

6
to_options là một bí danh cho sybolize_keys .
ma11hew28

45
Sẽ không tượng trưng cho băm lồng nhau.
oma

3
Tôi đã chuyển liên kết sang symbolize_keysURL mới và đang hoạt động (Rails 3). Ban đầu tôi chỉ sửa URL to_options, nhưng không có tài liệu nào ở liên kết đó. symbolize_keysthực sự có một mô tả, vì vậy tôi đã sử dụng nó để thay thế.
Craig Walker

23
deep_symbolize_keys! . Hoạt động trên đường ray 2+
mastaBlasta

14
Đối với những người tò mò làm thế nào để làm ngược lại, hash.stringify_keyslàm việc.
Nick

112

Đối với trường hợp cụ thể của YAML trong Ruby, nếu các khóa bắt đầu bằng ' :', chúng sẽ tự động được đặt làm biểu tượng.

yêu cầu 'yaml'
yêu cầu 'pp'
yaml_str = "
kết nối:
  - máy chủ: host1.example.com
    cổng: 10000
  - máy chủ: host2.example.com
    cổng: 20000
"
yaml_sym = "
: kết nối:
  -: máy chủ: máy chủ1.example.com
    : cổng: 10000
  -: máy chủ: máy chủ2.example.com
    : cổng: 20000
"
pp yaml_str = YAML.load (yaml_str)
đặt yaml_str.keys.first. class
pp yaml_sym = YAML.load (yaml_sym)
đặt yaml_sym.keys.first. class

Đầu ra:

# /opt/ruby-1.8.6-p287/bin/ruby ~ / test.rb
{"kết nối" =>
  [{"cổng" => 10000, "máy chủ" => "máy chủ1.example.com"},
   {"cổng" => 20000, "máy chủ" => "host2.example.com"}]}
Chuỗi
{: kết nối =>
  [{: port => 10000 ,: máy chủ => "máy chủ1.example.com"},
   {: port => 20000 ,: máy chủ => "host2.example.com"}]}
Biểu tượng

15
Ngọt! Có cách nào để đặt YAML#load_filemặc định tất cả các khóa thành biểu tượng thay vì chuỗi không phải bắt đầu mọi khóa bằng dấu hai chấm không?
ma11hew28

63

Thậm chí ngắn gọn hơn:

Hash[my_hash.map{|(k,v)| [k.to_sym,v]}]

6
Đây dường như là sự lựa chọn rõ ràng.
Casey Watson

12
Nó không tượng trưng cho băm lồng nhau.
rgtk

15
Một phiên bản dễ đọc hơn:my_hash.map { |k, v| [k.to_sym, v] }.to_h
Dennis

60

nếu bạn đang sử dụng Rails, việc này đơn giản hơn nhiều - bạn có thể sử dụng HashWithIndifferentAccess và truy cập các khóa cả dưới dạng Chuỗi và dưới dạng Biểu tượng:

my_hash.with_indifferent_access 

Xem thêm:

http://api.rubyonrails.org/groupes/ActiveSupport/HashWithIndifferentAccess.html


Hoặc bạn có thể sử dụng Gem "Facets of Ruby" tuyệt vời, chứa rất nhiều phần mở rộng cho các lớp Thư viện tiêu chuẩn và Ruby Core.

  require 'facets'
  > {'some' => 'thing', 'foo' => 'bar'}.symbolize_keys
    =>  {:some=>"thing", :foo=>"bar}

xem thêm: http://rubyworks.github.io/rubyfaux/?doc=http://rubyworks.github.io/facets/docs/facets-2.9.3/core.json#api- class-Hash


2
Thật ra điều đó ngược lại. Nó chuyển đổi từ biểu tượng thành một chuỗi. Để chuyển đổi thành biểu tượng, hãy sử dụng my_hash.symbolize_keys
Espen

#symbolize_keys chỉ hoạt động trong Rails - không phải trong Ruby / irb đơn giản. Cũng lưu ý rằng #symbolize_keys không hoạt động trên các giá trị băm được lồng sâu.
Tilo

54

Ruby 2.5.0bạn có thể sử dụng Hash#transform_keyshoặc Hash#transform_keys!.

{'a' => 1, 'b' => 2}.transform_keys(&:to_sym) #=> {:a => 1, :b => 2}

Cũng hoạt động với biến đổi_values ​​apidock.com/rails/Hash/transform_values . Điều đó thực sự tốt vì dường như không có sự tương đương để sửa đổi các giá trị như có stringify_keyshoặc symbolize_keys.
Mike Vallano

Có cách nào để biểu tượng sâu cho các phím
Abhay Kumar

Đây nên là giải pháp được lựa chọn sau năm 2018.
Ivan Wang


26

Đây là một cách để tượng trưng sâu sắc cho một đối tượng

def symbolize(obj)
    return obj.inject({}){|memo,(k,v)| memo[k.to_sym] =  symbolize(v); memo} if obj.is_a? Hash
    return obj.inject([]){|memo,v    | memo           << symbolize(v); memo} if obj.is_a? Array
    return obj
end

thật tuyệt, tôi sẽ đi với cái này ngay cả khi tôi sẽ đổi tên nó thành deep_symbolize :)
PierrOz

20

Tôi thực sự thích đá quý Mash .

bạn có thể làm mash['key'], hoặc mash[:key], hoặcmash.key


2
Đó là một viên ngọc rất tuyệt! Làm cho làm việc với băm rất thoải mái. Cảm ơn vì điều đó!
asaaki

Đẹp quá đơn giản. Phát triển mới về dự án này đang được tiếp tục ở Hashie ( github.com/intridea/hashie ) nhưng nó vẫn hoạt động khá giống nhau: github.com/intridea/hashie#keyconversion
jpalmieri 19/8/15

19

Nếu bạn đang sử dụng json và muốn sử dụng nó như một hàm băm, trong lõi Ruby, bạn có thể làm điều đó:

json_obj = JSON.parse(json_str, symbolize_names: true)

Symbolize_names : Nếu được đặt thành true, trả về các ký hiệu cho tên (khóa) trong một đối tượng JSON. Nếu không, chuỗi được trả lại. Chuỗi là mặc định.

Tài liệu: Json # parse Symbolize_names


symbol_hash = JSON.parse(JSON.generate(YAML.safe_load(FILENAME)), symbolize_names: true)là một cách khá DRY (nhưng không hiệu quả) để nhanh chóng lấy băm với các khóa lồng nhau làm biểu tượng nếu đến từ tệp YAML.
Cameron Gagnon

12

params.symbolize_keyscũng sẽ làm việc Phương pháp này biến các khóa băm thành các ký hiệu và trả về một hàm băm mới.


26
Phương pháp đó không phải là cốt lõi của Ruby. Đây là một phương pháp Rails.
Ricardo Acras

10

Một sửa đổi cho câu trả lời @igorsales

class Object
  def deep_symbolize_keys
    return self.inject({}){|memo,(k,v)| memo[k.to_sym] = v.deep_symbolize_keys; memo} if self.is_a? Hash
    return self.inject([]){|memo,v    | memo           << v.deep_symbolize_keys; memo} if self.is_a? Array
    return self
  end
end

Sẽ rất hữu ích nếu bạn đưa vào lý do tại sao bạn sửa đổi đối tượng để mọi người quét qua câu trả lời.
Dbz

9

Trong Rails bạn có thể sử dụng:

{'g'=> 'a', 2 => {'v' => 'b', 'x' => { 'z' => 'c'}}}.deep_symbolize_keys!

Chuyển đổi sang:

{:g=>"a", 2=>{:v=>"b", :x=>{:z=>"c"}}}

3
deep_symbolize_keysđược thêm vào phần mở rộng Hash của Rails , nhưng nó không phải là một phần của lõi Ruby.
Ryan Crispin Heneise


7

Đây là một lớp lót của tôi cho băm lồng nhau

def symbolize_keys(hash)
  hash.each_with_object({}) { |(k, v), h| h[k.to_sym] = v.is_a?(Hash) ? symbolize_keys(v) : v }
end

FYI, chỉ hoạt động cho Rails. Trong trường hợp đó, HashWithIndifferentAccess có thể là sự thay thế tốt hơn.
Adam Grant

6

Trong trường hợp lý do bạn cần làm điều này là do dữ liệu của bạn ban đầu đến từ JSON, bạn có thể bỏ qua bất kỳ phân tích cú pháp này bằng cách chuyển qua :symbolize_namestùy chọn khi nhập JSON.

Không cần Rails và hoạt động với Ruby> 1.9

JSON.parse(my_json, :symbolize_names => true)

4

Bạn có thể lười biếng, và gói nó trong lambda:

my_hash = YAML.load_file('yml')
my_lamb = lambda { |key| my_hash[key.to_s] }

my_lamb[:a] == my_hash['a'] #=> true

Nhưng điều này sẽ chỉ làm việc để đọc từ hàm băm - không viết.

Để làm điều đó, bạn có thể sử dụng Hash#merge

my_hash = Hash.new { |h,k| h[k] = h[k.to_s] }.merge(YAML.load_file('yml'))

Khối init sẽ chuyển đổi các khóa một lần theo yêu cầu, mặc dù nếu bạn cập nhật giá trị cho phiên bản chuỗi của khóa sau khi truy cập phiên bản ký hiệu, phiên bản ký hiệu sẽ không được cập nhật.

irb> x = { 'a' => 1, 'b' => 2 }
#=> {"a"=>1, "b"=>2}
irb> y = Hash.new { |h,k| h[k] = h[k.to_s] }.merge(x)
#=> {"a"=>1, "b"=>2}
irb> y[:a]  # the key :a doesn't exist for y, so the init block is called
#=> 1
irb> y
#=> {"a"=>1, :a=>1, "b"=>2}
irb> y[:a]  # the key :a now exists for y, so the init block is isn't called
#=> 1
irb> y['a'] = 3
#=> 3
irb> y
#=> {"a"=>3, :a=>1, "b"=>2}

Bạn cũng có thể có khối init không cập nhật hàm băm, điều này sẽ bảo vệ bạn khỏi loại lỗi đó, nhưng bạn vẫn dễ bị tổn thương ngược lại - cập nhật phiên bản biểu tượng sẽ không cập nhật phiên bản chuỗi:

irb> q = { 'c' => 4, 'd' => 5 }
#=> {"c"=>4, "d"=>5}
irb> r = Hash.new { |h,k| h[k.to_s] }.merge(q)
#=> {"c"=>4, "d"=>5}
irb> r[:c] # init block is called
#=> 4
irb> r
#=> {"c"=>4, "d"=>5}
irb> r[:c] # init block is called again, since this key still isn't in r
#=> 4
irb> r[:c] = 7
#=> 7
irb> r
#=> {:c=>7, "c"=>4, "d"=>5}

Vì vậy, điều cần cẩn thận với những điều này là chuyển đổi giữa hai hình thức chính. Gắn bó với một.


3

Sẽ làm một cái gì đó như công việc sau đây?

new_hash = Hash.new
my_hash.each { |k, v| new_hash[k.to_sym] = v }

Nó sẽ sao chép hàm băm, nhưng bạn sẽ không quan tâm đến điều đó hầu hết thời gian. Có lẽ có một cách để làm điều đó mà không cần sao chép tất cả dữ liệu.


3

một fwiw một lớp lót ngắn hơn:

my_hash.inject({}){|h,(k,v)| h.merge({ k.to_sym => v}) }

2

Còn cái này thì sao:

my_hash = HashWithIndifferentAccess.new(YAML.load_file('yml'))

# my_hash['key'] => "val"
# my_hash[:key]  => "val"

2

Điều này dành cho những người sử dụng mrubyvà không có bất kỳ symbolize_keysphương pháp nào được xác định:

class Hash
  def symbolize_keys!
    self.keys.each do |k|
      if self[k].is_a? Hash
        self[k].symbolize_keys!
      end
      if k.is_a? String
        raise RuntimeError, "Symbolizing key '#{k}' means overwrite some data (key :#{k} exists)" if self[k.to_sym]
        self[k.to_sym] = self[k]
        self.delete(k)
      end
    end
    return self
  end
end

Phương pháp:

  • chỉ tượng trưng cho các phím String
  • nếu tượng trưng cho một chuỗi có nghĩa là mất một số thông tin (ghi đè lên một phần của hàm băm) nâng cao một RuntimeError
  • tượng trưng cũng có băm đệ quy
  • trả lại hàm băm tượng trưng
  • làm việc tại chỗ!

1
Có lỗi chính tả trong phương pháp của bạn, bạn quên !trong symbolize_keys. Nếu không thì hoạt động tốt.
Ka Mok

1

Mảng chúng tôi muốn thay đổi.

chuỗi = ["HTML", "CSS", "JavaScript", "Python", "Ruby"]

Tạo một biến mới dưới dạng một mảng trống để chúng ta có thể ".push" các ký hiệu trong.

ký hiệu = []

Đây là nơi chúng ta định nghĩa một phương thức với một khối.

chuỗi.each {| x | bols.push (x.itern)}

Kết thúc mã.

Vì vậy, đây có lẽ là cách đơn giản nhất để chuyển đổi chuỗi thành biểu tượng trong (các) mảng của bạn trong Ruby. Tạo một chuỗi các chuỗi sau đó tạo một biến mới và đặt biến thành một mảng trống. Sau đó chọn từng phần tử trong mảng đầu tiên bạn đã tạo bằng phương thức ".each". Sau đó, sử dụng mã khối để ".push" tất cả các phần tử trong mảng mới của bạn và sử dụng ".i INTERN hoặc .to_sym" để chuyển đổi tất cả các phần tử thành ký hiệu.

Các biểu tượng nhanh hơn vì chúng tiết kiệm nhiều bộ nhớ hơn trong mã của bạn và bạn chỉ có thể sử dụng chúng một lần. Các biểu tượng được sử dụng phổ biến nhất cho các khóa trong hàm băm rất tuyệt vời. Tôi không phải là lập trình viên ruby ​​giỏi nhất nhưng dạng mã này đã giúp tôi rất nhiều. Nếu ai biết cách nào tốt hơn xin vui lòng chia sẻ và bạn cũng có thể sử dụng phương pháp này để băm!


2
Câu hỏi liên quan đến băm, không phải mảng.
Richard_G

1

Nếu bạn muốn giải pháp ruby ​​vanilla và như tôi không có quyền truy cập vào ActiveSupportđây là giải pháp tượng trưng sâu sắc (rất giống với giải pháp trước đây)

    def deep_convert(element)
      return element.collect { |e| deep_convert(e) } if element.is_a?(Array)
      return element.inject({}) { |sh,(k,v)| sh[k.to_sym] = deep_convert(v); sh } if element.is_a?(Hash)
      element
    end

1

Bắt đầu trên Psych 3.0, bạn có thể thêm tùy chọn Symbolize_names:

Psych.load("---\n foo: bar") # => {"foo"=>"bar"}

Psych.load("---\n foo: bar", symbolize_names: true) # => {:foo=>"bar"}

Lưu ý: nếu bạn có phiên bản Tâm lý thấp hơn 3.0 symbolize_names:sẽ bị âm thầm bỏ qua.

Ubuntu 18.04 của tôi bao gồm nó ra khỏi hộp với ruby ​​2.5.1p57


0
ruby-1.9.2-p180 :001 > h = {'aaa' => 1, 'bbb' => 2}
 => {"aaa"=>1, "bbb"=>2} 
ruby-1.9.2-p180 :002 > Hash[h.map{|a| [a.first.to_sym, a.last]}]
 => {:aaa=>1, :bbb=>2}

Bạn có thể gói atrong ngoặc để phân tách đối số khối để làm cho điều này thậm chí còn ngắn gọn hơn. Xem câu trả lời của tôi chẳng hạn.
Michael Barton

0

Đây không chính xác là một lớp lót, nhưng nó biến tất cả các khóa chuỗi thành các ký hiệu, cũng là các ký tự lồng nhau:

def recursive_symbolize_keys(my_hash)
  case my_hash
  when Hash
    Hash[
      my_hash.map do |key, value|
        [ key.respond_to?(:to_sym) ? key.to_sym : key, recursive_symbolize_keys(value) ]
      end
    ]
  when Enumerable
    my_hash.map { |value| recursive_symbolize_keys(value) }
  else
    my_hash
  end
end

0

Tôi thích lớp lót này, khi tôi không sử dụng Rails, vì sau đó tôi không phải tạo hàm băm thứ hai và giữ hai bộ dữ liệu trong khi tôi xử lý nó:

my_hash = { "a" => 1, "b" => "string", "c" => true }

my_hash.keys.each { |key| my_hash[key.to_sym] = my_hash.delete(key) }

my_hash
=> {:a=>1, :b=>"string", :c=>true}

Hash # xóa trả về giá trị của khóa đã xóa


0

Hash # Deep numkey cũng là một lựa chọn tốt, đặc biệt là:

  • nếu bạn tìm thấy sử dụng cho đường khác từ các khía cạnh trong dự án của bạn,
  • nếu bạn thích khả năng đọc mã hơn một lớp mã hóa.

Mẫu vật:

require 'facets/hash/deep_rekey'
my_hash = YAML.load_file('yml').deep_rekey

0

Trong ruby ​​tôi thấy đây là cách đơn giản và dễ hiểu nhất để biến các chuỗi chuỗi trong băm thành các ký hiệu:

my_hash.keys.each { |key| my_hash[key.to_sym] = my_hash.delete(key)}

Đối với mỗi khóa trong hàm băm, chúng ta gọi xóa trên nó, loại bỏ nó khỏi hàm băm (cũng xóa trả về giá trị được liên kết với khóa đã bị xóa) và chúng ta ngay lập tức đặt giá trị này bằng với khóa được ký hiệu.


0

Tương tự như các giải pháp trước đây nhưng được viết hơi khác một chút.

  • Điều này cho phép băm được lồng và / hoặc có mảng.
  • Nhận chuyển đổi các khóa thành một chuỗi như một phần thưởng.
  • Mã không biến đổi băm được truyền vào.

    module HashUtils
      def symbolize_keys(hash)
        transformer_function = ->(key) { key.to_sym }
        transform_keys(hash, transformer_function)
      end
    
      def stringify_keys(hash)
        transformer_function = ->(key) { key.to_s }
        transform_keys(hash, transformer_function)
      end
    
      def transform_keys(obj, transformer_function)
        case obj
        when Array
          obj.map{|value| transform_keys(value, transformer_function)}
        when Hash
          obj.each_with_object({}) do |(key, value), hash|
            hash[transformer_function.call(key)] = transform_keys(value, transformer_function)
          end
        else
          obj
        end
      end
    end
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.