可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I have a list of integers in my C# program. However, I know the number of items I have in my list only at runtime.
Let us say, for the sake of simplicity, my list is {1, 2, 3}
Now I need to generate all possible combinations as follows.
{1, 2, 3}
{1, 2}
{1, 3}
{2, 3}
{1}
{2}
{3}
Can somebody please help with this?
回答1:
try this:
static void Main(string[] args)
{
GetCombination(new List<int> { 1, 2, 3 });
}
static void GetCombination(List<int> list)
{
double count = Math.Pow(2, list.Count);
for (int i = 1; i <= count - 1; i++)
{
string str = Convert.ToString(i, 2).PadLeft(list.Count, '0');
for (int j = 0; j < str.Length; j++)
{
if (str[j] == '1')
{
Console.Write(list[j]);
}
}
Console.WriteLine();
}
}
回答2:
Here are two generic solutions for strongly typed lists that will return all unique combinations of list members (if you can solve this with simpler code, I salute you):
// Recursive
public static List<List<T>> GetAllCombos<T>(List<T> list)
{
List<List<T>> result = new List<List<T>>();
// head
result.Add(new List<T>());
result.Last().Add(list[0]);
if (list.Count == 1)
return result;
// tail
List<List<T>> tailCombos = GetAllCombos(list.Skip(1).ToList());
tailCombos.ForEach(combo =>
{
result.Add(new List<T>(combo));
combo.Add(list[0]);
result.Add(new List<T>(combo));
});
return result;
}
// Iterative, using 'i' as bitmask to choose each combo members
public static List<List<T>> GetAllCombos<T>(List<T> list)
{
int comboCount = (int) Math.Pow(2, list.Count) - 1;
List<List<T>> result = new List<List<T>>();
for (int i = 1; i < comboCount + 1; i++)
{
// make each combo here
result.Add(new List<T>());
for (int j = 0; j < list.Count; j++)
{
if ((i >> j) % 2 != 0)
result.Last().Add(list[j]);
}
}
return result;
}
// Example usage
List<List<int>> combos = GetAllCombos(new int[] { 1, 2, 3 }.ToList());
回答3:
Here's a generic solution using recursion
public static ICollection<ICollection<T>> Permutations<T>(ICollection<T> list) {
var result = new List<ICollection<T>>();
if (list.Count == 1) { // If only one possible permutation
result.Add(list); // Add it and return it
return result;
}
foreach (var element in list) { // For each element in that list
var remainingList = new List<T>(list);
remainingList.Remove(element); // Get a list containing everything except of chosen element
foreach (var permutation in Permutations<T>(remainingList)) { // Get all possible sub-permutations
permutation.Add(element); // Add that element
result.Add(permutation);
}
}
return result;
}
I know this is an old post, but someone might find this helpful.
回答4:
This answer uses the same algorithm as ojlovecd and (for his iterative solution) jaolho. The only thing I'm adding is an option to filter the results for a minimum number of items in the combinations. This can be useful, for example, if you are only interested in the combinations that contain at least two items.
Edit: As requested by @user3610374 a filter for the maximum number of items has been added.
Edit 2: As suggested by @stannius the algorithm has been changed to make it more efficient for cases where not all combinations are wanted.
/// <summary>
/// Method to create lists containing possible combinations of an input list of items. This is
/// basically copied from code by user "jaolho" on this thread:
/// http://stackoverflow.com/questions/7802822/all-possible-combinations-of-a-list-of-values
/// </summary>
/// <typeparam name="T">type of the items on the input list</typeparam>
/// <param name="inputList">list of items</param>
/// <param name="minimumItems">minimum number of items wanted in the generated combinations,
/// if zero the empty combination is included,
/// default is one</param>
/// <param name="maximumItems">maximum number of items wanted in the generated combinations,
/// default is no maximum limit</param>
/// <returns>list of lists for possible combinations of the input items</returns>
public static List<List<T>> ItemCombinations<T>(List<T> inputList, int minimumItems = 1,
int maximumItems = int.MaxValue)
{
int nonEmptyCombinations = (int)Math.Pow(2, inputList.Count) - 1;
List<List<T>> listOfLists = new List<List<T>>(nonEmptyCombinations + 1);
// Optimize generation of empty combination, if empty combination is wanted
if (minimumItems == 0)
listOfLists.Add(new List<T>());
if (minimumItems <= 1 && maximumItems >= inputList.Count)
{
// Simple case, generate all possible non-empty combinations
for (int bitPattern = 1; bitPattern <= nonEmptyCombinations; bitPattern++)
listOfLists.Add(GenerateCombination(inputList, bitPattern));
}
else
{
// Not-so-simple case, avoid generating the unwanted combinations
for (int bitPattern = 1; bitPattern <= nonEmptyCombinations; bitPattern++)
{
int bitCount = CountBits(bitPattern);
if (bitCount >= minimumItems && bitCount <= maximumItems)
listOfLists.Add(GenerateCombination(inputList, bitPattern));
}
}
return listOfLists;
}
/// <summary>
/// Sub-method of ItemCombinations() method to generate a combination based on a bit pattern.
/// </summary>
private static List<T> GenerateCombination<T>(List<T> inputList, int bitPattern)
{
List<T> thisCombination = new List<T>(inputList.Count);
for (int j = 0; j < inputList.Count; j++)
{
if ((bitPattern >> j & 1) == 1)
thisCombination.Add(inputList[j]);
}
return thisCombination;
}
/// <summary>
/// Sub-method of ItemCombinations() method to count the bits in a bit pattern. Based on this:
/// https://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetKernighan
/// </summary>
private static int CountBits(int bitPattern)
{
int numberBits = 0;
while (bitPattern != 0)
{
numberBits++;
bitPattern &= bitPattern - 1;
}
return numberBits;
}
回答5:
Another solution using Linq and recursion...
static void Main(string[] args)
{
List<List<long>> result = new List<List<long>>();
List<long> set = new List<long>() { 1, 2, 3, 4 };
GetCombination<long>(set, result);
result.Add(set);
IOrderedEnumerable<List<long>> sorted = result.OrderByDescending(s => s.Count);
sorted.ToList().ForEach(l => { l.ForEach(l1 => Console.Write(l1 + " ")); Console.WriteLine(); });
}
private static void GetCombination<T>(List<T> set, List<List<T>> result)
{
for (int i = 0; i < set.Count; i++)
{
List<T> temp = new List<T>(set.Where((s, index) => index != i));
if (temp.Count > 0 && !result.Where(l => l.Count == temp.Count).Any(l => l.SequenceEqual(temp)))
{
result.Add(temp);
GetCombination<T>(temp, result);
}
}
}
回答6:
Firstly, given a set of n elements, you compute all combinations of k elements out of it (nCk). You have to change the value of k from 1 to n to meet your requirement.
See this codeproject article for C# code for generating combinations.
In case, you are interested in developing the combination algorithm by yourself, check this SO question where there are a lot of links to the relevant material.
回答7:
protected List<List<T>> AllCombos<T>(Func<List<T>, List<T>, bool> comparer, params T[] items)
{
List<List<T>> results = new List<List<T>>();
List<T> workingWith = items.ToList();
results.Add(workingWith);
items.ToList().ForEach((x) =>
{
results.Add(new List<T>() { x });
});
for (int i = 0; i < workingWith.Count(); i++)
{
T removed = workingWith[i];
workingWith.RemoveAt(i);
List<List<T>> nextResults = AllCombos2(comparer, workingWith.ToArray());
results.AddRange(nextResults);
workingWith.Insert(i, removed);
}
results = results.Where(x => x.Count > 0).ToList();
for (int i = 0; i < results.Count; i++)
{
List<T> list = results[i];
if (results.Where(x => comparer(x, list)).Count() > 1)
{
results.RemoveAt(i);
}
}
return results;
}
protected List<List<T>> AllCombos2<T>(Func<List<T>, List<T>, bool> comparer, params T[] items)
{
List<List<T>> results = new List<List<T>>();
List<T> workingWith = items.ToList();
if (workingWith.Count > 1)
{
results.Add(workingWith);
}
for (int i = 0; i < workingWith.Count(); i++)
{
T removed = workingWith[i];
workingWith.RemoveAt(i);
List<List<T>> nextResults = AllCombos2(comparer, workingWith.ToArray());
results.AddRange(nextResults);
workingWith.Insert(i, removed);
}
results = results.Where(x => x.Count > 0).ToList();
for (int i = 0; i < results.Count; i++)
{
List<T> list = results[i];
if (results.Where(x => comparer(x, list)).Count() > 1)
{
results.RemoveAt(i);
}
}
return results;
}
This worked for me, it's slightly more complex and actually takes a comparer callback function, and it's actually 2 functions, the difference being that the AllCombos adds the single item lists explicitly. It is very raw and can definitely be trimmed down but it gets the job done. Any refactoring suggestions are welcome. Thanks,
回答8:
public class CombinationGenerator{
private readonly Dictionary<int, int> currentIndexesWithLevels = new Dictionary<int, int>();
private readonly LinkedList<List<int>> _combinationsList = new LinkedList<List<int>>();
private readonly int _combinationLength;
public CombinationGenerator(int combinationLength)
{
_combinationLength = combinationLength;
}
private void InitializeLevelIndexes(List<int> list)
{
for (int i = 0; i < _combinationLength; i++)
{
currentIndexesWithLevels.Add(i+1, i);
}
}
private void UpdateCurrentIndexesForLevels(int level)
{
int index;
if (level == 1)
{
index = currentIndexesWithLevels[level];
for (int i = level; i < _combinationLength + 1; i++)
{
index = index + 1;
currentIndexesWithLevels[i] = index;
}
}
else
{
int previousLevelIndex;
for (int i = level; i < _combinationLength + 1; i++)
{
if (i > level)
{
previousLevelIndex = currentIndexesWithLevels[i - 1];
currentIndexesWithLevels[i] = previousLevelIndex + 1;
}
else
{
index = currentIndexesWithLevels[level];
currentIndexesWithLevels[i] = index + 1;
}
}
}
}
public void FindCombinations(List<int> list, int level, Stack<int> stack)
{
int currentIndex;
InitializeLevelIndexes(list);
while (true)
{
currentIndex = currentIndexesWithLevels[level];
bool levelUp = false;
for (int i = currentIndex; i < list.Count; i++)
{
if (level < _combinationLength)
{
currentIndex = currentIndexesWithLevels[level];
MoveToUpperLevel(ref level, stack, list, currentIndex);
levelUp = true;
break;
}
levelUp = false;
stack.Push(list[i]);
if (stack.Count == _combinationLength)
{
AddCombination(stack);
stack.Pop();
}
}
if (!levelUp)
{
MoveToLowerLevel(ref level, stack, list, ref currentIndex);
while (currentIndex >= list.Count - 1)
{
if (level == 1)
{
AdjustStackCountToCurrentLevel(stack, level);
currentIndex = currentIndexesWithLevels[level];
if (currentIndex >= list.Count - 1)
{
return;
}
UpdateCurrentIndexesForLevels(level);
}
else
{
MoveToLowerLevel(ref level, stack, list, ref currentIndex);
}
}
}
}
}
private void AddCombination(Stack<int> stack)
{
List<int> listNew = new List<int>();
listNew.AddRange(stack);
_combinationsList.AddLast(listNew);
}
private void MoveToUpperLevel(ref int level, Stack<int> stack, List<int> list, int index)
{
stack.Push(list[index]);
level++;
}
private void MoveToLowerLevel(ref int level, Stack<int> stack, List<int> list, ref int currentIndex)
{
if (level != 1)
{
level--;
}
AdjustStackCountToCurrentLevel(stack, level);
UpdateCurrentIndexesForLevels(level);
currentIndex = currentIndexesWithLevels[level];
}
private void AdjustStackCountToCurrentLevel(Stack<int> stack, int currentLevel)
{
while (stack.Count >= currentLevel)
{
if (stack.Count != 0)
stack.Pop();
}
}
public void PrintPermutations()
{
int count = _combinationsList.Where(perm => perm.Count() == _combinationLength).Count();
Console.WriteLine("The number of combinations is " + count);
}
}
回答9:
We can use recursion for combination/permutation problems involving string or integers.
public static void Main(string[] args)
{
IntegerList = new List<int> { 1, 2, 3, 4 };
PrintAllCombination(default(int), default(int));
}
public static List<int> IntegerList { get; set; }
public static int Length { get { return IntegerList.Count; } }
public static void PrintAllCombination(int position, int prefix)
{
for (int i = position; i < Length; i++)
{
Console.WriteLine(prefix * 10 + IntegerList[i]);
PrintAllCombination(i + 1, prefix * 10 + IntegerList[i]);
}
}
回答10:
What about
static void Main(string[] args)
{
Combos(new [] { 1, 2, 3 });
}
static void Combos(int[] arr)
{
for (var i = 0; i <= Math.Pow(2, arr.Length); i++)
{
Console.WriteLine();
var j = i;
var idx = 0;
do
{
if ((j & 1) == 1) Console.Write($"{arr[idx]} ");
} while ((j >>= 1) > 0 && ++idx < arr.Length);
}
}
回答11:
A slightly more generalised version for Linq using C# 7. Here filtering by items that have two elements.
static void Main(string[] args)
{
foreach (var vals in Combos(new[] { "0", "1", "2", "3" }).Where(v => v.Skip(1).Any() && !v.Skip(2).Any()))
Console.WriteLine(string.Join(", ", vals));
}
static IEnumerable<IEnumerable<T>> Combos<T>(T[] arr)
{
IEnumerable<T> DoQuery(long j, long idx)
{
do
{
if ((j & 1) == 1) yield return arr[idx];
} while ((j >>= 1) > 0 && ++idx < arr.Length);
}
for (var i = 0; i < Math.Pow(2, arr.Length); i++)
yield return DoQuery(i, 0);
}