嵌套分组策略/算法c#

发布于 2024-07-25 01:07:27 字数 1452 浏览 3 评论 0原文

不确定我是否必须纠正此类问题的标签,但是您对以下问题的通用解决方案有什么想法吗?

给定发票集合:

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") }
}

我可以使用什么技术来指定类似以下内容:

var tree = invoices.ToHeirarchy(t => {
    t.GroupBy(x => x.Date);
    t.GroupBy(x => x.Customer);
})

结果如图所示:

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

并且还允许通过以下内容(允许对任何级别的发票进行计算)?

Assert.AreEqual(3, tree.Node[0].Items.Count)
Assert.AreEqual(DateTime.Parse("1/1/2009"), tree.Node[0].Key)

Assert.AreEqual(3, tree.Node[1].Items.Count)
Assert.AreEqual(DateTime.Parse("1/2/2009"), tree.Node[1].Key)

Assert.AreEqual("a", tree.Node[0].Node[0].Key)
Assert.AreEqual(1, tree.Node[0].Node[0].Items.Count)

Assert.AreEqual("b", tree.Node[0].Node[1].Key)
Assert.AreEqual(2, tree.Node[0].Node[1].Items.Count)

Not sure if I have to correct label for this type of problem, but do you have any thoughts on a generic solution for the following?

Given a collection of Invoices:

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") }
}

What technique can I use where specifying something like:

var tree = invoices.ToHeirarchy(t => {
    t.GroupBy(x => x.Date);
    t.GroupBy(x => x.Customer);
})

Results in a graph like:

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

And also allows the following to pass (allowing for calculations on the invoices from any level)?

Assert.AreEqual(3, tree.Node[0].Items.Count)
Assert.AreEqual(DateTime.Parse("1/1/2009"), tree.Node[0].Key)

Assert.AreEqual(3, tree.Node[1].Items.Count)
Assert.AreEqual(DateTime.Parse("1/2/2009"), tree.Node[1].Key)

Assert.AreEqual("a", tree.Node[0].Node[0].Key)
Assert.AreEqual(1, tree.Node[0].Node[0].Items.Count)

Assert.AreEqual("b", tree.Node[0].Node[1].Key)
Assert.AreEqual(2, tree.Node[0].Node[1].Items.Count)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

柠北森屋 2024-08-01 01:07:27

您正在寻找的术语是“嵌套 groupby”。

http://msdn.microsoft.com/en-us/vcsharp/ aa336754.aspx#nested

The term you're looking for is a "nested groupby".

http://msdn.microsoft.com/en-us/vcsharp/aa336754.aspx#nested

仅此而已 2024-08-01 01:07:27

远非可重用,但这应该可以做到:

       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();

Far from being reusable but this should do it:

       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();
甲如呢乙后呢 2024-08-01 01:07:27

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

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"));
    }
}

第一个解决方案的输出如下。

<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>

该解决方案远非完美,但可能会提供一些起点,并且它们提供了 LINQ to XML 查询树的全部功能。 如果您要大量使用此树,我建议为该树构建一个更适合需求的自定义节点类型。 但设计这个可能会非常困难 - 特别是如果你想要强类型。

最后我想提一下,我无法真正看到这样的结构的用途 - 使用 LINQ to object 直接从列表中获取结果不是更容易吗?

You need a type that is capable of representing the tree structure. There are some types in the framework that could be used - for example KeyValuePair<TKey, TValue>, the tree view node TreeNode, XML elements XmlElement and XElement, and probably some more. The following example contains two solutions using XElement to represent the tree. One uses lambdas to access the members, the other uses strings and both have pros and cons. I assume it is possible to get the best from solutions with complexer code.

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"));
    }
}

The ouput for the first solution is the following.

<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>

The second solution yields the following.

<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>

This solutions are far from perfect but might offer something to start with and they give the full power of LINQ to XML for querying the tree. If you are going to use this trees heavily, I suggest to build a custome node type for the tree that better fits the needs. But it will probably be quite hard to design this - esspecially if you want strong typing.

Finally I want to mention that I can not really see the use of such an structure - wouldn't it be much easyer to use LINQ to object to obtain the results directly from the list?

夢归不見 2024-08-01 01:07:27

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

Instead of a tree, you could sort the results by Date & then by Customer.

心意如水 2024-08-01 01:07:27

我还没有测试过,但我认为 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 分组以创建层次结构的其他选项,请查看它。

I haven't tested it but I think eulerfx's proposed answer is in the right direction. Below I've written my own solution for this type of thing in LINQ comprehension syntax.

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();

The ToList() calls are really optional depending on what your trying to achieve in your projection.

Recently I've asked a similar question and it appears that I may be answering it myself. Please take it look if you think it may help understand other options with regard to grouping with linq to create hierarchies.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文