2013-08-28 2 views
1

Начну с структуры данных.Linq выберите общее подмножество вложенных списков

class Device 
{ 
    public List<string> Interfaces { get; set; } 
} 

List<Device> allDevices; 

Я хотел бы использовать Linq запрос, чтобы выбрать все интерфейсы (строки), которые присутствуют в каждом устройстве в списке allDevices.

Спасибо в adavance.

ОБНОВЛЕНИЕ: Благодаря Арону мне удалось решить эту проблему. Вот мое решение:

List<string> commonInterfaces = allDevices.Select(device => device.Interfaces) 
    .Cast<IEnumerable<string>>() 
    .Aggregate(Enumerable.Intersect) 
    .ToList(); 
+3

Вы хотите только выбрать значения «Интерфейсы», которые существуют во всех «Устройствах»? –

+0

@ RichardDalton да, вот что я пытаюсь сделать. – sidon

+1

Метод '.AsEnumerable()' чище, чем литье ... извините! : P – Aron

ответ

3
var allInterfaces = from device in allDevices 
        from interface in device.Interfaces 
        select interface; 

var allInterfaces = allDevices.SelectMany(device => device.Interfaces); 

и если Ричард Далтон прав

var allCommonInterfaces = allDevices 
       .Select(device => device.Interfaces.AsEnumerable()) 
       .Aggregate(Enumerable.Intersect); 

Для удовольствия ... вот более «оптимальное» решение.

public static IEnumerable<T> CommonSubset<T> 
      (this IEnumerable<IEnumerable<T>> sequences, 
      EqualityComparer<T> comparer = null) 
{ 
    if (sequences == null) throw new ArgumentNullException("sequences"); 


    Enumerator<T> enumerator = sequences.GetEnumerator(); 
    if(enumerator.GetNext() == false) 
     throw new ArgumentException("Sequences must not be empty", "sequences"); 


    IEnumerable<T> first = enumerator.Current; 
    HashSet<T> commonSubset = new HashSet<T>(first); 
    while(enumerator.GetNext()) 
    { 
     var nextSequence = enumerator.Current; 
     var toRemove = commonSubset.Except(nextSequence, comparer ?? EqualityComparer<T>.Default).ToList(); 
     foreach(var r in toRemove) 
      commonSubset.Remove(r); 
    } 
    return commonSubset; 
} 
+0

Агрегат, похоже, подходит, но он не компилируется. Это ошибка: 'System.Collections.Generic.IEnumerable System.Linq.Enumerable.Intersect (System.Collections.Generic.IEnumerable , System.Collections.Generic.IEnumerable )' имеет неправильный тип возвращаемого – sidon

+0

I удалось исправить эту ошибку. Вывод моего решения на вопрос. Спасибо за помощь. – sidon

+0

Упс ...Список vs IEnumerable Я должен попытаться скомпилировать ответы ... – Aron

5

Вы можете использовать Enumerable.Intersect, например:

IEnumerable<string> commonSubset = allDevices.First().Interfaces; 
foreach (var interfaces in allDevices.Skip(1).Select(d => d.Interfaces)) 
{ 
    commonSubset = commonSubset.Intersect(interfaces); 
    if (!commonSubset.Any()) 
     break; 
} 

DEMO

Если вы хотите использовать его, вы могли бы сделать его метод расширения:

public static IEnumerable<T> CommonSubset<T>(this IEnumerable<IEnumerable<T>> sequences) 
{ 
    return CommonSubset(sequences, EqualityComparer<T>.Default); 
} 

public static IEnumerable<T> CommonSubset<T>(this IEnumerable<IEnumerable<T>> sequences, EqualityComparer<T> comparer) 
{ 
    if (sequences == null) throw new ArgumentNullException("sequences"); 
    if (!sequences.Any()) throw new ArgumentException("Sequences must not be empty", "sequences"); 

    IEnumerable<T> commonSubset = sequences.First(); 
    foreach (var sequence in sequences.Skip(1)) 
    { 
     commonSubset = commonSubset.Intersect(sequence, comparer); 
     if (!commonSubset.Any()) 
      break; 
    } 
    return commonSubset; 
} 

Теперь использование довольно прост (компаратор можно использовать для пользовательских типов):

var allInterfaces = allDevices.Select(d => d.Interfaces); 
var commonInterfaces = allInterfaces.CommonSubset(); 
Console.Write(string.Join(",", commonInterfaces)); 
+0

Ничего себе, спасибо за усилия, даже делая демо. Я согласен, что использование foreach, возможно, является оптимизированным решением, однако ответ Арона проще и точно отвечает на мой вопрос. – sidon

+0

@sidon: добавлен метод расширения для удобства чтения и удобства, который работает с любыми типами;) –

+0

Собственно. Оптимальным решением (где m и n являются большими) было бы использование 'var commonSubset = new HashSet (sequence.First())'. Затем используйте remove. Ты должен быть осторожен. Некоторые IEnumerables очень неэффективны при использовании 'GetEnumerator()' дважды. Поэтому снова для эффективности я бы назвал это только однажды ... и бросил ваш собственный метод 'TakeOneAndSkipOne (out T, out IEnumerable )'. – Aron

2

Я предполагаю, что вы ищете:

List<string> allInterfaces = allDevices.SelectMany(r=> r.Interfaces).ToList(); 

или вы можете выбрать IEnumerable<string> как:

var allInterfaces = allDevices.SelectMany(r=> r.Interfaces); 
Смежные вопросы