Лучшая производительность при обновлении объектов с помощью linq - PullRequest
2 голосов
/ 07 ноября 2008

У меня есть два списка пользовательских объектов, и я хочу обновить поле для всех объектов в одном списке, если в другом списке есть объект, который совпадает с другой парой полей.

Этот код лучше объясняет проблему и дает желаемые результаты. Однако для больших списков 20 КБ и 20 КБ с соответствующими объектами это занимает значительное время (31 с). Я могу улучшить это на ~ 50%, используя общий метод поиска списков (предиката).

using System;
using System.Linq;
using System.Linq.Expressions;
using System.Collections.Generic;
namespace ExperimentFW3
{
    public class PropValue
    {
        public string Name;
        public decimal Val;
        public decimal Total;
    }
    public class Adjustment
    {
        public string PropName;
        public decimal AdjVal;
    }
    class Program
    {
        static List<PropValue> propList;
        static List<Adjustment> adjList;

        public static void Main()
        {
            propList = new List<PropValue>{
                new PropValue{Name = "Alfa", Val=2.1M},
                new PropValue{Name = "Beta", Val=1.0M},
                new PropValue{Name = "Gamma", Val=8.0M}
            };
            adjList = new List<Adjustment>{
                new Adjustment{PropName = "Alfa", AdjVal=-0.1M},
                new Adjustment{PropName = "Beta", AdjVal=3M}
            };

            foreach (var p in propList)
            {
                Adjustment a = adjList.SingleOrDefault(
                    av => av.PropName.Equals(p.Name)
                    );
                if (a != null)
                    p.Total = p.Val + a.AdjVal;
                else
                    p.Total = p.Val;
            }
        }
    }
}

Желаемый результат: Альфа-итог = 2, Бета-итог = 4, Гамма-итог = 8

Но мне интересно, возможно ли это сделать еще быстрее? Внутреннее объединение двух списков занимает очень мало времени, даже если в результирующем наборе зацикливается более 20 тыс. Элементов.

var joined = from p in propList
             join a in adjList on p.Name equals a.PropName
             select new { p.Name, p.Val, p.Total, a.AdjVal };

Итак, мой вопрос: возможно ли сделать что-то, как я бы сделал с T-SQL? ОБНОВЛЕНИЕ из левого соединения с использованием ISNULL (val, 0) для значения корректировки.

Ответы [ 3 ]

6 голосов
/ 07 ноября 2008

Это объединение должно быть достаточно быстрым, поскольку сначала будет проходить цикл по всем adjList для создания поиска, затем для каждого элемента в propList он будет просто использовать поиск. Это быстрее, чем ваш метод O (N * M) в большем коде - хотя это можно легко исправить, вызвав ToLookup (или ToDictionary, поскольку вам нужен только один значение) на adjList перед циклом.

РЕДАКТИРОВАТЬ: Вот модифицированный код с использованием ToDictionary. Не проверяйте, заметьте ...

var adjDictionary = adjList.ToDictionary(av => av.PropName);
foreach (var p in propList)
{
    Adjustment a;
    if (adjDictionary.TryGetValue(p.Name, out a))
    {
        p.Total = p.Val + a.AdjVal;
    }
    else
    {
        p.Total = p.Val;
    }
}
0 голосов
/ 19 января 2010

Я знаю, что опоздал с публикацией этого сообщения, но я подумал, что кто-то оценил бы более понятный короткий ответ, который обрабатывает несколько записей за поиск в adjList. Создание LookUp позволит быстро выполнять поиск по нескольким элементам и вернет пустой список, если в LookUp нет записей.

var adjLookUp = adjList.ToLookUp(a => a.PropName);
foreach (var p in propList) 
    p.Total = p.Val + adjLookUp[p.Name].Sum(a => a.AdjVal);
0 голосов
/ 07 ноября 2008

Если в adjList могут быть повторяющиеся имена, вы должны сгруппировать элементы, прежде чем отправлять их в словарь.

Dictionary<string, decimal> adjDictionary = adjList
  .GroupBy(a => a.PropName)
  .ToDictionary(g => g.Key, g => g.Sum(a => a.AdjVal))

propList.ForEach(p => 
  {
    decimal a;
    adjDictionary.TryGetValue(p.Name, out a);
    p.Total = p.Val + a;
  });
...