2009-04-06 2 views
132

У меня есть следующие ситуацииСоздать список из двух списков объектов с помощью LINQ

class Person 
{ 
    string Name; 
    int Value; 
    int Change; 
} 

List<Person> list1; 
List<Person> list2; 

Мне нужно, чтобы объединить 2 списка в новый List<Person> в случае, если это тот же человек, комбайн записи будет иметь это имя, значение человека в списке2, изменение будет значением list2 - значением list1. Изменение равно 0, если нет дубликата

+1

Действительно ли linq действительно нужен - хороший foreach с небольшим количеством выражений linq-ish может также работать. – Rashack

+1

Добавляем этот комментарий как версию названия вопроса, и фактический вопрос не соответствует: реальный ответ на этот вопрос - [этот ответ от Майка] (http://stackoverflow.com/a/6772832/1542187). Большинство других ответов, хотя и полезно, фактически не решают проблему, представленную оригинальным плакатом. –

ответ

11

Для этого есть несколько частей, если каждый список не содержит дубликатов, имя является уникальным идентификатором, и ни один список не упорядочен.

Сначала нужно создать метод расширения Append, чтобы получить единый список:

static class Ext { 
    public static IEnumerable<T> Append(this IEnumerable<T> source, 
             IEnumerable<T> second) { 
    foreach (T t in source) { yield return t; } 
    foreach (T t in second) { yield return t; } 
    } 
} 

Таким образом, можно получить единый список:

var oneList = list1.Append(list2); 

Тогда группа по имени

var grouped = oneList.Group(p => p.Name); 

может тогда обрабатывать каждую группу с помощью помощника для обработки одной группы за раз

public Person MergePersonGroup(IGrouping<string, Person> pGroup) { 
    var l = pGroup.ToList(); // Avoid multiple enumeration. 
    var first = l.First(); 
    var result = new Person { 
    Name = first.Name, 
    Value = first.Value 
    }; 
    if (l.Count() == 1) { 
    return result; 
    } else if (l.Count() == 2) { 
    result.Change = first.Value - l.Last().Value; 
    return result; 
    } else { 
    throw new ApplicationException("Too many " + result.Name); 
    } 
} 

который может быть применен к каждому элементу grouped:

var finalResult = grouped.Select(g => MergePersonGroup(g)); 

(Предупреждение:. Непроверенные)

+2

Ваш 'Append' является почти точным дубликатом готового' Concat'. – Rawling

+0

@ Rawling: По какой-то причине я продолжал пропускать «Enumerable.Concat» и, таким образом, перепрограммировал его. – Richard

2

Вам нужно что-то вроде полного внешнего соединения. System.Linq.Enumerable не имеет метода, который реализует полное внешнее соединение, поэтому мы должны сделать это сами.

var dict1 = list1.ToDictionary(l1 => l1.Name); 
var dict2 = list2.ToDictionary(l2 => l2.Name); 
    //get the full list of names. 
var names = dict1.Keys.Union(dict2.Keys).ToList(); 
    //produce results 
var result = names 
.Select(name => 
{ 
    Person p1 = dict1.ContainsKey(name) ? dict1[name] : null; 
    Person p2 = dict2.ContainsKey(name) ? dict2[name] : null; 
     //left only 
    if (p2 == null) 
    { 
    p1.Change = 0; 
    return p1; 
    } 
     //right only 
    if (p1 == null) 
    { 
    p2.Change = 0; 
    return p2; 
    } 
     //both 
    p2.Change = p2.Value - p1.Value; 
    return p2; 
}).ToList(); 
224

Это можно легко сделать с помощью метода расширения Linq Union. Например:

var mergedList = list1.Union(list2).ToList(); 

Это вернет список, в котором два списка объединены, и удваивается. Если вы не укажете компаратор в методе расширения Union, как в моем примере, он будет использовать методы Equals и GetHashCode по умолчанию в вашем классе Person. Если вы, например, хотите сравнить людей, сравнивая их свойство Name, вы должны переопределить эти методы для выполнения сравнения самостоятельно. Проверьте следующий пример кода, чтобы выполнить это. Вы должны добавить этот код в свой класс Person.

/// <summary> 
/// Checks if the provided object is equal to the current Person 
/// </summary> 
/// <param name="obj">Object to compare to the current Person</param> 
/// <returns>True if equal, false if not</returns> 
public override bool Equals(object obj) 
{   
    // Try to cast the object to compare to to be a Person 
    var person = obj as Person; 

    return Equals(person); 
} 

/// <summary> 
/// Returns an identifier for this instance 
/// </summary> 
public override int GetHashCode() 
{ 
    return Name.GetHashCode(); 
} 

/// <summary> 
/// Checks if the provided Person is equal to the current Person 
/// </summary> 
/// <param name="personToCompareTo">Person to compare to the current person</param> 
/// <returns>True if equal, false if not</returns> 
public bool Equals(Person personToCompareTo) 
{ 
    // Check if person is being compared to a non person. In that case always return false. 
    if (personToCompareTo == null) return false; 

    // If the person to compare to does not have a Name assigned yet, we can't define if it's the same. Return false. 
    if (string.IsNullOrEmpty(personToCompareTo.Name) return false; 

    // Check if both person objects contain the same Name. In that case they're assumed equal. 
    return Name.Equals(personToCompareTo.Name); 
} 

Если вы не хотите, чтобы установить по умолчанию Равно метод вашего класса Person, чтобы всегда использовать имя для сравнения двух объектов, вы также можете написать класс компаратора, который использует интерфейс IEqualityComparer. Затем вы можете предоставить этот компаратор как второй параметр в методе расширения Linq. Более подробную информацию о том, как написать такой метод сравнения, можно найти на http://msdn.microsoft.com/en-us/library/system.collections.iequalitycomparer.aspx

+10

Я не вижу, как это отвечает на вопрос о слиянии ценностей. – wdanda

+1

Это не ответит, Союз будет содержать только элементы, присутствующие в двух наборах, а не какой-либо элемент, присутствующий в одном из двух списков – J4N

+5

@ J4N, возможно, вы запутываете 'Союз' с' Интерсект'? – Kos

53

Я заметил, что этот вопрос не был отмечен как ответ через 2 года - я думаю, что ближайший ответ - Ричардс, но его можно упростить довольно это:

list1.Concat(list2) 
    .ToLookup(p => p.Name) 
    .Select(g => g.Aggregate((p1, p2) => new Person 
    { 
     Name = p1.Name, 
     Value = p1.Value, 
     Change = p2.Value - p1.Value 
    })); 

Хотя это будет не ошибка в том случае, если у вас есть повторяющиеся имена в любом наборе.

Некоторые другие ответы предложили использовать unioning - это определенно не путь, так как он только даст вам отличный список, не делая объединения.

+6

Это сообщение действительно отвечает на вопрос, и делает это хорошо. – philu

+2

Это должен быть принятый ответ. Никогда не видел вопроса с таким количеством опросов для ответов, которые не отвечают на заданный вопрос! –

1
public void Linq95() 
{ 
    List<Customer> customers = GetCustomerList(); 
    List<Product> products = GetProductList(); 

    var customerNames = 
     from c in customers 
     select c.CompanyName; 
    var productNames = 
     from p in products 
     select p.ProductName; 

    var allNames = customerNames.Concat(productNames); 

    Console.WriteLine("Customer and product names:"); 
    foreach (var n in allNames) 
    { 
     Console.WriteLine(n); 
    } 
} 
60

Почему вы не просто используете Concat?

Concat является частью LINQ и более эффективным, чем делать в AddRange()

в вашем случае:

List<Person> list1 = ... 
List<Person> list2 = ... 
List<Person> total = list1.Concat(list2); 
+12

Откуда вы знаете, что это более эффективно? –

+0

@ Джерри Никсон Он/она не тестировал его, но объяснение кажется логичным. http://stackoverflow.com/questions/1337699/which-is-faster-union-or-concat – Nullius

+7

http: // stackoverflow.com/questions/100196/net-listt-concat-vs-addrange -> Комментарий Грега: 'На самом деле, из-за отсроченного исполнения, использование Concat, скорее всего, будет быстрее, потому что оно позволяет избежать выделения объектов - Concat ничего не копирует, это просто создает связи между списками, поэтому при перечислении и вы достигаете конца одного, это прозрачно приводит вас к началу следующего! «Это моя точка зрения. – J4N

2

ли следующий код работы для вашей проблемы? Я использовал foreach с немного linq внутри, чтобы сделать объединение списков и предположил, что люди равны, если их имена совпадают, и, кажется, печатает ожидаемые значения при запуске. Resharper не предлагает никаких предложений по преобразованию foreach в linq, поэтому это, вероятно, так же хорошо, как и для этого.

public class Person 
{ 
    public string Name { get; set; } 
    public int Value { get; set; } 
    public int Change { get; set; } 

    public Person(string name, int value) 
    { 
     Name = name; 
     Value = value; 
     Change = 0; 
    } 
} 


class Program 
{ 
    static void Main(string[] args) 
    { 
     List<Person> list1 = new List<Person> 
           { 
           new Person("a", 1), 
           new Person("b", 2), 
           new Person("c", 3), 
           new Person("d", 4) 
           }; 
     List<Person> list2 = new List<Person> 
           { 
           new Person("a", 4), 
           new Person("b", 5), 
           new Person("e", 6), 
           new Person("f", 7) 
           }; 

     List<Person> list3 = list2.ToList(); 

     foreach (var person in list1) 
     { 
     var existingPerson = list3.FirstOrDefault(x => x.Name == person.Name); 
     if (existingPerson != null) 
     { 
      existingPerson.Change = existingPerson.Value - person.Value; 
     } 
     else 
     { 
      list3.Add(person); 
     } 
     } 

     foreach (var person in list3) 
     { 
     Console.WriteLine("{0} {1} {2} ", person.Name,person.Value,person.Change); 
     } 
     Console.Read(); 
    } 
} 
6

Это Linq

var mergedList = list1.Union(list2).ToList(); 

Это Normaly (AddRange)

var mergedList=new List<Person>(); 
mergeList.AddRange(list1); 
mergeList.AddRange(list2); 

Это Normaly (Foreach)

var mergedList=new List<Person>(); 

foreach(var item in list1) 
{ 
    mergedList.Add(item); 
} 
foreach(var item in list2) 
{ 
    mergedList.Add(item); 
} 

Это Normaly (Foreach-Dublice)

var mergedList=new List<Person>(); 

foreach(var item in list1) 
{ 
    mergedList.Add(item); 
} 
foreach(var item in list2) 
{ 
    if(!mergedList.Contains(item)) 
    { 
    mergedList.Add(item); 
    } 
} 
Смежные вопросы