LINQ "zip" в String Array
Скажем, есть два массива:
String[] title = { "One","Two","three","Four"};
String[] user = { "rob","","john",""};
Мне нужно отфильтровать вышеупомянутый массив, когда значение user
равно Пустом, затем присоедините или запишите эти два вместе. Конечный результат должен выглядеть следующим образом:
{ "One:rob", "three:john" }
Как это можно сделать с помощью LINQ?
Ответы
Ответ 1
Звучит так, будто вы на самом деле хотите "застегнуть" данные вместе (не присоединяться) - то есть попарно; это верно? Если это так, просто:
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<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
Для начала вам понадобится оператор Zip
, чтобы объединить два массива. Здесь приведена сокращенная версия кода из Eric Lippert 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);
Ответ 3
В дополнение к уже опубликованным ответам, это решение без использования метода 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);
Ответ 4
Как дополнение к предыдущим ответам, 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 (хотя оно может иметь и более гибкое разнообразие).
Ответ 5
При взгляде на ответ Marc (и, в конечном счете, на метод Zip
на .Net 4) существует значительная часть накладных расходов, чтобы перечислять и присоединяться к строкам, где они в конечном итоге выбрасываются; можно ли это сделать без этих отходов?
Рассматривая ответ Джона, создание проекции динамических объектов для ссылки на существующие данные, а затем создание нового набора объектов из этого зеркала может быть сдерживающим фактором для использования этого метода, если общее количество строк было слишком большим.
В приведенном ниже фрагменте используются ссылки на исходные данные, и единственными потерянными проецируемыми правами являются те, у которых есть нулевая строка, которые впоследствии удаляются. Также перечисление данных сведено к минимуму.
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;
} )