programing

LINQ Aggregate 알고리즘 설명

nasanasas 2020. 9. 30. 10:57
반응형

LINQ Aggregate 알고리즘 설명


이것은 절름발이로 들릴 수 있지만에 대한 정말 좋은 설명을 찾을 수 없었습니다 Aggregate.

좋음은 짧고 설명 적이며 포괄적이고 작고 명확한 예를 의미합니다.


이해하기 가장 쉬운 정의 Aggregate는 이전에 수행 된 작업을 고려하여 목록의 각 요소에 대해 작업을 수행한다는 것입니다. 즉, 첫 번째 및 두 번째 요소에 대한 작업을 수행하고 결과를 전달합니다. 그런 다음 이전 결과와 세 번째 요소에 대해 작동하고 계속 진행됩니다. 기타

예 1. 숫자 합산

var nums = new[]{1,2,3,4};
var sum = nums.Aggregate( (a,b) => a + b);
Console.WriteLine(sum); // output: 10 (1+2+3+4)

이 추가 1하고 2만드는 3. 그런 다음 3(이전 결과) 및 3(순서대로 다음 요소)를 추가하여 6. 그런 다음 추가 64만들기 위해 10.

예 2. 문자열 배열에서 csv 만들기

var chars = new []{"a","b","c", "d"};
var csv = chars.Aggregate( (a,b) => a + ',' + b);
Console.WriteLine(csv); // Output a,b,c,d

이것은 거의 같은 방식으로 작동합니다. CONCATENATE a쉼표를하고 b있도록 a,b. 그런 다음 연결합니다 a,b쉼표와 c메이크업에 a,b,c. 등등.

예 3. 시드를 사용하여 숫자 곱하기

완전성을 위해 시드 값을 사용 하는 과부하Aggregate있습니다.

var multipliers = new []{10,20,30,40};
var multiplied = multipliers.Aggregate(5, (a,b) => a * b);
Console.WriteLine(multiplied); //Output 1200000 ((((5*10)*20)*30)*40)

위의 예와 매우 유사하게 이것은 값으로 시작 하여 결과를 제공하는 5시퀀스의 첫 번째 요소를 곱합니다 . 이 결과는 이월되고 시퀀스의 다음 숫자를 곱하여 결과를 제공합니다 . 이것은 시퀀스의 나머지 2 개 요소를 통해 계속됩니다.1050201000

라이브 예제 : http://rextester.com/ZXZ64749
문서 : http://msdn.microsoft.com/en-us/library/bb548651.aspx


추가

위의 예 2에서는 문자열 연결을 사용하여 쉼표로 구분 된 값 목록을 만듭니다. 이것은 Aggregate이 답변의 의도가 사용 된 것을 설명하는 단순한 방법 입니다. 그러나이 기술을 사용하여 실제로 많은 양의 쉼표로 구분 된 데이터를 생성하는 경우를 사용하는 것이 더 적절할 수 있으며 StringBuilder, Aggregate이는 시드 된 오버로드를 사용하여 StringBuilder.

var chars = new []{"a","b","c", "d"};
var csv = chars.Aggregate(new StringBuilder(), (a,b) => {
    if(a.Length>0)
        a.Append(",");
    a.Append(b);
    return a;
});
Console.WriteLine(csv);

업데이트 된 예 : http://rextester.com/YZCVXV6464


그것은 부분적으로 당신이 말하는 과부하에 달려 있지만 기본 아이디어는 다음과 같습니다.

  • "현재 값"으로 시드로 시작
  • 시퀀스를 반복합니다. 시퀀스의 각 값에 대해 :
    • 변환 할 수있는 사용자 지정 기능 적용 (currentValue, sequenceValue)으로를(nextValue)
    • 세트 currentValue = nextValue
  • 최종 반환 currentValue

Aggregate내 Edulinq 시리즈게시물이 유용 할 수 있습니다. 여기에는보다 자세한 설명 (다양한 과부하 포함) 및 구현이 포함되어 있습니다.

한 가지 간단한 예는 다음 Aggregate의 대안으로 사용 하는 것입니다 Count.

// 0 is the seed, and for each item, we effectively increment the current value.
// In this case we can ignore "item" itself.
int count = sequence.Aggregate(0, (current, item) => current + 1);

또는 문자열 시퀀스에서 모든 문자열 길이를 합산 할 수도 있습니다.

int total = sequence.Aggregate(0, (current, item) => current + item.Length);

개인적으로 나는 거의 찾을 수없는 Aggregate유용한 -은 "맞춤형"집계 방법은 나를 위해 일반적으로 충분하다.


Super short Aggregate는 Haskell / ML / F #에서 폴드처럼 작동합니다.

약간 더 긴 .Max (), .Min (), .Sum (), .Average ()는 모두 시퀀스의 요소를 반복하고 각각의 집계 함수를 사용하여 집계합니다. .Aggregate ()는 개발자가 시작 상태 (일명 시드) 및 집계 함수를 지정할 수 있다는 점에서 일반화 된 집계 자입니다.

나는 당신이 짧은 설명을 요청한 것을 알고 있지만 다른 사람들이 몇 가지 짧은 답변을 주었을 때 나는 당신이 약간 더 긴 것에 관심이있을 것이라고 생각했습니다.

코드가 포함 된 긴 버전 foreach를 사용하여 한 번, .Aggregate를 사용하여 샘플 표준 편차 를 구현하는 방법을 보여주는 한 가지 방법이 무엇인지 보여줄 수 있습니다 . 참고 : 여기서 성능의 우선 순위를 지정하지 않았으므로 불필요하게 colleciton에 대해 여러 번 반복합니다.

먼저 2 차 거리의 합을 만드는 데 사용되는 도우미 함수 :

static double SumOfQuadraticDistance (double average, int value, double state)
{
    var diff = (value - average);
    return state + diff * diff;
}

그런 다음 ForEach를 사용하여 표준 편차를 샘플링합니다.

static double SampleStandardDeviation_ForEach (
    this IEnumerable<int> ints)
{
    var length = ints.Count ();
    if (length < 2)
    {
        return 0.0;
    }

    const double seed = 0.0;
    var average = ints.Average ();

    var state = seed;
    foreach (var value in ints)
    {
        state = SumOfQuadraticDistance (average, value, state);
    }
    var sumOfQuadraticDistance = state;

    return Math.Sqrt (sumOfQuadraticDistance / (length - 1));
}

그런 다음 .Aggregate 사용 :

static double SampleStandardDeviation_Aggregate (
    this IEnumerable<int> ints)
{
    var length = ints.Count ();
    if (length < 2)
    {
        return 0.0;
    }

    const double seed = 0.0;
    var average = ints.Average ();

    var sumOfQuadraticDistance = ints
        .Aggregate (
            seed,
            (state, value) => SumOfQuadraticDistance (average, value, state)
            );

    return Math.Sqrt (sumOfQuadraticDistance / (length - 1));
}

이러한 함수는 sumOfQuadraticDistance가 계산되는 방법을 제외하고 동일합니다.

var state = seed;
foreach (var value in ints)
{
    state = SumOfQuadraticDistance (average, value, state);
}
var sumOfQuadraticDistance = state;

대:

var sumOfQuadraticDistance = ints
    .Aggregate (
        seed,
        (state, value) => SumOfQuadraticDistance (average, value, state)
        );

따라서 .Aggregate가하는 일은이 집계 패턴을 캡슐화하고 .Aggregate의 구현이 다음과 같이 보일 것으로 예상하는 것입니다.

public static TAggregate Aggregate<TAggregate, TValue> (
    this IEnumerable<TValue> values,
    TAggregate seed,
    Func<TAggregate, TValue, TAggregate> aggregator
    )
{
    var state = seed;

    foreach (var value in values)
    {
        state = aggregator (state, value);
    }

    return state;
}

표준 편차 함수를 사용하면 다음과 같이 표시됩니다.

var ints = new[] {3, 1, 4, 1, 5, 9, 2, 6, 5, 4};
var average = ints.Average ();
var sampleStandardDeviation = ints.SampleStandardDeviation_Aggregate ();
var sampleStandardDeviation2 = ints.SampleStandardDeviation_ForEach ();

Console.WriteLine (average);
Console.WriteLine (sampleStandardDeviation);
Console.WriteLine (sampleStandardDeviation2);

IMHO

그렇다면 .Aggregate는 가독성에 도움이 될까요? 일반적으로 .Where, .Select, .OrderBy 등이 가독성에 크게 도움이된다고 생각하기 때문에 LINQ를 좋아합니다 (인라인 된 계층 형 .Selects를 피하는 경우). Aggregate는 완전성 이유로 Linq에 있어야하지만 개인적으로 .Aggregate는 잘 작성된 foreach에 비해 가독성을 추가한다고 확신하지 않습니다.


사진은 천 단어의 가치가 있습니다

알림 :
Func<X, Y, R>유형 의 결과를 반환하는 Xand 유형의 두 입력이있는 함수입니다 .YR

Enumerable.Aggregate에는 세 가지 오버로드가 있습니다.


과부하 1 :

A Aggregate<A>(IEnumerable<A> a, Func<A, A, A> f)

집계 1

예:

new[]{1,2,3,4}.Aggregate((x, y) => x + y);  // 10


이 오버로드는 간단하지만 다음과 같은 제한이 있습니다.

  • 시퀀스에는 하나 이상의 요소가 포함되어야합니다.
    그렇지 않으면 함수에서 InvalidOperationException.
  • 요소와 결과는 동일한 유형이어야합니다.



과부하 2 :

B Aggregate<A, B>(IEnumerable<A> a, B bIn, Func<B, A, B> f)

집계 2

예:

var hayStack = new[] {"straw", "needle", "straw", "straw", "needle"};
var nNeedles = hayStack.Aggregate(0, (n, e) => e == "needle" ? n+1 : n);  // 2


이 과부하는 더 일반적입니다.

  • 시드 값을 제공해야합니다 ( bIn).
  • 컬렉션은 비어있을 수 있습니다
    .이 경우 함수는 결과로 시드 값을 산출합니다.
  • 요소와 결과는 다른 유형을 가질 수 있습니다.



과부하 3 :

C Aggregate<A,B,C>(IEnumerable<A> a, B bIn, Func<B,A,B> f, Func<B,C> f2)


세 번째 과부하는 그다지 유용한 IMO가 아닙니다.
오버로드 2와 그 결과를 변환하는 함수를 사용하여 더 간결하게 작성할 수 있습니다.


삽화는 이 훌륭한 블로그 포스트 에서 채택되었습니다 .


Aggregate는 기본적으로 데이터를 그룹화하거나 합산하는 데 사용됩니다.

MSDN에 따르면 "집계 함수는 시퀀스에 누산기 함수를 적용합니다."

예 1 : 배열의 모든 숫자를 더합니다.

int[] numbers = new int[] { 1,2,3,4,5 };
int aggregatedValue = numbers.Aggregate((total, nextValue) => total + nextValue);

* 중요 : 기본적으로 초기 집계 값은 수집 순서에서 1 개 요소입니다. 즉 : 총 변수 초기 값은 기본적으로 1입니다.

변수 설명

total : 함수가 반환 한 합계 값 (집계 값)을 보유합니다.

nextValue : 배열 시퀀스의 다음 값입니다. 이 값은 집계 된 값, 즉 합계에 추가 된 것입니다.

예 2 : 배열의 모든 항목을 추가합니다. 또한 10부터 더하기 시작하도록 초기 누산기 값을 설정합니다.

int[] numbers = new int[] { 1,2,3,4,5 };
int aggregatedValue = numbers.Aggregate(10, (total, nextValue) => total + nextValue);

인수 설명 :

첫 번째 인수는 배열의 다음 값으로 덧셈을 시작하는 데 사용되는 초기 (시작 값, 즉 시드 값)입니다.

두 번째 인수는 2 int를 취하는 func 인 func입니다.

1. total : 계산 후 func에서 반환 한 합계 값 (집계 값) 이전과 동일하게 유지됩니다.

2.nextValue : : 배열 시퀀스의 다음 값입니다. 이 값은 집계 된 값, 즉 합계에 추가 된 것입니다.

또한이 코드를 디버깅하면 집계 작동 방식을 더 잘 이해할 수 있습니다.


Jamiec의 답변 에서 많은 것을 배웠습니다 .

CSV 문자열을 생성하는 것이 유일한 필요하다면 이것을 시도 할 수 있습니다.

var csv3 = string.Join(",",chars);

여기에 100 만 개의 문자열이있는 테스트가 있습니다.

0.28 seconds = Aggregate w/ String Builder 
0.30 seconds = String.Join 

소스 코드는 여기


이미 여기에있는 모든 훌륭한 답변 외에도 일련의 변형 단계를 통해 항목을 안내하는데도 사용했습니다.

변환이로 구현 된 경우 Func<T,T>에 여러 변환을 추가하고을 List<Func<T,T>>사용 Aggregate하여 T각 단계 통해 의 인스턴스를 살펴볼 수 있습니다.

더 구체적인 예

string을 가져 와서 프로그래밍 방식으로 빌드 할 수있는 일련의 텍스트 변환을 살펴 보고 싶습니다 .

var transformationPipeLine = new List<Func<string, string>>();
transformationPipeLine.Add((input) => input.Trim());
transformationPipeLine.Add((input) => input.Substring(1));
transformationPipeLine.Add((input) => input.Substring(0, input.Length - 1));
transformationPipeLine.Add((input) => input.ToUpper());

var text = "    cat   ";
var output = transformationPipeLine.Aggregate(text, (input, transform)=> transform(input));
Console.WriteLine(output);

이렇게하면 변환 체인이 생성됩니다. 선행 및 후행 공백 제거-> 첫 번째 문자 제거-> 마지막 문자 제거-> 대문자로 변환. 이 체인의 단계는 필요한 모든 종류의 변환 파이프 라인을 생성하기 위해 필요에 따라 추가, 제거 또는 재정렬 할 수 있습니다.

이 특정 파이프 라인의 최종 결과 " cat ""A".


그것이 무엇이든T수 있다는 것을 깨닫고 나면 이것은 매우 강력해질 수 있습니다 . 예를 들어 필터와 같은 이미지 변환에 사용할 수 있습니다 .BitMap


모두가 그의 설명을했습니다. 내 설명은 그와 같습니다.

Aggregate 메서드는 컬렉션의 각 항목에 함수를 적용합니다. 예를 들어, 컬렉션 {6, 2, 8, 3}과 함수 Add (연산자 +)가 수행하고 (((6 + 2) +8) +3) 19를 반환한다고 가정합니다.

var numbers = new List<int> { 6, 2, 8, 3 };
int sum = numbers.Aggregate(func: (result, item) => result + item);
// sum: (((6+2)+8)+3) = 19

이 예제에서는 람다 식 대신 Add라는 명명 된 메서드가 전달됩니다.

var numbers = new List<int> { 6, 2, 8, 3 };
int sum = numbers.Aggregate(func: Add);
// sum: (((6+2)+8)+3) = 19

private static int Add(int x, int y) { return x + y; }

짧고 필수적인 정의는 다음과 같을 수 있습니다. Linq Aggregate 확장 메서드는 목록의 요소에 적용되는 일종의 재귀 함수를 선언 할 수 있습니다. 피연산자는 두 개입니다. 목록에 표시되는 순서대로 요소, 한 번에 하나의 요소, 이전 재귀 반복의 결과 또는 아직 재귀가 아닌 경우 아무 것도 표시하지 않습니다.

이런 식으로 숫자의 계승을 계산하거나 문자열을 연결할 수 있습니다.


AggregateLinq Sorting과 같은 Fluent API 사용 대한 설명 입니다.

var list = new List<Student>();
var sorted = list
    .OrderBy(s => s.LastName)
    .ThenBy(s => s.FirstName)
    .ThenBy(s => s.Age)
    .ThenBy(s => s.Grading)
    .ThenBy(s => s.TotalCourses);

필드 집합을 취하는 정렬 함수를 구현하고 싶습니다 Aggregate. for 루프 대신 다음과 같이 사용 하는 것은 매우 쉽습니다 .

public static IOrderedEnumerable<Student> MySort(
    this List<Student> list,
    params Func<Student, object>[] fields)
{
    var firstField = fields.First();
    var otherFields = fields.Skip(1);

    var init = list.OrderBy(firstField);
    return otherFields.Skip(1).Aggregate(init, (resultList, current) => resultList.ThenBy(current));
}

그리고 다음과 같이 사용할 수 있습니다.

var sorted = list.MySort(
    s => s.LastName,
    s => s.FirstName,
    s => s.Age,
    s => s.Grading,
    s => s.TotalCourses);

다차원 정수 배열의 열 합계에 사용되는 집계

        int[][] nonMagicSquare =
        {
            new int[] {  3,  1,  7,  8 },
            new int[] {  2,  4, 16,  5 },
            new int[] { 11,  6, 12, 15 },
            new int[] {  9, 13, 10, 14 }
        };

        IEnumerable<int> rowSums = nonMagicSquare
            .Select(row => row.Sum());
        IEnumerable<int> colSums = nonMagicSquare
            .Aggregate(
                (priorSums, currentRow) =>
                    priorSums.Select((priorSum, index) => priorSum + currentRow[index]).ToArray()
                );

Select with index는 Aggregate 함수 내에서 일치하는 열을 합산하고 새 배열을 반환하는 데 사용됩니다. {3 + 2 = 5, 1 + 4 = 5, 7 + 16 = 23, 8 + 5 = 13}.

        Console.WriteLine("rowSums: " + string.Join(", ", rowSums)); // rowSums: 19, 27, 44, 46
        Console.WriteLine("colSums: " + string.Join(", ", colSums)); // colSums: 25, 24, 45, 42

그러나 누적 된 유형 (int)이 소스 유형 (bool)과 다르기 때문에 부울 배열의 참 수를 계산하는 것이 더 어렵습니다. 여기서 두 번째 과부하를 사용하려면 시드가 필요합니다.

        bool[][] booleanTable =
        {
            new bool[] { true, true, true, false },
            new bool[] { false, false, false, true },
            new bool[] { true, false, false, true },
            new bool[] { true, true, false, false }
        };

        IEnumerable<int> rowCounts = booleanTable
            .Select(row => row.Select(value => value ? 1 : 0).Sum());
        IEnumerable<int> seed = new int[booleanTable.First().Length];
        IEnumerable<int> colCounts = booleanTable
            .Aggregate(seed,
                (priorSums, currentRow) =>
                    priorSums.Select((priorSum, index) => priorSum + (currentRow[index] ? 1 : 0)).ToArray()
                );

        Console.WriteLine("rowCounts: " + string.Join(", ", rowCounts)); // rowCounts: 3, 1, 2, 2
        Console.WriteLine("colCounts: " + string.Join(", ", colCounts)); // colCounts: 3, 2, 1, 2

참고 URL : https://stackoverflow.com/questions/7105505/linq-aggregate-algorithm-explained

반응형