在我的回答中,我将忽略您的最后一个结果: Adam, Bob: 1, 2, 3
,因为它在逻辑上是一个例外。跳到最后看看我的输出。
解释:
这个想法将是迭代“元素将属于哪个组”的组合。
假设您有元素“a,b,c”,并且您有组“1, 2”,让我们有一个大小为 3 的数组作为元素的数量,它将包含组“1, 2”的所有可能组合, 重复:
{1, 1, 1} {1, 1, 2} {1, 2, 1} {1, 2, 2}
{2, 1, 1} {2, 1, 2} {2, 2, 1} {2, 2, 2}
现在我们将取出每个组,并使用以下逻辑从中制作一个键值集合:
Group ofelements[i]
将是 的值comb[i]
。
Example with {1, 2, 1}:
a: group 1
b: group 2
c: group 1
And in a different view, the way you wanted it:
group 1: a, c
group 2: b
毕竟,您只需过滤所有不包含所有组的组合,因为您希望所有组至少具有一个值。
因此,您应该检查所有组是否以某种组合出现并过滤不匹配的组,因此您将得到:
{1, 1, 2} {1, 2, 1} {1, 2, 2}
{2, 1, 2} {2, 2, 1} {2, 1, 1}
这将导致:
1: a, b
2: c
1: a, c
2: b
1: a
2: b, c
1: b
2: a, c
1: c
2: a, b
1: b, c
2: a
这种破组的组合逻辑也适用于更多的元素和组。这是我的实现,它可能可以做得更好,因为即使我在编码时也有点迷失(这真的不是一个直观的问题,呵呵),但它工作得很好。
执行:
public static IEnumerable<ILookup<TValue, TKey>> Group<TKey, TValue>
(List<TValue> keys,
List<TKey> values,
bool allowEmptyGroups = false)
{
var indices = new int[values.Count];
var maxIndex = values.Count - 1;
var nextIndex = maxIndex;
indices[maxIndex] = -1;
while (nextIndex >= 0)
{
indices[nextIndex]++;
if (indices[nextIndex] == keys.Count)
{
indices[nextIndex] = 0;
nextIndex--;
continue;
}
nextIndex = maxIndex;
if (!allowEmptyGroups && indices.Distinct().Count() != keys.Count)
{
continue;
}
yield return indices.Select((keyIndex, valueIndex) =>
new
{
Key = keys[keyIndex],
Value = values[valueIndex]
})
.OrderBy(x => x.Key)
.ToLookup(x => x.Key, x => x.Value);
}
}
用法:
var employees = new List<string>() { "Adam", "Bob"};
var jobs = new List<string>() { "1", "2", "3"};
var c = 0;
foreach (var group in CombinationsEx.Group(employees, jobs))
{
foreach (var sub in group)
{
Console.WriteLine(sub.Key + ": " + string.Join(", ", sub));
}
c++;
Console.WriteLine();
}
Console.WriteLine(c + " combinations.");
输出:
Adam: 1, 2
Bob: 3
Adam: 1, 3
Bob: 2
Adam: 1
Bob: 2, 3
Adam: 2, 3
Bob: 1
Adam: 2
Bob: 1, 3
Adam: 3
Bob: 1, 2
6 combinations.
更新
组合键组合原型:
public static IEnumerable<ILookup<TKey[], TValue>> GroupCombined<TKey, TValue>
(List<TKey> keys,
List<TValue> values)
{
// foreach (int i in Enumerable.Range(1, keys.Count))
for (var i = 1; i <= keys.Count; i++)
{
foreach (var lookup in Group(Enumerable.Range(0, i).ToList(), keys))
{
foreach (var lookup1 in
Group(lookup.Select(keysComb => keysComb.ToArray()).ToList(),
values))
{
yield return lookup1;
}
}
}
/*
Same functionality:
return from i in Enumerable.Range(1, keys.Count)
from lookup in Group(Enumerable.Range(0, i).ToList(), keys)
from lookup1 in Group(lookup.Select(keysComb =>
keysComb.ToArray()).ToList(),
values)
select lookup1;
*/
}
仍然存在重复的小问题,但它会产生所有结果。
这是我用来删除重复项的方法,作为临时解决方案:
var c = 0;
var d = 0;
var employees = new List<string> { "Adam", "Bob", "James" };
var jobs = new List<string> {"1", "2"};
var prevStrs = new List<string>();
foreach (var group in CombinationsEx.GroupCombined(employees, jobs))
{
var currStr = string.Join(Environment.NewLine,
group.Select(sub =>
string.Format("{0}: {1}",
string.Join(", ", sub.Key),
string.Join(", ", sub))));
if (prevStrs.Contains(currStr))
{
d++;
continue;
}
prevStrs.Add(currStr);
Console.WriteLine(currStr);
Console.WriteLine();
c++;
}
Console.WriteLine(c + " combinations.");
Console.WriteLine(d + " duplicates.");
输出:
Adam, Bob, James: 1, 2
Adam, Bob: 1
James: 2
James: 1
Adam, Bob: 2
Adam, James: 1
Bob: 2
Bob: 1
Adam, James: 2
Adam: 1
Bob, James: 2
Bob, James: 1
Adam: 2
7 combinations.
6 duplicates.
请注意,它还将生成非组合组(如果可能 - 因为不允许空组)。要仅生成组合键,您需要替换它:
for (var i = 1; i <= keys.Count; i++)
有了这个:
for (var i = 1; i < keys.Count; i++)
在 GroupCombined 方法的开头。用三名员工和三份工作测试该方法,看看它是如何工作的。
另一个编辑:
更好的重复处理将在 GroupCombined 级别处理重复的组合键:
public static IEnumerable<ILookup<TKey[], TValue>> GroupCombined<TKey, TValue>
(List<TKey> keys,
List<TValue> values)
{
for (var i = 1; i <= keys.Count; i++)
{
var prevs = new List<TKey[][]>();
foreach (var lookup in Group(Enumerable.Range(0, i).ToList(), keys))
{
var found = false;
var curr = lookup.Select(sub => sub.OrderBy(k => k).ToArray())
.OrderBy(arr => arr.FirstOrDefault()).ToArray();
foreach (var prev in prevs.Where(prev => prev.Length == curr.Length))
{
var isDuplicate = true;
for (var x = 0; x < prev.Length; x++)
{
var currSub = curr[x];
var prevSub = prev[x];
if (currSub.Length != prevSub.Length ||
!currSub.SequenceEqual(prevSub))
{
isDuplicate = false;
break;
}
}
if (isDuplicate)
{
found = true;
break;
}
}
if (found)
{
continue;
}
prevs.Add(curr);
foreach (var group in
Group(lookup.Select(keysComb => keysComb.ToArray()).ToList(),
values))
{
yield return group;
}
}
}
}
看起来,给方法添加约束是很聪明的,这样也可能TKey
是.ICompareable<TKey>
IEquatable<TKey>
这最终导致 0 个重复。