Mengapa menggunakan simbol sebagai kunci hash di Ruby?

162

Banyak kali orang menggunakan simbol sebagai kunci dalam hash Ruby.

Apa keuntungan menggunakan string?

Misalnya:

hash[:name]

vs.

hash['name']
Maks
sumber

Jawaban:

227

TL; DR:

Menggunakan simbol tidak hanya menghemat waktu ketika melakukan perbandingan, tetapi juga menghemat memori, karena mereka hanya disimpan sekali.

Simbol Ruby tidak dapat diubah (tidak dapat diubah), yang membuat pencarian sesuatu menjadi lebih mudah

Jawaban singkat (ish):

Menggunakan simbol tidak hanya menghemat waktu ketika melakukan perbandingan, tetapi juga menghemat memori, karena mereka hanya disimpan sekali.

Simbol di Ruby pada dasarnya adalah "string tidak berubah" .. itu berarti bahwa mereka tidak dapat diubah, dan itu menyiratkan bahwa simbol yang sama ketika direferensikan berulang kali di seluruh kode sumber Anda, selalu disimpan sebagai entitas yang sama, misalnya memiliki id objek yang sama .

String di sisi lain bisa berubah , mereka dapat diubah kapan saja. Ini menyiratkan bahwa Ruby perlu menyimpan setiap string yang Anda sebutkan di seluruh kode sumber Anda di entitas yang terpisah itu, misalnya jika Anda memiliki string "nama" beberapa kali yang disebutkan dalam kode sumber Anda, Ruby perlu menyimpan semua ini dalam objek String yang terpisah, karena mereka mungkin berubah nanti (itulah sifat dari string Ruby).

Jika Anda menggunakan string sebagai kunci hash, Ruby perlu mengevaluasi string dan melihat isinya (dan menghitung fungsi hash pada itu) dan membandingkan hasilnya dengan nilai (hash) dari kunci yang sudah disimpan di hash .

Jika Anda menggunakan simbol sebagai kunci Hash, itu tersirat bahwa itu tidak dapat diubah, jadi Ruby pada dasarnya hanya dapat melakukan perbandingan dari (fungsi hash dari) objek-id terhadap (hash) objek-id dari kunci yang sudah disimpan dalam Hash. (lebih cepat)

Kelemahan: Setiap simbol menggunakan slot di tabel simbol juru bahasa Ruby, yang tidak pernah dirilis. Simbol tidak pernah dikumpulkan dari sampah. Jadi kasus sudut adalah ketika Anda memiliki sejumlah besar simbol (misalnya yang dihasilkan secara otomatis). Dalam hal ini Anda harus mengevaluasi bagaimana ini mempengaruhi ukuran penerjemah Ruby Anda.

Catatan:

Jika Anda melakukan perbandingan string, Ruby dapat membandingkan simbol hanya dengan id objeknya, tanpa harus mengevaluasinya. Itu jauh lebih cepat daripada membandingkan string, yang perlu dievaluasi.

Jika Anda mengakses hash, Ruby selalu menerapkan fungsi hash untuk menghitung "kunci hash" dari kunci apa pun yang Anda gunakan. Anda dapat membayangkan sesuatu seperti hash MD5. Dan kemudian Ruby membandingkan "kunci hash" itu satu sama lain.

Jawaban panjang:

https://web.archive.org/web/20180709094450/http://www.reactive.io/tips/2009/01/11/the-difference-between-ruby-symbols-and-strings

http://www.randomhacks.net.s3-website-us-east-1.amazonaws.com/2007/01/20/13-ways-of-looking-at-a-ruby-symbol/

Tilo
sumber
5
Fyi, Symbols akan menjadi GCd di versi Ruby berikutnya: bugs.ruby-lang.org/issues/9634
Ajedi32
2
Selain itu, String secara otomatis dibekukan saat digunakan sebagai kunci Hash di Ruby. Jadi tidak sepenuhnya benar bahwa String bisa berubah ketika berbicara tentang mereka dalam konteks ini.
Ajedi32
1
Wawasan hebat tentang topik & Tautan pertama di bagian "Jawaban panjang" dihapus atau dimigrasikan.
Hbksagar
2
Simbol adalah sampah yang dikumpulkan di Ruby 2.2
Marc-André Lafortune
2
Jawaban bagus! Di sisi trolling, "jawaban singkat" Anda juga cukup panjang. ;)
technophyle
22

Alasannya adalah efisiensi, dengan beberapa keuntungan lebih dari sebuah String:

  1. Simbol tidak berubah, jadi pertanyaan "apa yang terjadi jika kunci berubah?" tidak perlu ditanyakan.
  2. String digandakan dalam kode Anda dan biasanya akan mengambil lebih banyak ruang di memori.
  3. Pencarian hash harus menghitung hash tombol untuk membandingkannya. Ini O(n)untuk String dan konstanta untuk Simbol.

Selain itu, Ruby 1.9 memperkenalkan sintaks yang disederhanakan hanya untuk hash dengan kunci simbol (misalnya h.merge(foo: 42, bar: 6)), dan Ruby 2.0 memiliki argumen kata kunci yang hanya berfungsi untuk kunci simbol.

Catatan :

1) Anda mungkin terkejut mengetahui bahwa Ruby memperlakukan Stringkunci secara berbeda dari jenis lainnya. Memang:

s = "foo"
h = {}
h[s] = "bar"
s.upcase!
h.rehash   # must be called whenever a key changes!
h[s]   # => nil, not "bar"
h.keys
h.keys.first.upcase!  # => TypeError: can't modify frozen string

Hanya untuk kunci string, Ruby akan menggunakan salinan beku alih-alih objek itu sendiri.

2) Huruf "b", "a", dan "r" disimpan hanya sekali untuk semua kemunculan :bardalam suatu program. Sebelum Ruby 2.2, itu adalah ide yang buruk untuk terus-menerus membuat yang baru Symbolsyang tidak pernah digunakan kembali, karena mereka akan tetap berada di tabel pencarian Simbol global selamanya. Ruby 2.2 akan mengumpulkan sampah, jadi jangan khawatir.

3) Sebenarnya, menghitung hash untuk Simbol tidak memerlukan waktu di Ruby 1.8.x, karena ID objek digunakan secara langsung:

:bar.object_id == :bar.hash # => true in Ruby 1.8.7

Di Ruby 1.9.x, ini telah berubah karena hash berubah dari satu sesi ke sesi lainnya (termasuk yang dari Symbols):

:bar.hash # => some number that will be different next time Ruby 1.9 is ran
Marc-André Lafortune
sumber
+1 untuk catatan bagus Anda! Saya awalnya tidak menyebutkan fungsi hash dalam jawaban saya, karena saya mencoba membuatnya lebih mudah untuk dibaca :)
Tilo
@Tilo: memang, itulah sebabnya saya menulis jawaban saya :-) Saya baru saja mengedit jawaban saya untuk menyebutkan sintaks khusus di Ruby 1.9 dan parameter bernama Ruby 2.0 yang dijanjikan
Marc-André Lafortune
Bisakah Anda menjelaskan bagaimana pencarian Hash konstan untuk Simbol dan O (n) untuk Strings?
Asad Moosvi
7

Re: apa keuntungan dibandingkan menggunakan string?

  • Styling: ini adalah cara Ruby
  • (Sangat) mencari nilai sedikit lebih cepat karena hashing simbol setara dengan hashing integer vs hashing string.

  • Kerugian: mengkonsumsi slot di tabel simbol program yang tidak pernah dirilis.

Larry K
sumber
4
+1 untuk menyebutkan bahwa simbol tersebut bukan sampah yang dikumpulkan.
Vortico
lambang itu tidak pernah menjadi sampah yang dikumpulkan - tidak benar sejak ruby ​​2.2+
eudaimonia
0

Saya akan sangat tertarik dengan tindak lanjut tentang string beku yang diperkenalkan di Ruby 2.x.

Ketika Anda berurusan dengan banyak string yang berasal dari input teks (saya sedang memikirkan params atau payload HTTP, melalui Rack, misalnya), itu cara yang lebih mudah untuk menggunakan string di mana-mana.

Ketika Anda berurusan dengan puluhan dari mereka tetapi mereka tidak pernah berubah (jika itu adalah "kosakata" bisnis Anda), saya suka berpikir bahwa membekukannya dapat membuat perbedaan. Saya belum melakukan benchmark apa pun, tapi saya kira itu akan menutup kinerja simbol.

selamat
sumber