I have a List<T> that contains some user defined class data.
I want to find the unique instances of a combination of 2 data fields.
For example, if the entries contain the fields Name and Age, I want the unique cases of the Name and Age combination, e.g. Darren 32, should only be retrieved once, even if it is in the list multiple times.
Can this be achieved with LINQ?
Thanks.
You need to extract only these data fields and make them unique:
var result = list
.Select(x => new { Age = a.Age, Name = x.Name})
.Distinct();
This creates a IEnumerable of a anonymous type which contains a Age and Name property.
If you need to find the items behind the unique data, you need GroupBy. This will provide the list with the single items behind each group.
var result = list
.GroupBy(x => new { Age = a.Age, Name = x.Name});
foreach (var uniqueItem in result )
{
var age = uniqueItem.Key.Age;
var name = uniqueItem.Key.Name;
foreach (var item in uniqueItem)
{
//item is a single item which is part of the group
}
}
myList.Select(l => new { l.Name, l.Age })
.Distinct()
.ToDictionary(x => x.Name, x => x.Age)
You'll have to write your own equality comparer, and use Linq's Distinct function.
Have a look at the Distinct extension method
Easy:
var people = new List<Person>();
// code to populate people
var uniqueNameAges =
(from p in people
select new { p.Name, p.Age }).Distinct();
And then to a dictionary:
var dictionary =
uniqueNameAges
.ToDictionary(x => x.Name, x => x.Age);
Or to a lookup (very much like Dictionary<string, IEnumerable<int>> in this case):
var lookup =
uniqueNameAges
.ToLookup(x => x.Name, x => x.Age);
If you then have people named "John" with distinct ages then you could access them like so:
IEnumerable<int> ages = lookup["John"];
Related
I'm new to Lambda and LINQ expressions but I have this so far:
// Sample setup/data (all of it)
int[] placeIds = {1, 2, 3, 4, 5};
string[] animals = { "Cow", "Camel", "Elephant", "Goat", "Dog" };
var placeIdsList = new List<int>(placeIds);
var animalsList = new List<string>(animals);
So in the above setup, animals are unique and belong to places which have unique ids. I have a list of place ids and animals being input and I want to find the ids of the places an animal belongs to. Each place can only have one animal (so one to one relationship for this exercise).
int[] placeIdsInput = {1, 3, 5};
string[] animalsInput = { "Elephant", "Dog" };
var placeIdsInputList = new List<int>(placeIdsInput);
var animalsInputList = new List<string>(animalsInput);
In theory this will return the placeIds of the elephant and the dog which let's say is 3 and 5. PlaceId's being input will always have a count >= count of animals input.
I am trying this but need help on the finishing step (I think):
var placeIdsOfAnimals = _getService.QueryWithNoTracking<Places>()
.Include(i => i.Animals)
.Where(q => q.Contains(placeIdsInputList))
.Select(q => new AnimalDTO
{
Id = q.Id,
Animal = q.Animal
})
.ToList();
Now how do I pass in the animals I want to look for to get the place Ids of them returned?
Inside your Where-Function you can modify the lambda so that it filters for only the animals that are inlcuded in your animalsInputList and only the placeIds that are included in your placeIdInputList. It would look like that:
var placeIdsOfAnimals = _getService.QueryWithNoTracking<Places>()
.Include(i => i.Animals)
.Where(q => q.Contains(placeIdsInputList)
&& animalsInputList.Contains(q.Animal)
&& placeIdsInputList.Contains(q.Id))
.Select(q => new AnimalDTO
{
Id = q.Id,
Anaiml = q.Animal
})
.ToList();
(The last part could already be covered by your q.Contains(placeIdInputList) - but I am not sure how your Places and Animals data structure looks like internally, so I included it in my query)
If you now want it to only return the list of place Ids, you could exchange your current Select with the following:
.Select(q => q.Id)
Well, I would create a dictionary to keep the values of animals and its place holder ids at first:
var dataSource = animals
.Zip(placeIds, (animal, placeId) => new { Key = animal, Value = placeId})
.ToDictionary(x => x.Key, x => x.Value);
Then, would use LINQ to fetch the animal Ids:
var output = animalsInput.Select(x => new
{
Id = dataSource.TryGetValue(x, out var placeId) ? placeId : -1,
Animal = x
});
I have some data that I would like to count and group by. It looks like this:
Cat Cat Dog Dog Dog
My goal is to get a list of objects that have the name of the animal and the number of times it appears in the data set
Object 1
Name: Cat
NumberAppearances: 2
Object 2
Name: Dog
NumberAppearances: 3
I am trying to do this with a LINQ query and this is what I have but the count is wrong. I think it's counting the length instead of the number of times it appears. How would I modify this?
animalData.Select(x => x.AnimalType).Distinct().ToList().ForEach(a =>
AnimalObject animal = new AnimalObject();
animal.Name = a.Name;
animal.Number = a.Distinct().Count();
animalList.Add(animal);
});
This is all you need to accomplish your task:
var result = animalData.GroupBy(x => x.AnimalType).Select(g => new AnimalObject
{
Name = g.Key,
Number = g.Count()
}).ToList();
foreach (var e in result)
Console.WriteLine($"Name: {e.Name} \n NumberAppearances: {e.Number}");
note -
as your LINQ query is currently written, you need not call distinct nor ToList as the former will result
in the incorrect outcome and the latter is unnecessary.
stick to good naming conventions, you don't need the Object suffix after the type name so just Animal will suffice.
You can use this one without creating a new object, with the help of Dictionary:
var animalCounts = animalList.GroupBy(a => a.AnimalType).ToDictionary(
grp => grp.Key, grp => grp.ToList().Count());
foreach(var animalCount in animalCounts) {
Console.WriteLine($"Name: {animalCount.Key}{Environment.NewLine}NumberOfApperances: {animalCount.Value}");
}
Group the data by AnimalType, then transform it to AnimalObject.
var result = animalData.GroupBy(x => x.AnimalType)
.Select(g => new AnimalObject { Name = g.Key, Number = g.Count() })
.ToList();
As a follow up to my last question here:
Filtering a list of HtmlElements based on a list of partial ids
I need to take this statement:
doc.All.Cast<HtmlElement>()
.Where(x => x.Id != null)
.Where(x => ids
.Any(id => x.Id.Contains(id))).ToList();
and join it with an array of strings called fields. Assuming the array and list will have the same amount of elements each and line up correctly. I tried using Zip() but thought I might need to use an additional linq statement to make it work.
Assuming that fieldList[0] and IdList[0] corresponding to each other, you can do the following:
var IdList = doc.All.Cast<HtmlElement>()
.Where(x => x.Id != null)
.Where(x => ids
.Any(id => x.Id.Contains(id))).ToList();
var resultList = fieldList
.Select( (item, index) => new { Field = item, Id = IdList[index] })
.ToDictionary(x => x.Id, x => x.Field);
You have mentioned it already, you can use Enumerable.Join:
var joined = from id in fields
join ele in elements on id equals ele.Id
select new { Element = ele, ID = id };
var dict = joined.ToDictionary(x => x.ID, x => x.Element);
I've presumed that you want to join them via ID. I've also presumed that the string[] contains only unique ID's. Otherwise you need to use Distinct.
I am currently using Linq to retrieve a list of distinct values from my data table. I am then looping through the list and again calling a linq query to retrieve a list of values for each
value in the first list.
_keyList = new SortedList<int, List<int>>();
var AUGroupList = ProcessSummaryData.AsEnumerable()
.Select(x => x.Field<int>("AUGroupID"))
.Distinct()
.ToList<int>();
foreach (var au in AUGroupList)
{
var AUList = ProcessSummaryData.AsEnumerable()
.Where(x => x.Field<int>("AUGroupID") == au)
.Select(x => x.Field<int>("ActivityUnitID"))
.ToList<int>();
_keyList.Add(au, AUList);
}
I am then adding the value to a sorted list along with the corresponding second list.
How can I combine the above two queries into one Linq query so that I don't have to call them separately?
You should be able to do something like:
var groupQuery = from d in ProcessSummary.AsEnumerable()
group d by new { Key = d.Field<int>("AUGroupID") } into g
select new { GroupID = g.Key, Values = g.Distinct().ToList() };
Then you can loop through the groupQuery and populate the sorted list. The Key property will contain the group id, and the Values property will have a distinct list of values.
Have you tried this?
var _keyList = new SortedList<int, List<int>>();
var AUGroupList = ProcessSummaryData.AsEnumerable()
.Select(x => x.Field<int>("AUGroupID"))
.Distinct()
.Where(x => x.Field<int>("AUGroupID") == au)
.Select(x => x.Field<int>("ActivityUnitID"))
.ToList<int>();
_keyList.Add(au, AUList);
}
Your provider should cope with that, if not there's a few other ways.
I have the following block of code which works fine;
var boughtItemsToday = (from DBControl.MoneySpent
bought in BoughtItemDB.BoughtItems
select bought);
BoughtItems = new ObservableCollection<DBControl.MoneySpent>(boughtItemsToday);
It returns data from my MoneySpent table which includes ItemCategory, ItemAmount, ItemDateTime.
I want to change it to group by ItemCategory and ItemAmount so I can see where I am spending most of my money, so I created a GroupBy query, and ended up with this;
var finalQuery = boughtItemsToday.AsQueryable().GroupBy(category => category.ItemCategory);
BoughtItems = new ObservableCollection<DBControl.MoneySpent>(finalQuery);
Which gives me 2 errors;
Error 1 The best overloaded method match for 'System.Collections.ObjectModel.ObservableCollection.ObservableCollection(System.Collections.Generic.List)' has some invalid arguments
Error 2 Argument 1: cannot convert from 'System.Linq.IQueryable>' to 'System.Collections.Generic.List'
And this is where I'm stuck! How can I use the GroupBy and Sum aggregate function to get a list of my categories and the associated spend in 1 LINQ query?!
Any help/suggestions gratefully received.
Mark
.GroupBy(category => category.ItemCategory); returns an enumerable of IGrouping objects, where the key of each IGrouping is a distinct ItemCategory value, and the value is a list of MoneySpent objects. So, you won't be able to simply drop these groupings into an ObservableCollection as you're currently doing.
Instead, you probably want to Select each grouped result into a new MoneySpent object:
var finalQuery = boughtItemsToday
.GroupBy(category => category.ItemCategory)
.Select(grouping => new MoneySpent { ItemCategory = grouping.Key, ItemAmount = grouping.Sum(moneySpent => moneySpent.ItemAmount);
BoughtItems = new ObservableCollection<DBControl.MoneySpent>(finalQuery);
You can project each group to an anyonymous (or better yet create a new type for this) class with the properties you want:
var finalQuery = boughtItemsToday.GroupBy(category => category.ItemCategory);
.Select(g => new
{
ItemCategory = g.Key,
Cost = g.Sum(x => x.ItemAmount)
});
The AsQueryable() should not be needed at all since boughtItemsToday is an IQuerable anyway. You can also just combine the queries:
var finalQuery = BoughtItemDB.BoughtItems
.GroupBy(item => item.ItemCategory);
.Select(g => new
{
ItemCategory = g.Key,
Cost = g.Sum(x => x.ItemAmount)
});