Я столкнулся с этим вопросом, исследуя аналогичную проблему: оптимальное добавление жидкостей для уменьшения расслоения. Похоже, мое решение будет применимо и к вашей ситуации.
Если вы хотите смешать жидкости A, B и C в пропорции 30,20,10 (то есть 30 единиц A, 20 единиц B и 10 единиц C), вы получите стратификацию, если добавите все A, затем все B, а затем все C. Вам лучше смешивать меньшие единицы. Например, делайте единичные добавления в последовательности [A, B, A, C, B, A]. Это полностью предотвратит расслоение.
Я нашел способ сделать это, рассматривая это как некое слияние, используя очередь с приоритетами. Если я создаю структуру для описания дополнений:
MergeItem
Item, Count, Frequency, Priority
Частота выражается как «один на каждый N». Таким образом, А, который добавляется три раза из шести, имеет частоту 2 (6/3).
И инициализировать кучу, которая изначально содержит:
(A, 3, 2, 2)
(B, 2, 3, 3)
(C, 1, 6, 6)
Теперь я удаляю первый элемент из кучи и выводю его. Затем уменьшите его количество на 1, увеличьте приоритет по частоте и добавьте его обратно в кучу. В результате получается куча:
(B, 2, 3, 0)
(A, 2, 2, 4)
(C, 1, 6, 6)
Затем удалите B из кучи, выведите и обновите ее, затем добавьте обратно в кучу:
(A, 2, 2, 4)
(C, 1, 6, 6)
(B, 1, 3, 6)
Если я продолжу в том же духе, я получу желаемую смесь. Я использую пользовательский компаратор, чтобы гарантировать, что при вставке в кучу равных элементов Приоритета в первую очередь упорядочивается элемент с наибольшим значением частоты (т. Е. С наименьшей частотой).
Я написал более полное описание проблемы и ее решения в своем блоге и представил некоторый работающий код C #, который иллюстрирует ее. См. Равномерное распределение предметов в списке .
Обновление после комментариев
Я думаю, что моя проблема похожа на проблему ОП, и поэтому мое решение потенциально полезно. Я прошу прощения за то, что не сформулировал мой ответ больше в терминах вопроса ОП.
Первое возражение, что мое решение использует A, B и C, а не 0, 1 и 2, легко исправить. Это просто вопрос номенклатуры. Мне легче и менее запутанно думать и говорить «два А», а не «два 1». Но для целей этого обсуждения я изменил свои выводы ниже, чтобы использовать номенклатуру ОП.
Конечно, моя проблема связана с понятием расстояния. Если вы хотите «распределить вещи равномерно», подразумевается расстояние. Но, опять же, это был мой провал из-за того, что я не смог адекватно показать, насколько моя проблема похожа на проблему ОП.
Я провел несколько тестов с двумя примерами, которые предоставил ОП. То есть:
[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]
В моей номенклатуре они выражены как [2,2,2] и [4,3,2,1] соответственно. То есть в последнем примере «4 элемента типа 0, 3 элемента типа 1, 2 элемента типа 2 и 1 элемент типа 3».
Я запустил свою тестовую программу (как описано ниже) и опубликовал свои результаты. При отсутствии данных от ОП я не могу сказать, похожи ли мои результаты, хуже или лучше его. Также я не могу сравнить свои результаты с результатами кого-либо еще, потому что никто другой не опубликовал их.
Однако я могу сказать, что алгоритм обеспечивает хорошее решение моей проблемы устранения стратификации при смешивании жидкостей. И, похоже, это дает разумное решение проблемы ОП.
Для результатов, показанных ниже, я использовал алгоритм, который я подробно описал в своей записи в блоге, с начальным приоритетом, установленным на Frequency/2
, и модификатором кучи, измененным для более частого элемента. Здесь показан измененный код с комментариями к измененным строкам.
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;
}
}
Запустив мою тестовую программу с первым примером OP, я получаю:
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
Так что мой алгоритм работает для тривиальной задачи, при которой все числа равны.
За вторую проблему, которую опубликовал ОП, я получил:
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
Я не вижу очевидного способа улучшить это. Его можно переставить, чтобы сделать расстояния для элемента 0 [2,3,2,3] или некоторого другого расположения 2 и 3, но это изменит отклонения для элементов 1 и / или 2. Я действительно не знаю, что «оптимальный» в этой ситуации. Лучше иметь большее отклонение по более частым или менее частым предметам?
Не имея других проблем в OP, я использовал его описания, чтобы составить несколько своих собственных. Он сказал в своем посте:
Типичный список содержит ~ 50 наименований с ~ 15 различными значениями в разных количествах.
Итак, мои два теста были:
[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
И мои результаты:
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
И для второго примера:
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