私の答えでは、あなたが持っていた最後の結果を無視します: 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}
次に、各グループを取得し、次のロジックを使用して、そこからキーと値のコレクションを作成します。
のグループはelements[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, 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 メソッドの先頭。3 人の従業員と 3 つのジョブでメソッドをテストし、正確にどのように機能するかを確認します。
別の編集:
より良い重複処理は、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になります。