哈希与兰达斯

时间:2013-09-11 05:15:06

标签: ruby hash lambda

我找到两个看起来彼此接近的例子来寻找斐波那契数字:

  • LAMBDA

    fibonacci = ->(x){ x < 2 ? x : fibonacci[x-1] + fibonacci[x-2] }
    fibonacci[6]  # => 8
    
  • 哈希

    fibonacci = Hash.new{ |h,x| h[x] = x < 2 ? x : h[x-1] + h[x-2] }
    fibonacci[6]  # => 8 
    

我之前在红宝石中使用了哈希和lambdas,但不是这样的。这是一种存储函数的方法:

if x < 2
  x
else
 fibonacci[x-1] + fibonacci[x-2]
  1. 你能详细解释一下这是怎么回事吗?这是使用递归吗?
  2. 这样的哈希和lambdas之间有什么区别?

3 个答案:

答案 0 :(得分:9)

是的,它正在使用递归。如果我们查看{} -brackets中的代码,我们就可以找到答案。让我们开始看哈希。 new关键字后面的值是默认值。如果散列中尚不存在该值,则将分配的值。

hash = Hash.new
p hash['new_value'] #=> nil

default_value_hash = Hash.new(0)
puts default_value_hash['new_value'] #=> 0

hash_with_block = Hash.new{|h,x| x}
puts hash_with_block['new_value'] #=> 'new_value'

所以当我们宣布

 fibonacci = Hash.new{ |h,x| h[x] = x < 2 ? x : h[x-1] + h[x-2] }

我们基本上是说 - 使用默认值创建一个新哈希。如果我们要求一个小于或等于2的数字(x),只需返回输入(x)。否则,请给出键值为x-1和x-2的字典值的总和。基本上是斐波那契算法。如果x-1和x-2不存在,它将再次运行相同的代码,直到两个基本输入值为1和2。

两种方法的区别在于散列保存了值(在散列中......)。在某些情况下,这可能是一个巨大的优势。每次调用lambda时,都需要重新计算被调用值以下所有数字的值。

# Let's create a counter to keep track of the number of time the lambda is called.
# Please do not use global variables in real code. I am just lazy here.
@lambda_counter = 0

fibonacci_lambda = ->(x){ 
  @lambda_counter += 1
  x < 2 ? x : fibonacci_lambda[x-1] + fibonacci_lambda[x-2]
}

p (1..20).map{|x| fibonacci_lambda[x]}
# => [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765]

p @lambda_counter # => 57290
# lambda called 57290 times!

@hash_counter = 0

fibonacci_hash = Hash.new{ |h,x|
  @hash_counter += 1
  h[x] = x < 2 ? x : h[x-1] + h[x-2]
}

p (1..20).map{|x| fibonacci_hash[x]}
# => [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765]

p @hash_counter # => 21
# Only called 21 times!

调用差异很大的原因是递归的本质。 lambda不存储其值,当计算10的值时,它会重新计算3次超过20次的值。在散列中,可以存储并保存该值以供以后使用。

答案 1 :(得分:4)

在第一种情况下,您将定义一个递归调用的递归。

在哈希的情况下,值也将以递归方式计算,但是存储然后访问以提供结果。

  • LAMBDA

    fibonacci = ->(x){ x < 2 ? x : fibonacci[x-1] + fibonacci[x-2] }
    fibonacci[6]
    fibonacci # => <Proc:0x2d026a0@(irb):5 (lambda)>
    
  • 哈希

    fibonacci = Hash.new{ |h,x| h[x] = x < 2 ? x : h[x-1] + h[x-2] }
    fibonacci[6] 
    fibonacci # => {1=>1, 0=>0, 2=>1, 3=>2, 4=>3, 5=>5, 6=>8}
    

在一种情况下,您不会在内存中留下任何足迹,而散列将继续保留计算值。所以这取决于你需要什么。

如果您需要再次访问fibonacci[6],lambda将重新计算结果,而哈希将立即为您提供结果而无需重做计算。

答案 2 :(得分:-1)

  

这样的哈希和lambdas之间有什么区别?

lambdas和哈希没什么共同之处。你的问题就像问:

  

方法和数组之间有什么区别?

只有Hashes可以为不存在的键指定默认值:

h = Hash.new(10)

h["a"] = 2
puts h["a"]
puts h["b"]

--output:--
2
10 

哈希还提供了一种动态指定默认值的方法:您可以提供一个块。这是一个例子:

h = Hash.new do |h, key|
  h[key] = key.length
end

puts h['hello']
puts h['hi']
p h

--output:--
5
2
{"hello"=>5, "hi"=>2}

当您访问不存在的键时,将调用该块,并且该块可以执行您想要的任何操作。所以有人巧妙地想出你可以创建一个哈希并指定一个计算斐波纳契数的默认值。以下是它的工作原理:

h = Hash.new do |h, key| 
  if key < 2  
    h[key] = key
  else 
    h[key] = h[key-1] + h[key-2] 
  end
end

创建Hash h,这是一个没有键或值的Hash。如果你那么写:

puts h[3]

... 3是一个不存在的键,因此使用args h和3调用该块。块中的else子句执行,它为您提供:

h[3-1] + h[3-2]

或:

h[2] +  h[1]

但是为了评估这个陈述,ruby必须首先评估h [2]。但是当ruby在哈希中查找h [2]时,键2是一个不存在的键,所以使用args h和2调用该块,给你:

(h[2-1] + h[2-2]) + h[1]

或:

(h[1] + h[0])    + h[1]

要评估该语句,ruby首先必须评估第一个h [1],当ruby尝试在哈希中查找h [1]时,1是一个不存在的键,因此使用args调用该块h和1.这次if分支执行,导致这种情况发生:

 h[1] = 1

和1作为h [1]的值返回,给你:

 (1 + h[0])      + h[1]

然后ruby查找h [0],并且因为0是一个不存在的键,所以使用args h和0调用该块,并执行if子句并执行此操作:

h[0] = 0

和0作为h [0]的值返回,给出:

(1 + 0)        + h[1]

然后ruby在哈希中查找h [1],这次键1存在,它的值为1,给你:

(1 + 0)        + 1

等于2,所以h [3]设置为2.调用h [3]后,得到这个输出:

puts h[3]
p h

--output:--
2
{1=>1, 0=>0, 2=>1, 3=>2}

如您所见,以前的计算都是哈希中的缓存,这意味着不必再为其他斐波那契数字执行那些计算。