J'ai rencontré cette question tout en recherchant un problème similaire: des ajouts optimaux de liquides pour réduire la stratification. Il semble que ma solution soit également applicable à votre situation.
Si vous voulez mélanger les liquides A, B et C dans la proportion 30,20,10 (c'est-à-dire 30 unités de A, 20 unités de B et 10 unités de C), vous vous retrouvez avec une stratification si vous ajoutez tous le A, puis tout le B, puis tout le C. Il vaut mieux mélanger des unités plus petites. Par exemple, effectuez des ajouts d'unité dans la séquence [A, B, A, C, B, A]. Cela empêchera complètement la stratification.
La façon dont je l'ai trouvé est de le traiter comme une sorte de fusion, en utilisant une file d'attente prioritaire. Si je crée une structure pour décrire les ajouts:
MergeItem
Item, Count, Frequency, Priority
La fréquence est exprimée comme "un tous les N". Ainsi, A, qui est ajouté trois fois sur six, a une fréquence de 2 (6/3).
Et initialisez un tas qui contient initialement:
(A, 3, 2, 2)
(B, 2, 3, 3)
(C, 1, 6, 6)
Maintenant, je supprime le premier élément du tas et je le génère. Ensuite, réduisez son nombre de 1 et augmentez la priorité par fréquence et ajoutez-le au tas. Le tas résultant est:
(B, 2, 3, 0)
(A, 2, 2, 4)
(C, 1, 6, 6)
Ensuite, supprimez B du tas, éditez-le et mettez-le à jour, puis rajoutez-le au tas:
(A, 2, 2, 4)
(C, 1, 6, 6)
(B, 1, 3, 6)
Si je continue de cette façon, j'obtiens le mélange souhaité. J'utilise un comparateur personnalisé pour m'assurer que lorsque des éléments de priorité égaux sont insérés dans le tas, celui avec la valeur de fréquence la plus élevée (c'est-à-dire la moins fréquente) est commandé en premier.
J'ai écrit une description plus complète du problème et de sa solution sur mon blog, et présenté un code C # fonctionnel qui l'illustre. Voir Répartition uniforme des éléments dans une liste .
Mettre à jour après les commentaires
Je pense que mon problème est similaire au problème de l'OP, et donc que ma solution est potentiellement utile. Je m'excuse de ne pas avoir cadré ma réponse davantage dans les termes de la question du PO.
La première objection, que ma solution utilise A, B et C plutôt que 0, 1 et 2, est facilement corrigée. C'est simplement une question de nomenclature. Je trouve plus facile et moins déroutant de penser et de dire "deux A" plutôt que "deux 1". Mais aux fins de cette discussion, j'ai modifié mes sorties ci-dessous pour utiliser la nomenclature du PO.
Bien sûr, mon problème concerne le concept de distance. Si vous voulez "répartir uniformément les choses", la distance est implicite. Mais, encore une fois, c'était mon échec de ne pas montrer de manière adéquate comment mon problème est similaire au problème du PO.
J'ai effectué quelques tests avec les deux exemples fournis par l'OP. C'est:
[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]
Dans ma nomenclature, ceux-ci sont exprimés respectivement en [2,2,2] et [4,3,2,1]. Soit, dans le dernier exemple, "4 éléments de type 0, 3 éléments de type 1, 2 éléments de type 2 et 1 élément de type 3."
J'ai exécuté mon programme de test (comme décrit ci-dessous) et j'ai publié mes résultats. En l'absence de données de l'OP, je ne peux pas dire si mes résultats sont similaires, pires ou meilleurs que les siens. Je ne peux pas non plus comparer mes résultats à ceux des autres parce que personne d'autre n'en a publié.
Je peux dire, cependant, que l'algorithme fournit une bonne solution à mon problème d'élimination de la stratification lors du mélange de liquides. Et il semble qu'il offre une solution raisonnable au problème du PO.
Pour les résultats ci-dessous, j'ai utilisé l'algorithme que j'ai détaillé dans mon article de blog, avec la priorité initiale définie sur Frequency/2
et le comparateur de tas modifié pour favoriser l'élément le plus fréquent. Le code modifié est affiché ici, avec les lignes modifiées commentées.
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;
}
}
En exécutant mon programme de test avec le premier exemple de l'OP, j'obtiens:
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
Donc mon algorithme fonctionne pour le problème trivial de tous les comptes étant égaux.
Pour le deuxième problème signalé par l'OP, j'ai obtenu:
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
Je ne vois pas de moyen évident d'améliorer cela. Il pourrait être réorganisé pour faire les distances pour l'élément 0 [2,3,2,3] ou un autre arrangement de 2 et 3, mais cela changera les écarts pour les éléments 1 et / ou 2. Je ne sais vraiment pas quoi "optimum" est dans cette situation. Est-il préférable d'avoir un écart plus important sur les éléments les plus fréquents ou les moins fréquents?
En l'absence d'autres problèmes de l'OP, j'ai utilisé ses descriptions pour en inventer quelques-unes. Il a déclaré dans son message:
Une liste typique comprend ~ 50 articles avec ~ 15 valeurs différentes en quantités variées.
Mes deux tests ont donc été:
[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
Et mes résultats:
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
Et pour le deuxième exemple:
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