أفضل طريقة لتحويل السلاسل إلى رموز في التجزئة
سؤال
ما هي الطريقة (الأسرع/الأنظف/المباشرة) لتحويل جميع المفاتيح في التجزئة من السلاسل إلى الرموز في روبي؟
سيكون هذا مفيدًا عند تحليل YAML.
my_hash = YAML.load_file('yml')
أود أن أكون قادرًا على استخدام:
my_hash[:key]
بدلا من:
my_hash['key']
المحلول
إذا كنت تريد بطانة واحدة،
my_hash = my_hash.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
سيتم نسخ التجزئة إلى واحدة جديدة مع المفاتيح المرمزة.
نصائح أخرى
إليك طريقة أفضل، إذا كنت تستخدم Rails:
المعلمات.رمز_المفاتيح
النهاية.
إذا لم تكن كذلك، فما عليك سوى نسخ الكود الخاص بهم (وهو موجود أيضًا في الرابط):
myhash.keys.each do |key|
myhash[(key.to_sym rescue key) || key] = myhash.delete(key)
end
بالنسبة لحالة YAML المحددة في Ruby، إذا كانت المفاتيح تبدأ بـ ":
"، سيتم احتجازهم تلقائيًا كرموز.
require 'yaml' require 'pp' yaml_str = " connections: - host: host1.example.com port: 10000 - host: host2.example.com port: 20000 " yaml_sym = " :connections: - :host: host1.example.com :port: 10000 - :host: host2.example.com :port: 20000 " pp yaml_str = YAML.load(yaml_str) puts yaml_str.keys.first.class pp yaml_sym = YAML.load(yaml_sym) puts yaml_sym.keys.first.class
انتاج:
# /opt/ruby-1.8.6-p287/bin/ruby ~/test.rb {"connections"=> [{"port"=>10000, "host"=>"host1.example.com"}, {"port"=>20000, "host"=>"host2.example.com"}]} String {:connections=> [{:port=>10000, :host=>"host1.example.com"}, {:port=>20000, :host=>"host2.example.com"}]} Symbol
بل وأكثر إيجازًا:
Hash[my_hash.map{|(k,v)| [k.to_sym,v]}]
إذا كنت تستخدم Rails، فالأمر أبسط بكثير - يمكنك استخدام HashWithIndifferentAccess والوصول إلى المفاتيح كسلسلة وكرموز:
my_hash.with_indifferent_access
أنظر أيضا:
http://api.rubyonrails.org/classes/ActiveSupport/HashWithIndifferentAccess.html
أو يمكنك استخدام جوهرة "Facets of Ruby" الرائعة، والتي تحتوي على الكثير من الامتدادات لفئات Ruby Core وStandard Library.
require 'facets'
> {'some' => 'thing', 'foo' => 'bar'}.symbolize_keys
=> {:some=>"thing", :foo=>"bar}
منذ Ruby 2.5.0
يمكنك استخدام Hash#transform_keys
أو Hash#transform_keys!
.
{'a' => 1, 'b' => 2}.transform_keys(&:to_sym) #=> {:a => 1, :b => 2}
http://api.rubyonrails.org/classes/Hash.html#method-i-symbolize_keys
hash = { 'name' => 'Rob', 'age' => '28' }
hash.symbolize_keys
# => { name: "Rob", age: "28" }
إليك طريقة للترميز العميق لكائن ما
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
أنا حقا أحب الهريس جوهرة.
يمكنك ان تفعل mash['key']
, ، أو mash[:key]
, ، أو mash.key
إذا كنت تستخدم json، وتريد استخدامه كتجزئة، فيمكنك القيام بذلك في Ruby الأساسي:
json_obj = JSON.parse(json_str, symbolize_names: true)
رمز_الأسماء:إذا تم التعيين على صواب، فسيتم إرجاع رموز للأسماء (المفاتيح) في كائن JSON.وإلا يتم إرجاع السلاسل.السلاسل هي الافتراضية.
الوثيقة: Json#parse يرمز للأسماء
params.symbolize_keys
سوف تعمل أيضا.تعمل هذه الطريقة على تحويل مفاتيح التجزئة إلى رموز وإرجاع تجزئة جديدة.
تعديل علىإجابة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
{'g'=> 'a', 2 => {'v' => 'b', 'x' => { 'z' => 'c'}}}.deep_symbolize_keys!
يتحول إلى:
{:g=>"a", 2=>{:v=>"b", :x=>{:z=>"c"}}}
هناك الكثير من الإجابات هنا، ولكن وظيفة القضبان هي الطريقة الوحيدة hash.symbolize_keys
هذه هي الخطوط الملاحية المنتظمة الخاصة بي للتجزئة المتداخلة
def symbolize_keys(hash)
hash.each_with_object({}) { |(k, v), h| h[k.to_sym] = v.is_a?(Hash) ? symbolize_keys(v) : v }
end
في حالة سبب ما عليك القيام به هو أن بياناتك جاءت في الأصل من JSON، ويمكنك تخطي أي من هذا التحليل بمجرد تمرير :symbolize_names
الخيار عند تناول JSON.
لا توجد حاجة إلى Rails وتعمل مع Ruby >1.9
JSON.parse(my_json, :symbolize_names => true)
يمكن أن تكون كسولًا، وتلفه في ملف lambda
:
my_hash = YAML.load_file('yml')
my_lamb = lambda { |key| my_hash[key.to_s] }
my_lamb[:a] == my_hash['a'] #=> true
لكن هذا لن ينجح إلا في القراءة من التجزئة، وليس في الكتابة.
للقيام بذلك، يمكنك استخدام Hash#merge
my_hash = Hash.new { |h,k| h[k] = h[k.to_s] }.merge(YAML.load_file('yml'))
ستقوم كتلة init بتحويل المفاتيح مرة واحدة عند الطلب، على الرغم من أنه إذا قمت بتحديث قيمة إصدار السلسلة من المفتاح بعد الوصول إلى إصدار الرمز، فلن يتم تحديث إصدار الرمز.
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}
يمكن أيضًا أن تجعل كتلة init لا تقوم بتحديث التجزئة، مما قد يحميك من هذا النوع من الأخطاء، ولكنك ستظل عرضة للعكس - فتحديث إصدار الرمز لن يؤدي إلى تحديث إصدار السلسلة:
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}
لذا فإن الشيء الذي يجب توخي الحذر بشأنه هو التبديل بين النموذجين الرئيسيين.العصا مع واحد.
هل هناك شيء مثل العمل التالي؟
new_hash = Hash.new
my_hash.each { |k, v| new_hash[k.to_sym] = v }
سيقوم بنسخ التجزئة، لكنك لن تهتم بذلك في معظم الأوقات.ربما تكون هناك طريقة للقيام بذلك دون نسخ جميع البيانات.
خط واحد أقصر:
my_hash.inject({}){|h,(k,v)| h.merge({ k.to_sym => v}) }
وماذا عن هذا:
my_hash = HashWithIndifferentAccess.new(YAML.load_file('yml'))
# my_hash['key'] => "val"
# my_hash[:key] => "val"
هذا بالنسبة للأشخاص الذين يستخدمون mruby
وليس لديك أي symbolize_keys
الطريقة المحددة:
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
طريقة:
- يرمز فقط إلى المفاتيح الموجودة
String
- إذا كان رمز سلسلة يعني فقدان بعض المعلومات (الكتابة فوق جزء من التجزئة) قم برفع أ
RuntimeError
- يرمز أيضًا إلى التجزئة الواردة بشكل متكرر
- إرجاع التجزئة الرمزية
- يعمل في مكانه!
المصفوفة التي نريد تغييرها.
سلاسل = ["HTML"، "CSS"، "JavaScript"، "Python"، "Ruby"]
قم بإنشاء متغير جديد كمصفوفة فارغة حتى نتمكن من ".دفع" الرموز إلى الداخل.
الرموز = [ ]
هنا نحدد طريقة مع كتلة.
سلاسل. Symbors.push (x.intern)}
نهاية الكود.
ربما تكون هذه هي الطريقة الأكثر مباشرة لتحويل السلاسل إلى رموز في المصفوفة (المصفوفات) الخاصة بك في روبي.أنشئ مصفوفة من السلاسل ثم أنشئ متغيرًا جديدًا واضبط المتغير على مصفوفة فارغة.ثم حدد كل عنصر في المصفوفة الأولى التي قمت بإنشائها باستخدام الطريقة ".each".ثم استخدم رمز الكتلة ".push" لجميع العناصر في المصفوفة الجديدة واستخدم ".intern أو .to_sym" لتحويل جميع العناصر إلى رموز.
تكون الرموز أسرع لأنها توفر المزيد من الذاكرة داخل التعليمات البرمجية الخاصة بك ويمكنك استخدامها مرة واحدة فقط.يتم استخدام الرموز بشكل شائع للمفاتيح في التجزئة وهو أمر رائع.أنا لست أفضل مبرمج روبي ولكن هذا النوع من التعليمات البرمجية ساعدني كثيرًا. إذا كان أي شخص يعرف طريقة أفضل، فيرجى مشاركتها ويمكنك استخدام هذه الطريقة للتجزئة أيضًا!
إذا كنت ترغب في حل روبي الفانيليا وأنا لا أستطيع الوصول إليه ActiveSupport
إليك حل الترميز العميق (يشبه إلى حد كبير الحلول السابقة)
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
ابتداء من نفسية 3.0 يمكنك إضافة أسماء_الرموز: خيار
Psych.load("---\n foo: bar")
# => {"foo"=>"bar"}
Psych.load("---\n foo: bar", symbolize_names: true)
# => {:foo=>"bar"}
ملحوظة:إذا كان لديك إصدار Psych أقل من 3.0 symbolize_names:
سيتم تجاهلها بصمت.
يتضمن نظام Ubuntu 18.04 الخاص بي هذا المنتج خارج الصندوق مع Ruby 2.5.1p57
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}
هذا ليس سطرًا واحدًا تمامًا، ولكنه يحول جميع مفاتيح السلسلة إلى رموز، وكذلك الرموز المتداخلة:
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
تعجبني هذه الجملة، عندما لا أستخدم Rails، لأنني لن أضطر إلى إجراء تجزئة ثانية والاحتفاظ بمجموعتين من البيانات أثناء معالجتها:
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#delete بإرجاع قيمة المفتاح المحذوف
تجزئة الأوجه#deep_rekey يعد أيضًا خيارًا جيدًا، خاصة:
- إذا وجدت استخدامًا لسكر آخر من جوانب في مشروعك،
- إذا كنت تفضل سهولة قراءة التعليمات البرمجية على الخطوط الفردية الغامضة.
عينة:
require 'facets/hash/deep_rekey'
my_hash = YAML.load_file('yml').deep_rekey
في روبي، أجد أن هذه هي الطريقة الأكثر بساطة وسهولة في الفهم لتحويل مفاتيح السلسلة في التجزئة إلى رموز:
my_hash.keys.each { |key| my_hash[key.to_sym] = my_hash.delete(key)}
لكل مفتاح في التجزئة نسميه حذفًا مما يؤدي إلى إزالته من التجزئة (حذف أيضًا إرجاع القيمة المرتبطة بالمفتاح تم حذفه) وقمنا على الفور بضبط هذا على أنه يساوي المفتاح المرمز.
مشابهة للحلول السابقة ولكنها مكتوبة بشكل مختلف قليلاً.
- يسمح هذا بتجزئة متداخلة و/أو تحتوي على صفائف.
- احصل على تحويل المفاتيح إلى سلسلة كمكافأة.
لا يؤدي الرمز إلى تغيير التجزئة التي تم تمريرها.
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