0

I've searched for a solution on SO but I couldn't find it, so I've made this new question.

I have 2 list of two different classes.

First class:

public class Class1
{
    public int C1Property1 {get;set;}
    public int C1Property2 {get;set;}
    public int C1Property3 {get;set;}
}

And second class:

public class Class2
{
    public int C2Property1 {get;set;}
    public int C2Property2 {get;set;}
    public int C2Property3 {get;set;}
}

And I have two list of those classes:

List<Class1> Class1List = new List<Class1>();
List<Class2> Class2List = new List<Class2>();

Now comes the hard part for me: Two of those properties in both classes have the same value, but with a different name: i.e. C1Property1 = C2Property1 and C1Property2 = C2Property2. The list Class1List has the property C1Property1 which is empty, and I need to populate it using the property from Class2List. I do this by using the following code:

foreach(var element1 in Class1List)
{
     foreach(var element2 in Class2List)
     {
         if(element2.C2Property2 == element1.C1Property2)
         {
             element1.C1Property1 = element2.C2Property1;
         }
     }
}

This solution works how I intent it, but is very ugly and I have 2 foreach-loops which can be slow (list can contain over 10 000 elements). In the example classes I wrote only 3 properties to show how it works, but originally it has ~20 properties each, and only 2 of them are the same. Can I do this faster and more efficient? Some LinQ perhaps? I can't show more code, sorry. I hope that you will understand what I'm asking. I need to take only one property from Class2List and place it on Class1List only when one of parameters in list are same.

In my second attempt i use something like that :

foreach (var element1 in Class1List)
{
    foreach (var element2 in Class2List.Where(element2 => element2.C2Property2 == element1.C1Property2 ))
    {
        element2.C2Property2 = element1.C1Property2;
        break;
    }
}

This should be faster but still look ugly

Aht
  • 583
  • 4
  • 25
  • 2
    I've edited your post so it's easier to read. As for your problem, I'm not sure how to make it more efficient. Even if you do use Linq to make it a one-liner, it will still do the foreach-loops behind the scenes if I'm not mistaken. But there are a lot of people here on SO with more knowledge than me, so perhaps someone else has a suggestion that works. @NSNoob That question combines two lists of different classes into one, while OP wants to set the property of one object-value in list1 to another object-value in list2 (and also compare two other properties for equality). – Kevin Cruijssen Dec 11 '15 at 12:04
  • @KevinCruijssen oh okay. thanks for explaining that kevin – NSNoob Dec 11 '15 at 12:06
  • Yea sorry for English I know that was hard to understand :) – Aht Dec 11 '15 at 12:16
  • Why don't you break after setting the property? Is it possible (and wanted) that there are more elements in Class2List that have an equal property and should change element1`s property? – René Vogt Dec 11 '15 at 12:25
  • Isn't the fastest solution to 1. sorting both lists, 2. Use a single `while` with `index1` and `index2` where you only increase the index which looses `list1.PropertyA.CompareTo(list2.Property2)`. Should give you a lot less iterations. Especially if the lists are similar. – jgauffin Dec 11 '15 at 12:53

2 Answers2

3

So here I have three options:

Using LINQ

Class1List.ForEach(element1 =>
{
    Class2 element2 = Class2List.FirstOrDefault(e2 => e2.C2Property2 == element1.C1Property2);
    if (element2 != null) element1.C1Property1 = element2.C2Property1;
});

This took my machine 4.58s for 20000 elements in each list. And though the code looks (to me) a little better, this is effectivly the same as your code.

Using a dictionary

Using a dictionary to access the Class2 elements via a hash is really effective:

Dictionary<int, Class2> dictionary =                Class2List.GroupBy(e2 => e2.C2Property2, e2 => e2).Select(elements => elements.First()).ToDictionary(e2 => e2.C2Property2, e2 => e2);

Class1List.ForEach(element1 =>
{
    if (dictionary.ContainsKey(element1.C1Property2))
        element1.C1Property1 = dictionary[element1.C1Property2].C2Property1;
});

This took my machine 0.00878s for 20000 elements in each list.

Parallel

If your data gets really really big, you might consider to use Parallel.ForEach

Dictionary<int, Class2> dictionary =
            Class2List.GroupBy(e2 => e2.C2Property2, e2 => e2).Select(elements => elements.First()).ToDictionary(e2 => e2.C2Property2, e2 => e2);

Parallel.ForEach(Class1List, element1 =>
{
    if (dictionary.ContainsKey(element1.C1Property2))
        element1.C1Property1 = dictionary[element1.C1Property2].C2Property1;
});

But with only 20000 elements in each list this took my machine even longer (0.0197s) than the non-parallel version.

René Vogt
  • 43,056
  • 14
  • 77
  • 99
1

It was a pretty interesting thing to figure out, but I think something like this might work:

Class1List.ForEach(c1 =>
    c1.C1Property1 = Class2List.Where(c2 => c2.C2Property2 == c1.C1Property2)
                               .Select(r => r.C2Property1)
                               .FirstOrDefault());

Here is a test class:

using System;
using System.Collections.Generic;
using System.Linq;

namespace SO_Test
{
    public class ObjectA
    {
        public int Property1 { get; set; }
        public int? Property2 { get; set; }

        public override string ToString()
        {
            return String.Format("({0}, {1})", Property1, Property2);
        }
    }

    public class ObjectB
    {
        public int Property1 { get; set; }
        public int? Property2 { get; set; }

        public override string ToString()
        {
            return String.Format("({0}, {1})", Property1, Property2);
        }
    }

    class Program
    {
        static void Main()
        {
            var listA = new List<ObjectA>
            { 
                new ObjectA { Property1 = 5,  Property2 = null }, 
                new ObjectA { Property1 = 16, Property2 = null }, 
                new ObjectA { Property1 = 9,  Property2 = null }, 
                new ObjectA { Property1 = 38, Property2 = null } 
            };
            var listB = new List<ObjectB>
            { 
                new ObjectB { Property1 = 5, Property2 = 1 }, 
                new ObjectB { Property1 = 9, Property2 = 2 }, 
                new ObjectB { Property1 = 16, Property2 = 3 } 
            };

            Console.WriteLine("BEFORE");
            Console.WriteLine("ListA: {0}", String.Join(", ", listA));
            Console.WriteLine("ListB: {0}", String.Join(", ", listB));

            listA.ForEach(a =>
                a.Property2 = listB.Where(b => b.Property1 == a.Property1)
                                   .Select(r => r.Property2)
                                   .FirstOrDefault());

            Console.WriteLine("AFTER");
            Console.WriteLine("ListA: {0}", String.Join(", ", listA));
            Console.WriteLine("ListB: {0}", String.Join(", ", listB));
            Console.ReadLine();
        }
    }
}

Output:

BEFORE
ListA: (5, ), (16, ), (9, ), (38, )
ListB: (5, 1), (9, 2), (16, 3)
AFTER
ListA: (5, 1), (16, 3), (9, 2), (38, )
ListB: (5, 1), (9, 2), (16, 3)
Kevin Cruijssen
  • 9,153
  • 9
  • 61
  • 135