Ermittelt den Index des Array-Elements schneller als O (n)

104

Vorausgesetzt, ich habe ein RIESIGES Array und einen Wert daraus. Ich möchte einen Index des Werts im Array erhalten. Gibt es einen anderen Weg, als anzurufen Array#index, um ihn zu bekommen? Das Problem ergibt sich aus der Notwendigkeit, ein wirklich großes Array zu behalten und sehr Array#indexoft anzurufen.

Nach einigen Versuchen stellte ich fest, dass das Zwischenspeichern von Indizes innerhalb von Elementen durch Speichern von Strukturen mit (value, index)Feldern anstelle des Werts selbst einen großen Leistungsschritt darstellt (20-facher Gewinn).

Trotzdem frage ich mich, ob es eine bequemere Möglichkeit gibt, den Index von en element ohne Caching zu finden (oder ob es eine gute Caching-Technik gibt, die die Leistung steigert).

gmile
quelle

Antworten:

118

Konvertieren Sie das Array in einen Hash. Dann suchen Sie nach dem Schlüssel.

array = ['a', 'b', 'c']
hash = Hash[array.map.with_index.to_a]    # => {"a"=>0, "b"=>1, "c"=>2}
hash['b'] # => 1
sawa
quelle
2
am schnellsten, wenn das Array sehr lang ist
Kevin
17
Abhängig von Ihrem Anwendungsfall kann dies problematisch sein, wenn doppelte Werte vorhanden sind. Die oben beschriebene Methode gibt das Äquivalent oder #rindex (letztes Vorkommen des Werts) zurück. Um #index-äquivalente Ergebnisse zu erhalten, gibt der Hash den ersten Index des Werts zurück, den Sie vor dem Erstellen in umgekehrter Reihenfolge wie das Array umkehren müssen Der Hash subtrahiert dann den zurückgegebenen Indexwert von der Gesamtlänge des anfänglichen Arrays - 1. # (array.length - 1) - Hash ['b']
ashoda
2
Dauert die Umwandlung in einen Hash nicht O (n) Zeit? Ich nehme an, wenn es mehr als einmal verwendet wird, ist die Hash-Konvertierung leistungsfähiger. Aber ist es für den einmaligen Gebrauch nicht anders als das Durchlaufen des Arrays?
Ahnbizcad
Ja, und wahrscheinlich schlimmer für den einmaligen Gebrauch, wenn es wirklich darauf ankommt, da die Hash-Berechnung nicht so schnell kurzschließt wie ein Vergleich.
Peter DeWeese
199

Warum nicht Index oder Index verwenden?

array = %w( a b c d e)
# get FIRST index of element searched
puts array.index('a')
# get LAST index of element searched
puts array.rindex('a')

Index: http://www.ruby-doc.org/core-1.9.3/Array.html#method-i-index

Index: http://www.ruby-doc.org/core-1.9.3/Array.html#method-i-rindex

Roger
quelle
13
Dies ist genau das, was die OP aufgrund der Größe ihres Arrays nicht wollte. Der Array # -Index ist O (n), und wenn Sie dies mehrmals tun, wird die Leistung beeinträchtigt. Die Hash-Suche ist O (1).
Tim
4
@tim, nun, ich kann mich zum Zeitpunkt meiner Antwort nicht daran erinnern, dass DIESE Frage dieselbe war. Vielleicht hat das OP die Frage später überarbeitet, was diese Antwort ungültig machen würde.
Roger
3
Würde es nicht sagen, dass es zu einem bestimmten Zeitpunkt bearbeitet wurde?
Tim
Hehe, ja das stimmt. Nun, ich und weitere 30 Leute haben damals darüber gelesen. Ich denke: /
Roger
9

Andere Antworten berücksichtigen nicht die Möglichkeit, dass ein Eintrag mehrmals in einem Array aufgeführt wird. Dies gibt einen Hash zurück, bei dem jeder Schlüssel ein eindeutiges Objekt im Array ist und jeder Wert ein Array von Indizes ist, das dem Standort des Objekts entspricht:

a = [1, 2, 3, 1, 2, 3, 4]
=> [1, 2, 3, 1, 2, 3, 4]

indices = a.each_with_index.inject(Hash.new { Array.new }) do |hash, (obj, i)| 
    hash[obj] += [i]
    hash
end
=> { 1 => [0, 3], 2 => [1, 4], 3 => [2, 5], 4 => [6] }

Dies ermöglicht eine schnelle Suche nach doppelten Einträgen:

indices.select { |k, v| v.size > 1 }
=> { 1 => [0, 3], 2 => [1, 4], 3 => [2, 5] }
hololeap
quelle
6

Gibt es einen guten Grund, keinen Hash zu verwenden? Lookups sind O(1)vs. O(n)für das Array.

Erik Peterson
quelle
Der Punkt ist - ich rufe #keysHash auf, der ein Array zurückgibt, das ich benutze. Trotzdem könnte ich auch über meine Architektur nachdenken ...
gmile
3

Wenn es sich um ein sortiertes Array handelt, können Sie einen binären Suchalgorithmus ( O(log n)) verwenden. Erweitern Sie beispielsweise die Array-Klasse mit dieser Funktionalität:

class Array
  def b_search(e, l = 0, u = length - 1)
    return if lower_index > upper_index

    midpoint_index = (lower_index + upper_index) / 2
    return midpoint_index if self[midpoint_index] == value

    if value < self[midpoint_index]
      b_search(value, lower_index, upper_index - 1)
    else
      b_search(value, lower_index + 1, upper_index)
    end
  end
end
isakkarlsson
quelle
3
Es ist eigentlich nicht so schwer zu lesen. Erster Teil, Rückgabe, wenn die Untergrenze größer als die Obergrenze ist (die Rekursion wurde abgelegt). Der zweite Teil prüft, ob wir die linke oder rechte Seite benötigen, indem wir den Mittelpunkt m mit dem Wert an diesem Punkt mit e vergleichen. Wenn wir nicht die Antwort haben, die wir wollen, rekursieren wir.
Ioquatix
Ich denke, es ist besser für das Ego der Leute, die abstimmen, als zu bearbeiten.
Andre Figueiredo
2

Wenn Sie eine Kombination aus der Antwort von @ sawa und dem dort aufgeführten Kommentar verwenden, können Sie einen "schnellen" Index und einen Index für die Array-Klasse implementieren.

class Array
  def quick_index el
    hash = Hash[self.map.with_index.to_a]
    hash[el]
  end

  def quick_rindex el
    hash = Hash[self.reverse.map.with_index.to_a]
    array.length - 1 - hash[el]
  end
end
ianstarz
quelle
2

Wenn Ihr Array eine natürliche Reihenfolge hat, verwenden Sie die binäre Suche.

Verwenden Sie die binäre Suche.

Die binäre Suche hat O(log n)Zugriffszeit.

Hier sind die Schritte zur Verwendung der binären Suche:

  • Wie ist die Reihenfolge Ihres Arrays? Ist es zum Beispiel nach Namen sortiert?
  • Verwenden Sie bsearchdiese Option , um Elemente oder Indizes zu finden

Codebeispiel

# assume array is sorted by name!

array.bsearch { |each| "Jamie" <=> each.name } # returns element
(0..array.size).bsearch { |n| "Jamie" <=> array[n].name } # returns index
akuhn
quelle
0

Trotzdem frage ich mich, ob es eine bequemere Möglichkeit gibt, den Index von en element ohne Caching zu finden (oder ob es eine gute Caching-Technik gibt, die die Leistung steigert).

Sie können die binäre Suche verwenden (wenn Ihr Array geordnet ist und die Werte, die Sie im Array speichern, in irgendeiner Weise vergleichbar sind). Damit dies funktioniert, müssen Sie in der Lage sein, der binären Suche mitzuteilen, ob sie "nach links" oder "nach rechts" des aktuellen Elements suchen soll. Aber ich glaube, es ist nichts Falsches daran, das indexbeim Einfügen zu speichern und es dann zu verwenden, wenn Sie das Element aus demselben Array erhalten.

Julik
quelle