【问题标题】:Get all subsets of a collection获取集合的所有子集
【发布时间】:2020-06-18 05:24:34
【问题描述】:

我正在尝试创建一个返回集合的所有子集的方法。

例如,如果我有集合 10,20,30,我希望得到以下输出

        return new List<List<int>>()
        {
            new List<int>(){10},
            new List<int>(){20},
            new List<int>(){30},
            new List<int>(){10,20},
            new List<int>(){10,30},
            new List<int>(){20,30},
            //new List<int>(){20,10}, that substet already exists
            // new List<int>(){30,20}, that subset already exists
            new List<int>(){10,20,30}
        };

因为集合也可以是字符串的集合,例如我想创建一个泛型方法。这是我已经解决的based on this solution

    static void Main(string[] args)
    {
        Foo<int>(new int[] { 10, 20, 30});
    }

    static List<List<T>> Foo<T>(T[] set)
    {

        // Init list
        List<List<T>> subsets = new List<List<T>>();

        // Loop over individual elements
        for (int i = 1; i < set.Length; i++)
        {
            subsets.Add(new List<T>(){set[i - 1]});

            List<List<T>> newSubsets = new List<List<T>>();

            // Loop over existing subsets
            for (int j = 0; j < subsets.Count; j++)
            {
                var tempList = new List<T>();
                tempList.Add(subsets[j][0]);
                tempList.Add(subsets[i][0]);
                var newSubset = tempList;
                newSubsets.Add(newSubset);
            }

            subsets.AddRange(newSubsets);
        }

        // Add in the last element
        //subsets.Add(set[set.Length - 1]);
        //subsets.Sort();

        //Console.WriteLine(string.Join(Environment.NewLine, subsets));
        return null;
    }

编辑

对不起,我仍然得到重复...

    static List<List<T>> GetSubsets<T>(IEnumerable<T> Set)
    {
        var set = Set.ToList<T>();

        // Init list
        List<List<T>> subsets = new List<List<T>>();

        subsets.Add(new List<T>()); // add the empty set

        // Loop over individual elements
        for (int i = 1; i < set.Count; i++)
        {
            subsets.Add(new List<T>(){set[i - 1]});

            List<List<T>> newSubsets = new List<List<T>>();

            // Loop over existing subsets
            for (int j = 0; j < subsets.Count; j++)
            {
                var newSubset = new List<T>();
                foreach(var temp in subsets[j])
                    newSubset.Add(temp);

                newSubset.Add(set[i]);


                newSubsets.Add(newSubset);
            }

            subsets.AddRange(newSubsets);
        }

        // Add in the last element
        subsets.Add(new List<T>(){set[set.Count - 1]});
        //subsets.Sort();

        return subsets;
    }

然后我可以将该方法称为:

【问题讨论】:

  • 我想得到一个集合的所有子集。如果我有集合 {1,2,3} 我想得到 {1},{2},{3},{1,2},{1,3},{2,3},{1, 2,3} .
  • 您当前的代码有什么特别的问题?什么不起作用?
  • 输入中的元素是否不同?如果没有,您想如何处理重复项?
  • 如果您不知道集合的所有子集的含义,请尝试回答另一个问题。我不想听起来很抱歉,但我只想完成这项作业,非常感谢您的帮助。也许我应该将问题命名为获取集合的所有子集,但由于我将其与 c# 相关联,因此我想将集合传递给方法,而子集是列表或数组,这就是为什么我想要 List&lt;List&lt;T&gt;&gt; 作为输出的原因我正在尝试创建的方法。
  • @TonoNam:我认为人们理解子集问题。不清楚的不是你想要做什么,而是你正在努力解决什么问题。

标签: c# algorithm set subset


【解决方案1】:

这是一个基本算法,我使用以下技术制作单人拼字游戏解算器(报纸上的)。

让你的集合有n 元素。将一个从 0 开始的整数递增到 2^n。对于每个生成器编号位掩码,整数的每个位置。如果整数的第i 位置是1,则选择集合的第i 元素。对于从02^n 的每个生成的整数,执行上述位标记和选择将获得所有子集。

这是一个帖子:http://phoxis.org/2009/10/13/allcombgen/

【讨论】:

    【解决方案2】:

    这是 Marvin Mendes 在 this answer 中提供的代码的改编版,但被重构为带有迭代器块的单个方法。

    public static IEnumerable<IEnumerable<T>> Subsets<T>(IEnumerable<T> source)
    {
        List<T> list = source.ToList();
        int length = list.Count;
        int max = (int)Math.Pow(2, list.Count);
    
        for (int count = 0; count < max; count++)
        {
            List<T> subset = new List<T>();
            uint rs = 0;
            while (rs < length)
            {
                if ((count & (1u << (int)rs)) > 0)
                {
                    subset.Add(list[(int)rs]);
                }
                rs++;
            }
            yield return subset;
        }
    }
    

    【讨论】:

    • 很抱歉问了一个愚蠢的问题,但为什么会这样呢?我不太了解转换部分 (if ((count &amp; (1u &lt;&lt; (int)rs)) &gt; 0))
    • @Casey 所以它将count 视为一个二进制数,其位数等于列表的长度。如果一个数字是1,那么列表中对应于该项目的项目包含在该排列中,它是0,那么它不是。如果您从0 数到11111...(其中一个的数量等于列表的大小),那么您已经计算了每个排列。
    【解决方案3】:

    我知道这个问题有点老了,但我一直在寻找答案,但在这里找不到任何好处,所以我想分享这个解决方案,它是在这个博客中找到的改编版:http://praseedp.blogspot.com.br/2010/02/subset-generation-in-c.html

    我只将类转换为泛型类:

    public class SubSet<T>
    {
        private IList<T> _list;
        private int _length;
        private int _max;
        private int _count;
    
        public SubSet(IList<T> list)
        {
            if (list== null)
                throw new ArgumentNullException("lista");
            _list = list;
            _length = _list.Count;
            _count = 0;
            _max = (int)Math.Pow(2, _length);
        }
    
    
        public IList<T> Next()
        {
            if (_count == _max)
            {
                return null;
            }
            uint rs = 0;
    
            IList<T> l = new List<T>();            
    
            while (rs < _length)
            {
                if ((_count & (1u << (int)rs)) > 0)
                {
                    l.Add(_list[(int)rs]);                    
                }
                rs++;
            }
            _count++;
            return l;            
        }
    }
    

    要使用此代码,您可以执行以下操作:

            List<string> lst = new List<string>();
    
            lst.AddRange(new string[] {"A", "B", "C" });
    
            SubSet<string> subs = new SubSet<string>(lst);
    
            IList<string> l = subs.Next();
    
            while (l != null)
            {
    
                DoSomething(l);
                l = subs.Next();
            }
    

    请记住:这段代码仍然是 O(2^n),如果您在列表中传递类似 20 个元素的内容,您将获得 2^20= 1048576 个子集!

    编辑: 正如 Servy 建议的那样,我添加了一个带有 interator 块的实现以与 Linq 和 foreach 一起使用,新类是这样的:

    private class SubSet<T> : IEnumerable<IEnumerable<T>>
        {
            private IList<T> _list;
            private int _length;
            private int _max;
            private int _count;
    
            public SubSet(IEnumerable<T> list)
            {
                if (list == null)
                    throw new ArgumentNullException("list");
                _list = new List<T>(list);
                _length = _list.Count;
                _count = 0;
                _max = (int)Math.Pow(2, _length);
            }
    
            public int Count
            {
                get { return _max; }
            }
    
    
    
            private IList<T> Next()
            {
                if (_count == _max)
                {
                    return null;
                }
                uint rs = 0;
    
                IList<T> l = new List<T>();
    
                while (rs < _length)
                {
                    if ((_count & (1u << (int)rs)) > 0)
                    {
                        l.Add(_list[(int)rs]);
                    }
                    rs++;
                }
                _count++;
                return l;
            }
    
            public IEnumerator<IEnumerable<T>> GetEnumerator()
            {
                IList<T> subset;
                while ((subset = Next()) != null)
                {
                    yield return subset;
                }
            }
    
            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
            {
                return GetEnumerator();
            }
        }
    

    你现在可以像这样使用它:

            List<string> lst = new List<string>();
    
            lst.AddRange(new string[] {"A", "B", "C" });
    
            SubSet<string> subs = new SubSet<string>(lst);
    
            foreach(IList<string> l in subs)
            {
                DoSomething(l);
            }
    

    感谢 Servy 的建议。

    【讨论】:

    • 您可以通过使其返回 IEnumerable&lt;IEnumerable&lt;T&gt;&gt; 并使用迭代器块来编写它来提高此类方法的可用性,而不是使用与 @ 不完全匹配的自定义迭代器987654327@ 语法。如果您进行更改,您将允许调用者轻松地foreach 处理结果、使用 LINQ 等,并且由于迭代器块,您的工作量不会很大。
    • 感谢您的建议,这可以非常简单地完成,只需在 SubSet 类中运行循环,在 IEnumerable> 上插入元素,或者像你说的那样使用 interator 块,但我猜这个类是危险的,因为 O(2^n) 并且可能需要太多时间来返回所有子集。我这样做是因为更容易控制你想从子集中返回多少集合,当然,为了简单起见。
    • 如果您将项目放入列表中,您将是正确的,因为这会急切地评估所有结果。我不建议这样做。如果您使用迭代器块,它将延迟执行,从而导致每个子集仅在请求时才被计算,从而在这方面保持与当前实现相同的行为。
    • 我猜这就是你想要的,如果不是,请纠正我,谢谢:-)
    • 这当然行得通,尽管我建议您将整个解决方案制作成一个返回 IEnumerable&lt;IEnumerable&lt;T&gt;&gt; 而不是同时公开两者的方法,但这也很好。
    【解决方案4】:

    它不会给出重复值;

    不要在子集的开头添加 int 数组的值

    正确的程序如下:

    class Program
        {
            static HashSet<List<int>> SubsetMaker(int[] a, int sum)
            {
                var set = a.ToList<int>();
                HashSet<List<int>> subsets = new HashSet<List<int>>();
                subsets.Add(new List<int>());
                for (int i =0;i<set.Count;i++)
                {
                    //subsets.Add(new List<int>() { set[i]});
                    HashSet<List<int>> newSubsets = new HashSet<List<int>>();
                    for (int j = 0; j < subsets.Count; j++)
                    {
                       var newSubset = new List<int>();
                       foreach (var temp in subsets.ElementAt(j))
                       {
                          newSubset.Add(temp);
    
    
                        }
                        newSubset.Add(set[i]);
                        newSubsets.Add(newSubset);
    
                    }
                    Console.WriteLine("New Subset");
                    foreach (var t in newSubsets)
                    {
                        var temp = string.Join<int>(",", t);
                        temp = "{" + temp + "}";
                        Console.WriteLine(temp);
                    }
                    Console.ReadLine();
    
                    subsets.UnionWith(newSubsets);
                }
                //subsets.Add(new List<int>() { set[set.Count - 1] });
                //subsets=subsets.;
                return subsets;
    
            }
            static void Main(string[] args)
            {
                int[] b = new int[] { 1,2,3 };
                int suma = 6;
                var test = SubsetMaker(b, suma);
                Console.WriteLine("Printing final set...");
                foreach (var t in test)
                {
                    var temp = string.Join<int>(",", t);
                    temp = "{" + temp + "}";
                    Console.WriteLine(temp);
                }
                Console.ReadLine();
    
            }
        }
    

    【讨论】:

      【解决方案5】:

      你不想返回一组列表,你想使用 java 的 set 类型。 Set 通过只保存每种类型的一个唯一元素,已经完成了您正在寻找的部分工作。因此,例如,您不能两次添加 20。它是一种无序类型,因此您可以编写一个组合函数来创建一堆集合,然后返回一个包含这些集合的列表。

      【讨论】:

      • 他将其标记为 C# 而不是 java。不过,我认为它不会改变你的答案。
      【解决方案6】:

      获取特定子集长度的集合的所有子集:

          public static IEnumerable<IEnumerable<T>> GetPermutations<T>(IEnumerable<T> list, int length) where T : IComparable
          {
              if (length == 1) return list.Select(t => new T[] { t });
              return GetPermutations(list, length - 1).SelectMany(t => list.Where(e => t.All(g => g.CompareTo(e) != 0)), (t1, t2) => t1.Concat(new T[] { t2 }));
          }
      
          public static IEnumerable<IEnumerable<T>> GetOrderedSubSets<T>(IEnumerable<T> list, int length) where T : IComparable
          {
              if (length == 1) return list.Select(t => new T[] { t });
              return GetOrderedSubSets(list, length - 1).SelectMany(t => list.Where(e => t.All(g => g.CompareTo(e) == -1)), (t1, t2) => t1.Concat(new T[] { t2 }));
          }
      

      测试代码:

              List<int> set = new List<int> { 1, 2, 3 };
              foreach (var x in GetPermutations(set, 3))
              {
                  Console.WriteLine(string.Join(", ", x));
              }
              Console.WriteLine();
              foreach (var x in GetPermutations(set, 2))
              {
                  Console.WriteLine(string.Join(", ", x));
              }
              Console.WriteLine();
              foreach (var x in GetOrderedSubSets(set, 2))
              {
                  Console.WriteLine(string.Join(", ", x));
              }
      

      测试结果:

      1, 2, 3
      1, 3, 2
      2, 1, 3
      2, 3, 1
      3, 1, 2
      3, 2, 1
      
      1, 2
      1, 3
      2, 1
      2, 3
      3, 1
      3, 2
      
      1, 2
      1, 3
      2, 3
      

      【讨论】:

        【解决方案7】:

        一个基于递归的简单算法:

        private static List<List<int>> GetPowerList(List<int> a)
            {
                int n = a.Count;
                var sublists = new List<List<int>>() { new List<int>() };
                for (int i = 0; i < n; i++)
                {
                    for (int j = i; j < n; j++)
                    {
                        var first = a[i];
                        var last = a[j];
                        if ((j - i) > 1)
                        {
                            sublists.AddRange(GetPowerList(a
                                .GetRange(i + 1, j - i - 1))
                                .Select(l => l
                                .Prepend(first)
                                .Append(last).ToList()));
                        }
                        else sublists.Add(a.GetRange(i,j - i + 1));
                    }
                }
                return sublists;
            }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2018-04-03
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-12-01
          相关资源
          最近更新 更多