在C#中,如何在嵌套的foreach循环中强制迭代IEnumerable?

时间:2017-05-02 14:08:49

标签: c#

我有两个IEnumerable s:

IEnumerable<string> first = ...
IEnumerable<string> second = ...

我想创建第二个IEnumerable<string>,它是每个IEnumerable的每个元素的串联。

例如:

IEnumerable<string> first = new [] {"a", "b"};
IEnumerable<string> second = new [] {"c", "d"};

foreach (string one in first)
{
   foreach (string two in second)
   {
      yield return string.Format("{0} {1}", one, two);
   }
}

这会产生:

"a c"; "a d"; "b c"; "b d";

问题是,有时两个IEnumerable中的一个是空的:

IEnumerable<string> first = new string[0];
IEnumerable<string> second = new [] {"c", "d"};

在这种情况下,嵌套的foreach结构永远不会到达yield return语句。当IEnumerable为空时,我希望结果只是非空IEnumerable的列表。

如何制作我想要的组合?

修改: 实际上,我有三个不同的IEnumerable我试图合并,所以添加条件为每个可能的空IEnumerable排列看起来很糟糕。如果这是唯一的方法,那么我想我必须这样做。

5 个答案:

答案 0 :(得分:3)

您可以简单地检查第一个可枚举是否为空:

IEnumerable<string> first = new [] {"a", "b"};
IEnumerable<string> second = new [] {"c", "d"};

var firstList = first.ToList();

if (!firstList.Any()) {
    return second;
}

foreach (string one in firstList)
{
   foreach (string two in second)
   {
      yield return string.Format("{0} {1}", one, two);
   }
}

要消除在正面情况下的双IEnumerable评估,只需将第一个可枚举转换为列表

答案 1 :(得分:1)

您当前的方法应该有效,直到任何集合为空。如果是这种情况,您需要在前面进行一些检查:

if(!first.Any())
    foreach(var e in second) yield return e;
else if(!second.Any())
    foreach(var e in first) yield return e;

foreach (string one in first)
{
   foreach (string two in second)
   {
      yield return string.Format("{0} {1}", one, two);
   }
}

但是,您应该考虑使用前面的ToList立即执行,以避免同一集合的多次迭代。

答案 2 :(得分:1)

假设您输出案例:

IEnumerable<string> first = new string[0];
IEnumerable<string> second = new [] {"c", "d"};

将是:

c
d

这样可行:

var query = from x in first.Any() ? first : new [] { "" }
            from y in second.Any() ? second : new[] { "" }
            select x + y;
  

代码更少,更易于维护和调试!

编辑:如果你有任何其他的IEnumerable,每个IEnumerable只包含1行(包括支票)

var query = from x in first.Any() ? first : new [] { "" }
            from y in second.Any() ? second : new[] { "" }
            from z in third.Any() ? third : new[] { "" }
            select x + y + z;

编辑2 :您可以在最后添加空格:

select (x + y + z).Aggregate(string.Empty, (c, i) => c + i + ' ');

答案 3 :(得分:1)

即使没有项目,也只需使用<<-SQL枚举收集。

Enumerable.DefaultIfEmpty()

注意:我已使用IEnumerable<string> first = new string[0]; IEnumerable<string> second = new[] { "a", "b" }; IEnumerable<string> third = new[] { "c", null, "d" }; var permutations = from one in first.DefaultIfEmpty() from two in second.DefaultIfEmpty() from three in third.DefaultIfEmpty() select String.Join(" ", NotEmpty(one, two, three)); 加入非空或空的项目以及选择要加入的非空项目的方法(如果您不想要加入,则可以内联此代码单独的方法):

String.Join

以上样本的输出是

private static IEnumerable<string> NotEmpty(params string[] items)
{
    return items.Where(s => !String.IsNullOrEmpty(s));
}

对于两个集合和foreach循环(虽然我会优先选择LINQ):

[ "a c", "a", "a d", "b c", "b", "b d" ]

输出:

IEnumerable<string> first = new[] { "a", "b" };
IEnumerable<string> second = new string[0];

foreach(var one in first.DefaultIfEmpty())
{
     foreach(var two in second.DefaultIfEmpty())
        yield return $"{one} {two}".Trim(); // with two items simple Trim() can be used
}

答案 4 :(得分:1)

如果您有多个列表,则可以设置递归迭代器。你会想要注意堆栈,我认为字符串连接不太理想,并且传递列表列表相当笨重,但这应该让你开始。

using System;
using System.Collections.Generic;
using System.Linq;

namespace en
{
    class Program
    {
        static void Main(string[] args)
        {
            // three sample lists, for demonstration purposes.
            var a = new List<string>() { "a", "b", "c" };
            var b = new List<string>() { "1", "2", "3" };
            var c = new List<string>() { "i", "ii", "iii" };

            // the function needs everything in one argument, so create a list of the lists.
            var lists = new List<List<string>>() { a, b, c };

            var en = DoStuff(lists).GetEnumerator();

            while (en.MoveNext())
            {
                Console.WriteLine(en.Current);
            }
        }

        // This is the internal function. I only made it private because the "prefix" variable
        // is mostly for internal use, but there might be a use case for exposing that ...
        private static IEnumerable<String> DoStuffRecursive(IEnumerable<String> prefix, IEnumerable<IEnumerable<String>> lists)
        {
            // start with a sanity check
            if (object.ReferenceEquals(null, lists) || lists.Count() == 0)
            {
                yield return String.Empty;
            }

            // Figure out how far along iteration is
            var len = lists.Count();

            // down to one list. This is the exit point of the recursive function.
            if (len == 1)
            {
                // Grab the final list from the parameter and iterate over the values.
                // Create the final string to be returned here.
                var currentList = lists.First();
                foreach (var item in currentList)
                {
                    var result = prefix.ToList();
                    result.Add(item);

                    yield return String.Join(" ", result);
                }
            }
            else
            {
                // Split the parameter. Take the first list from the parameter and 
                // separate it from the remaining lists. Those will be handled 
                // in deeper calls.
                var currentList = lists.First();
                var remainingLists = lists.Skip(1);

                foreach (var item in currentList)
                {
                    var iterationPrefix = prefix.ToList();
                    iterationPrefix.Add(item);

                    // here's where the magic happens. You can't return a recursive function
                    // call, but you can return the results from a recursive function call.
                    // http://stackoverflow.com/a/2055944/1462295
                    foreach (var x in DoStuffRecursive(iterationPrefix, remainingLists))
                    {
                        yield return x;
                    }
                }
            }
        }

        // public function. Only difference from the private function is the prefix is implied.
        public static IEnumerable<String> DoStuff(IEnumerable<IEnumerable<String>> lists)
        {
            return DoStuffRecursive(new List<String>(), lists);
        }
    }
}

控制台输出:

a 1 i
a 1 ii
a 1 iii
a 2 i
a 2 ii
a 2 iii
a 3 i
a 3 ii
a 3 iii
b 1 i
b 1 ii
b 1 iii
b 2 i
b 2 ii
b 2 iii
b 3 i
b 3 ii
b 3 iii
c 1 i
c 1 ii
c 1 iii
c 2 i
c 2 ii
c 2 iii
c 3 i
c 3 ii
c 3 iii