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.
Related
I have a List of Dictionary<String, String> data structure. I want to filter it to get only those Dictionary entries that match some key-value pairs (input to the filtering method). Number of these key-value pairs vary from one call to another.
I wrote following code to achieve what I wanted to do. If I use the GetPlanningDataMatching method, it works perfect without any issues.
However, if I use GetPlanningDataMatching_alt method, I get index out of bounds error at (row[planningDataKeys[inx]] == planningDataValues[inx]). inx is equal to the planningDataKeys.Count.
What am I doing wrong?
My question is different than What is an IndexOutOfRangeException / ArgumentOutOfRangeException and how do I fix it?, otherwise both of my methods - GetPlanningDataMatching and GetPlanningDataMatching_alt would have failed.
// Build this data structure in the class constructor (not shown here)
List<Dictionary<String, String>> planningData = null;
// planningDataKeys.Count is always same as planningDataValues.Count
public List<Dictionary<String, String>> GetPlanningDataMatching_alt(List<String> planningDataKeys, List<String> planningDataValues)
{
IEnumerable<Dictionary<String, String>> matchingPlanningData = null;
for (int inx = 0; inx < planningDataKeys.Count; ++inx)
matchingPlanningData = (inx == 0 ? planningData : matchingPlanningData)
.Where(row => row[planningDataKeys[inx]] == planningDataValues[inx]);
return matchingPlanningData.ToList();
}
// planningDataKeys.Count is always same as planningDataValues.Count
public List<Dictionary<String, String>> GetPlanningDataMatching(List<String> planningDataKeys, List<String> planningDataValues)
{
List<Dictionary<String, String>> matchingPlanningData = null;
for (int inx = 0; inx < planningDataKeys.Count; ++inx)
matchingPlanningData = (inx == 0 ? planningData : matchingPlanningData)
.Where(row => row[planningDataKeys[inx]] == planningDataValues[inx])
.ToList();
return matchingPlanningData;
}
My gut feeling is your problem is in not materialising the collection within the for loop.
E.g. in the second method, you do the .ToList() each iteration, which is why your inx is captured correctly.
But in the _alt method, you capture a reference to inx, each iteration INCREASES the inx, and then you use the backwards ++inx rather than the typical inx++ meaning on the LAST iteration it WILL be equal to the count.
And finally, you call the .tolist() on your IEnumerable which causes the code to look at the indexes beyond what is in the array.
So moving .ToList() would fix it.
As a side note, this whole thing seem really strange from the readability perspective.
How about a
public List<Dictionary<String, String>> GetPlanningDataMatching(Func<KeyValuePair, Bool> predicate)
{
return this.planningData.Where(predicate).ToList();
}
And then to call, do something like
var stuff = new Dictionary<String, String>(){{"a", "b"}};
WhateverClass()
.GetPlanningDataMatching(kp => stuff.ContainsKey(kp.Key) && stuff[kp.Key] == kp.Value);
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'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();
}
I have a Dictionary<string, string>.
I need to look within that dictionary to see if a value exists based on input from somewhere else and if it exists remove it.
ContainsValue just says true/false and not the index or key of that item.
Help!
Thanks
EDIT: Just found this - what do you think?
var key = (from k in dic where string.Compare(k.Value, "two", true) ==
0 select k.Key).FirstOrDefault();
EDIT 2: I also just knocked this up which might work
foreach (KeyValuePair<string, string> kvp in myDic)
{
if (myList.Any(x => x.Id == kvp.Value))
myDic.Remove(kvp.Key);
}
Are you trying to remove a single value or all matching values?
If you are trying to remove a single value, how do you define the value you wish to remove?
The reason you don't get a key back when querying on values is because the dictionary could contain multiple keys paired with the specified value.
If you wish to remove all matching instances of the same value, you can do this:
foreach(var item in dic.Where(kvp => kvp.Value == value).ToList())
{
dic.Remove(item.Key);
}
And if you wish to remove the first matching instance, you can query to find the first item and just remove that:
var item = dic.First(kvp => kvp.Value == value);
dic.Remove(item.Key);
Note: The ToList() call is necessary to copy the values to a new collection. If the call is not made, the loop will be modifying the collection it is iterating over, causing an exception to be thrown on the next attempt to iterate after the first value is removed.
Dictionary<string, string> source
//
//functional programming - do not modify state - only create new state
Dictionary<string, string> result = source
.Where(kvp => string.Compare(kvp.Value, "two", true) != 0)
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value)
//
// or you could modify state
List<string> keys = source
.Where(kvp => string.Compare(kvp.Value, "two", true) == 0)
.Select(kvp => kvp.Key)
.ToList();
foreach(string theKey in keys)
{
source.Remove(theKey);
}
Loop through the dictionary to find the index and then remove it.
Here is a method you can use:
public static void RemoveAllByValue<K, V>(this Dictionary<K, V> dictionary, V value)
{
foreach (var key in dictionary.Where(
kvp => EqualityComparer<V>.Default.Equals(kvp.Value, value)).
Select(x => x.Key).ToArray())
dictionary.Remove(key);
}
You can use the following as extension method
public static void RemoveByValue<T,T1>(this Dictionary<T,T1> src , T1 Value)
{
foreach (var item in src.Where(kvp => kvp.Value.Equals( Value)).ToList())
{
src.Remove(item.Key);
}
}
In my case I use this
var key=dict.FirstOrDefault(m => m.Value == s).Key;
dict.Remove(key);
Is there a built-in function for converting a string array into a dictionary of strings or do you need to do a loop here?
Assuming you're using .NET 3.5, you can turn any sequence (i.e. IEnumerable<T>) into a dictionary:
var dictionary = sequence.ToDictionary(item => item.Key,
item => item.Value)
where Key and Value are the appropriate properties you want to act as the key and value. You can specify just one projection which is used for the key, if the item itself is the value you want.
So for example, if you wanted to map the upper case version of each string to the original, you could use:
var dictionary = strings.ToDictionary(x => x.ToUpper());
In your case, what do you want the keys and values to be?
If you actually just want a set (which you can check to see if it contains a particular string, for example), you can use:
var words = new HashSet<string>(listOfStrings);
You can use LINQ to do this, but the question that Andrew asks should be answered first (what are your keys and values):
using System.Linq;
string[] myArray = new[] { "A", "B", "C" };
myArray.ToDictionary(key => key, value => value);
The result is a dictionary like this:
A -> A
B -> B
C -> C
IMO, When we say an Array we are talking about a list of values that we can get a value with calling its index (value => array[index]), So a correct dictionary is a dictionary with a key of index.
And with thanks to #John Skeet, the proper way to achieve that is:
var dictionary = array
.Select((v, i) => new {Key = i, Value = v})
.ToDictionary(o => o.Key, o => o.Value);
Another way is to use an extension method like this:
public static Dictionary<int, T> ToDictionary<T>(this IEnumerable<T> array)
{
return array
.Select((v, i) => new {Key = i, Value = v})
.ToDictionary(o => o.Key, o => o.Value);
}
If you need a dictionary without values, you might need a HashSet:
var hashset = new HashSet<string>(stringsArray);
What do you mean?
A dictionary is a hash, where keys map to values.
What are your keys and what are your values?
foreach(var entry in myStringArray)
myDictionary.Add(????, entry);
I'll assume that the question has to do with arrays where the keys and values alternate. I ran into this problem when trying to convert redis protocol to a dictionary.
private Dictionary<T, T> ListToDictionary<T>(IEnumerable<T> a)
{
var keys = a.Where((s, i) => i % 2 == 0);
var values = a.Where((s, i) => i % 2 == 1);
return keys
.Zip(values, (k, v) => new KeyValuePair<T, T>(k, v))
.ToDictionary(kv => kv.Key, kv => kv.Value);
}
Dictionary<int, string> dictionaryTest = new Dictionary<int, string>();
for (int i = 0; i < testArray.Length; i++)
{
dictionaryTest.Add(i, testArray[i]);
}
foreach (KeyValuePair<int, string> item in dictionaryTest)
{
Console.WriteLine("Array Position {0} and Position Value {1}",item.Key,item.Value.ToString());
}
The Question is not very clear, but Yes you can convert a string to Dictionary provided the string is delimited with some characters to support Dictionary<Key,Value> pair
So if a string is like a=first;b=second;c=third;d=fourth you can split it first based on ; then on = to create a Dictionary<string,string> the below extension method does the same
public static Dictionary<string, string> ToDictionary(this string stringData, char propertyDelimiter = ';', char keyValueDelimiter = '=')
{
Dictionary<string, string> keyValuePairs = new Dictionary<string, string>();
Array.ForEach<string>(stringData.Split(propertyDelimiter), s =>
{
if(s != null && s.Length != 0)
keyValuePairs.Add(s.Split(keyValueDelimiter)[0], s.Split(keyValueDelimiter)[1]);
});
return keyValuePairs;
}
and can use it like
var myDictionary = "a=first;b=second;c=third;d=fourth".ToDictionary();
since the default parameter is ; & = for the extension method.
You can create a dictionary from an IEnumerable<T>, including an array, via:
var dictionary = myEnumerable.ToDictionary(element => element.Key,
element => element.Value)
where Key and Value are the key and value you want to store in each dictionary element. Available in .NET Framework 3.5+/.NET Core 1.0+/.NET 5.0+. Official documentation.
If you want the dictionary values to be the elements from the original enumerable:
var dictionary = myEnumerable.ToDictionary(element => element.Key)
If you only need high-performance set operations, you may be able to use:
var words = new HashSet<string>(listOfStrings);
In simple terms, the HashSet class can be thought of as a Dictionary<TKey,TValue> collection without values. Official documentation.
(Note that a 'sequence' in an entirely unrelated object.
Originally submitted an existing answer edit but it was rejected by the author so posting separately, including with links to the official Microsoft documentation.)