C# Linq를 사용하여 트리 전체 갯수 구하는 로직 문의

안녕하세요.

제가 현재 트리 구조를 제작 중입니다.

(참고로 Caliburn.Micro를 이용중이라, NotifyOfPropertyChange라는 갱신 메소드를 통해 화면 갱신합니다.)

Recursive하게 Linq를 이용해서 로직을 구현하고 싶은데요.

현재 트리구조는 아래와 같습니다.
TreeModel.cs

public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public EnumTreeType Type { get; set; }
public bool Used { get; set; }
public bool Visibility { get; set; }
public object ParentTree { get; set; }

TreeItemViewModel.cs

public int Id
{
    get => Model.Id;
    set
    {
        Model.Id = value;
        NotifyOfPropertyChange(() => Id);
    }
}


public string Name
{
    get => Model.Name;
    set
    {
        Model.Name = value;
        NotifyOfPropertyChange(() => Name);
    }
}

/// <summary>
/// 트리 노드의 해설 -
/// 트리 노드를 묘사하는 문구를 입력
/// </summary>
public string Description
{
    get => Model.Description;
    set
    {
        Model.Description = value;
        NotifyOfPropertyChange(() => Description);
    }
}

/// <summary>
/// 트리 노드의 타입 -
/// 트리 노드의 타입을 지정할 수 있다.
/// 1.ROOT : 부모가 없고, 자식만 존재 최초 노드
/// 2.BRANCH : 부모와 자식이 둘다 존재 중간 노드
/// 3.LEAF : 자식은 없고 부모만 존재 말단 노드
/// </summary>
public EnumTreeType Type
{
    get => Model.Type;
    set 
    {
        Model.Type = value;
        NotifyOfPropertyChange(() => Type);
    }
}

public bool Used
{
    get => Model.Used;
    set
    {
        Model.Used = value;
        NotifyOfPropertyChange(() => Used);
    }
}

public bool Visibility
{
    get => Model.Visibility;
    set
    {
        Model.Visibility = value;
        NotifyOfPropertyChange(() => Visibility);
    }
}


public object ParentTree
{
    get => Model.ParentTree;
    set
    {
        Model.ParentTree = value;
        NotifyOfPropertyChange(() => ParentTree);
    }
}


{
    get { return _children; }
    set
    {
        _children = value;
        NotifyOfPropertyChange(() => Children);
    }
}

private ObservableCollection<TreeItemViewModel> _children;

ViewModel을 통해서 로직을 만들고 핼퍼클래스로 재귀적으로 갯수 계산하는 로직 구현하려고하는데요.

제가 짠 헬퍼 클래스의 재귀메소드는 이렇습니다.

조언을 구합니다.

public static class TreeManager
{
    public static int Count;
            
    public static int GetTreeCount(ObservableCollection<TreeItemViewModel> collection)
    {
        /// Get The number of node with the recursive travers of tree structure
        /// node0
        ///   ㄴnode1
        ///     ㄴnode4
        ///        ㄴnode7
        ///   ㄴnode2
        ///     ㄴnode5
        ///   ㄴnode3
        ///     ㄴnode6
        /// 
        Count = 0;

        TreeRecursizeCounter(collection);
        return Count;
    }

    private static void TreeRecursizeCounter(ObservableCollection<TreeItemViewModel> collection)
    {
        if (!(collection != null && collection.Count() > 0))
        {
            Debug.WriteLine($"자손없음");
            return;
        }

        foreach (var item in collection)
        {
            Debug.WriteLine($"{item.DisplayName} 도착!! 총 노드 : {++Count}개");
            if (item.Children.Count() > 0)
            {
                Debug.WriteLine($"하위 레벨 진입");
                TreeRecursizeCounter(item.Children);
            }
        }
    }        
}

이정도 입니다.

감사합니다.

좋아요 1

글쎄요… LINQ로는 하이라키 구조를 질의하는 효과적인 방법은 저도 모르겠습니다… 혹시 아시는 분이 계시면 저를 포함한 분들을 위해 댓글 부탁 드립니다.

일반적인 방법으로 질의하는 것은 아래의 코드로 작성해 봤습니다.

var root = new Node(1, "node0")
{
    Children = new List<Node>
    {
        new(11, "node4")
        {
            Children = new List<Node>
            {
                new(111, "node7")
            }
        },
        new(21, "node2")
        {
            Children = new List<Node>
            {
                new(211, "node5")
            }
        },
        new(31, "node3")
        {
            Children = new List<Node>
            {
                new(311, "node6")
            }
        }
    }
};

// 부모 설정
InitParentNode(root);

var count = CountNodes(root);
Console.WriteLine($"Total Nodes: {count}");

ShowNodes(root);


void InitParentNode(Node node)
{
    foreach (var child in node.Children)
    {
        child.Parent = node;
        InitParentNode(child);
    }
}

// 일반 버젼
int CountNodes(Node node)
{
    var count = 0;

    CountChildren(node);

    return count;

    void CountChildren(Node node)
    {
        count++;
        foreach (var child in node.Children)
            CountChildren(child);
    }
}

void ShowNodes(Node node)
{
    var tab = 0;
    
    ShowChildren(node);

    void ShowChildren(Node node)
    {
        Console.WriteLine($"{"".PadLeft(tab)}{node.Name}");
        tab += 3;
        foreach (var child in node.Children)
            ShowChildren(child);
        tab -= 3;
    }
}



public class Node
{
    public int Id { get; }
    public string Name { get; }
    public string Description { get; init; } = default!;
    public TreeType TreeType => this switch
    {
        { Parent: null } => TreeType.Root,
        { Parent: not null, Children: var c } when c.Count > 0 => TreeType.Branch,
        { Parent: not null, Children: var c } when c.Count is 0 => TreeType.Leaf,
        _ => TreeType.Root
    };

    public Node Parent { get; set; } = default!;
    public IList<Node> Children { get; init; } = new List<Node>();

    public Node(int id, string name) => (Id, Name) = (id, name);
}

public enum TreeType
{
    Root,
    Branch,
    Leaf
}

| 출력

Total Nodes: 7
node0
   node4
      node7
   node2
      node5
   node3
      node6

참고로 로컬 함수를 사용하면 변수를 공유할 수 있으므로 좀 더 효율적인 코드가 가능합니다.

좋아요 1

앗! 감사합니다!!!

로컬함수로 지금 구조보단 깔끔해질 수 있을 것 같습니다.

물론 Linq로 작성가능하신분 댓글 부탁드립니다.

감사합니다~!!!

좋아요 2

아래 글의 답변과 유사한 구현으로 하이라키 구조를 탐색하는 Linq 기능을 구현할 수 있을 것 같습니다.

좋아요 2

감사합니다.
연구해볼께요~!! :grinning:

좋아요 1