Я не тонкий "чистый" LINQ действительно подходит для решения этой проблемы. Тем не менее, вот решение, которое требует только два IEnumerable
:
var users = new[] { "A", "B", "C" };
var accounts = new[] { 1, 2, 3, 4, 5, 6, 7, 8 };
var accountsPerUser = accounts.Count()/users.Count();
var leftover = accounts.Count()%users.Count();
var assignments = users
.Select((u, i) => new {
User = u,
AccountsToAssign = accountsPerUser + (i < leftover ? 1 : 0),
AccountsAlreadyAssigned =
(accountsPerUser + 1)*(i < leftover ? i : leftover)
+ accountsPerUser*(i < leftover ? 0 : i - leftover)
})
.Select(x => new {
x.User,
Accounts = accounts
.Skip(x.AccountsAlreadyAssigned)
.Take(x.AccountsToAssign)
});
Чтобы сократить текст, я использую термин User
вместо SystemUser
.
Идея довольно проста. Первым leftover
пользователям назначается accountsPerUser + 1
из accounts
. Остальным пользователям назначается только accountsPerUser
.
Первый Select
использует перегрузку, которая предоставляет индекс для вычисления этих значений:
User | Index | AccountsAlreadyAssigned | AccountsToAssign
-----+-------+-------------------------+-----------------
A | 0 | 0 | 3
B | 1 | 3 | 3
C | 1 | 6 | 2
Второй Select
использует эти значения для Skip
и Take
правильных чисел от accounts
.
Если вы хотите, вы можете «объединить» два оператора Select
и заменить AccountsAlreadyAssigned
и AccountsToAssign
на выражения, используемые для их вычисления. Однако это усложнит понимание запроса.
Вот альтернатива "не LINQ". Он основан на IList
, но может быть легко преобразован в IEnumerable
. Или вместо того, чтобы возвращать назначения в виде кортежей, они могут выполнять назначения внутри цикла.
IEnumerable<Tuple<T, IList<U>>> AssignEvenly<T, U>(IList<T> targetItems, IList<U> sourceItems) {
var fraction = sourceItems.Count/targetItems.Count;
var remainder = sourceItems.Count%targetItems.Count;
var sourceIndex = 0;
for (var targetIndex = 0; targetIndex < targetItems.Count; ++targetIndex) {
var itemsToAssign = fraction + (targetIndex < remainder ? 1 : 0);
yield return Tuple.Create(
targetItems[targetIndex],
(IList<U>) sourceItems.Skip(sourceIndex).Take(itemsToAssign).ToList()
);
sourceIndex += itemsToAssign;
}
}