I'm trying to sort several columns of a table depending of the previous sorted column.
It works fine for the first two columns. But as soon as I sort the third column the second one loses its sort. As far as I know for now there must be a problem with my foreach loop. Here is my code for sorting:
public List<object> inhaltSortieren(List<object> zuSortierendeListe, Dictionary<string, SortierRichtung> sortierung)
{
IOrderedEnumerable<object> sortierteListe = null;
if (sortierung.First().Value == SortierRichtung.asc)
sortierteListe = zuSortierendeListe.OrderBy(x => x.GetType().GetProperty(sortierung.First().Key).GetValue(x, null));
else if (sortierung.First().Value == SortierRichtung.desc)
sortierteListe = zuSortierendeListe.OrderByDescending(x => x.GetType().GetProperty(sortierung.First().Key).GetValue(x, null));
bool first = true;
foreach (KeyValuePair<string, SortierRichtung> spalte in sortierung)
{
if (first)
{
first = false;
continue;
}
if (spalte.Value == SortierRichtung.asc)
sortierteListe = sortierteListe.ThenBy(x => x.GetType().GetProperty(spalte.Key).GetValue(x, null));
else if (spalte.Value == SortierRichtung.desc)
sortierteListe = sortierteListe.ThenByDescending(x => x.GetType().GetProperty(spalte.Key).GetValue(x, null));
}
return sortierteListe.ToList();
}
Any ideas?
Update: Maybe I add some further information:
#param zusortierendeListe: this is the List I want to sort, it is a List of Objects
#param sortierung: This is the direction I want to sort, ascending or descending
The objects themselves are Lists of Tabledata
You're passing in a Dictionary; the order in which you get values out of a Dictionary when you use it as an IEnumerable<KeyValuePair> (as your foreach loop does) is (probably) not the order in which you added them!
You'll need to use a List<KeyValuePair> (or some other ordered IEnumerable<KeyValuePair>) instead of the Dictionary, or even create a custom class to hold the field and direction and pass a List of that.
Also have a look here
Just to make your code a bit more clear. You can put everything into the for-each loop or leave it as it is, but then use sortierung.Skip(1) in your code, because you used the first entry already. I also changed the Dictionary argument to IEnumerable> according to the previous comment.
object GetValue(object value, string name)
{
return value.GetType().GetProperty(name).GetValue(value, null);
}
public List<object> SortContent(List<object> listToSort, Tuple<string, SortDirection>[] sortInfos)
{
if (sortInfos == null || sortInfos.Length == 0)
return listToSort;
IOrderedEnumerable<object> sortedList = null;
foreach (var column in sortInfos)
{
Func<object, object> sort = x => GetValue(x, column.Key);
bool desc = column.Value == SortDirection.Descending;
if (sortedList == null)
sortedList = desc ? listToSort.OrderByDescending(sort) : listToSort.OrderBy(sort);
else
sortedList = desc ? sortedList.ThenByDescending(sort) : sortedList.ThenBy(sort);
}
return sortedList.ToList();
}
Related
Hello i have a method that compares the objects of 2 Lists for differences. Right now this works but only for one property at a time.
Here is the Method:
public SPpowerPlantList compareTwoLists(string sqlServer, string database, DateTime timestampCurrent, string noteCurrent, DateTime timestampOld, string noteOld)
{
int count = 0;
SPpowerPlantList powerPlantListCurrent = loadProjectsAndComponentsFromSqlServer(sqlServer, database, timestampCurrent, noteCurrent);
SPpowerPlantList powerPlantListOld = loadProjectsAndComponentsFromSqlServer(sqlServer, database, timestampOld, noteOld);
SPpowerPlantList powerPlantListDifferences = new SPpowerPlantList();
count = powerPlantListOld.Count - powerPlantListCurrent.Count;
var differentObjects = powerPlantListCurrent.Where(p => !powerPlantListOld.Any(l => p.mwWeb == l.mwWeb)).ToList();
foreach (var differentObject in differentObjects)
{
powerPlantListDifferences.Add(differentObject);
}
return powerPlantListDifferences;
}
This works and i get 4 Objects in the new List. The Problem is that i have a few other properties that i need to compare. Instead of mwWeb for example name. When i try to change it i need for every new property a new List and a new Foreach-Loop.
e.g.
int count = 0;
SPpowerPlantList powerPlantListCurrent = loadProjectsAndComponentsFromSqlServer(sqlServer, database, timestampCurrent, noteCurrent);
SPpowerPlantList powerPlantListOld = loadProjectsAndComponentsFromSqlServer(sqlServer, database, timestampOld, noteOld);
SPpowerPlantList powerPlantListDifferences = new SPpowerPlantList();
SPpowerPlantList powerPlantListDifferences2 = new SPpowerPlantList();
count = powerPlantListOld.Count - powerPlantListCurrent.Count;
var differentObjects = powerPlantListCurrent.Where(p => !powerPlantListOld.Any(l => p.mwWeb == l.mwWeb)).ToList();
var differentObjects2 = powerPlantListCurrent.Where(p => !powerPlantListOld.Any(l => p.shortName == l.shortName)).ToList();
foreach (var differentObject in differentObjects)
{
powerPlantListDifferences.Add(differentObject);
}
foreach (var differentObject in differentObjects2)
{
powerPlantListDifferences2.Add(differentObject);
}
return powerPlantListDifferences;
Is there a way to prevent this? or to make more querys and get only 1 List with all different Objects back?
I tried it with except and intersect but that didnt worked.
So any help or advise would be great and thx for your time.
PS: If there is something wrong with my question-style please say it to me becouse i try to learn to ask better questions.
You may be able to simply chain the properties that you wanted to compare within your Where() clause using OR statements :
// This should get you any elements that have different A properties, B properties, etc.
var different = current.Where(p => !old.Any(l => p.A == l.A || p.B == l.B))
.ToList();
If that doesn't work and you really want to use the Except() or Intersect() methods to properly compare the objects, you could write your own custom IEqualityComparer<YourPowerPlant> to use to properly compare them :
class PowerPlantComparer : IEqualityComparer<YourPowerPlant>
{
// Powerplants are are equal if specific properties are equal.
public bool Equals(YourPowerPlant x, YourPowerPlant 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;
// Checks the other properties to compare (examples using mwWeb and shortName)
return x.mwWeb == y.mwWeb && x.shortName == y.shortName;
}
// If Equals() returns true for a pair of objects
// then GetHashCode() must return the same value for these objects.
public int GetHashCode(YourPowerPlant powerPlant)
{
// Check whether the object is null
if (Object.ReferenceEquals(powerPlant, null)) return 0;
// Get hash code for the mwWeb field if it is not null.
int hashA = powerPlant.mwWeb == null ? 0 : powerPlant.mwWeb.GetHashCode();
// Get hash code for the shortName field if it is not null.
int hashB = powerPlant.shortName == null ? 0 : powerPlant.shortName.GetHashCode();
// Calculate the hash code for the product.
return hashA ^ hashB;
}
}
and then you could likely use something like one of the following depending on your needs :
var different = current.Except(old,new PowerPlantComparer());
or :
var different = current.Intersect(old,new PowerPlantComparer());
One way is to use IEqualityComparer as Rion Williams suggested, if you'd like a more flexible solution you can split logic in to two parts. First create helper method that accepts two lists, and function where you can define what properties you wish to compare. For example :
public static class Helper
{
public static SPpowerPlantList GetDifference(this SPpowerPlantList current, SPpowerPlantList old, Func<PowerPlant, PowerPlant, bool> func)
{
var diff = current.Where(p => old.All(l => func(p, l))).ToList();
var result = new SPpowerPlantList();
foreach (var item in diff) result.Add(item);
return result;
}
}
And use it :
public SPpowerPlantList compareTwoLists(string sqlServer, string database,
DateTime timestampCurrent, string noteCurrent,
DateTime timestampOld, string noteOld)
{
var powerPlantListCurrent = ...;
var powerPlantListOld = ...;
var diff = powerPlantListCurrent.GetDifference(
powerPlantListOld,
(x, y) => x.mwWeb != y.mwWeb ||
x.shortName != y.shortName);
return diff;
}
P.S. if it better suits your needs, you could move method inside of existing class :
public class MyClass
{
public SPpowerPlantList GetDifference(SPpowerPlantList current, SPpowerPlantList old, Func<PowerPlant, PowerPlant, bool> func)
{
...
}
}
And call it (inside of class) :
var result = GetDifference(currentValues, oldValues, (x, y) => x.mwWeb != y.mwWeb);
The easiest way to do this would be to compare some unique identifier (ID)
var differentObjects = powerPlantListCurrent
.Where(p => !powerPlantListOld.Any(l => p.Id == l.Id)
.ToList();
If the other properties might have been updated and you want to check that too, you'll have to compare all of them to detect changes made to existing elements:
Implement a camparison-method (IComparable, IEquatable, IEqualityComparer, or override Equals) or, if that's not possible because you didn't write the class yourself (code generated or external assembly), write a method to compare two of those SPpowerPlantList elements and use that instead of comparing every single property in Linq. For example:
public bool AreThoseTheSame(SPpowerPlantList a,SPpowerPlantList b)
{
if(a.mwWeb != b.mwWeb) return false;
if(a.shortName != b.shortName) return false;
//etc.
return true;
}
Then replace your difference call with this:
var differentObjects = powerPlantListCurrent
.Where(p => !powerPlantListOld.Any(l => AreThoseTheSame(p,l))
.ToList();
I used .Net framwork 4.0 with WinForm application component DataGridView and set DataSource with DataTable.Then there's a button to add row into DataGridView.
That code like this.
gridTable = (DataTable)dgrMainGrid.DataSource;
DataRow dr = gridTable.NewRow();
Before adding New Row into DataTable I checked if there's a duplicate row.To do that I used this LINQ Query.
//Item Code cannot duplicate
var results = from itmCode in gridTable.AsEnumerable()
where (itmCode.Field<string>("Item Code") == txtGrdItmLoc.Text)
select itmCode;
There after how I check the duplicate rows available or not in the data table?
if(//doWhatever function here ){
//if there's duplicate row values
isNotDuplicate = false;
}
else{
isNotDuplicate=true;
}
Before go to following step I need to get is there a duplicate or not and set it into isNotDuplicate variable or similar thing to check that. so i think to count the results rows but there's no such function to count 'var results`, Any possibility to do that?
if (!isDuplicate)
{
dr["#"] = true;
dr["Item Code"] = lSysItemCode;
dr["Stock Code"] = txtGdrItmItemLoc.Text;
dr["Brand"] = txtGrdItmBrand.Text;
dr["Model No"] = cmbGrdItmModel.SelectedValue.ToString();
gridTable.Rows.Add(dr);
dgrMainGrid.DataSource = gridTable;
}
I can use for loop with DataTable and check whether it's contain new value that equals to "Item Code" but I looking alternative method with linq.
Simply I'm looking replacement for this by using linq.
foreach (DataRow r in gridTable.Rows) {
if (r["Item Code"].ToString() == txtGrdItmLoc.Text) {
isDuplicate = true;
}
}
Sample Project : http://1drv.ms/1K4JnHt
Sample Code : http://pastebin.com/v7NMdUrf
You have not made it clear that in your DataTable if you are looking for duplicates for any specific Item Code or for any Item Code. Anyways,here is the code for both the scenarios:-
If you are looking for duplicates for any specific Item Code then you can simply check the count like this:-
bool istxtGrdItmLocDuplicate = gridTable.AsEnumerable()
.Count(x => x.Field<string>("ItemCode") == txtGrdItmLoc.Text) > 1;
If you are looking for duplicates in the entire DataTable, then simply group by Item Code and check the respective count for each Item Code like this:-
bool isDuplicate = gridTable.AsEnumerable()
.GroupBy(x => x.Field<string>("ItemCode")
.Any(x => x.Count() > 1);
IEnumerable<T> has a Count() method (check here), if you are not seeing it in intellisense then you are missing some using instruction, like using System.Linq; or some other...
Then you would just do:
if(results.Count()>0){
//if there's duplicate row values
isNotDuplicate = false;
}
else
{
isNotDuplicate=true;
}
First cast it to IEnumerable :
Convert DataRowCollection to IEnumerable<T>
Then you can use LINQ extension methods to do something like this (to check all values that has duplicates):
var duplicates = resultsList.Where(r => resultsList.Count(r2 => r2.Field<string>("Item Code") == r.Field<string>("Item Code")) > 0);
If you want check each value for duplicate you can use .Count method, something like this:
bool hasDuplicates = resultsList.Count(r2 => r2.Field<string>("Item Code") == "your code") > 1;
Ok, if for some reason this doesn't work you can write this function yourself:
public static class Helper
{
// or other collection type
public static int MyCount<T>(this IEnumerable<T> collection, Func<T, bool> function)
{
int count = 0;
foreach (T i in collection)
if (function(i)) ++count;
return count;
}
}
And use it like :
results.MyCount(r => r.Field<string>("Item Code") == "Item Code");
I am trying to query a list of dictionary by dictionary key and value using linq. The following gives me the error of "cannot convert keyvaluepair to type bool."
Thanks in advance.
var list = new List<Dictionary<string, object>>();
foreach (DataRow row in wordCloud.Rows)
{
var dict = new Dictionary<string, object>();
foreach (DataColumn col in wordCloud.Columns)
{
dict[col.ColumnName] = row[col];
}
list.Add(dict);
}
if (!string.IsNullOrWhiteSpace(text))
{
var item = list.Where(dict => dict.Where(x => x.Key == "word" && x.Value == text)).FirstOrDefault();
}
Thanks this is what I am using.
var item = list.Where(dict => dict["WORD"].Equals(text)).FirstOrDefault();
Your compiler error is caused by your predicate in list.Where not using a Boolean expression. dict.Where(...) is going to produce an IEnumerable<KeyValuePair<K,V>>, which is not a Boolean operation. Furthermore, your technique misuses a dictionary, because it will only have one pair that has a given key, there is no need to loop over it. To deal with both issues, I suggest writing a method to investigate the dictionary and produce a Boolean result for matches.
bool DictionaryContainsText(Dictionary<string, object> dictionary, string text)
{
string key = "word";
if (dictionary.ContainsKey(key) && dictionary[key] != null)
{
return dictionary[key].Equals(text);
}
return false;
}
You can then consume this method in the filtering of your list.
var item = list.Where(dict => DictionaryContainsText(dict, text)).FirstOrDefault();
All that said, I wonder if you are starting from the wrong design? A DataTable to a List<Dictionary<K,V>> seems a bit less intuitive to use than a list of a defined type. Should you not consider defining a class with appropriately named (and typed!) properties that you could consume instead? This is left as an activity for you to consider.
Your error is happening because the predicate of list.Where( ... ) is dict => dict.Where( ... ) which isn't a Boolean value.
Depending on how you want your code to work, you could potentially replace it with list.FirstOrDefault(dict => dict.Any( ... )), which would eventually return the first dictionary that contains the key-value pair ("word", text). (I think this is the intended functionality of the code, but I can't be positive without further information.)
I have a Dictionary, I want to write a method to check whether all values are same in this Dictionary.
Dictionary Type:
Dictionary<string, List<string>>
List {1,2,3}`and {2,1,3} are same in my case.
I have done this previously for simple datatype values, but I can not find logic for new requirement, please help me.
For simple values:
MyDict.GroupBy(x => x.Value).Where(x => x.Count() > 1)
I have also written a Generic Method to compare two datatypes in this way.
// 1
// Require that the counts are equal
if (a.Count != b.Count)
{
return false;
}
// 2
// Initialize new Dictionary of the type
Dictionary<T, int> d = new Dictionary<T, int>();
// 3
// Add each key's frequency from collection A to the Dictionary
foreach (T item in a)
{
int c;
if (d.TryGetValue(item, out c))
{
d[item] = c + 1;
}
else
{
d.Add(item, 1);
}
}
// 4
// Add each key's frequency from collection B to the Dictionary
// Return early if we detect a mismatch
foreach (T item in b)
{
int c;
if (d.TryGetValue(item, out c))
{
if (c == 0)
{
return false;
}
else
{
d[item] = c - 1;
}
}
else
{
// Not in dictionary
return false;
}
}
// 5
// Verify that all frequencies are zero
foreach (int v in d.Values)
{
if (v != 0)
{
return false;
}
}
// 6
// We know the collections are equal
return true;
Implement an IEqualityComparer for List<string> that compares two list based on their content. Then just use Distinct on Values and check the count:
dictionary.Values.Distinct(new ListEqualityComparer()).Count() == 1
This should do the trick
var lists = dic.Select(kv => kv.Value.OrderBy(x => x)).ToList();
var first = lists.First();
var areEqual = lists.Skip(1).All(hs => hs.SequenceEqual(first));
You'll need to add some checks to make this work for the empty case.
...or if you want to take #Selman's approach here's an implementation of the IEqualityComparer:
class SequenceComparer<T>:IEqualityComparer<IEnumerable<T>>
{
public bool Equals(IEnumerable<T> left, IEnumerable<T> right)
{
return left.OrderBy(x => x).SequenceEqual(right.OrderBy(x => x));
}
public int GetHashCode(IEnumerable<T> item)
{
//no need to sort because XOR is commutative
return item.Aggregate(0, (acc, val) => val.GetHashCode() ^ acc);
}
}
You could make a variant of this combining the best of both approaches using a HashSet<T> that might be considerably more efficient in the case that you have many candidates to test:
HashSet<IEnumerable<int>> hs = new HashSet<IEnumerable<int>>(new SequenceComparer<int>());
hs.Add(dic.First().Value);
var allEqual = dic.All(kvp => !hs.Add(kvp.Value));
This uses the feature of HashSets that disallows adding more than one item that is considered equal with an item already in the set. We make the HashSet use the custom IEqualityComparer above...
So we insert an arbitrary item from the dictionary before we start, then the moment another item is allowed into the set (i.e. hs.Add(kvp.Value) is true), we can say that there's more than one item in the set and bail out early. .All does this automatically.
Selman22's answer works perfectly - you can also do this for your Dictionary<string, List<string>> without having to implement an IEqualityComparer yourself:
var firstValue = dictionary.Values.First().OrderBy(x => x);
return dictionary.Values.All (x => x.OrderBy(y => y).SequenceEqual(firstValue));
We compare the first value to every other value, and check equality in each case. Note that List<string>.OrderBy(x => x) simply sorts the list of strings alphabetically.
Its not the fastest sdolution, but its works for me:
bool AreEqual = l1.Intersect(l2).ToList().Count() == l1.Count() && l1.Count() == l2.Count();
i have this code
private static IQueryable<Persoon> Filter(IQueryable<Persoon> qF, IDictionary<string, string> filter)
{
IQueryable<Persoon> temp;
temp = qF;
foreach (var key in filter)
{
if (key.Key == "naam")
{
temp = temp.Where(f => f.Naam == key.Value);
}
else if (key.Key == "leeftijd")
{
temp = temp.Where(af => af.Leeftijd != null && af.Leeftijd.AantalJaarOud.ToString() == key.Value);
}
}
return temp;
}
what it does (it's a simplified version which is made to test the behaviour) is that you give this function a IQueryable of Persoon (from the database) and a list of filters.
So you give the filter naam,john and leefttijd,30 you get all Persoon objecten named John and Age 30.
When i enter the loop first, right after i do the first where (the leeftijd where) at the } after it, i see that tmp has 3 objects. Then the code goes for the second time in the loop, enters the first If (where filter eq naam) and right there, when i look at tmp, it only has 0 objects.
what the first view of it not working was, was the fact that the function returns no results (should be 2: 3 30's and 2 Johns of those). So i concluded that the multiple .Where was the problem.
But now i see that the temp is empty even BEFORE i do the second where.
What am i doing wrong?
LINQ's lambda expressions use late parameter binding so when the xpression is finally processed the variable "key" no longer points at the right values.
Try changing your code to store key in a local variable and use it instead:
private static IQueryable<Persoon> Filter(IQueryable<Persoon> qF, IDictionary<string, string> filter)
{
IQueryable<Persoon> temp;
temp = qF;
foreach (var key in filter)
{
var currentKeyValue = key.Value;
if (key.Key == "naam")
{
temp = temp.Where(f => f.Naam == currentKeyValue);
}
else if (key.Key == "leeftijd")
{
temp = temp.Where(af => af.Leeftijd != null && af.Leeftijd.AantalJaarOud == Int32.Parse(currentKeyValue));
}
}
return temp;
}
Another thing I think you should change is the casting of the age field to string rather than the opposite direction. therefore the database is comparing numbers rather than strings.