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;
Related
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 realize my title probably isn't very clear so here's an example:
I have a list of objects with two properties, A and B.
public class Item
{
public int A { get; set; }
public int B { get; set; }
}
var list = new List<Item>
{
new Item() { A = 0, B = 0 },
new Item() { A = 0, B = 1 },
new Item() { A = 1, B = 0 },
new Item() { A = 2, B = 0 },
new Item() { A = 2, B = 1 },
new Item() { A = 2, B = 2 },
new Item() { A = 3, B = 0 },
new Item() { A = 3, B = 1 },
}
Using LINQ, what's the most elegant way to collapse all the A = 2 items into the first A = 2 item and return along with all the other items? This would be the expected result.
var list = new List<Item>
{
new Item() { A = 0, B = 0 },
new Item() { A = 0, B = 1 },
new Item() { A = 1, B = 0 },
new Item() { A = 2, B = 0 },
new Item() { A = 3, B = 0 },
new Item() { A = 3, B = 1 },
}
I'm not a LINQ expert and already have a "manual" solution but I really like the expressiveness of LINQ and was curious to see if it could be done better.
How about:
var collapsed = list.GroupBy(i => i.A)
.SelectMany(g => g.Key == 2 ? g.Take(1) : g);
The idea is to first group them by A and then select those again (flattening it with .SelectMany) but in the case of the Key being the one we want to collapse, we just take the first entry with Take(1).
One way you can accomplish this is with GroupBy. Group the items by A, and use a SelectMany to project each group into a flat list again. In the SelectMany, check if A is 2 and if so Take(1), otherwise return all results for that group. We're using Take instead of First because the result has to be IEnumerable.
var grouped = list.GroupBy(g => g.A);
var collapsed = grouped.SelectMany(g =>
{
if (g.Key == 2)
{
return g.Take(1);
}
return g;
});
One possible solution (if you insist on LINQ):
int a = 2;
var output = list.GroupBy(o => o.A == a ? a.ToString() : Guid.NewGuid().ToString())
.Select(g => g.First())
.ToList();
Group all items with A=2 into group with key equal to 2, but all other items will have unique group key (new guid), so you will have many groups having one item. Then from each group we take first item.
Yet another way:
var newlist = list.Where (l => l.A != 2 ).ToList();
newlist.Add( list.First (l => l.A == 2) );
An alternative to other answers based on GroupBy can be Aggregate:
// Aggregate lets iterate a sequence and accumulate a result (the first arg)
var list2 = list.Aggregate(new List<Item>(), (result, next) => {
// This will add the item in the source sequence either
// if A != 2 or, if it's A == 2, it will check that there's no A == 2
// already in the resulting sequence!
if(next.A != 2 || !result.Any(item => item.A == 2)) result.Add(next);
return result;
});
What about this:
list.RemoveAll(l => l.A == 2 && l != list.FirstOrDefault(i => i.A == 2));
if you whould like more efficient way it would be:
var first = list.FirstOrDefault(i => i.A == 2);
list.RemoveAll(l => l.A == 2 && l != first);
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;
I have two lists of the same type. That type does not have an identifier or any other guaranteed way to programatically distinguish.
List A: {1, 2, 2, 3, 5, 8, 8, 8}
List B: {1, 3, 5, 8}
I want the items from A that are not in B.
Desired Result: {2, 2, 8, 8}
If the types had identifiers, I could use a statement like the following...
var result = listA
.Where(a => listB.Where(b => b.Id == a.Id).Count() == 0)
.ToList();
So far, the only way I can do this is with a loop where I add each item the number of times it doesn't appear in the original list.
foreach (var val in listB.Select(b => b.val).Distinct())
{
var countA = listA.Where(a => a.val == val).Count();
var countB = listB.Where(b => b.val == val).Count();
var item = listA.Where(a => a.val == val).FirstOrDefault();
for (int i=0; i<countA-countB; i++)
result.Add(item);
}
Is there a cleaner way to achieve this?
EDIT:
Here is a simplified version of the object in the lists. It's coming from a Web service that's hitting another system.
public class myObject
{
public DateTime SomeDate { get; set; }
public decimal SomeNumber; { get; set; }
public bool IsSomething { get; set; }
public string SomeString { get; set; }
}
The data I am receiving has the same values for SomeDate/SomeString and repeated values for SomeNumber and IsSomething. Two objects might have equal properties, but I need to treat them as distinct objects.
try this:
var listA = new List<Int32> {1, 2, 2, 3, 5, 8, 8, 8};
var listB = new List<Int32> {1, 3, 5, 8};
var listResult = new List<Int32>(listA);
foreach(var itemB in listB)
{
listResult.Remove(itemB);
}
What am I missing?
class Program
{
static void Main(string[] args)
{
List<int> a = new List<int>();
a.Add(1);
a.Add(2);
a.Add(2);
a.Add(3);
a.Add(5);
a.Add(8);
a.Add(8);
a.Add(8);
List<int> b = new List<int>();
b.Add(1);
b.Add(3);
b.Add(5);
b.Add(8);
foreach (int x in b)
a.Remove(x);
foreach (int x in a)
Console.WriteLine(x);
Console.ReadKey(false);
}
}
Are the objects same instances in both lists? If so you can use .Where(a => listB.Where(b => b == a).Count() == 0)
Or
.Where(a => !listB.Any(b => b == a))
You could sort both lists and then iterate through them both at the same time.
public IEnumerable<int> GetComplement(IEnumerable<int> a, IEnumerable<int> b)
{
var listA = a.ToList();
listA.Sort();
var listB = b.ToList();
listB.Sort();
int i=0,j=0;
while( i < listA.Count && j < listB.Count )
{
if(listA[i] > listB[j]) {yield return listB[j];j++;}
else if (listA[i] < listB[j]) {yield return listA[i]; i++; }
else {i++;j++;}
}
while(i < listA.Count)
{
yield return listA[i];
i++;
}
while(j < listB.Count)
{
yield return listB[j];
j++;
}
}
I don't know if this is "cleaner", but it should be more performant on large sets of data.
This is a bit nasty but it does what you want. Not sure about performance though.
var a = new List<int> { 1, 2, 2, 3, 5, 8, 8, 8 };
var b = new List<int> { 1, 3, 5, 8 };
var c = from x in a.Distinct()
let a_count = a.Count(el => el == x)
let b_count = b.Count(el => el == x)
from val in Enumerable.Repeat (x, a_count - b_count)
select val;
Why don't you implement your own equality comparer for your myObject:
public class YourTypeEqualityComparer : IEqualityComparer<myObject>
{
public bool Equals(myObject x, myObject y)
public int GetHashCode(myObject obj)
}
and then use it like this:
var list1 = new List<myObj>();
var list2 = new List<myObj>()
list1.RemoveAll(i =>
list2.Contains(list1),
new YourTypeEqualityComparer()
);
now list1 contains result.