【问题标题】:When to use Struct instead of Hash in Ruby?何时在 Ruby 中使用 Struct 而不是 Hash?
【发布时间】:2011-03-17 13:52:17
【问题描述】:

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

  • Struct 能做什么好?
  • 有什么 Struct 能做而 Hash 不能做的事吗?

google了一下,Struct的概念在C中很重要,但是我对C了解不多。

【问题讨论】:

    标签: ruby hash struct


    【解决方案1】:

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

     Customer = Struct.new(:name, :address) do
    
      def greeting; "Hello #{name}!" ; end
    
    end
    
    Customer.new("Dave", "123 Main").greeting  # => "Hello Dave!"
    

    【讨论】:

    • 我认为这是一个巨大的差异,可以证明结构与哈希是合理的。因为按照 Rails 约定,你不应该在同一个 ruby​​ 文件中有两个类(因为自动加载问题),很多时候 Struct 是创建充当演示者/装饰者的类替换的好方法。
    【解决方案2】:

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

    • 结构具有一组固定的属性,而您可以向哈希添加新键。
    • 调用结构实例上不存在的属性将导致 NoMethodError,而从哈希中获取不存在键的值只会返回 nil。
    • 不同结构的两个实例永远不会相等,即使结构具有相同的属性并且实例具有相同的值(即Struct.new(:x).new(42) == Struct.new(:x).new(42) 为假,而Foo = Struct.new(:x); Foo.new(42)==Foo.new(42) 为真)。
    • 结构的to_a 方法返回一个值数组,而散列上的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 的结果时所解释的不同,而不是使用两次调用它相同的论点。
    • @MarkRushakoff:如果我先做Foo = Struct.new(:x); Bar = Struct.new(:x),然后再做Foo.new(42) == Bar.new(42),我会错的。如果我这样做Foo.new(42) == Foo.new(42),我会做到的。如果你仔细阅读,这正是我所说的(不同结构的两个实例“)。
    • 我明白你的意思。我不清楚,因为您没有将其与使用相同 Struct 类型时相等性按预期工作的解释进行对比。
    • 我理解其中的区别,但是当 Hash 可以做同样的事情并且更容易处理时,使用 Struct 优于 Hash 的真正优势是什么?似乎结构有点多余。
    • @igrek 我知道这是一个老问题,但答案是肯定的。该数组的顺序与您传入属性名称的顺序相同。
    【解决方案3】:

    如果您只是要封装数据,那么哈希(或哈希数组)就可以了。如果您打算让数据操作或与其他数据交互,那么 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,一切都会是一样的。
    【解决方案4】:

    我知道这个问题几乎得到了很好的回答,但令人惊讶的是,没有人谈论 Struct 的最大差异之一和真正的好处。我想这就是为什么somebody is still asking

    我了解其中的区别,但是当 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)
    

    【讨论】:

    • 也许这很有趣:我用 Cygwin Ruby (2.2.3p173) 重新运行了一次你的基准测试,得到了用户乘以 62.462 的哈希值和 19.875。然后我在 JVM 1.7 上使用 JRuby 1.7.23 重新运行它,得到 8.401 的 Hash 和 8.701 的 Struct。虽然 Ruby 的速度优势很大,但两者在 JRuby 下似乎具有相同的速度。
    • 我使用更新的 CRuby 版本 (2.5) 重新运行这个简单的基准测试,但 Struct 仍然更快。不像以前那么多,但仍然明显更快(12.19 秒对我的机器上的 8.28 秒)
    • 在我的 MacBook 上使用 Ruby 2.6.6 Hash(5.9 秒)比 Struct(9.4 秒)快。使用 Ruby 2.3.1 Struct 比 hash 快 3 倍。
    【解决方案5】:

    来自Struct 文档:

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

    另一方面,Hash:

    哈希是键值对的集合。它类似于数组,除了索引是通过任何对象类型的任意键完成的,而不是整数索引。您通过键或值遍历哈希的顺序可能看起来是任意的,并且通常不会在插入顺序中。

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

    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 
    

    简而言之,当你想要一个 "plain old data" structure 的类时,你会创建一个新的 Struct(可以选择使用更多方法来扩展它),当你不需要正式类型时,你会使用 Hash完全没有。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-03-22
      • 1970-01-01
      • 2011-10-26
      • 2013-05-13
      • 2014-10-12
      • 2015-07-07
      • 2016-03-23
      相关资源
      最近更新 更多