在C#中将对象列表分组到组中

时间:2015-07-12 22:31:54

标签: c# list nested-lists

我有一张'门票列表'和每张票#39;包含三个数字。我会将所有门票分组,以便每组包含共享至少一个共同号码的门票。如何将此数据排序到分组票证的最终列表中?

简而言之,这是最初的门票清单:

ticketA = { 1, 2, 3 }
ticketB = { 3, 4, 1 }
ticketC = { 5, 6, 7 }
ticketD = { 7, 8, 5 }
ticketE = { 9, 10, 11 }
ticketF = { 11, 1, 9 }

结果输出将(分成单独的行以便于直观阅读:

GroupedTickets = {
    <List>( ticketA, ticketB, ticketF ticketE )
    <List>( ticketC, ticketD )
}

以下是我用来为...找出解决方案的代码片段。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace CrowdTool
{
    class Ticket
    {
        public string Name { get; set; }
        public List<int> Numbers { get; set; }
    }

    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            Sort();
        }

        public void Sort()
        {
            List<Ticket> allTickets = new List<Ticket>();
            Ticket ticketA = new Ticket();
            ticketA.Numbers = new List<int> { 1, 2, 3 };
            allTickets.Add(ticketA);
            Ticket ticketB = new Ticket();
            ticketB.Numbers = new List<int> { 3, 4, 1 };
            allTickets.Add(ticketB);
            Ticket ticketC = new Ticket();
            ticketC.Numbers = new List<int> { 5, 6, 7 };
            allTickets.Add(ticketC);
            Ticket ticketD = new Ticket();
            ticketD.Numbers = new List<int> { 7, 8, 5 };
            allTickets.Add(ticketD);
            Ticket ticketE = new Ticket();
            ticketE.Numbers = new List<int> { 9, 10, 11 };
            allTickets.Add(ticketE);
            Ticket ticketF = new Ticket();
            ticketF.Numbers = new List<int> { 11, 1, 9 };
            allTickets.Add(ticketF);

            // variable to store groups of tickets
            List <List<Ticket>> GroupedTickets = new List<List<Ticket>>();
            foreach (var ticket in allTickets)
            {
                Console.WriteLine(ticket);
            }

        }
    }
}

2 个答案:

答案 0 :(得分:3)

所以,我已采取制作所有组的方法 - 所有票号。然后可以查询最终结果以获得您想要的结果。

我不得不将数据更改为适合处理的表单。我从这开始:

var tickets = new Dictionary<string, int[]>()
{
    { "TicketA", new [] { 1, 2, 3 } },
    { "TicketB", new [] { 3, 4, 1 } },
    { "TicketC", new [] { 5, 6, 7 } },
    { "TicketD", new [] { 7, 8, 5 } },
    { "TicketE", new [] { 9, 10, 11 } },
    { "TicketF", new [] { 11, 1, 9 } },
};

现在我可以进行此查询:

var groupedTickets =
    tickets
        .SelectMany(t => t.Value, (t, n) => new { t, n })
        .ToLookup(x => x.n, x => x.t)
        .OrderBy(x => x.Key)
        .Select(x => new
        {
            number = x.Key,
            tickets = x.Select(y => new
            {
                ticket = y.Key,
                numbers = y.Value
            }).ToList()
        })
        .ToList();

现在这给了我这样的结果:

grouped tickets

但是看到整件事情并不容易,所以我重新格式化了这样:

1: TicketA = {1, 2, 3}, TicketB = {3, 4, 1}, TicketF = {11, 1, 9} 
2: TicketA = {1, 2, 3} 
3: TicketA = {1, 2, 3}, TicketB = {3, 4, 1} 
4: TicketB = {3, 4, 1} 
5: TicketC = {5, 6, 7}, TicketD = {7, 8, 5} 
6: TicketC = {5, 6, 7} 
7: TicketC = {5, 6, 7}, TicketD = {7, 8, 5} 
8: TicketD = {7, 8, 5} 
9: TicketE = {9, 10, 11}, TicketF = {11, 1, 9} 
10: TicketE = {9, 10, 11} 
11: TicketE = {9, 10, 11}, TicketF = {11, 1, 9} 

您应该可以查询groupedTickets以获得您想要的内容。

例如,你可以这样做:

var output =
    groupedTickets
        .Where(x => x.tickets.Skip(1).Any())
        .Select(x => String.Join(", ", x.tickets.Select(y => y.ticket)))
        .OrderBy(x => x)
        .Distinct();

这将为您提供此输出:

TicketA, TicketB 
TicketA, TicketB, TicketF 
TicketC, TicketD 
TicketE, TicketF 

这与请求的输出非常相似,但为显示目的而格式化。

基于问题编辑,此处的评论是更新的解决方案。

var lookup =
    tickets
        .SelectMany(t => t.Value, (t, n) => new { t, n })
        .ToLookup(x => x.n, x => x.t.Value);

var groupedTickets =
    tickets
        .SelectMany(t => t.Value, (t, n) => new { t, n })
        .OrderBy(x => x.n)
        .ToLookup(x => x.n, x => x.t)
        .SelectMany(
            x => x.SelectMany(y => y.Value),
            (x, y) => new []
            {
                Tuple.Create(x.Key, y), 
                Tuple.Create(y, x.Key)
            })
        .SelectMany(t => t)
        .Where(t => t.Item1 != t.Item2)
        .Distinct();

Func<
    IEnumerable<Tuple<int, int>>,
    IEnumerable<Tuple<int, int>>,
    int,
    IEnumerable<Tuple<int, int>>> fold = null;
fold = (ts0, ts1, n) =>
    n == 0
        ? ts0
        : ts0
            .Concat(fold(
                ts0.Join(
                    ts1,
                    t0 => t0.Item2,
                    t1 => t1.Item1,
                    (t0, t1) => Tuple.Create(t0.Item1, t1.Item2)),
                ts1,
                n - 1))
            .Distinct()
            .ToArray();

var pairs = tickets.SelectMany(t => t.Value).Distinct().Count();

var final =
    fold(groupedTickets, groupedTickets, pairs)
        .OrderBy(x => x.Item1)
        .ThenBy(x => x.Item2)
        .GroupBy(x => x.Item1, x => x.Item2)
        .GroupBy(x => String.Join(",", x), x => x.Key)
        .Select(x => x.SelectMany(y => lookup[y]).Distinct());

这会产生两个不同的集合:

{ { 1, 2, 3 }, { 3, 4, 1 }, { 11, 1, 9 }, { 9, 10, 11 } }

{ { 5, 6, 7 }, { 7, 8, 5 } }

答案 1 :(得分:0)

不是很优化,但是这可以完成工作,你可以提高它的效率(最明显的是使用.Clear()和.AddRange())。

var tickets = new Dictionary<string, List<int>>()
{
    { "TicketA", new List<int> { 1, 2, 3 } },
    { "TicketB", new List<int> { 3, 4, 1 } },
    { "TicketC", new List<int> { 5, 6, 7 } },
    { "TicketD", new List<int> { 7, 8, 5 } },
    { "TicketE", new List<int> { 9, 10, 11 } },
    { "TicketF", new List<int> { 11, 1, 9 } },
};

var newDict = new Dictionary<string, List<int>>(tickets);
foreach(var ticket in newDict)
{
    bool madeChange = true;
    while(madeChange)
    {
        var groupTickets = newDict.Where(t => t.Key != ticket.Key && t.Value.Intersect(ticket.Value).Any() && t.Value.Except(ticket.Value).Any()).ToList();
        madeChange = false;
        if (groupTickets.Any())
        {
            var newSet = groupTickets.SelectMany (t => t.Value).Union(ticket.Value).Distinct().ToList();                
            ticket.Value.Clear();
            ticket.Value.AddRange(newSet);
            foreach(var groupTicket in groupTickets)
            {
                groupTicket.Value.Clear();
                groupTicket.Value.AddRange(newSet);
            }
            madeChange = true;
        }
    }
}

newDict.GroupBy (t => String.Join(",", t.Value)).Dump();

基本上,它会查找具有匹配编号的所有门票。然后,它会将数字插入匹配的所有票据中。它重复这一过程,直到找不到新的数字。