2

I'm new to C#.

I have the following struct.

struct Foo
{
    string key;
    Bar values;
}

I have two lists of Foo, L1 and L2 of equal size both contain same set of keys.

I have to merge the corresponding Foo instances in L1 and L2.

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

I wrote the following to achieve this.

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

My problem is that my key is not unique. I have n number of elements in L1 with the same key (say "key1") (which are also appearing in L2 somewhere). So, the above join statement selects n matching entries from L2 for each "key1" from L1 and I get n*n elements with key "key1" in the result where I want only n. (So, this is kind of crossproduct for those set of elements).

I want to use Join and still select an element from L1 with "key1" and force the Linq to use the first available 'unused' "key1" element from L2. Is this possible? Is join a bad idea here?

(Also, the I want to preserve the order of the keys as in L1. I tried to handle all elements with such keys before the join and removed those entries from L1 and L2. This disturbed the order of the keys and it looked ugly).

I'm looking for a solution without any explicit for loops.

enter image description here

PermanentGuest
  • 5,213
  • 2
  • 27
  • 36

5 Answers5

2

From your comment to ElectricRouge answer, you could do something like

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

this would give you a list of all keys in l1, and the corresponding grouped keys in l2.

Not sure it's really readable.

Raphaël Althaus
  • 59,727
  • 6
  • 96
  • 122
  • I see what you mean. And I think this should be sufficient. I'm trying it out now and trying to resolve some compiler problems about the compatibility of the expression "list2.GroupBy(m => m.Id)" and "list2". I will let you know soon,. – PermanentGuest Jan 29 '14 at 12:52
  • I couldn't get this working because the GroupBy returns some IGrouping lists and couldn't extract my structs from that. So, I adapted your idea to use Distinct() with a custom comparator. Please see my answer. – PermanentGuest Jan 29 '14 at 13:54
1

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

For this you can do something like this.

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

Is this what you are looking for?

ElectricRouge
  • 1,239
  • 3
  • 22
  • 33
  • Please see my answer to the above answer. I don't want to put the two lists 'one after the other'. I want to put them 'side by side' and do something with the matching elements. – PermanentGuest Jan 29 '14 at 11:41
  • Ideally I'm looking for a solution without for loops. With for loop, I can simply iterate thru the entries in the first list, if I find an entry in S1 with more than one corresponding item in S2, then I could somehow handle that. Is something possible without for loop? – PermanentGuest Jan 29 '14 at 12:09
1

I want to use Join and still select an element from L1 with "key1" and force the Linq to use the first available 'unused' "key1" element from L2. Is this possible?

When combining elements from the two lists you want to pick the first element in the second list having the same key as the element in the first list. (Previously, I interpreted you question differently, and a solution to this different problem is available in the edit history of this answer.)

For quick access to the desired values in the second list a dictionary is created providing lookup from keys to the desired value from the second list:

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

The use of First expresses the requirement that you want to pick the first element in the second list having the same key.

The merged list is now created by using projection over the first list:

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

When you use foreach to iterate mergedList or ToList() the desired result will be computed.

Martin Liversage
  • 104,481
  • 22
  • 209
  • 256
  • Thank you for your answer. This is an interesting approach, but bit complicated. In fact, the index is part of the 'virtual key' only for the first list. For the second list, it doesn't matter. Please see my own answer (adapted from Raphaël's answer) where the second list was compressed to get rid of the duplicates. – PermanentGuest Jan 29 '14 at 14:06
  • @PermanentGuest: Good that you solved your problem. Apparently, I misunderstood the term _unused_ in _use the first available 'unused' "key1" element from L2_. Actually, I still do not understand what you mean by _unused_. – Martin Liversage Jan 29 '14 at 14:11
  • yes, that sentence was bit confusing. Sorry for that. What I wanted to say was, I want to have all objects from list1 in their order and 'corresponding' ones from list2. Now, if there are duplicate keys, then how to define 'corresponding' for them? So, I cooked up that definition. Consider only the duplicate keys. Take the first of such key from list1 and take first available object from list2 with that key. When taking the next such object from list1, don't take the object chosen in the last step(obvious), instead take the next one(hence 'unused'). – PermanentGuest Jan 29 '14 at 14:24
  • @PermanentGuest: I edited my answer to provide a solution that hopefully is based on a correct understanding of you problem. – Martin Liversage Jan 29 '14 at 15:53
  • Thanks, Martin. This should work. Please see the answers from Raphael and mine. They are basically of the similar idea (compressing list2 to avoid duplicates). – PermanentGuest Jan 29 '14 at 16:26
0

You could use Union to remove the duplicated keys. Documentation at 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();

Example taken from : how to merge 2 List<T> with removing duplicate values in C#

Or you can use Concat to merge a list of different types (Keeping all keys). See the documentation her : http://msdn.microsoft.com/en-us/library/bb302894(v=vs.110).aspx

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

Example taken from same question : Merge two (or more) lists into one, in C# .NET

Community
  • 1
  • 1
  • Don't know if this would remove the duplicated keys. But using Union would. See http://msdn.microsoft.com/en-us/library/bb341731.aspx – Bjarke Brask Rubeksen Jan 29 '14 at 11:36
  • I don't want to append one list after the other. I need to find the corresponding entries in two lists and do some operation on them. That is my preliminary requirement. I could remove my duplicate entries before that operation and process them seperately. But, that loses my order in L1. – PermanentGuest Jan 29 '14 at 11:39
  • Example one it for merging lists and removing duplicated entries. Example to is for appending lists. – Bjarke Brask Rubeksen Jan 30 '14 at 10:06
0

Finally I adapted Raphaël's answer as below.

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

Short explanation:

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

creates a new list by removing the duplicate keys from L2. Join L1 with this new list to get the required result.

PermanentGuest
  • 5,213
  • 2
  • 27
  • 36