Вложенная стратегия группировки / алгоритм c # - PullRequest
2 голосов
/ 18 июня 2009

Не уверен, что мне нужно исправить метку для этого типа проблемы, но есть ли у вас какие-либо мысли по поводу общего решения для следующего?

Учитывая коллекцию счетов:

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)

Ответы [ 5 ]

2 голосов
/ 18 июня 2009

Термин, который вы ищете, является «вложенным groupby».

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

1 голос
/ 18 июня 2009

Вам нужен тип, способный представлять древовидную структуру. В каркасе можно использовать несколько типов - например, KeyValuePair<TKey, TValue>, узел древовидного представления TreeNode, элементы XML XmlElement и XElement и, возможно, некоторые другие. В следующем примере представлены два решения, использующие XElement для представления дерева. Один использует лямбды для доступа к членам, другой использует строки, и оба имеют свои плюсы и минусы. Я предполагаю, что можно получить лучшее от решений с более сложным кодом.

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 для объектов, чтобы получать результаты непосредственно из списка?

1 голос
/ 18 июня 2009

Далеко не для повторного использования, но это должно сделать это:

       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();
0 голосов
/ 17 ноября 2009

Я не проверял это, но я думаю, что предлагаемый ответ 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 для создания иерархий.

0 голосов
/ 18 июня 2009

Вместо дерева вы можете отсортировать результаты по дате, а затем по клиенту.

...