简体   繁体   English

将对象列表按C#分组

[英]Sort a list of objects into groups in C#

I have a list of 'tickets' and each 'ticket' contains three numbers. 我有一个“门票”列表,每个“门票”都包含三个数字。 I would to sort all the tickets into groups so each group contains tickets which share at least one number in common. 我将所有票证归为一组,以便每个组包含至少共享一个共同号码的票证。 How do I sort this data into a final list of grouped tickets? 如何将这些数据排序到分组票证的最终列表中?

In short here is the initial list of tickets: 简而言之,这是门票的初始清单:

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

The resulting output would be (broken into seperate lines for ease of reading visually: 产生的输出将是(分成几行,以便于视觉上阅读:

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

Below is the snippet of code I've been using to figure out a solution for... 下面是我一直在寻找解决方案的代码片段...

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

        }
    }
}

So, I've taken the approach of making all the groups - for all ticket numbers. 因此,我采用了对所有票证号码进行分组的方法。 The final results can then be queried to get you what you want. 然后可以查询最终结果,以获取所需的信息。

I had to change the data into a form that suited the processing. 我不得不将数据更改为适合处理的形式。 I started with this: 我从这个开始:

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 } },
};

Now I can do this query: 现在,我可以执行以下查询:

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

Now that gave me the results like this: 现在,给了我这样的结果:

团体票

But that's not terribly easy to see the whole thing, so I reformatted like this: 但这并不是一件容易的事,因此我重新格式化为:

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} 

You should be able to query against groupedTickets to get precisely what you want. 您应该能够对groupedTickets进行查询,以准确获取所需的内容。

For example, you could do this: 例如,您可以这样做:

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

Which will give you this output: 这将为您提供以下输出:

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

And this is quite similar to the output requested, but formatted for display purposes. 这与请求的输出非常相似,但出于显示目的对其进行了格式化。


Based on the question edit and the comments below here is an updated solution. 根据问题编辑和下面的评论,这里是更新的解决方案。

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

This produces the two distinct sets: 这将产生两个不同的集合:

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

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

Not very optimized, but this will do the job and you can improve upon it for efficiency (most obviously with the .Clear() and .AddRange()). 并不是很优化,但这可以完成工作,您可以提高效率(最明显的是使用.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();

Essentially, it will look for all tickets with a matching number. 本质上,它将查找具有匹配编号的所有票证。 It will then insert the numbers into all tickets which match. 然后它将数字插入所有匹配的票证中。 It repeats this until no new numbers are found. 重复此操作直到找不到新的数字。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM