Sort descending by salary where teacher year = 3 - c#

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

Related

Which of the following queries in LINQ for List is faster - Query with AND or subqueries? [duplicate]

This question already has answers here:
Should I use two "where" clauses or "&&" in my LINQ query?
(6 answers)
Closed 9 months ago.
I have the following query in LINQ against List<>
eg:
List<Person> list = new List<Person>
{
new person{ID=1,Name="john",salary=2500, state="A", Street="AA"},
new person{ID=2,Name="Sena",salary=1500, state="B", Street="AB"},
new person{ID=3,Name="Max",salary=5500, state="C", Street="AA"},
new person{ID=4,Name="Gen",salary=3500, state="A", Street="AB"}
};
is this faster?
var result = list.Where(p => p.state =="A" && p.Street=="AA" && p.salary>1000);
or
var result = list.Where(p => p.state =="A" )
.Where(p => p.Street=="AA")
.Where(p => p.salary>1000);
or
var result = list.Where(p => p.state =="A" && p.Street=="AA")
.Where(p => p.salary>1000);
Which is prefered way, although all queries returns the same results. When there are few results any query may work faster. What if List<> contains 10000 of elements. I am sure certainly it will make a difference. Which one is faster?
The third one seems fastest, see fiddle
One of the tests returned:
A: 7.675900
B: 0.157700
C: 0.002200
Below the code used:
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
static string getState(int gs)
{
switch (gs)
{
case 1:
return "A";
case 2:
return "B";
default:
return "C";
}
;
}
public static void Main()
{
Random rnd = new Random();
List<Person> list = new List<Person>{new Person{ID = 1, Name = "john", salary = 2500, state = "A", Street = "AA"}, new Person{ID = 2, Name = "Sena", salary = 1500, state = "B", Street = "AB"}, new Person{ID = 3, Name = "Max", salary = 5500, state = "C", Street = "AA"}, new Person{ID = 4, Name = "Gen", salary = 3500, state = "A", Street = "AB"}};
// add 10000 Persons
for (int x = 5; x < 100000; x++)
{
list.Add(new Person()
{ID = x, Name = x.ToString(), salary = rnd.Next(1500, 5500), state = getState(rnd.Next(1, 3)), Street = getState(rnd.Next(1, 3)) + getState(rnd.Next(1, 3))});
}
DateTime start;
// A
start = DateTime.Now;
var resultA = list.Where(p => p.state == "A" && p.Street == "AA" && p.salary > 1000);
Console.WriteLine("A: {0:N6}", (DateTime.Now - start).TotalMilliseconds / 1.0);
// B
start = DateTime.Now;
var resultB = list.Where(p => p.state == "A").Where(p => p.Street == "AA").Where(p => p.salary > 1000);
Console.WriteLine("B: {0:N6}", (DateTime.Now - start).TotalMilliseconds / 1.0);
// C
start = DateTime.Now;
var resultC = list.Where(p => p.state == "A" && p.Street == "AA").Where(p => p.salary > 1000);
Console.WriteLine("C: {0:N6}", (DateTime.Now - start).TotalMilliseconds / 1.0);
}
}
public class Person
{
public int ID { get; set; }
public string Name { get; set; }
public int salary { get; set; }
public string state { get; set; }
public string Street { get; set; }
}
EDIT: The last 6 results when testing using BenchmarkDotNet:
A: 0,001 B: 0,001 C: 0,0325
A: 0,0006 B: 0,001 C: 0,0329
A: 0,0007 B: 0,001 C: 0,0314
A: 0,0007 B: 0,001 C: 0,0312
A: 0,0007 B: 0,001 C: 0,0329
A: 0,0007 B: 0,001 C: 0,0313
Conclusion: testing is more difficult than most people believe ... 😉

C# Linq GroupBy - With Specific criteria

I have the below table which I want to group ,
Id NameId ValueId
1 1 4
1 10 18
1 9 15
2 1 4
2 10 17
2 9 0
3 1 5
3 9 16
3 10 18
4 1 5
4 10 18
4 9 16
5 1 4
5 10 17
5 9 0
The result should be grouped with Id having similar ValueId for all the corresponding NameId
Output
GroupId Id ValueId
fed1afcc-a778-48ef-9ee5-4b70886ce67c 1 4,18,15
a31055df-2e4e-472e-9301-e0e0a4e99f1e 2,5 4,17,0
8b9b3dca-4ce0-4cae-a870-1d1026bd608a 3,4 5,18,16
I actually don't need them concatenated, it is just a representation of how the output should be grouped.
I have done a working implementation using a nested for loop, which compare each entity to the subsequent entity and does a check for all the values. Am I over doing something which can be simply achieved in Linq?
Or how can this be done in Linq ?
A minimal version of my current code
var tableResult = _repository.GetData().OrderBy(x =>x.NameId).ToList();
var ids = tableResult.Select(x => x.id).Distinct().ToList();
var listOfProductGroup = new List<ProductGroup>();
for (int i = 0; i < ids.Count; i++)
{
var currentId = ids[i];
var currentEntity = tableResult.Where(x => x.id == currentId).ToList();
var productGroup = new ProductGroup(Guid.NewGuid(), currentProductId);
for (int j = i + 1; j < ids.Count; j++)
{
var subsequentId = ids[j];
var subsequentEntity = tableResult.Where(x => x.id == subsequentId ).ToList();
//This is my extension method which does the comparison
if(currentEntity.EqualsAll(subsequentEntity))
{
productGroup.Id.Add(subsequentId );
//am removing the product to avoid extra loop
ids.RemoveAt(j);
//adjust the index to match the removed product
j = j - 1;
}
}
}
listOfProductGroup.Add(productGroup);
public class ProductGroup
{
public ProductGroup(Guid groupId, int id)
{
GroupId= groupId;
Id= new List<int>()
{
id
};
}
public Guid GroupId{ get; set; }
public IList<int> Id { get; set; }
}
Something like this, perhaps:
class DictionaryComparer : IEqualityComparer<Dictionary<int, int>>
{
public bool Equals(Dictionary<int, int> x, Dictionary<int, int> y)
{
return x.Count == y.Count && !x.Except(y).Any();
}
public int GetHashCode(Dictionary<int, int> obj)
{
int hash = 0;
foreach (var kvp in obj.OrderBy(x => x.Key))
{
hash = hash ^ EqualityComparer<KeyValuePair<int, int>>.Default.GetHashCode(kvp);
}
return hash;
}
}
class Thing
{
public int Id { get; set; }
public int NameId { get; set; }
public int ValueId { get; set; }
public Thing(int id, int nameId, int valueId)
{
Id = id;
NameId = nameId;
ValueId = valueId;
}
}
class Program
{
static void Main(string[] args)
{
var data = new Thing[]
{
new Thing(1, 1, 4),
new Thing(1, 10, 18),
new Thing(1, 9, 15),
new Thing(2, 1, 4),
new Thing(2, 10, 17),
new Thing(2, 9, 0),
new Thing(3, 1, 5),
new Thing(3, 9, 16),
new Thing(3, 10, 18),
new Thing(4, 1, 5),
new Thing(4, 10, 18),
new Thing(4, 9, 16),
new Thing(5, 1, 4),
new Thing(5, 10, 17),
new Thing(5, 9, 0),
};
var #as = data.GroupBy(x => x.Id)
.Select(x => new {Id = x.Key, Data = x.ToDictionary(t => t.NameId, t => t.ValueId)})
.GroupBy(x => x.Data, x => x.Id, new DictionaryComparer());
foreach (var a in #as)
{
Console.WriteLine("{0} {1}", string.Join(",", a), string.Join(",", a.Key.Values));
}
Console.ReadKey();
}
}

linq Contains but less

I have a list to search a table,
List<long> searchListIds = new List<long>();
searchListIds.Add(1);
searchListIds.Add(2);
List<long> searchListFieldValues = new List<long>();
searchListFieldValues.Add(100);
searchListFieldValues.Add(50);
and my query is:
var adsWithRelevantadFields =
from adField in cwContext.tblAdFields
group adField by adField.adId into adAdFields
where searchListIds.All(i => adAdFields.Select(co => co.listId).Contains(i))
&& searchListFieldValues.All(i => adAdFields.Select(co => co.listFieldValue).Contains(i))
select adAdFields.Key;
everything is ok, but now: i need to get all records that meet less than searchListFieldValues. i mean:
all adId that have (listId == 1)&(listFieldValue <100) AND (listId == 2)&(listFieldValue <50)
contains part must change to something like contains-less
example:
cwContext.tblAdFields:
id 1 2 3 4 5 6 7
adId 1 2 1 2 3 3 3
listId 1 1 2 2 1 2 3
listfieldValue 100 100 50 50 100 49 10
Now if I want to get (listId == 1)&(listFieldValue ==100) AND (listId == 2)&(listFieldValue ==50) my code works, and return id adId: 1,2
but I can't get
all adId that have (listId == 1)&(listFieldValue ==100) AND (listId == 2)&(listFieldValue <50)
it must return 3
You should try changing Contains to Any, but I'm not sure if LINQ to Entities will translate it correctly into proper SQL statement.
var adsWithRelevantadFields =
from adField in cwContext.tblAdFields
group adField by adField.adId into adAdFields
where searchListIds.All(i => adAdFields.Select(co => co.listId).Contains(i))
&& searchListFieldValues.All(i => adAdFields.Select(co => co.listFieldValue).Any(x => x < i))
select adAdFields.Key;
Here is a full example that should work if I understood you correctly:
class Program
{
static void Main(string[] args)
{
List<int> searchListIds = new List<int>
{
1,
2,
};
List<int> searchListFieldValues = new List<int>
{
100,
50,
};
List<Tuple<int, int>> searchParameters = new List<Tuple<int,int>>();
for (int i = 0; i < searchListIds.Count; i++)
{
searchParameters.Add(new Tuple<int,int>(searchListIds[i], searchListFieldValues[i]));
}
List<AdField> adFields = new List<AdField>
{
new AdField(1, 1, 1, 100),
new AdField(2, 2, 1, 100),
new AdField(3, 1, 2, 50),
new AdField(4, 2, 2, 50),
new AdField(5, 3, 1, 100),
new AdField(6, 3, 2, 49),
new AdField(7, 3, 3, 10)
};
var result = adFields.Where(af => searchParameters.Any(sp => af.ListId == sp.Item1 && af.ListFieldValue < sp.Item2)).Select(af => af.AdId).Distinct();
foreach (var item in result)
{
Console.WriteLine(item);
}
Console.Read();
}
public class AdField
{
public int Id { get; private set; }
public int AdId { get; private set; }
public int ListId { get; private set; }
public int ListFieldValue { get; private set; }
public AdField(int id, int adId, int listId, int listFieldValue)
{
Id = id;
AdId = adId;
ListId = listId;
ListFieldValue = listFieldValue;
}
}
}
First, you're probably looking for functionality of Any() instead of Contains(). Another thing is that if your search criteria consists of two items - use one list of Tuple<int,int> instead of two lists. In this case you will e able to efficiently search by combination of listId and fieldValue:
var result = from adField in cwContext.tblAdFields
where searchParams.Any(sp => adField.listId == sp.Item1 && adField.listFieldValue < sp.Item2)
group adField by adField.adId into adAdFields
select adAdField.Key;

Find the best matching product version from a list of available product versions

How to return the best matching/next available product versionId from a list of available product versions ?
Here is the logic based on the sample data in the table
Look for the best matching version available less than 10.10.20 and should return its versionID
eg1:GetVersion("10.10.20") should return 5 ( because in table there is no "10,10,20" major.minor.build combination available ,so it should look for the best matching version
here the next available version is 10.7.1 ie., versionID 5
eg2:GetVersion("7.0.0") should return 3 ( because in table there is no "7,0,0" major.minor.build combination available ,so it should look for next available matching version .here the
next available version is 6.2.1 ie., versionID 3
eg3:GetVersion("7.5.1") should return 4 ,here exact match is available soit should return versionid 4
[Serializable]
public class ProductVersions
{
[Key]
public int Version_Id { get; set; }
public int Major { get; set; }
public int Minor { get; set; }
public int Build { get; set; }
}
Here is some sample data in my ProductVersions Table
[version_id , Major,Minor,Build]
1 3 0 1
2 4 10 5
3 6 2 1
4 7 5 1
5 10 7 1
6 11 10 10
Here is my method that is expected to return best available product version
private int GetVersion(string versionNumber)
{
int version-id=0;
version-id= //retrieve best matching version
return version-id
}
You can use the build-in Version class, since it already implements the <= operator you are basically looking for, and also can handle the string parsing for you:
var data = new List<Version>()
{
new Version(3,0,1),
new Version(4,10,5),
new Version(6,2,1),
new Version(7,5,1),
new Version(10,7,1),
new Version(11,10,10)
};
var case1 = new Version("10.10.20");
// match1 is 5; the index of a List is 0-based, so we add 1
var match1 = data.FindLastIndex(d => d <= case1) + 1;
var case2 = new Version("7.0.0");
// match2 is 3
var match2 = data.FindLastIndex(d => d <= case2) + 1;
var case3 = new Version("7.5.1");
// match3 is 4
var match3 = data.FindLastIndex(d => d <= case3) + 1;
It should be trivial to convert your sequence of ProductVersions to a list of Version objects.
If you don't want to use the Version class for whatever reason, you can implement the <= (and all other missing operators) yourself:
public class ProductVersions
{
//TODO error checking
public int Version_Id { get; set; }
public int Major { get; set; }
public int Minor { get; set; }
public int Build { get; set; }
public ProductVersions(int major, int minor, int build)
{
Major=major;
Minor=minor;
Build=build;
}
public ProductVersions(string version)
{
var tmp = version.Split('.');
Major = Int32.Parse(tmp[0]);
Minor = Int32.Parse(tmp[1]);
Build = Int32.Parse(tmp[2]);
}
public static bool operator == (ProductVersions a, ProductVersions b)
{
return a.Major==b.Major && a.Minor==b.Minor && a.Build==b.Build;
}
public static bool operator != (ProductVersions a, ProductVersions b)
{
return !(a==b);
}
public static bool operator <= (ProductVersions a, ProductVersions b)
{
if (a == b)
return true;
return a < b;
}
public static bool operator >= (ProductVersions a, ProductVersions b)
{
if (a == b)
return true;
return a > b;
}
public static bool operator < (ProductVersions a, ProductVersions b)
{
if(a.Major==b.Major)
if(a.Minor==b.Minor)
return a.Build < b.Build;
else
return a.Minor < b.Minor;
else
return a.Major < b.Major;
}
public static bool operator > (ProductVersions a, ProductVersions b)
{
if(a.Major==b.Major)
if(a.Minor==b.Minor)
return a.Build > b.Build;
else
return a.Minor > b.Minor;
else
return a.Major > b.Major;
}
And a simple test:
var data = new List<ProductVersions>()
{
new ProductVersions(3,0,1) { Version_Id = 1},
new ProductVersions(4,10,5) { Version_Id = 2},
new ProductVersions(6,2,1) { Version_Id = 3},
new ProductVersions(7,5,1) { Version_Id = 4},
new ProductVersions(10,7,1) { Version_Id = 5},
new ProductVersions(11,10,10) { Version_Id = 6}
};
// ensure data is sorted by version
data.Sort((a,b) => a > b ? 1 : a < b ? -1 : 0);
var case1 = new ProductVersions("10.10.20");
// match1 is 5
var match1 = data.Last(d => d <= case1).Version_Id;
var case2 = new ProductVersions("7.0.0");
// match2 is 3
var match2 = data.Last(d => d <= case2).Version_Id;
var case3 = new ProductVersions("7.5.1");
// match3 is 4
var match3 = data.Last(d => d <= case3).Version_Id;
I like Dominic's answer using the version class (why invent when it exists?) But in case you are wondering here is how to do it without using the Version class and you assume the list is already sorted (so you don't need to sort it like he did).
(TL;DR)
// assume verArray is already ordered (this would need to be sorted otherwise.)
// this where checks for less than or equal to.
int result = verArray.Where(v => (v.Major < major) ||
(v.Major == major && v.Minor < minor) ||
(v.Major == major && v.Minor == minor && v.Build <= build))
.Last().Version_Id;
The full code and test:
public ProductVersions[]verArray = {
new ProductVersions() { Version_Id = 1, Major = 3, Minor = 0, Build = 1 },
new ProductVersions() { Version_Id = 2, Major = 4, Minor = 10, Build = 5 },
new ProductVersions() { Version_Id = 3, Major = 6, Minor = 2, Build = 1 },
new ProductVersions() { Version_Id = 4, Major = 7, Minor = 5, Build = 1 },
new ProductVersions() { Version_Id = 5, Major = 10, Minor = 7, Build = 1 },
new ProductVersions() { Version_Id = 6, Major = 11, Minor = 10, Build = 10 },
};
void Main()
{
string test = "10.10.20";
Console.WriteLine(test + " gives "+GetVersion(test));
test = "7.0.0";
Console.WriteLine(test + " gives "+GetVersion(test));
test = "7.5.1";
Console.WriteLine(test + " gives "+GetVersion(test));
}
private int GetVersion(string versionNumber)
{
string [] input = versionNumber.Split(".".ToCharArray());
int major = int.Parse(input[0]);
int minor = int.Parse(input[1]);
int build = int.Parse(input[2]);
// assume verArray is already ordered (this would need to be sorted otherwise.
int result = verArray.Where(v => (v.Major < major) ||
(v.Major == major && v.Minor < minor) ||
(v.Major == major && v.Minor == minor && v.Build <= build))
.Last().Version_Id;
return result;
}
public class ProductVersions
{
public int Version_Id { get; set; }
public int Major { get; set; }
public int Minor { get; set; }
public int Build { get; set; }
}
This returns the following:
10.10.20 gives 5
7.0.0 gives 3
7.5.1 gives 4
I just checked for the samples you gave. This code works for me.
private int getver(int m, int n, int b)
{
List<ProductVersions> pv = new List<ProductVersions>();
pv.Add(new ProductVersions { Version_Id = 3, Major = 6, Minor = 2, Build = 1 });
pv.Add(new ProductVersions { Version_Id = 4, Major = 7, Minor = 5, Build = 1 });
pv.Add(new ProductVersions { Version_Id = 5, Major = 10, Minor = 7, Build = 1 });
pv.Add(new ProductVersions { Version_Id = 6, Major = 11, Minor = 10, Build = 10 });
int mm = m;
if (m == 0)
mm = int.MaxValue;
int nn = n;
if (n == 0)
nn = int.MaxValue;
int bb = b;
if (b == 0)
bb = int.MaxValue;
var v = pv.FindAll(mj => mj.Major <= m).FindAll(mn => n == 0 ? mn.Major <= mm - 1 && mn.Minor <= nn : mn.Minor <= n).FindAll(bl => b == 0 ? bl.Minor <= nn - 1 && bl.Build <= bb : bl.Build <= b).Last().Version_Id;
return v;
}
I am trying to shrink the list based on the criteria from major, minor and build levels and getting the last entity of the list. Here I assume that the list is sorted based on these values.
I found this one a quick and simple way, but with some limitations.
var v = from p in pv
select new { version = p.Version_Id, val = p.Major * 100000000 + p.Minor * 10000 + p.Build };
int vver=v.ToList().FindAll(pp => pp.val <= m * 100000000 + n * 10000 + b).Last().version;

Count of flattened parent child association in LINQ

I'm trying to get a count of parents with no children plus parents children. As I write this I realize it is better explained with code.. So, here it goes:
With these example types:
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
public List<Order> Orders { get; set; }
}
public class Order
{
public int Id { get; set; }
public string Description { get; set; }
}
And this data:
var customers = new List<Customer>
{
new Customer
{
Id = 2,
Name = "Jane Doe"
},
new Customer
{
Id = 1,
Name = "John Doe",
Orders = new List<Order>
{
new Order { Id = 342, Description = "Ordered a ball" },
new Order { Id = 345, Description = "Ordered a bat" }
}
}
};
// I'm trying to get a count of customer orders added with customers with no orders
// In the above data, I would expect a count of 3 as detailed below
//
// CId Name OId
// ---- -------- ----
// 2 Jane Doe
// 1 John Doe 342
// 1 John Doe 345
int customerAndOrdersCount = {linq call here}; // equals 3
I am trying to get a count of 3 back.
Thank you in advance for your help.
-Jessy Houle
ADDED AFTER:
I was truly impressed with all the great (and quick) answers. For others coming to this question, looking for a few options, here is a Unit Test with a few of the working examples from below.
[TestMethod]
public void TestSolutions()
{
var customers = GetCustomers(); // data from above
var count1 = customers.Select(customer => customer.Orders).Sum(orders => (orders != null) ? orders.Count() : 1);
var count2 = (from c in customers from o in (c.Orders ?? Enumerable.Empty<Order>() ).DefaultIfEmpty() select c).Count();
var count3 = customers.Sum(c => c.Orders == null ? 1 : c.Orders.Count());
var count4 = customers.Sum(c => c.Orders==null ? 1 : Math.Max(1, c.Orders.Count()));
Assert.AreEqual(3, count1);
Assert.AreEqual(3, count2);
Assert.AreEqual(3, count3);
Assert.AreEqual(3, count4);
}
Again, thank you all for your help!
How about
int customerAndOrdersCount = customers.Sum(c => c.Orders==null ? 1 : Math.Max(1, c.Orders.Count()));
If you would initialize that Order property with an empty list instead of a null, you could do:
int count =
(
from c in customers
from o in c.Orders.DefaultIfEmpty()
select c
).Count();
If you decide to keep the uninitialized property around, then instead do:
int count =
(
from c in customers
from o in (c.Orders ?? Enumerable.Empty<Order>() ).DefaultIfEmpty()
select c
).Count();
customers
.Select(customer => customer.Order)
.Sum(orders => (orders != null) ? orders.Count() : 1)
This works if you want to count "no orders" as 1 and count the orders otherwise:
int customerOrders = customers.Sum(c => c.Orders == null ? 1 : c.Orders.Count());
By the way, the question is very exemplary.
You probabbly searching for something like this:
customers.GroupBy(customer=>customer). //group by object iyself
Select(c=> //select
new
{
ID = c.Key.Id,
Name = c.Key.Name,
Count = (c.Key.Orders!=null)? c.Key.Orders.Count():0
}
);
var orderFreeCustomers = customers.Where(c=>c.Orders== null || c.Orders.Any()==false);
var totalOrders = customers.Where (c => c.Orders !=null).
Aggregate (0,(v,e)=>(v+e.Orders.Count) );
Result is the sum of those two values

Categories