何时在Ruby中使用Struct代替Hash?


72

我没有太多的编程经验。但是,对我来说,Struct似乎有点类似于Hash。

  • Struct可以做什么好?
  • Struct能做些什么,而Hash做不到?

谷歌搜索之后,Struct的概念在C中很重要,但是我对C并不了解。

Answers:


98

结构在以下方面(除了代码外观之外)与使用哈希图不同:

  • 在将新键添加到哈希中时,结构具有一组固定的属性。
  • 调用结构实例上不存在的属性将导致NoMethodError,而从哈希中获取不存在的键的值将仅返回nil。
  • 即使结构具有相同的属性且实例具有相同的值(即,Struct.new(:x).new(42) == Struct.new(:x).new(42)为false,而Foo = Struct.new(:x); Foo.new(42)==Foo.new(42)为true),不同结构的两个实例也永远不会相等。
  • to_a用于struct的方法返回一个值数组,而to_a在哈希表上则获得一个键-值对数组(其中“ pair”表示“双元素数组”)
  • 如果Foo = Struct.new(:x, :y, :z)可以Foo.new(1,2,3)创建实例而Foo不用拼写出属性名称。

因此,回答这个问题:当您要使用一组已知的属性对对象进行建模时,请使用结构。当您想建模任意使用哈希表(例如,计算每个单词在字符串中出现的频率或将昵称映射为全名等)时,绝对不是结构的工作,而使用名称,年龄和地址来建模的人的完美契合Person = Struct.new(name, age, address))。

附带说明:C结构与ruby结构几乎没有关系,因此不要让自己对此感到困惑。


您的其他观点都是正确的(因此为+1),但其Struct#==工作原理与您实际存储结果时所讲的不同Struct.new,而不是用相同的参数两次调用。
Mark Rushakoff

@MarkRushakoff:如果Foo = Struct.new(:x); Bar = Struct.new(:x)再这样做,Foo.new(42) == Bar.new(42)我会得到错误的结果。如果我愿意,Foo.new(42) == Foo.new(42)我会成真。而且,如果您仔细阅读,这也正是我所说的(两个不同结构的实例”。)
sepp2k 2010年

我明白你的意思了。我不清楚,因为您没有将其与使用相同的Struct类型时相等按预期工作的解释进行对比。
Mark Rushakoff

我理解这些差异,但是当Hash可以完成相同的事情并且更易于处理时,使用Struct而非Hash的真正优势是什么?似乎结构是多余的。
rcd

1
@igrek我知道这是一个老问题,但是答案是肯定的。该数组与您在属性名称中传递的顺序相同。
3limin4t0r

45

我知道这个问题几乎可以得到很好的回答,但是令人惊讶的是,没有人谈论过最大的区别之一和真正的好处Struct。我猜这就是为什么有人还在问

我理解这些差异,但是当Hash可以做同样的事情并且更易于处理时,使用Struct而不是Hash的真正优势是什么?似乎结构是多余的。

Struct更快的

require 'benchmark'

Benchmark.bm 10 do |bench|
  bench.report "Hash: " do
    50_000_000.times do { name: "John Smith", age: 45 } end
  end

  bench.report "Struct: " do
    klass = Struct.new(:name, :age)
    50_000_000.times do klass.new("John Smith", 45) end
  end

end

# ruby 2.2.2p95 (2015-04-13 revision 50295) [x64-mingw32].
#                 user     system      total        real
# Hash:       22.340000   0.016000  22.356000 ( 24.260674)
# Struct:     12.979000   0.000000  12.979000 ( 14.095455)

# ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-darwin11.0]
# 
#                  user     system      total        real
# Hash:       31.980000   0.060000  32.040000 ( 32.039914)
# Struct:     16.880000   0.010000  16.890000 ( 16.886061)

3
也许这很有趣:我使用Cygwin Ruby(2.2.3p173)重新运行了一次基准测试,并为用户获得了Hash和19.875的用户时间62.462。然后,我在JVM 1.7上使用JRuby 1.7.23重新运行它,并为Hash获得了8.401,为Struct获得了8.701。尽管在Ruby上速度优势很大,但在JRuby下两者似乎具有相同的速度。
user1934428'1

我使用较新的CRuby版本(2.5)重新运行了这个简单的基准,而Struct仍然更快。并不像它曾经是很多,但仍然显著更快(12.19sec VS 8.28秒,我的机器上)
十亩

在我的MacBook上,使用Ruby 2.6.6哈希(5.9秒)要比Struct(9.4秒)快。使用Ruby 2.3.1时,Struct的速度是哈希值的3倍。
基里尔

11

Struct文档中:

Struct是使用访问器方法将许多属性捆绑在一起的便捷方法,而无需编写显式类。

另一方面,哈希

哈希是键值对的集合。它类似于Array,不同之处在于,索引是通过任何对象类型的任意键完成的,而不是通过整数索引完成的。按键或值遍历哈希的顺序似乎是任意的,并且通常不会按插入顺序。

主要区别在于您访问数据的方式。

ruby-1.9.1-p378 > Point = Struct.new(:x, :y)
 => Point 
ruby-1.9.1-p378 > p = Point.new(4,5)
 => #<struct Point x=4, y=5> 
ruby-1.9.1-p378 > p.x
 => 4 
ruby-1.9.1-p378 > p.y
 => 5 
ruby-1.9.1-p378 > p = {:x => 4, :y => 5}
 => {:x=>4, :y=>5} 
ruby-1.9.1-p378 > p.x
NoMethodError: undefined method `x' for {:x=>4, :y=>5}:Hash
    from (irb):7
    from /Users/mr/.rvm/rubies/ruby-1.9.1-p378/bin/irb:17:in `<main>'
ruby-1.9.1-p378 > p[:x]
 => 4 
ruby-1.9.1-p378 > p[:y]
 => 5 

简而言之,当您想要一个具有“普通旧数据”结构的类时(可以选择使用更多方法进行扩展),可以创建一个新的Struct ,并且在不需要正式类型时使用Hash完全没有


11

另一个主要区别是可以向Struct添加行为方法。

 Customer = Struct.new(:name, :address) do

  def greeting; "Hello #{name}!" ; end

end

Customer.new("Dave", "123 Main").greeting  # => "Hello Dave!"

1
我认为这是一个足以证明Struct vs Hash合理性的巨大差异。由于根据Rails约定,您不应在同一个ruby文件中拥有两个类(由于自动加载的考虑),因此很多时候Struct是创建充当演示者/装饰者的类替代的好方法。
sandre89 '17

0

如果您只是要封装数据,那么哈希(或哈希数组)就可以了。如果您打算让数据进行操作或与其他数据进行交互,那么Struct可以带来一些有趣的可能性:

Point = Struct.new(:x, :y)
point_a = Point.new(0,0)
point_b = Point.new(2,3)

class Point
  def distance_to another_point
    Math.sqrt((self.x - another_point.x)**2 + (self.y - another_point.y)**2)
  end
end

puts point_a.distance_to point_b

您可以做class Point << Hash,所有事情都会一样。
Nakilon
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.