Me encontré con esta pregunta mientras investigaba un problema similar: adiciones óptimas de líquidos para reducir la estratificación. Parece que mi solución también sería aplicable a su situación.
Si desea mezclar líquidos A, B y C en la proporción de 30,20,10 (es decir, 30 unidades de A, 20 unidades de B y 10 unidades de C), terminará con la estratificación si agrega todos la A, luego toda la B y luego toda la C. Es mejor mezclar unidades más pequeñas. Por ejemplo, haga adiciones de una sola unidad en la secuencia [A, B, A, C, B, A]. Eso evitará la estratificación por completo.
La forma en que lo hice es tratarlo como una especie de fusión, utilizando una cola de prioridad. Si creo una estructura para describir las adiciones:
MergeItem
Item, Count, Frequency, Priority
La frecuencia se expresa como "uno cada N". Entonces, A, que se agrega tres de seis veces, tiene una frecuencia de 2 (6/3).
E inicialice un montón que inicialmente contiene:
(A, 3, 2, 2)
(B, 2, 3, 3)
(C, 1, 6, 6)
Ahora, elimino el primer elemento del montón y lo envío. Luego reduzca su recuento en 1 y aumente la Prioridad por Frecuencia y agréguelo nuevamente al montón. El montón resultante es:
(B, 2, 3, 0)
(A, 2, 2, 4)
(C, 1, 6, 6)
A continuación, elimine B del montón, la salida y actualícela, luego agregue nuevamente al montón:
(A, 2, 2, 4)
(C, 1, 6, 6)
(B, 1, 3, 6)
Si continúo de esa manera, obtengo la mezcla deseada. Utilizo un comparador personalizado para garantizar que cuando se insertan elementos de prioridad iguales en el montón, se ordena primero el que tiene el valor de frecuencia más alto (es decir, el menos frecuente).
Escribí una descripción más completa del problema y su solución en mi blog, y presenté un código C # que lo ilustra. Consulte Distribución uniforme de elementos en una lista .
Actualización después de comentarios
Creo que mi problema es similar al del OP y, por lo tanto, mi solución es potencialmente útil. Pido disculpas por no enmarcar mi respuesta más en los términos de la pregunta del OP.
La primera objeción, que mi solución está usando A, B y C en lugar de 0, 1 y 2, se soluciona fácilmente. Es simplemente una cuestión de nomenclatura. Me resulta más fácil y menos confuso pensar y decir "dos A" en lugar de "dos 1". Pero para los propósitos de esta discusión, he modificado mis resultados a continuación para usar la nomenclatura del OP.
Por supuesto, mi problema trata con el concepto de distancia. Si desea "distribuir las cosas de manera uniforme", la distancia está implícita. Pero, nuevamente, fue mi fracaso por no mostrar adecuadamente cómo mi problema es similar al problema del OP.
Ejecuté algunas pruebas con los dos ejemplos que proporcionó el OP. Es decir:
[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]
En mi nomenclatura, esos se expresan como [2,2,2] y [4,3,2,1], respectivamente. Es decir, en el último ejemplo, "4 elementos del tipo 0, 3 elementos del tipo 1, 2 elementos del tipo 2 y 1 elemento del tipo 3".
Ejecuté mi programa de prueba (como se describe a continuación) y publiqué mis resultados. En ausencia del aporte del OP, no puedo decir si mis resultados son similares, peores o mejores que los suyos. Tampoco puedo comparar mis resultados con los resultados de nadie más porque nadie más ha publicado ninguno.
Sin embargo, puedo decir que el algoritmo proporciona una buena solución a mi problema de eliminar la estratificación al mezclar líquidos. Y parece que proporciona una solución razonable al problema del OP.
Para los resultados que se muestran a continuación, utilicé el algoritmo que detallé en mi entrada de blog, con la prioridad inicial establecida en Frequency/2
, y el comparador de montón modificado para favorecer el elemento más frecuente. El código modificado se muestra aquí, con las líneas modificadas comentadas.
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;
}
}
Al ejecutar mi programa de prueba con el primer ejemplo del OP, obtengo:
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
Entonces mi algoritmo funciona para el problema trivial de que todos los recuentos sean iguales.
Para el segundo problema que publicó el OP, obtuve:
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
No veo una forma obvia de mejorar eso. Podría reorganizarse para hacer las distancias para el ítem 0 [2,3,2,3] o algún otro arreglo de 2 y 3, pero eso cambiará las desviaciones para los ítems 1 y / o 2. Realmente no sé qué "óptimo" está en esta situación. ¿Es mejor tener una desviación mayor en los artículos más frecuentes o menos frecuentes?
Al carecer de otros problemas del OP, utilicé sus descripciones para inventar algunas propias. Él dijo en su publicación:
Una lista típica tiene ~ 50 artículos con ~ 15 valores diferentes en cantidades variadas.
Entonces mis dos pruebas fueron:
[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
Y mis resultados:
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
Y para el segundo ejemplo:
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