Select and group by string occurrences using LINQ? - c#

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();

Related

Using lambda and linq expressions to get list of integer IDs returned

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
});

Group inside group linq

I have a datatable like this:
I want to group this table For FIELD A and FIELD B,
and the third field of my group should be lists of FIELD C, but it must be grouped by ID field.
At the end, the result should be like this:
First Field | Second Field | Third Field
------------+--------------+----------------
5 | XXXX |(8) (2,6,3) (9)
5 | KKKK |(8,3)
The third field must be a list of lists.
How can i do this with LINQ?
I tried this so far:
var trytogroup = (from p in datatable.AsEnumerable()
group p by new
{
ID = p["Id"].ToLong(),
FieldA = p["FieldA"].ToLong(),
FieldB = p["FieldB"].ToString()
} into g
select new
{
FirstField = g.Key.FieldA,
SecondField = g.Key.FieldB,
ThirdField = datatable.AsEnumerable().Where(p => p["FieldA"].ToLong() == g.Key.FieldA && p["FieldB"].ToString() == g.Key.FieldB).Select(p => p["FieldC"].ToLong()).GroupBy(x => x["Id"].ToLong()).Distinct().ToList()
});
What's wrong with your query:
You don't need to group by three fields on first place. Grouping by ID should be done within group which you have by FieldA and FieldB
When getting ThirdField you don't need to query datatable again - you already have all required data. You just need to add grouping by ID
Correct query:
from r in datatable.AsEnumerable()
group r by new {
FieldA = r.Field<long>("FieldA"),
FieldB = r.Field<string>("FieldB")
} into g
select new
{
First = g.Key.FieldA,
Second = g.Key.FieldB,
Third = g.GroupBy(r => r.Field<long>("ID"))
.Select(idGroup => idGroup.Select(i => i.Field<long>("FieldC")).ToList())
}
If you prefere lambdas, your query could look like:
dataSource
.GroupBy(item => new { item.FieldA, item.FieldB })
.Select(group => new
{
First = group.Key.FieldA,
Second = group.Key.FieldB,
Third = group.GroupBy(q => q.Id).Select(q => q.Select(e => e.FieldC).ToArray()).ToArray()
}).ToArray();
Just few small notes. .GroupBy uses Lookup to get the Groupings, so some overhead can be avoided by replacing .GroupBy( with .ToLookup( when deffered execution is not needed.
The elements in each Grouping are stored in array, so I don't see much use in converting them .ToList (but you can save a bit of space if you convert them .ToArray).
DataTable.AsEnumerable uses .Rows.Cast<TRow>(), but also seems to do some extra stuff when there is any DataView sorting or filtering that are usually not needed.
var groups = datatable.Rows.Cast<DataRow>()
.ToLookup(r => Tuple.Create(
r["FieldA"] as long?,
r["FieldB"]?.ToString()
))
.Select(g => Tuple.Create(
g.Key.Item1,
g.Key.Item2,
g.ToLookup(r => r["ID"] as long?, r => r["FieldC"] as long?)
)).ToList();
As usual, premature optimization is the root of all evil but I thought the information might be useful.

How to c# List<> order by and Group by according to parameters?

I have a class and its List
abc cs = new abc();
List<abc> Lst_CS = new List<abc>();
and I set some value by HidenField in foreach loop
foreach (blah blah)
{
cs = new abc{
No = VKNT,
GuidID=hdnGuidID.Value.ToString(),
RecID=hdnRecID.Value.ToString(),
Date=HdnDate.Value.ToString()
};
Lst_CS.Add(cs);
}
and finally I get a List_CS and I order by Lst_CS according to Date like this;
IEnumerable<abc> query = Lst_CS.OrderBy(l => l.Date).ToList();
but in extra, I want to group by according to No.
Briefly, I want to order by Date and then group by No on Lst_CS How can I do ?
Thanks for your answer
Well you just just do the ordering then the grouping like so:
Lst_CS.OrderBy(l => l.Date)
.GroupBy(l => l.No)
.ToList();
Each list of items in each group will be ordered by date. The groupings will be in the order that they are found when the entire list is ordered by date.
Also your ForEach can be done in one Linq statement, then combined with the ordering and grouping:
var query = blah.Select(b => new abc{
No = VKNT,
GuidID=hdnGuidID.Value.ToString(),
RecID=hdnRecID.Value.ToString(),
Date=HdnDate.Value.ToString()
})
.OrderBy(l => l.Date)
.GroupBy(l => l.No)
.ToList();

Using LINQ on ObservableCollection with GroupBy and Sum aggregate

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)
});

LINQ using C# question

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"];

Categories