47 votes

Quand est-il préférable d'utiliser un Struct plutôt qu'un Hash en Ruby ?

Un Ruby Struct permet de générer une instance avec un ensemble d'accesseurs :

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

Cela semble pratique et puissant, cependant, un Hash fait quelque chose d'assez similaire :

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

Quelles sont les situations réelles dans lesquelles je devrais préférer un Struct (et pourquoi), et quels sont les avertissements ou les pièges à éviter en choisissant l'un plutôt que l'autre ?

0 votes

Je considérerais qu'une structure est plus facile à comprendre, c'est-à-dire qu'elle conduit à un code plus facile à maintenir. Je laisse à quelqu'un d'autre le soin de commenter les éventuels avantages en termes de performances.

0 votes

Pourquoi ne pas spécifier une classe de clients dans un tel scénario ? Par commodité ?

1 votes

L'utilisation de Customer = Struct.new permet de définir une classe Customer, mais avec certains comportements par défaut. Vous pouvez facilement modifier ou remplacer ce comportement si vous le souhaitez.

22voto

gaqzi Points 1383

Personnellement, j'utilise une structure dans les cas où je veux qu'un élément de données se comporte comme une collection de données au lieu d'être faiblement couplé sous un Hash .

Par exemple, j'ai créé un script qui télécharge des vidéos de Youtube et dans lequel j'ai une structure pour représenter une vidéo et pour tester si toutes les données sont en place :

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

Plus loin dans mon code, j'ai une boucle qui parcourt toutes les rangées de la page HTML source des vidéos jusqu'à ce que empty? ne retourne pas vrai.

Un autre exemple que j'ai vu est James Edward Gray IIs classe de configuration qui utilise OpenStruct pour ajouter facilement des variables de configuration chargées depuis un fichier externe :

#!/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

La différence entre [Struct](http://www.ruby-doc.org/core-1.8.7/classes/Struct.html) y [OpenStruct](http://www.ruby-doc.org/stdlib/libdoc/ostruct/rdoc/index.html) c'est que Struct ne répond qu'aux attributs que vous avez définis, OpenStruct répond à n'importe quel ensemble d'attributs - mais ceux qui n'ont pas de valeur définie retourneront Nil

0 votes

Pourquoi tout le code que j'ai écrit ne s'affiche pas ici, mais si je vais éditer le message, tout s'affiche et tout semble parfait dans l'aperçu ?

0 votes

Le << a tout gâché, l'a transformé en << et maintenant tout va bien. :)

1 votes

Merci, ce sont de bons exemples. Le premier semble démontrer clairement les avantages de Struct. Le second, bien qu'il soit un bel exemple, fonctionnerait tout aussi bien comme un Hash, n'est-ce pas ?

10voto

mpospelov Points 884

Voici des benchmarks plus lisibles pour ceux qui aiment les métriques IPS (itération par seconde) :

Pour les petites instances :

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

les résultats :

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

Pour une énorme liste :

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

les résultats :

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

Conclusion

Comme vous pouvez le constater, struct est un peu plus rapide, mais il faut définir les champs struct avant de l'utiliser, donc si les performances sont vraiment importantes pour vous, utilisez struct ;)

10voto

JDonner Points 187

Une structure a la particularité de vous permettre d'accéder à ses éléments par index et par nom :

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

ce qui est parfois utile.

9voto

lzap Points 5049

C'est surtout la performance. Struct est beaucoup plus rapide, de plusieurs ordres de grandeur. Et consomme moins de mémoire par rapport à Hash ou OpenStruct. Plus d'informations ici : Quand dois-je utiliser Struct ou OpenStruct ?

6voto

the Tin Man Points 69148

Concernant les commentaires sur la rapidité d'utilisation des Hashs, Struct ou OpenStruct : Les hachages gagneront toujours pour un usage général. C'est la base d'OpenStruct sans le glaçage supplémentaire, donc ce n'est pas aussi flexible, mais c'est simple et efficace.

Utilisation de 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

Utilisation de définitions plus concises du hachage et d'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

Si la structure, Hash ou Struct ou OpenStruct, est définie une fois puis utilisée plusieurs fois, la vitesse d'accès devient plus importante, et un Struct commence à briller :

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

Notez cependant que le Struct n'est que 4x plus rapide que le Hash pour l'accès, alors que le Hash est 17x plus rapide pour l'initialisation, l'affectation et l'accès. Vous devrez déterminer quelle est la meilleure solution à utiliser en fonction des besoins d'une application particulière. Par conséquent, j'ai tendance à utiliser les hachages pour un usage général.

De plus, la vitesse d'utilisation d'OpenStruct s'est grandement améliorée au fil des ans ; elle était auparavant plus lente que Struct d'après les benchmarks que j'ai vus dans le passé et en comparant avec la version 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

et :

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

et :

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X