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;
                      } )