比较两个哈希值,无论是符号还是字符串,rails
Compare two hashes no matter symbols or strings, rails
我想比较两个哈希并强制它们相等:
- 键上有符号和值
- 第二个只有字符串。
例如:
sym_hash = {:id=>58, :locale=>:"en-US"}
string_hash = {"id"=>58, "locale"=>"en-US"}
这样试不行:
> sym_hash == string_hash
=> false
我首先尝试符号化 string_hash
:
> string_hash.deep_symbolize_keys
=> {:id=>58, :locale=>"en-US"}
但它仍然是错误的,因为 sym_hash
在 locale
var.
前面仍然有 :
然后我尝试字符串化 sym_hash
:
> sym_hash.with_indifferent_access
=> {"id"=>58, "locale"=>:"en-US"}
但是当我测试相等性时它仍然是 false
出于同样的原因。
编辑
为了回答很多关于 为什么 的评论,我希望这些哈希在这里相等,我将解释我正在尝试做的事情。
我正在使用 Reque
来管理我的工作。现在我想做一个 class 以避免有相同的*工作 运行,或者同时被排队两次。
(相同:对我来说,相同的工作是具有相同参数的工作,例如,我希望能够将具有不同 ID 的相同工作排队两次。)
为此,我正在使用插件 resque-status
,到目前为止,我能够知道工作何时 运行。此外,当我使用 set
保存参数时,我注意到写入 Redis
的消息(因为 resque-status 使用 Redis 来跟踪作业的状态)没有用符号正确保存。
这是我的 class:
# This class is used to run thread-lock jobs with Resque.
#
# It will check if the job with the exact same params is already running or in the queue.
# If the job is not finished, it will returns false,
# otherwise, it will run and returns a the uuid of the job.
#
class JobLock
def self.run(obj, params = {})
# Get rid of completed jobs.
Resque::Plugins::Status::Hash.clear_completed
# Check if your job is currently running or is in the queue.
if !detect_processing_job(obj, params)
job_uuid = obj.create(params)
Resque::Plugins::Status::Hash.set(job_uuid,
job_name: obj.to_s,
params: params)
job_uuid
else
false
end
end
def self.detect_processing_job(obj, params = {})
Resque::Plugins::Status::Hash.statuses.detect do |job|
job['job_name'] == obj.to_s && compare_hashes(job['params'], params)
end
end
def self.compare_hashes(string_hash, sym_hash)
[sym_hash, string_hash].map do |h|
h.map { |kv| kv.map(&:to_s) }.sort
end.reduce :==
end
end
下面是我如何使用它:
JobLock.run(MyAwesomeJob, id: 58, locale: :"en-US")
如您所见,我使用了@mudasobwa 的回答,但我希望有更简单的方法来实现我想要做的事情!
下面的版本作为PHP强制强制相等:
[sym_hash, string_hash].map do |h|
h.map { |kv| kv.map(&:to_s) }.sort
end.reduce :==
顺便说一句,这不是单线的,只是因为我尊重拥有智能手机的人。在宽度为 80 的终端上,它是一个完美的 oneliner。
仅将符号强制转换为字符串,保留数字以区别于它们的字符串表示:
[sym_hash, string_hash].map do |h|
h.map { |kv| kv.map { |e| e.is_a?(Symbol) ? e.to_s : e } }.sort
end.reduce :==
locale
在sym_hash
中的值是一个符号:"en-US"
,
而 string_hash
中 locale
的值是一个字符串。
所以他们不相等。
现在如果你这样做:
sym_hash = {:id=>58, :locale=>"en-US"}
string_hash = {"id"=>58, "locale"=>"en-US"}
string_hash.symbolize_keys!
sym_hash == string_hash
=> true
您可以尝试将两个哈希转换为 JSON,然后比较它们:
require 'json'
# => true
sym_hash = {:id=>58, :locale=>:"en-US"}
# => {:id=>58, :locale=>:"en-US"}
string_hash = {"id"=>58, "locale"=>"en-US"}
# => {"id"=>58, "locale"=>"en-US"}
sym_hash.to_json == string_hash.to_json
# => true
这个怎么样?
require 'set'
def sorta_equal?(sym_hash, str_hash)
return false unless sym_hash.size == str_hash.size
sym_hash.to_a.to_set == str_hash.map { |pair|
pair.map { |o| o.is_a?(String) ? o.to_sym : o } }.to_set
end
sym_hash= {:id=>58, :locale=>:"en-US"}
sorta_equal?(sym_hash, {"id"=>58, "locale"=>"en-US"}) #=> true
sorta_equal?(sym_hash, {"locale"=>"en-US", "id"=>58 }) #=> true
sorta_equal?(sym_hash, {"id"=>58, "local"=>"en-US", "a"=>"b" }) #=> false
sorta_equal?(sym_hash, {"id"=>58, "lacole"=>"en-US"}) #=> false
sorta_equal?(sym_hash, {"id"=>58, [1,2,3]=>"en-US"}) #=> false
sorta_equal?({}, {}) #=> true
class A; end
a = A.new
sorta_equal?({:id=>a, :local=>:b}, {"id"=>a, "local"=>"b"}) #=> true
最后,为了回答我的问题,我不需要强制比较哈希值。我使用 Marshal
来避免问题
class JobLock
def self.run(obj, params = {})
# Get rid of completed jobs.
Resque::Plugins::Status::Hash.clear_completed
# Check if your job is currently running or is in the queue.
if !detect_processing_job(obj, params)
job_uuid = obj.create(params)
Resque::Plugins::Status::Hash.set(job_uuid,
job_name: obj.to_s,
params: Marshal.dump(params))
job_uuid
else
false
end
end
def self.detect_processing_job(obj, params = {})
Resque::Plugins::Status::Hash.statuses.detect do |job|
job['job_name'] == obj.to_s && Marshal.load(job['params']) == params
end
end
end
无论如何,我把这个问题放在这里是因为它可能会在将来帮助一些人...
这与原始问题略有不同,是对上述一些建议的改编。如果值也可以是 String
/ Symbol
不可知论者,那么我可以建议:
def flat_hash_to_sorted_string_hash(hash)
hash.map { |key_value| key_value.map(&:to_s) }.sort.to_h.to_json
end
此辅助函数随后可用于断言两个哈希具有有效相同的值而不区分类型
assert_equal flat_hash_to_sorted_string_hash({ 'b' => 2, a: '1' }), flat_hash_to_sorted_string_hash({ b: '2', 'a' => 1 }) #=> true
细分:
- 通过映射一个散列,结果是一个数组
- 通过使键/值成为一致的类型,我们可以利用
Array#sort
方法而不会引发错误:ArgumentError: comparison of Array with Array failed
- 排序以公共顺序获取键
- to_h return 对象返回一个hash
注意:这不适用于具有嵌套对象的复杂哈希值或 Float/Int,但正如您所见,Int/String 比较也适用。这是受到已经讨论过的 JSON 方法的启发,但不需要使用 JSON,并且感觉这里不仅仅是评论,因为这是 post 我找到了灵感我正在寻找的解决方案。
我想比较两个哈希并强制它们相等:
- 键上有符号和值
- 第二个只有字符串。
例如:
sym_hash = {:id=>58, :locale=>:"en-US"}
string_hash = {"id"=>58, "locale"=>"en-US"}
这样试不行:
> sym_hash == string_hash
=> false
我首先尝试符号化 string_hash
:
> string_hash.deep_symbolize_keys
=> {:id=>58, :locale=>"en-US"}
但它仍然是错误的,因为 sym_hash
在 locale
var.
:
然后我尝试字符串化 sym_hash
:
> sym_hash.with_indifferent_access
=> {"id"=>58, "locale"=>:"en-US"}
但是当我测试相等性时它仍然是 false
出于同样的原因。
编辑
为了回答很多关于 为什么 的评论,我希望这些哈希在这里相等,我将解释我正在尝试做的事情。
我正在使用 Reque
来管理我的工作。现在我想做一个 class 以避免有相同的*工作 运行,或者同时被排队两次。
(相同:对我来说,相同的工作是具有相同参数的工作,例如,我希望能够将具有不同 ID 的相同工作排队两次。)
为此,我正在使用插件 resque-status
,到目前为止,我能够知道工作何时 运行。此外,当我使用 set
保存参数时,我注意到写入 Redis
的消息(因为 resque-status 使用 Redis 来跟踪作业的状态)没有用符号正确保存。
这是我的 class:
# This class is used to run thread-lock jobs with Resque.
#
# It will check if the job with the exact same params is already running or in the queue.
# If the job is not finished, it will returns false,
# otherwise, it will run and returns a the uuid of the job.
#
class JobLock
def self.run(obj, params = {})
# Get rid of completed jobs.
Resque::Plugins::Status::Hash.clear_completed
# Check if your job is currently running or is in the queue.
if !detect_processing_job(obj, params)
job_uuid = obj.create(params)
Resque::Plugins::Status::Hash.set(job_uuid,
job_name: obj.to_s,
params: params)
job_uuid
else
false
end
end
def self.detect_processing_job(obj, params = {})
Resque::Plugins::Status::Hash.statuses.detect do |job|
job['job_name'] == obj.to_s && compare_hashes(job['params'], params)
end
end
def self.compare_hashes(string_hash, sym_hash)
[sym_hash, string_hash].map do |h|
h.map { |kv| kv.map(&:to_s) }.sort
end.reduce :==
end
end
下面是我如何使用它:
JobLock.run(MyAwesomeJob, id: 58, locale: :"en-US")
如您所见,我使用了@mudasobwa 的回答,但我希望有更简单的方法来实现我想要做的事情!
下面的版本作为PHP强制强制相等:
[sym_hash, string_hash].map do |h|
h.map { |kv| kv.map(&:to_s) }.sort
end.reduce :==
顺便说一句,这不是单线的,只是因为我尊重拥有智能手机的人。在宽度为 80 的终端上,它是一个完美的 oneliner。
仅将符号强制转换为字符串,保留数字以区别于它们的字符串表示:
[sym_hash, string_hash].map do |h|
h.map { |kv| kv.map { |e| e.is_a?(Symbol) ? e.to_s : e } }.sort
end.reduce :==
locale
在sym_hash
中的值是一个符号:"en-US"
,
而 string_hash
中 locale
的值是一个字符串。
所以他们不相等。
现在如果你这样做:
sym_hash = {:id=>58, :locale=>"en-US"}
string_hash = {"id"=>58, "locale"=>"en-US"}
string_hash.symbolize_keys!
sym_hash == string_hash
=> true
您可以尝试将两个哈希转换为 JSON,然后比较它们:
require 'json'
# => true
sym_hash = {:id=>58, :locale=>:"en-US"}
# => {:id=>58, :locale=>:"en-US"}
string_hash = {"id"=>58, "locale"=>"en-US"}
# => {"id"=>58, "locale"=>"en-US"}
sym_hash.to_json == string_hash.to_json
# => true
这个怎么样?
require 'set'
def sorta_equal?(sym_hash, str_hash)
return false unless sym_hash.size == str_hash.size
sym_hash.to_a.to_set == str_hash.map { |pair|
pair.map { |o| o.is_a?(String) ? o.to_sym : o } }.to_set
end
sym_hash= {:id=>58, :locale=>:"en-US"}
sorta_equal?(sym_hash, {"id"=>58, "locale"=>"en-US"}) #=> true
sorta_equal?(sym_hash, {"locale"=>"en-US", "id"=>58 }) #=> true
sorta_equal?(sym_hash, {"id"=>58, "local"=>"en-US", "a"=>"b" }) #=> false
sorta_equal?(sym_hash, {"id"=>58, "lacole"=>"en-US"}) #=> false
sorta_equal?(sym_hash, {"id"=>58, [1,2,3]=>"en-US"}) #=> false
sorta_equal?({}, {}) #=> true
class A; end
a = A.new
sorta_equal?({:id=>a, :local=>:b}, {"id"=>a, "local"=>"b"}) #=> true
最后,为了回答我的问题,我不需要强制比较哈希值。我使用 Marshal
来避免问题
class JobLock
def self.run(obj, params = {})
# Get rid of completed jobs.
Resque::Plugins::Status::Hash.clear_completed
# Check if your job is currently running or is in the queue.
if !detect_processing_job(obj, params)
job_uuid = obj.create(params)
Resque::Plugins::Status::Hash.set(job_uuid,
job_name: obj.to_s,
params: Marshal.dump(params))
job_uuid
else
false
end
end
def self.detect_processing_job(obj, params = {})
Resque::Plugins::Status::Hash.statuses.detect do |job|
job['job_name'] == obj.to_s && Marshal.load(job['params']) == params
end
end
end
无论如何,我把这个问题放在这里是因为它可能会在将来帮助一些人...
这与原始问题略有不同,是对上述一些建议的改编。如果值也可以是 String
/ Symbol
不可知论者,那么我可以建议:
def flat_hash_to_sorted_string_hash(hash)
hash.map { |key_value| key_value.map(&:to_s) }.sort.to_h.to_json
end
此辅助函数随后可用于断言两个哈希具有有效相同的值而不区分类型
assert_equal flat_hash_to_sorted_string_hash({ 'b' => 2, a: '1' }), flat_hash_to_sorted_string_hash({ b: '2', 'a' => 1 }) #=> true
细分:
- 通过映射一个散列,结果是一个数组
- 通过使键/值成为一致的类型,我们可以利用
Array#sort
方法而不会引发错误:ArgumentError: comparison of Array with Array failed
- 排序以公共顺序获取键
- to_h return 对象返回一个hash
注意:这不适用于具有嵌套对象的复杂哈希值或 Float/Int,但正如您所见,Int/String 比较也适用。这是受到已经讨论过的 JSON 方法的启发,但不需要使用 JSON,并且感觉这里不仅仅是评论,因为这是 post 我找到了灵感我正在寻找的解决方案。