I have a C#.NET application and want to do Group By on multiple conditions.
I have a list like this:
var testq = new List<TestQuestion>()
{
new TestQuestion
{
Id = 1,
QuestionId = 1,
SelectedAnswerId = null
},
new TestQuestion
{
Id = 2,
QuestionId = 2,
SelectedAnswerId = 1
},
new TestQuestion
{
Id =3,
QuestionId = 1,
SelectedAnswerId = 1
},
new TestQuestion
{
Id = 4,
QuestionId = 3,
SelectedAnswerId = 5
},
new TestQuestion
{
Id = 5,
QuestionId = 1,
SelectedAnswerId = 2
},
new TestQuestion
{
Id = 6,
QuestionId = 3,
SelectedAnswerId = 3
},
new TestQuestion
{
Id =7,
QuestionId = 4,
SelectedAnswerId = null
},
new TestQuestion
{
Id =8,
QuestionId = 5,
SelectedAnswerId = null
},
};
My code is :
var result = testq
.Where(p => p.SelectedAnswerId.HasValue)
.GroupBy(p => p.QuestionId)
.Select(p => p.FirstOrDefault())
.ToList();
now, result ID's is (2 ,3, 4)
but result is not true...
The result should be this :
ID's -> (2 ,3, 4, 7, 8)
I want to group by the result based on the QuestionID field and The first record that does not have a (SelectedAnswerId)field value is empty,
Also, records in which the question ID is only there once, regardless of the value of the field (SelectedAnswerId) in the output. that's mean, last two items in the list
please guide me...
Try this:
var result = testq
.Where(p => p.SelectedAnswerId.HasValue || testq.Count(x => x.QuestionId == p.QuestionId) == 1)
.GroupBy(p => p.QuestionId)
.Select(p => p.FirstOrDefault())
.Distinct()
.ToList();
C# Fiddle
You have to filter the SelectedAnswerId in Select not in the Where clause. Try the below,
var result = testq
.GroupBy(p => p.QuestionId)
.Select(p =>
{
var grouped = p.ToList(); //Get the groupBy list
TestQuestion testQuestion = grouped.FirstOrDefault(x => x.SelectedAnswerId.HasValue); //Get anything with AnswerId
return testQuestion ?? grouped.FirstOrDefault(); //If not not available with Answer then get the First or Default value
})
.ToList();
C# Fiddle with test data.
Although after a GroupBy the order of the elements in each group is fairly defined (see Enumerable.GroupBy, it seems that the element that you want is not the First one in each group.
You want the First element of each group that has a non-null SelectedAnswerId, or if there is no such one, you want the First element of each group that has a null SelectedAnswerId.
How about this:
var result = testQ.GroupBy(question => question.QuestionId);
// every group contains a sequence of questions with same questionId
// select the ones with a null SelectedAnswerId and the ones with a non-null value
.Select(group => new
{
NullSelectedAnswer = group
.Where(group.SelectedAnswerId == null)
.FirstOrDefault(),
NonNullselectedAnswer = group
.Where(group.SelectedAnswerId != null)
.FirstOrDefault(),
})
// if there is any NonNullSelectedAnswer, take that one, otherwise take the null one:
.Select(selectionResult => selectionResult.NonNullSelectedAnswer ??
selectionResult.NullSelectedAnswer);
Related
I need some help with some LINQ logic that I am trying to do
Using EF, I have this result set:
Basically what I want to achieve is if the user wants to find an element that has TagID 3 AND TagID 4 it should only return Low, Medium
This should ignore Low as this element doesn't have TagID 4
Also if the user just wants the elements that contain TagID 3, it should return Low, Medium and Low as both contain TagID 3
I have tried this just to get Low, Medium back (the harder logic) but to no prevail.
var result = result.Where(x => x.TagID == 3 && x.TagID == 4).ToList();
A step in the right direction is all that is needed please
This should work if tags are only available once per ID (i.e. no items with the same ID and the same tag ID).
I don't think EF will be available to translate to SQL -> materialize first.
var q = result.ToList();
var tagIDs = new HashSet<int>() { 3, 4 };
IEnumerable<string> itemContents =
q.Where(x => tagIDs.Contains(x.TagID)). // Keep only the tags we're interested in
GroupBy(x => x.Id). // Group the items by ID
Where(g => (g.Count() == tagIDs.Count)). // Select the groups having the right number of items
SelectMany(g => g.Select(x => x.ItemContent)). // Extract ItemContent
Distinct(); // Remove duplicates
I don't know if EF this swallows, here is an example:
var data = new[]
{
new { Id = 12, TagID = 3, ItemContent = "Low" },
new { Id = 13, TagID = 3, ItemContent = "Low, Medium" },
new { Id = 13, TagID = 4, ItemContent = "Low, Medium" },
};
var search = new List<int>(new[] { 3, 4 });
var result = data
// group the items on ItemContent
.GroupBy(item => item.ItemContent, d => d, (k, g) => new { ItemContent = k, g })
// only select groups when all searchitems are found in a list of TagID
.Where(groupedItem => search.All(i => groupedItem.g.Select(y => y.TagID).Contains(i)))
// select the result
.Select(groupedItem => groupedItem);
foreach (var r in result)
Console.WriteLine(r.ItemContent);
Console.ReadLine();
I want to order some posts by how many times a user has posted a post.
I have the following:
IList<User> orderTopContributors =
this.GetPosts()
.GroupBy(x => x.Author.Id)
.Select(x => new
{
AuthorCount = x.Count()
})
.OrderByDescending( x => x.AuthorCount )
.ToList();
Where am i going wrong? There is an error with the casting:
Severity Code Description Project File Line Error CS0266 Cannot
implicitly convert type 'System.Collections.Generic.List< Items>>' to
'System.Collections.Generic.IList'. An explicit conversion
exists (are you missing a cast?)
tl;dr: Use the SelectMany method
You have few mistakes:
First of all (you fixed this one in an edit), you should use OrderByDescending in order to get the order from the biggest to the smallest.
Next (you fixed this one in an edit), you are expecting to receive IList<User>, either change it to IEnumrable<User> or add .ToList() in the end of your Linq.
Lastly, if you want to flatten your groups to a single list use SelectMany and select your flattened lists:
Example code:
IList<User> orderTopContributors = GetPosts()
.GroupBy(x => x.Id)
.Select(x => new
{
AuthorCount = x.Count(),
Posts = x
})
.OrderByDescending(x => x.AuthorCount)
.SelectMany(x => x.Posts)
.ToList();
When you are using .GroupBy you turn your IEnumerable<User> to IEnumerable<IEnumerable<User>> since there are few groups (many times many), by using the SelectMany method you state which IEnumerable<T> you want to take from each group and aggregate it to the final result:
Example pseudo:
var Users = new List<User>
{
{ UserID = 576, PostId = 7 },
{ UserID = 576, PostId = 4 },
{ UserID = 4, PostId = 2 },
{ UserID = 2, PostId = 5 },
{ UserID = 2, PostId = 1 },
{ UserID = 576, PostId = 9 }
}
var Ordered = Users
.GroupBy(x => x.UserID)
.Select(x => new
{
AuthorCount = x.Count(),
Posts = x
})
.OrderByDescending(x => x.AuthorCount)
.SelectMany(x => x.Posts)
.ToList();
Ordered is now:
List<User>
{
{ UserID = 576, PostId = 7 },
{ UserID = 576, PostId = 4 },
{ UserID = 576, PostId = 9 },
{ UserID = 2, PostId = 5 },
{ UserID = 2, PostId = 1 },
{ UserID = 4, PostId = 2 }
}
suppose I have a list that comes from a database like this:
List<Item> list =
{
new Item
{
TypeID = 2,
Count = 5
},
new Item
{
TypeID = 2,
Count = 7
},
new Item
{
TypeID = 5,
Count = 2
}
};
I would like to sum up all elements with the same TypeID so that I have a final result list with two elements only:
List<Item> list =
{
new Item
{
TypeID = 2,
Count = 12
},
new Item
{
TypeID = 5,
Count = 2
}
};
How can I achive this using LINQ?
Cheers
Simon
list.GroupBy(x=>x.TypeID)
.Select(x=>new Item(){TypeID=x.Key,Count=x.Sum(y=>y.Count) })
.ToList();
You can use GroupBy to group by TypeID first and then do Sum on each group:
var result = list.GroupBy(x => x.TypeID)
.Select(g => new Item() {
TypeId = g.Key,
Count = g.Sum(x => x.Count)
})
.ToList();
Linq extension method Union.
var mergedList = list1.Union(list2).ToList();
I have an ICollection of records (userID,itemID,rating) and an IEnumerable items
for a specific userID and each itemID from a set of itemIDs, i need to produce a list of the users rating for the items or 0 if no such record exists. the list should be ordered by the items.
example:
records = [(1,1,2),(1,2,3),(2,3,1)]
items = [3,1]
userID = 1
result = [0,2]
my attempt:
dataset.Where((x) => (x.userID == uID) & items.Contains(x.iID)).Select((x) => x.rating);
it does the job but it doesn't return 0 as default value and it isnt ordered...
i'm new to C# and LINQ, a pointer in the correct direction will be very appreciated.
Thank you.
This does the job:
var records = new int[][] { new int[] { 1, 1, 2 }, new int[] { 1, 2, 3 }, new int[] { 2, 3, 1 } };
var items = new int[] { 3, 1 };
var userId = 1;
var result = items.Select(i =>
{
// When there's a match
if (records.Any(r => r[0] == userId && r[1] == i))
{
// Return all numbers
return records.Where(r => r[0] == userId && r[1] == i).Select(r => r[2]);
}
else
{
// Just return 0
return new int[] { 0 };
}
}).SelectMany(r => r); // flatten the int[][] to int[]
// output
result.ToList().ForEach(i => Console.Write("{0} ", i));
Console.ReadKey(true);
How about:
dataset.Where((x) => (x.userID == uID)).Select((x) => items.Contains(x.iID) ? x.rating : 0)
This does the job. But whether it's maintainable/readable solution is topic for another discussion:
// using your example as pseudo-code input
var records = [(1,1,2),(1,2,3),(2,3,1)];
var items = [3,1];
var userID = 1;
var output = items
.OrderByDescending(i => i)
.GroupJoin(records,
i => i,
r => r.ItemId,
(i, r) => new { ItemId = i, Records = r})
.Select(g => g.Records.FirstOrDefault(r => r.UserId == userId))
.Select(r => r == null ? 0 : r.Rating);
How this query works...
ordering is obvious
the ugly GroupJoin - it joins every element from items with all records that share same ItemId into annonymous type {ItemId, Records}
now we select first record for each entry that matches userId - if none is found, null will be returned (thanks to FirstOrDefault)
last thing we do is check whether we have value (we select Rating) or not - 0
How about this. your question sounds bit like an outer join from SQL, and you can do this with a GroupJoin, SelectMany:
var record1 = new Record() { userID = 1, itemID = 1, rating = 2 };
var record2 = new Record() { userID = 1, itemID = 2, rating = 3 };
var record3 = new Record() { userID = 2, itemID = 3, rating = 1 };
var records = new List<Record> { record1, record2, record3 };
int userID = 1;
var items = new List<int> { 3, 1 };
var results = items
.GroupJoin( records.Where(r => r.userID == userID), item => item, record => record.itemID, (item, record) => new { item, ratings = record.Select(r => r.rating) } )
.OrderBy( itemRating => itemRating.item)
.SelectMany( itemRating => itemRating.ratings.DefaultIfEmpty(), (itemRating, rating) => rating);
To explain what is going on
For each item GroupJoin gets the list of rating (or empty list if no rating) for the specified user
OrderBy is obvious
SelectMany flattens the ratings lists, providing a zero if the ratings list is empty (by DefaultIfEmpty)
Hope this makes sense.
Be aware, if there is more than one rating for an item by a user, they will all appear in the final list.
I have a table that looks like this:
Id GroupId Value
and it has about 100 rows
How can I return the top 10 rows for value but with no duplicating GroupId?
This should do it:
var results = table
.GroupBy(x => x.GroupId)
.Select(x => new { Row = x, Value = x.Max(y => y.Value) })
.OrderByDescending(x => x.Value)
.Select(x => x.Row)
.Take(10);
Edit: Modified to return the entire object.
Not sure if this translates to LINQ-to-SQL, but here's an idea from L2Obj
var query = (from foo in foos
group foo by foo.GroupId into fg
select fg.OrderByDescending(f => f.Value).First())
.OrderByDescending(f => f.Value)
.Take(10);
In english, it groups on the GroupId and then selects the Foo with the highest Value from each group, orders those, and then takes 10. If anything, you could get a concrete list of your objects from L2SQL and then perform the grouping in memory, should not be a performance/memory issue since you say there are only 100 rows.
For LINQ-to-SQL, you might try something like this
var sqlQuery = (from foo in foos
join y in
(from f2 in foos
join x in
(from f1 in foos
group f1 by f1.GroupId into vg
select new { GroupId = vg.Key, MaxVal = vg.Max(f => f.Value) })
on f2.GroupId equals x.GroupId
where f2.Value == x.MaxVal
group f2 by f2.GroupId into mg
select new { GroupId = mg.Key, MinId = mg.Min(f => f.Id) })
on foo.Id equals y.MinId
orderby foo.Value descending
select foo).Take(10);
This is based on a SQL query to perform the same operation
Select top 10 f.*
From Foos f
Inner Join
(Select f.GroupID, min(f.Id) as MinId
From Foos f
Inner Join
(Select GroupId, Max(Value) as MaxVal
From Foos
Group By GroupId) x
on f.GroupId = x.GroupId
and f.Value = x.MaxVal
Group By f.GroupId) y
on f.Id = y.MinId
order by f.Value desc
It basically performs two groupings. The first gets the max value for each group, the second gets the min ID for each record from each group that has the max value (in case 2 records in a group have the same value), and then selects the top 10 records.
This one will get the full row values (it's working for me with the sample data I show bellow):
static void Main(string[] args)
{
Whatever one = new Whatever() {GroupId = 1, Id = 1, Value = 2};
Whatever two = new Whatever() { GroupId = 1, Id = 2, Value = 8 };
Whatever three = new Whatever() { GroupId = 2, Id = 3, Value = 16 };
Whatever four = new Whatever() { GroupId = 2, Id = 4, Value = 7 };
Whatever five = new Whatever() { GroupId = 3, Id = 5, Value = 21 };
Whatever six = new Whatever() { GroupId = 3, Id = 6, Value = 12 };
Whatever seven = new Whatever() { GroupId = 4, Id = 7, Value = 5 };
Whatever eight = new Whatever() { GroupId = 5, Id = 8, Value = 17 };
Whatever nine = new Whatever() { GroupId = 6, Id = 9, Value = 13 };
Whatever ten = new Whatever() { GroupId = 7, Id = 10, Value = 44 };
List<Whatever> list = new List<Whatever>();
list.Add(one);
list.Add(two);
list.Add(three);
list.Add(four);
list.Add(five);
list.Add(six);
list.Add(seven);
list.Add(eight);
list.Add(nine);
list.Add(ten);
var results = (from w in list
group w by w.GroupId into g
select new { GroupId = g.Key,
Value = g.Max(w => w.Value),
Id = g.OrderBy(w=>w.Value).Last().Id }).
OrderByDescending(w=>w.Value).Take(5);
foreach (var r in results)
{
Console.WriteLine("GroupId = {0},
Id = {1},
Value = {2}",
r.GroupId, r.Id, r.Value);
}
}
Output:
GroupId = 7, Id = 10, Value = 44
GroupId = 3, Id = 5, Value = 21
GroupId = 5, Id = 8, Value = 17
GroupId = 2, Id = 3, Value = 16
GroupId = 6, Id = 9, Value = 13