I'm trying to write a single Linq Query that can concat inner collections of tags from an outer collection of parent products and return a collection of Tags with a consolidated count ordered decending by count.
Here's a working solution that I currently have and am looking for help to convert to a single Linq Query.
Module Module1
Class Tag
Property Name As String
Property Count As Integer
End Class
Class Product
Property Name As String
Property tags As List(Of Tag)
End Class
Sub Main()
Dim Products As New List(Of Product) From {
{New Product With {.Name = "P1",
.tags = New List(Of Tag) From {{New Tag With {.Name = "T1"}},
{New Tag With {.Name = "T3"}},
{New Tag With {.Name = "T5"}}
}
}
},
{New Product With {.Name = "P2",
.tags = New List(Of Tag) From {{New Tag With {.Name = "T2"}},
{New Tag With {.Name = "T4"}},
{New Tag With {.Name = "T6"}}
}
}
},
{New Product With {.Name = "P3",
.tags = New List(Of Tag) From {{New Tag With {.Name = "T2"}},
{New Tag With {.Name = "T3"}},
{New Tag With {.Name = "T4"}}
}
}
},
{New Product With {.Name = "P4",
.tags = New List(Of Tag) From {{New Tag With {.Name = "T4"}},
{New Tag With {.Name = "T5"}},
{New Tag With {.Name = "T6"}},
{New Tag With {.Name = "T7"}},
{New Tag With {.Name = "T8"}}
}
}
}
}
Dim ReportingTags As New List(Of Tag)
'-- Start : Needs to be converted to pure Linq (if possible)------------------------------------------
Dim InterimTags As New List(Of String)
For Each p As Product In Products
Dim TmpTags As New List(Of String)
InterimTags.AddRange(TmpTags.Concat(From t In p.tags Select t.Name))
Next
ReportingTags.AddRange((From t In InterimTags
Group By name = t Into Count = Count()
Select New Tag With {.Name = name,
.Count = Count}).OrderByDescending(Function(t) t.Count))
'-- End ----------------------------------------------------------------------------------------------
For Each t As Tag In ReportingTags
Console.WriteLine("Tag: {0} - Count: {1}", t.Name, t.Count)
Next
Console.ReadLine()
End Sub
End Module
I'll be taking the output and converting it to a observablecollection(of TagModel) - so I'm trying to eliminate double/triple handling of the data.
Thanks
Graeme
I'm a C# guy, but Linq is Linq, so I figured I'd take a stab. I was able to match your results by putting the following in your red section:
Dim outStuff = From t In Products.SelectMany(Function(p) p.tags)
Group By tagName = t.Name
Into g = Group, Count()
Order By Count Descending
And then I used
For Each t In outStuff
Console.WriteLine("Tag: {0} - Count: {1}", t.tagName, t.Count)
Next
to do the output.
I'm SURE this isn't the most graceful or elegant VB, and you can probably refine the Linq to be a little prettier, but it should be a good start for you.
The sample code in your question helped a lot to get this done.
For a quick explanation, if you're not familiar with SelectMany or Group By, they do a couple of different and extremely useful things.
SelectMany works like Select, except that when applied to a collection property, the contents of all of the collection properties are flattened out and returned as a single collection of the appropriate type, instead of being returned as a collection of collections. In this case, it gets all the tags from all the products, and puts them into one collection to work with.
Group By does sort of the opposite, and takes a big flat group, and turns it into a collection of collections, grouped according the criteria designated (t.Name in this case).