Когда лучше использовать Struct, а не Hash в Ruby? - PullRequest
47 голосов
/ 01 мая 2009

Ruby Struct позволяет генерировать экземпляр с помощью набора методов доступа:

# Create a structure named by its constant
Customer = Struct.new(:name, :address)     #=> Customer
Customer.new("Dave", "123 Main")           #=> #<Customer name="Dave", address="123 Main">

Это выглядит удобно и мощно, однако, Hash делает нечто похожее:

Customer = {:name => "Dave", :address => "123 Main"}

В каких реальных ситуациях я бы предпочел Структуру (и почему), и каковы предостережения или ловушки при выборе одного из них?

Ответы [ 5 ]

21 голосов
/ 01 мая 2009

Лично я использую структуру в тех случаях, когда я хочу, чтобы часть данных действовала как набор данных, а не как слабо связанная в Hash.

Например, я создал скрипт, который загружает видео с Youtube, и там у меня есть структура для представления видео и проверки наличия всех данных:


Video = Struct.new(:title, :video_id, :id) do
  def to_s
    "http://youtube.com/get_video.php?t=#{id}&video_id=#{video_id}&fmt=18"
  end

  def empty?
    @title.nil? and @video_id.nil? and @id.nil?
  end
end

Позже в моем коде есть цикл, который проходит по всем строкам исходной HTML-страницы видео, пока empty? не вернет true.

Другой пример, который я видел, это Джеймс Эдвард Грей II класс конфигурации , который использует OpenStruct для простого добавления переменных конфигурации, загружаемых из внешнего файла:

#!/usr/bin/env ruby -wKU

require "ostruct"

module Config
  module_function

  def load_config_file(path)
    eval <<-END_CONFIG
    config = OpenStruct.new
    #{File.read(path)}
    config
    END_CONFIG
  end
end

# configuration_file.rb
config.db = File.join(ENV['HOME'], '.cool-program.db')
config.user = ENV['USER']

# Usage:
Config = Config.load_config('configuration_file.rb')
Config.db   # => /home/ba/.cool-program.db
Config.user # => ba
Config.non_existant # => Nil

Разница между <a href="http://www.ruby-doc.org/core-1.8.7/classes/Struct.html" rel="noreferrer">Struct</a> и <a href="http://www.ruby-doc.org/stdlib/libdoc/ostruct/rdoc/index.html" rel="noreferrer">OpenStruct</a> заключается в том, что Struct отвечает только на атрибуты, которые вы установили, OpenStruct отвечает на любой набор атрибутов - но те, у которых значение не установлено, вернет Nil

10 голосов
/ 10 октября 2010

Struct имеет функцию, которую вы можете получить по его элементам по индексу, а также по имени:

irb(main):004:0> Person = Struct.new(:name, :age)
=> Person
irb(main):005:0> p = Person.new("fred", 26)
=> #
irb(main):006:0> p[0]
=> "fred"
irb(main):007:0> p[1]
=> 26
irb(main):008:0> p.name
=> "fred"
irb(main):009:0> p.age
=> 26

, что иногда полезно.

6 голосов
/ 11 декабря 2014

В основном это производительность. Структура намного быстрее, на порядок. И потребляет меньше памяти по сравнению с Hash или OpenStruct. Больше информации здесь: Когда я должен использовать Struct vs. OpenStruct?

4 голосов
/ 15 мая 2017

Относительно комментариев о скорости использования Хэшей, Struct или OpenStruct: Хэш всегда выигрывает для общего использования. Это основа OpenStruct без дополнительной глазури, поэтому он не такой гибкий, но он скудный и подлый.

Использование Ruby 2.4.1:

require 'fruity'
require 'ostruct'

def _hash
  h = {}
  h['a'] = 1
  h['a']
end

def _struct
  s = Struct.new(:a)
  foo = s.new(1)
  foo.a
end

def _ostruct
  person = OpenStruct.new
  person.a = 1
  person.a
end

compare do
  a_hash { _hash }
  a_struct { _struct }
  an_ostruct { _ostruct }
end

# >> Running each test 4096 times. Test will take about 2 seconds.
# >> a_hash is faster than an_ostruct by 13x ± 1.0
# >> an_ostruct is similar to a_struct

Использование более кратких определений хеша и OpenStruct:

require 'fruity'
require 'ostruct'

def _hash
  h = {'a' => 1}
  h['a']
end

def _struct
  s = Struct.new(:a)
  foo = s.new(1)
  foo.a
end

def _ostruct
  person = OpenStruct.new('a' => 1)
  person.a
end

compare do
  a_hash { _hash }
  a_struct { _struct }
  an_ostruct { _ostruct }
end

# >> Running each test 4096 times. Test will take about 2 seconds.
# >> a_hash is faster than an_ostruct by 17x ± 10.0
# >> an_ostruct is similar to a_struct

Если структура, Hash или Struct или OpenStruct определяется один раз, а затем используется многократно, тогда скорость доступа становится более важной, и структура начинает сиять:

require 'fruity'
require 'ostruct'

HSH = {'a' => 1}
def _hash
  HSH['a']
end

STRCT = Struct.new(:a).new(1)
def _struct
  STRCT.a
end

OSTRCT = OpenStruct.new('a' => 1)
def _ostruct
  OSTRCT.a
end

puts "Ruby version: #{RUBY_VERSION}"

compare do
  a_hash { _hash }
  a_struct { _struct }
  an_ostruct { _ostruct }
end

# >> Ruby version: 2.4.1
# >> Running each test 65536 times. Test will take about 2 seconds.
# >> a_struct is faster than a_hash by 4x ± 1.0
# >> a_hash is similar to an_ostruct

Обратите внимание, что Struct только в 4 раза быстрее, чем хэш для доступа, тогда как хэш в 17 раз быстрее для инициализации, назначения и доступа. Вы должны выяснить, какой из них лучше всего использовать, исходя из потребностей конкретного приложения. В результате я склонен использовать Хеши для общего использования.

Кроме того, скорость использования OpenStruct значительно улучшилась за эти годы; Раньше он был медленнее, чем Struct, основываясь на тестах, которые я видел в прошлом, и сравнивал с 1.9.3-p551:

require 'fruity'
require 'ostruct'

def _hash
  h = {}
  h['a'] = 1
  h['a']
end

def _struct
  s = Struct.new(:a)
  foo = s.new(1)
  foo.a
end

def _ostruct
  person = OpenStruct.new
  person.a = 1
  person.a
end

puts "Ruby version: #{RUBY_VERSION}"

compare do
  a_hash { _hash }
  a_struct { _struct }
  an_ostruct { _ostruct }
end

# >> Ruby version: 1.9.3
# >> Running each test 4096 times. Test will take about 2 seconds.
# >> a_hash is faster than a_struct by 7x ± 1.0
# >> a_struct is faster than an_ostruct by 2x ± 0.1

и

require 'fruity'
require 'ostruct'

def _hash
  h = {'a' => 1}
  h['a']
end

def _struct
  s = Struct.new(:a)
  foo = s.new(1)
  foo.a
end

def _ostruct
  person = OpenStruct.new('a' => 1)
  person.a
end

puts "Ruby version: #{RUBY_VERSION}"

compare do
  a_hash { _hash }
  a_struct { _struct }
  an_ostruct { _ostruct }
end

# >> Ruby version: 1.9.3
# >> Running each test 4096 times. Test will take about 2 seconds.
# >> a_hash is faster than a_struct by 7x ± 1.0
# >> a_struct is faster than an_ostruct by 2x ± 1.0

и

require 'fruity'
require 'ostruct'

HSH = {'a' => 1}
def _hash
  HSH['a']
end

STRCT = Struct.new(:a).new(1)
def _struct
  STRCT.a
end

OSTRCT = OpenStruct.new('a' => 1)
def _ostruct
  OSTRCT.a
end

puts "Ruby version: #{RUBY_VERSION}"

compare do
  a_hash { _hash }
  a_struct { _struct }
  an_ostruct { _ostruct }
end

# >> Ruby version: 1.9.3
# >> Running each test 32768 times. Test will take about 1 second.
# >> a_struct is faster than an_ostruct by 3x ± 1.0
# >> an_ostruct is similar to a_hash
2 голосов
/ 02 августа 2018

Вот более удобные тесты для тех, кто любит показатели IPS (итераций в секунду):

Для небольших экземпляров:

require 'benchmark/ips'
require 'ostruct'

MyStruct = Struct.new(:a)
Benchmark.ips do |x|
  x.report('hash') { a = { a: 1 }; a[:a] }
  x.report('struct') { a = MyStuct.new(1); a.a }
  x.report('ostruct') { a = OpenStruct.new(a: 1); a.a }

  x.compare!
end

Результаты:

Warming up --------------------------------------
                hash   147.162k i/100ms
              struct   171.949k i/100ms
             ostruct    21.086k i/100ms
Calculating -------------------------------------
                hash      2.608M (± 3.1%) i/s -     13.097M in   5.028022s
              struct      3.680M (± 1.8%) i/s -     18.399M in   5.001510s
             ostruct    239.108k (± 5.5%) i/s -      1.202M in   5.046817s

Comparison:
              struct:  3679772.2 i/s
                hash:  2607565.1 i/s - 1.41x  slower
             ostruct:   239108.4 i/s - 15.39x  slower

Для огромного списка:

require 'benchmark/ips'
require 'ostruct'

MyStruct = Struct.new(:a, :b, :c, :d, :e, :f, :g, :h, :i, :j, :k, :l, :m, :n, :o, :p, :q, :r, :s, :t, :u, :v, :w, :x, :y, :z)

Benchmark.ips do |x|
  x.report('hash') do
    hash = { a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9, j: 10, k: 11, l: 12, m: 13, n: 14, o: 15, p: 16, q: 17, r: 18, s: 19, t: 20, u: 21, v: 22, w: 23, x: 24, y: 25, z: 26 }
    hash[:a]; hash[:b]; hash[:c]; hash[:d]; hash[:e]; hash[:f]; hash[:g]; hash[:h]; hash[:i]; hash[:j]; hash[:k]; hash[:l]; hash[:m]; hash[:n]; hash[:o]; hash[:p]; hash[:q]; hash[:r]; hash[:s]; hash[:t]; hash[:u]; hash[:v]; hash[:w]; hash[:x]; hash[:y]; hash[:z]
  end

  x.report('struct') do
    struct = MyStruct.new(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26)
    struct.a;struct.b;struct.c;struct.d;struct.e;struct.f;struct.g;struct.h;struct.i;struct.j;struct.k;struct.l;struct.m;struct.n;struct.o;struct.p;struct.q;struct.r;struct.s;struct.t;struct.u;struct.v;struct.w;struct.x;struct.y;struct.z
  end

  x.report('ostruct') do
    ostruct = OpenStruct.new( a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9, j: 10, k: 11, l: 12, m: 13, n: 14, o: 15, p: 16, q: 17, r: 18, s: 19, t: 20, u: 21, v: 22, w: 23, x: 24, y: 25, z: 26)
    ostruct.a;ostruct.b;ostruct.c;ostruct.d;ostruct.e;ostruct.f;ostruct.g;ostruct.h;ostruct.i;ostruct.j;ostruct.k;ostruct.l;ostruct.m;ostruct.n;ostruct.o;ostruct.p;ostruct.q;ostruct.r;ostruct.s;ostruct.t;ostruct.u;ostruct.v;ostruct.w;ostruct.x;ostruct.y;ostruct.z;
  end

  x.compare!
end

Результаты:

Warming up --------------------------------------
                hash    51.741k i/100ms
              struct    62.346k i/100ms
             ostruct     1.010k i/100ms
Calculating -------------------------------------
                hash    603.104k (± 3.9%) i/s -      3.053M in   5.070565s
              struct    780.005k (± 3.4%) i/s -      3.928M in   5.041571s
             ostruct     11.321k (± 3.4%) i/s -     56.560k in   5.001660s

Comparison:
              struct:   780004.8 i/s
                hash:   603103.8 i/s - 1.29x  slower
             ostruct:    11321.2 i/s - 68.90x  slower

Заключение

Как вы можете видеть, struct немного быстрее, но требует определения полей struct перед ее использованием, поэтому, если производительность действительно важна, используйте struct;)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...