Mi sono imbattuto in questa domanda mentre cercavo un problema simile: aggiunte ottimali di liquidi per ridurre la stratificazione. Sembra che la mia soluzione sia applicabile anche alla tua situazione.
Se vuoi mescolare i liquidi A, B e C nella proporzione 30,20,10 (cioè 30 unità di A, 20 unità di B e 10 unità di C), finisci con la stratificazione se aggiungi tutto la A, poi tutta la B e poi tutta la C. Farai meglio a mescolare unità più piccole. Ad esempio, eseguire aggiunte di unità singole nella sequenza [A, B, A, C, B, A]. Ciò impedirà del tutto la stratificazione.
Il modo in cui ho scoperto di farlo è di trattarlo come una specie di unione, usando una coda prioritaria. Se creo una struttura per descrivere le aggiunte:
MergeItem
Item, Count, Frequency, Priority
La frequenza è espressa come "uno ogni N". Quindi A, che viene aggiunto tre volte su sei, ha una frequenza di 2 (6/3).
E inizializza un heap che inizialmente contiene:
(A, 3, 2, 2)
(B, 2, 3, 3)
(C, 1, 6, 6)
Ora rimuovo il primo elemento dall'heap e lo output. Quindi riduci il conteggio di 1 e aumenta la priorità per frequenza e aggiungilo nuovamente all'heap. L'heap risultante è:
(B, 2, 3, 0)
(A, 2, 2, 4)
(C, 1, 6, 6)
Quindi, rimuovere B dall'heap, emetterlo e aggiornarlo, quindi aggiungere di nuovo all'heap:
(A, 2, 2, 4)
(C, 1, 6, 6)
(B, 1, 3, 6)
Se continuo in quel modo, ottengo la miscela desiderata. Uso un comparatore personalizzato per garantire che quando vengono inseriti nell'Heap articoli con priorità uguale, venga ordinato per primo quello con il valore di Frequenza più alto (ovvero il meno frequente).
Ho scritto una descrizione più completa del problema e della sua soluzione sul mio blog e ho presentato un codice C # funzionante che lo illustra. Vedi Distribuire uniformemente gli articoli in un elenco .
Aggiorna dopo i commenti
Penso che il mio problema sia simile al problema del PO e quindi che la mia soluzione sia potenzialmente utile. Mi scuso per non aver inquadrato più la mia risposta nei termini della domanda del PO.
La prima obiezione, secondo cui la mia soluzione sta usando A, B e C anziché 0, 1 e 2, è facilmente risolta. È semplicemente una questione di nomenclatura. Trovo più facile e meno confuso pensare e dire "due A" anziché "due 1". Ma ai fini di questa discussione ho modificato i miei risultati di seguito per utilizzare la nomenclatura del PO.
Ovviamente il mio problema riguarda il concetto di distanza. Se si desidera "distribuire le cose in modo uniforme", è implicita la distanza. Ma, ancora una volta, non sono riuscito a dimostrare in modo adeguato come il mio problema fosse simile a quello del PO.
Ho eseguito alcuni test con i due esempi forniti dall'OP. Questo è:
[1,1,2,2,3,3] // which I converted to [0,0,1,1,2,2]
[0,0,0,0,1,1,1,2,2,3]
Nella mia nomenclatura quelli sono espressi come [2,2,2] e [4,3,2,1], rispettivamente. Cioè, nell'ultimo esempio, "4 articoli di tipo 0, 3 articoli di tipo 1, 2 articoli di tipo 2 e 1 articolo di tipo 3."
Ho eseguito il mio programma di test (come descritto immediatamente di seguito) e ho pubblicato i miei risultati. Assente input dall'OP, non posso dire se i miei risultati sono simili, peggiori o migliori dei suoi. Né posso confrontare i miei risultati con quelli di chiunque altro perché nessun altro ha pubblicato alcun risultato.
Posso dire, tuttavia, che l'algoritmo fornisce una buona soluzione al mio problema di eliminare la stratificazione durante la miscelazione di liquidi. E sembra che fornisca una soluzione ragionevole al problema del PO.
Per i risultati mostrati di seguito, ho usato l'algoritmo che ho dettagliato nel mio post di blog, con la priorità iniziale impostata su Frequency/2
e il comparatore di heap modificato per favorire l'elemento più frequente. Il codice modificato viene mostrato qui, con le righe modificate commentate.
private class HeapItem : IComparable<HeapItem>
{
public int ItemIndex { get; private set; }
public int Count { get; set; }
public double Frequency { get; private set; }
public double Priority { get; set; }
public HeapItem(int itemIndex, int count, int totalItems)
{
ItemIndex = itemIndex;
Count = count;
Frequency = (double)totalItems / Count;
// ** Modified the initial priority setting.
Priority = Frequency/2;
}
public int CompareTo(HeapItem other)
{
if (other == null) return 1;
var rslt = Priority.CompareTo(other.Priority);
if (rslt == 0)
{
// ** Modified to favor the more frequent item.
rslt = Frequency.CompareTo(other.Frequency);
}
return rslt;
}
}
Eseguendo il mio programma di test con il primo esempio dell'OP, ottengo:
Counts: 2,2,2
Sequence: 1,0,2,1,0,2
Distances for item type 0: 3,3
Stddev = 0
Distances for item type 1: 3,3
Stddev = 0
Distances for item type 2: 3,3
Stddev = 0
Quindi il mio algoritmo funziona per il banale problema di eguagliare tutti i conteggi.
Per il secondo problema pubblicato dall'OP, ho ottenuto:
Counts: 4,3,2,1
Sequence: 0,1,2,0,1,3,0,2,1,0
Distances for item type 0: 3,3,3,1
Stddev = 0.866025403784439
Distances for item type 1: 3,4,3
Stddev = 0.471404520791032
Distances for item type 2: 5,5
Stddev = 0
Distances for item type 3: 10
Stddev = 0
Standard dev: 0.866025403784439,0.471404520791032,0,0
Non vedo un modo ovvio per migliorarlo. Potrebbe essere riorganizzato per fare le distanze per l'articolo 0 [2,3,2,3] o qualche altra disposizione di 2 e 3, ma ciò cambierà le deviazioni per gli articoli 1 e / o 2. Non so davvero cosa "ottimale" è in questa situazione. È meglio avere una deviazione maggiore sugli articoli più frequenti o meno frequenti?
In assenza di altri problemi dall'OP, ho usato le sue descrizioni per inventarne alcune. Ha detto nel suo post:
Un elenco tipico ha ~ 50 articoli con ~ 15 valori diversi in varie quantità.
Quindi i miei due test sono stati:
[8,7,6,5,5,4,3,3,2,2,2,1,1,1,1] // 51 items, 15 types
[12,6,5,4,4,3,3,3,2,2,2,1,1] // 48 items, 13 types
E i miei risultati:
Counts: 8,7,6,5,5,4,3,3,2,2,2,1,1,1,1
Sequence: 0,1,2,3,4,5,7,6,0,1,2,8,9,10,4,3,0,1,5,2,0,1,3,4,6,7,14,11,13,12,0,2,5,1,0,3,4,2,8,10,9,1,0,7,6,5,3,4,2,1,0
Distances for item type 0: 8,8,4,10,4,8,8,1
Stddev = 2.82566363886433
Distances for item type 1: 8,8,4,12,8,8,3
Stddev = 2.76272565797339
Distances for item type 2: 8,9,12,6,11,5
Stddev = 2.5
Distances for item type 3: 12,7,13,11,8
Stddev = 2.31516738055804
Distances for item type 4: 10,9,13,11,8
Stddev = 1.72046505340853
Distances for item type 5: 13,14,13,11
Stddev = 1.08972473588517
Distances for item type 6: 17,20,14
Stddev = 2.44948974278318
Distances for item type 7: 19,18,14
Stddev = 2.16024689946929
Distances for item type 8: 27,24
Stddev = 1.5
Distances for item type 9: 28,23
Stddev = 2.5
Distances for item type 10: 26,25
Stddev = 0.5
Distances for item type 11: 51
Stddev = 0
Distances for item type 12: 51
Stddev = 0
Distances for item type 13: 51
Stddev = 0
Distances for item type 14: 51
Stddev = 0
E per il secondo esempio:
Counts: 12,6,5,4,4,3,3,3,2,2,2,1,1
Sequence: 0,1,2,0,3,4,7,5,6,0,1,8,9,10,0,2,0,3,4,1,0,2,6,7,5,12,11,0,1,0,3,4,2,0,1,10,8,9,0,7,5,6,0,
4,3,2,1,0
Distances for item type 0: 3,6,5,2,4,7,2,4,5,4,5,1
Stddev = 1.68325082306035
Distances for item type 1: 9,9,9,6,12,3
Stddev = 2.82842712474619
Distances for item type 2: 13,6,11,13,5
Stddev = 3.44093010681705
Distances for item type 3: 13,13,14,8
Stddev = 2.34520787991171
Distances for item type 4: 13,13,12,10
Stddev = 1.22474487139159
Distances for item type 5: 17,16,15
Stddev = 0.816496580927726
Distances for item type 6: 14,19,15
Stddev = 2.16024689946929
Distances for item type 7: 17,16,15
Stddev = 0.816496580927726
Distances for item type 8: 25,23
Stddev = 1
Distances for item type 9: 25,23
Stddev = 1
Distances for item type 10: 22,26
Stddev = 2
Distances for item type 11: 48
Stddev = 0
Distances for item type 12: 48
Stddev = 0