Wie überprüfe ich, ob mein Array ein Objekt enthält?

77

Ich habe eine Reihe @horses = [], die ich mit einigen zufälligen Pferden fülle.

Wie kann ich überprüfen, ob mein @horsesArray ein Pferd enthält, das bereits enthalten ist (existiert)?

Ich habe so etwas versucht wie:

@suggested_horses = []
  @suggested_horses << Horse.find(:first,:offset=>rand(Horse.count))
  while @suggested_horses.length < 8
    horse = Horse.find(:first,:offset=>rand(Horse.count))
    unless @suggested_horses.exists?(horse.id)
       @suggested_horses<< horse
    end
  end

Ich habe es auch mit versucht, include?aber ich habe gesehen, dass es nur für Streicher war. Mit exists?bekomme ich folgenden Fehler:

undefined method `exists?' for #<Array:0xc11c0b8>

Die Frage ist also, wie ich überprüfen kann, ob in meinem Array bereits ein "Pferd" enthalten ist, damit ich es nicht mit demselben Pferd fülle.

necker
quelle
Diese Frage wäre ein Duplikat von stackoverflow.com/questions/1529986/…, wenn diese Frage nicht in Python formuliert wäre.
Dave Schweisguth

Antworten:

177

Arrays in Ruby haben keine exists?Methode, aber sie haben eine include?Methode, wie in den Dokumenten beschrieben . Etwas wie

unless @suggested_horses.include?(horse)
   @suggested_horses << horse
end

sollte sofort funktionieren.

Tomasz Cudziło
quelle
1
das könnte schlecht sein, da einschließen? scannt das gesamte Array und ist in der Größenordnung von n O (n)
Kush
8
Es ist zeitlich linear und geht die Liste einmal über jedes Element durch. Es macht jedoch keinen Sinn, sich über die Leistung Gedanken zu machen. Es ist so ein einfacher Fall. Wenn Sie sich für eine schnelle Suche interessieren, verwenden Sie entweder Hashoder Setvon std-lib.
Tomasz Cudziło
3
Umgekehrt: es sei denn, Pferd.in?(@suggested_horses) @suggested_horses << Pferdeende
Felix Livni
2
@ Kush: Einverstanden, O (n) ist nicht ideal. Wenn wir jedoch kurzschließen, ist der durchschnittliche Fall immer noch O (n);) Schließlich haben wir uns entschieden, in Ruby (von allen langs ...) zu schreiben
Mike Rapadas
24

Wenn Sie überprüfen möchten, ob sich ein Objekt im Array befindet, indem Sie ein Attribut für das Objekt überprüfen, können Sie any?einen Block verwenden und übergeben, der als wahr oder falsch ausgewertet wird:

unless @suggested_horses.any? {|h| h.id == horse.id }
  @suggested_horses << horse
end
Andrew
quelle
3

Warum nicht einfach acht verschiedene Zahlen von 0bis auswählen Horse.countund damit Ihre Pferde holen?

offsets = (0...Horse.count).to_a.sample(8)
@suggested_horses = offsets.map{|i| Horse.first(:offset => i) }

Dies hat den zusätzlichen Vorteil, dass es keine Endlosschleife verursacht, wenn Sie weniger als 8 Pferde in Ihrer Datenbank haben.

Hinweis: Array#sample ist neu in Version 1.9 (und kommt in Version 1.8.8). Aktualisieren Sie also entweder Ihren Ruby require 'backports'oder verwenden Sie etwas Ähnliches shuffle.first(n).

Marc-André Lafortune
quelle
2

#include?sollte funktionieren, funktioniert es für allgemeine Objekte , nicht nur für Zeichenfolgen. Ihr Problem im Beispielcode ist dieser Test:

unless @suggested_horses.exists?(horse.id)
  @suggested_horses<< horse
end

(sogar unter Verwendung #include?). Sie versuchen, nach einem bestimmten Objekt zu suchen, nicht nach einer ID. So sollte es sein:

unless @suggested_horses.include?(horse)
  @suggested_horses << horse
end

ActiveRecord hat den Vergleichsoperator für Objekte neu definiert , damit nur der Status (neu / erstellt) und die ID überprüft werden

MBO
quelle
1

Die include?Methode des Arrays akzeptiert jedes Objekt, nicht nur eine Zeichenfolge. Das sollte funktionieren:

@suggested_horses = [] 
@suggested_horses << Horse.first(:offset => rand(Horse.count)) 
while @suggested_horses.length < 8 
  horse = Horse.first(:offset => rand(Horse.count)) 
  @suggested_horses << horse unless @suggested_horses.include?(horse)
end
John Topley
quelle
1

Die Frage ist also, wie ich überprüfen kann, ob in meinem Array bereits ein "Pferd" enthalten ist, damit ich es nicht mit demselben Pferd fülle.

Während es bei den Antworten darum geht, das Array zu durchsuchen, um festzustellen, ob eine bestimmte Zeichenfolge oder ein bestimmtes Objekt vorhanden ist, läuft das wirklich falsch, da die Suche mit zunehmender Größe des Arrays länger dauert.

Verwenden Sie stattdessen entweder einen Hash oder einen Set . Beide erlauben nur eine einzelne Instanz eines bestimmten Elements. Set verhält sich näher an einem Array, lässt jedoch nur eine einzelne Instanz zu. Dies ist ein präventiverer Ansatz, bei dem Doppelarbeit aufgrund der Art des Containers vermieden wird.

hash = {}
hash['a'] = nil
hash['b'] = nil
hash # => {"a"=>nil, "b"=>nil}
hash['a'] = nil
hash # => {"a"=>nil, "b"=>nil}

require 'set'
ary = [].to_set
ary << 'a'
ary << 'b'
ary # => #<Set: {"a", "b"}>
ary << 'a'
ary # => #<Set: {"a", "b"}>

Hash verwendet Name / Wert-Paare, was bedeutet, dass die Werte keinen wirklichen Nutzen haben, aber es scheint ein bisschen mehr Geschwindigkeit zu geben, wenn ein Hash verwendet wird, basierend auf einigen Tests.

require 'benchmark'
require 'set'

ALPHABET = ('a' .. 'z').to_a
N = 100_000
Benchmark.bm(5) do |x|
  x.report('Hash') { 
    N.times {
      h = {}
      ALPHABET.each { |i|
        h[i] = nil
      }
    }
  }

  x.report('Array') {
    N.times {
      a = Set.new
      ALPHABET.each { |i|
        a << i
      }
    }
  }
end

Welche Ausgänge:

            user     system      total        real
Hash    8.140000   0.130000   8.270000 (  8.279462)
Array  10.680000   0.120000  10.800000 ( 10.813385)
der Blechmann
quelle
0

Dies ...

horse = Horse.find(:first,:offset=>rand(Horse.count))
unless @suggested_horses.exists?(horse.id)
   @suggested_horses<< horse
end

Sollte wohl das sein ...

horse = Horse.find(:first,:offset=>rand(Horse.count))
unless @suggested_horses.include?(horse)
   @suggested_horses<< horse
end
Chris McCauley
quelle