2014-01-29 3 views
2

Я новичок в C#.Как обращаться с дублирующимися ключами при соединении двух списков?

У меня есть следующая структура.

struct Foo 
{ 
    string key; 
    Bar values; 
} 

У меня есть два списка Foo, L1 и L2 одинакового размера, оба содержат одинаковый набор ключей.

Мне нужно объединить соответствующие экземпляры Foo в L1 и L2.

Foo Merge(Foo f1, Foo f2) 
{ 
    // merge f1 and f2. 
    return result. 
} 

Для этого я написал следующие данные.

resultList = L1.Join(L2, f1 => f1.key, f2 => f2.key, (f1, f2) => Merge(f1, f2) 
         ).ToList()) 

Моя проблема в том, что мой ключ не уникален. У меня есть n количество элементов в L1 с одним и тем же ключом (например, «key1») (которые также появляются в L2 где-то). Таким образом, вышеупомянутый оператор соединения выбирает n совпадающих записей из L2 для каждого «ключа1» из L1, и я получаю n * n элементов с ключом «key1» в результате, где я хочу только n. (Итак, это своего рода кросспродукт для тех наборов элементов).

Я хочу использовать Join и по-прежнему выбирать элемент из L1 с помощью клавиши «key1» и принудительно использовать Linq для использования первого доступного «неиспользуемого» элемента «key1» из L2. Это возможно? Соединиться с плохой идеей здесь?

(Кроме того, я хочу сохранить порядок ключей, как в L1. Я пытался обработать все элементы с такими ключами до объединения и удалить эти записи из L1 и L2. Это нарушило порядок ключей и это выглядело уродливо).

Я ищу решение без каких-либо явных для циклов.

enter image description here

+0

все ключи в первом списке находятся во втором списке, но в другом порядке, или это еще сложнее? –

+0

Это еще сложнее. Двойные ключи присутствуют несколько раз. – PermanentGuest

+0

Да, но если у вас есть 3 раза «3» в качестве ключа в первом списке, у вас будет 3 раза «3» в качестве второго во втором списке (но не в том же месте)? –

ответ

0

Наконец, я адаптировал ответ Рафаэля, как показано ниже.

public class FooComparer : IEqualityComparer<Foo> 
{ 
     public bool Equals(Foo o1, Foo o2) 
     { 
      return o1.key == o2.key; 
     } 

     public int GetHashCode(Foo obj) 
     { 
      return obj.key.GetHashCode(); 
     } 
} 


resultList = L1.Join(L2.Select(m => m).Distinct(new FooComparer()).ToList(), f1 => f1.key, f2 => f2.key, (f1, f2) => Merge(f1, f2) 
         ).ToList()); 

Краткое объяснение:

L2.Select(m => m).Distinct(new FooComparer()).ToList() 

создает новый список, удалив дубликаты ключей из L2. Присоедините L1 к этому новому списку, чтобы получить требуемый результат.

0

Вы можете использовать союз для удаления дублированных ключей. Документация на http://msdn.microsoft.com/en-us/library/bb341731.aspx

List<int> list1 = new List<int> { 1, 12, 12, 5}; 
List<int> list2 = new List<int> { 12, 5, 7, 9, 1 }; 
List<int> ulist = list1.Union(list2).ToList(); 

Пример взят из: how to merge 2 List<T> with removing duplicate values in C#

Или вы можете использовать Concat сливаться список различных типов (Сохраняя все ключи). Смотрите документацию ей: http://msdn.microsoft.com/en-us/library/bb302894(v=vs.110).aspx

var MyCombinedList = Collection1.Concat(Collection2) 
           .Concat(Collection3) 
           .ToList(); 

пример, взятый из того же вопроса: Merge two (or more) lists into one, in C# .NET

+0

Не знаю, удалили бы это дублированные ключи. Но использование Союза. См. Http://msdn.microsoft.com/en-us/library/bb341731.aspx –

+0

Я не хочу добавлять один список за другим. Мне нужно найти соответствующие записи в двух списках и выполнить некоторую операцию над ними. Это мое предварительное требование. Я могу удалить свои повторяющиеся записи до этой операции и обработать их отдельно. Но, что теряет мой заказ в L1. – PermanentGuest

+0

Пример для объединения списков и удаления дублированных записей. Пример для добавления списков. –

1

I need to find the corresponding entries in two lists and do some operation on them. That is my preliminary requirement.

Для этого вы можете сделать что-то подобное.

var z=S1.Select(i=>i.Key).Tolist(); //make a list of all keys in S1 
List<Foo> result=new List<Foo>(); 
foreach(var item in z) // Compare with S2 using keys in z 
{ 
    var x=item.Where(i=>i.Key==item.Key) 
    result.Add(x); 
} 

Это вы что искали?

+0

См. Мой ответ на приведенный выше ответ. Я не хочу ставить два списка «один за другим». Я хочу поставить их «бок о бок» и сделать что-то с соответствующими элементами. – PermanentGuest

+0

Помогает ли отредактированный ответ? – ElectricRouge

+0

В идеале я ищу решение без циклов. С циклом for я могу просто перебирать записи в первом списке, если я найду запись в S1 с более чем одним соответствующим элементом в S2, тогда я мог бы как-то справиться с этим. Что-то возможно без цикла? – PermanentGuest

2

Ваш комментарий к ElectricRouge ответ, вы могли бы сделать что-то вроде

var z = list1.Join(list2.GroupBy(m => m.Id), 
        m => m.Id, 
        g => g.Key, 
        (l1, l2) => new{l1, l2}); 

это даст вам список всех ключей в l1, и соответствующие сгруппированных ключи в l2.

Не уверен, что это действительно читаемо.

+0

Я понимаю, что вы имеете в виду. И я думаю, этого должно быть достаточно. Я пытаюсь это сделать и пытаюсь решить некоторые проблемы с компилятором о совместимости выражения «list2.GroupBy (m => m.Id)» и «list2». Я скоро сообщу вам. – PermanentGuest

+0

Я не мог заставить это работать, потому что GroupBy возвращает некоторые списки IGrouping и не может извлечь из этого мои структуры. Итак, я приспособил вашу идею к использованию Distinct() с помощью специализированного компаратора.Пожалуйста, см. Мой ответ. – PermanentGuest

1

Я хочу использовать Join и по-прежнему выбирать элемент из L1 с помощью клавиши «key1» и принудительно использовать Linq для использования первого доступного «неиспользуемого» элемента «key1» из L2. Это возможно?

При объединении элементов из двух списков вы хотите выбрать первый элемент во втором списке с тем же ключом, что и элемент в первом списке. (Раньше я интерпретировал ваш вопрос по-другому, и решение этой проблемы можно найти в истории изменений этого ответа.)

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

var dictionary2 = list2 
    .GroupBy(foo => foo.Key) 
    .ToDictionary(group => group.Key, group => group.First()); 

использование First выражает требование, что вы хотите выбрать первый элемент во втором списке, имеющих один и тот же ключ.

Объединенный список теперь создается с помощью проекции по первому списку:

var mergedList = list1.Select(
    foo => Merge(
    foo, 
    dictionary2[foo.Key] 
) 
); 

При использовании foreach итерировать mergedList или ToList() желаемый результат будет вычислен.

+0

Благодарим вас за ответ. Это интересный подход, но немного сложный. Фактически, индекс является частью «виртуального ключа» только для первого списка. Для второго списка это не имеет значения. Пожалуйста, см. Мой собственный ответ (адаптированный из ответа Рафаэля), где второй список был сжат, чтобы избавиться от дубликатов. – PermanentGuest

+0

@PermanentGuest: Хорошо, что вы решили свою проблему. По-видимому, я неправильно понял термин _unused_ в _use первый доступный «неиспользуемый» элемент «key1» из L2_. На самом деле, я до сих пор не понимаю, что вы подразумеваете под _unused_. –

+0

Да, это предложение было немного запутанным. Простите за это. Я хотел сказать, что хочу иметь все объекты из списка1 в их порядке и «соответствующие» из списка2. Теперь, если есть повторяющиеся ключи, то как определить «соответствующий» для них? Итак, я приготовил это определение. Рассмотрим только дубликаты ключей. Возьмите первый из таких ключей из списка1 и возьмите первый доступный объект из списка2 с этим ключом. Когда вы берете следующий такой объект из списка1, не принимайте объект, выбранный на последнем шаге (очевидно), вместо этого берете следующий (следовательно, «неиспользуемый»). – PermanentGuest

Смежные вопросы