I'm using .NET Core 6, SQL Server and Oracle databases. I created 2 object type generic lists. All lists contains same kind information.
Line, MaterialName, MaterialId and MaterialCount information and I don't have id information. I'm unable to join
var productList = new List<object>(); // **used elements**
var reportList = new List<object>(); // **must be used elements**
I have 4 lines and 20 materials. reportList has 40 elements, productList has 21.
I need to calculate percentile of materials. I need to proportion the materials used in an assembly line to the materials used as a percentage. The example below should use 2500 motherboards, but it looks like 2000 was used. So 100 * 2000 / 2500 = 80. So the efficiency is 80%.
Examples:
reportList element productList element
{ {
"materialId": 1, "materialId": 1,
"line": "Line1", "line": "Line1",
"materialName": "Mainboard", "materialName": "Mainboard",
"materialCount": 2500 "materialCount": 2000
}, },
Final list element have to be:
{
"materialId": 1,
"line": "Line1",
"materialName": "Mainboard",
"materialCount": 80
},
If a product has never been used, it will not be registered in the productlist. The percentage number will automatically be 0.(So materialCount must be 0. materialCount = 0). So final list elements count will be same with reportList.
What doesn't work? They are simple generic lists. After "." symbol, we can't use any information because they are list. I can't type something is equal to something. We need something different...
*from report in reportList
join product in productList
on report.Line equals generalRule.Line*
Assuming, you are using Linq to objects, you can use the moreLinq library
It has a LeftJoin extension method.
class Element
{
public int materialId { get; set; }
public string line { get; set; }
public string materialName { get; set; }
public double materialCount { get; set; } // Must be double here for the calculation to work
}
var productList = new List<Element>(); // **used elements**
var reportList = new List<Element>(); // **must be used elements**
var result = reportList
.LeftJoin(
productList,
// Join by materialId, line and materialName
r => new { materialId = r.materialId, line = r.line, materialName = r.materialName },
// No element in productList found, materialCount will be 0
r => new Element {materialId = r.materialId, line = r.line, materialName = r.materialName },
// An element in productList was found => do the calculation
(r, p) => new Element {materialId = r.materialId, line = r.line, materialName = r.materialName, materialCount = 100 * p.materialCount / r.materialCount });
var dict = new Dictionary<ClassA,int>();
I should add the Name and Seat number into the dict but I have to take it from separate classes,
eg
dict.Add(student.name)
dict.Add(class.studentlist.score)
I want it to get an output of
Student Score
Amy 78
Amy 89
Amy 45
Ben 34
.
.
.
as well as one that shows the total score if the student's name repeats more than once
Student Score
Amy (total score)
Ben (total score)
I'm not sure how to go about doing this, or if it is even possible? The name and score comes from different classes so I'm a bit confused.
You cannot have a dictionary with multiple Name keys, name should be unigue. So try to create a list
var students = new List<Student>();
students.Add(new Student { Name = "Amy", Score = 78 });
students.Add(new Student { Name = "Ben", Score = 34 });
students.Add(new Student { Name = "Amy", Score = 89 });
students.Add(new Student { Name = "Amy", Score = 45 });
List<string,int> scores = students.GroupBy(s => s.Name)
.Select(i => new Student { Name = i.Key, Score = i.Sum(x => x.Score) }).ToList();
public class Student
{
public string Name { get; set; }
public int Score { get; set; }
}
UPDATE
#Cleptus suggested to use a Dictionary<string, List> where string is a name, and list is to keep the score. It is a very interesting idea, but I like more a list, since it is more like an relational db and linq is ideal for list collections. I feel Dictionaries as too hierarchical , and the always need an extra step or code to get some information.
but dictionary could be used to keep the result information
Dictionary<string, int> result = students.GroupBy(s => s.Name)
.ToDictionary( i => i.Key, i=> i.Sum(x => x.Score));
var amyResult=result["Amy"];
Assuming your class student is:
public class Student
{
public string Name { get; set; }
}
You could use a Dictionary whose key would be a Student and whose content would be a list/array of scores.
List<Student, List<int>> results = new List<Student, List<int>>();
results.Add(new Student() { Name = "Amy"}, new List<int>() { 78, 89, 45 });
results.Add(new Student() { Name = "Ben"}, new List<int>() { 61 });
And to show the data, you just need to iterate the keys and show the data however you need (either aggregated or individually).
using System.Linq;
....
foreach (Student currentStudent in results.Keys) {
List<int> studentResults = results[currentStudent];
// This would show your first needed output (individual scores)
foreach(int result in studentResults) Console.WriteLine(currentStudent.Name + ": " + result.ToString());
// This would show your second needed output (total scores)
Console.WriteLine(currentStudent.Name + ": " + studentResults.Sum().ToString());
}
The second one takes advantage of IEnumerable.Sum()
I have a list of teachers and I want to sort in descending order by salary teachers who have years of work experience = 3.
I want experience != 3 to keep their index (keep their position) and only sorting by salary teacher have experience = 3
Please help me to solve this problem.
class Teacher
{
public int id { get; set; }
public string name { get; set; }
public int year { get; set; }
public double salary { get; set; }
public Teacher()
{
}
public Teacher(int id, string name, int year, double salary)
{
this.id = id;
this.name = name;
this.year = year;
this.salary = salary;
}
}
List<Teacher> teacher = new List<Teacher>();
teacher.Add(new Teacher(1, "Teacher A", 4, 2000));
teacher.Add(new Teacher(2, "Teacher B", 3, 3000));
teacher.Add(new Teacher(3, "Teacher C", 5, 5000));
teacher.Add(new Teacher(4, "Teacher D", 3, 4000));
teacher.Add(new Teacher(5, "Teacher E", 3, 7000));
Output:
1, Teacher A, 4, 2000
5, Teacher E, 3, 7000
3, Teacher C, 5, 5000
4, Teacher D, 3, 4000
2, Teacher B, 3, 3000
Ugly Solution, but working:
Mind: Conversion to Array is not neccessary.
using System;
using System.Linq;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
List<Teacher> teacher = new List<Teacher>();
teacher.Add(new Teacher(1, "Teacher A", 4, 2000));
teacher.Add(new Teacher(2, "Teacher B", 3, 3000));
teacher.Add(new Teacher(3, "Teacher C", 5, 5000));
teacher.Add(new Teacher(4, "Teacher D", 3, 4000));
teacher.Add(new Teacher(5, "Teacher E", 3, 7000));
var teachArr = teacher.ToArray();
// Create separate List of only those teacher, you want to re-order
// So, filter and sort.
var threeYearTeachArr = teacher
.Where(t => t.year == 3) // Filter
.OrderByDescending(t => t.salary) // Sort
.ToArray(); // Do it!
// Then replace all filtered items in the original collection
// with the sorted ones. => Only filtered will change places.
// We traverse 2 arrays, so we create two indexes and check both against their
// respective collection sizes, but we increment only the "original"
for( int i = 0, threes = 0; i < teachArr.Length && threes < threeYearTeachArr.Length; i++ )
{
// only if the current entry is one of those we sorted...
if( teachArr[i].year == 3 )
{
// ... replace it with the next entry in the sorted list.
// post-increment: use threes' value, then increment
teachArr[i] = threeYearTeachArr[threes++];
}
}
foreach( var t in teachArr )
{
Console.WriteLine($"{t.id} {t.name} | {t.year} | {t.salary}");
}
}
}
class Teacher
{
public int id { get; set; }
public string name { get; set; }
public int year { get; set; }
public double salary { get; set; }
public Teacher()
{
}
public Teacher(int id, string name, int year, double salary)
{
this.id = id;
this.name = name;
this.year = year;
this.salary = salary;
}
}
Output:
1 Teacher A | 4 | 2000
5 Teacher E | 3 | 7000
3 Teacher C | 5 | 5000
4 Teacher D | 3 | 4000
2 Teacher B | 3 | 3000
See in action: https://dotnetfiddle.net/AaIqzE
A simple and naive solution would be to just do a simple bubble sort where you only consider the year 3 teachers:
for (int i1 = 0; i1 < teacher.Count; i1++)
{
if (teacher[i1].year != 3)
continue;
for (int i2 = i1 + 1; i2 < teacher.Count; i2++)
{
if (teacher[i2].year != 3)
continue;
if (teacher[i1].salary > teacher[i2].salary)
(teacher[i1], teacher[i2]) = (teacher[i2], teacher[i1]);
}
}
This will have a performance characteristic of O(n^2) so it will perform badly if you have a lot of teachers. Fildor has a better solution, I'm just presenting an alternative.
Interesting puzzle.
My first thought is to pair the list with their indices, then split the list into pass/fail based on your filter criteria: teacher.year == 3. Then we can order the pass list, fix up the indices separately, and finally re-merge the pass and fail data back together.
Wow, sounds complex. Let's try it and see how it looks:
List<Teacher> SortYear3(IEnumerable<Teacher> source)
{
var indexed = source.Select((teacher, index) => (index, teacher)).ToArray();
var pass = indexed.Where(pair => pair.teacher.year == 3);
var passIndices = pass.Select(pair => pair.index).ToArray();
var passOrdered = pass.Select(pair => pair.teacher).OrderByDescending(teacher => teacher.salary).ToArray();
var reindex = Enumerable.Range(0, passIndices.Length).Select(i => (index: passIndices[i], teacher: passOrdered[i]));
var merged = indexed.Where(pair => pair.teacher.year != 3).Concat(reindex).OrderBy(p => p.index);
return merged.Select(pair => pair.teacher).ToList();
}
Well... it works, but mostly as an example of when LINQ is not the answer. And those intermediate arrays are a bit ugly, so let's not.
The next thought is to pull out the items you want to sort, sort them into an array, then feed them back in while adding items to a result list:
List<Teacher> SortYear3(List<Teacher> source)
{
var sorted = source.Where(t => t.year == 3).OrderByDescending(t => t.salary).ToArray();
var result = new List<Teacher>();
for (int i = 0, sortindex = 0; i < source.Count; i++)
{
var next = source[i];
if (next.year == 3)
result.Add(sorted[sortindex++]);
else
result.Add(next);
}
return result;
}
Down to one array allocation, but it still looks a little clunky. Let's copy the list to start with and just replace the ones that we sorted:
List<Teacher> SortYear3(List<Teacher> source)
{
var sorted = source.Where(t => t.year == 3).OrderByDescending(t => t.salary).ToArray();
var result = source.ToList();
for (int i = 0, sortindex = 0; i < result.Count; i++)
{
if (result[i].year == 3)
result[i] = sorted[sortindex++];
}
return result;
}
That looks much better... and is now almost exactly what #fildor wrote. Well, that's embarrassing. Let's spice it up a little: make it generic, give it some parameters to specify the filtering and sorting, etc.
IEnumerable<T> SortSelected<T, TKey>(IEnumerable<T> source, Func<T, bool> filter, Func<T, TKey> sortKey, bool descending = true)
{
var result = source.ToList();
var filtered = result.Where(filter);
var sorted = (descending ? filtered.OrderByDescending(sortKey) : filtered.OrderBy(sortKey)).ToArray();
for (int i = 0, j = 0; j < sorted.Count; i++)
{
if (filter(result[i]))
result[i] = sorted[j++];
}
return result;
}
List<Teacher> SortYear3(List<Teacher> source)
=> SortSelected(source, t => t.year == 3, t => t.salary, true).ToList();
(OK, so maybe I shouldn't answer these things when I've been up for more than 24 hours.)
Please check this answer, it is much more easier to understand and more optimised
using System;
using System.Xml.Serialization;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
List<Teacher> teacher = new List<Teacher>();
teacher.Add(new Teacher(1, "Teacher A", 4, 2000));
teacher.Add(new Teacher(2, "Teacher B", 3, 3000));
teacher.Add(new Teacher(3, "Teacher C", 5, 5000));
teacher.Add(new Teacher(4, "Teacher D", 3, 4000));
teacher.Add(new Teacher(5, "Teacher E", 3, 7000));
var expTeacher=teacher.Where(x=>x.year==3).OrderByDescending(x=>x.salary).ToList();
for(int i=0,j=0;i<teacher.Count && j<expTeacher.Count;i++)
{
if(teacher[i].year==3)
{
teacher[i]= expTeacher[j];
j++;
}
}
foreach(var teach in teacher)
{
Console.WriteLine(teach.id+", "+teach.name+", "+teach.year+", "+teach.salary);
}
}
}
class Teacher
{
public int id { get; set; }
public string name { get; set; }
public int year { get; set; }
public double salary { get; set; }
public Teacher()
{
}
public Teacher(int id, string name, int year, double salary)
{
this.id = id;
this.name = name;
this.year = year;
this.salary = salary;
}
}
I'm just guessing with the answer because in general you question is not clear either in requirement as the output which I assume is that what you are already getting.
According to response, at first what came to my head was
var t2 = teachers.Where(t => t.year == 3).OrderByDescending(t => t.salary);
var t3 = teachers.Where(t => !t2.Select(ts => ts.id).Contains(t.id));
var final = t2.Concat(t3);
Yes, it is not optimal an probably there is a better way to achieve that, but it gives output as needed (?)
Teacher = 5 Teacher E 3 7000
Teacher = 4 Teacher D 3 4000
Teacher = 2 Teacher B 3 3000
Teacher = 1 Teacher A 4 2000
Teacher = 3 Teacher C 5 5000
I understood and solved it by my way. Fildor give me the idea
List<Coach> sorted = coaches.Where(x => x.YearOfExperience == 3).OrderByDescending(x => x.Salary).ToList();
List<Coach> originalList = coaches;
int index = 0;
for (int i = 0; i < originalList.Count; i++)
{
if (originalList[i].YearOfExperience == 3)
{
originalList[i] = sorted[index++];
}
}
foreach (var item in originalList)
{
item.show();
}
If you really want to filter your list for teachers having 3 years of experience then you can simply apply Where extension method using linq.
var requiredTeachers=teacher.Where(x=>x.year==3).OrderByDescending(x=>x.salary).ToList();
I'm little perplexed by the behavior of this select LINQ statement. Just below the LOOK HERE comments you can see a select LINQ statement. That select statement is on the employees collection. So, it should accept only x as the input param. Out of curiosity I passed i to the delegate and it works. When it iterates through the select, it assigns 0 first and then it increments by 1. The result can be seen at the end of this post.
Where does the variable i get its value from? First of all, why does it allow me to use a variable i which is nowhere in the scope. It is not in the global scope neither in the local Main method. Any help is appreciated to understand this mystery.
namespace ConsoleApplication
{
using System;
using System.Collections.Generic;
using System.Linq;
public class Employee
{
public int EmployeedId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
class Program
{
static void Main(string[] args)
{
var employees = new List<Employee>()
{
new Employee() { FirstName = "John", LastName = "Doe" },
new Employee() { FirstName = "Jacob", LastName = "Doe" }
};
// LOOK HERE...
var newEmployees = employees.Select((x, i) => new { id = i, name = x.FirstName + " " + x.LastName });
newEmployees.ToList().ForEach(x => { Console.Write(x.id); Console.Write(" "); Console.WriteLine(x.name); });
Console.ReadKey();
}
}
}
The result is
0 John Doe
1 Jacob Doe
Enumerable.Select has an overload that projects the current index of the element in the sequence. Also Enumerable.Where and Enumerable.SkipWhile/TakeWhile have it. You can use it like a loop variable in a for-loop which is sometimes handy.
One example which uses the index to create an anonymous type to group a long list into groups of 4:
var list = Enumerable.Range(1, 1000).ToList();
List<List<int>> groupsOf4 = list
.Select((num, index) => new { num, index })
.GroupBy(x => x.index / 4).Select(g => g.Select(x => x.num).ToList())
.ToList(); // 250 groups of 4
or one with Where which only selects even indices:
var evenIndices = list.Where((num, index) => index % 2 == 0);
It might also be important to mention that you can use these overloads that project the index only in method-syntax. LINQ query-syntax does not support it.
Think I have been looking at my code too much.
But my problems is that I have a unordered list and I need to select the object with the highest number closes to or equals an input.
I have created this little sample to illustrate what I trying to do.
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
var persons = new List<Person>
{
new Person {Age = 10, Name = "Aaron"},
new Person {Age = 15, Name = "Alice"},
new Person {Age = 20, Name = "John"},
new Person {Age = 22, Name = "Bob"},
new Person {Age = 24, Name = "Malcom"}
};
int i = 17; //should return 'Alice 15'
int y = 22; //should return 'Bob 22
var person = persons.Where(p => p.Age <= input).OrderByDecending(p => p.Age).First();
This first excludes the ones that are greater than input (your i or y). Then starts to sort them, then it just takes the first result.
More efficient than sorting the entire collection (which might be expensive on a large input collection) using the MoreLinq project's MaxBy:
var person = persons.Where(p => p.Age <= input)
.MaxBy(p => p.Age);
var nearest = persons.OrderBy(p => Math.Abs(p.Age - i)).First();
This returns the person which age is nearest to the input value using Math.Abs to get the absolute difference.
You traverse the list and store the absolute value of your int minus the Age. Store the Person in a variable until somebody with less difference shows up. If the same, store the one with the higher Age. Done.
this is an example i have, it should help you
List<int> list = new List<int> { 10, 15, 20, 22, 24 };
int number = 17;
int closest = list.Aggregate((x, y) => Math.Abs(x - number) < Math.Abs(y - number) ? x : y);
Label2.Text = closest.ToString();