Related
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 ... 😉
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 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();
}
}
I'm querying a datatable and I seem stuck on selecting a group of groups.
This code
var grouping = table.AsEnumerable()
.Where(x => curveids.Contains(x.Field<short>("CurveID")) && x.Field<DateTime>("Timestamp").Hour >= hour && x.Field<DateTime>("Timestamp").Hour < (hour + 1))
.GroupBy(x => x.Field<DateTime>("Timestamp")).Where(x => x.Select(y => y["CurveID"]).Count() == curveids.Count);
Groups by timestamp and returns a group of x curves, where x = curveid.Count(). It contains 5000ish groups.
However for each day there can be more than one timestamp.
int nrdays = grouping.GroupBy(z => z.Key.Date).Count();
tells me there are 255 distinct days.
I would now like to group this again, but not by time stamp but by calendar day and then take the first (as in earliest) group for each day. I tried this:
var grouping2 = grouping.GroupBy(z => z.Key.Date).OrderBy(a => a.Key).Take(curveids.Count);
but this only returns 4 groups and I dont get why?
It should return 255 groups with each of them containing the same timestamp and x curveids, so x*255 record sets.
The datatable has 3 columns, Timestamp (DateTime), CurveID(short), Price(double).
UPDATE
As requested by Mr Skeet a full example:
public class listprx
{
public DateTime timestamp;
public int curveID;
public double prx;
}
static void Main(string[] args)
{
var data = new List<listprx>();
// populating data
for (int i = 0; i < 50000; i++)
{
Random rand = new Random(i);
var tempdt = new DateTime(2016, rand.Next(1, 12), rand.Next(1, 29), rand.Next(1, 23), rand.Next(1, 59), 0);
if(i % 3 == 0)
{
data.Add(new listprx { timestamp = tempdt, curveID = 1, prx = rand.Next(1,50)});
data.Add(new listprx { timestamp = tempdt, curveID = 2, prx = rand.Next(1, 50) });
}
else if (i % 5 == 0)
{
data.Add(new listprx { timestamp = tempdt, curveID = 1, prx = rand.Next(1, 50) });
}
else
{
data.Add(new listprx { timestamp = tempdt, curveID = 1, prx = rand.Next(1, 50) });
data.Add(new listprx { timestamp = tempdt, curveID = 2, prx = rand.Next(1, 50) });
data.Add(new listprx { timestamp = tempdt, curveID = 3, prx = rand.Next(1, 50) });
}
}
// setting hour criteria
int hour = 16;
int nrcurves = 3;
// grouping by timestamp and only take those where all curves are there, (as close to the desired time as possible
var grouping = data.Where(x => x.timestamp.Hour >= hour && x.timestamp.Hour < (hour + 1))
.GroupBy(x => x.timestamp).Where(x => x.Select(y => y.curveID).Count() == nrcurves);
// Grouping by day and take only the time stamp that is closest to the hour
// this fails
var grouping2 = grouping.GroupBy(z => z.Key.Date).OrderBy(a => a.Key).Take(nrcurves);
Console.WriteLine("Nr of timestamps with all curves {0}, nr of days {1}, nr of groups in second group {2}, expected same as nr days"
, grouping.Count(), grouping.GroupBy(z => z.Key.Date).Count(), grouping2.Count());
Console.ReadLine();
}
UPDATE 2
I have removed the random element and simplified further:
public class listprx
{
public DateTime timestamp;
public int curveID;
}
static void Main(string[] args)
{
var data = new List<listprx>();
// populating data
var tempdt = new DateTime(2016, 4, 6, 16, 1, 0);
for (int i = 0; i < 4; i++)
{
if (i == 2)
{
tempdt = tempdt.AddDays(1);
}
if(i % 2 == 0 )
{
data.Add(new listprx { timestamp = tempdt, curveID = 1});
}
else
{
data.Add(new listprx { timestamp = tempdt, curveID = 1});
data.Add(new listprx { timestamp = tempdt, curveID = 2});
}
tempdt = tempdt.AddMinutes(i+1);
}
// setting hour criteria
int hour = 16;
int nrcurves = 2;
//grouping by timestamp and only take those where all curves are there, (as close to the desired time as possible
var grouping = data.Where(x => x.timestamp.Hour >= hour && x.timestamp.Hour < (hour + 1))
.GroupBy(x => x.timestamp).Where(x => x.Select(y => y.curveID).Count() == nrcurves);
//Grouping by day and take only the time stamp that is closest to the hour
//this fails
var grouping2 = grouping.GroupBy(z => z.Key.Date).OrderBy(a => a.Key).Take(nrcurves);
Console.WriteLine("Nr of timestamps with all curves {0}, nr of days {1}, nr of groups in second group {2}, expected same as nr days"
, grouping.Count(), grouping.GroupBy(z => z.Key.Date).Count(), grouping2.Count());
Console.ReadLine();
}
The expected end result is:
Timestamp CurveID
------------------------
6/4/16 16:02 1
6/4/16 16:02 2
7/4/16 16:06 1
7/4/16 16:06 2
Edited answer working on your example.
Ok, I went trought your example and fixed some bugs and my answer. Let's clear code a bit and comment what went wrong where.
Our models will be
public class Curve
{
public int CurveID { get; set; }
public DateTime Timestamp { get; set; }
}
public class CurveGroup
{
public DateTime Timestamp { get; set; }
public IEnumerable<Curve> Curves { get; set; }
}
next is function to generate test data:
public static List<Curve> GetData()
{
var data = new List<Curve>();
var startTime = new DateTime(2016, 4, 6, 16, 1, 0);
for (int i = 0; i < 4; i++)
{
if (i == 2)
{
//startTime.AddDays(1); - this line does nothing, DateTime is an immutable struct so all function changing its value returns a new copy
startTime = startTime.AddDays(1);
}
if (i % 2 == 0)
{
data.Add(CreateNewCurve(startTime, 1));
}
else
{
data.Add(CreateNewCurve(startTime, 1));
data.Add(CreateNewCurve(startTime, 2));
}
//startTime.AddMinutes(i + 1); same issue as above
startTime = startTime.AddMinutes(i + 1);
}
return data;
}
public static Curve CreateNewCurve(DateTime time, int curveID)
{
return new Curve()
{
Timestamp = time,
CurveID = curveID
};
}
and here goes main function
static void Main(string[] args)
{
var data = GetData();
int hour = 16;
int totalCurveCount = 2;
var grouping = data
.Where(x => x.Timestamp.Hour >= hour && x.Timestamp.Hour < (hour + 1))
.GroupBy(x => x.Timestamp)
.Where(x => x.Count() == totalCurveCount); //there is no need to select curveId like in your code: Where(x => x.Select(y => y.curveID).Count() == nrcurves);
var grouping2 = grouping
.GroupBy(x => x.Key.Date)
.Select(x =>
new CurveGroup
{
Timestamp = x.Key,
Curves = x.OrderBy(c => c.Key).Take(totalCurveCount).SelectMany(c => c)
}
);
foreach (var g in grouping2)
{
foreach (var c in g.Curves)
{
Console.WriteLine(c.Timestamp);
Console.WriteLine(c.CurveID);
}
}
}
this returns expected results.
Your code failed because your second grouping is not taking (Take(nrcurves)) values in groups but groups themselves. So instead of returning 255 groups with 2 values in each you return 2 groups with all values in them.
Hope this fixes your issue.
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;