Select highest Age where Age is closest to a number - c#

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

Related

Sort descending by salary where teacher year = 3

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 have a list of date of birth. I want to convert it by age group. i.e: group-1: age 0 - 15, group 2: age 15 -20 etc

var allBirthdate = await _Repository.GetAll().Where(x => x.DateOfBirth.HasValue).
Select(x => x.DateOfBirth.Value).ToListAsync();
If you wish to use groupby method, you can use following approach but it's a bit complicated and messy in your case. But this may be followed in some cases.
private static (List<Person> OlderPersons, List<Person> YoungerPersons) GroupByAgeV1(List<Person> people, DateTime refDate)
{
var groupedDates = people.Where(x => x.DateOfBirth.HasValue).GroupBy(p => p.DateOfBirth >= refDate).ToList();
var olderPersons = groupedDates.Where(g => g.Key == false)
.Select(g => g.ToList())
.ToList()
.FirstOrDefault();
var youngerPersons = groupedDates.Where(g => g.Key == false)
.Select(g => g.ToList())
.ToList()
.FirstOrDefault();
return (OlderPersons: olderPersons, YoungerPersons: youngerPersons);
}
This approach seems better and easier to read in your case
private static (List<Person> OlderPersons, List<Person> YoungerPersons) GroupByAgeV2(List<Person> people, DateTime refDate)
{
var peopleHasBirtDate = people.Where(x => x.DateOfBirth.HasValue).ToList();
var olderPersons = peopleHasBirtDate.Where(p => p.DateOfBirth >= refDate).ToList();
var youngerPersons = peopleHasBirtDate.Where(p => p.DateOfBirth < refDate).ToList();
return (OlderPersons: olderPersons, YoungerPersons: youngerPersons);
}
Add a property (read-only) to your class, or write an extension to calculate the age group:
public int AgeGroup
{
get
{
if (!DateOfBirth.HasValue)
{
return 0;
}
int year = DateTime.Now.Year;
int age = DateTime.Now.Year - DateOfBirth.Value.Year;
return
age <= 15 ? 1 :
age <= 20 ? 2 :
age <= 35 ? 3 :
age <= 60 ? 4 : 5;
}
}
Then, get a dictionary which has the age group id (1,2,3) as the key and a list of your class instances which fall in this age group as the value, like the following:
Dictionary<int, List<Person>> byAgeGroup = people
.GroupBy(x => x.AgeGroup)
.ToDictionary(x => x.Key, x => x.ToList());
Here's a simple console app that takes a similar approach as outlined by #Oguz Ozgul:
using System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApp3
{
internal class Person
{
public string Name { get; set; }
public DateTime DateOfBirth { get; set; }
public int Age
=> (int) Math.Floor((DateTimeOffset.UtcNow - DateOfBirth).TotalDays / 365);
public int AgeGroup
{
get
{
if (Age < 15) // [0,14]
return 1;
if (Age < 20) // [15,19]
return 2;
if (Age < 30) // [20,29]
return 3;
if (Age < 40) // [30,39]
return 4;
if (Age < 60) // [40, 59]
return 5;
return 6; // 60+
}
}
}
internal class Program
{
private static readonly Dictionary<int, string> _ageGroupRanges
= new Dictionary<int, string>
{
{1, "0 to 14"},
{2, "15 to 19"},
{3, "20 to 29"},
{4, "30 to 39"},
{5, "40 to 59"},
{6, "60+"}
};
private static void Main(string[] args)
{
var people = new List<Person>
{
new Person {Name = "Joe", DateOfBirth = new DateTime(1980, 01, 01)},
new Person {Name = "Dan", DateOfBirth = new DateTime(1982, 01, 01)},
new Person {Name = "Laura", DateOfBirth = new DateTime(2010, 01, 01)},
new Person {Name = "Beth", DateOfBirth = new DateTime(2020, 01, 01)}
};
var peopleGroupedByAge = people
.GroupBy(p => p.AgeGroup)
.OrderBy(g => g.Key);
foreach (var group in peopleGroupedByAge)
{
var ageGroup = group.Key;
var ageGroupRange = _ageGroupRanges[ageGroup];
var namesOfPeopleInGroup = string.Join(", ", group.Select(p => p.Name));
Console.WriteLine($"Age group {ageGroup} ({ageGroupRange}): {namesOfPeopleInGroup}");
}
Console.ReadKey(true);
}
}
}
The most important bits are the the Age and AgeGroup computed properties added on to the Person object. Step through the code in a debugger and inspect there values - you'll see that they encapsulate the logic around calculating which age group someone falls into.
Finally we need to group our Person objects into groups depending on what their age group is. We can achieve this by the GroupBy method:
var peopleGroupedByAge = people
.GroupBy(p => p.AgeGroup)
.OrderBy(g => g.Key);

Remove from list items that have fields equal to some item fields

Here is my code:
public class Person
{
public int age;
public int grade;
public string name;
}
List<Person> _list = new List<Person>();
// .... add lots of items
var personToRemove = new Person {age = 99, grade = 7, };
How to write a command that removes from _list all persons what have the same age and grade values that personToRemove has.
You have to use .RemoveAll() with predicate to remove all persons with matching details in personToRemove person object.
So your query will be.
int totalRemoved = _list.RemoveAll(x => x.age == personToRemove.age && x.grade == personToRemove.grade);
Input:
_list.Add(new Person { age = 99, grade = 7 });
_list.Add(new Person { age = 87, grade = 7 });
_list.Add(new Person { age = 57, grade = 8 });
Output:
Edit:
You can also use traditional looping for elegant way to remove match person from list of persons.
for (int i = _list.Count - 1; i >= 0; i--)
{
if (_list[i].age == personToRemove.age && _list[i].grade == personToRemove.grade)
{
_list.RemoveAt(i);
break;
}
}
You can use where method to filter out the result
List <Person> _list = new List <Person> ();
// .... add lots of items
var personToRemove = new Person {
age = 99, grade = 7,
};
_list.Add(new Person {
age = 99, grade = 7
});
_list.Add(new Person {
age = 99, grade = 7
});
_list.Add(new Person {
age = 99, grade = 8
});
_list.Add(new Person {
age = 100, grade = 8
});
var result = _list.Where(a => a.age != personToRemove.age || a.grade != personToRemove.grade);
Since you are looking some otherways without list.RemoveAll. You can use list.Except method
List<Person> _list = new List<Person>();
// get the list of Person you want to remove by using where.
List<Person> _Removelist = _list.Where(x => x.age == personToRemove.age && x.grade == personToRemove.grade).ToList();
List<Person> _finalList = _list.Except(_Removelist ).ToList();

group items by range of values using linq (IGrouping)

Say I have a list of Person class
public class Person
{
public int Age { get; set; }
public string Name { get; set; }
}
How can I group by dynamic ranges? (For example starting from the youngest person I would like to group by ranges of 5 so if the youngest person is 12 the groups would be 12-17, 18-23 ....)
How can I determine the Key of IGrouping interface? (Set the Key of each group to be the ages average in that group for example)
To get the key to group by you can create a function:
String GetAgeInterval(Int32 age, Int32 minimumAge, Int32 intervalSize) {
var group = (age - minimumAge)/intervalSize;
var startAge = group*intervalSize + minimumAge;
var endAge = startAge + intervalSize - 1;
return String.Format("{0}-{1}", startAge, endAge);
}
Assuming that the minimum age is 12 and the interval size is 5 then for ages between 12 and 16 (inclusive) the function will return the string 12-16, for ages between 17 and 21 (inclusive) the function will return the string 17-21 etc. Or you can use an interval size of 6 to get the intervals 12-17, 18-23 etc.
You can then create the groups:
var minimumAge = persons.Min(person => person.Age);
var personsByAgeIntervals = persons
.GroupBy(person => GetAgeInterval(person.Age, minimumAge, 5));
To get the average age in each group you can do something like this:
var groups = personsByAgeIntervals.Select(
grouping => new {
AgeInterval = grouping.Key,
AverageAge = grouping.Average(person => person.Age),
Persons = grouping.ToList()
}
);
This will create a sequence of groups represented by an anonymous type with properties AgeInterval, AverageAge and Persons.
Using Linq but not IGrouping (I've never used this interface, so I didn't think helping you would be the best time to start). I added a configuration class to set the min/max age as well as a basic descriptor.
public class GroupConfiguration {
public int MinimumAge { get; set; }
public int MaximumAge { get; set; }
public string Description { get; set; }
}
I created a list of Person (people) and populated it with a few sample records.
List<Person> people = new List<Person>() {
new Person(12, "Joe"),
new Person(17, "Bob"),
new Person(21, "Sally"),
new Person(15, "Jim")
};
Then I created a list of GroupConfiguration (configurations) and populated it with 3 logical-for-me records.
List<GroupConfiguration> configurations = new List<GroupConfiguration>() {
new GroupConfiguration() {MinimumAge = 0, MaximumAge=17, Description="Minors"},
new GroupConfiguration() {MinimumAge = 18, MaximumAge=20, Description="Adult-No Alcohol"},
new GroupConfiguration() {MinimumAge = 21, MaximumAge=999, Description="Adult-Alcohol"},
};
I then load them to a dictionary, to maintain the relationship between the configuration and the results that match that configration. This uses Linq to find the records from people that match MinimumAge <= age <= MaximumAge. This would allow someone to be placed in multiple results, if there were MinimumAge and Maximum age overlaps.
Dictionary<GroupConfiguration, IEnumerable<Person>> groupingDictionary = configurations.ToDictionary(groupConfiguration => groupConfiguration, groupConfiguration
=> people.Where(x => x.Age >= groupConfiguration.MinimumAge && x.Age <= groupConfiguration.MaximumAge));
Throwing this in a console program, I validated that 3 people exist in the Minors group, 0 in the Adult-No Alcohol group, and 1 in the Adult-Alcohol group.
foreach (var kvp in groupingDictionary) {
Console.WriteLine(kvp.Key.Description + " " + kvp.Value.Count());
}
Console.ReadLine();

Find 2nd max salary using linq

I have following sql query for finding 2nd max salary.
Select * From Employee E1 Where
(2) = (Select Count(Distinct(E2.Salary)) From Employee E2 Where
E2.Salary > E1.Salary)
I want to convert it into Linq statement.
I think what you're asking is to find the employee with the second-highest salary?
If so, that would be something like
var employee = Employees
.OrderByDescending(e => e.Salary)
.Skip(1)
.First();
If multiple employees may have equal salary and you wish to return an IEnumerable of all the employees with the second-highest salary you could do:
var employees = Employees
.GroupBy(e => e.Salary)
.OrderByDescending(g => g.Key)
.Skip(1)
.First();
(kudos to #diceguyd30 for suggesting this latter enhancement)
List<Employee> employees = new List<Employee>()
{
new Employee { Id = 1, UserName = "Anil" , Salary = 5000},
new Employee { Id = 2, UserName = "Sunil" , Salary = 6000},
new Employee { Id = 3, UserName = "Lokesh" , Salary = 5500},
new Employee { Id = 4, UserName = "Vinay" , Salary = 7000}
};
var emp = employees.OrderByDescending(x => x.Salary).Skip(1).FirstOrDefault();
You can define equally comparer class as bellow:
public class EqualityComparer : IEqualityComparer<Employee >
{
#region IEqualityComparer<Employee> Members
bool IEqualityComparer<Employee>.Equals(Employee x, Employee y)
{
// Check whether the compared objects reference the same data.
if (Object.ReferenceEquals(x, y))
return true;
// Check whether any of the compared objects is null.
if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null))
return false;
return x.Salary == y.Salary;
}
int IEqualityComparer<Employee>.GetHashCode(Employee obj)
{
return obj.Salary.GetHashCode();
}
#endregion
}
and use it as bellow:
var outval = lst.OrderByDescending(p => p.Id)
.Distinct(new EqualityComparer()).Skip(1).First();
or do it without equally comparer (in two line):
var lst2 = lst.OrderByDescending(p => p.Id).Skip(1);
var result = lst2.SkipWhile(p => p.Salary == lst2.First().Salary).First();
Edit: As Ani said to work with sql should do : var lst = myDataContext.Employees.AsEnumerable(); but if is for commercial software it's better to use TSQL or find another linq way.
Using LINQ, you can find the 3rd highest salary like this:
// first use LINQ to sort by salary, then skip first 2 and get next
var thirdHighestSalary= (from n in db.Employee order by n.salary descending select n).distinct().skip(2). FirstOrDefault()
// write the result to console
Console.WriteLine(Third Highest Salary is : {0},thirdHighestSalary.Salary);
This will work for duplicate record as well as nth highest salary just need to play with take and skip thats all for ex below is for 3 rd highest salary with duplicate record present in table-
emplist.OrderByDescending(x => x.Salary).Select(x=>x.Salary).Distinct().Take(3).Skip(2).First();
public class Program
{
public static void Main()
{
IList<int> intList = new List<int>() { 10, 21, 91, 30, 91, 45, 51, 87, 87 };
var largest = intList.Max();
Console.WriteLine("Largest Element: {0}", largest);
var secondLargest = intList.Max(i => {
if(i != largest)
return i;
return 0;
});
Console.WriteLine("second highest element in list: {0}", secondLargest);
}
}

Categories