我有一个等待分配帐户的系统用户列表。
分配算法非常简单,分配应该尽可能公平,这意味着如果我有40个帐户和20个系统用户,我需要为每个系统用户分配2个帐户。
如果我有41个帐户和20个系统用户,我需要为每个系统用户分配2个帐户,并再次在系统用户之间拆分剩余帐户(在这种情况下,将为一个系统用户分配一个额外帐户)。
我试图在使用LINQ查询时弄清楚如何做到这一点
到目前为止,我认为应该涉及分组,我的查询如下:
from account in accounts
let accountsPerSystemUser = accounts.Count / systemUsers.Count
let leftover = accounts.Count % systemUsers.Count
from systemUser in systemUsers
group account by systemUser into accountsGroup
select accountsGroup
但我不确定如何从这里开始。
我很肯定我在这里错过了一个where子句,如果你达到了分配给系统用户的最大帐户数量,它将阻止分组。
如何正确实现查询,以便分组知道分配多少?
答案 0 :(得分:2)
这是一个简单的实现,如果您可以将自己限制为IList<T>
accounts
(您可以随时使用ToList
)。
public static IEnumerable<IGrouping<TBucket, TSource>> DistributeBy<TSource, TBucket>(
this IEnumerable<TSource> source, IList<TBucket> buckets)
{
var tagged = source.Select((item,i) => new {item, tag = i % buckets.Count});
var grouped = from t in tagged
group t.item by buckets[t.tag];
return grouped;
}
// ...
var accountsGrouped = accounts.DistributeBy(systemUsers);
基本上,这会抓取每个帐户的索引和“标记”,每个帐户的整数除法的余数除以系统用户的数量。这些标签是他们将属于的系统用户的索引。然后它只是由系统用户在该索引处对它们进行分组。
这可以确保您的公平性要求,因为余数将在0到1之间循环减去系统用户数。
0 % 20 = 0
1 % 20 = 1
2 % 20 = 2
...
19 % 20 = 19
20 % 20 = 0
21 % 21 = 1
22 % 22 = 2
...
39 % 20 = 19
40 % 20 = 0
答案 1 :(得分:1)
你不能使用“纯LINQ”(即使用查询理解语法)来做到这一点,说实话,LINQ可能不是最好的方法。尽管如此,这是一个如何实现它的例子:
var listB = new List<string>() { "a", "b", "c", "d", "e" };
var listA = new List<string>() { "1", "2", "3" };
var groupings = (from b in listB.Select((b, i) => new
{
Index = i,
Element = b
})
group b.Element by b.Index % listA.Count).Zip(listA, (bs, a) => new
{
A = a,
Bs = bs
});
foreach (var item in groupings)
{
Console.WriteLine("{0}: {1}", item.A, string.Join(",", item.Bs));
}
输出:
1: a,d
2: b,e
3: c
答案 2 :(得分:1)
我不瘦“纯”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;
}
}