Im getting a table Tags from the db.
the table has columns ID and TagName
I'm doing something like this to get a list of strings:
var taglist = Model.Tags.Select(x => x.TagName.ToLower()).ToArray();
then I'm comparing against another string array to get the strings that occur in both:
var intersectList = tagList.Intersect(anotherList);
I have my list, but now I also want the ID of each item remaining in the intersect list that corresponds to the tagList. (can just be an int array)
Can anyone help with a good way to do this?
Don't use intersect, it only works for collections of the same type. You could do a simple join or other form of filtering. It would be easiest to throw the string list into a HashSet and filter by tags that contain TagNames in that set. This way, you keep your tags unprojected so they keep their ids and other properties.
var stringSet = anotherList.ToHashSet(StringComparer.OrdinalIgnoreCase);
var tagList = Model.Tags.Where(t => stringSet.Contains(t.TagName)).ToList();
And put them into a list. Don't throw them into an array unless you specifically need an array (for use in a method that expects an array).
Could you do:
var intersectIds = Model.Tags
.Where(tag => anotherList.Contains(tag.TagName))
.Select(tag => tag.Id)
.ToList();
Maybe use Dictionary<int, string> instead of Array?
Related
I have a list of duplicate names and I want to get the list without the duplicates.
CSVCategories = from line in File.ReadAllLines(path).Skip(1)
let columns = line.Split(',')
select new Category
{
Name = columns[9]
};
var results = CSVCategories.GroupBy(x => x.Name)
.Select(g => g.FirstOrDefault())
.ToList();
I try to look at the elements and debug using the following loop, but it still returns the duplicates from the list including empty strings for null values:
foreach(var item in results)
{
Console.WriteLine(item.Name);
}
Calling Distinct does not work most likely because your Category class does not have proper implementation of Equals and GetHashCode.
You have two options. Properly overwrite Equals and GetHashCode methods, or use Hashset to check if Name is not already added.
var uniqueNames = new Hashset<string>();
// Original select statement
CSVCategories = CSVCategories.Where(x => uniqueName.Add(x.Name)).ToList();
Linq encourages immutability so it never modifies your input collection. So Distinct() returns a new collection rather modified the collection inline. Try:
foreach(var item in CSVCategories.Distinct())
{
Console.WriteLine(item.Name);
}
I noticed that the results variable brought me back a list containing duplicates, but only that were different in their casing.
E.g. My original list CSVCategories contained the elements: ["Home", "home", "EmptyString", "home", "Town", "Town", "Park"]
When de-duplicating with GroupBy, the results query returned ["Home", "home", "EmptyString", "Town", "Park"], so it kind of worked. Keeping values that are empty and those that have a different casing.
Now I need to find a way to remove casing duplicates and empty strings.
I am trying to concate List<> as follows-
List<Student> finalList = new List<Student>();
var sortedDict = dictOfList.OrderBy(k => k.Key);
foreach (KeyValuePair<int, List<Student>> entry in sortedDict) {
List<Student> ListFromDict = (List<Student>)entry.Value;
finalList.Concat(ListFromDict);
}
But no concatenation happens. finalList remains empty. Any help?
A call to Concat does not modify the original list, instead it returns a new list - or to be totally accurate: it returns an IEnumerable<string> that will produce the contents of both lists concatenated, without modifying either of them.
You probably want to use AddRange which does what you want:
List<Student> ListFromDict = (List<Student>)entry.Value;
finalList.AddRange(ListFromDict);
Or even shorter (in one line of code):
finalList.AddRange((List<Student>)entry.Value);
And because entry.Value is already of type List<Student>, you can use just this:
finalList.AddRange(entry.Value);
Other answers have explained why Concat isn't helping you - but they've all kept your original loop. There's no need for that - LINQ has you covered:
List<Student> finalList = dictOfList.OrderBy(k => k.Key)
.SelectMany(pair => pair.Value)
.ToList();
To be clear, this replaces the whole of your existing code, not just the body of the loop.
Much simpler :) Whenever you find yourself using a foreach loop which does nothing but build another collection, it's worth seeing whether you can eliminate that loop using LINQ.
You may want to read up the documentation on Enumerable.Concat:
Return Value
Type: System.Collections.Generic.IEnumerable
An IEnumerable that contains the concatenated elements of the two input sequences.
So you may want to use the return value, which holds the new elements.
As an alternative, you can use List.AddRange, which Adds the elements of the specified collection to the end of the List.
As an aside, you can also achieve your goal with a simple LINQ query:
var finalList = dictOfList.OrderBy(k => k.Key)
.SelectMany(k => k.Value)
.ToList();
As specified here, Concat generates a new sequence whereas AddRange actually adds the elements to the list. You thus should rewrite it to:
List<Student> finalList = new List<Student>();
var sortedDict = dictOfList.OrderBy(k => k.Key);
foreach (KeyValuePair<int, List<Student>> entry in sortedDict) {
List<Student> ListFromDict = (List<Student>)entry.Value;
finalList.AddRange(ListFromDict);
}
Furthermore you can improve the efficiency a bit, by omitting the cast to a List<T> object since entry.Value is already a List<T> (and technically only needs to be an IEnumerable<T>):
var sortedDict = dictOfList.OrderBy(k => k.Key);
foreach (KeyValuePair<int, List<Student>> entry in sortedDict) {
finalList.AddRange(entry.Value);
}
Concat method does not modify original collection, instead it returns brand new collection with concatenation result. So, either try finalList = finalList.Concat(ListFromDict) or use AddRange method which modifies target list.
My method will need to return a collection that contains settings.AgentIds and agent ids that correspond to settings.LabelIds. So I decided to use Union
IEnumerable<IAgentsGroup> labelGroups = _agentsGroups.Values.Where(x => settings.LabelIds.Contains(x.Id));
IEnumerable<IEnumerable<Guid>> labelAgentIds = labelGroups.Select(x => x.AgentIds);
return labelAgentIds.Union(settings.AgentIds);
But I dont now how to combine it into the one Collection<Guid>,
beacuse settings.AgentIds has type Collection<Guid> and labelAgentIds has IEnumerable<IEnumerable<Guid>> type?
If you're content to just flatten the IEnumerable<IEnumerable<Guid>> into an IEnumerable<Guid> in the obvious way, then SelectMany is your friend:
IEnumerable<Guid> labelAgentIds = labelGroups.SelectMany(x => x.AgentIds);
return labelAgentIds.Union(settings.AgentIds);
Whereas Select maps each single element in the input to another single element in the output, SelectMany maps each single element in the input to any number of elements in the output (maybe 0, maybe 1, maybe more).
You can use SelectMany to flatten the collection:
IEnumerable<Guid> labelAgentIds = labelGroups.SelectMany(x => x.AgentIds);
I think you want to flatten the IEnumerable<Collection<Guid>>, then use SerlectMany:
var allGuids = labelAgentIds.SelectMany(col => col).Union(settings.AgentIds);
Collection<Guid> result = new Collection<Guid>(allGuids.ToList());
Say I have a list of objects like so
list <type>
---------------
o1: date-23.03
o2: date-23.03
o3: date-24.05
o4: date-25.05
How to make another list that contains inner lists of objects that has the same date? For example:
new list<list<type>>
-----------------------
List<type> innerList1 {o1, o2}
List<type> innerList2 {o3}
List<type> innerList3 {o4}
Possible LINQ solutions would be cool, but an algorithm would be nice too.
Don't use a List<object> but a List<RealClass>
Presuming that it's actually a known type and that it's a DateTime property:
List<List<ClassName>> objectsbyDate = listOfObjects
.GroupBy(x => x.DateTimeProperty.Date)
.Select(g => g.ToList())
.ToList();
If it's actually string property as commented, why is that so? You should fix that. However, if you insist on a string you can still use Enumerable.GroupBy. But what if two objects have different years? You won't even mention it since the year is not stored.
Instead convert a DateTime to string at the very last step if you want to display it.
Grouping by your date-object:
List<object> list = new List<object> {o1,o2,o3,o4};
var result = list.GroupBy(g => g);
foreach(var group in result) {
Console.WriteLine(group.Key);
}
I have a List<int> ListOfIDs containing some numbers which are IDs.
I have a List<CustomClass> ListOfObjects containing some objects, which properties reflecting their IDs.
I've searched high and low for a Linq query that will allow me to return from my List a sublist of only those objects which have an ID that is contained within the List.
My attempt does not compile and I cannot seem to correct the syntax :
List<CustomClass> SubList = ListOfObjects.Where(ListOfIDs.Contains(p => p.ID))
Thanks very much.
I think you want to do like this?
List<CustomClass> SubList = ListOfObjects
.Where(obj => ListOfIDs.Contains(obj.ID))
.ToList();
I think this is what you need:
List<CustomClass> SubList = ListOfObjects.Where(p => ListOfIDs.Contains(p.ID)).ToList();
Don't forget to call ToList() in the end.
Also consider using HashSet for ListOfIDs, because complexity of Contains operation is just O(1). But, well it depends on how much data you have.
Here's the correct syntax for what you're trying to do:
... ListOfObjects.Where(p => ListOfIDs.Contains(p.ID)).ToList();
Though this might be faster that the Where(Contains) method:
var sublist = (
from obj in ListOfObjects
join id in ListOfIDs on id equals obj.ID
select obj ).ToList();
Try to use this piece of code snippet:
List<CustomClass> SubList = ListOfObjects.Where(o => ListOfIDs.Contains(o.ID))
.ToList();