Performant работает с разделом SUM OVER в LINQ

Я пытаюсь найти лучший способ вычислить раздел промежуточной суммы с помощью самостоятельной коллекции с использованием LINQ.

Запрос ниже является несколько простым примером того, что я преследую. Выходными данными являются RowNumber, RowType и сумма всех предыдущих RowValues ​​в RowType текущей строки.

DECLARE @T TABLE (RowNumber INT, RowType INT, RowValue INT) 

INSERT @T VALUES (1,1,1),(2,1,1),(3,1,1),(4,1,1),(5,1,1),(6,2,1),(7,2,1),(8,2,1),(9,2,1),(10,2,1) 

;WITH Data AS(SELECT RowNumber, RowType,RowValue FROM @T)

SELECT
    This.RowNumber,
    This.RowType,
    RunningValue = COALESCE(This.RowValue + SUM(Prior.RowValue),This.RowValue)
FROM
    Data This
    LEFT OUTER JOIN Data Prior ON Prior.RowNumber <  This.RowNumber AND Prior.RowType = This.RowType
GROUP BY
    This.RowNumber,
    This.RowType,
    This.RowValue
/* OR
SELECT
    This.RowNumber,
    This.RowType,
    RunningValue = SUM(RowValue) OVER (PARTITION BY RowType ORDER BY RowNUmber)
FROM
    Data This
*/

Теперь моя не рабочая попытка.

var joinedWithPreviousSums = allRows.Join(
    allRows,
    previousRows => new {previousRows.RowNumber, previousRows.RowType, previousRows.RowValue}, 
    row=> new { row.RowNumber, row.RowType, row.RowValue}, 
    (previousRows, row) => new { row.RowNumber, row.RowType, row.RowValue })
    .Where(previousRows.RowType == row.RowType && previousRows.RowNumber < row.RowNumber)
    .Select(row.RowNumber, row.RowType,RunningValue = Sum(previousRows.Value) + row.RowValue)).ToList()

Конечно, последние две строки выше являются мусором и пытаются проиллюстрировать мою желаемую проекцию, намекая на то, что у меня недостаточно знаний о сложных сложных проекциях LINQ.

Я читал, где некоторые варианты приведенного ниже утверждения могут работать и могут быть работоспособными, однако, есть ли способ достичь аналогичных результатов, не принося результатов?

int s = 0;
var subgroup  = people.OrderBy(x => x.Amount)
                      .TakeWhile(x => (s += x.Amount) < 1000)
                      .ToList(); 

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

namespace ConsoleApplication1
{
    class Program
    {
        delegate string CreateGroupingDelegate(int i);

        static void Main(string[] args)
        {
            List <TestClass> list = new List<TestClass>() 
               {
                    new TestClass(1, 1, 1),
                    new TestClass(2, 2, 5), 
                    new TestClass(3, 1, 1 ),
                    new TestClass(4, 2, 5),
                    new TestClass(5, 1, 1),
                    new TestClass(6, 2, 5)
            };
            int running_total = 0;

            var result_set = list.Select(x => new { x.RowNumber, x.RowType, running_total = (running_total = running_total + x.RowValue) }).ToList();


            foreach (var v in result_set)
            {
                Console.WriteLine("list element: {0}, total so far: {1}",
                    v.RowNumber,
                    v.running_total);
            }

            Console.ReadLine();
        }
    }

    public class TestClass
    {
        public TestClass(int rowNumber, int rowType, int rowValue)
        {
            RowNumber = rowNumber;
            RowType = rowType;
            RowValue = rowValue;
        }

        public int RowNumber { get; set; }
        public int RowType { get; set; }
        public int RowValue { get; set; }
    }

}

Всего 2 ответа


Ваш ответ может быть значительно упрощен, но даже тогда он плохо масштабируется, так как он должен пройти через Where для каждой строки, чтобы вычислить каждую строку, поэтому O ( list.Count^2 ).

Вот более простая версия, которая сохраняет первоначальный порядок:

var result = list.Select(item => new {
    RowType = item.RowType,
    RowValue = list.Where(prior => prior.RowNumber <= item.RowNumber && prior.RowType == item.RowType).Sum(prior => prior.RowValue)
});

Вы можете просмотреть list один раз, если хотите отсортировать. (Если вы знаете, что порядок правильный или можете использовать более простую сортировку, вы можете удалить или заменить OrderBy / ThenBy .)

var ans = list.OrderBy(x => x.RowType)
              .ThenBy(x => x.RowNumber)
              .Scan(first => new { first.RowType, first.RowValue },
                    (res, cur) => res.RowType == cur.RowType ? new { res.RowType, RowValue = res.RowValue + cur.RowValue }
                                                             : new { cur.RowType, cur.RowValue }
              );

Этот ответ использует метод расширения, подобный Aggregate , но возвращает промежуточные результаты, основанные на операторе сканирования APL:

// TRes seedFn(T FirstValue)
// TRes combineFn(TRes PrevResult, T CurValue)
public static IEnumerable<TRes> Scan<T, TRes>(this IEnumerable<T> src, Func<T, TRes> seedFn, Func<TRes, T, TRes> combineFn) {
    using (var srce = src.GetEnumerator()) {
        if (srce.MoveNext()) {
            var prev = seedFn(srce.Current);

            while (srce.MoveNext()) {
                yield return prev;
                prev = combineFn(prev, srce.Current);
            }
            yield return prev;
        }
    }
}

Мои глаза были застеклены, увидев это. Ответ на мой длинный вопрос после 6 часов черепа кажется таким же простым, как этот. Спасибо @NetMage за указание на SelectMany, который мне не хватало.

var result = list.SelectMany(item => list.Where(x => x.RowNumber <= item.RowNumber && x.RowType == item.RowType)
.GroupBy(g => g.RowType)
.Select(p => new
{
    RowType = p.Max(s => s.RowType),
    RowValue = p.Sum(s => s.RowValue)
}));

Есть идеи?

10000