Combine duplicates in a list - c#

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

Related

insert item in correct position in a c# list

I have a collection of Products in a list (List<Product> ) where product holds id, name and price.
If I would order the list in a descending way based on price, is there a one liner or extensionmethod that allows me to insert a new product in the correct position of the list?
public class Product
{
public int Id {get; set;}
public string Name {get; set;}
public int Price {get; set;} // assume only whole integers for price
}
public class Main()
{
List<Product> products = new();
products.Add(new Product(id= 1, Name="Product1", Price=10 };
products.Add(new Product(id= 2, Name="Product2", Price=15 };
products.Add(new Product(id= 3, Name="Product3", Price=11 };
products.Add(new Product(id= 4, Name="Product4", Price=20 };
products = products.OrderByDescending(prd => prd.Price).ToList();
var newProduct = new({id = 5, Name="new product", Price = 17})
// Is there an short solution available that allows me to insert a new product with
// price = 17 and that will be inserted between products with price 15 and 20?
// Without repeatedly iterating over the list to find the one lower and the one higher
// than the new price and recalculate the index...
var lastIndex = products.FindLastIndex(x => x.Price >= newProduct.Price);
products.Insert(lastIndex + 1, p5);
}
Edit for Solution: I upvoted Tim Schmelter's answer as the most correct one. It is not a single line, as it requires a custom extension method, but I think a single line solution isn't available. Adding it and do a OrderByDescending() works, and is simple, but then depends on the OrderByDescending() statement for the rest of the code...
You can use a SortedList<TKey, TValue>:
SortedList<int, Product> productList = new();
var p = new Product{ Id = 1, Name = "Product1", Price = 10 };
productList.Add(p.Price, p);
p = new Product { Id = 2, Name = "Product2", Price = 15 };
productList.Add(p.Price, p);
p = new Product { Id = 3, Name = "Product3", Price = 11 };
productList.Add(p.Price, p);
p = new Product { Id = 4, Name = "Product4", Price = 20 };
productList.Add(p.Price, p);
p = new Product { Id = 5, Name = "Product5", Price = 17 };
productList.Add(p.Price, p);
foreach(var x in productList)
Console.WriteLine($"{x.Key} {x.Value.Name}");
outputs:
10 Product1
11 Product3
15 Product2
17 Product5
20 Product4
Edit: Note that it doesn't allow duplicate keys, so like a dictionary. You could solve it by using a SortedList<int, List<Product>>. For example with this extension method:
public static class CollectionExtensions
{
public static void AddItem<TKey, TValue>(this SortedList<TKey, List<TValue>> sortedList, TValue item, Func<TValue, TKey> getKey)
{
TKey key = getKey(item);
if (sortedList.TryGetValue(key, out var list))
{
list.Add(item);
}
else
{
sortedList.Add(key, new List<TValue> { item });
}
}
}
Usage:
SortedList<int, List<Product>> productLists = new();
productLists.AddItem(new Product { Id = 1, Name = "Product1", Price = 10 }, p => p.Price);
productLists.AddItem(new Product { Id = 2, Name = "Product2", Price = 10 }, p => p.Price);
productLists.AddItem(new Product { Id = 3, Name = "Product3", Price = 20 }, p => p.Price);
productLists.AddItem(new Product { Id = 4, Name = "Product4", Price = 20 }, p => p.Price);
productLists.AddItem(new Product { Id = 5, Name = "Product5", Price = 15 }, p => p.Price);
foreach (var x in productLists)
Console.WriteLine($"{x.Key} {string.Join("|", x.Value.Select(p => p.Name))}");
outputs:
10 Product1|Product2
15 Product5
20 Product3|Product4
You could calculate the position of the new element before adding it to the list, and then use List.Insert.

GroupBy bool and Select values for new list

I am trying to get into more complex Linq queries and right away catch a point I am feeling stuck. I have a following list in DB:
ID ELAPSED TIME APPISRUNNING
1 12 TRUE
2 54 TRUE
3 32 FALSE
Where ELAPSED TIME is TimeSpan and APPISRUNNING is a bool.
I would like to build a chart based on these values (https://github.com/beto-rodriguez/LiveCharts2). Chart build fine with this:
Title = "Analytics";
this.ActivityChartSeries = new ISeries[]
{
new PieSeries<double> { Values = new double[] { 2 }},
new PieSeries<double> { Values = new double[] { 2 }},
new PieSeries<double> { Values = new double[] { 2 }},
new PieSeries<double> { Values = new double[] { 2 }},
new PieSeries<double> { Values = new double[] { 2 }},
};
Now I somehow need to first GroupBy bool and then select a new List? I have tried following:
IEnumerable<DataRecord> dataRecords = await this.DataStore.GetItemsAsync();
this.ActivityChartSeries = dataRecords
.GroupBy(g => g.AppIsRunning)
.Select(m => new
{ // BELOW IS TOTALLY UNCLEAR FOR ME
Values = m.Select(r => r.EngineElapsed.Ticks),
Name = m.Select(r => r.Name),
})
.Select(x =>
new PieSeries<double>
{
Values = new List<double> { x.Values.FirstOrDefault() },
Name = x.Name.FirstOrDefault(),
});
Type of assigned variable:
public IEnumerable<ISeries> ActivityChartSeries
This part is totally unclear for me:
Values = m.Select(r => r.EngineElapsed.Ticks),
Name = m.Select(r => r.Name),
How after GroupBy I can create two types of data? Basically I need
"Application Running" and "Values"
"Application is not Running" and "Values"
EDIT:
Code provided by Somar Zein compiles fine:
var results = activityChartSeries
.GroupBy(a=> a.AppIsRunning)
.Select(item=> new PieSeries<double>{
Name = item.Key ? "Application is Running" : "Application is not Running",
Values = item.Select(x=> Convert.ToDouble(x.ElapsedTime.Ticks)).ToList()
});
However as a result I am getting something like this, why it is reloading in a loop?
Here is result:
enter image description here
EDIT2:
So I have created an example for testing purposes:
Class:
public class DataModel
{
public int Id { get; set; }
public TimeSpan ElapsedTime { get; set; }
public bool AppIsRunning { get; set; }
}
Code:
List<DataModel> records = new List<DataModel>();
records.Add(new DataModel { Id = 1, ElapsedTime = new TimeSpan(1, 20, 30), AppIsRunning = true });
records.Add(new DataModel { Id = 2, ElapsedTime = new TimeSpan(1, 20, 30), AppIsRunning = true });
records.Add(new DataModel { Id = 3, ElapsedTime = new TimeSpan(1, 20, 30), AppIsRunning = true });
records.Add(new DataModel { Id = 4, ElapsedTime = new TimeSpan(1, 20, 30), AppIsRunning = true });
records.Add(new DataModel { Id = 5, ElapsedTime = new TimeSpan(1, 20, 30), AppIsRunning = true });
this.ActivityChartSeries = records
.GroupBy(g => g.AppIsRunning)
.Select(item => new PieSeries<double>
{
Name = item.Key ? "Running" : "Not Running",
Values = new double[] { 2, 4 },
});
I get the same reloading effect, even thou originally provided Example from LiveCharts work fine.
you could try doing something like following:
var results = activityChartSeries
.GroupBy(a=> a.AppIsRunning)
.Select(item=> new PieSeries<double>{
Name = item.Key ? "Application is Running" : "Application is not Running",
Values = item.Select(x=> Convert.ToDouble(x.ElapsedTime.Ticks)).ToList()
});
hope that could be helpful!

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.

Sort collection by another collection values

I have List<SomeData> data;
public class SomeData
{
public int Key { get; set;}
public decimal Value { get; set;}
}
Also i have List<int> DataOrder;
I need to sort List<SomeData>data by Key, placing it in same order as List<int> DataOrder values.
Is there any common algorithms for that?
Example:
List<SomeData> data = new List<SomeData>();
data.Add(new SomeData{ Key = 10, Value = 14 })
data.Add(new SomeData{ Key = 25, Value = 22 })
data.Add(new SomeData{ Key = 567, Value = 3 })
data.Add(new SomeData{ Key = 57, Value = 300 })
data.Add(new SomeData{ Key = 17, Value = 200 })
data.Add(new SomeData{ Key = 343, Value = 42 })
List<int> DataOrder = new List<int>{1, 25, 700, 567, 343, 350, 10};
Result after sorting:
foreach(var element in data)
{
Console.WriteLine(element.Key);
}
Out:
25
567
343
10
57
17
Edit: initial data array can have Key, that not contain in DataOrder
Such value should be placed at the end of result collection in any order.
Example changed to illustrate it.
What about joining:
var mySortedList = (from i in DataOrder
join d in data on i equals d.Key
select new SomeData
{
Key = d.Key,
Value = d.Value
});
EDIT: To also add those values from data that do NOT share any key within the DataOrder-list you may simply add a Union to the result as follows:
var result = mySortedList.Union(data.Where(x => !DataOrder.Contains(x.Key)));
Solved
public class SomeData
{
public int Key { get; set; }
public decimal Value { get; set; }
}
class Program
{
static void Main(string[] args)
{
List<SomeData> orders = new List<SomeData>();
orders.Add(new SomeData { Key = 10, Value = 14 });
orders.Add(new SomeData { Key = 25, Value = 22 });
orders.Add(new SomeData { Key = 567, Value = 3 });
orders.Add(new SomeData { Key = 57, Value = 300 });
orders.Add(new SomeData { Key = 17, Value = 200 });
orders.Add(new SomeData { Key = 343, Value = 42 });
List<int> ids = new List<int> { 1, 25, 700, 567, 343, 350, 10 };
//get orders only from ids with order
List<SomeData> existedOrders = (from order in orders
join id in ids
on new { onlyId = order.Key }
equals new { onlyId = id }
orderby ids.IndexOf(id)
select order).ToList();
//add others
existedOrders.AddRange(orders.Except(existedOrders).ToList());
}
}
//with #HimBromBeere solution you can reduce query
//get orders only from ids with order
List<SomeData> existedOrders = (from order in orders
join id in ids
on order.Key equals id
orderby ids.IndexOf(id)
select order).ToList();
int count = 0;
for(int i in DataOrder)
{
var index = data.IndexOf(d => d.Key == i);
swap(data[count], data[index]);
count++;
}
and swap function is for swap places of items.

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