Sort collection by another collection values - c#

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.

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.

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.

How to select records with duplicate column by group using LINQ?

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

How to index nested list?

I am having a real problem iterating a list<list<object>> and would like to ask anybody for their insight.
as you can see I have a list<list<object>> posCheckOptions which contains 32 lists and each list contains x amount of objects, i.e 348 for 0, 325 for 1 etc.
for example, in this list of 348 there are duplicate elements which I would like to remove. in duplicate I mean, the name of the stock could be the same so, if I have say, VODAFONE 4 times, I would like to add the qty of shares from each one to the first time Vodafone is spotted and delete the duplicates.
for(int k = 0; k<posCheckOptions.Count; k++)
{
int l = 0;
int m = 1;
while (l != m)
{
foreach(var x in posCheckOptions[k][l].name)
{
if(posCheckOptions[k][l].date != posCheckOptions[k][m].date
&& posCheckOptions[k][l].strike != posCheckOptions[k][m].strike
&& posCheckOptions[k][l].callPut != posCheckOptions[k][m].callPut)
{
m++;
}
else
{
posCheckOptions[k][l].size = posCheckOptions[k][l].size + posheckOptions[k][m].size;
posCheckOptions[k].RemoveAt(m);
m--;
}
}
l++; m = l;
}
}
What I am trying to code, well at least the idea is, I start from posCheckOptions[0][0] and compare elements posCheckOptions[0][0].date to posCheckOptions[0][1].date (i compare 4 fields) (poscheckOptions is a list whos type T is a class with 76 varaibles). If what I compare is not equal (i.e. not duplicate I move the index up level and continue to the next, so on. On my travels if I find a duplicate element, i do the addition and remove, move the index back one and start until I reach the end.
im getting confused with the fact, im not sure if i need 2 index running in j posCheckOptions[0][j] because j only goes to j+1 once say index m has looped all 348 elements. This may not be 348 if i delete 2....
any advice is really welcome :-)
Looks like your problem can be easily solved using LINQ. I'm gonna show it on simplified example, so you'll have to adjust it you your real one.
Assume we have a class names Item as following:
public class Item
{
public int Key { get; set; }
public int SubKey { get; set; }
public int Quantity { get; set; }
}
And a list (List<Item>):
var items = new List<Item>() {
new Item { Key = 1, SubKey = 1, Quantity = 100 },
new Item { Key = 1, SubKey = 2, Quantity = 400 },
new Item { Key = 2, SubKey = 1, Quantity = 60 },
new Item { Key = 1, SubKey = 1, Quantity = 10 },
new Item { Key = 2, SubKey = 1, Quantity = 30 },
new Item { Key = 1, SubKey = 1, Quantity = 70 }
};
Now, we'd like to sum Quantity for elements with the same pair of Key and SubKey (what will also remove duplicates). There is GroupBy method within LINQ, so let's use it:
var groupedItems = items.GroupBy(x => new { x.Key, x.SubKey })
.Select(g => new Item {
Key = g.Key.Key,
SubKey = g.Key.SubKey,
Quantity = g.Sum(x => x.Quantity)
}).ToList();
As a result, we have a new List<Item> with only one element for every Key/SubKey pair and Quantity which is a sum of Quantities for items with that key pair.
Can it be expanded for <List<List<Item>> as an input and output? Sure it can.
Source nested Items collection:
var nestedItems = new List<List<Item>>() {
new List<Item>() {
new Item { Key = 1, SubKey = 1, Quantity = 100 },
new Item { Key = 1, SubKey = 2, Quantity = 400 },
new Item { Key = 2, SubKey = 1, Quantity = 60 },
new Item { Key = 1, SubKey = 1, Quantity = 10 },
new Item { Key = 2, SubKey = 1, Quantity = 30 },
new Item { Key = 1, SubKey = 1, Quantity = 70 }
},
new List<Item>() {
new Item { Key = 1, SubKey = 1, Quantity = 100 },
new Item { Key = 1, SubKey = 2, Quantity = 400 },
new Item { Key = 2, SubKey = 1, Quantity = 60 },
new Item { Key = 1, SubKey = 1, Quantity = 10 },
new Item { Key = 2, SubKey = 1, Quantity = 30 },
new Item { Key = 1, SubKey = 1, Quantity = 70 }
},
new List<Item>() {
new Item { Key = 1, SubKey = 1, Quantity = 100 },
new Item { Key = 1, SubKey = 2, Quantity = 400 },
new Item { Key = 2, SubKey = 1, Quantity = 60 },
new Item { Key = 1, SubKey = 1, Quantity = 10 },
new Item { Key = 2, SubKey = 1, Quantity = 30 },
new Item { Key = 1, SubKey = 1, Quantity = 70 }
}
};
And the query:
var nestedGroupedItems = nestedItems.Select(x => x.GroupBy(y => new {y.Key, y.SubKey })
.Select(g => new Item {
Key = g.Key.Key,
SubKey = g.Key.SubKey,
Quantity = g.Sum(y => y.Quantity)
}).ToList()).ToList();
I would suggest to do the following:
Implement the IComparable interface to the type of which the objects in the lists are. This way you can store the comparison logic inside the type itself.
Create a list of this type (just like the lists in posCheckOptions). Lets call it bigList
iterarte over all lists in posCheckOptions
iterate over each item in the contained list and check if the item is contained in bigList. If it is, delete it from the current inner list. If not add it to bigList
I'm making some assumptions about the Type that contains your stock information, feel free to adjust as necessary. What you can do is create a dictionary mapping stock names to the stock objects. Update the object in the dictionary if it exists, or add it to the dictionary.
var allStock = mylist.SelectMany(l => l.Select(inner => inner));
var lookup = new Dictionary<string, Stock>();
foreach (var stock in allStock)
{
if (!lookup.ContainsKey(stock.Name)) {
lookup.Add(stock.Name, stock);
continue;
}
lookup[stock.Name].Quantity += stock.Quantity;
}
Now you have a dictionary mapping the names of stock to the actual stock. Just iterate over the values to get a list back out if that's what you need.

Categories

Resources