Modo efficiente per mescolare gli oggetti


20

Sto scrivendo un programma per alcuni software di quiz. Ho una classe di domande che contiene gli ArrayList per domande, risposte, opzioni, segni e segni negativi. Qualcosa come questo:

class question
{
    private ArrayList<Integer> index_list;
    private ArrayList<String> question_list;        
    private ArrayList<String> answer_list;      
    private ArrayList<String> opt1_list;        
    private ArrayList<String> opt2_list;    
}

Voglio mescolare tutte le domande, ma affinché le domande vengano mescolate, tutti gli oggetti devono essere mescolati. Avrei affrontato questo problema in questo modo:

Prima di tutto, non avrei usato questo design e non avrei usato String ArrayList<String>come tipo di variabile di istanza, e quindi avrei usato il Collections.shufflemetodo per mescolare gli oggetti. Ma il mio team insiste su questo design.

Ora, la classe di domande contiene liste di array in aumento man mano che si inserisce la domanda. Come mescolare le domande ora?


30
Odio parlare in modo assoluto, ma se il tuo team insiste su questo progetto, allora si sbagliano. Diglielo! Di 'loro che l'ho detto (e l'ho scritto su Internet, quindi devo avere ragione).
Joachim Sauer,

10
Sì, dì loro che ci sono molte persone qui che ti dicono che questo tipo di design è un tipico errore per principianti.
Doc Brown,

6
Per curiosità: quali vantaggi vede il tuo team in questo design?
Joachim Sauer,

9
Le convenzioni di denominazione Java sono CamelCase per i nomi delle classi e camelCase per i nomi delle variabili.
Tulains Córdova,

Penso che tu debba confrontarti con il tuo team su questa terribile decisione di progettazione. Se continuano a insistere, scopri perché. Se è solo testardaggine, forse inizia a pensare di trovare una nuova squadra in un futuro non troppo lontano. Se hanno ragioni per questa struttura, allora considera quelle ragioni per i loro meriti.
Ben Lee,

Risposte:


95

La tua squadra soffre di un problema comune: la negazione degli oggetti .

Invece di una classe che contiene una singola domanda con tutte le informazioni ad essa associate, si tenta di creare una classe chiamata questionche contiene tutte le domande in una singola istanza.

Questo è il modo sbagliato di farlo e complica ciò che cerchi di fare molto ! L'ordinamento (e lo shuffle) di matrici parallele (o Liste) è un brutto affare e non esiste un'API comune, semplicemente perché di solito si desidera evitarlo affatto .

Ti suggerisco di ristrutturare il tuo codice in questo modo:

class Question
{
    private Integer index;
    private String question;        
    private String answer;      
    private String opt1;        
    private String opt2;    
}

// somewhere else
List<Question> questionList = new ArrayList<Question>();

In questo modo, mescolare la tua domanda diventa banale (usando Collections.shuffle()):

Collections.shuffle(questionList);

39
non è nemmeno la negazione di un oggetto, è una negazione della struttura di dati
jk.

22

Non Fai un altro elenco / coda di indici e lo mescoli. Quindi ripeti gli indici che guidano l'ordine "mischiato" delle altre tue raccolte.

Anche al di fuori del tuo scenario con elementi divisi, la raccolta ordini separata offre una serie di vantaggi (il parallelismo, la velocità quando il ripristino della raccolta originale è costoso, ecc.).


10
Sono riluttante a votare questo: è la soluzione migliore se il progetto è stato risolto, ma insistere su questo progetto è così sbagliato che non vorrei dare alcun suggerimento al riguardo. (meh, comunque votato ;-))
Joachim Sauer,

3
@joachimSauer - mentre sono d'accordo, ci sono molti altri scenari (meno offensivi) in cui la raccolta originale deve rimanere statica mentre il percorso attraverso di essi deve variare.
Telastyn,

4
si, lo so. E mescolare una raccolta di indici è l'approccio corretto per quelle situazioni. La mia unica paura è che il team dei PO prenderà questo e dirà "abbastanza bene", senza rivedere il loro design.
Joachim Sauer,

1
Questa risposta è preziosa soprattutto nel caso in cui non si abbia la libertà di rivedere / ricodificare la classe o la struttura di raccolta sottostante, ad esempio si deve accontentarsi di un'API per una raccolta gestita dal sistema operativo. Mescolare gli indici è una grande intuizione e si regge da solo anche se non è acuta un'intuizione come rifare il disegno sottostante.
Hardmath,

@Joachim Sauer: mescolare gli indici in realtà non è necessariamente la migliore soluzione al problema, come affermato. Vedi la mia risposta per un'alternativa.
Michael Borgwardt,

16

Concordo con le altre risposte sul fatto che la soluzione corretta è utilizzare un modello di oggetto adeguato.

Tuttavia, in realtà è abbastanza facile mescolare più liste in modo identico:

Random rnd = new Random();
long seed = rnd.nextLong();

rnd.setSeed(seed);
Collections.shuffle(index_list, rnd);
rnd.setSeed(seed);
Collections.shuffle(question_list, rnd);
rnd.setSeed(seed);
Collections.shuffle(answer_list, rnd);
...

Questo è ... un modo pulito per farlo! Ora, per il caso dello "smistamento", dobbiamo solo trovare un seme che produce un elenco ordinato quando applicato in questo modo e quindi mescoliamo tutti gli elenchi con questo seme!
Joachim Sauer,

1
@JoachimSauer: beh, l'ordinamento non faceva parte del problema. Anche se è una domanda interessante se esiste un modo sistematico per trovare un tale seme per un dato RNG.
Michael Borgwardt,

2
@MichaelBorgwardt una volta superate le 17 domande, non puoi semplicemente esprimere la quantità di possibili mescolamenti nei 48 bit che java Random usa (log_2 (17!) = 48.33)
maniaco del cricchetto,

@ratchetfreak: non mi sembra un vero problema. Ed è banale usare SecureRandom invece, se necessario.
Michael Borgwardt,

4
@Telastyn: l'elenco degli indici è IMO uno strato di riferimento indiretto che rende concettualmente la tua soluzione più complessa e se è più o meno performante dipende dalla frequenza con cui si accede agli elenchi dopo lo shuffle. Ma le differenze di rendimento sarebbero insignificanti date le dimensioni realistiche per un quiz a cui gli umani devono rispondere.
Michael Borgwardt,

3

Crea un corso Question2:

class Question2
{
    public Integer index_list;
    public String question_list;        
    public String answer_list;      
    public String opt1_list;        
    public String opt2_list;    
}

Quindi creare una funzione mappando questiona ArrayList<Question2>, usare Collection.Shuffleper quel risultato e creare una seconda funzione per mappare di ArrayList<Question2>nuovo a question.

Successivamente, vai al tuo team e prova a convincerli che l'uso di un ArrayList<Question2>invece di questionmigliorerebbe molto il loro codice, dal momento che li salverebbe molte di quelle conversioni inutili.


1
Questa è una buona idea, ma solo dopo che i tentativi a priori di modificare il design sono falliti.
Sebastian Redl,

@SebastianRedl: a volte è più facile convincere le persone di un design migliore quando si mostra loro la soluzione nel codice.
Doc Brown,

1

La mia risposta ingenua e sbagliata originale :

Basta creare (almeno) nnumeri casuali e scambiare l'oggetto n con l'elemento iin un ciclo for per ogni elenco che hai.

In pseudo codice:

for (in i = 0; i < question_list.length(); i++) {
  int random = randomNumber(0, questions_list.length()-1);
  question_list.switchItems(i, random);
  answer_list.switchItems(i, random);
  opt1_list.switchItems(i, random);
  opt2_list.switchItems(i, random);

}

Aggiornare:

Grazie per the_lotus per aver segnalato l'articolo horror sulla codifica. Mi sento molto più intelligente ora :-) Comunque Jeff Atwood mostra anche come farlo nel modo giusto, usando l' algoritmo Fisher-Yates :

for (int i = question_list.Length - 1; i > 0; i--){
  int n = rand.Next(i + 1); //assuming rand.Next(x) returns values between 0 and x-1
  question_list.switchItems(i, n);
  answer_list.switchItems(i, n);
  opt1_list.switchItems(i, n);
  opt2_list.switchItems(i, n);
}

La differenza principale qui è che ogni elemento viene scambiato una sola volta.

E mentre le altre risposte spiegano correttamente che il tuo modello a oggetti è difettoso, potresti non essere in grado di cambiarlo. Quindi l'algoritmo Fisher-Yates risolverebbe il tuo problema senza cambiare il tuo modello di dati.


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.