简体   繁体   中英

How to compare 2 lists/dictionaries using LINQ (Find values in list1 that are greater than values in list2)

I went through quite a few stackoverflow threads, but I don't seem to find anything on this specific case. Consider 2 dictionaries containing values as it follows:

Dictionary<string, List<double>> someValues1 = new Dictionary<string, List<double>>();
Dictionary<string, List<double>> someValues2 = new Dictionary<string, List<double>>();

Now, the lists in each dictionary keep on filling up with new values at runtime and we are therefore in this case only interested in comparing the last value of each list cross dictionaries.
In other words, I am trying to do the following thing:

  • Filter out each dictionary to extract only the last value from each List
  • Check if any of the extracted values from someValues1 are greater than any of the values extracted from someValues2
  • Reshape the lot into some useful format such as Tuple<string, double, string, double> containing the dictionary information from the matching pairs.

I have tried the following thing so far, which I believe is going the right way, but I am having difficulty in restructuring the matches into a Tuple:

Dictionary<string, List<double>> someValues1  = new Dictionary<string, List<double>>();
Dictionary<string, List<double>> someValues2 = new Dictionary<string, List<double>>();

someValues1 .Add("A1", new List<double>() { 0.1, 1.23 });
someValues1 .Add("B1", new List<double>() { 0.1, 24.2 });
someValues1 .Add("C1", new List<double>() { 0.1, 1.783 });

someValues2.Add("A2", new List<double>() { 0.1, 0.1, 0.2 });
someValues2.Add("B2", new List<double>() { 0.1, 1.23 });
someValues2.Add("C2", new List<double>() { 0.1, 0.1, 5.63 });

var result= someValues1 
    .Select(item => item.Value.Last())            // Select last value from lists in someValues1 
    .Where(v => someValues2                       // Transform (extract matches v < v2)
        .Select(item2 => item2.Value.Last())      // Select last value from lists in someValues2 
        .Any(v2 => v2 > v)).ToList();

I would expect to get the following matches:

  • { "A1", 1.23, "C2", 5.63 } // 1.23 is < 5.63
  • { "C1", 1.783, "C2", 5.63 } // 1.783 is < 5.63

I believe this solves your problem.

var result = someValues2
    .SelectMany(e2 => someValues1
        .Where(e1 => e1.Value.Last() < e2.Value.Last())
        .Select(e1 => (e1.Key, e1.Value.Last(), e2.Key, e2.Value.Last())))
    .ToList();

EDIT: As properly pointed out by Peter Csala in his answer, making multiple calls to Last() can degrade performance.

However, in this scenario, where we're dealing with a List , the call should be O(1) as we can see in the source code.

public static TSource Last<TSource>(this IEnumerable<TSource> source) {
    if (source == null) throw Error.ArgumentNull("source");
    IList<TSource> list = source as IList<TSource>;
    if (list != null) {
        int count = list.Count;
        if (count > 0) return list[count - 1];
    }
    else {
        using (IEnumerator<TSource> e = source.GetEnumerator()) {
            if (e.MoveNext()) {
                TSource result;
                do {
                    result = e.Current;
                } while (e.MoveNext());
                return result;
            }
        }
    }
    throw Error.NoElements();
}

https://github.com/Microsoft/referencesource/blob/master/System.Core/System/Linq/Enumerable.cs#L1088

As Thiago Cordeiro has suggested SelectMany can be handy.

But as you can see the Last operator has been called multiple times (in the Where and the Select clauses) which might result multiple iterations.

You can optimize this by using let keyword:

var result = 
    from left in someValues1
        let leftKey = left.Key
        let leftValue = left.Value.Last()
    from right in someValues2
        let rightKey = right.Key
        let rightValue = right.Value.Last()
    where leftValue < rightValue
    select (leftKey, leftValue, rightKey, rightValue);

You can also take advantage of the collection initializer to populate your Dictionaries easily:

var someValues1 = new Dictionary<string, List<double>>
{
    { "A1", new List<double> {0.1, 1.23} },
    { "B1", new List<double> {0.1, 24.2} },
    { "C1", new List<double> {0.1, 1.783} }
};

var someValues2 = new Dictionary<string, List<double>>
{
    { "A2", new List<double> {0.1, 0.1, 0.2} },
    { "B2", new List<double> {0.1, 1.23} },
    { "C2", new List<double> {0.1, 0.1, 5.63} }
};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM