嵌套分组策略/算法c#

嵌套分组策略/算法c#,c#,algorithm,data-structures,hierarchical-grouping,C#,Algorithm,Data Structures,Hierarchical Grouping,不确定我是否必须为这类问题更正标签,但您对以下问题的通用解决方案有何想法 给定发票集合: var invoices = new List<Invoice>() { new Invoice() { Id = 1, Customer = "a", Date = DateTime.Parse("1/1/2009") }, new Invoice() { Id = 2, Customer = "a", Date = DateTime.Parse("1/2/2009") }, new Invo

不确定我是否必须为这类问题更正标签,但您对以下问题的通用解决方案有何想法

给定发票集合:

var invoices = new List<Invoice>()
{
new Invoice() { Id = 1, Customer = "a", Date = DateTime.Parse("1/1/2009") },
new Invoice() { Id = 2, Customer = "a", Date = DateTime.Parse("1/2/2009") },
new Invoice() { Id = 3, Customer = "a", Date = DateTime.Parse("1/2/2009") },
new Invoice() { Id = 4, Customer = "b", Date = DateTime.Parse("1/1/2009") },
new Invoice() { Id = 5, Customer = "b", Date = DateTime.Parse("1/1/2009") },
new Invoice() { Id = 6, Customer = "b", Date = DateTime.Parse("1/2/2009") }
}
结果显示如下图形:

Date "1/1/2009"
  Customer a
    Invoice 1
  Customer b
    Invoice 4
    Invoice 5
Date "1/2/2009"
  Customer a
    Invoice 2
    Invoice 3
  Customer b
    Invoice 6
并且还允许通过以下各项(允许从任何级别计算发票)


您可以先按日期,然后按客户对结果进行排序,而不是按树进行排序。

远不能重复使用,但这应该可以做到:

       var tree = invoices.GroupBy(x => x.Date).Select(x => new
            {
                Key = x.Key,
                Items = x.GroupBy(y => y.Customer).Select(y => new
                    {
                        Key = y.Key,
                        Items = y.Select(z => z.Id).ToList()
                    })
            }).ToList();

您要查找的术语是“嵌套groupby”


您需要一个能够表示树结构的类型。框架中有一些类型可以使用,例如
KeyValuePair
、树视图节点
TreeNode
、XML元素
xmlement
XElement
,可能还有更多。以下示例包含两个使用
XElement
表示树的解决方案。一个使用lambda访问成员,另一个使用字符串,两者都有优点和缺点。我假设有可能从使用complexer代码的解决方案中获得最佳效果

static void Main()
{
    IEnumerable<Invoice> invoices = new List<Invoice>()
    { 
        new Invoice() { Id = 1, Customer = "a", Date = DateTime.Parse("1/1/2009") },
        new Invoice() { Id = 2, Customer = "a", Date = DateTime.Parse("1/2/2009") }, 
        new Invoice() { Id = 3, Customer = "a", Date = DateTime.Parse("1/2/2009") }, 
        new Invoice() { Id = 4, Customer = "b", Date = DateTime.Parse("1/1/2009") }, 
        new Invoice() { Id = 5, Customer = "b", Date = DateTime.Parse("1/1/2009") }, 
        new Invoice() { Id = 6, Customer = "b", Date = DateTime.Parse("1/2/2009") } 
    };


    StringBuilder sb = new StringBuilder();
    TextWriter tw = new StringWriter(sb);

    using (XmlWriter xmlWriter = new XmlTextWriter(tw) { Formatting = Formatting.Indented })
    {

        XElement t1 = new XElement("Root", BuildTree(invoices, i => i.Customer, i => i.Date, i => i.Id));
        XElement t2 = new XElement("Root", BuildTree(invoices, "Customer", "Date", "Id"));

        var xyz = t2.Elements("Customer").ElementAt(1).Descendants("Item").Count();

        t1.WriteTo(xmlWriter);
        t2.WriteTo(xmlWriter);
    }

    Console.WriteLine(sb.ToString());

    Console.ReadLine();
}

public static IEnumerable<XElement> BuildTree<T>(IEnumerable<T> collection, params Func<T, Object>[] groups)
{
    if ((groups != null) && (groups.Length > 0))
    {
        return collection
            .GroupBy(groups[0])
            .Select(grp => new XElement(
                "Group",
                new XAttribute("Value", grp.Key),
                BuildTree(grp, groups.Skip(1).ToArray())));
    }
    else
    {
        return collection.Select(i => new XElement("Item"));
    }
}

public static IEnumerable<XElement> BuildTree<T>(IEnumerable<T> collection, params String[] groups)
{
    if ((groups != null) && (groups.Length > 0))
    {
        return collection
            .GroupBy(i => typeof(T).GetProperty(groups[0]).GetValue(i, null))
            .Select(grp => new XElement(
                groups[0],
                new XAttribute("Value", grp.Key),
                BuildTree(grp, groups.Skip(1).ToArray())));
    }
    else
    {
        return collection.Select(i => new XElement("Item"));
    }
}
static void Main()
{
IEnumerable发票=新列表()
{ 
新发票(){Id=1,Customer=“a”,Date=DateTime.Parse(“1/1/2009”)},
新发票(){Id=2,Customer=“a”,Date=DateTime.Parse(“1/2/2009”)},
新发票(){Id=3,Customer=“a”,Date=DateTime.Parse(“1/2/2009”)},
新发票(){Id=4,Customer=“b”,Date=DateTime.Parse(“1/1/2009”)},
新发票(){Id=5,Customer=“b”,Date=DateTime.Parse(“1/1/2009”)},
新发票(){Id=6,Customer=“b”,Date=DateTime.Parse(“1/2/2009”)}
};
StringBuilder sb=新的StringBuilder();
TextWriter tw=新的StringWriter(sb);
使用(XmlWriter XmlWriter=newXMLTextWriter(tw){Formatting=Formatting.Indented})
{
XElement t1=新XElement(“根”,构建树(发票,i=>i.Customer,i=>i.Date,i=>i.Id));
XElement t2=新XElement(“根”,构建树(发票,“客户”,“日期”,“Id”);
var xyz=t2.Elements(“客户”).ElementAt(1).子体(“项目”).Count();
t1.WriteTo(xmlWriter);
t2.WriteTo(xmlWriter);
}
Console.WriteLine(sb.ToString());
Console.ReadLine();
}
公共静态IEnumerable构建树(IEnumerable集合,参数Func[]组)
{
如果((groups!=null)&&(groups.Length>0))
{
回收
.GroupBy(组[0])
.选择(grp=>new XElement(
“集团”,
新XAttribute(“值”,grp.Key),
BuildTree(grp,groups.Skip(1.ToArray());
}
其他的
{
返回集合。选择(i=>newxelement(“项”);
}
}
公共静态IEnumerable构建树(IEnumerable集合,参数字符串[]组)
{
如果((groups!=null)&&(groups.Length>0))
{
回收
.GroupBy(i=>typeof(T).GetProperty(组[0]).GetValue(i,null))
.选择(grp=>new XElement(
组[0],
新XAttribute(“值”,grp.Key),
BuildTree(grp,groups.Skip(1.ToArray());
}
其他的
{
返回集合。选择(i=>newxelement(“项”);
}
}
第一个解决方案的输出如下所示

<Root>
  <Group Value="a">
    <Group Value="2009-01-01T00:00:00">
      <Group Value="1">
        <Item />
      </Group>
    </Group>
    <Group Value="2009-02-01T00:00:00">
      <Group Value="2">
        <Item />
      </Group>
      <Group Value="3">
        <Item />
      </Group>
    </Group>
  </Group>
  <Group Value="b">
    <Group Value="2009-01-01T00:00:00">
      <Group Value="4">
        <Item />
      </Group>
      <Group Value="5">
        <Item />
      </Group>
    </Group>
    <Group Value="2009-02-01T00:00:00">
      <Group Value="6">
        <Item />
      </Group>
    </Group>
  </Group>
</Root>
<Root>
  <Customer Value="a">
    <Date Value="2009-01-01T00:00:00">
      <Id Value="1">
        <Item />
      </Id>
    </Date>
    <Date Value="2009-02-01T00:00:00">
      <Id Value="2">
        <Item />
      </Id>
      <Id Value="3">
        <Item />
      </Id>
    </Date>
  </Customer>
  <Customer Value="b">
    <Date Value="2009-01-01T00:00:00">
      <Id Value="4">
        <Item />
      </Id>
      <Id Value="5">
        <Item />
      </Id>
    </Date>
    <Date Value="2009-02-01T00:00:00">
      <Id Value="6">
        <Item />
      </Id>
    </Date>
  </Customer>
</Root>

第二种解决方案产生以下结果

<Root>
  <Group Value="a">
    <Group Value="2009-01-01T00:00:00">
      <Group Value="1">
        <Item />
      </Group>
    </Group>
    <Group Value="2009-02-01T00:00:00">
      <Group Value="2">
        <Item />
      </Group>
      <Group Value="3">
        <Item />
      </Group>
    </Group>
  </Group>
  <Group Value="b">
    <Group Value="2009-01-01T00:00:00">
      <Group Value="4">
        <Item />
      </Group>
      <Group Value="5">
        <Item />
      </Group>
    </Group>
    <Group Value="2009-02-01T00:00:00">
      <Group Value="6">
        <Item />
      </Group>
    </Group>
  </Group>
</Root>
<Root>
  <Customer Value="a">
    <Date Value="2009-01-01T00:00:00">
      <Id Value="1">
        <Item />
      </Id>
    </Date>
    <Date Value="2009-02-01T00:00:00">
      <Id Value="2">
        <Item />
      </Id>
      <Id Value="3">
        <Item />
      </Id>
    </Date>
  </Customer>
  <Customer Value="b">
    <Date Value="2009-01-01T00:00:00">
      <Id Value="4">
        <Item />
      </Id>
      <Id Value="5">
        <Item />
      </Id>
    </Date>
    <Date Value="2009-02-01T00:00:00">
      <Id Value="6">
        <Item />
      </Id>
    </Date>
  </Customer>
</Root>

这种解决方案远非十全十美,但可能会提供一些东西作为起点,它们为查询树提供了LINQtoXML的全部功能。如果要大量使用此树,我建议为该树构建一个更适合需要的自定义节点类型。但是这可能很难设计——特别是如果你想要强输入的话


最后,我想说的是,我真的看不到这种结构的用途——使用LINQ to object直接从列表中获得结果不是更容易吗?

我没有测试过它,但我认为eulerfx提出的答案是正确的。下面,我用LINQ理解语法为这类事情编写了自己的解决方案

var tree =
    (from i in invoices
    group i by i.Date into g1
    select new
    {
        Key = g1.Key,
        Items =
            (from d in g1
            group d by d.Customer into g2
            select new
            {
                Key = g2.Key,
                Items =
                    from d in g2
                    select new
                    {
                        Key = d.Id,
                    }
            }).ToList()
    }).ToList();
ToList()调用实际上是可选的,这取决于您在投影中试图实现的目标


最近我问了一个问题,似乎我自己也在回答。如果您认为它有助于理解使用linq分组以创建层次结构的其他选项,请查看。

您在投影方面需要什么?匿名类型、预定义类型、一些通用填充结构(如数据集或字典)?