简体   繁体   中英

LinQ C# - Grouping a list of objects depending on strings contained in their List<string> property

I have a problem with grouping on a List of strings.

My class "Post" contains a List property called Tags:

public class Post
{
    public string Title { get; set; }
    public int Position { get; set; }
    public List<string> Tags { get; set; }
}

Somewhere else in my code I am gathering all my posts in a List. Now, many of these posts share tags with each other. Assume:

Post_1.Tags = new List<string>(){"a", "b", "c"};
Post_2.Tags = new List<string>(){"a", "d", "e"};
Post_3.Tags = new List<string>(){"b", "c", "d"};
Post_4.Tags = new List<string>(){"c", "d", "f"};

I am trying to obtain a collection with this kind of structure:

Key: a; Values: Post_1, Post_2;
Key: b; Values: Post_1, Post_3;
Key: c; Values: Post_1, Post_3, Post_4;
Key: d; Values: Post_2, Post_3, Post_4;
Key: e; Values: Post_2;
Key: f; Values: Post_4;

I guess I need to use GroupBy(), but I'm stuck with the syntax, since:

List<Post> Posts = // all my posts
ILookup<string, Post> ByTag = Posts.GroupBy(p => p.Tags) ...

is obviously targeting the List of tags and not the single tags.

Any suggestions?

var tags = Posts.SelectMany(p => p.Tags).Distinct();
var groups = from p in Posts
    from t in tags.Where(tag => p.Tags.Contains(tag))
    group p by t
    into gr
    select gr;

Explanation SelectMany.Distinct - makes IEnumerable, which contains all tags from all posts without duplicates. First 2 from - joins tags and posts. Condition of joining - post should contain tag. Resulting collection will every possible pair of post and it's tag. Group will group those pairs in a way, that gr's key will be a tag and gr will be collection of all posts with that tag. I got used to dictionaries and wrote a code which can be later used converted to Dictionary<string, List<Post>> by calling ToDictionary(g => g.Key, g => g.ToList()) . If you want a Lookup, you don't need group part:

var tags = Posts.SelectMany(p => p.Tags).Distinct();
var postAndTagPairs = from p in Posts
    from t in tags.Where(tag => p.Tags.Contains(tag))
    select new {t, p};
var lookup = postAndTagPairs.ToLookup(pat => pat.t, pat => pat.p);

You first need to get a distinct list of tags. Assuming the posts are in a list like this:

var posts = new List<Post>
{
    new Post { Tags = new List<string>(){"a", "b", "c"} },
    new Post { Tags = new List<string>(){"a", "d", "e"} },
    new Post { Tags = new List<string>(){"b", "c", "d"} },
    new Post { Tags = new List<string>(){"c", "d", "f"} }
};

You get the tags like this:

var allTags = posts.SelectMany(p => p.Tags).Distinct();

Then you can do your grouping:

var result = from p in posts
             from t in allTags.Where(t => p.Tags.Contains(t))
             group p by t into grp
             select grp;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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