Ottieni l'indice dell'elemento dell'array più velocemente di O (n)


104

Dato che ho una matrice ENORME e un valore da essa. Voglio ottenere l'indice del valore in array. C'è un altro modo, piuttosto che chiamare Array#indexper ottenerlo? Il problema deriva dalla necessità di mantenere un array davvero enorme e chiamare Array#indexun'enorme quantità di volte.

Dopo un paio di tentativi ho scoperto che la memorizzazione nella cache degli indici all'interno degli elementi memorizzando strutture con (value, index)campi invece del valore stesso dà un enorme passo in avanti nelle prestazioni (20 volte la vittoria).

Tuttavia mi chiedo se ci sia un modo più conveniente per trovare index of en element senza caching (o se c'è una buona tecnica di caching che aumenterà le prestazioni).

Risposte:


118

Converti l'array in un hash. Quindi cerca la chiave.

array = ['a', 'b', 'c']
hash = Hash[array.map.with_index.to_a]    # => {"a"=>0, "b"=>1, "c"=>2}
hash['b'] # => 1

2
più veloce se l'array è molto lungo
Kevin

17
A seconda del caso d'uso, ciò potrebbe essere problematico se sono presenti valori duplicati. Il metodo descritto sopra restituirà l'equivalente o #rindex (ultima occorrenza di valore) Per ottenere risultati equivalenti a #index, ovvero l'hash che restituisce il primo indice del valore dovresti fare qualcosa sulla falsariga di invertire l'array prima di creare l'hash sottraendo quindi il valore dell'indice restituito dalla lunghezza totale dell'array iniziale - 1. # (array.length - 1) - hash ['b']
ashoda

2
La conversione in un hash non richiede O (n) tempo? Suppongo che se verrà utilizzato più di una volta, la conversione hash sarà più performante. ma per un utilizzo singolo, non è diverso quindi iterare attraverso l'array?
ahnbizcad

Sì, e probabilmente peggio per uso singolo se è davvero importante poiché il calcolo dell'hash non va in cortocircuito così rapidamente come un confronto.
Peter DeWeese

199

Perché non utilizzare index o rindex?

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')

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

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


13
Questo è esattamente ciò che l'OP ha detto di NON volere, a causa delle grandi dimensioni del loro array. L'indice dell'array # è O (n) e farlo più volte ucciderà le prestazioni. La ricerca hash è O (1).
Tim

4
@tim, beh non ricordo al momento della mia risposta che QUESTA era la stessa domanda, forse l'OP ha rivisto la domanda in seguito, il che invaliderebbe questa risposta.
Roger

3
Non direbbe allora che è stato modificato in un momento specifico?
Tim

Hehe, sì, è vero. Beh, io e altre 30 persone stavamo leggendo su di esso allora. Immagino: /
Roger

9

Altre risposte non tengono conto della possibilità di una voce elencata più volte in una matrice. Questo restituirà un hash in cui ogni chiave è un oggetto univoco nell'array e ogni valore è un array di indici che corrisponde a dove risiede l'oggetto:

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] }

Ciò consente una rapida ricerca di voci duplicate:

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

6

C'è una buona ragione per non usare un hash? Le ricerche sono O(1)rispetto O(n)all'array.


Il punto è: sto chiamando #keyshash, che restituisce un array che sto usando. Tuttavia, potrei pensare anche alla mia architettura ...
gmile

3

Se è un array ordinato, potresti usare un algoritmo di ricerca binaria ( O(log n)). Ad esempio, estendendo la classe Array con questa funzionalità:

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

3
In realtà non è così difficile da leggere. Prima parte, torna se il limite inferiore è maggiore del limite superiore (la ricorsione è stata archiviata). la seconda parte controlla se abbiamo bisogno del lato sinistro o destro confrontando il punto medio m con il valore in quel punto a e. se non abbiamo la risposta che vogliamo, ricorriamo.
ioquatix

Penso che faccia meglio per l'ego delle persone che votano piuttosto che modificare.
Andre Figueiredo

2

Prendendo una combinazione della risposta di @ sawa e del commento qui elencato, potresti implementare un indice "rapido" e un rindex sulla classe dell'array.

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

2

Se il tuo array ha un ordine naturale usa la ricerca binaria.

Usa la ricerca binaria.

La ricerca binaria ha O(log n)tempo di accesso.

Ecco i passaggi su come utilizzare la ricerca binaria,

  • Qual è l'ordine del tuo array? Ad esempio, è ordinato per nome?
  • Da utilizzare bsearchper trovare elementi o indici

Esempio di codice

# 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

0

Tuttavia mi chiedo se ci sia un modo più conveniente per trovare index of en element senza caching (o se c'è una buona tecnica di caching che aumenterà le prestazioni).

È possibile utilizzare la ricerca binaria (se l'array è ordinato ei valori memorizzati nell'array sono in qualche modo confrontabili). Affinché funzioni, devi essere in grado di dire alla ricerca binaria se deve guardare "a sinistra" o "a destra" dell'elemento corrente. Ma credo che non ci sia nulla di sbagliato nel memorizzare il indexmomento dell'inserimento e quindi utilizzarlo se si ottiene l'elemento dallo stesso array.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.