字符串数组中的LINQ“zip”

假设有两个数组:

String[] title = { "One","Two","three","Four"}; String[] user = { "rob","","john",""}; 

我需要在user值为Empty时过滤掉上面的数组,然后将两者连接或压缩。 最终输出应该是:

 { "One:rob", "three:john" } 

如何使用LINQ完成?

听起来你实际上想要将数据“压缩”在一起(不是连接) – 即成对匹配; 那是对的吗? 如果是这样,只需:

  var qry = from row in title.Zip(user, (t, u) => new { Title = t, User = u }) where !string.IsNullOrEmpty(row.User) select row.Title + ":" + row.User; foreach (string s in qry) Console.WriteLine(s); 

从这里使用Zip操作:

 // http://blogs.msdn.com/ericlippert/archive/2009/05/07/zip-me-up.aspx public static IEnumerable Zip (this IEnumerable first, IEnumerable second, Func resultSelector) { if (first == null) throw new ArgumentNullException("first"); if (second == null) throw new ArgumentNullException("second"); if (resultSelector == null) throw new ArgumentNullException("resultSelector"); return ZipIterator(first, second, resultSelector); } private static IEnumerable ZipIterator (IEnumerable first, IEnumerable second, Func resultSelector) { using (IEnumerator e1 = first.GetEnumerator()) using (IEnumerator e2 = second.GetEnumerator()) while (e1.MoveNext() && e2.MoveNext()) yield return resultSelector(e1.Current, e2.Current); } 

首先,您需要一个Zip运算符将两个数组压缩在一起。 这是Eric Lippert博客的代码的缩写版本(此版本中没有错误检查,仅为了简洁起见):

 public static IEnumerable Zip (this IEnumerable first, IEnumerable second, Func resultSelector) { using (IEnumerator e1 = first.GetEnumerator()) using (IEnumerator e2 = second.GetEnumerator()) while (e1.MoveNext() && e2.MoveNext()) yield return resultSelector(e1.Current, e2.Current); } 

请注意, Zip将位于.NET 4.0的标准库中。

然后你需要应用一个filter和一个投影。 所以我们得到:

 var results = title.Zip(user, (Title, User) => new { Title, User }) .Where(x => x.Title != "") .Select(x => x.Title + ":" + x.User); 

作为已发布答案的补充,这里是一个不使用Zip方法的解决方案。 这假设两个数组的长度相同。

  var pairs = from idx in Enumerable.Range(0, title.Length) let pair = new {Title = title[idx], User = user[idx]} where !String.IsNullOrEmpty(pair.User) select String.Format("{0}:{1}", pair.Title, pair.User); 

作为前一个答案的另一个补充,Zip通常被定义并与Tuple类型一起使用。 这减轻了用户必须提供resultSelectorfunction的resultSelector

 public class Tuple // other definitions for higher arity { public TItem1 Item1 { get; private set; } public TItem2 Item2 { get; private set; } public Tuple(TItem1 item1, TItem2 item2) { Item1 = item1; Item2 = item2; } } 

因此:

 public static IEnumerable> Zip (this IEnumerable first, IEnumerable second) { using (IEnumerator e1 = first.GetEnumerator()) using (IEnumerator e2 = second.GetEnumerator()) { while (e1.MoveNext() && e2.MoveNext()) yield return new Tuple(e1.Current, e2.Current); } } 

我相信这更接近CLR 4.0将具有的内容(尽管它也可能具有更灵活的变化)。

在查看Marc的答案(以及最终的.Net 4中的Zip方法)时,枚举和连接最终被丢弃的行会产生大量的开销; 没有那种浪费可以做到吗?

在查看Jon的答案时,创建动态实体的投影以引用现有数据,然后从该镜像创建一组新实体,如果总行数过大,则可能会阻碍使用该方法。

下面的代码段使用对原始数据的引用,并且创建的唯一浪费的预计权限是具有空字符串的随后被删除的权限。 此外,数据的枚举保持最小。

 String[] title = { "One","Two","three","Four"}; String[] user = { "rob","","john",""}; user.Select ((usr, index) => string.IsNullOrEmpty(usr) ? string.Empty : string.Format("{0}:{1}", title[index], usr )) .Where (cmb => string.IsNullOrEmpty(cmb) == false) 

顺便说一句,这种方法可能有用户数组,其大小比标题数组小。


Aggregate函数被忽略了,在这里它正在运行:

 int index = 0; user.Aggregate (new List(), (result, usr) => { if (string.IsNullOrEmpty(usr) == false) result.Add(string.Format("{0}:{1}", title[index], usr)); ++index; return result; } )