Eu me deparei com essa pergunta enquanto pesquisava um problema semelhante: adições ótimas de líquidos para reduzir a estratificação. Parece que minha solução também seria aplicável à sua situação.
Se você deseja misturar os líquidos A, B e C na proporção 30,20,10 (ou seja, 30 unidades de A, 20 unidades de B e 10 unidades de C), você terminará com estratificação se adicionar todos os o A, depois todo o B e depois todo o C. É melhor misturar unidades menores. Por exemplo, faça adições de unidade única na sequência [A, B, A, C, B, A]. Isso impedirá completamente a estratificação.
A maneira que encontrei para fazer isso é tratá-lo como uma espécie de mesclagem, usando uma fila de prioridade. Se eu criar uma estrutura para descrever as adições:
MergeItem
Item, Count, Frequency, Priority
A frequência é expressa como "um a cada N". Então A, que é adicionado três em seis vezes, tem uma frequência de 2 (6/3).
E inicialize um heap que contém inicialmente:
(A, 3, 2, 2)
(B, 2, 3, 3)
(C, 1, 6, 6)
Agora, removo o primeiro item da pilha e o produzo. Em seguida, reduza sua contagem em 1 e aumente a Prioridade por frequência e adicione-a novamente ao heap. O heap resultante é:
(B, 2, 3, 0)
(A, 2, 2, 4)
(C, 1, 6, 6)
Em seguida, remova B do heap, produza e atualize-o e adicione-o novamente ao heap:
(A, 2, 2, 4)
(C, 1, 6, 6)
(B, 1, 3, 6)
Se eu continuar dessa maneira, recebo a mistura desejada. Uso um comparador personalizado para garantir que, quando itens de prioridade igual forem inseridos no heap, aquele com o maior valor de frequência (ou seja, o menos frequente) seja solicitado primeiro.
Eu escrevi uma descrição mais completa do problema e sua solução no meu blog e apresentei alguns códigos C # funcionais que ilustram isso. Consulte Distribuição uniforme de itens em uma lista .
Atualizar após comentários
Acho que meu problema é semelhante ao do OP e, portanto, que minha solução é potencialmente útil. Peço desculpas por não enquadrar minha resposta mais nos termos da pergunta do OP.
A primeira objeção, de que minha solução está usando A, B e C, em vez de 0, 1 e 2, é facilmente remediada. É simplesmente uma questão de nomenclatura. Acho mais fácil e menos confuso pensar e dizer "dois A's" em vez de "dois 1's". Mas, para os propósitos desta discussão, modifiquei minhas saídas abaixo para usar a nomenclatura do OP.
É claro que meu problema lida com o conceito de distância. Se você deseja "espalhar as coisas uniformemente", a distância está implícita. Mas, novamente, foi minha falha por não mostrar adequadamente como meu problema é semelhante ao problema do OP.
Fiz alguns testes com os dois exemplos que o OP forneceu. Isso é:
[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]
Na minha nomenclatura, esses são expressos como [2,2,2] e [4,3,2,1], respectivamente. Ou seja, no último exemplo, "4 itens do tipo 0, 3 itens do tipo 1, 2 itens do tipo 2 e 1 item do tipo 3."
Eu executei meu programa de teste (como descrito imediatamente abaixo) e publiquei meus resultados. Ausência de informações do OP, não sei dizer se meus resultados são semelhantes a, piores ou melhores que os dele. Nem posso comparar meus resultados com os resultados de mais ninguém porque ninguém mais postou nenhum.
Posso dizer, no entanto, que o algoritmo fornece uma boa solução para o meu problema de eliminar a estratificação ao misturar líquidos. E parece que fornece uma solução razoável para o problema do OP.
Para os resultados mostrados abaixo, usei o algoritmo que detalhei na entrada do meu blog, com a prioridade inicial definida como Frequency/2
e o comparador de heap modificado para favorecer o item mais frequente. O código modificado é mostrado aqui, com as linhas 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;
}
}
Executando meu programa de teste com o primeiro exemplo do OP, recebo:
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
Portanto, meu algoritmo funciona para o problema trivial de todas as contagens serem iguais.
Para o segundo problema que o OP postou, obtive:
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
Não vejo uma maneira óbvia de melhorar isso. Poderia ser reorganizado para fazer as distâncias para o item 0 [2,3,2,3] ou algum outro arranjo de 2 e 3, mas isso mudará os desvios para os itens 1 e / ou 2. Eu realmente não sei o que "ótimo" está nessa situação. É melhor ter um desvio maior nos itens mais frequentes ou nos menos frequentes?
Na falta de outros problemas do OP, usei suas descrições para fazer algumas das minhas. Ele disse em seu post:
Uma lista típica possui ~ 50 itens com ~ 15 valores diferentes em quantidades variadas.
Então, meus dois testes foram:
[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 meus 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
E para o segundo exemplo:
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