Когда лучше использовать Struct, а не Hash в Ruby?
A 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"}
Каковы реальные ситуации, когда я должен предпочесть Struct (и почему), и каковы предостережения или подводные камни при выборе одного над другим?
Ответы
Ответ 1
Лично я использую структуру в тех случаях, когда я хочу, чтобы часть данных действовала как совокупность данных, а не свободно связана под Hash
.
Например, я создал script, который загружает видео с 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.
Еще один пример, который я видел, - Джеймс Эдвард Грей IIs класс конфигурации, который использует 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
Разница между Struct
и OpenStruct
заключается в том, что Struct
отвечает только на атрибуты, которые вы установили, OpenStruct
отвечает на любой набор атрибутов, но те, у которых нет значения, вернут Nil
Ответ 2
У 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
что иногда полезно.
Ответ 3
Это в основном производительность. Строй намного быстрее, по порядку величин. И потребляет меньше памяти по сравнению с Hash или OpenStruct. Подробнее здесь: Когда следует использовать Struct vs. OpenStruct?
Ответ 4
Относительно комментариев о скорости использования хэшей, 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 определены один раз, а затем используются много раз, тогда скорость доступа становится более важной, и Struct начинает светиться:
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 раза быстрее, чем Hash для доступа, тогда как Hash на 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