How to get nested key value pair from dictionary with linq [duplicate] - c#

This question already has answers here:
C# String replace with dictionary
(8 answers)
Closed 3 years ago.
I have a lstSubs List<KeyValuePair<string, string>
which contain value
FNAME, "ABC"
LNAME ,"XYZ"
VAR001, "VAR002"
VAR002 , "ActualValueforVAR001"
VAR003, "VAR004"
VAR004 , "VAR005"
VAR005, "ActualValueforVAR003"
I have a String like envelop "Hello [FNAME] [LNAME] you have created a request for [VAR001] which got assigned to [VAR003]"
var regex = new Regex(#"\[(.*?)\]");
var matches = regex.Matches(envelop.ToString());
foreach (Match match in matches)
{
columnValue = linq to get the value from the list based on key;
envelop.Replace(match.Value, columnValue);
}
in this, The straight Key,Value pairs are easy to get via Linq but I am getting tough time to fetch the complex values which are nested in terms of connected Key, Value.
is there any way in LINQ or have to go with a loop.
Expected Output : Hello ABC XYZ you have created a request for ActualValueforVAR001 which got assigned to ActualValueforVAR003
Thanks,
PS. The code is not complete. it's a part of entire code edited with an intention to make it concise to issue
Edited: some of my text was not visible due to formatting.
They Dictionary values are nested as I am creating them based on some conditions in which they got configured

First, let's turn initial List<T> into a Dictionary<K, V>:
List<KeyValuePair<string, string>> list = new List<KeyValuePair<string, string>>() {
new KeyValuePair<string, string>("FNAME", "ABC"),
new KeyValuePair<string, string>("LNAME", "XYZ"),
new KeyValuePair<string, string>("VAR001", "VAR002"),
new KeyValuePair<string, string>("VAR002", "ActualValueforVAR001"),
new KeyValuePair<string, string>("VAR003", "VAR004"),
new KeyValuePair<string, string>("VAR004", "VAR005"),
new KeyValuePair<string, string>("VAR005", "ActualValueforVAR003"),
};
Dictionary<string, string> dict = list.ToDictionary(
pair => pair.Key,
pair => pair.Value,
StringComparer.OrdinalIgnoreCase); // Comment out if should be case sensitive
// Some values can be nested
while (true) {
bool nestedFound = false;
foreach (var pair in dict.ToList()) {
if (dict.TryGetValue(pair.Value, out var newValue)) {
dict[pair.Key] = newValue;
nestedFound = true;
}
}
if (!nestedFound)
break;
}
Then for a given envelop
string envelop =
#"Hello [FNAME] [LNAME] you have created a request for [VAR001] which got assigned to [VAR003]";
you can put a simple Regex.Replace:
string result = Regex
.Replace(envelop,
#"\[[A-Za-z0-9]+\]",
m => dict.TryGetValue(m.Value.Trim('[', ']'), out var value) ? value : "???");
Console.Write(result);
Outcome:
Hello ABC XYZ you have created a request for ActualValueforVAR001 which got assigned to ActualValueforVAR003

Related

'An item with the same key has already been added.' [duplicate]

I keep getting an error with the following code:
Dictionary<string, string> rct3Features = new Dictionary<string, string>();
Dictionary<string, string> rct4Features = new Dictionary<string, string>();
foreach (string line in rct3Lines)
{
string[] items = line.Split(new String[] { " " }, 2, StringSplitOptions.None);
rct3Features.Add(items[0], items[1]);
////To print out the dictionary (to see if it works)
//foreach (KeyValuePair<string, string> item in rct3Features)
//{
// Console.WriteLine(item.Key + " " + item.Value);
//}
}
The error throws an ArgumentException saying,
"An item with the same key has already been added."
I am unsure after several Google searches how to fix this.
Later in the code I need to access the dictionary for a compare function:
Compare4To3(rct4Features, rct3Features);
public static void Compare4To3(Dictionary<string, string> dictionaryOne, Dictionary<string, string> dictionaryTwo)
{
//foreach (string item in dictionaryOne)
//{
//To print out the dictionary (to see if it works)
foreach (KeyValuePair<string, string> item in dictionaryOne)
{
Console.WriteLine(item.Key + " " + item.Value);
}
//if (dictionaryTwo.ContainsKey(dictionaryOne.Keys)
//{
// Console.Write("True");
//}
//else
//{
// Console.Write("False");
//}
//}
}
This function isn't completed, but I am trying to resolve this exception. What are the ways I can fix this exception error, and keep access to the dictionary for use with this function? Thank you
This error is fairly self-explanatory. Dictionary keys are unique and you cannot have more than one of the same key. To fix this, you should modify your code like so:
Dictionary<string, string> rct3Features = new Dictionary<string, string>();
Dictionary<string, string> rct4Features = new Dictionary<string, string>();
foreach (string line in rct3Lines)
{
string[] items = line.Split(new String[] { " " }, 2, StringSplitOptions.None);
if (!rct3Features.ContainsKey(items[0]))
{
rct3Features.Add(items[0], items[1]);
}
////To print out the dictionary (to see if it works)
//foreach (KeyValuePair<string, string> item in rct3Features)
//{
// Console.WriteLine(item.Key + " " + item.Value);
//}
}
This simple if statement ensures that you are only attempting to add a new entry to the Dictionary when the Key (items[0]) is not already present.
If you want "insert or replace" semantics, use this syntax:
A[key] = value; // <-- insert or replace semantics
It's more efficient and readable than calls involving "ContainsKey()" or "Remove()" prior to "Add()".
So in your case:
rct3Features[items[0]] = items[1];
As others have said, you are adding the same key more than once. If this is a NOT a valid scenario, then check Jdinklage Morgoone's answer (which only saves the first value found for a key), or, consider this workaround (which only saves the last value found for a key):
// This will always overwrite the existing value if one is already stored for this key
rct3Features[items[0]] = items[1];
Otherwise, if it is valid to have multiple values for a single key, then you should consider storing your values in a List<string> for each string key.
For example:
var rct3Features = new Dictionary<string, List<string>>();
var rct4Features = new Dictionary<string, List<string>>();
foreach (string line in rct3Lines)
{
string[] items = line.Split(new String[] { " " }, 2, StringSplitOptions.None);
if (!rct3Features.ContainsKey(items[0]))
{
// No items for this key have been added, so create a new list
// for the value with item[1] as the only item in the list
rct3Features.Add(items[0], new List<string> { items[1] });
}
else
{
// This key already exists, so add item[1] to the existing list value
rct3Features[items[0]].Add(items[1]);
}
}
// To display your keys and values (testing)
foreach (KeyValuePair<string, List<string>> item in rct3Features)
{
Console.WriteLine("The Key: {0} has values:", item.Key);
foreach (string value in item.Value)
{
Console.WriteLine(" - {0}", value);
}
}
To illustrate the problem you are having, let's look at some code...
Dictionary<string, string> test = new Dictionary<string, string>();
test.Add("Key1", "Value1"); // Works fine
test.Add("Key2", "Value2"); // Works fine
test.Add("Key1", "Value3"); // Fails because of duplicate key
The reason that a dictionary has a key/value pair is a feature so you can do this...
var myString = test["Key2"]; // myString is now Value2.
If Dictionary had 2 Key2's, it wouldn't know which one to return, so it limits you to a unique key.
That Exception is thrown if there is already a key in the dictionary when you try to add the new one.
There must be more than one line in rct3Lines with the same first word. You can't have 2 entries in the same dictionary with the same key.
You need to decide what you want to happen if the key already exists - if you want to just update the value where the key exists you can simply
rct3Features[items[0]]=items[1]
but, if not you may want to test if the key already exists with:
if(rect3Features.ContainsKey(items[0]))
{
//Do something
}
else
{
//Do something else
}
I suggest .NET's TryAdd:
https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.dictionary-2.tryadd?view=net-7.0
I suggest a extension method for environments where .NET's TryAdd is not available:
public static class DictionaryUtils
{
/// <summary>
/// Prevents exception "Item with Same Key has already been added".
/// </summary>
public static void TryAdd<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey key, TValue value)
{
if (!dictionary.ContainsKey(key))
{
dictionary.Add(key, value);
}
}
}
Clear the dictionary before adding any items to it. I don't know how a dictionary of one object affects another's during assignment but I got the error after creating another object with the same key,value pairs.
NB:
If you are going to add items in a loop just make sure you clear the dictionary before entering the loop.

Split string which contains item and value into Dictionary in C#

What is the best way to split the name/description of the value into a dictionary <string, string>?
string test = postedByUser='Jason, Bourne' postedById='48775' Text='Some text in here' postedDate='2020-04-21'
so ideally i want
dictionary key = postedByUser, value = Jason, Bourne
dictionary key = postedById, value = 48775
etc
code added so far
string test = #"postedByUser=Jason, Bourne' postedById='48775' Text='Some text in here' postedDate='2020-04-21'";
Dictionary<string, string> dict = new Dictionary<string, string>();
List<string> lst = test.Split('=').ToList();
foreach(string item in lst)
{
// cant figure out how edit the orginal string to remove the item that has
//been split by the '='
}
Well you can the following code will solve your problem but I suggest you to add more exception handling to make the code robust.
string test = #"postedByUser='Jason, Bourne' postedById='48775' Text='Some text in here' postedDate='2020-04-21'";
Dictionary<string, string> dict = new Dictionary<string, string>();
List<string> keyvalues = test.Split("' ").ToList();
foreach(var keyvalue in keyvalues)
{
var splitKeyValue = keyvalue.Split('=');
dict.Add(splitKeyValue[0], splitKeyValue[1]);
}
EDIT:
For .NET Framework 4.6,
List<string> keyvalues = test.Split(new string[] { "' " }, StringSplitOptions.None).ToList();
Try this one (quite ugly but should work):
var dict = test.Split("' ").Select(t=>string.Concat(t,"'").Split("=")).ToDictionary(t=>t[0],t=>t[1]);
Use String.Split(..) to separate out your string for processing.
String.Split documentation
First split the string based on a ' ' space character as a separator e.g.
var splitStrings = test.Split(' ', StringSplitOptions.None);
then run through each string in strings and separate using '='. This will give you a list of 2 strings where the first is your key and the second is your value.
Regular expressions can be used to solve this problem:
string text = #"postedByUser='Jason, Bourne' postedById='48775' Text='Some text in here' postedDate='2020-04-21'";
Dictionary<string, string> result = new Dictionary<string, string>();
foreach (Match m in Regex.Matches(text, #"(\w+)=\'(.+?)\'"))
{
result.Add(m.Groups[1].Value, m.Groups[2].Value);
}
Here is complete sample.

Linq query for building a dictionary from a reg file

I'm building a simple dictionary from a reg file (export from Windows Regedit). The .reg file contains a key in square brackets, followed by zero or more lines of text, followed by a blank line. This code will create the dictionary that I need:
var a = File.ReadLines("test.reg");
var dict = new Dictionary<String, List<String>>();
foreach (var key in a) {
if (key.StartsWith("[HKEY")) {
var iter = a.GetEnumerator();
var value = new List<String>();
do {
iter.MoveNext();
value.Add(iter.Current);
} while (String.IsNullOrWhiteSpace(iter.Current) == false);
dict.Add(key, value);
}
}
I feel like there is a cleaner (prettier?) way to do this in a single Linq statement (using a group by), but it's unclear to me how to implement the iteration of the value items into a list. I suspect I could do the same GetEnumerator in a let statement but it seems like there should be a way to implement this without resorting to an explicit iterator.
Sample data:
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\.msu]
#="Microsoft.System.Update.1"
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\.MTS]
#="WMP11.AssocFile.M2TS"
"Content Type"="video/vnd.dlna.mpeg-tts"
"PerceivedType"="video"
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\.MTS\OpenWithProgIds]
"WMP11.AssocFile.M2TS"=hex(0):
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\.MTS\ShellEx]
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\.MTS\ShellEx\{BB2E617C-0920-11D1-9A0B-00C04FC2D6C1}]
#="{9DBD2C50-62AD-11D0-B806-00C04FD706EC}"
Update
I'm sorry I need to be more specific. The files am looking at around ~300MB so I took the approach I did to keep the memory footprint down. I'd prefer an approach that doesn't require pulling the entire file into memory.
You can always use Regex:
var dict = new Dictionary<String, List<String>>();
var a = File.ReadAllText(#"test.reg");
var results = Regex.Matches(a, "(\\[[^\\]]+\\])([^\\[]+)\r\n\r\n", RegexOptions.Singleline);
foreach (Match item in results)
{
dict.Add(
item.Groups[1].Value,
item.Groups[2].Value.Split(new[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries).ToList()
);
}
I whipped this out real quick. You might be able to improve the regex pattern.
Instead of using GetEnumerator you can take advantage of TakeWhile and Split methods to break your list into smaller list (each sublist represents one key and its values)
var registryLines = File.ReadLines("test.reg");
Dictionary<string, List<string>> resultKeys = new Dictionary<string, List<string>>();
while (registryLines.Count() > 0)
{
// Take the key and values into a single list
var keyValues = registryLines.TakeWhile(x => !String.IsNullOrWhiteSpace(x)).ToList();
// Adds a new entry to the dictionary using the first value as key and the rest of the list as value
if (keyValues != null && keyValues.Count > 0)
resultKeys.Add(keyValues[0], keyValues.Skip(1).ToList());
// Jumps to the next registry (+1 to skip the blank line)
registryLines = registryLines.Skip(keyValues.Count + 1);
}
EDIT based on your update
Update I'm sorry I need to be more specific. The files am looking at
around ~300MB so I took the approach I did to keep the memory
footprint down. I'd prefer an approach that doesn't require pulling
the entire file into memory.
Well, if you can't read the whole file into memory, it makes no sense to me asking for a LINQ solution. Here is a sample of how you can do it reading line by line (still no need for GetEnumerator)
Dictionary<string, List<string>> resultKeys = new Dictionary<string, List<string>>();
using (StreamReader reader = File.OpenText("test.reg"))
{
List<string> keyAndValues = new List<string>();
while (!reader.EndOfStream)
{
string line = reader.ReadLine();
// Adds key and values to a list until it finds a blank line
if (!string.IsNullOrWhiteSpace(line))
keyAndValues.Add(line);
else
{
// Adds a new entry to the dictionary using the first value as key and the rest of the list as value
if (keyAndValues != null && keyAndValues.Count > 0)
resultKeys.Add(keyAndValues[0], keyAndValues.Skip(1).ToList());
// Starts a new Key collection
keyAndValues = new List<string>();
}
}
}
I think you can use a code like this - if you can use memory -:
var lines = File.ReadAllText(fileName);
var result =
Regex.Matches(lines, #"\[(?<key>HKEY[^]]+)\]\s+(?<value>[^[]+)")
.OfType<Match>()
.ToDictionary(k => k.Groups["key"], v => v.Groups["value"].ToString().Trim('\n', '\r', ' '));
C# Demo
This will take 24.173 seconds for a file with more than 4 million lines - Size:~550MB - by using 1.2 GB memory.
Edit :
The best way is using File.ReadAllLines as it is lazy:
var lines = File.ReadAllLines(fileName);
var keyRegex = new Regex(#"\[(?<key>HKEY[^]]+)\]");
var currentKey = string.Empty;
var currentValue = string.Empty;
var result = new Dictionary<string, string>();
foreach (var line in lines)
{
var match = keyRegex.Match(line);
if (match.Length > 0)
{
if (!string.IsNullOrEmpty(currentKey))
{
result.Add(currentKey, currentValue);
currentValue = string.Empty;
}
currentKey = match.Groups["key"].ToString();
}
else
{
currentValue += line;
}
}
This will take 17093 milliseconds for a file with 795180 lines.

How to efficiently parse a maximum of two depths with Json.NET?

Given Json input like the following:
{
"Group A": {
"Prop A": 42,
"Prop B": true,
"Prop C": [ "Hello", "World!" ]
},
"Group B": {
"Prop A": 72
}
}
I would like to populate the following data structure:
Dictionary<string, Dictionary<string, string>> Groups;
Such that the following statement would be true (differences in whitespace are not important):
Groups["Group A"]["Prop C"] == "[\"Hello\",\"World!\"]"
Inefficient Solution:
The following seems to solve this problem, but it is quite inefficient since it incurs unnecessary intermediary serialization:
Groups = new Dictionary<string, Dictionary<string, string>>();
foreach (var jsonGroup in JObject.Parse(jsonText)) {
var group = new Dictionary<string, string>();
Groups[jsonGroup.Key] = group;
foreach (var jsonProperty in jsonGroup.Value.Children<JProperty>())
group[jsonProperty.Name] = JsonConvert.SerializeObject(jsonProperty.Value);
}
Sadly JProperty.Value.ToString() seems to return odd values, for instance "False" instead of "false".
It seems that the intended behaviour of JValue.ToString() is to return the enclosed value as a string rather than encode to JSON. Instead JValue.ToString(IFormatProvider) should be used to produce a valid JSON encoded string.
https://github.com/JamesNK/Newtonsoft.Json/issues/266
However, in my particular case it seems to make better sense to retain the Json.NET representation since this avoids reparsing the same JSON content multiple times:
Groups = new Dictionary<string, Dictionary<string, JToken>>();
foreach (var jsonGroup in JObject.Parse(jsonText)) {
var group = new Dictionary<string, JToken>();
Groups[jsonGroup.Key] = group;
foreach (var jsonProperty in jsonGroup.Value.Children<JProperty>())
group[jsonProperty.Name] = jsonProperty.Value;
}
And then, instead of parsing these sub-structures with JObject.Parse I can use JToken.ToObject<T> to convert them into the desired representation:
bool myValue = Groups["Group A"]["Prop B"].ToObject<bool>();

Best way to read rapidshare API response?

I've started working with the rapidshare.com API. I'm just wondering what is the best way to read the reply from and API call.
To be honest I think the API is all over the place. Some replies are comma delimited which is fine. I'm having with problems with the account info response. This is not comma delimited and the fields may not always be in the same order.
Here is a sample response:
accountid=123456 type=prem servertime=1260968445 addtime=1230841165 validuntil=1262377165 username=DOWNLOADER directstart=1 protectfiles=0 rsantihack=0 plustrafficmode=0 mirrors= jsconfig=1 email=take#hike.com lots=0 fpoints=12071 ppoints=10 curfiles=150 curspace=800426795 bodkb=5000000 premkbleft=23394289 ppointrate=93
I'm thinking that regex is the way to go here. Here is my expression that seems to cath all responses that contain values:
(accountid|type|servertime|addtime|validuntil|username|directstart|protectfiles|rsantihack|plustrafficmode|mirrors|jsconfig|email|lots|fpoints|ppoints|curfiles|curspace|bodkb|premkbleft|ppointrate|refstring|cookie)\=[\w._#]+
If the order of data is to be considered random then how do I determine which value is which?
I'm just curious as to how everybody else is working with this.
Thanks,
Conor
i assume c#.
string[] s = #"accountid=123456 type=prem servertime=1260968445 addtime=1230841165 validuntil=1262377165 username=DOWNLOADER directstart=1 protectfiles=0 rsantihack=0 plustrafficmode=0 mirrors= jsconfig=1 email=take#hike.com lots=0 fpoints=12071 ppoints=10 curfiles=150 curspace=800426795 bodkb=5000000 premkbleft=23394289 ppointrate=93".Split(" ");
var params = new Dictionary<string, string>();
foreach(var l in s)
{
var tmp = l.Split("=");
params[tmp[0]] = params[tmp[1]];
}
(it may contain bugs.. but the idea is obvious?)
You probably want to split this up into a Dictionary object of some kind, so that you can access the value by a key.
Here's an example of a C# console application that works with .NET 3.5:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
namespace SO
{
class Program
{
static void Main(string[] args)
{
string input = #"accountid=123456 type=prem servertime=1260968445";
string pattern = #"(?<Key>[^ =]+)(?:\=)(?<Value>[^ ]+)(?=\ ?)";
Dictionary<string, string> fields =
(from Match m in Regex.Matches(input, pattern)
select new
{
key = m.Groups["Key"].Value,
value = m.Groups["Value"].Value
}
).ToDictionary(p => p.key, p => p.value);
//iterate over all fields
foreach (KeyValuePair<string, string> field in fields)
{
Console.WriteLine(
string.Format("{0} : {1}", field.Key, field.Value)
);
}
//get value from a key
Console.WriteLine(
string.Format("{0} : {1}", "type", fields["type"])
);
}
}
}
Link to another example in PHP:
How to use rapidshare API to get Account Details ?? PHP question
Here is what I have done.
It's basically just a working version the code from Yossarian.
// Command to send to API
String command = "sub=getaccountdetails_v1&type=prem&login="+Globals.username+"&password="+Globals.password;
// This will return the response from rapidshare API request.
// It just performs # webrequest and returs the raw text/html. It's only a few lines. Sorry I haven't included it here.
String input = executeRequest(command);
input = input.Trim();
string[] s = input.Split('\n');
Dictionary<string,string> terms = new Dictionary<string, string>();
foreach(var l in s)
{
String[] tmp = l.Split('=');
terms.Add(tmp[0], tmp[1]);
}
foreach (KeyValuePair<String, String> term in terms)
{
txtOutput.Text += term.Key + " :: " + term.Value+"\n";
}
Thanks for your help guys.

Categories

Resources