Neu: Das englische Ruby on Rails 4.0 Buch.

2.10. Arrays und Hashes

Wie in vielen Programmiersprachen sind auch in Ruby Arrays und Hashes beliebte Strukturen, um Daten zu speichern.

Arrays

Ein Array ist eine Liste von Objekten. Spielen wir doch einmal ein wenig im irb:
sw@debian:~/sandbox$ irb
1.9.3p194 :001:0> a = [1,2,3,4,5]
=> [1, 2, 3, 4, 5]
1.9.3p194 :002:0> a.class
=> Array
1.9.3p194 :003:0> exit
Das ist einfach und verständlich.
Mal schauen, ob das so auch mit Strings im Array funktioniert:
sw@debian:~/sandbox$ irb
1.9.3p194 :001:0> a = ['Test', 'Apfelbaum', 'blau']
=> ["Test", "Apfelbaum", "blau"]
1.9.3p194 :002:0> a.class
=> Array
1.9.3p194 :003:0> a[1]
=> "Apfelbaum"
1.9.3p194 :004:0> a[1].class
=> String
1.9.3p194 :005:0> exit
Das geht auch.
Dann fehlt nur noch ein Array mit einem Mix aus beidem. Ist klar, dass es gehen muss, weil das Array ja Objekte speichert und es egal ist, welche Art von Objekten (sprich String, Fixnum, Float, …) das sind. Aber ein Versuch schadet ja nicht:
sw@debian:~/sandbox$ irb
1.9.3p194 :001:0> a = [1, 2, 'Haus', 'Baum', 4]
=> [1, 2, "Haus", "Baum", 4]
1.9.3p194 :002:0> a.class
=> Array
1.9.3p194 :003:0> a[0]
=> 1
1.9.3p194 :004:0> a[0].class
=> Fixnum
1.9.3p194 :005:0> a[2]
=> "Haus"
1.9.3p194 :006:0> a[2].class
=> String
1.9.3p194 :007:0> exit
Sehen wir uns als Nächstes mal die ri-Hilfeseite zu Array an:
sw@debian:~/sandbox$ ri Array
= Array < Object

------------------------------------------------------------------------------
= Includes:
Enumerable (from ruby site)

(from ruby site)
------------------------------------------------------------------------------
Arrays are ordered, integer-indexed collections of any object. Array indexing
starts at 0, as in C or Java.  A negative index is assumed to be relative to
the end of the array---that is, an index of -1 indicates the last element of
the array, -2 is the next to last element in the array, and so on.

------------------------------------------------------------------------------
= Class methods:

  [], new, try_convert

= Instance methods:
  &, *, +, -, <<, <=>, ==, [], []=, abbrev, assoc, at, clear, collect,
  collect!, combination, compact, compact!, concat, count, cycle, dclone,
  delete, delete_at, delete_if, drop, drop_while, each, each_index, empty?,
  eql?, fetch, fill, find_index, first, flatten, flatten!, frozen?, hash,
  include?, index, initialize_copy, insert, inspect, join, keep_if, last,
  length, map, map!, pack, permutation, pop, pretty_print, pretty_print_cycle,
  product, push, rassoc, reject, reject!, repeated_combination,
  repeated_permutation, replace, reverse, reverse!, reverse_each, rindex,
  rotate, rotate!, sample, select, select!, shelljoin, shift, shuffle,
  shuffle!, size, slice, slice!, sort, sort!, sort_by!, take, take_while,
  to_a, to_ary, to_csv, to_s, transpose, uniq, uniq!, unshift, values_at, zip,
  |

sw@debian:~/sandbox$ 
Arrays können also auch (wie jede Klasse) mit der Methode new erstellt werden. Einzelne neue Elemente lassen sich dann mit der Methode << hinzufügen. Auch hierzu ein Beispiel:
sw@debian:~/sandbox$ irb
1.9.3p194 :001:0> a = Array.new
=> []
1.9.3p194 :002:0> a.class
=> Array
1.9.3p194 :003:0> a << 'erster Eintrag'
=> ["erster Eintrag"]
1.9.3p194 :004:0> a << 'zweiter Eintrag'
=> ["erster Eintrag", "zweiter Eintrag"]
1.9.3p194 :005:0> a << 'dritter Eintrag'
=> ["erster Eintrag", "zweiter Eintrag", "dritter Eintrag"]
1.9.3p194 :006:0> a.size
=> 3
1.9.3p194 :007:0> exit
Aber versuchen wir doch mal, ein Array mit Einträgen aus einer selbstdefinierten Klasse zu erstellen:
sw@debian:~/sandbox$ irb
1.9.3p194 :001:0> class Tier              # Definition einer Klasse Tier
1.9.3p194 :002:1>   def initialize(de_name,en_name)
1.9.3p194 :003:2>     @de_name = de_name
1.9.3p194 :004:2>     @en_name = en_name
1.9.3p194 :005:2>   end
1.9.3p194 :006:1>   def to_s
1.9.3p194 :007:2>     @de_name + ' (' + @en_name + ')'
1.9.3p194 :008:2>   end
1.9.3p194 :009:1> end
=> nil
1.9.3p194 :010:0> lumpi = Tier.new('Hund', 'Dog')
=> #<Tier:0x873b5c8 @de_name="Hund", @en_name="Dog">
1.9.3p194 :011:0> hansi = Tier.new('Wellensittich', 'Budgerigar')           
=> #<Tier:0x86b60f8 @de_name="Wellensittich", @en_name="Budgerigar">
1.9.3p194 :012:0> haustiere = Array.new   # haustiere ist ein Array
=> []
1.9.3p194 :013:0> haustiere << hansi      # hansi wird hinzugefuegt
=> [#<Tier:0x8d11f18 @de_name="Wellensittich", @en_name="Budgerigar">]
1.9.3p194 :014:0> haustiere << lumpi      # lumpi wird hinzugefuegt
=> [#<Tier:0x8d11f18 @de_name="Wellensittich", @en_name="Budgerigar">, #<Tier:0x8d9751c @de_name="Hund", @en_name="Dog">]
1.9.3p194 :015:0> haustiere.count
=> 2
1.9.3p194 :016:0> puts haustiere[1]       # Ausgabe der Position 1
Hund (Dog)
=> nil
1.9.3p194 :017:0> puts haustiere[0]       # Ausgabe der Position 0
Wellensittich (Budgerigar)
=> nil
1.9.3p194 :018:0> exit

Iterator each

Mit der Methode each kann man sich Stück für Stück durch ein Array arbeiten. Beispiel:
sw@debian:~/sandbox$ irb
1.9.3p194 :001:0> einkaufsliste = ['Eier', 'Butter', 'Mehl']
=> ["Eier", "Butter", "Mehl"]
1.9.3p194 :002:0> einkaufsliste.each do |produkt|
1.9.3p194 :003:1*   puts produkt
1.9.3p194 :004:1> end
Eier
Butter
Mehl
=> ["Eier", "Butter", "Mehl"]
1.9.3p194 :005:0> exit
Auch hier gibt Ihnen ri Hilfe und ein Beispiel, falls Sie mal vergessen haben sollten, wie each anzuwenden ist:
sw@debian:~/sandbox$ ri Array.each
= Array.each

(from ruby site)
------------------------------------------------------------------------------
  ary.each {|item| block }   -> ary
  ary.each                   -> an_enumerator

------------------------------------------------------------------------------

Calls block once for each element in self, passing that element as a
parameter.

If no block is given, an enumerator is returned instead.

  a = [ "a", "b", "c" ]
  a.each {|x| print x, " -- " }

produces:

  a -- b -- c --

sw@debian:~/sandbox$ 

Hashes

Ein Hash ist eine Liste aus Schlüssel-/Wert-Paaren (key/value pairs). Ein Beispiel mit Strings als Schlüssel:
sw@debian:~/sandbox$ irb
1.9.3p194 :001:0> preisliste = { '6 Eier' => 1.2, '12 Eier' => 2, '500 gr Butter' => 0.99 }
=> {"6 Eier"=>1.2, "12 Eier"=>2, "500 gr Butter"=>0.99}
1.9.3p194 :002:0> preisliste['12 Eier']
=> 2
1.9.3p194 :003:0> preisliste.count
=> 3
1.9.3p194 :004:0> exit
Natürlich können in Hashes nicht nur Strings, sondern – wie bei Arrays – auch selbstdefinierte Klassen als Objekte in den Werten gespeichert werden (siehe „Arrays“).
Wie zu Array gibt es auch zu Hash eine sehr gute ri-Hilfeseite:
sw@debian:~/sandbox$ ri Hash
= Hash < Object

------------------------------------------------------------------------------
= Includes:
Enumerable (from ruby site)

(from ruby site)
------------------------------------------------------------------------------
A Hash is a collection of key-value pairs. It is similar to an Array, except
that indexing is done via arbitrary keys of any object type, not an integer
index. Hashes enumerate their values in the order that the corresponding keys
were inserted.

Hashes have a default value that is returned when
accessing keys that do not exist in the hash. By default, that value is nil.

------------------------------------------------------------------------------
= Class methods:

  [], new, try_convert

= Instance methods:
  ==, [], []=, assoc, clear, compare_by_identity, compare_by_identity?,
  default, default=, default_proc, default_proc=, delete, delete_if, each,
  each_key, each_pair, each_value, empty?, eql?, fetch, flatten, has_key?,
  has_value?, hash, include?, initialize_copy, inspect, invert, keep_if, key,
  key?, keys, length, member?, merge, merge!, pretty_print,
  pretty_print_cycle, rassoc, rehash, reject, reject!, replace, select,
  select!, shift, size, store, to_a, to_hash, to_s, update, value?, values,
  values_at

sw@debian:~/sandbox$ 

Symbole (Symbols)

Symbols sind eine merkwürdige und schwer erklärbare Sache. Allerdings sind sie auch sehr praktisch und werden unter anderem bei Hashes häufig benutzt. Normalerweise werden bei Variablen immer neue Objekte angelegt:
sw@debian:~/sandbox$ irb
1.9.3p194 :001:0> a = 'Beispiel 1'
 => "Beispiel 1" 
1.9.3p194 :002:0> a.object_id
 => 2156685080 
1.9.3p194 :003:0> a = 'Beispiel 2'
 => "Beispiel 2" 
1.9.3p194 :004:0> a.object_id
 => 2151901180 
1.9.3p194 :005:0> exit
Wir haben zweimal die Variable a, aber einmal hat sie die Objekt-ID 2156685080 und dann die Objekt-ID 2151901180. Wir könnten das immer so weitermachen. Es würde jedes Mal eine andere Objekt-ID und damit ein neues Objekt herauskommen. Das ist prinzipiell nicht weiter schlimm und im Sinne der Objektorientierung auch logisch. Allerdings ist es auch ein recht verschwenderischer Umgang mit Speicherplatz.
Ein Symbol wird durch einen Doppelpunkt vor dem Namen definiert und kann selber keine Werte speichern, aber hat immer die gleiche Objekt-ID und ist deshalb sehr gut als Schlüssel (key) geeignet:
sw@debian:~/sandbox$ irb
1.9.3p194 :001:0> :a.object_id
 => 373448 
1.9.3p194 :002:0> exit
Machen wir noch mal einen kleinen Versuch, um den Unterschied klarer herauszustellen. Wir benutzen 3-mal hintereinander ein String-Objekt mit dem Inhalt white und dann 3-mal hintereinander das Symbol :white. Bei "white" wird immer ein neues Objekt angelegt. Beim Symbol :white nur beim ersten Mal:
sw@debian:~/sandbox$ irb
1.9.3p194 :001:0> 'white'.object_id
 => 2152156400 
1.9.3p194 :002:0> 'white'.object_id
 => 2152152240 
1.9.3p194 :003:0> 'white'.object_id
 => 2152164440 
1.9.3p194 :004:0> :white.object_id
 => 373448 
1.9.3p194 :005:0> :white.object_id
 => 373448 
1.9.3p194 :006:0> :white.object_id
 => 373448 
1.9.3p194 :007:0> exit
Die Benutzung von Symbolen als Schlüssel ist bei Hashes sehr viel speichereffizienter:
sw@debian:~/sandbox$ irb
1.9.3p194 :001:0> farben = { :black => '#000000', :white => ':FFFFFF', :green => '#00C000', :blue => '#0000FF' }
=> {:black=>"#000000", :white=>":FFFFFF", :green=>"#00C000", :blue=>"#0000FF"}
1.9.3p194 :002:0> puts farben[:green]
#00C000
=> nil
1.9.3p194 :003:0> exit
Symbole werden Sie in Rails noch oft sehen. Wenn Sie weitere Informationen zu Symbols suchen, können Sie mit ri Symbol die Hilfeseite zur Klasse Symbol aufrufen.

Iterator each

Mit der Methode each kann man sich Stück für Stück durch einen Hash arbeiten. Beispiel:
sw@debian:~/sandbox$ irb
1.9.3p194 :001:0> einkaufsliste = { 'Eier' => 6, 'Butter' => '500 gr' }
=> {"Eier"=>6, "Butter"=>"500 gr"}
1.9.3p194 :002:0> einkaufsliste.each do |key,value|
1.9.3p194 :003:1*   puts "Produkt: #{key} - Menge: #{value}"
1.9.3p194 :004:1> end
Produkt: Eier - Menge: 6
Produkt: Butter - Menge: 500 gr
=> {"Eier"=>6, "Butter"=>"500 gr"}
1.9.3p194 :005:0> exit
Auch hier gibt Ihnen ri Hilfe und ein Beispiel, falls Sie mal vergessen haben sollten, wie each anzuwenden ist:
sw@debian:~/sandbox$ ri Hash.each
= Hash.each

(from ruby site)
------------------------------------------------------------------------------
  hsh.each      {| key, value | block } -> hsh
  hsh.each_pair {| key, value | block } -> hsh
  hsh.each                              -> an_enumerator
  hsh.each_pair                         -> an_enumerator

------------------------------------------------------------------------------

Calls block once for each key in hsh, passing the key-value
pair as parameters.

If no block is given, an enumerator is returned instead.

  h = { "a" => 100, "b" => 200 }
  h.each {|key, value| puts "#{key} is #{value}" }

produces:

  a is 100
  b is 200

sw@debian:~/sandbox$ 

Autor

Stefan Wintermeyer