Selecting "custom distinct" items from a List using LINQ - c#

I have a generic List of Policy objects.
The list contains the following data
id policyNumber policySequence otherData
1 101 1 aaaa
2 101 2 bbbb
3 101 3 cccc
4 102 1 dddd
5 103 1 eeee
6 103 2 ffff
I want to select the one row containing the highest policySequence for each policyNumber, so that I end up with the following:
id policyNumber policySequence created
3 101 3 cccc
4 102 1 dddd
6 103 2 ffff
I have a solution below using a foreach, but was wondering if there was an easier, cleaner way to do this in LINQ?
class Program
{
static void Main(string[] args)
{
List<Policy> policyList = new List<Policy>
{
new Policy {id = 1, policyNumber = 101, policySequence = 1, otherData = "aaaa"},
new Policy {id = 2, policyNumber = 101, policySequence = 2, otherData = "bbbb"},
new Policy {id = 3, policyNumber = 101, policySequence = 3, otherData = "cccc"},
new Policy {id = 4, policyNumber = 102, policySequence = 1, otherData = "dddd"},
new Policy {id = 5, policyNumber = 103, policySequence = 1, otherData = "eeee"},
new Policy {id = 6, policyNumber = 103, policySequence = 2, otherData = "ffff"}
};
List<Policy> filteredPolicyList = new List<Policy>();
foreach(var policy in policyList)
{
if(!filteredPolicyList.Exists(x => x.policyNumber == policy.policyNumber))
{
filteredPolicyList.Add(policy);
}
else
{
var currentPolicyInFilteredList = filteredPolicyList.Where(x => x.policyNumber == policy.policyNumber).First();
if (policy.policySequence > currentPolicyInFilteredList.policySequence)
{
filteredPolicyList.Remove(currentPolicyInFilteredList);
filteredPolicyList.Add(policy);
}
}
}
}
}
public class Policy
{
public int id;
public int policyNumber;
public int policySequence;
public string otherData;
}

var maxPolicies = policyList
.GroupBy(p => p.PolicyNumber)
.Select(grp => grp.OrderByDescending(p => p.PolicySequence).First());

If you're using LINQ to Objects, you could use the MoreLINQ project's DistinctBy method:
var maxPolicies = policyList.OrderByDescending(x => x.PolicySequence)
.DistinctBy(x => x.PolicyNumber);

You could group and aggregate:
var result = from p in policyList
group p by p.policyNumber into g
select new { Policy = g.Key, Max = g.Max() };

Related

How to use group by and also sum on a database record using linq

i have a table structure that looks that this
listed_companyid numberof_units userid
----------------- ----------------- ----------------
2 4 2
2 2 2
1 6 2
5 3 3
For userid = 2 i want
total_unit = 12
// Additionally
listed_companyid = 2, total = 6
listed_companyid = 1, total = 6
Code
var listed = dbContext.listedCompanies.ToList();
var stock = dbContext.stocks.Where(m=>m.userid == 2).ToList();
var result = (from s in stock
join l in listed on s.listed_companyid equals l.id group s by new { s.listed_companyid } into g select new
{
g.Key,
total_unit = g.Sum(s => s.numberof_units)
});
You can project a nested GroupBy
Given
public class Something
{
public int listed_companyid { get; set; }
public int numberof_units { get; set; }
public int userid { get; set; }
}
Sample App
var list = new List<Something>
{
new Something() { listed_companyid = 2, numberof_units = 4, userid = 2 },
new Something() { listed_companyid = 2, numberof_units = 2, userid = 2 },
new Something() { listed_companyid = 1, numberof_units = 6, userid = 2 },
new Something() { listed_companyid = 5, numberof_units = 3, userid = 3 },
};
var results = list.GroupBy(x => x.userid)
.Select(x => new
{
userId = x.Key,
total_unit = x.Sum(y => y.numberof_units),
sub = x.GroupBy(y => y.listed_companyid)
.Select(y => new
{
listed_companyid = y.Key,
total = y.Sum(z => z.numberof_units)
})
});
foreach (var result in results)
{
Console.WriteLine("userId : " + result.userId + ", total_unit : " + result.total_unit);
foreach (var sub in result.sub)
Console.WriteLine(" - listed_companyid : " + sub.listed_companyid + ", total : " + sub.total);
}
Output
userId : 2, total_unit : 12
- listed_companyid : 2, total : 6
- listed_companyid : 1, total : 6
userId : 3, total_unit : 3
- listed_companyid : 5, total : 3
Full Demo Here
Note : Convert to Query Syntax, and IQueryable as desired

Combine duplicates in a list

In my current test project I'm looking to combine all objects in a list where one of their values is the same as in another object, I would then like to check the other values under these objects and combine them together, here's and example:
Object1
{
id = 111,
price1 = 10,
price 2 = 20
}
Object2
{
id = 222,
price1 = 10,
price 2 = 20
}
Object3
{
id = 111,
price1 = 30,
price 2 = 70
}
Object4
{
id = 444,
price1 = 15,
price 2 = 25
}
From the above Object1 and and Object3 would be combined based on their related 'id' value, their prices would then be combined and would result in the following object replacing Object1 and Object3 in a list:
NewObject
{
id = 111,
price1 = 40,
price 2 = 90
}
The end list would then look like this:
NewObject
{
id = 111,
price1 = 40,
price 2 = 90
}
Object2
{
id = 222,
price1 = 10,
price 2 = 20
}
Object4
{
id = 444,
price1 = 15,
price 2 = 25
}
So far I would go about obtaining the value using linq as follows:
Select all with the same id add thier values
Create new object with combined values for all obtained in step 1 and add to new list
Continue over list and if the 'id 'already exists in new list then ignore it as it's already been combined into the new list
Is there maybe a quicker easier way with a single LINQ statement?
var result = source
.GroupBy(x => x.id,
(key, values) => new {
id = key,
price1 = values.Sum(x => x.price1),
price2 = values.Sum(x => x.price2)
});
try group by
var combined = list.GroupBy(x => x.id, x => x).Select(x => new ListObj()
{
id = x.Key,
price1 = x.Sum(s => s.price1),
price2 = x.Sum(s => s.price2),
});
whole console app:
class Program
{
static void Main(string[] args)
{
var list = new List<ListObj>()
{
new ListObj()
{
id = 111,
price1 = 10,
price2 = 20
},
new ListObj()
{
id = 222,
price1 = 10,
price2 = 20
},
new ListObj()
{
id = 111,
price1 = 30,
price2 = 70
},
new ListObj()
{
id = 444,
price1 = 15,
price2 = 25
},
};
var combined = list
.GroupBy(x => x.id, x => x)
.Select(x => new ListObj()
{
id = x.Key,
price1 = x.Sum(s => s.price1),
price2 = x.Sum(s => s.price2),
});
Console.ReadKey();
}
}
public class ListObj
{
public int id { get; set; }
public int price1 { get; set; }
public int price2 { get; set; }
}

Join three list using multiple columns c# linq lambda

I have these lists:
var subjects = new List<SubjectModel>
{
new SubjectModel { subjId = 1, subjName = "Math" },
new SubjectModel { subjId = 2, subjName = "Science" },
new SubjectModel { subjId = 3, subjName = "History" },
new SubjectModel { subjId = 4, subjName = "Language" }
};
var quizzes = new List<QuizModel>
{
new QuizModel { quizId = 1, quizDate = DateTime.Parse("2016-11-25"), quizScore = 10, subjectId = 1 },
new QuizModel { quizId = 2, quizDate = DateTime.Parse("2016-11-25"), quizScore = 15, subjectId = 1 },
new QuizModel { quizId = 3, quizDate = DateTime.Parse("2016-11-25"), quizScore = 8, subjectId = 2 },
new QuizModel { quizId = 4, quizDate = DateTime.Parse("2016-11-26"), quizScore = 13, subjectId = 1 },
new QuizModel { quizId = 5, quizDate = DateTime.Parse("2016-11-26"), quizScore = 20, subjectId = 2 }
};
var exams = new List<ExamModel>
{
new ExamModel { examId = 1, examDate = DateTime.Parse("2016-11-25"), examScore = 90, subjectId = 1 },
new ExamModel { examId = 2, examDate = DateTime.Parse("2016-11-25"), examScore = 88, subjectId = 2 },
new ExamModel { examId = 3, examDate = DateTime.Parse("2016-11-25"), examScore = 92, subjectId = 4 },
new ExamModel { examId = , examDate = DateTime.Parse("2016-11-26"), examScore = 84, subjectId = 1 },
};
var exercises = new List<ExerciseModel>
{
new ExerciseModel { exerciseId = 1, exerciseDate = DateTime.Parse("2016-11-25"), exerciseScore = 17, subjectId = 1 },
new ExerciseModel { exerciseId = 2, exerciseDate = DateTime.Parse("2016-11-25"), exerciseScore = 15, subjectId = 2 },
new ExerciseModel { exerciseId = 3, exerciseDate = DateTime.Parse("2016-11-26"), exerciseScore = 15, subjectId = 1 },
new ExerciseModel { exerciseId = 4, exerciseDate = DateTime.Parse("2016-11-26"), exerciseScore = 12, subjectId = 4 },
new ExerciseModel { exerciseId = 5, exerciseDate = DateTime.Parse("2016-11-26"), exerciseScore = 10, subjectId = 1 },
};
I was able to successfully group each of them by date and by subject.
var allQuizzes = quizzes.GroupBy(qz => qz.quizDate, (q, values) =>
new
{
Date = q,
Quizzes = values.GroupBy(v => v.subjectId, (c, values2) =>
new {
SubjectId = c,
QuizSum = values2.Sum(v2 => v2.quizScore)
})
});
var allExercises = exercises.GroupBy(ex => ex.exerciseDate, (e, values) =>
new {
Date = e,
Exercises = values.GroupBy(x => x.subjectId, (z, values2) =>
new {
SubjectId = z,
ExerSum = values2.Sum(r => r.exerciseScore)
})
});
var allExams = exams.GroupBy(ex => ex.examDate, (e, values) =>
new
{
Date = e,
Exercises = values.GroupBy(x => x.subjectId, (z, values2) =>
new
{
SubjectId = z,
ExamSum = values2.Sum(r => r.examScore)
})
});
However, I need to join all three of them to get the sum of all scores. The final table should display like this.
-----------------------------------------------------------------
| Date | Math | Science | History | Language |
| 11/25/2016 | 132 | 111 | 0 | 92 |
| 11/26/2016 | 122 | 20 | 0 | 12 |
-----------------------------------------------------------------
I tried to join them, but it can't seem to join by multiple columns.
I select from all 3 collections results in form of the same anonymous class (the same Idea had Andrei in first answer), that allows me just to collect all results together in all list, without mapping and converting.
var allQuiz = quizzes.GroupBy(x => new { x.subjectId, x.quizDate })
.Select(x => new {
Date = x.Key.quizDate,
Subj = x.Key.subjectId,
Sum = x.Sum(r=>r.quizScore)});
var allExam= exams.GroupBy(x => new { x.subjectId, x.examDate })
.Select(x => new {
Date = x.Key.examDate,
Subj = x.Key.subjectId,
Sum = x.Sum(r=>r.examScore)});
var allExc = exercises.GroupBy(x => new { x.subjectId, x.exerciseDate })
.Select(x => new {
Date = x.Key.exerciseDate,
Subj = x.Key.subjectId,
Sum = x.Sum(r=>r.exerciseScore)});
Combining of all results together:
var all = allQuiz.ToList();
all.AddRange(allExam.ToList());
all.AddRange(allExc.ToList());
var result = all.GroupBy(x => new { x.Date, x.Subj })
.Select(x => new { x.Key.Date, x.Key.Subj, Sum = x.Sum(s => s.Sum)});
var list = result.GroupBy(r => r.Date).Select(x => new {
Date = x.Key,
Math = x.SingleOrDefault(t=>t.Subj==1)?.Sum ?? 0,
Science = x.SingleOrDefault(t=>t.Subj==2)?.Sum ?? 0,
History = x.SingleOrDefault(t=>t.Subj==3)?.Sum ?? 0,
Language = x.SingleOrDefault(t=>t.Subj==4)?.Sum ?? 0,
});
Output in LinqPad:
Here is an idea. Instead of keeping the distinction while grouping, you could convert all three to the same structure. For instance:
var allQuizzes = quizzes.GroupBy(qz => qz.quizDate, (q, values) =>
new
{
Date = q,
Results = values.GroupBy(v => v.subjectId, (c, values2) =>
new {
SubjectId = c,
Sum = values2.Sum(v2 => v2.quizScore)
})
});
Notice names "Results" and "Sum" - you can use the same for the other two objects. And now you have three collections, all of the same structure:
{
Date:
Results: [
{SubjectId, Sum}
{SubjectId, Sum}
...
]
}
Since they are all the same now, you can stop treating them differently, use UNION to merge all three, group them by date and within that by subject. Then you could probably iterate through subject list to get necessary info, depends on what you mean by "final table".
This is what i came up with.
It may not be best optimized, but might be enough for you.
I rendered the results into a StringBuilder in my test.
var result =
quizzes.Select(q => new {SubjectId = q.subjectId, Date = q.quizDate, Score = q.quizScore})
.Union(exams.Select(e => new {SubjectId = e.subjectId, Date = e.examDate, Score = e.examScore}))
.Union(exercises.Select(e => new {SubjectId = e.subjectId, Date = e.exerciseDate, Score = e.exerciseScore}))
.GroupBy(arg => arg.Date,
(key, values)=>
new
{
Key = key,
Scores = values.GroupBy(v => v.SubjectId, (s, values2) => new { SubjectId = s, SumScore = values2.Sum(v2 => v2.Score) })
});
StringBuilder sb = new StringBuilder("Date\t\t");
foreach (SubjectModel subject in subjects)
{
sb.Append($"{subject.subjName}\t");
}
sb.AppendLine();
foreach (var record in result)
{
sb.Append($"{record.Key.ToShortDateString()}\t");
foreach (SubjectModel subject in subjects)
{
int sum = record.Scores.Where(s => s.SubjectId == subject.subjId).Select(s => s.SumScore).DefaultIfEmpty(0).Single();
sb.Append($"{sum}\t");
}
sb.AppendLine();
}
string finalTable = sb.ToString();
Instead of using three different anonymous objects to hold the results, make your own class:
public enum TestType
{
Quiz,
Exam,
Exercise,
}
public class TestScore
{
public TestType Type { get; set; }
public DateTime Date { get; set; }
public int Score { get; set; }
public int SubjectId { get; set; }
// Constructors - make a TestScore object
public TestScore(QuizModel q)
{
Type = TestType.Quiz;
Date = q.quizDate;
Score = q.quizScore;
SubjectId = q.SubjectId;
}
public TestScore(ExamModel e)
{
Type = TestType.Exam;
Date = e.examDate;
Score = e.examScore;
SubjectId = e.SubjectId;
}
public TestScore(ExerciseModel e)
{
Type = TestType.Exercise;
Date = e.exerciseDate;
Score = e.exerciseScore;
SubjectId = e.SubjectId;
}
}
Convert to TestScore:
List<TestScore> scores = new List<TestScore>();
scores.AddRange(quizzes.Select(q => new TestScore(q));
scores.AddRange(exams.Select(e => new TestScore(e));
scores.AddRange(exercises.Select(e => new TestScore(e));
Now you have one datasource instead of three, displaying the results becomes easy.

Select common elements for 2 or more departments from list group by then having count

I have a class that looks like so:
{
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
public int CategoryId { get; set; }
public int DepId{ get; set; }
}
}
Data that my list folds looks like so:
ID | Name | CategoryId | DepId
---------------------------------------
1 | Post | 1 | 1
2 | Post | 1 | 2
3 | Printer | 2 | 1
4 | Printer | 2 | 3
5 | Post | 3 | 3
6 | Printer | 2 | 1
This data holds Access data to some categories.
What I would like to get is common categories for 2 (or more) departaments
If user selects that he want categories for department with id=1 then he should get elements with id 1 and 3, but if he wants categories for department 1 and 3 he should get elements 4 and 6.
For DepId IN(1,3) I would like to get this result:
Name | CategoryId
----------------------
Printer | 2
Something like JOIN in SQL.
I was able to code it in sql:
SELECT * FROM(
SELECT
C.Cat_Id AS Id,
MAX(C.Name) AS Name,
FROM
Categories_Access AS CA (NOLOCK)
JOIN dbo.Categories AS C (NOLOCK) ON C.Cat_Id = CA.Cat_Id
WHERE
CA.DepId IN (1,3)
GROUP BY C.Cat_Id
HAVING COUNT(*)=2
) A ORDER BY A.Name
Now I would like to do same thing in C#.
EDIT
This is my attempt:
var cat = new List<Category>();
cat.Add(new Category {Id = 1, CategoryId = 1, Name = "Post", DepId = 1});
cat.Add(new Category {Id = 2, CategoryId = 1, Name = "Post", DepId = 2});
cat.Add(new Category {Id = 3, CategoryId = 2, Name = "Printer", DepId = 1});
cat.Add(new Category {Id = 4, CategoryId = 2, Name = "Printer", DepId = 3});
cat.Add(new Category {Id = 5, CategoryId = 3, Name = "Another", DepId = 3});
cat.Add(new Category {Id = 6, CategoryId = 2, Name = "Printer", DepId = 1});
cat.Add(new Category {Id = 7, CategoryId = 4, Name = "Else", DepId = 1});
var ids = new List<int> {1, 2};
var Query = from p in cat.Where(i => ids.Contains(i.DepId)).GroupBy(p => p.CategoryId)
select new
{
count = p.Count(),
p.First().Name,
p.First().CategoryId
};
What I need to do is just to select items that have count=ids.Count.
My finale version (based on #roughnex answer):
private static IEnumerable<Cat> Filter(IEnumerable<Category> items, List<int> ids)
{
return items.Where(d => ids.Contains(d.DepId))
.GroupBy(g => new { g.CategoryId, g.Name })
.Where(g => g.Count() == ids.Count)
.Select(g => new Cat { Id = g.Key.CategoryId, Name = g.Key.Name });
}
In C# (LINQ) to select common elements you ll use
List<int> Depts = new List<int>() {1, 3};
var result = Categories.Where(d => Depts.Contains(d.DeptId))
.GroupBy(g => new {g.CatId, g.Name})
.Where(g => g.Count() >= 2)
.Select(g => new {g.Key.CatId, g.Key.Name});
So based on what you've said, you've already parsed the data from a SQL proc into a List<Category>. If that is the case, the following snippet should guide you:
var items = new List<Category>();
var deptIds = new List<int>() { 1, 3 };
var query = items.Where(item => deptIds.Contains(item.DepId))
.Select(category => category);
When you want to do an IN in LINQ, you have to invert it and use Contains. Whereas in SQL it's ColumnName IN (List), in LINQ it's List.Contains(ColumnName). Hope that helps.

Group by using linq (range + count)

var data = new[] {
new { Id = 0, Cat = 1, Price = 2 },
new { Id = 1, Cat = 1, Price = 10 },
new { Id = 2, Cat = 1, Price = 30 },
new { Id = 3, Cat = 2, Price = 50 },
new { Id = 4, Cat = 2, Price = 120 },
new { Id = 5, Cat = 2, Price = 200 },
new { Id = 6, Cat = 2, Price = 1024 },
};
var ranges = new[] { 10, 50, 100, 500 };
Needed output is grouped price count by equal or greater than the range used according categories.
(in one linq statement)
cat range count
-------------------------------------
1 10 2 (In 1. categories there is 2 item that price >= 10(range) [10;30])
2 10 4 (In 2. categories there is 4 item that price >= 10(range) [50;120;200;1024])
2 50 4 ....
2 100 3 ....
2 500 1 (In 2. categories there is 1 item that price >= 500(range) [1024])
Try this:
var data = new[] {
new { Id = 0, Cat = 1, Price = 2 },
new { Id = 1, Cat = 1, Price = 10 },
new { Id = 2, Cat = 1, Price = 30 },
new { Id = 3, Cat = 2, Price = 50 },
new { Id = 4, Cat = 2, Price = 120 },
new { Id = 5, Cat = 2, Price = 200 },
new { Id = 6, Cat = 2, Price = 1024 },
};
var ranges = new[] { 10, 50, 100, 500 };
var result = from r in ranges
from g in data
where g.Price >= r
select new {g.Cat, Price=r};
var groupedData =
from d in result
group d by new{d.Cat, d.Price} into g
select new{Cat=g.Key.Cat, Price=g.Key.Price, TotalCount=g.Count()};
This should work:
var values =
data.SelectMany(x => ranges.Where(y => x.Price >= y)
.Select(y => new { Record = x, Range = y }))
.GroupBy(x => new { Cat = x.Record.Cat, Range = x.Range })
.Select(x => new { Cat = x.Key.Cat, Range = x.Key.Range, Count = x.Count()});
Results:
{ Cat = 1, Range = 10, Count = 2 }
{ Cat = 2, Range = 10, Count = 4 }
{ Cat = 2, Range = 50, Count = 4 }
{ Cat = 2, Range = 100, Count = 3 }
{ Cat = 2, Range = 500, Count = 1 }

Categories

Resources