维护Hash Ruby 1.8.7的迭代顺序?

时间:2014-01-10 04:17:13

标签: ruby hash ruby-1.8.7

这是一项简单的家庭作业......

给出一个如下所示的哈希:

cost_of_groceries = {
"milk" => 3.50,
"egg" => 1.50,
"broccolli" => 0.75
}

我想打印哪些杂货不到2美元,哪些杂货超过2美元。这将是一个示例输出:

milk is more than $2
eggs is less than $2
broccolli is less than $2

没问题,但是这不是使用Ruby 1.8.7以正确的顺序打印。

我的代码:

cost_of_groceries.each do |x,y|
  if y > 2
    puts "#{x} is more than $2"
  else
    puts "#{x} is less than $2"
  end
end

这就是我得到的:

broccolli is less than $2
egg is less than $2
milk is more than $2
=> {"broccolli"=>0.75, "egg"=>1.5, "milk"=>3.5}

我意识到1.9之前的Ruby没有维护Hash上的迭代顺序,我知道我可以使用不同的版本来解决这个问题,但我希望深入研究这个并学习1.9之前的替代方法0.3。我不知道什么时候会派上用场。

这是一篇类似的帖子:“Ruby maintain Hash insertion order

4 个答案:

答案 0 :(得分:2)

1.9之前的Ruby没有维持哈希的“插入”顺序。这是一种强制已知订单的方法,而不依赖于排序:

BASE_COST = 2.0

COST_OF_GROCERIES = {
  "milk"      => 3.50,
  "egg"       => 1.50,
  "broccolli" => 0.75
}

DESIRED_ORDER = %w[milk egg broccolli]
COST_OF_GROCERIES.values_at(*DESIRED_ORDER) # => [3.5, 1.5, 0.75]

只按所需顺序返回值。

以下是按相同顺序处理哈希的方法:

DESIRED_ORDER.each do |k|
  lt_gt = COST_OF_GROCERIES[k] > BASE_COST ? 'more' : 'less'
  puts '%s is %s than %0.2f' % [k, lt_gt, BASE_COST]
end
# >> milk is more than 2.00
# >> egg is less than 2.00
# >> broccolli is less than 2.00

这是另一种看待它的方式......

Enumerable的zip让我们加入两个数组元素,交织它们:

DESIRED_ORDER.zip(COST_OF_GROCERIES.values_at(*DESIRED_ORDER)) # => [["milk", 3.5], ["egg", 1.5], ["broccolli", 0.75]]

我们可以将zip的输出传递给map,以添加价格是“更多”还是“更少”:

groceries = DESIRED_ORDER.zip(COST_OF_GROCERIES.values_at(*DESIRED_ORDER)).map{ |grocery, price| 
  [
    grocery, 
    price, 
    price > BASE_COST ? 'more' : 'less'
  ] 
} 
groceries # => [["milk", 3.5, "more"], ["egg", 1.5, "less"], ["broccolli", 0.75, "less"]]

查看groceries的内容:如果使用ERB或Haml渲染网页,则数组数组正是您要传递给视图的数据类型。

然后我们可以生成一些输出字符串并打印出来:

puts groceries.map{ |ary|
  '%s, at $%.2f is %s than $%0.2f' % [*ary, BASE_COST]
}
# >> milk, at $3.50 is more than $2.00
# >> egg, at $1.50 is less than $2.00
# >> broccolli, at $0.75 is less than $2.00

使用格式字符串类似于ERB或Haml模板。这距离ERB / Haml的起步只有几步之遥。

我将上述内容分成了更小的步骤,但实际的过程可以写成:

puts DESIRED_ORDER.zip(COST_OF_GROCERIES.values_at(*DESIRED_ORDER)).map{ |grocery, price| 
  [
    grocery, 
    price, 
    price > BASE_COST ? 'more' : 'less'
  ] 
}.map{ |ary|
  '%s, at $%.2f is %s than $%0.2f' % [*ary, BASE_COST]
}
# >> milk, at $3.50 is more than $2.00
# >> egg, at $1.50 is less than $2.00
# >> broccolli, at $0.75 is less than $2.00

答案 1 :(得分:0)

这是我能够混搭的:它有效,但违反了作业问题的背景:

hash={}

hash.instance_eval do
  def []=(key,val)
    ordered_keys << key
    super(key,val)
  end

  def ordered_keys
    @ordered_keys ||= []
  end

  def each_in_order(&block)
    ordered_keys.each do |key|
      yield(key, self[key])
    end
  end
end


hash['milk'] = 3.50
hash['egg'] = 1.50
hash['broccolli'] = 0.75



hash.each_in_order do |key, val|
  if val > 2
    puts "#{key} is more Than $2"
  else
    puts "#{key} is less than $2"
  end    
end

答案 2 :(得分:0)

基本上,要使用任意顺序迭代哈希,最好的方法通常是使用键数组指定顺序并循环遍历。所以你有一个像['eggs', 'milk', 'broccoli']这样的数组,你可以访问相应键的哈希对象。

答案 3 :(得分:0)

我认为通过继承Hash维护秩序可能是一项有趣的练习。 []=(other)可用于添加或更改元素,delete可用于删除它们,但我没有尝试覆盖添加或删除元素的其他哈希方法:

class MyHash < Hash
  def initialize(*vals)
    pairs =  vals.each_slice(2).to_a
    @keys = pairs.map(&:first)
    pairs.each_with_object(super()) {|(k,v), h| h[k] = v}
  end

  def []=(key,val)
    @keys << key unless @keys.include?(key)
    super
  end

  def delete(key)
    @keys.delete(key)
    super
  end

  def print_prices(cutoff)
    @keys.each do |k|
      if self[k] < cutoff
         puts "#{k} is less than %0.2f" % [cutoff]
      else
         puts "#{k} is at least %0.2f" % [cutoff]      
      end    
    end
  end  
end

h = MyHash.new("milk", 3.50, "egg", 1.50, "broccolli", 0.75)
  # => {"milk"=>3.5, "egg"=>1.5, "broccolli"=>0.75}

h.print_prices(2.00)         
milk is at least 2.00
egg is less than 2.00
broccolli is less than 2.00

h["fish"] = 2.00
h["egg"] = 2.10
h.delete("milk")

h.print_prices(2.00)         
egg is at least 2.00
broccolli is less than 2.00
fish is at least 2.00