当我处理多个数据类型时,如何处理多个foreach循环?

4

最近有一个问题一直困扰着我,它涉及到我的程序中循环的指数级增长。请看下面的代码并阅读注释。

void Main()
{
    //Here we are just creating simple lists
    List<string> strings = new List<string>();
    strings.Add("a");
    strings.Add("b");
    strings.Add("c");

    List<int> integers = new List<int>();
    integers.Add(1);
    integers.Add(2);
    integers.Add(3);

    //Creating complex classes ( not really )
    ComplexClass cc1 = new ComplexClass();
    cc1.CCString = "A test";
    cc1.CCInt = 2;

    ComplexClass cc2 = new ComplexClass();
    cc2.CCString = "Another test";
    cc2.CCInt = 6;

    //Creating a list of these too
    List< ComplexClass > complexClasses = new List< ComplexClass >();
    complexClasses.Add(cc1);
    complexClasses.Add(cc2);


    //Here i want to create every possible combination using each of the lists 
    //and then add these to a testData class to do other things with, serialize, save, print etc.
    //The main question is here, the for loops will definitely increase exponentially with each
    //list added to. 
    foreach( int i in integers )
    {
        foreach( string s in strings )
        {
            foreach( ComplexClass compClass in complexClasses )
            {
                TestData data = new TestData();
                data.TestInteger = i;
                data.TestString = s;
                data.TestComplexClass = compClass;

                OutPutTestData( data );
            }
        }
    }
}

//Simply outputs the data as test but I will be keeping the object for later also
public void OutPutTestData( TestData testData )
{
    Console.WriteLine( testData.TestString + testData.TestInteger + testData.TestComplexClass.CCString );
}

//The "Complex class" again not that complex but an example of what im tring to achieve
public class ComplexClass
{
    public string CCString{ get; set; }
    public int CCInt { get; set; }
}

//The overall test object which holds multiple properties of different data types
public class TestData
{
    public string TestString { get; set; }
    public int TestInteger { get; set; }
    public ComplexClass TestComplexClass { get; set; }
}

输出

a1 测试1

a1 测试2

b1 测试1

b1 测试2

c1 测试1

c1 测试2

a2 测试1

a2 测试2

b2 测试1

b2 测试2

c2 测试1

c2 测试2

a3 测试1

a3 测试2

b3 测试1

b3 测试2

c3 测试1

c3 测试2

如你所见,循环工作,并给出了提供数据的所有可能组合。

我的问题是 for 循环的指数增长随着我添加更多列表。可能会有大量的列表。

我确实明白,当发现组合时迭代次数将增加,这并不是问题,因为我计划根据用户的输入在估算出总迭代次数后以编程方式限制可能发生的迭代次数。

例如,总迭代次数将是234,因此仅迭代120次(120个组合)。

提供的代码在嵌套的 foreach 循环中运行良好,但随着指数增长,变得难以阅读、难以管理,一般来说也不太美观。

我查看了这些排列算法:

生成列表所有可能排列的算法?

理解递归以生成排列

但它们只允许使用一个特定的数据类型而不是多个。

我还研究了笛卡尔积,但再次发现唯一的示例是指单个数据类型。


1
我想我不确定问题确切是什么。无论你做什么,组合的数量都会呈指数增长,除非我漏掉了什么。 - Phil Ringsmuth
@KyleT 所以将它们全部转换为对象序列。完成。 - Servy
1
@Servy,您能否提供您认为应该如何工作的解决方案? - Master Yoda
1
你看过LINQ和N元笛卡尔积以及其后续内容了吗? - wimh
1
@Servy,您提出的解决方案将添加两个强制转换,并且仍需要额外的代码来维护,但如果您对此感到非常强烈,请继续。我不会重新打开它。 - D Stanley
显示剩余22条评论
2个回答

4
尽管您已经选择了一个答案,但我认为您可能想看一下这个... 通过使用递归,您只需要将所有的List放在List<IList>中。对此,您只需将任何新添加的List添加到List<IList>中即可。
我为您的ComplexClass添加了一个override ToString()以适应此内容。
        public static void Test()
        {
            //Here we are just creating simple lists
            List<string> strings = new List<string>();
            strings.Add("a");
            strings.Add("b");
            strings.Add("c");

            List<int> integers = new List<int>();
            integers.Add(1);
            integers.Add(2);
            integers.Add(3);

            //Creating complex classes ( not really )
            ComplexClass cc1 = new ComplexClass();
            cc1.CCString = "A test";
            cc1.CCInt = 2;

            ComplexClass cc2 = new ComplexClass();
            cc2.CCString = "Another test";
            cc2.CCInt = 6;

            //Creating a list of these too
            List<ComplexClass> complexClasses = new List<ComplexClass>();
            complexClasses.Add(cc1);
            complexClasses.Add(cc2);

            // NEW LIST
            List<double> doubles = new List<double>();
            doubles.Add(99.99);
            doubles.Add(100.12);

            List<IList> myLists = new List<IList> {integers, strings, complexClasses, doubles};
            Permutate("", myLists, 0);

            Console.ReadLine();
        }

        public static void Permutate(string s, List<IList> list, int i)
        {
            if (i == list.Count)
            {
                Console.WriteLine(s);
            }
            else
            {
                foreach (object obj in list[i])
                {
                    Permutate(s + obj + " ", list, i + 1);
                }
            }
        }

        //The "Complex class" again not that complex but an example of what im tring to achieve
        public class ComplexClass
        {
            public string CCString { get; set; }
            public int CCInt { get; set; }

            // Added override
            public override string ToString()
            {
                return CCString + CCInt;
            }
        }

结果(未能捕获所有结果):

这里输入图片描述


这实际上更符合我的需求。干得好,谢谢你的回答。我认为减少列表引用的次数对于这个问题至关重要。 - Master Yoda
我已经开始学习委托、回调以及这类的东西。我明白通过某种方式传递方法是可能的,这样你就不会与 toString 方法耦合在一起了,对吧? - MVCDS

3

您可以通过在Linq中进行交叉连接来消除for循环:

var query = 
    from  i in integers
    from s in strings 
    from compClass in complexClasses
    select new TestData()
    {
        TestInteger = i,
        TestString = s,
        TestComplexClass = compClass
    };

foreach (var data in query)
    OutPutTestData( data );

如果这些列表都是相同类型的,那么你可以构建一个查询来交叉连接不同数量的列表。但在你的情况下,由于这些列表是不同类型的,所以这是不可能的(除非使用反射、动态或者更加丑陋的方法)。


这与原帖作者的解决方案实际上没有明显的区别。 - Servy
但我有一种感觉,这更易读和更易于更改,至少对于C#“使用者”来说。 - MVCDS
@Servy 从功能上讲,它完全相同,但是添加嵌套的for循环会使代码更加清晰。我仍然不确定实际问题是什么(或者这是否解决了任何问题),但这是一种替代方案。 - D Stanley
@DStanley 但这并没有特别干净。我的意思是,你可以从除了最内部的foreach之外的所有循环中删除大括号,两种情况下代码看起来几乎完全相同,即使在风格上的差异中,唯一的区别也非常小。 - Servy
2
@Servy 我认为实际上可能需要更多的工作... 因为您可以将新的初始化程序添加到 new TestData() 中,而 OutPutTestData 很可能会忽略它(除非您记得更新它并留下良好的文档记录),这会创建错误的阳性测试结果。 有绕过此问题的方法,但我同意您最终仍然需要在3个不同的位置更新代码。 因此,在那一点上,这只是一个风格问题 - 由于代码缩进的原因,嵌套的 for 循环层数过多时阅读起来会有难度... - user700390
显示剩余8条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接