C#-将多对多对象列表放到两个对象列表的不同组合列表中

我似乎无法掌握如何执行此操作,也找不到一种简单的方法来解释它……所以我希望这个简化的示例有意义。

被赋予对象的列表:

print var

我需要的结果是这样的两个对象列表的列表:

public class flatManyToMany
{
    public string BookTitle { get; set; }
    public int BookPages { get; set; }
    public string ReaderName { get; set; }
    public int ReaderAge { get; set; }
}

var flatManyToMany = new List<flatManyToMany>();

flatManyToMany.Add(new flatManyToMany { BookTitle = "How to Do This Double List",BookPages = 105,ReaderName = "Kyle",ReaderAge = 29 });
flatManyToMany.Add(new flatManyToMany { BookTitle = "How to Do This Double List",ReaderName = "Bob",ReaderAge = 34 });
flatManyToMany.Add(new flatManyToMany { BookTitle = "Gone With Jon Skeet",BookPages = 192,ReaderAge = 29 });
flatManyToMany.Add(new flatManyToMany { BookTitle = "Gone With Jon Skeet",ReaderName = "James",ReaderAge = 45 });
flatManyToMany.Add(new flatManyToMany { BookTitle = "Gone With Jon Skeet",ReaderName = "Brian",ReaderAge = 15 });
flatManyToMany.Add(new flatManyToMany { BookTitle = "Why Is This So Hard?",BookPages = 56,ReaderAge = 29 });
flatManyToMany.Add(new flatManyToMany { BookTitle = "Why Is This So Hard?",ReaderAge = 45 });
flatManyToMany.Add(new flatManyToMany { BookTitle = "Why Is This So Hard?",ReaderAge = 15 });
flatManyToMany.Add(new flatManyToMany { BookTitle = "Impostor Syndrome",BookPages = 454,ReaderAge = 29 });
flatManyToMany.Add(new flatManyToMany { BookTitle = "Self Doubt and You",BookPages = 999,ReaderAge = 29 });

该书在最终结果中只应显示一次,但读者可以显示多次。如果同一位读者阅读了多本书,则可以放在一起。

这是我需要结果的方式:

public class ResultDoubleList
{
    public List<Book> Books { get; set; } = new List<Book>();
    public List<Reader> Readers { get; set; } = new List<Reader>();
}

public class Book
{
    public string Title { get; set; }
    public int Pages { get; set; }
}

public class Reader
{
    public string Name { get; set; }
    public int Age { get; set; }
}

因此,书籍清单和读者清单的独特组合是最终结果。该书仅显示一次,但读者可以显示多次。具有完全相同的读者列表的图书将被分组在一起。

即使有人能告诉我这种最终结果的名称,我也将不胜感激。

tjdalele 回答:C#-将多对多对象列表放到两个对象列表的不同组合列表中

您可以使用以下冗长的LINQ查询来做到这一点:

var result = flatManyToMany
    .GroupBy(f1 => (f1.BookTitle,f1.BookPages))
    .Select(g1 => (bookInfo: g1.Key,readers:
                        g1.Select(f2 => new Reader { Name= f2.ReaderName,Age= f2.ReaderAge }),readerKey:
                        String.Join("|",g1.Select(f3 => $"{f3.ReaderName}{f3.ReaderAge}"))))
    .GroupBy(a1 => a1.readerKey)
    .Select(g2 => new ResultDoubleList {
        Books = g2.Select(a2 => new Book {
                    Title = a2.bookInfo.BookTitle,Pages = a2.bookInfo.BookPages
                }
            ).ToList(),Readers = g2.First().readers.ToList() // Any will do,since they have the same readers
    })
    .ToList();

这个想法是分组两次。每本书一次,每个读者组一次。

首先,我们按ValueTuple (f1.BookTitle,f1.BookPages)分组。创建Book对象的好处是ValueTuple自动覆盖EqualsGetHashCode。对于用作字典或查找中的键的类型(如GroupBy),这是必需的。另外,您可以在Book类中覆盖这些方法,并按Book对象进行分组。如果您有唯一的图书ID,请改用此ID。

然后,我们使用Select创建一个临时结果。我们再次创建一个具有3个字段的元组。包含书籍信息的元组,可枚举的Reader对象,最后,我们创建一个包含所有读者作为关键字的字符串,稍后将使用该字符串按独特的读者组进行分组。如果您有唯一的读者ID,请使用此ID代替姓名和年龄。

到目前为止,我们有一个

IEnumerable<(
    (string BookTitle,int BookPages) bookInfo,IEnumerable<Reader> readers,string readerKey
)>

现在,我们按readerKey分组,然后创建ResultDoubleList个对象的列表。

如果您在理解细节方面有困难,请将LINQ查询分为几个查询。通过使用“显式”重构,您可以查看得到的结果类型。 (这就是我从上方得到复杂的IEnumerable<T>的方式。)这也使您可以在调试器中检查中间结果。


此测试...

int resultNo = 1;
foreach (ResultDoubleList item in result) {
    Console.WriteLine($"\r\nresult({resultNo++}):");
    Console.WriteLine("Books");
    foreach (var book in item.Books) {
        Console.WriteLine($"    {book.Title,-28} {book.Pages,3}");
    }
    Console.WriteLine("Readers");
    foreach (var reader in item.Readers) {
        Console.WriteLine($"    {reader.Name,-8} {reader.Age,2}");
    }
}
Console.ReadKey();

...产量:

result(1):
Books
    How to Do This Double List   105
Readers
    Kyle     29
    Bob      34

result(2):
Books
    Gone With Jon Skeet          192
    Why Is This So Hard?          56
Readers
    Kyle     29
    James    45
    Brian    15

result(3):
Books
    Impostor Syndrome            454
    Self Doubt and You           999
Readers
    Kyle     29
,

A。带有针对读者组的字符串键

var booksReadByGroups = flatManyToMany.GroupBy(a => a.BookTitle)
    .Select(g => new
    {
        Book = new Book { Title = g.Key,Pages = g.Max(a => a.BookPages) },Readers = g.Select(a => new Reader { Name = a.ReaderName,Age = a.ReaderAge }).ToList()
    })
    .GroupBy(b => string.Join("+",b.Readers.OrderBy(r=>r.Name).ThenBy(r=>r.Age).Select(r => $"{r.Name}{r.Age}")))
    .Select(g => new
    {
        Books = g.Select(b => b.Book),Readers = g.First().Readers
    })
    .ToList();

Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(booksReadByGroups));

以上内容产生了(有些行手动中断):

[{
    "Books":[
        {"Title":"How to Do This Double List","Pages":105}
    ],"Readers":[
        {"Name":"Kyle","Age":29},{"Name":"Bob","Age":34}
    ]
},{
    "Books":[
        {"Title":"Gone With Jon Skeet","Pages":192},{"Title":"Why Is This So Hard?","Pages":56}
    ],{"Name":"James","Age":45},{"Name":"Brian","Age":15}
    ]
},{
    "Books":[
        {"Title":"Impostor Syndrome","Pages":454},{"Title":"Self Doubt and You","Pages":999}
    ],"Age":29}
    ]
}]

B。较短但较丑

我们需要两次GroupBy,但是没有必要进行第一个投影,一个Select就足够了。

var readerGroups = flatManyToMany.GroupBy(a => a.BookTitle)
    .GroupBy(g => string.Join("+",g.OrderBy(r=>r.ReaderName).ThenBy(r=>r.ReaderAge).Select(r => $"{r.ReaderName}{r.ReaderAge}")))
    .Select(g => new
    {
        Books = g.Select( g2 => new Book { Title = g2.Key,Pages = g2.Max(a => a.BookPages) }),Readers = g.First().Select(a => new Reader { Name = a.ReaderName,Age = a.ReaderAge })
    });

C。使用IEquatable

此版本最长,但可以说是最正确的,因为它由Reader类决定哪些读者被认为是平等的。

class ReadersComparer : IEqualityComparer<List<Reader>>
{
    public bool Equals(List<Reader> a,List<Reader> b) => Enumerable.SequenceEqual(a,b); // Please note this doesn't order the lists so you either need to order them before,or order them here and implement IComparable on the Reader class
    public int GetHashCode(List<Reader> os)
    {
        int hash = 19;
        foreach (var o in os) { hash = hash * 31 + o.GetHashCode(); }
        return hash;
    }
}

public class Reader : IEquatable<Reader>
{
    public string Name { get; set; }
    public int Age { get; set; }

    public override int GetHashCode() => (Name,Age).GetHashCode();
    public bool Equals(Reader other) => (other is null) ? false : this.Name == other.Name && this.Age == other.Age;
    public override bool Equals(object obj) => Equals(obj as Reader);
}


static void Main(string[] args)
{
var actsOfReading = new[]{
    new Reading { BookTitle = "How to Do This Double List",BookPages = 105,ReaderName = "Kyle",ReaderAge = 29},new Reading { BookTitle = "How to Do This Double List",ReaderName = "Bob",ReaderAge = 34},new Reading { BookTitle = "Gone With Jon Skeet",BookPages = 192,ReaderName = "James",ReaderAge = 45},ReaderName = "Brian",ReaderAge = 15},new Reading { BookTitle = "Why Is This So Hard?",BookPages = 56,new Reading { BookTitle = "Impostor Syndrome",BookPages = 454,new Reading { BookTitle = "Self Doubt and You",BookPages = 999,ReaderAge = 29}
};


var booksReadByGroups = actsOfReading.GroupBy(a => a.BookTitle)
    .Select(g => new
    {
        Book = new Book { Title = g.Key,Age = a.ReaderAge }).ToList()
    })
    .GroupBy(b => b.Readers,new ReadersComparer())
    .Select(g => new
    {
        Books = g.Select(b => b.Book),Readers = g.First().Readers
    })
    .ToList();

    Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(booksReadByGroups));
}

输出(手动格式)

[{
 "Books": [{
 "Title": "How to Do This Double List","Pages": 105 }
 ],"Readers": [{
 "Name": "Kyle","Age": 29 },{
 "Name": "Bob","Age": 34 }
 ]
},{
 "Books": [{
 "Title": "Gone With Jon Skeet","Pages": 192 },{
 "Title": "Why Is This So Hard?","Pages": 56 }
 ],{
 "Name": "James","Age": 45 },{
 "Name": "Brian","Age": 15 }
 ]
},{
 "Books": [{
 "Title": "Impostor Syndrome","Pages": 454 },{
 "Title": "Self Doubt and You","Pages": 999 }
 ],"Age": 29 }
 ]
}
]
,

假设书名和读者名是ID。

var results = flatManyToMany
.GroupBy(f => new { f.BookTitle,f.BookPages })
.Select(g => new
{
    Book = new Book() { Title = g.Key.BookTitle,Pages = g.Key.BookPages },Readers = g.Select(i => new Reader() { Name = i.ReaderName,Age = i.ReaderAge })
})
.GroupBy(i => string.Concat(i.Readers.Select(r => r.Name).Distinct()))
.Select(g => new ResultDoubleList()
{
    Books = g.Select(i => i.Book).ToList(),Readers = g.SelectMany(i => i.Readers).GroupBy(r => r.Name).Select(r => r.First()).ToList()
})
;
    foreach(var result in results)
    {
        Console.WriteLine("Result:");
        Console.WriteLine("\tBooks:");
        foreach(var b in result.Books)
        {
            Console.WriteLine($"\t\t{b.Title}");
        }
        Console.WriteLine("\tReaders:");
        foreach (var reader in result.Readers)
        {
            Console.WriteLine($"\t\t{reader.Name}");
        }
    }


本文链接:https://www.f2er.com/3140933.html

大家都在问