keisoft 2025. 4. 9. 12:39



예를 들어, Category 속성을 가진 Item 클래스의 리스트가 있을 때, Category 값이 변경되는 지점의 인덱스를 찾는 방법은 다음과 같습니다:

using System;
using System.Collections.Generic;
using System.Linq;

public class Item
{
    public string Category { get; set; }
    public string Name { get; set; }
}

public class Program
{
    public static void Main()
    {
        List<Item> items = new List<Item>
        {
            new Item { Category = "A", Name = "Apple" },
            new Item { Category = "A", Name = "Avocado" },
            new Item { Category = "B", Name = "Banana" },
            new Item { Category = "B", Name = "Blueberry" },
            new Item { Category = "C", Name = "Cherry" },
        };

        var sortedItems = items.OrderBy(x => x.Category).ToList();

        var changeIndices = sortedItems
            .Select((item, index) => new { Item = item, Index = index })
            .Where(x => x.Index == 0 || x.Item.Category != sortedItems[x.Index - 1].Category)
            .Select(x => x.Index)
            .ToList();

        foreach (var index in changeIndices)
        {
            Console.WriteLine($"Change at index {index}, Category: {sortedItems[index].Category}");
        }
    }
}

코드 설명:

1. 정렬: items 리스트를 Category 기준으로 정렬하여 sortedItems 리스트를 생성합니다.


2. 인덱스와 항목 매핑: Select 메서드를 사용하여 각 항목과 해당 인덱스를 새로운 익명 객체로 매핑합니다.


3. 조건 필터링: Where 메서드를 사용하여 다음 조건을 만족하는 항목만 필터링합니다:

첫 번째 항목인 경우 (x.Index == 0)

현재 항목의 Category가 이전 항목과 다른 경우 (x.Item.Category != sortedItems[x.Index - 1].Category)



4. 인덱스 추출: 필터링된 결과에서 Select 메서드를 사용하여 인덱스만 추출합니다.


5. 결과 출력: 각 변경 지점의 인덱스와 해당 Category를 출력합니다.



출력 결과:

Change at index 0, Category: A
Change at index 2, Category: B
Change at index 4, Category: C


좋습니다! 컬럼명을 문자열로 지정하고 싶다면, 런타임에 해당 속성 값을 읽기 위해 Reflection을 써야 합니다. 아래는 LINQ + Reflection + 고급 문법을 조합한 방법입니다.


---

문자열 기준 속성으로 그룹 변경 인덱스 구하기

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

public class Item
{
    public string Category { get; set; }
    public string Name { get; set; }
}

public static class LinqExtensions
{
    public static List<int> GetChangeIndicesByProperty<T>(this IEnumerable<T> source, string propertyName)
    {
        var prop = typeof(T).GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance);
        if (prop == null)
            throw new ArgumentException($"Property '{propertyName}' not found on type '{typeof(T).Name}'");

        // 정렬 후 리스트화
        var sorted = source.OrderBy(x => prop.GetValue(x, null)).ToList();

        // 변경 지점 인덱스 계산
        return sorted
            .Select((item, index) => new { Value = prop.GetValue(item, null), Index = index })
            .Where(x => x.Index == 0 || !object.Equals(x.Value, prop.GetValue(sorted[x.Index - 1], null)))
            .Select(x => x.Index)
            .ToList();
    }
}

public class Program
{
    public static void Main()
    {
        var items = new List<Item>
        {
            new Item { Category = "B", Name = "Banana" },
            new Item { Category = "A", Name = "Apple" },
            new Item { Category = "A", Name = "Avocado" },
            new Item { Category = "C", Name = "Cherry" },
            new Item { Category = "B", Name = "Blueberry" }
        };

        string sortBy = "Category"; // 또는 "Name"
        var changeIndices = items.GetChangeIndicesByProperty(sortBy);

        var sorted = items.OrderBy(x => typeof(Item).GetProperty(sortBy).GetValue(x)).ToList();

        foreach (var index in changeIndices)
        {
            var value = typeof(Item).GetProperty(sortBy).GetValue(sorted[index]);
            Console.WriteLine($"Change at index {index}, {sortBy}: {value}");
        }
    }
}


---

출력 (Category 기준일 경우):

Change at index 0, Category: A
Change at index 2, Category: B
Change at index 4, Category: C


---

이 코드는 원하는 컬럼명을 문자열로 받아, 해당 컬럼 기준으로 정렬하고, 값이 변하는 인덱스를 반환합니다. 매우 동적으로 설계되어 다양한 객체에 적용할 수 있어요.

혹시 컬럼이 여러 개일 경우(복합 정렬)도 고려하셔야 하나요?

좋습니다! List<dynamic> 자료형을 사용하는 경우는 주로 익명 객체나 ExpandoObject 등을 쓸 때인데, 이럴 경우에도 문자열로 지정한 컬럼명 기준으로 정렬하고 값이 변하는 인덱스를 LINQ + Reflection을 활용해 얻을 수 있습니다.

아래는 List<dynamic>에 대응하는 코드입니다:


---

예제 코드 (List<dynamic> + 컬럼명 문자열 기준):

using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;

public static class DynamicListExtensions
{
    public static List<int> GetChangeIndicesByProperty(this List<dynamic> source, string propertyName)
    {
        // 정렬
        var sorted = source.OrderBy(x => GetPropertyValue(x, propertyName)).ToList();

        // 변경 지점 찾기
        return sorted
            .Select((item, index) => new { Value = GetPropertyValue(item, propertyName), Index = index })
            .Where(x => x.Index == 0 || !object.Equals(
                x.Value, GetPropertyValue(sorted[x.Index - 1], propertyName)))
            .Select(x => x.Index)
            .ToList();
    }

    private static object GetPropertyValue(dynamic obj, string propertyName)
    {
        if (obj is ExpandoObject)
        {
            var dict = (IDictionary<string, object>)obj;
            return dict.ContainsKey(propertyName) ? dict[propertyName] : null;
        }

        var prop = obj.GetType().GetProperty(propertyName);
        return prop?.GetValue(obj, null);
    }
}

public class Program
{
    public static void Main()
    {
        dynamic item1 = new ExpandoObject();
        item1.Category = "B";
        item1.Name = "Banana";

        dynamic item2 = new ExpandoObject();
        item2.Category = "A";
        item2.Name = "Apple";

        dynamic item3 = new ExpandoObject();
        item3.Category = "A";
        item3.Name = "Avocado";

        dynamic item4 = new ExpandoObject();
        item4.Category = "C";
        item4.Name = "Cherry";

        dynamic item5 = new ExpandoObject();
        item5.Category = "B";
        item5.Name = "Blueberry";

        List<dynamic> items = new List<dynamic> { item1, item2, item3, item4, item5 };

        string sortBy = "Category";
        var changeIndices = items.GetChangeIndicesByProperty(sortBy);

        var sorted = items.OrderBy(x => DynamicListExtensions.GetPropertyValue(x, sortBy)).ToList();

        foreach (var index in changeIndices)
        {
            Console.WriteLine($"Change at index {index}, {sortBy}: {DynamicListExtensions.GetPropertyValue(sorted[index], sortBy)}");
        }
    }
}


---

출력:

Change at index 0, Category: A
Change at index 2, Category: B
Change at index 4, Category: C


---

이 방식은 ExpandoObject와 익명 타입(dynamic) 모두에 대응합니다. 만약 JSON 파싱 결과라면 ExpandoObject 구조일 가능성이 높기 때문에 이 코드 그대로 활용하셔도 됩니다.

필요하시면 GroupBy 등으로 확장하거나 복합 조건 정렬도 도와드릴 수 있어요. 원하시는 방향이 있을까요?


r