Filtro di una lista

1

Cercherò di semplificare il mio problema. Diciamo che ho una collezione (più specificamente una lista) con diversi tipi di elementi, a scopo di spiegazione diciamo che abbiamo oggetti "Persona" con diversa età di nascita e un ID. Rappresenterò una persona come questa: {<id>,<year>}

Ad un certo punto, voglio ottenere un elenco secondario di tutte le persone nate nel 1988 e nel 1989, e se ci sono no persone nate in quegli anni, io voglio tutte le persone nate nel 1990:

[{1,1988},{2,1988},{3,1982},{4,1995},{5,1989},{6,1989},{7,1990}] ==> 
[{1,1988},{2,1988},{5,1989},{6,1989}]


[{1,1990},{2,1981},{3,1983},{4,1993},{5,1981},{6,1989},{7,1990}] ==>
[{6,1989}]

[{1,1990},{2,1981},{3,1983},{4,1993},{5,1981},{6,1985},{7,1990}] ==>
[{1,1990},{7,1990}]

Opzione 0

All'inizio pensavo di usare filter metodo sulla libreria di apache commons, ma questo non funzionerebbe come avrei bisogno di sapere su tutti i elemento sulla lista per decidere se un elemento particolare va o meno nella lista secondaria, quindi ho escluso questa opzione.

Opzione 1

Quindi, il modo più semplice che posso vedere per fare ciò è scorrere l'elenco alla ricerca di elementi che corrispondono alla prima condizione (1988 o 1989), e quindi, se non riesco a trovarne, riavvia il ciclo per cercare elementi che corrisponde alla seconda condizione (1900). Ma non mi piace perché devi scorrere l'elenco più di una volta.

Opzione 2

L'altra opzione a cui posso pensare è di scorrere l'elenco una sola volta e di avere due diversi sotto-elenchi, uno con gli elementi che corrispondono alla prima condizione e un altro con gli elementi che corrispondono alla seconda condizione. Alla fine, se la prima sottocartella è vuota, restituiamo la seconda lista secondaria.

Quale di questi due è migliore (più efficiente)?
Quale useresti e perché?
Pensi che ci sia una soluzione migliore di queste?

    
posta mario595 29.07.2014 - 16:54
fonte

3 risposte

1

Ecco le considerazioni pertinenti che potrebbero influenzare ciò che farei.

  • Questo metodo è cruciale per il tempo? In caso contrario, dimenticare le considerazioni su quale è più veloce. Il loop su una struttura dati è raramente più costoso rispetto all'elaborazione effettiva che stai facendo sui dati.
  • Se è time-critical, che è più veloce in un esperimento? Indovinare quale alternativa sia più veloce è quasi sempre inutile: se vale la pena fare micro-ottimizzazione, vale la pena fare empiricamente. Sì, questo significa codificare entrambi e buttare via una delle soluzioni. Questa è la vita.
  • Nel complesso, quello che è il più facile da leggere pur soddisfacendo i requisiti è meglio di qualsiasi altra cosa. Se la libreria che si sta utilizzando supporta il filtro complesso, magari con un callback del filtro definito dall'utente, scrivere un callback complesso, assegnargli un nome significativo e quindi risolvere il problema originale in una riga. Non puoi essere più leggibile di così. Altrimenti, vale la pena provarlo con lo pseudocodice: una alternativa ha un if in più e un altro ciclo che viene eseguito solo in modo condizionale; l'altro ha un corpo del loop più complicato e un piccolo if che restituisce una lista se l'altra è vuota. Questo sembra un po 'più vicino alla dichiarazione del problema originale "se non ci sono persone nate in quegli anni, voglio tutte le persone nate nel 1990", quindi potrebbe essere meglio. Ma di nuovo dovresti guardare entrambi e poi decidere quale sia preferibile.
risposta data 29.07.2014 - 17:15
fonte
1

Which one of these two is better (more efficient)?

Non hai veramente definito ciò che intendi per "efficiente", ma d'altra parte, il tuo problema reale è un po 'più complesso di quello che hai descritto. Sarei interessato ad avere maggiori dettagli, se possibile.

Dato che non ci sono requisiti specifici di temporizzazione o utilizzo della memoria, né chiari limiti esterni sui tuoi input, il problema è un po 'troppo generico: non dovresti davvero preoccuparti di quale opzione sia più efficiente.

Vediamo, ad esempio, la peggiore complessità del caso:

  • Opzione 1: O (n) ora, O (n) spazio
  • Opzione 2: O (n) ora, O (n) spazio

Io ora più di una persona che direbbe all'istante:  " Se è lineare, non sprecare il tuo tempo con esso! "

Ora, praticamente, l'opzione 2 potrebbe sembrare più interessante perché:

  • devi solo eseguire il ciclo una sola volta sui tuoi dati e,
  • per la maggior parte delle persone (me compreso), la rappresentazione mentale di come viene eseguito il calcolo presuppone che sia meglio calcolare tutto in un unico passaggio anziché eseguire più passaggi (perché l'attraversamento di un elenco potrebbe richiedere un sovraccarico); quel modello non prende necessariamente in considerazione vincoli esterni come I / O, memoria, cache e così via, e come interagiscono. In effetti, trovo davvero difficile sapere in anticipo quale sia l'opzione migliore, in generale. Tuttavia, più passaggi sono un ottimo modo per separare il lavoro in blocchi leggibili (ad esempio, vedi come i compilatori non provano a fare tutto in una volta).

Se hai alcune prove obiettive che devi ottimizzare quella parte, sperimenta e misura per una metrica specifica con input rappresentativi.

Which would you use and why?

Vorrei usare l'Opzione 1, principalmente per Keep It Simple, Stupid , e anche perché non c'è apparente ragione per cui "l'efficienza" è fondamentale qui.

L'opzione 1 è leggibile, facile da capire, facile da gestire.

Do you think there is a better solution than these?

"Meglio" è ugualmente difficile da comprendere, qui, per gli stessi motivi di cui sopra. Ma se supponiamo che "Opzione 2" sia decisamente migliore di "Opzione 1", allora l'opzione 2 può essere ancora più efficace:

Non appena incontri il 1988 o il 1989, smetti di compilare il secondo elenco.

Quindi, non memorizzerai più di N elementi (in entrambi gli elenchi di output), se la tua lista di input ha N elementi.

Per il gusto di farlo, ecco un'implementazione di prototipo in Common Lisp. Ricorda, non raccomando questo approccio, personalmente rimango personalmente con l'Opzione 1.

Per prima cosa, ecco alcune sequenze di test (incluse le tue):

   (defparameter *test*
     '(((c 1990) (x 1990))
       ((a 1989) (b 1988) (c 1990))
       ((a 1990) (b 1985) (c 1998))
       ((a 1990) (b 1985) (c 1988))
       ((1 1990) (2 1981) (3 1983) (4 1993) (5 1981) (6 1985) (7 1990))
       ((1 1990) (2 1981) (3 1983) (4 1993) (5 1981) (6 1989) (7 1990))
       ((1 1988) (2 1988) (3 1982) (4 1995) (5 1989) (6 1989) (7 1990))))

Ecco un'opzione modificata 2 che non raccoglie elementi "1990" se alcuni elementi attuali o precedenti erano membri di {1988,1989}:

 (defun option-2 (pair-list)
   (loop for (name year) in pair-list
         for member-p = (member year '(1989 1988) :test #'eql)
         for some-member-p = member-p then (or some-member-p member-p)
         when member-p 
           collect (list name year) into bag-a
         when (and (not some-member-p) (eql year 1990))
           collect (list name year) into bag-b
         finally (return (if some-member-p bag-a bag-b))))

   (mapcar #'option-2 *test*)

   => (((C 1990) (X 1990)) ((A 1989) (B 1988)) ((A 1990)) ((C 1988))
      ((1 1990) (7 1990)) ((6 1989)) ((1 1988) (2 1988) (5 1989) (6 1989)))

Per un confronto, ecco l'Opzione 1. È più facile da capire, IMHO:

 (defun option-1 (pair-list)
      (or (remove-if-not (lambda (x) (member x '(1989 1988) :test #'eql)) 
                         pair-list :key #'second)                   
          (remove-if-not (lambda (x) (eql x 1990)) 
                         pair-list :key #'second))))

 (mapcar #'option-1 *test*)

  => (((C 1990) (X 1990)) ((A 1989) (B 1988)) ((A 1990)) ((C 1988))
      ((1 1990) (7 1990)) ((6 1989)) ((1 1988) (2 1988) (5 1989) (6 1989)))
    
risposta data 30.07.2014 - 02:24
fonte
0

Il mio primo pensiero è quello di ordinare l'elenco o tenerlo in ordine. In questo modo, sai mentre lo attraversi dopo aver superato la prima condizione, sei alla seconda condizione. Puoi anche terminare anticipatamente dopo aver superato sia la prima che la seconda condizione.

Suppongo che questo sia un esempio ridotto, e potresti farlo su più di una proprietà.

Quindi, il secondo modo in cui lo farei è avere una lista. Aggiungi elementi forma la condizione 1 alla parte anteriore dell'elenco, aggiungi elementi dalla condizione 2 alla fine dell'elenco. Mantieni un contatore di quanti elementi corrispondono alla condizione 1. Se il contatore è maggiore di 0, restituisci la prima parte dell'elenco, se è 0, quindi restituisci l'elenco così com'è.

    
risposta data 29.07.2014 - 19:24
fonte

Leggi altre domande sui tag