如何创建自定义哈希,其中键查找基于我定义的函数?

时间:2017-09-20 14:35:09

标签: ruby function hash key

我正在使用Ruby 2.4。如何创建自定义哈希,其中键的查找功能是我定义的?我有这个功能......

  def str_almost_equal(a, b)
    a.downcase == b.downcase || (a.size == b.size && a.downcase.chars.map.with_index{|c, i| c == b.downcase[i]}.count(false) == 1)
  end

如果键的两个类都是字符串并且它们与上面的方法匹配,我希望我的哈希查找键。所以,例如,如果我有

hash = []
hash["aaa"] = 1

然后我希望hash [“aab”]返回1,因为“aaa”和“aab”使用我定义的函数评估为true。

4 个答案:

答案 0 :(得分:2)

您可以定义自己继承自Hash的类,并根据自己的喜好覆盖[](key)方法。所以例如

class MyHash < Hash
  def [](key)
    # do something to return the appropriate value
  end
end

根据您的调用代码所需的Hash接口的其他方法,您可能需要覆盖其他方法。

但是,考虑到您的用例,我怀疑您是否能够以非常有效的方式实现该方法,因为您必须遍历存储在哈希中的所有候选项(至少具有相同长度+ -1的候选项)评估它们是否匹配。当然,如果散列中没有很多候选者,你可能会侥幸逃脱。

最重要的是,我并不认为在散列中提供查找键时返回1的行为是一种类似哈希的行为。我宁愿退回比赛。返回1将违反IMO最少意外的原则。

OP评论后的补充

在不知道您的确切用例的情况下,也许我已经离开了,为了提高查询速度,我会牺牲插入时间和空间的速度。这意味着我会在插入时预先计算密钥的所有变体,并将其插入用于存储的对象中。

class VariantsSet < Set 

  PLACEHOLDER = '_' # Use a character that will not be used in your keys

  def add_variants(string)
    merge(all_variants(string))
  end 

  def delete(string)
    all_variants(string).each { |variant| super(variant) }
  end

  def includes_any_variant?(string)
    all_variants(string).any? { |variant| include?(variant) }
  end

  private

  def all_variants(string)
    downcase_string = string.downcase
    string_length = string.length

    variants = [downcase_string]

    string_length.times do |i|
      variants << downcase_string[0, i] + PLACEHOLDER + downcase_string[i + 1 , string_length]
    end

    variants
  end
end

用法如下:

2.4.2 :026 > s = VariantsSet.new
 => #<VariantsSet: {}> 
2.4.2 :027 > s.add_variants('foobar')
 => #<VariantsSet: {"foobar", "_oobar", "f_obar", "fo_bar", "foo_ar", "foob_r", "fooba_"}> 
2.4.2 :028 > s.includes_any_variant?('foobaz')
 => true 
2.4.2 :029 > s.includes_any_variant?('blubs')
 => false 

根据您要存储的密钥数量,这可能需要相当多的RAM,因此如果失控,您可能需要使用例如RAM。用于存储值的数据库。但我仍然建议预先计算变体。

基本上可以使用Hash作为超类来完成相同的操作,如果这更符合您的喜好。

扩展为显示哈希实现

class VariantsHash < Hash

  PLACEHOLDER = '_' # Use a character that will not be used in your keys

  def []=(string, value)
    all_variants(string).each do |variant|
      super(variant, value)
    end
  end 

  def delete(string)
    all_variants(string).each do |variant|
      super(variant)
    end
  end 

  def [](string)
    match = all_variants(string).detect do |variant|
      super(variant)
    end

    super(match) if match
  end

  private

  def all_variants(string)
    downcase_string = string.downcase
    string_length = string.length

    variants = [downcase_string]

    string_length.times do |i|
      variants << downcase_string[0, i] + PLACEHOLDER + downcase_string[i + 1 , string_length]
    end

    variants
  end
end 

答案 1 :(得分:0)

这可能不是最好的答案,但我认为它可以帮到你

使用新班级

class NewHash < Hash
  def [](key)
    if keys.include?(key)
      super
    else
      found = keys.find { |k| compare_almost(k, key) }
      self[found] if found
    end
  end

  private

  def compare_almost(a, b)
    a.downcase == b.downcase ||
      (a.size == b.size && a.downcase.chars.map.with_index{|c, i| c == b.downcase[i]}.count(false) == 1)
  end
end

hash = NewHash.new
hash['aaa'] = 1
p hash['aab'] # => 1
p hash['acs'] # => nil

为每个对象使用模块

module CompareAlmost
  def [](key)
    if keys.include?(key)
      super
    else
      found = keys.find { |k| compare_almost(k, key) }
      self[found] if found
    end
  end

  private

  def compare_almost(a, b)
    a.downcase == b.downcase ||
      (a.size == b.size && a.downcase.chars.map.with_index{|c, i| c == b.downcase[i]}.count(false) == 1)
  end
end

hash = {'aaa' => 2}
hash.extend CompareAlmost
p hash['aab'] # => 2

替换origin Hash类

class Hash
  def [](key)
    if keys.include?(key)
      fetch key
    else
      found = keys.find { |k| compare_almost(k, key) }
      self[found] if found
    end
  end

  private

  def compare_almost(a, b)
    a.downcase == b.downcase ||
      (a.size == b.size && a.downcase.chars.map.with_index{|c, i| c == b.downcase[i]}.count(false) == 1)
  end
end

hash = { 'aaa' => 3 }
p hash['aab'] # => 3

答案 2 :(得分:0)

从您的问题中不清楚您的用例是什么,具体而言,如果密钥已经存在会发生什么。假设您只想使用自定义函数在密钥不存在时查找值,那么它只是为散列设置自定义默认值。如果是这种情况,解决方案非常简单:

## your custom function, changed to a Proc so it can be passed to a method
str_almost_equal = Proc.new do |a,b|
    a.downcase == b.downcase || (a.size == b.size && a.downcase.chars.map.with_index{|c, i| c == b.downcase[i]}.count(false) == 1)
  end

## iterator to run all values through your function
def check_all(a, array, func)
  array.each do |n|
    return n if func.call(a, n)
  end
end

## Hash with custom default value
h = Hash.new do |h,k| 
  if k.is_a? String
    key = check_all(k, h.keys, str_almost_equal) 
    h[key] if key
  end

h["aaa"]    ## nil
h["aaa"] = 3
h["aab"]    ## 3
h["bab"]    ## nil
h["aab"] = 4
h["aab"]    ## 4

答案 3 :(得分:0)

对于用户创建的类型,哈希查找使用两种方法#eql?#hash。这是覆盖哈希行为的规范方法。

class StringWrapper
  attr_reader :string

  def initialize(string)
    @string = string
  end

  def eql?(other)
    string.downcase == other.string.downcase ||
    (string.size == other.string.size && 
     string.downcase.chars.map.with_index{|c, i| c == 
     other.string.downcase[i]}.count(false) == 1
    )
  end

  def hash
    # never actually do this part
    0
  end
end

hash = {}
hash[StringWrapper.new("aaa")] = 1
hash[StringWrapper.new("aab")] # => 1

但是使用Hash而不是其他数据类型的原因是,无论您有多少数据,查找密钥所需的时间通常都不会增长。但在我们上面的实施中,我们打破了这一点。看看让我们看看哈希是如何工作的。在引擎盖下,您的数据大致类似于 1

|0  |1  |2  |3  |4  |5  |6  |7  |8  |9  |10 |
|   |   |   |aaa|   |   |aad|   |   |   |aab|
|   |   |   |aae|   |   |   |   |   |   |   |

当你在哈希中查找一个键时,Ruby并没有将它与已经存在的所有键进行比较。如果它这样做,那么你添加的键越多,查找就会越来越慢。相反,它会为密钥生成一个哈希 - 通常是一个很大的伪随机数。在我的计算机上"aaa".hash返回2707793439826082248。 2707793439826082248 % 113所以它被放入3号桶中。然后,当我们需要查找它时,流程再次发生,Ruby知道要检查哪个桶。现在不是检查每个键,而只需检查存储在bucked 3中的那些键。这是我们在定义#hash时总是返回0时所做的。

|0  |1  |2  |3  |4  |5  |6  |7  |8  |9  |10 |
|aaa|   |   |   |   |   |   |   |   |   |   |
|aab|   |   |   |   |   |   |   |   |   |   |
|aac|   |   |   |   |   |   |   |   |   |   |
|aad|   |   |   |   |   |   |   |   |   |   |

每个对象都存储在同一个存储桶中,因此我们破坏了Hash查找性能。在定义自定义哈希方法时,您需要的东西在每次计算时都是相同的并且看起来是随机的,因此模运算会将数据均匀地分布在桶中。但哈希方法也必须尊重#eql?的实现。如果你没有在大多数时间里看到错误的桶。例如,如果您想要一个哈希处理以相同的字母开头的对象,那将很容易。

 def hash
   string[0].hash
 end

您要求的问题是,可以构建一系列查找,其中每个字符串彼此相等。 &#34;富&#34; ===&#34; boo&#34;。 &#34;嘘声&#34; ==&#34; bao&#34;。 &#34;保&#34; ==&#34; bar&#34;。为了让程序尊重你的相等定义,所有这些字符串都需要在同一个哈希桶中。这意味着为了使您的哈希行为符合您描述的方式每个字符串需要具有相同的哈希桶。根据定义,这不是真正的哈希。所以我认为你应该退后一步,考虑一下你正在尝试解决的问题以及哈希是否真的是正确的方法。

你当然可以做出ulferts建议的代价,但代价是使用更多内存,但

会发生什么
hash = { "aaa" => 1 }
hash["cab"] = 2
hash["aab"]

1 :具有少量项目的Ruby哈希实际上实现为数组,至少在MRI中。这些图有望帮助解释散列表如何在高级别工作,而不是实际显示给定Ruby如何实现它们。我确定它们有任何不准确之处。