Can I merge two lists using Linq? - c#

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).

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

Get recurring child result by depth level

I have "googled" for many hour but still I am not able to find an answer. Basically, I have a table that looks like this:
Parent | ID
null | 1
1 | 2
2 | 3
3 | 4
3 | 5
3 | 6
4 | 7
null | 8
How can I use entity linq to filter based on Id and "depth level" (which is a simple count of elements including the Id element and n - 1 elements passed the Id element)?
For example when I pass Id 2 and depth level 2
Result will be
Parent | ID
2 | 3 //level 1
3 | 4 //level 2
3 | 5 //level 2
3 | 6 //level 2
If I pass Id 3 and depth level also 2
Result will be
Parent ID
3 | 4 //level 1
3 | 5 //level 1
3 | 6 //level 1
4 | 7 //level 2
Thanks for your help
I tired to simulate your scenario in a Console application. Following is one of the possible solution. Its very basic so you need to change and use it as you need. But it returns results as per logic you mention in your question.
class Program
{
static List<Data> data = new List<Data>();
static List<Data> result = new List<Data>();
static void Main(string[] args)
{
data.Add(new Data() { Parent = null, ID = 1 });
data.Add(new Data() { Parent = 1, ID = 2 });
data.Add(new Data() { Parent = 2, ID = 3 });
data.Add(new Data() { Parent = 3, ID = 4 });
data.Add(new Data() { Parent = 4, ID = 5 });
data.Add(new Data() { Parent = null, ID = 6 });
// Take() implementation is for Depth.
result = findChildren(3).Take(2).ToList();
Console.ReadLine();
}
static List<Data> findChildren(int Id)
{
return data.Where(x => x.Parent == Id ).Union(data.Where(x => x.Parent == Id).SelectMany(y => findChildren(y.ID))).ToList();
}
}
public class Data
{
public int? Parent { get; set; }
public int ID { get; set; }
}
var Id = 2;
var Level = 2;
var head = new Item { Id = Id };
var result = new List<Item>();
for(var i = 0; i < Level; i++)
{
head = context.Table.FirstOrDefault(x => x.ParetId == head.Id);
if(head != null)
result.Add(head);
else
break;
}

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.

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 }

Selecting "custom distinct" items from a List using LINQ

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

Categories

Resources