2009-07-24 6 views
10

Say есть два массива:LINQ «молния» в массиве строк

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

Мне нужно отфильтровать выше массив, когда значение user пусто, то присоединиться или заархивировать вместе. Конечный результат должен быть следующим:

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

Как это можно сделать с помощью LINQ?

+3

'Zip' теперь является стандартным методом, как в .NET 4.0. http://msdn.microsoft.com/en-us/library/dd267698.aspx – Mashmagar

ответ

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

используя Zip операцию из here:

// 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); 
} 
+0

К сожалению, я не сформировал пример хорошо, значения в обоих массивах не совпадают вообще. Пожалуйста, ознакомьтесь с обновленным Вопросом. – Kusek

+0

Я понял это; заменен на «zip» –

+0

LOL - люблю тот факт, что мы оба использовали код Эрика. –

9

Для начала, вам нужен 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); 
5

В качестве дополнения к уже опубликованным ответам, это решение без использования метода 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); 
0

В качестве другого дополнения к предыдущим ответам, Страна, как правило, определяется и используется в сочетании с 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 будет иметь (хотя он может иметь более гибкий выбор, а).

0

Рассматривая ответ Марка (и, в конечном счете, метод 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; 
         }) 
Смежные вопросы