I have a list of type List<KeyValuePair<string, bool>>. This list contains 8 items. I also have a string line. I'm trying to check if line does not contain a word from the TKey (string) and the TValue is false.
I'm not using a dictionary because lst could potentially contain duplicates.
This should be true if line does not contain any TKey where the corresponding TValue is false.
It seems that neither Any nor All are suiting my needs here:
if (lst.Any(x => !line.Contains(x.Key) && x.Value == false)
{
// this is always true even if line does contain a TKey.
// I'm not exactly sure why.
}
if (lst.All(x => !line.Contains(x.Key) && x.Value == false)
{
// this is always false even if line does not contain a TKey.
// I think it would only be true if line contained *every* TKey?
}
Is there a way to do this?
One way to do it is to check that all words with Value of false are missing from the target string by filtering on the p.Value:
if (list.Where(p => !p.Value).All(w => !line.Contains(x.Key))) {
...
}
You could also fix your query:
if (lst.All(x => !line.Contains(x.Key) || x.Value) {
...
}
This requires that for each KVP at least one of the following were true:
Line does not contain the key, or
The value is true
This should work for you
if (!lst.Any(x => line.Contains(x.Key) && !x.Value))
{
//line does not contain any TKey where the corresponding TValue is false
}
Related
I'm trying to generalize a duplicate checker function, which depending on which type of object, checks the properties said class has (provided in a configuration) are equal to those in another list.
I have decided to create a Dictionary, which will accept a type string for the key (Book, Author, Shop, etc.) and an array of properties that need to be equal.
Example of Dictionary enties:
"Book", ["Title", "CoverImage", "NumberOfPages"]
"Author", ["Name", "Address", "SomethingElse"]
Then, I pass an object to the function and use Reflection to get the name of the type...
obj.GetType().Name;
... which I then use to fetch the right KVP from the Dictionary, meaning that if I pass a Book object, I get "Book". We then use that to get the configuration via ...
configDictionary["obj.GetType().Name"]
... which gives us the array of strings that are the properties that we need to check equality on.
I've gotten to the part where I need something along the lines of
list.Where(x => --> for each of the strings in the array - x.GetType.GetProperty(string) && --> same for next string && same for next string
... and then I need to top it off with an...
x.Id != obj.Id
To make sure we check for duplicates based on our logic (different id's and matches on all properties but has different Id's thus - a duplicate).
The end query should look like
Books:
someList.Where(x =>
x.Title == obj.Title
&& x.CoverImage == obj.CoverImage
&& x.NumberOfPages == obj.NumberOfPages
&& x.Id != obj.Id)
.FirstOrDefault();
Authors:
someList.Where(x => x.Name == obj.Name
&& x.Address == obj.Address
&& x.SomethingElse == obj.SomethingElse
&& x.Id != obj.Id)FirstOrDefault();
Try to avoid reflection because it can slow down your application. As an alternative you can create a dictionary and put all comparators into it:
var configDictionary = new Dictionary<string, List<Func<object, object, bool>>>
{
{
"Book",
new List<Func<object, object, bool>>
{
(b1, b2) => ((Book)b1).Title == ((Book)b2).Title,
(b1, b2) => ((Book)b1).CoverImage == ((Book)b2).CoverImage,
(b1, b2) => ((Book)b1).NumberOfPages == ((Book)b2).NumberOfPages,
(b1, b2) => ((Book)b1).Id != ((Book)b2).Id,
}
},
// same for Authors
};
Now you can use it in Where method:
var typeName = obj.GetType().Name; // here we using Reflection but once per collection, not per each item
var first = someList.Where(x => configDictionary[typeName].All(f => f(x, obj))).FirstOrDefault();
Also, because FirstOrDefault also has overload that accept predicate last line can be rewritten to:
var first = someList.FirstOrDefault(x => configDictionary[typeName].All(f => f(x, obj)));
A better solution will be creating custom attribute which will tag property. Then in class override default method Equals which will get all properties with this attribute and return equality.
I'm currently working on a C# 4.7.2 application. I'm about to write an extension method for a custom type and I'm struggling with my LINQ query unfortunately.
I need filter a List<Dictionary<string, object>> to find the elements of Dictionary<string, object> in the list with a certain key and remove it from my list. Furthermore, a list entry can be null.
A list entry (dictionary) can look like this, there can be several elements with value key A, i need to remove all actually:
Key | Value
"MyId" : "A",
"Width" : 100,
"Length" : 50
Very simple structure. The tricky thing is to find the dictionary elements in the list. My extension method looks like that:
public static List<Dictionary<string, object>> RemoveItem(this List<Dictionary<string, object> items, string value)
{
var itemToRemove = items.FirstOrDefault(x => x.ContainsKey("MyId")).Values.Contains(value);
items.Remove(itemToRemove);
return items;
}
Unfortunately this LINQ query does not work correctly.
Do you know how to solve this issue?
Thank you very much!!
You want to remove all with a key and a value? You don't even need LINQ:
public static int RemoveItems(this List<Dictionary<string, object>> dictionaryList, string value)
{
int removed = dictionaryList
.RemoveAll(dict => dict.TryGetValue("MyId", out object val) && value.Equals(val));
return removed;
}
You could use the method RemoveAll of the list. Then you give in a predicate that checks the dictionary (which is a collection of KeyValuePair<TKey, TValue>):
items.RemoveAll(dict => dict.Any(kv => kv.Key == "MyId" && ( kv.Value as string ) == "A"));
Or as suggested by Tim Schmelter:
items.RemoveAll(dict => dict.TryGetValue("MyId", out object value) && (value as string) == "A");
You would need to do something like:
itemsToRemove = items.Where(x => x.ContainsKey("MyId") && x["MyId"].ToString() == value);
From your description, you seem to want the first dictionary containing the key with the value from the parameter value. That would be items.FirstOrDefault(x => x.ContainsKey(value))
What you are doing is getting dictionary containing one predefined key "myId" and then going through the objects inside the dictionary and comparing their values with your value parameter, which is not what you described you want.
If you expect more dictionaries to contain the given key,and you want to remove all of them, you should use list.RemoveAll(dict => dict.ContainsKey(value))
public static List<Dictionary<string, object>> RemoveItem(this List<Dictionary<string, object>> items, string value, string key)
{
foreach (var item in items)
{
if(item.ContainsKey(key) && item[key] == value)
{
item.Remove(key);
}
}
return items;
}
This gonna work just fine.
Below I check to see if a text value is in LstNewItems and NOT in ListOfItemsToControl. This works fine. Within the if statement i want to remove the row from LstNewItems that matches the text value? lstNewItems is a list.
string name = rows[i].Cells[0].Value.ToString();
if (MyGlobals.lstNewItems.Any(x => x.sItemName == name) && !MyGlobals.ListOfItemsToControl.Any(y => y.sItemName == name))
{
//Remove the row from LstNewItems that matches the text value
}
It's not clear what the collection actually is.
If it's a generic List<> you can do:
MyGlobals.lstNewItems.RemoveAt(i);
Tell me if it works otherwise give us the exact type of the collection.
Remember you can't modify a collection inside a loop that uses it as an enumeration. Like this:
foreach (var x in MyGlobals.lstNewItems)
{
MyGlobals.lstNewItems.Remove(x); // will throw an InvalidOperationException
}
if (MyGlobals.lstNewItems.Any(x => x.sItemName == rows[i].Cells[0].Value.ToString() && !MyGlobals.ListOfItemsToControl.Any(y => y.sItemName == rows[i].Cells[0].Value.ToString())))
{
MyGlobals.lstNewItems.RemoveAll(item => item.sItemName == rows[i].Cells[0].Value.ToString());
}
I have a dictionary in my C# program that contains a list of Key + Values.
The values are itemid, Month, Year, Count
I would like to query the dictionary by comparing a set of values (itemid, Month, Year) and return true or false if a the specific itemid + Month + Year exist.
So if all 3 values (itemid + Month + Year) exist return true else return false.
I tried something like this
(if (myd.Select(d => d.Value.itemid == item.itemid && d.Value.Month == DateRange.Month && d.Value.Year == DateRange.Year).ToString() != "")
The above didn't work.
You seem to misunderstand the usage of the Select() method. Select creates a "projection"; given an input collection (enumerable) of elements, it produces an output enumerable that is equal in cardinality, but is composed of elements that are each the result of a transformation of the corresponding element of the input.
What you need is either a Where() method (which returns a list, lesser or equal in cardinality to the input, of the elements from the input for which a boolean condition is true), or an Any() method (which returns a single "true" or "false" if any of the elements in the input meet the condition):
if(myd.Where(d => d.Value.itemid == item.itemid
&& d.Value.Month == DateRange.Month
&& d.Value.Year == DateRange.Year).Count() >= 1)
...
//produces the equivalent result but generally performs faster
if(myd.Any(d => d.Value.itemid == item.itemid
&& d.Value.Month == DateRange.Month
&& d.Value.Year == DateRange.Year))
...
If like linq-Syntax, this checks if at least one item that satisfies all three condition exists:
if((from d in myd
where d.Value.itemid == item.itemid
&& d.Value.Month == DateRange.Month
&& d.Value.Year == DateRange.Year).FirstOrDefault() != null)
You'll need to create a 2nd dictionary if you want to do lookups with another key (in this case itemid,month,year, probably placed into a Tuple), and you'll need to maintain them both whenever you add/remove from either.
use Where to filter (I'm assuming the condition in your code is defined correctly... you haven't given us enough info to evaluate that)
var filteredDictionary = myd.Where(d => d.Value.itemid == item.itemid && d.Value.Month == DateRange.Month && d.Value.Year == DateRange.Year)
use Select to transform from a KeyValuePair to just a value
var filteredValues = filteredDictionary.Select(x=>x.Value)
use Any to return a bool of whether or not an item exists
bool matchExists = myd.Any(d => d.Value.itemid == item.itemid && d.Value.Month == DateRange.Month && d.Value.Year == DateRange.Year)
Any is going to be most efficient for the problem you describe because it will stop evaluating items in the dictionary and return True as soon as a match is found
If you are working with .NET 4.0 you can use Tuples as keys.
var dict = new Dictionary<Tuple<int,int,int>,MyValueType>();
You can then test if a value exists with
var key = new Tuple<int,int,int>(item.itemid, DateRange.Month, DateRange.Year);
if(dict.ContainsKey(key)) {
...
}
Or you can test
if(dict.ContainsValue(referenceValue)) {
...
}
You will have to pass it a reference value containing what you are looking for. Make sure that your value type implements EqualityComparer<T>.
The first method will be much faster, since the dictionary is accessed by the key. If you are not accessing the dictionary by the key, you could use a List<T> as well.
I have a list of objects that I am trying to bind to a listview. I am sorting by two properties. The problem exists whereby some records may not have one of the properties. This is causing an error. I would like it to still bind the records that have the property.
IEnumerable<ERec> list = retailerList.Cast<ERec>();
lvwRetailStores.DataSource = list.OrderByDescending(r => r.Properties["RS_Partner Type"].ToString())
.ThenBy(r => r.Properties["RS_Title"].ToString());
list.Where(r => r.Properties["RS_Partner_Type"] != null && r.Properties["RS_Title"] != null)
.OrderByDescending(r => r.Properties["RS_Partner Type"].ToString())
.ThenBy(r => r.Properties["RS_Title"].ToString());
Or instead of != null, use whatever test the Properties collection has.
I've found that the ?? Operator works well. I use Parenthesis to evaluate for null,
For Example:
Datetime? Today = DateTimeValue
// Check for Null, if Null put Today's date
datetime GoodDate = Today ?? DateTime.Now
This same logic works in Lambda, just use parenthesis to ensure that the correct comparisons are used.
You can use a ternary expression in the lambda:
list.OrderByDescending(r => r.Properties["RS_Partner_Type"] == null ? null : r.Properties["RS_Partner Type"].ToString())
.ThenBy(r => r.Properties["RS_Title"] == null ? null : r.Properties["RS_Title"].ToString());
Another common approach is to give the collection a suitable default value, and return that when the collection doesn't have a particular key. For instance, if Properties implements IDictionary,
public static class IDictionaryExtension {
public static TValue GetValue<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key, TValue default) {
TValue result;
return dict.TryGetValue(key, out result) ? result : dflt;
}
}
...
lvwRetailStores.DataSource = list.OrderByDescending(r => r.GetValue("RS_Partner Type", "").ToString())
.ThenBy(r => r.GetValue("RS_Title","").ToString());