【问题标题】:UNION from multiple lists来自多个列表的 UNION
【发布时间】:2021-04-10 12:54:04
【问题描述】:

我有一个class book,它有一个属性IEnumerable<string> Tags

public class Book
{
    public Book(string title, IEnumerable<string> tags)
        => (Title, Tags) = (title, tags);

    public string Title { get; }
    public IEnumerable<string> Tags { get; }
}

现在我有了IEnumerable&lt;Book&gt; books,我只需要获取 ALL 对象之间的公共标签。

public static void TestMethod()
{
    var book1 = new Book("Book1", new List<string> { "c#", ".net", "programming" });
    var book2 = new Book("Book2", new List<string> { "ado", "c#" });
    var book3 = new Book("Book3", new List<string> { "programming", "c#" });
    var book4 = new Book("Book4", new List<string> { "ef-core", "database", ".net" });
    var book5 = new Book("Book5", new List<string> { ".net", "visual-studio", "c#", "programming" });

    var list1 = new List<Book> { book1, book2, book3 };
    var list2 = new List<Book> { book1, book3, book5 };
    var list3 = new List<Book> { book1, book4, book5 };
    var list4 = new List<Book> { book1, book2, book4 };


    // expected returns:
    // list1 => { "c#" }
    // list2 => { "c#", "programming" }
    // list3 => { ".net" }
    // list4 => { }

    Console.WriteLine($"list1 => {{{string.Join(';', GetCommonTags(list1))}}}");
    Console.WriteLine($"list2 => {{{string.Join(';', GetCommonTags(list2))}}}");
    Console.WriteLine($"list3 => {{{string.Join(';', GetCommonTags(list3))}}}");
    Console.WriteLine($"list4 => {{{string.Join(';', GetCommonTags(list4))}}}");
}

我真的不知道这样做的正确方法。直到那一刻,我都以这种可怕的方式来做这件事(在巴西,我们称之为“gambiarra”,或英语中的“Workaround”):

public static IEnumerable<string> GetCommonTags(IEnumerable<Book> books)
{
    var allTags = books.SelectMany(b => b.Tags).Distinct();
    var commonTags = new List<string>();

    foreach (var tag in allTags)
    {
        var isCommonTag = true;
        foreach (var book in books)
        {
            if (!book.Tags.Contains(tag))
            {
                isCommonTag = false;
                break;
            }
        }

        if (isCommonTag) commonTags.Add(tag);
    }

    return commonTags;
}

它正在工作,但如何正确地做到这一点,或者使用 linq?

【问题讨论】:

    标签: c# .net linq collections


    【解决方案1】:

    使用Aggregate方法,你可以Intersect跨越所有标签,使用default(IEnumerable&lt;string&gt;)作为标志来获取第一组Tags

    public IEnumerable<string> GetCommonTags(IEnumerable<Book> books)
        => books.Aggregate(default(IEnumerable<string>),
                           (commonTags, book) => commonTags == default(IEnumerable<string>)
                                                    ? book.Tags
                                                    : commonTags.Intersect(book.Tags));
    

    【讨论】:

    • 谢谢@NetMage,我真的很喜欢你的解决方案,今天学到了一种新方法:)。我之前没有使用过Aggregate,所以会读到。谢谢。
    • @user1672994 Aggregate 有一些有趣的用途,它与 APL reduce 运算符 (/) 相当。编写您自己的与 APL 扫描运算符 (\) 等效的 LINQ 也可用于简洁的 LINQ 处理。
    【解决方案2】:

    HashSet&lt;Tag&gt; 在这里会很有效,并且只需要通过一次:

    var result = new HashSet(b[0].Tags);   //Init with first book
    for(var i = 1; i < b.Count; i++)
        result.IntersectWith(b[i].Tags);
    

    如果bIEnumerable,那就有点复杂了。

    【讨论】:

    • 我试过了。改成IList可以工作,但结果是list1 => {c#;.net;programming} list2 => {c#;.net;programming} list3 => {c#;.net;programming} list4 => {c#;。网络;编程}
    【解决方案3】:

    您可以使用All linq 操作

    public static IEnumerable<string> GetCommonTags(IEnumerable<Book> books)
    {
        var allTags = books.SelectMany(b => b.Tags).Distinct()
                         .Where(t => books.All(b => b.Tags.Contains(t)));
        return allTags;
    }
    

    上述代码首先获取所有Tags,并使用All 验证标签是否是books collection 的一部分,该All 确定序列的所有元素是否满足条件。

    你可以查看这个小提琴https://dotnetfiddle.net/aHzyP0,它演示了你的场景。

    【讨论】:

    • books.Count()上似乎是O(n^2)?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-20
    • 2023-03-04
    • 1970-01-01
    • 1970-01-01
    • 2017-07-10
    相关资源
    最近更新 更多