假设有两个数组:
String[] title = { "One","Two","three","Four"};
String[] user = { "rob","","john",""};
当user
值为Empty时,我需要过滤掉上面的数组,然后将两者连接或压缩。最终输出应该是:
{ "One:rob", "three:john" }
如何使用LINQ完成?
答案 0 :(得分:10)
首先,您需要一个Zip
运算符将两个数组压缩在一起。这是来自Eric Lippert's blog的代码的缩写版本(此版本中没有错误检查,仅为了简洁起见):
public static IEnumerable<TResult> Zip<TFirst, TSecond, TResult>
(this IEnumerable<TFirst> first,
IEnumerable<TSecond> second,
Func<TFirst, TSecond, TResult> resultSelector)
{
using (IEnumerator<TFirst> e1 = first.GetEnumerator())
using (IEnumerator<TSecond> e2 = second.GetEnumerator())
while (e1.MoveNext() && e2.MoveNext())
yield return resultSelector(e1.Current, e2.Current);
}
请注意,Zip
将位于.NET 4.0的标准库中。
然后你需要应用滤镜和投影。所以我们得到:
var results = title.Zip(user, (Title, User) => new { Title, User })
.Where(x => x.Title != "")
.Select(x => x.Title + ":" + x.User);
答案 1 :(得分:9)
听起来你实际上想要将数据“压缩”在一起(不是连接) - 即成对匹配;那是对的吗?如果是这样,只需:
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);
使用here中的Zip
操作:
// http://blogs.msdn.com/ericlippert/archive/2009/05/07/zip-me-up.aspx
public static IEnumerable<TResult> Zip<TFirst, TSecond, TResult>
(this IEnumerable<TFirst> first,
IEnumerable<TSecond> second,
Func<TFirst, TSecond, TResult> 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<TResult> ZipIterator<TFirst, TSecond, TResult>
(IEnumerable<TFirst> first,
IEnumerable<TSecond> second,
Func<TFirst, TSecond, TResult> resultSelector)
{
using (IEnumerator<TFirst> e1 = first.GetEnumerator())
using (IEnumerator<TSecond> e2 = second.GetEnumerator())
while (e1.MoveNext() && e2.MoveNext())
yield return resultSelector(e1.Current, e2.Current);
}
答案 2 :(得分:6)
作为已发布答案的补充,这里是一个不使用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);
答案 3 :(得分:0)
作为前一个答案的另一个补充,Zip通常被定义并与Tuple
类型一起使用。这使用户不必提供resultSelector
功能。
public class Tuple<TItem1, TItem2> // 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<Tuple<TFirst, TSecond>> Zip<TFirst, TSecond>
(this IEnumerable<TFirst> first, IEnumerable<TSecond> second)
{
using (IEnumerator<TFirst> e1 = first.GetEnumerator())
using (IEnumerator<TSecond> e2 = second.GetEnumerator())
{
while (e1.MoveNext() && e2.MoveNext())
yield return new Tuple<TFirst, TSecond>(e1.Current, e2.Current);
}
}
我相信这更接近CLR 4.0所具有的(尽管它可能也有更灵活的变化)。
答案 4 :(得分:0)
在查看Marc的答案(最终是。{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<string>(),
(result, usr) =>
{
if (string.IsNullOrEmpty(usr) == false)
result.Add(string.Format("{0}:{1}", title[index], usr));
++index;
return result;
} )