IComparer逻辑将层次结构排序为平面列表

时间:2013-12-12 13:41:44

标签: c# entity-framework sorting icomparer

我目前正在开发一个IComparer,它对于int和string的简单属性工作正常,而且asending和descending也正常工作,但是我遇到了一个层次结构的数据结构问题。

让我们假设您的数据库中包含以下表格:

HierarchyTable
    ID, int
    Name, string
    SortOrder, int
    ParentID, int

HierarchyTable具有ID和ParentID之间的关系,以建立自引用关系,构建我们的层次结构。

现在问题从我的SortOrder开始。 SortOrder不是一个唯一的int,它表示整个级别的排序顺序,而只存储您当前级别的排序顺序。

让我们假设以下数据:

ID --- Name --- SortOrder --- ParentID
1  --- A    --- 0         --- null
2  --- B    --- 1         --- 4
3  --- C    --- 2         --- 1
4  --- D    --- 1         --- 1
5  --- E    --- 1         --- 3

这将导致以下层次结构:

ID --- Name --- SortOrder --- ParentID
1  --- A    --- 0         --- null
    4  --- D    --- 1         --- 1
        2  --- B    --- 1         --- 4
    3  --- C    --- 2         --- 1
        5  --- E    --- 1         --- 3

现在我希望将这个层次结构放在一个平面列表中,借助IComparer和一个只调用Sort方法的List,这里是一个正确的排序平面列表。

此表格结构位于我的实体框架应用程序中,代表实体之一,因此如果需要,我可以将其扩展为其他属性。

这个简单示例的实体看起来像这样:

public class HierarchyTable
{
    public int ID { get; set; }
    public string Name { get; set; }
    public int SortOrder { get; set; }
    public in ParentID { get; set; }

    //Navigation Properties created by Entity Framework
    public virtual HierarchyTable Parent { get; set; }
    public virtual ICollection<HierarchyTable> Children { get; set; }
}

4 个答案:

答案 0 :(得分:1)

您的比较器需要SortOrders的列表,遵循每条记录(父,子,孙......)的整个祖先链。像这样:

ID --- Name --- SortOrder --- ParentID --- HierarchicalSortOrder
1  --- A    --- 0         --- null     --- 0
2  --- B    --- 1         --- 4        --- 0,1,1
3  --- C    --- 2         --- 1        --- 0,2
4  --- D    --- 1         --- 1        --- 0,1
5  --- E    --- 1         --- 3        --- 0,2,1

然后你可以简单地对HierarchicalSortOrder进行排序:

1 --- 0
4 --- 0,1
2 --- 0,1,1
3 --- 0,2
5 --- 0,2,1

以下函数构造此HierarchicalSortOrder:

public string GetHierarchicalSortOrder(HierarchyTable element)
{
   List<int> sortOrders = new List<int>() {element.SortOrder};

   while (element.Parent != null)
   {
      element = element.Parent;
      sortOrders.Insert(0, element);
   }
   return String.Join(",", sortOrders);
}

为简单起见,我假设排序顺序没有联系;如果有,你还应该在列表中包含element.ID,否则孩子会被附加到错误的父母身上。

答案 1 :(得分:1)

首先应使用级别编号展平结构,然后按levelNo + SeqNo排序
查看我上一个问题的答案: Flatten (with level numbers) a hierarchical list

答案 2 :(得分:1)

以下是HierarchyTable课程的扩展版本,可让您非常接近您所寻找的内容。基本上它只是增加了以下内容。

  • a ToString() method
  • HierarchicalSort()进行排序的静态ICollection<HierarchyTable>方法。
  • 用于创建HierarchicalSort的静态GetNextNode()方法。

HierarchicalSort排序方法同时创建层次结构和排序,但只返回排序。层次结构刚刚落后,因此将其拆分为两种方法可能是一个好主意。函数返回时,topNode变量包含分层数据。

没有真正的理由使用自定义IComparer,因为您只是在示例中对SortOrder进行排序。如果需要,您可以轻松扩展它。

public class HierarchyTable {
    public HierarchyTable(HierarchyTable parent) {
        Parent = parent;
        Children = new List<HierarchyTable>();
    }

    public int ID { get; set; }
    public string Name { get; set; }
    public int SortOrder { get; set; }
    public int ParentID { get; set; }

    //Navigation Properties created by Entity Framework
    public virtual HierarchyTable Parent { get; set; }
    public virtual ICollection<HierarchyTable> Children { get; set; }

    public override string ToString() {
        StringBuilder sb = new StringBuilder();

        sb.Append("ID: " + ID).Append('\t');
        sb.Append("Name: " + Name).Append('\t');
        sb.Append("SortOrder: " + SortOrder).Append('\t');
        sb.Append("ParentID: " + ParentID);

        return sb.ToString();
    }

    //-----------------------------------------------------
    // Builds a hierarchical tree out of a List<HierarchyTable>
    // and copies each child row into a different 
    // List<HierarchyTable> as it is being built.
    //-----------------------------------------------------
    public static List<HierarchyTable> HierarchicalSort(ICollection<HierarchyTable> inputlist) {

        HierarchyTable topNode = inputlist.ElementAt(0);
        HierarchyTable current = topNode;
        HierarchyTable child = null;

        inputlist.Remove(topNode);

        List<HierarchyTable> copyList = inputlist.Take(inputlist.Count).ToList();
        List<HierarchyTable> outputList = new List<HierarchyTable>() { topNode };

        foreach (HierarchyTable rec in inputlist) {
            do {                    
                child = GetNextNode(current, copyList);

                if (child != null) {
                    child.Parent = current;
                    current.Children.Add(child);
                    current = child;
                    copyList.Remove(child);
                    outputList.Add(child);
                }

            } while (child != null);

            current = topNode;
        }

        return outputList;
    }

    //-----------------------------------------------------
    // Returns the first child of a sorted match on 
    // ID == ParentID. If you end up needing to sort on 
    // multiple columns or objects that don't already 
    // implement IComparer, then make your own comparer
    // class. The syntax would be:
    //
    // .OrderBy(x => x, new ComparerClass());
    //-----------------------------------------------------
    private static HierarchyTable GetNextNode(HierarchyTable current, ICollection<HierarchyTable> inputlist) {

        List<HierarchyTable> sublist = inputlist
            .Where(
                a => a.ParentID == current.ID
            ).OrderBy(
                x => x.SortOrder
            ).ToList();

        if(sublist.Count > 0)
            return sublist.First();

        return null;
    }
}

样本用法:

    List<HierarchyTable> htable = new List<HierarchyTable>(){
        new HierarchyTable() {ID = 1, Name = "A", SortOrder = 0, ParentID = 0},
        new HierarchyTable() {ID = 2, Name = "B", SortOrder = 1, ParentID = 4},
        new HierarchyTable() {ID = 3, Name = "C", SortOrder = 2, ParentID = 1},
        new HierarchyTable() {ID = 4, Name = "D", SortOrder = 1, ParentID = 1},
        new HierarchyTable() {ID = 5, Name = "E", SortOrder = 1, ParentID = 3}
    };

    List<HierarchyTable> sorted = HierarchyTable.HierarchicalSort(htable);

    foreach (HierarchyTable ht in sorted)
        Console.WriteLine(ht);

控制台:


ID: 1   Name: A SortOrder: 0    ParentID: 0
ID: 4   Name: D SortOrder: 1    ParentID: 1
ID: 2   Name: B SortOrder: 1    ParentID: 4
ID: 3   Name: C SortOrder: 2    ParentID: 1
ID: 5   Name: E SortOrder: 1    ParentID: 3
祝你好运。我希望这会有所帮助。

<强>更新

另一种方法是继续将层次结构展平为列表,然后使用类似于此的IComparer调用sort方法。

public class HierarchyTableComparer : IComparer<HierarchyTable> {
    public int Compare(HierarchyTable a, HierarchyTable b) {
        int comp = 0;

        if ((comp = a.SortOrder.CompareTo(b.SortOrder)) != 0) {
            return comp;
        }
        else if ((comp = a.ID.CompareTo(b.ParentID)) != 0) {
            return comp;
        }
        else {
            return 0;
        }
    }
}

如果框架完成了链接层次元素的初始工作,那么传统的排序就可以了,因为列表已经按级别排序了。

样本用法:

//----------------------------------------------------
// Flattened Hierarchy
//----------------------------------------------------
List<HierarchyTable> htable = new List<HierarchyTable>(){
    new HierarchyTable() {ID = 1, Name = "A", SortOrder = 0, ParentID = 0},
    new HierarchyTable() {ID = 4, Name = "D", SortOrder = 1, ParentID = 1},
    new HierarchyTable() {ID = 2, Name = "B", SortOrder = 1, ParentID = 4},
    new HierarchyTable() {ID = 3, Name = "C", SortOrder = 2, ParentID = 1},
    new HierarchyTable() {ID = 5, Name = "E", SortOrder = 1, ParentID = 3}
};

htable.Sort(new HierarchyTableComparer());

foreach (HierarchyTable ht in htable)
    Console.WriteLine(ht);

控制台:


ID: 1   Name: A SortOrder: 0    ParentID: 0
ID: 4   Name: D SortOrder: 1    ParentID: 1
ID: 2   Name: B SortOrder: 1    ParentID: 4
ID: 5   Name: E SortOrder: 1    ParentID: 3
ID: 3   Name: C SortOrder: 2    ParentID: 1

答案 3 :(得分:1)

如何在HierarchyTable类中使用递归排序。这通常是我在抽象语法树中导航的方式。如果您计划使用HierarchyTable执行各种操作,并且希望对类进行最少的修改,那么使用Visitor设计模式可能是您的最佳选择。它是处理分层节点结构时使用的事实模式。一旦实现,它允许您将依赖于遍历的函数添加到树节点,而根本不更改节点的类。

如果您尚未使用它,我很乐意展示它是如何实现的,只要问一下。我再也不想为你的问题走错路。 :)

如果您有兴趣,这些wiki可以很好地概述访问者模式的使用情况:

基本上,当您想要询问每个节点时,标准模式是最佳的,当您想要快速短路某些节点而不是浪费时间时,最好使用分层模式。他们都在分层数据上工作,所以我真的不喜欢命名区别。

无论如何,这里是使用IComparer在HierarchyTable类中进行的递归排序。使用这些技术还可以从层次结构中的任何级别开始排序。

    public List<HierarchyTable> FlatSort(IComparer<HierarchyTable> comparer) {
        List<HierarchyTable> sorted = new List<HierarchyTable>(){
            this   
        };

        if (Children.Count > 0) {
            //------------------------------------------------
            // Create sorted copy of children using IComparer
            //------------------------------------------------
            List<HierarchyTable> children = Children.ToList();
            children.Sort(comparer);

            foreach (HierarchyTable child in children)
                sorted.AddRange(child.FlatSort(comparer));
        }

        return sorted;
    }

    public class HierarchyTableComparer : IComparer<HierarchyTable> {
        public int Compare(HierarchyTable a, HierarchyTable b) {
            int comp = 0;

            if ((comp = a.SortOrder.CompareTo(b.SortOrder)) != 0) {
                return comp;
            }
            else {
                return 0;
            }
        }
    }

样本使用:

    List<HierarchyTable> flatsort = hierarchyTableInstance.FlatSort(new HierarchyTableComparer());

    foreach (HierarchyTable item in flatsort)
        Console.WriteLine(item);