Лучшая производительность при обновлении объектов с помощью linq

2

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

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

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

Желаемый результат: Alfa total = 2, Beta total = 4, Gamma total = 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? UPDATE из левого соединения, используя значение ISNULL (val, 0) для значения настройки.

Теги:
optimization
linq
performance
linq-to-objects

3 ответа

6
Лучший ответ

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

EDIT: здесь изменен код с помощью 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
    Спасибо за быстрый ответ! Недавно я начал смотреть на linq и попал в попытки эмулировать t-sql в коде. Ваш непроверенный код измеряется в мс, поэтому я получил повышение производительности, о котором я просил.
  • 0
    Рад, что это помогло ... хотя я не уверен, почему этот Q & A теперь вики сообщества :(
Показать ещё 2 комментария
0

Я знаю, что опаздываю, но я подумал, что кто-то оценит более четкий ответ ниже, который обрабатывает несколько записей для каждого поиска в 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

Если 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;
  });

Ещё вопросы

Сообщество Overcoder
Наверх
Меню