Guest User

Untitled

a guest
Feb 18th, 2018
70
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 4.02 KB | None | 0 0
  1. # Oftentimes, we want to create lists of things that have something in
  2. # common. For example we may have a list of objects, each with a
  3. # name. We want to count how many times each name occurs. We could do
  4. # so with a hash, like this:
  5.  
  6. h = {}
  7. a.each{|o|
  8. # make sure there's a value here
  9. h[o.name] ||= 0
  10. h[o.name] += 1
  11. }
  12.  
  13. # But it turns out there's an easier way, using the less common
  14. # Hash.new method:
  15.  
  16. h = Hash.new(0)
  17. a.each{|o| h[o.name] += 1}
  18.  
  19. # (or using a reduce to get it down to one line)
  20.  
  21. h = a.reduce(Hash.new(0)){|h, o| h[o.name] += 1; h}
  22.  
  23. # What's going on here? When we supply an argument to Hash.new, we
  24. # provide a default value that's used for any key that's not already
  25. # in the hash. When we omit the argument (or use the short-hand form
  26. # {}) the default value is set to nil.
  27.  
  28. g = Hash.new(5)
  29. h = {}
  30. g["nancy"] = 10
  31.  
  32. g["nancy"] # => 10
  33. g["sean"] # => 5
  34. h["sean"] # => nil
  35.  
  36. # However, care should be taken with using normal objects as default
  37. # values, as the following example shows:
  38.  
  39. h = Hash.new([])
  40. h["mary"] << 5
  41. h["abigail"] << 10
  42.  
  43. h["mary"] # => [5, 10]
  44. h["abigail"] # => [5, 10]
  45.  
  46. # What's going on here? We're setting a particular object--an instance
  47. # of the Array class--to the default value. However, this means that
  48. # the exact same array is used for every index of the Hash. Needless
  49. # to say, this probably isn't what you want. But there is another,
  50. # more powerful form of Hash.new that can solve this exact problem:
  51. # Hash.new {|hash, key| ... }. Using the block form, we can fix our
  52. # above example:
  53.  
  54. h = Hash.new{|hash, key| hash[key] = []}
  55.  
  56. h["mary"] << 5
  57. h["abigail"] << 10
  58.  
  59. h["mary"] # => [5]
  60. h["abigail"] # => [10]
  61.  
  62. # Now we're producing a new array element for every key. This sort of
  63. # thing can be useful if we have a list of objects each with names and
  64. # values, and we want a list of the values corresponding to each
  65. # name. We can now write:
  66.  
  67. h = Hash.new{|h, k| h[k] = []}
  68. a.each{|o| h[o.name] << h.value}
  69.  
  70. # The possibilities are endless. Effectively the block form makes
  71. # hashes into lightweight methods much like proc and lambda, with the
  72. # added benefit that we get memoization for free. For example, lets
  73. # say we want a function to calculate the fibonacci numbers. We all
  74. # know the formula: f(n) = f(n-1) + f(n-2). We can compute it
  75. # naievely like this:
  76.  
  77. def f(n)
  78. case n
  79. when 0 then 1
  80. when 1 then 1
  81. else
  82. f(n-1) + f(n-2)
  83. end
  84. end
  85.  
  86. # This works fine for small values of n. f(20) takes less than three
  87. # hundreths of a second on my MacBook Pro. But it quickly becomes
  88. # useless: f(30) takes 0.34 seconds, but I got bored waiting for f(40)
  89. # to finish. Is calculating the Fibbonaci sequence fundamently a hard
  90. # problem, where we can't expect to find a good solution? Or do we
  91. # just need to be smarter about it? Consider the following expansion,
  92. # of the n = 5 case:
  93.  
  94. f(5) = f(4) + f(3)
  95. = f(3) + f(2) + f(2) + f(1)
  96. = f(2) + f(1) + f(1) + f(0) + f(1) + f(0) + f(1)
  97. = f(1) + f(0) + f(1) + f(1) + f(0) + f(1) + f(0) + f(1)
  98. = 8
  99.  
  100. # While we got the right answer, notice how we kept repeating the same
  101. # calculations. Even in the trivial case of n = 5, we calculated f(3)
  102. # twice, and f(2) three times. Since we know that larger values of the
  103. # problem depend on the values of smaller values, we can save
  104. # ourselves an enormous amount of time my memoizing the function,
  105. # which simply means saving the results of our earlier runs for
  106. # later. The Ruby hash is a natural at this. Here's a much more
  107. # effecient and yet concise implementation of the above:
  108.  
  109. f = Hash.new do |h, k|
  110. case k
  111. when 0 then h[0] = 1
  112. when 1 then h[1] = 1
  113. else
  114. h[k] = h[k-2] + h[k-1]
  115. end
  116. end
  117.  
  118. # With our new and improved Fib, we can easily compute f(5000):
  119. def time; t = Time.now; yield ; Time.now - t; end
  120. time{f[5000]} # => 0.033939
  121.  
  122. # I should take this time to note that there is a very simple
  123. # iterative algorithm for fibbonaci, and you should never actually use
  124. # a recursive algorithm to compute it. However, this technique can be
  125. # very useful for the many recursive algorithms which can not be
  126. # trivially made iterative.
Add Comment
Please, Sign In to add comment