Exception upon trimming the extension of key values of dictionary - c#

I have a dictionary with key values and I am trying to trim the extension of these values but get the exception message that item with same key has already been added.
Not sure why it is the case.
This is the code I used
How can I overcome this problem?
dictFilesNotThere = dictFilesNotThere.ToDictionary
(t => t.Key.Remove(8, 3), t => t.Value);
The key value is as follow '7dim-058-ns' and I trying to get it to '7dim-058' instead

Suppose you have following items in the dictionary:
dictFilesNotThere.Add("7dim-058-ns", 1);
dictFilesNotThere.Add("7dim-058-n2", 2);
dictFilesNotThere.Add("7dim-058-n3", 2);
Then after removing via t.Key.Remove(8, 3) you will get: 7dim-058 as the key for all the above items. Since in the dictionary you can't have duplicate keys, that is why the exception.
To get rid of the problem, you can setup a counter and add that to the key, if the key is found in the dictionary before. Something like:
Dictionary<string, int> dictFilesNotThereCopy = new Dictionary<string, int>();
int counter = 0;
foreach (KeyValuePair<string,int> item in dictFilesNotThere)
{
if (dictFilesNotThereCopy.ContainsKey(item.Key.Remove(8, 3)))
dictFilesNotThereCopy.Add((item.Key.Remove(8, 3) + (counter++)).ToString(), item.Value);
else
dictFilesNotThereCopy.Add(item.Key.Remove(8, 3), item.Value);
}

If the same key already exists you have to specify which you want to keep, for example the first:
dictFilesNotThere.Select(kv => new { kv, newkey = kv.Key.Substring(0, 8) })
.GroupBy(x => x.newkey)
.ToDictionary(g => g.Key, g => g.First().kv.Value);

Related

Populate new dictionary from old dictionary

I have a Dictionary<string, int> where the string is a randomized collection of characters and the int is the ASCII sum of that string.
e.g.
["aaaaaaaaab", 971],
["aaaaaaaaba", 971],
["aaaaaaabaa", 971],
["aaaaaabaaa", 971]
I would like to make a new dictionary from the original where the new key is the value from the original, and the new value is the List<string> which would contain all the strings with the key as the ASCII sum.
e.g.
[971, List<string>{ "aaaaaaaaab", "aaaaaaaaba", "aaaaaaabaa", "aaaaaabaaa"}]
How can I achieve this? I cannot wrap my head around the required steps.
Use could GroupBy and ToDictionary
The premise is :
group by the old Value
project to a new dictionary given the values of the GroupBy
which will be the grouped list of KeyValuePair from the original dictionary, that in-turn has the key selected out of it (.Select(y => y.Key)
Example
var newDict = old.GroupBy(x => x.Value)
.ToDictionary(x => x.Key, x => x.Select(y => y.Key)
.ToList());
Additional Resources
Enumerable.GroupBy Method
Groups the elements of a sequence.
Enumerable.ToDictionary Method
Creates a Dictionary<TKey,TValue> from an IEnumerable<T>.
Since values are not unique, you need to group by Value before converting to dictionary:
var inverse = original
.GroupBy(p => p.Value)
.ToDictionary(g => g.Key, g => g.Select(p => p.Key).ToList());
If you wanted to do this without Linq, you could do the following:
foreach(KeyValuePair<string, int> entry in dict) {
if(!dict2.ContainsKey(entry.Value)) {
dict2[entry.Value] = new List<string>();
}
dict2[entry.Value].Add(entry.Key);
}
Assuming you have dict defined as Dictionary<string, int> dict and dict2 defined as Dictionary<int, List<string>> dict2
Here is a complete example for anyone that wants to "wrap their head around" how to do this, without LINQ.
using System;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
Dictionary<string,int> origDict = new Dictionary<string,int>{{"tttt",1},{"fttt",1},{"fftt",2}};
var vals = new int[origDict.Count];
origDict.Values.CopyTo(vals,0);
var keys = new string[origDict.Count];
origDict.Keys.CopyTo(keys,0);
Dictionary<int,List<string>> newDict = new Dictionary<int,List<string>>();
for(int i = 0; i < vals.Length; i++){
int val = vals[i];
if(newDict.ContainsKey(val)){
newDict[val].Add(keys[i]);
}else{
newDict[val] = new List<string>();
newDict[val].Add(keys[i]);
}
}
foreach(var key in newDict.Keys){
Console.WriteLine(key);
foreach(var val in newDict[key]){
Console.WriteLine(val);
}
}
}
}
Output:
1
tttt
fttt
2
fftt

C# Dictionary get all keys with same value

I have a Dictionary with Keys and Values. Is it possible to get all Key who has the same Value?
Example 1=456894, 2=548962, 3=548962, 4=654876 and then get the Key 2 and 3 because it has the same value.
Dictionary<int, int> doublechek = new Dictionary<int, int>();
One possible approach:
var result = doublechek
.GroupBy(z => z.Value)
.Where(z => z.Count() > 1)
.SelectMany(z => z)
.Select(z => z.Key)
.ToList();
GroupBy and Count will get only those with duplicates. SelectMany and Key will get the keys of those with duplicates (i.e. 2 and 3).
I would create a new "flipped" dictionary, containing all the unique values from doublecheck as keys, and for each key a list of values containing all the keys from doublecheck that have the referenced value.
Like so:
Dictionary<int, List<int>> flipped = new Dictionary<int, List<int>>();
foreach (var key in doublecheck.Keys)
{
if (!flipped.ContainsKey(doublecheck[key]))
flipped.Add(doublecheck[key], new List<int>());
flipped[doublecheck[key]].Add(key);
}
The dictionary flipped can be used to find all the keys of doublecheck that have the value you are searching.
Update
When using TryGetValue the code looks like this:
Dictionary<int, List<int>> flipped = new Dictionary<int, List<int>>();
foreach (var key in doublecheck.Keys)
{
if (!flipped.TryGetValue(doublecheck[key], out var valueList);
{
valueList = new List<int>();
flipped.Add(doublecheck[key], valueList);
}
valueList.Add(key);
}
return true;

How to use ToDictionary with <string, object> dictionary

I am trying to create a dictionary that can hold multiple values per key, and I have created an class called Pair that consists of two strings. I have defined idDictionary to contain a string as the key, and Pair as the value, but I am unsure how to write the ToDictionary statement as this concept is new to me, and I couldn't find any examples of this.
Dictionary<string, Pair<string, string>> idDictionary = new Dictionary<string, Pair<string, string>>();
I know with a regular generic Dictionary of I would simply use something like this:
idDictionary = resultData.Rows.Select(row => row.Split(','))
.ToDictionary(id => id[0], id => id[1]);
I am not sure how I would implement something similar for the object called Pair. Maybe I'm missing something really simple, but many thanks to those with answers.
EDIT to include full code block and more thorough explanation
The original code block is here (with a generic dictionary). The reason I am changing this is due to the fact that if there is more than 1 value per key, the application errors out due to duplicate keys.
private List<ImportItem<T>> ProcessReportResult(CSVTable resultData, ICollection<ImportItem<T>> data, Func<T, string> keyFilter)
{
WriteLog("{1}{0} records found.{1}", resultData.Rows.Length, Environment.NewLine);
//key = Order Number; value = Order ID
var idDictionary = resultData.Rows.Select((row => row.Split(','))).ToDictionary(id => id[0], id => id[1]);
idDictionary.ForEach(id => WriteLog("Input Id = {0} - Matching record Id = {1}", id.Key, id.Value));
var processList = data.Where(item => idDictionary.ContainsKey(keyFilter(item.DataItem))).ToList();
processList.ForEach(item => item.id = idDictionary[keyFilter(item.DataItem)]);
return processList;
}
A genral solution to get the one to many key value store can be achieved via grouping but that would require to have value as List of items. If I try to explain it via your given sample then the query to convert the rows to per key multi value store can be created by:
idDictionary =
resultData.Rows
.GroupBy(row => row.Id, row => row.Split(','))
.ToDictionary(g => g.Key, g => g.ToList());
Update:
Specific solution to your problem. Assuming that data would have structure something like:
List<Row> rows = new List<Row>{
new Row{
values = "1,A"
},
new Row{
values = "2,C,D,E"
},
new DataRow{
values = "3,E,X,CV,B"
},
};
You can use the Group here as well to get the Key, Value(List). Note, Here I have skipped the first value which is already captured as key at index 0.
var idDictionary =
rows.GroupBy(row => row.values.Split(',')[0],
row => row.values.Split(',').Skip(1))
.ToDictionary(g => g.Key, g => g.ToList());
This will give you the result like:
/* output -
|1, (A)|
|2, (C,D,E)|
|3, (E,X,CV,B)|
*/
Though you have to change the implementation for fetching the values via List.
But this solution will prevent the Program if there are more than one values found per key.
Not sure exactly what you need maybe this simple example will help?
idDictionary = resultData.Rows
.Select((row => row.Split(',')))
.ToDictionary<string, Pair<string, string>>
(id => id[0],id => new Pair(id[1],id[1]));
This version of ToDictionary takes two functions, one that returns the key and one that returns the value for each item in the enumeration.
You'll have to decide whether you want a tuple-based approach (or pair even) if you know how many items are in each row or if you need to consider that each row may have a different number of items.
// Setup sample data
var resultData = new
{
Rows = new string[] { "1,A,B,C", "2,A,B", "3,A,B,C,D" }
};
// If same length for each row, tuple would work easily
// Dictionary<string, Tuple<string, string>>
var tuples = resultData.Rows
.Select(r => r.Split(','))
.ToDictionary(
r => r[0],
r => Tuple.Create(r[1], r[2])
);
// If length is variable, then some type of collection could be better
// Dictionary<string, List<string>>
var lists = resultData.Rows
.Select(r => r.Split(','))
.ToDictionary(
r => r[0],
r => r.Skip(1).ToList() // Skip adding id element
);
Here is the output for the 1st item to compare each:
?lists["1"]
Count = 3
[0]: "A"
[1]: "B"
[2]: "C"
?tuples["1"]
{(A, B)}
Item1: "A"
Item2: "B"
The original code block is here (with a generic dictionary). The reason I am changing this is due to the fact that if there is more than 1 value per key, the application errors out due to duplicate keys.
Seems like what you are looking for is ToLookup
"Lookup<TKey, TElement>
represents a collection of keys each mapped to one or more values."
.
var idDictionary = resultData.Rows.Select((row => row.Split(',')))
.ToLookup(id => id[0], id => id[1]);
EDIT
A short sample:
var lines = new string[] { "a,b", "a,c", "d,e" };
var dict = lines.Select(line => line.Split(','))
.ToLookup(x => x[0], x => x[1]);
result:
Key: a Value: [b,c]
Key: e Value: [e]
Sample usage:
Console.WriteLine(string.Join(",", dict["a"]));

How to get the duplicate key in a ToDictionary cast? [duplicate]

This question already has answers here:
How do you get the duplicate key that ToDictionary() has failed on?
(4 answers)
Closed 7 years ago.
i have to cast a list to a dictionary in my app but im getting an error saying that "An item with the same key has already been added". But it is a list with more then 5k objects and i need to see wich objects are with the same key. Is there a way to do that?
In the message exception i cant get it, so i thought that i can do it using a foreach or something.
Any suggestions?
Thanks!
EDIT:
var targetDictionary = targetCollection.ToDictionary(k => k.Key);
This target collection is a generic IEnumerable, and the key i get from a thirdy party database so i do not have access to it. The solution is find the problematic object and tells the supplier about it.
You can use LINQ to catch the duplicates. You can then process them as you wish.
create a dictionary that doesn't contain the duplicates
var duplicates = myList.GroupBy(x => x.SomeKey).Where(x => x.Count() > 1);
var dictionaryWithoutDups = myList
.Except(duplicates.SelectMany(x => x))
.ToDictionary(x => x.SomeKey);
create a dictionary that contains only the first of each duplicate
var groups = myList.GroupBy(x => x.SomeKey);
var dictionaryWithFirsts = groups.Select(x => x.First()).ToDictionary(x => x.SomeKey);
var badGroups = collection.GroupBy(item => item.Key)
.Where(group => group.Count() > 1);
foreach (var badGroup in badGroups)
{
Console.WriteLine("The key {0} appears {1} times.", badGroup.Key, badGroup.Count());
forach (var badItem in badGroup)
{
Console.WriteLine(badItem);
}
}
var goodItems = collection.GroupBy(item => item.Key)
.Where(group => group.Count() == 1)
.SelectMany(group => group);
foreach (var goodItem in goodItems)
{
Console.WriteLine("The key {0} appears exactly once.", goodItem.Key);
}
var dictionary = goodItems.ToDictionary(item => item.Key);
If you want to keep the duplicates you can use .ToLookup instead. It creates a ILookup<TKey, TValue> which is basically a read-only Dictionary<TKey, IEnumerable<TValue>> where the duplicates are stored as the "value" in a collection.
If you are just looking for dups
HashSet<string> hs = new HashSet<string>();
foreach(string s in myList) if(!hs.Add(s)) Debug.WriteLine("dup: " + s);
Or you could change hs to Dictionary if you want to process
Dictionary<string, myclass> dl = new Dictionary<string, myclass>();
foreach(string s in myList)
{
if(dl.ContainsKey(s))
{
Debug.WriteLine("dup: " + s);
}
else
{
dl.Add(s, null);
}
}
I see you accepted a LINQ answer but LINQ is not going to out perform this.

Рow to get 2 to 4 of the object element

I had error from line
Dictionary<int, string> viewBusinesAndCountLeft = grptemp.Take(4).ToDictionary(x => x.Count, x => x.BusinessCategoryName);
error: "The element with the same key has already been added."
How to do this?
var grptemp = (from adsBusines in m_adsRepository.SaleBusinesAds
group adsBusines by adsBusines.BusinessCategory.Name
into grp
select new
{
BusinessCategoryName = grp.Key,
Count = grp.Select(x => x.BusinessCategory.ChildItems.Count()).Distinct().Count()
}).Take(8);
Dictionary<int, string> viewBusinesAndCountLeft = grptemp.Take(4).ToDictionary(x => x.Count, x => x.BusinessCategoryName);
Dictionary<int, string> viewBusinesAndCountRigth = grptemp.Skip(4).Take(4).ToDictionary(x => x.Count, x => x.BusinessCategoryName);
You are using the count as the key for the dictionary which means you will throw that exception whenever you happen to find two categories with the same count.
If I understand what you're trying to do correctly, you should have the 'business category' as the key to the dictionary and the count as the value.
E.g.
Dictionary<string, int> viewBusinesAndCountLeft = grptemp.Take(4).ToDictionary(x => x.BusinessCategoryName, x => x.Count);
Declaring a Dictionary<T,U> means a dictionary with key of type T (in this case, int) - and the key in a dictionary must be unique.
You probably want the key to be the string, and the value to be the count. Try switching your arguments:
Dictionary<string, int> viewBusinesAndCountLeft =
grptemp.Take(4).ToDictionary(x => x.BusinessCategoryName, x => x.Count);

Categories

Resources