C # Linq.ToDictionary () Key already exists

Final Edit: I was able to find the duplicate field in the ini file. Thanks for all your help!

I use regex to parse an ini file and LINQ to store it in a dictionary:

Data examples:
[WindowSettings]
Window X Pos = '0'
Window Y Pos = '0'
Window Maximized = 'false'
Window Name = 'Jabberwocky'

[Entrance]
Directory = 'C: \ Rosetta Stone \ Logs'

EDIT: here is the file actually causing the problem: http://pastebin.com/mQSrkrcP

EDIT2: I narrowed it down to the point of invoking the last section in the file: [list_first_nonprintable]

For some reason, one of the files that I parse with this throws this exception:

System.ArgumentException: An item with the same key has already been added.

Do I have any way to find out which key is causing the problem (so that I can fix the file), or just skip the key that causes this and continue parsing?

Here is the code:

try { // Read content of ini file. string data = System.IO.File.ReadAllText(project); // Create regular expression to parse ini file. string pattern = @"^((?:\[)(?<Section>[^\]]*)(?:\])(?:[\r\n]{0,}|\Z))((?!\[)(?<Key>[^=]*?)(?:=)(?<Value>[^\r\n]*)(?:[\r\n]{0,4}))*"; //pattern = @" //^ # Beginning of the line //((?:\[) # Section Start // (?<Section>[^\]]*) # Actual Section text into Section Group // (?:\]) # Section End then EOL/EOB // (?:[\r\n]{0,}|\Z)) # Match but don't capture the CRLF or EOB // ( # Begin capture groups (Key Value Pairs) // (?!\[) # Stop capture groups if a [ is found; new section // (?<Key>[^=]*?) # Any text before the =, matched few as possible // (?:=) # Get the = now // (?<Value>[^\r\n]*) # Get everything that is not an Line Changes // (?:[\r\n]{0,4}) # MBDC \r\n // )* # End Capture groups"; // Parse each file into a Dictionary. Dictionary<string, Dictionary<string, string>> iniFile = (from Match m in Regex.Matches(data, pattern, RegexOptions.IgnorePatternWhitespace | RegexOptions.Multiline) select new { Section = m.Groups["Section"].Value, kvps = (from cpKey in m.Groups["Key"].Captures.Cast<Capture>().Select((a, i) => new { a.Value, i }) join cpValue in m.Groups["Value"].Captures.Cast<Capture>().Select((b, i) => new { b.Value, i }) on cpKey.i equals cpValue.i select new KeyValuePair<string, string>(cpKey.Value, cpValue.Value)).ToDictionary(kvp => kvp.Key, kvp => kvp.Value) }).ToDictionary(itm => itm.Section, itm => itm.kvps); return iniFile; } catch (ArgumentException ex) { System.Diagnostics.Debug.Write(ex.ToString()); return new Dictionary<string, Dictionary<string, string>>(); } 

Thanks in advance.

+4
source share
3 answers

This means that when you convert to a dictionary -

 .ToDictionary(itm => itm.Section, itm => itm.kvps); 

- there are several keys (itm.Section). You can use ToLookup , which is similar to a dictionary, but allows you to use multiple keys.

Edit

There are several ways to call ToLookup. The easiest is to specify a key selector:

 var lookup = // ... .ToLookup(itm => itm.Section); 

This should provide a search where the key is of type Group . Getting the search value should return IEnumerable, where T is an anonymous type:

 Group g = null; // TODO get group var lookupvalues = lookup[g]; 

If the .NET compiler does not like this (sometimes it seems that it has problems figuring out what the various types should be), you can also specify an element selector, for example:

 ILookup<string, KeyValuePair<string,string>> lookup = // ... .ToLookup( itm => itm.Section.Value, // key selector itm => itm.kvps // element selector ); 
+8
source

You can write your own ToDictionary method that won't break with duplicate keys easily enough.

 public static Dictionary<K,V> ToDictionary<TSource, K, V>( this IEnumerable<TSource> source, Func<TSource, K> keySelector, Funct<TSource, V> valueSelector) { //TODO validate inputs for null arguments. Dictionary<K,V> output = new Dictionary<K,V>(); foreach(TSource item in source) { //overwrites previous values output[keySelector(item)] = valueSelector(item); //ignores future duplicates, comment above and //uncomment below to change behavior //K key = keySelector(item); //if(!output.ContainsKey(key)) //{ //output.Add(key, valueSelector(item)); //} } return output; } 

I assume that you could figure out how to implement additional overloads (without a selector value).

+3
source

You can use Tuple to transfer multiple keys. Check out the sample code below:

 .ToDictionary(k => new Tuple<string,string>(k.key1,k.key2), v => v.value) 
0
source

Source: https://habr.com/ru/post/1412855/


All Articles