For example, how can I group the following records by GroupId using LINQ, and sum all other columns in each group? (thus merging all rows in each group into one)
var list = new List<Foo>()
{
new Foo() { GroupId = 0, ValueA = 10, ValueB = 100 },
new Foo() { GroupId = 1, ValueA = 30, ValueB = 700 },
new Foo() { GroupId = 1, ValueA = 40, ValueB = 500 },
new Foo() { GroupId = 2, ValueA = 80, ValueB = 300 },
new Foo() { GroupId = 2, ValueA = 20, ValueB = 200 },
new Foo() { GroupId = 2, ValueA = 20, ValueB = 200 }
};
Expected result is :
| GroupId | ValueA | ValueB |
|---------|--------|--------|
| 0 | 10 | 100 |
| 1 | 70 | 1200 |
| 2 | 120 | 700 |
list.GroupBy(i => i.GroupId)
.Select(g => new { GroupId = g.Key,
ValueA = g.Sum(i => i.ValueA),
ValueB = g.Sum(i => i.ValueB)});
or just for fun you can do it within one GroupBy call using its overload:
list.GroupBy(i => i.GroupId,
(key, groupedItems) => new {
GroupId = key,
ValueA = groupedItems.Sum(i => i.ValueA),
ValueB = groupedItems.Sum(i => i.ValueB),
});
or you can use Aggregate to avoid iterating each group many times:
list.GroupBy(i => i.GroupId)
.Select(g => g.Aggregate((i1, i2) => new Foo{ GroupId = i1.GroupId,
ValueA = i1.ValueA + i2.ValueA,
ValueB = i1.ValueB + i2.ValueB,
}));
var query = list.GroupBy(x=> x.GroupId)
.Select(g=> new
{
GroupId = g.Key,
ValueA = g.Sum(x => x.ValueA),
ValueB = g.Sum(x => x.ValueB)
});
Use GroupBy to collect Foos into groups based on their GroupId, and then create a new object for each "row" of the result (that object can be a Foo itself based on the code you give, but it could just as easily be anything else, including an anonymous type). At that point you will also sum up the other values.
var sums = list.GroupBy(f => f.GroupId)
.Select(g => new Foo
{
GroupId = g.Key,
ValueA = g.Sum(f => f.ValueA),
ValueB = g.Sum(f => f.ValueB)
}).ToList();
Avoiding the lambda expressions (almost) and using a "purely" LINQ way:
var sums = from foo in list
group foo by foo.GroupId into groupings
orderby groupings.Key ascending
select new
{
GroupId = groupings.Key,
ValueA = groupings.Sum(g => g.ValueA),
ValueB = groupings.Sum(g => g.ValueB)
};
I just think LINQ is a bit more natural-language looking, as compared to lambda (not that there's anything wrong with that...)
Related
I'm trying to merge two lists and I thought I had a solution but if there are two PackItems with the same length the results are not as expected.
Expectations/requirements.
Both lists contain the same total number of pieces for each length.
EDIT: Added code to clarify the input requirements.
The same length can be used in multiple PacksItems.
The same lengths can be produced out of multiple CoilNums.
The goal is to contain a list the contains a unique entry for each PackItem.ID/CoilNum.
Requirement for the output is that the total number of pieces for each length matched the input lists.
Here is the code I have so far.
public class PackItem
{
public int ID { get; set; }
public int Quantity { get; set; }
public string Length { get; set; }
}
public class ProductionInfo
{
public ProductionInfo AddID(PackItem item)
{
LineID = item.ID;
Quantity = Math.Min(Quantity, item.Quantity);
return this;
}
public int LineID { get; set; }
public string CoilNum { get; set; }
public int Quantity { get; set; }
public string Length { get; set; }
}
private void DoTest()
{
var packItems = new List<PackItem>()
{
new PackItem() {ID = 4, Quantity = 5, Length = "10"},
new PackItem() {ID = 5, Quantity = 2, Length = "4"},
new PackItem() {ID = 6, Quantity = 1, Length = "4"}
};
var productionInfoList = new List<ProductionInfo>()
{
new ProductionInfo() { CoilNum = "A", Quantity = 4, Length = "10"},
new ProductionInfo() { CoilNum = "B", Quantity = 1, Length = "10"},
new ProductionInfo() { CoilNum = "B", Quantity = 2, Length = "4"},
new ProductionInfo() { CoilNum = "A", Quantity = 1, Length = "4"},
};
//assert that both lists meet input requirements
var result1 = "";
var sum1 = packItems.GroupBy(i => i.Length);
foreach (var group in sum1) result1 += $"{group.Sum(i=>i.Quantity)} | {group.Key}\n";
var input2 = "";
var result2 = "";
var sum2 = productionInfoList.GroupBy(i => i.Length);
foreach (var group in sum2) result2 += $"{group.Sum(i => i.Quantity)} | {group.Key}\n";
Console.WriteLine("packItems: \nSum(Quantity) | Length");
Console.WriteLine(result1);
Console.WriteLine("productionInfoList: \nSum(Quantity) | Length");
Console.WriteLine(result2);
if (result1 == result2)
{
Console.WriteLine("Both Lists have the same quantity of each length");
}
else
{
Console.WriteLine("Error: Both Lists do not have the same quantity of each length");
return;
}
var merged = productionInfoList.SelectMany(x => packItems, (x, y) => new { x, y })
.Where(i => i.x.Length == i.y.Length)
.Select(i => i.x.AddID(i.y));
Console.WriteLine("ID | Coil | Qty | Length");
foreach (var item in merged)
{
Console.WriteLine($"{item.LineID} | {item.CoilNum} | {item.Quantity} | {item.Length}");
}
}
//expected output
ID | Coil | Qty | Length
4 | A | 4 | 10
4 | B | 1 | 10
5 | B | 2 | 4
6 | A | 1 | 4
//actual output
ID | Coil | Qty | Length
4 | A | 4 | 10
4 | B | 1 | 10
5 | B | 2 | 4
6 | B | 1 | 4
5 | A | 1 | 4
6 | A | 1 | 4
I'm stuck at this point and they only way I can think of is splitting each of these lists into individual items of one each, and then compiling a list by looping through them one by one.
Is there a way this can be done with Linq?
Here is a method that produces the correct output. Is there an easier way to do this? Can this be done with Linq only?
private void DoTest()
{
var packItems = new List<PackItem>()
{
new PackItem() {ID = 4, Quantity = 5, Length = "10"},
new PackItem() {ID = 5, Quantity = 2, Length = "4"},
new PackItem() {ID = 6, Quantity = 1, Length = "4"}
};
var productionInfoList = new List<ProductionInfo>()
{
new ProductionInfo() { CoilNum = "A", Quantity = 4, Length = "10"},
new ProductionInfo() { CoilNum = "B", Quantity = 1, Length = "10"},
new ProductionInfo() { CoilNum = "B", Quantity = 2, Length = "4"},
new ProductionInfo() { CoilNum = "A", Quantity = 1, Length = "4"},
};
//first create a list with one item for each pieces
var individualProduction = new List<ProductionInfo>();
foreach (var item in productionInfoList)
{
for (int i = 0; i < item.Quantity; i++)
{
individualProduction.Add(new ProductionInfo()
{
Quantity = 1,
Length = item.Length,
CoilNum = item.CoilNum
});
}
}
//next loop through and assign all the pack line ids
foreach (var item in individualProduction)
{
var packItem = packItems.FirstOrDefault(i => i.Quantity > 0 && i.Length == item.Length);
if (packItem != null)
{
packItem.Quantity -= 1;
item.LineID = packItem.ID;
}
else
{
item.Quantity = 0;
}
}
//now group them back into a merged list
var grouped = individualProduction.GroupBy(i => (i.CoilNum, i.LineID, i.Length));
//output the merged list
var merged1 = grouped.Select(g => new ProductionInfo()
{
LineID = g.Key.LineID,
CoilNum = g.Key.CoilNum,
Length = g.Key.Length,
Quantity = g.Count()
});
}
Quite unclear ...
This one is closed of the desired result but doesn't take into consideration any quantity so that the fist PackItem is always choosed. If decreasing the pItem.Quantity this would select the next available pItem.ID where Quantity > 0. But this will require more code :)
var results = productionInfoList.Select(pInfo =>
{
var pItem = packItems.First(z => z.Length == pInfo.Length);
return new { pItem.ID, pInfo.CoilNum, pInfo.Quantity, pInfo.Length };
}).ToList();
When you have a goal of : The goal is to contain a list the contains a unique entry for each PackItem.ID/CoilNum. your bottom answer is correct, since it has a unique id coilNum pair. What you are looking for is a different uniquenes.
var l = packItems.Join(productionInfoList, x => x.Length, y => y.Length, (x, y) => { y.AddID(x); return y; }).GroupBy(x => new { x.CoilNum, x.Length }).Select(x => x.First());
It is unclear on the exact rules of the case, but here I am using Length as a unique key to perform a join operation (Would recommend to have a different unique key for join operations).
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; }
}
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.
For some reason some records have the same ID. Aim is to list all the whole record which have the same ID. For example, how can group the following records by GroupId using LINQ, and find all records with the same ID and list them all? (thus merging all rows in each group into one)
var list = new List<Record>()
{
new Record() { GroupId = 0, ValueA = 20, ValueB = 300 },
new Record() { GroupId = 1, ValueA = 30, ValueB = 700 },
new Record() { GroupId = 1, ValueA = 40, ValueB = 500 },
new Record() { GroupId = 2, ValueA = 80, ValueB = 300 },
new Record() { GroupId = 2, ValueA = 20, ValueB = 200 },
new Record() { GroupId = 2, ValueA = 20, ValueB = 200 }
};
Expect result is the last 5 records.
Another way is:
var results = (from l in list
group new {l.ValueA, l.ValueB} by l.GroupId
into g
select new {GroupId = g.Key, Values = g.ToList()}).ToList();
If you prefer lambda expression, then
var results = (list.GroupBy(l => l.GroupId, l => new {l.ValueA, l.ValueB})
.Select(g => new {GroupId = g.Key, Values = g.ToList()})).ToList();
This should give you records based on unique GroupIdalong the other values associated.
EDIT
foreach (var result in results)
{
foreach (var value in result.Values)
{
int valueA = value.ValueA;
int valueB = value.ValueB;
}
}
I think you are looking for something similar to this.
Hope it helps.
Answer for "how can group the following records by GroupId using LINQ"
var groupList = list.GroupBy((mRecord) => mRecord.GroupId).ToList();
var groups = list.ToLookup(record => record.GroupId);
var groups1 = groups[1]; //all records with GroupId = 1
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.