Il problema
Abbiamo bisogno di memorizzare i dati in un modo simile alla tabella, ma abbiamo limiti di spazio molto stretti (~ 1Mb per tabella di 10k + righe). Archiviamo dati come questo:
ID | reviews | factor | score | interval | etc.
---+---------+--------+-------+----------+-----
1 | 244 | 2.4 | 10 | 4268 | ...
in un semplice formato binario (una matrice unidimensionale di byte, in cui l'indice di ogni riga può essere calcolato semplicemente conoscendo la lunghezza di ogni riga, che è fissa).
C'è solo una funzione che legge sempre questi dati (ottiene una riga dal suo indice) e solo una funzione che aggiunge una nuova riga (alla fine). La rimozione di elementi dalla tabella non sarà mai richiesta (la tabella è solo per l'aggiunta). Entrambe le funzioni sono coperte da una discreta quantità di test unitari.
Il problema è il seguente: dobbiamo essere in grado di scorrere rapidamente le righe ordinate da diverse colonne . In altre parole, abbiamo bisogno che i dati siano ordinati per almeno due colonne.
Una soluzione semplice
Per risolvere questo problema, implementeremmo degli indici che, ancora una volta, sarebbero costituiti da blocchi di dati binari. Ora lo farebbe intuitivamente creando strutture dati ordinate che elencano solo l'indice della riga nella tabella originale:
factor_index score_index
------------ -----------
6 2
2 1
3 6
1 4
. .
La funzione che aggiunge una nuova riga alla tabella dovrebbe essere aggiornata per far sì che anche gli indici vengano aggiornati.
ESEMPIO: per ottenere il primo elemento ordinato per punteggio, basta cercare il primo valore nella tabella indice per il punteggio (2) e ottenere la riga corrispondente dalla tabella originale (la terza riga se siamo d'accordo che la tabella è a zero indici).
Tuttavia, mi è stato suggerito di adottare un approccio diverso.
Una versione più complessa ma presumibilmente più sicura
Invece di archiviare solo gli indici, duplichiamo i campi ID in ogni tabella indice:
factor_index | ID score_index | ID
-------------+--- ------------+---
6 | 46 2 | 8
2 | 8 1 | 14
3 | 91 6 | 46
1 | 14 4 | 60
. | . . | .
Quindi mantieni la tabella originale ordinata per ID e usa gli indici solo come posizione di partenza per una ricerca binaria nella tabella originale.
La funzione che aggiunge un nuovo record ora dovrà eseguire una ricerca binaria per ID per trovare dove inserire la nuova riga, e perché gli indici vengano aggiornati.
ESEMPIO: per ottenere il primo elemento ordinato per punteggio, cerchiamo la prima riga nella tabella indice per il punteggio (2, 8) e utilizziamo l'indice (2) come posizione di partenza per una ricerca binaria nella tabella. Se i dati sono validi, non abbiamo nemmeno bisogno di fare una ricerca binaria, perché nella posizione 2 troveremo la riga con l'ID 8. Se, tuttavia, troviamo che il record nella posizione 2 ha un indice diverso, continuiamo con una ricerca binaria per trovare quella giusta e registrare l'errore.
L'argomento per questo approccio è che funzionerà anche se l'indice punta alla riga sbagliata nella tabella.
Trovo difficile credere che questo approccio sia davvero migliore, per i seguenti motivi:
- Richiede una ricerca binaria, che può essere una nuova fonte di bug.
- Richiede che la tabella sia mantenuta in ordine, il che implica un inserimento più complesso (al contrario di una semplice append).
- Non fa in modo che il tavolo principale non funzioni: se ciò accade, l'indice potrebbe anche non riuscire a trovare il record tramite la ricerca binaria.
- Richiede la scrittura (e il test) di codice che non è mai nemmeno pensato per essere eseguito.
- Utilizza più dati di quelli necessari.
La domanda
È molto prioritario per la nostra applicazione che il dato di cui sopra sia sempre valido. Ma questo giustifica la scrittura di strutture di dati più complesse e meccanismi di ricerca per evitare casi limite che potrebbero o meno accadere? Non dovremmo invece impiegare tempo e sforzi per scrivere casi di test più robusti per una versione più semplice?