Alternate of if and foreach for Linq - c#

Could there be a sophisticated LINQ for the following code.
My code is trying to prepare a dictionary of string(key), string(value), by first getting a list of objects from another dictionary and then looping over to that list of objects.
Dictionary<string, string> displayNames = new Dictionary<string, string>();
List<DefDefaultDataSet.dbEnumsRow> enumList;
//allEnums dictionary: Key as string and value as List<DefDefaultDataSet.dbEnumsRow>
//enumID is a string object
if (allEnums.TryGetValue(enumID, out enumList))
{
foreach (DefDefaultDataSet.dbEnumsRow row in enumList)
{
string enumValue = row.Value;
//If already have enumvalue ,no need to add again
if (!string.IsNullOrWhiteSpace(enumValue) && !displayNames.ContainsKey(enumValue))
{
displayNames.Add(enumValue, FindResourceVal(row.ResourceKey, uniqueKey));
}
}
}

You can keep the if statement, then filters using Where and create the dictionary using ToDictinary:
if (allEnums.TryGetValue(enumID, out enumList))
displayNames = enumList.Where(e => !string.IsNullOrWhiteSpace(e.Value) && !displayNames.ContainsKey(e.Value))
.ToDictionary(k => k.ResourceKey, v => FindResourceVal(v.ResourceKey, uniqueKey));

Related

How can I merge two dictionaries without getting ArgumentException on the key?

I can't figure out how to keep the keys and values on a dictionary when I try to merge two dictionaries. I keep getting ArgumentException due to duplicate of key. When the key match I would just like to add the value by =+ kvp.value;
I have a list of Dictionaries where the
1st Dictionary = kvp = "jump", 2;
2ndDictionary = kvp = "jump", 4;
I like to merge them and get something like:
Dictionary = kvp = "jump", 6;
That I can later add to my list of Dictionaries
I've tried to run something I found in StackOverflow thread.
foreach (var dict in listOfDict)
{
dict.SelectMany(d => d)
.ToLookup(pair => pair.Key, pair => pair.Value)
.ToDictionary(group => group.Key, group => group.First());
}
But I keep getting.
cannot be inferred from the usage. Try specifying the type arguments
explicitly.
I want to avoid getting all keys and all values on separate lists that I later loop through to add key and value on a new dictionary.
Simplest extension to list of dictionary of double values with using Linq:
public static class ExtListOfDict {
public static Dictionary<TKey, double> SumValue1<TKey>(this List<Dictionary<TKey, double>> list)
=> list?.SelectMany(i => i).ToLookup(i => i.Key, i => i.Value).ToDictionary(i => i.Key, i => i.Sum());
}
without linq:
public static Dictionary<TKey, double> SumValue2<TKey>(this List<Dictionary<TKey, double>> list) {
if(list?.Count > 0) {
var dir = new Dictionary<TKey, double>(list[0]);
for(var i = 1; i < list.Count; i++)
foreach (var kv in list[i])
if (dir.TryGetValue(kv.Key, out double sum))
dir[kv.Key] = sum + kv.Value;
else
dir.Add(kv.Key, kv.Value);
return dir;
} else
return null;
}
If you like the LINQ approach, I would go with something like this:
var dictionaries = new List<Dictionary<string, int>>(); // this is the list of dictionaries you want to merge
var unifiedDictionary = new Dictionary<string, int>(); // this is the dictionary where you merge and add the values
foreach (var kvp in dictionaries.SelectMany(dictionary => dictionary))
{
if (unifiedDictionary.ContainsKey(kvp.Key))
{
unifiedDictionary[kvp.Key] += kvp.Value;
}
else
{
unifiedDictionary.Add(kvp.Key, kvp.Value);
}
}
However, if this is too hard to read (I am not always a fan of excessive LINQ over explicit code blocks), you can use the for-loop approach:
var dictionaries = new List<Dictionary<string, int>>(); // this is the list of dictionaries you want to merge
var unifiedDictionary = new Dictionary<string, int>(); // this is the dictionary where you merge and add the values
foreach (var dictionary in dictionaries)
{
foreach (var kvp in dictionary)
{
if (unifiedDictionary.ContainsKey(kvp.Key))
{
unifiedDictionary[kvp.Key] += kvp.Value;
}
else
{
unifiedDictionary.Add(kvp.Key, kvp.Value);
}
}
}
Hope this helps you. If further help and explanations are needed, please tell me.
Here is a solution based on the CollectionsMarshal.GetValueRefOrAddDefault API (.NET 6), and on the INumber<TSelf> interface (.NET 7):
public static Dictionary<TKey, TValue> ToSumDictionary<TKey, TValue>(
this IEnumerable<Dictionary<TKey, TValue>> dictionaries)
where TValue : struct, INumber<TValue>
{
ArgumentNullException.ThrowIfNull(dictionaries);
Dictionary<TKey, TValue> result = null;
foreach (var dictionary in dictionaries)
{
if (result is null)
{
result = new(dictionary, dictionary.Comparer);
continue;
}
if (!ReferenceEquals(dictionary.Comparer, result.Comparer))
throw new InvalidOperationException("Incompatible comparers.");
foreach (var (key, value) in dictionary)
{
ref TValue refValue = ref CollectionsMarshal
.GetValueRefOrAddDefault(result, key, out bool exists);
refValue = exists ? refValue + value : value;
}
}
result ??= new();
return result;
}
The key of each KeyValuePair<TKey, TValue> in each dictionary is hashed only once.
If you are getting an exception due to duplicate keys, then it sounds like you have duplicate keys!
Have you checked the two dictionaries before you try to merge them? Simply calling =+ kvp.value without checking to see if the first dictionary already has a key of that name is very likely to be your problem.
You need to check for an existing entry with that key, and if one is found, take whatever action is appropriate for your scenario (ie ignore, overwrite, ask the user to decide, etc)

Get Values from dictionary present in List

I have a list like,
List<string> list = new List<string>();
list.Add("MEASUREMENT");
list.Add("TEST");
I have a dictionary like,
Dictionary<string, string> dict = new Dictionary<string, string>();
dict.Add("BPGA", "TEST");
dict.Add("PPPP", "TEST");
dict.Add("RM_1000", "MEASUREMENT");
dict.Add("RM_2000", "MEASUREMENT");
dict.Add("CDMA", "TEST");
dict.Add("X100", "XXX");
Now, I want to get all matched data from dictionary based on list.
Means, all data from list match with dict value then get new dictionary with following mathched values
Is there any way to achieve this by using lambda expression?
I want result like this.
Key Value
"BPGA", "TEST"
"PPPP", "TEST"
"RM_1000", "MEASUREMENT"
"RM_2000", "MEASUREMENT"
"CDMA", "TEST"
Thanks in advance!
You should be using the dictionary like it is intended to be used i.e. a common key with multiple values for example:
Dictionary<string, List<string>> dict = new Dictionary<string, List<string>>();
Then all you need to do when adding the values is:
dict.Add("TEST", new List<string>() { /*strings go in here*/ });
Then to get all the results from a key like:
List<string> testValues = dict["TEST"];
To make it safe however you should check that the key exists i.e.
if (dict.ContainsKey("TEST"))
{
//Get the values
}
Then to add values to a current key you go do something like:
dict["TEST"].Add("NewValue");
If you insist on keeping the same structure, although I do not recommend it, something like the following will work:
List<string> testKeys = new List<string>();
foreach (var pairs in dict)
{
if (pair.Value == "TEST")
{
testKeys.Add(pair.Key);
}
}
Or even the following LINQ statement:
List<string> testKeys = dict.Where(p => p.Value == "TEST").Select(p => p.Key).ToList();
For a generic query to find the ones from your list use:
List<string> values = dict.Where(p => list.Contains(p.Value)).ToList();

Can't store multiple items in IDictionary [duplicate]

This question already has an answer here:
How to store multiple items in IDictionary?
(1 answer)
Closed 6 years ago.
I have created this IDictionary:
IDictionary<string, string> trace = new Dictionary<string, string>();
my goal is use it for save the content of json deserialized. I save the content in the IDictionary like this:
var obj = JsonConvert.DeserializeObject<List<RootObject>>(responseText);
foreach (var item in obj)
{
trace["date"] = item.trace.details.date;
trace["type"] = item.trace.details.type;
}
now in the obj variable I have 180 elements, the foreach over all items available in obj. The problem is that in the trace dictionary for each iteration each item is replaced, so I get only the item of the last iteration. How can I save all items in the dictionary? A dictionary shouldn't push each item automatically in the next iteration, instead of replacing it?
As #Santosh pointed out, this is expected behavior. You could instead use a List<Dictionary<String,String>>
var traces = new List<Dictionary<string, string>>();
var obj = JsonConvert.DeserializeObject<List<RootObject>>(responseText);
foreach (var item in obj)
{
var trace = new Dictionary<String,String>();
trace["date"] = item.trace.details.date;
trace["type"] = item.trace.details.type;
...
traces.Add(trace);
}
Try:
IDictionary<string, string> trace = new Dictionary<string, IList<string>>();
trace.Add("date", new List<string>())
trace.Add("type", new List<string>())
var obj = JsonConvert.DeserializeObject<List<RootObject>>(responseText);
foreach (var item in obj)
{
trace["date"].Add(item.trace.details.date)
trace["type"].Add(item.trace.details.type)
}
Plz, feel free to improve this design.
This is not how dictionaries work in C#. As the name suggests it should be one key with one value. You don't have repeated entries in dictionaries, right?
What you're probably trying to do is add to values in each entry, so I'll suggest using Tuple, since I don't know the type of your json parsed data, I'll assume string for everything, but type really won't change anything here :
var list = new List<Tuple<string,string>>();
foreach (var item in obj)
{
list.Add(new Tuple<string, string>(item.trace.details.date, item.trace.details.type));
}
Now you'll reach each entry as list[i].Item1 for a date on a given i index and list[i].Item2 for a type on the same index.
Using LINQ, you can do this:
var obj = JsonConvert.DeserializeObject<List<RootObject>>(responseText);
var traces = obj.Select(item => new Dictionary<string, string> {
{ "date", item.trace.details.date },
{ "type", item.trace.details.type }
});
You will get an IEnumerable<Dictionary<string, string>>. There isn't really a simpler way to do what you ask other than to use a collection of dictionaries.
Perhaps you can use a Lookup<string, string>, this is a one-to-many dictionary. You can create one using ToLookup() extension method:
var dates = obj.Select(o => new { Key = "date", Value = o.trace.details.date });
var types = obj.Select(o => new { Key = "type", Value = o.trace.details.type });
var lookUp = dates.Concat(types).ToLookup(kv => kv.Key, kv => kv.Value);

How to write Dictionary contents to a textfile

I have declared a dictionary like below--
Dictionary<int, string> dic = new Dictionary<int, string>()
{
{977,"String1"},
{1021,"String2"},
{784,"String3"},
{801, "String4"}
};
In my textfile value will be contain like this-
977,"String1",
1021,"String2",
784,"String3",
801, "String4"
I want to store above dictionary keys and values manually in a text file and want to access like dictionary by for loop.
Like this--
foreach (KeyValuePair<int, string> pair in dic)
{
if (pair.Key == any_integer_value_to_compare)
{
Console.WriteLine(pair.Value);
}
}
How can I do that??
You can use serialization/De-serialization to store and retrieve dictionary data from a XML file. It's better method as far as performance is concerned.
Here is how you can implement that:
Declare class Item:
public class item
{
[XmlAttribute]
public int id;
[XmlAttribute]
public string value;
}
Your Dictionary:
Dictionary<int, string> dict = new Dictionary<int, string>()
{
{977,"String1"},
{1021,"String2"},
{784,"String3"},
{801, "String4"}
};
Create Serializer object:
XmlSerializer serializer = new XmlSerializer(typeof(item[]),newXmlRootAttribute() { ElementName = "items" });
Serialization:
serializer.Serialize(stream,dict.Select(kv=>new item(){id = kv.Key,value=kv.Value}).ToArray() );
Deserialization:
var myDict = ((item[])serializer.Deserialize(stream)).ToDictionary(i =>i.id, i => i.value);
Accessing your dictionary values
foreach (KeyValuePair<int, string> pair in myDict )
{
if (pair.Key == any_integer_value_to_compare)
{
Console.WriteLine(pair.Value);
}
}
In this way you can systematically store the state of your dictionary values at the particular instance and access it later on by de-serializing the XML file.
To create or override a CSV file (note: there're no spaces and quotes)
977,String1
1021,String2
784,String3
801,String4
you can use Linq
File.WriteAllLines(#"C:\MyTest\MyFile.txt", dic
.Select(pair => String.Join(",", pair.Key, pair.Value)));
If you want to add quotations for values, add them (the simplest implementation)
File.WriteAllLines(#"C:\MyTest\MyFile.txt", dic
.Select(pair => String.Join(",", pair.Key, "\"" + pair.Value + "\"")));
you can't add spaces since line
{801, "String4"},
is totally equals to
{801,"String4"},
In case you want to convert the dictionary into String (and then combine it
with some other text(s) in order ro put into text file):
String text = String.Join(Environment.NewLine, dic
.Select(pair => String.Join(",", pair.Key, pair.Value))

Getting rid of duplicates inside List<> contained within a dictionary

I have the following dictionary.
Dictionary<string, List<string>> dictSubjects = new Dictionary<string, List<string>>();
and I am trying to get rid of potential duplicates residing within each list instace of the respective dictionary entry.
This is what I have tried but get and error along the lines of the list being read only
foreach (var kvp in dictSubjects)
{
lstSubjectsNoDupes.Clear();
for (int i = kvp.Value.Count - 1; i >= 0; i--)
{
if(lstSubjectsNoDupes.Contains(kvp.Value[i]))
{
lstSubjectsNoDupes.Add(kvp.Value[i]);
}
}
kvp.Value = lstSubjectsNoDupes;
}
How can I effectively get rid of potential duplicates within each list of my Dictionary?
The simplest way if you don't care too much about efficiency would be:
dictSubjects = dictSubjects.ToDictionary(pair => pair.Key,
pair => pair.Value.Distinct().ToList());
Alternatively, to update the existing dictionary:
foreach (var key in dictSubjects.Keys.ToList())
{
dictSubjects[key] = dictSubjects[key].Distinct().ToList();
}
Note the use of ToList here to avoid iterating over a view of a collection which is being modified. Without this, InvalidOperationException is thrown.
What about
foreach (var kvp in dictSubjects.ToList())
dictSubjects[kvp.Key] = kvp.Value.Distinct().ToList();

Categories

Resources