【问题标题】:All Possible Combinations of a list of Values值列表的所有可能组合
【发布时间】:2011-12-09 19:26:40
【问题描述】:

我的 C# 程序中有一个整数列表 List<int>。但是,我只在运行时才知道列表中的项目数。

让我们说,为了简单起见,我的列表是{1, 2, 3} 现在我需要生成所有可能的组合,如下所示。

{1, 2, 3}
{1, 2}
{1, 3}
{2, 3}
{1}
{2}
{3}

有人可以帮忙吗?

【问题讨论】:

标签: c# algorithm math combinations


【解决方案1】:

试试这个:

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();
    }
}

【讨论】:

  • +1 您需要自己尝试解决这个问题,才能真正体会到这个解决方案的天才之处。 @ojlovecd,干得好!
  • 这将是指数级的慢!随着 N(整数的数量)变大。 OP 正在寻找一个 IEnumerable 的幂集,它有 1
  • 为什么要将数字转换为二进制字符串?这似乎非常不理想。该数字已经以二进制形式存储,您可以简单地使用位掩码。
  • 这是一个非常简单的解决方案,可以解决古老的组合/排列问题。 1)我发现它使用了大多数中级程序员用来描述它的术语,以及 2)它解决了更广泛的问题(唯一排列生成)并且问题中提出的具体问题使这成为一个很棒的答案!做得很好。
  • 这是正确的算法,但执行起来很糟糕。使用 Pow 并将 int 转换为字符串来检查一下是错误的,只能由初学者完成。我不知道我是否应该编辑这个答案,因为它会大大改变它。
【解决方案2】:

假设初始集合中的所有项目都是distinct,我们可以尝试使用Linq进行查询;让我们概括一下解决方案:

代码:

public static IEnumerable<T[]> Combinations<T>(IEnumerable<T> source) {
  if (null == source)
    throw new ArgumentNullException(nameof(source));

  T[] data = source.ToArray();

  return Enumerable
    .Range(0, 1 << (data.Length))
    .Select(index => data
       .Where((v, i) => (index & (1 << i)) != 0)
       .ToArray());
}

演示:

  var data = new char[] { 'A', 'B', 'C' };

  var result = Combinations(data);

  foreach (var item in result)
    Console.WriteLine($"[{string.Join(", ", item)}]");

结果:

[]
[A]
[B]
[A, B]
[C]
[A, C]
[B, C]
[A, B, C]

如果你想排除初始的空数组,用.Range(1, (1 &lt;&lt; (data.Length)) - 1)代替.Range(0, 1 &lt;&lt; (data.Length))

【讨论】:

  • 这可能是 OP 想要的,但这些值不是排列!
  • 这无疑是解决 Sach 所提问题的最简单、最优雅的解决方案。你一定喜欢 Linq!
  • 虽然这是组合,而不是排列,但它正是我想要的。如此优雅、简单且强大的方法!我很遗憾我只有 1 票可以投票。
【解决方案3】:

以下是强类型列表的两个通用解决方案,它们将返回列表成员的所有唯一组合(如果你能用更简单的代码解决这个问题,我向你致敬):

// 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());

【讨论】:

  • “如果你能用更简单的代码解决这个问题,我向你致敬”。好的,我在下面的答案中将“i
  • 请将 Pow 替换为位移,然后这个答案比接受的答案好得多。
  • 我更喜欢递归方法。方法参数List&lt;T&gt; list 可以替换为Queue&lt;T&gt;。只需将开头的第一项出列,将所有list[0]s 替换为该项,然后在递归调用中传递相同的队列。
  • 同意欣赏递归函数。在a Yield helper function 的帮助下,建议对递归方法进行以下改进: private static IEnumerable> GetAllCombos(this IEnumerable list) { if (list.Count() == 1) { 收益返回列表; } else { var first = list.First();先收益回报。收益(); foreach (var combo in GetAllCombos(list.Skip(1))) { yield return combo; yield return combo.Prepend(first); } } }
【解决方案4】:

此答案使用与 ojlovecd 和(对于他的迭代解决方案)jaolho 相同的算法。我唯一要添加的是一个选项,用于过滤组合中最少数量的项目的结果。这可能很有用,例如,如果您只对包含至少两个项目的组合感兴趣。

编辑:根据@user3610374 的要求,添加了最大项目数的过滤器。

编辑 2:正如@stannius 所建议的,算法已被更改,以便在不需要所有组合的情况下更有效。

  /// <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;
  }

【讨论】:

  • 这很完美,但我建议也包括 maxItems
  • 您不希望 Max value 的默认值是输入列表计数吗?
  • @user3610374 嗯,我想是这样,但使用 int.MaxValue 提供相同的结果,并且可以编码为默认参数值。
  • @stannius 感谢您的建议,我已经编辑了我的答案。
  • @RenniePet 很酷。我会赞成您更新的答案,但昨天我使用您的代码解决我的问题时已经这样做了。
【解决方案5】:

这是一个使用递归的通用解决方案

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;
}

我知道这是一篇旧帖子,但有人可能会觉得这很有帮助。

【讨论】:

  • 这不符合 OP 的要求。它只返回 6 个项目,每个项目的长度为 3。dotnetfiddle.net/A2lNPb
  • 我喜欢这个。非常优雅的解决方案。
  • 这是 OP 要求组合时的排列解决方案
【解决方案6】:

使用 Linq 和递归的另一种解决方案...

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);
            }
        }
    }

【讨论】:

    【解决方案7】:

    这是对@ojlovecd 答案的改进,不使用字符串。

        static void Main(string[] args)
        {
            GetCombination(new List<int> { 1, 2, 3 });
        }
    
    
        private static void GetCombination(List<int> list)
        {
            double count = Math.Pow(2, list.Count);
            for (int i = 1; i <= count - 1; i++)
            {
                for (int j = 0; j < list.Count; j++)
                {
                    int b = i & (1 << j);
                    if (b > 0)
                    {
                        Console.Write(list[j]);
                    }
                }
                Console.WriteLine();
            }
        }
    

    【讨论】:

    • 我建议 1. 对最多 63 个项目列表使用 unsigned long 2. 使用 1 &lt;&lt; list.Count 而不是 Math.Pow 留在整数域中。 3.将内部for更改为for (unsigned long j = 1; j &lt;= count; j &lt;&lt;= 1)int b = i &amp; j;并测试if (b != 0) 4.将外部for更改为for (int i = 1; i &lt; count; ++i)
    【解决方案8】:

    首先,给定一组 n 个元素,计算其中 k 个元素的所有组合 (nCk)。您必须将 k 的值从 1 更改为 n 以满足您的要求。

    请参阅此codeproject article 以获取用于生成组合的 C# 代码。

    如果您有兴趣自己开发组合算法,请查看SO question,那里有很多相关资料的链接。

    【讨论】:

      【解决方案9】:
      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;
          }
      

      这对我有用,它稍微复杂一些,实际上需要一个比较器回调函数,它实际上是 2 个函数,不同之处在于 AllCombos 显式添加了单个项目列表。它非常原始,绝对可以修剪,但它可以完成工作。欢迎任何重构建议。谢谢,

      【讨论】:

        【解决方案10】:
        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);
            }
        
        }
        

        【讨论】:

        • 寻找组合的迭代解决方案
        【解决方案11】:

        我们可以将递归用于涉及字符串或整数的组合/排列问题。

        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]);
            }
        
        }
        

        【讨论】:

          【解决方案12】:

          怎么样

          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);
              }
          }
          

          【讨论】:

            【解决方案13】:

            使用 C# 7 的 Linq 稍微更通用的版本。这里按具有两个元素的项目进行过滤。

            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);
            }
            

            【讨论】:

              【解决方案14】:

              请找到非常非常简单的解决方案,无需递归且不占用 RAM。

              Unique Combinations

              【讨论】:

              • 请在链接周围添加一些内容,而不是在这里复制/粘贴。
              【解决方案15】:

              这就是我的做法。

              public static List<List<int>> GetCombination(List<int> lst, int index, int count)
              {
                  List<List<int>> combinations = new List<List<int>>();
                  List<int> comb;
                  if (count == 0 || index == lst.Count)
                  {
                      return null;
                  }
                  for (int i = index; i < lst.Count; i++)
                  {
                      comb = new List<int>();
                      comb.Add(lst.ElementAt(i));
                      combinations.Add(comb);
                      var rest = GetCombination(lst,i + 1, count - 1);
                      if (rest != null)
                      {
                          foreach (var item in rest)
                          {
                              combinations.Add(comb.Union(item).ToList());
                          }
                      }
                  }
                  return combinations;
              }
              

              你称它为:

              List<int> lst= new List<int>(new int[]{ 1, 2, 3, 4 });
              var combinations = GetCombination(lst, 0, lst.Length)
              

              【讨论】:

                【解决方案16】:

                我只是遇到了需要这样做的情况,这就是我想出的:

                private static List<string> GetCombinations(List<string> elements)
                {
                    List<string> combinations = new List<string>();
                    combinations.AddRange(elements);
                    for (int i = 0; i < elements.Count - 1; i++)
                    {
                        combinations = (from combination in combinations
                                        join element in elements on 1 equals 1
                                        let value = string.Join(string.Empty, $"{combination}{element}".OrderBy(c => c).Distinct())
                                        select value).Distinct().ToList();
                    }
                
                    return combinations;
                }
                

                它可能效率不高,当然还有改进的余地,但可以完成工作!

                List<string> elements = new List<string> { "1", "2", "3" };
                List<string> combinations = GetCombinations(elements);
                
                foreach (string combination in combinations)
                {
                    System.Console.Write(combination);
                }
                

                【讨论】:

                  猜你喜欢
                  • 2022-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2023-04-02
                  • 1970-01-01
                  相关资源
                  最近更新 更多