Get Key values from a file by using linq query - c#

There are multiple files in a folder that the code should read one by one. I have to extract some key value from the file to perform some business logic.
the file look like this,
x-sender:
x-receiver:
Received:
X-AuditID:
Received:
Received:
From:
To:
Subject:
Thread-Topic:
Thread-Index:
Date:
Message-ID:
Accept-Language:
Content-Language:
X-MS-Has-Attach:
There are multiple keys that can increase and decrease as per file. The order of the key could also be changed. Every key has some value.
Code:
private void BtnStart_Click(object sender, EventArgs e)
{
// searches current directory
foreach (string file in Directory.EnumerateFiles(NetWorkPath, "*.eml"))
{
var dic = File.ReadAllLines(file)
.Select(l => l.Split(new[] { ':' }))
.ToDictionary(s => s[0].Trim(), s => s[1].Trim());
string myUser = dic["From"];
}
}
I was trying to read the file and convert that into dictionary , So that i can access by using Keys. But it is giving me an error "An item with the same key has already been added.".
Any help??

Instead of ToDictionary, You can use ToLookup
......same code....
.Where(s => s.Length>1)
.ToLookup(s => s[0].Trim(), s => s[1].Trim());
Then you can check as
string myUser = dic["From"].FirstOrDefault();

That's because Receieved is in there multiple times and Dictionary doesn't allow duplicate entries for it's key value.
You could use a Tuple<string, string>, that would allow duplicates.
If you don't want to return it though, you could just use an anonymous type:
foreach (string file in Directory.EnumerateFiles(NetWorkPath, "*.eml"))
{
var items = myList
.Select(l => l.Split(new [] {':' }, StringSplitOptions.RemoveEmptyEntries))
.Where(l => l != null && l.Count() == 2)
.Select(l => new
{
Key = l[0],
Value = l[1],
})
.ToList();
string myUser = items.First(i => i.Key == "From").Value;
}

You have 2 elements with a same name - Received:

It is means that you have already added in dictionary the same key twice,
for content of your file it is Received:

Related

Efficient way to create a new list based of the differences in values in 2 dictionaries?

I currently have 2 strings that are formatted as an XML that are later converted into dictionaries for comparison.
So, I have a 2 Dictionary<string, object>, dict1 and dict2, that I need to compare. I need to:
Add the key to a list of strings if the values of these two dictionaries do not match
Add the key of dict2 to the list if dict1 does not contain this key
Currently, I have a simple foreach loop
foreach (string propName in dict2.Keys)
{
string oldDictValue;
string newDicValue = dict1[propName].ToString();
if (dict1.ContainsKey(propName))
{
oldDictValue = dict2[propName].ToString();
if (oldDictValue != newDicValue)
list.Add(propName);
}
else
{
list.Add(propName);
}
}
I would like to a faster solution to this problem if possible?
I don't claim that this is any faster, but it should be on par and it's less code:
List<string> list =
dict2
.Keys
.Where(k => !(dict1.ContainsKey(k) && dict1[k].Equals(dict2[k])))
.ToList();
I did do some testing with this:
List<string> list =
dict2
.Keys
.AsParallel()
.Where(k => !(dict1.ContainsKey(k) && dict1[k].Equals(dict2[k])))
.ToList();
That produced a significantly faster run.
Here's how I produced my test data:
var dict1 = Enumerable.Range(0, 10000000).Select(x => Random.Shared.Next(2000000)).Distinct().ToDictionary(x => x.ToString(), x => (object)Random.Shared.Next(20));
var dict2 = Enumerable.Range(0, 10000000).Select(x => Random.Shared.Next(2000000)).Distinct().ToDictionary(x => x.ToString(), x => (object)Random.Shared.Next(20));
You could make it faster by avoiding to get separately the dict1[propName] and the dict2[propName]. You could get the value along with the key, either by enumerating directly the KeyValuePairs stored in the dictionary, or by calling the TryGetValue method:
foreach (var (key, value2) in dict2)
{
if (!dict1.TryGetValue(key, out var value1)
|| value1.ToString() != value2.ToString())
{
list.Add(key);
}
}

Using C# Lambda to split string and search value

I have a string with the following value:
0:12211,90:33221,23:09011
In each pair, the first value (before the : (colon)) is an employee id, the second value after is a payroll id.
So If I want to get the payroll id for employee id 23 right now I have to do:
var arrayValues=mystring.split(',');
and then for each arrayValues do the same:
var employeeData = arrayValue.split(':');
That way I will get the key and the value.
Is there a way to get the Payroll ID by a given employee id using lambda?
If the employeeId is not in the string then by default it should return the payrollid for employeeid 0 zero.
Using a Linq pipeline and anonymous objects:
"0:12211,90:33221,23:09011"
.Split(',')
.Select(x => x.Split(':'))
.Select(x => new { employeeId = x[0], payrollId = x[1] })
.Where(x=> x.employeeId == "23")
Results in this:
{
employeeId = "23",
payrollId = "09011"
}
These three lines represent your data processing and projection logic:
.Split(',')
.Select(x => x.Split(':'))
.Select(x => new { employeeId = x[0], payrollId = x[1] })
Then you can add any filtering logic with Where after this the second Select
You can try something like that
"0:12211,90:33221,23:09011"
.Split(new char[] { ',' })
.Select(c => {
var pair = c.Split(new char[] { ':' });
return new KeyValuePair<string, string>(pair[0], pair[1]);
})
.ToList();
You have to be aware of validations of data
If I were you, I'd use a dictionary. Especially if you're going to do more than one lookup.
Dictionary<int, int> employeeIDToPayrollID = "0:12211,90:33221,23:09011"
.Split(',') //Split on comma into ["0:12211", "90:33221", "23:09011"]
.Select(x => x.Split(':')) //Split each string on colon into [ ["0", "12211"]... ]
.ToDictionary(int.Parse(x => x[0]), int.Parse(x => x[1]))
and now, you just have to write employeeIDtoPayrollID[0] to get 12211 back. Notice that int.Parse will throw an exception if your IDs aren't integers. You can remove those calls if you want to have a Dictionary<string, string>.
You can use string.Split along with string.Substring.
var result =
str.Split(',')
.Where(s => s.Substring(0,s.IndexOf(":",StringComparison.Ordinal)) == "23")
.Select(s => s.Substring(s.IndexOf(":",StringComparison.Ordinal) + 1))
.FirstOrDefault();
if this logic will be used more than once then I'd put it to a method:
public string GetPayrollIdByEmployeeId(string source, string employeeId){
return source.Split(',')
.Where(s => s.Substring(0, s.IndexOf(":", StringComparison.Ordinal)) == employeeId)
.Select(s => s.Substring(s.IndexOf(":", StringComparison.Ordinal) + 1))
.FirstOrDefault();
}
Assuming you have more than three pairs in the string (how long is that string, anyway?) you can convert it to a Dictionary and use that going forward.
First, split on the comma and then on the colon and put in a Dictionary:
var empInfo = src.Split(',').Select(p => p.Split(':')).ToDictionary(pa => pa[0], pa => pa[1]);
Now, you can write a function to lookup payroll IDs from employee IDs:
string LookupPayrollID(Dictionary<string, string> empInfo, string empID) => empInfo.TryGetValue(empID, out var prID) ? prID : empInfo["0"];
And you can call it to get the answer:
var emp23prid = LookupPayrollID(empInfo, "23");
var emp32prid = LookupPayrollID(empInfo, "32");
If you just have three employees in the string, creating a Dictionary is probably overkill and a simpler answer may be appropriate, such as searching the string.

C# LINQ Query / Projection from FormCollection

I have a dynamic form which I cannot use MVC's binding with. When I post this FormCollection to my controller, simplified, I have the following data in the form collection:
public ActionResult Foo(FormCollection coll)
...
coll["Data0Key"] contains "category"
coll["Data0Value"] contains "123"
coll["Data1Key"] contains "location"
coll["Data1Value"] contains "21"
coll["Data7Key"] contains "area"
coll["Data7Value"] contains "test"
coll["SomethingElse"] contains "irrelevent"
.. I have an unknown number of these and would like to create key-value pairs from the seperate key and value objects in the collection
I have been attempting along the lines of;
var settings = coll.AllKeys
.Where(k => k.StartsWith("Data"))
.ToDictionary(k => k, k => coll[k]);
which gives me a dictionary of:
Data0Key, category
Data0Value, 123
Data1Key, location
Data1Value, 21
Data7Key, area
Data7Value, test
What I would really like to have is a collection of key value pairs structured like;
category, 123
location, 21
area, test
Is what I am trying to achieve possible at all, or do I need to find a different approach?
I think you want to only iterate over the "DataxKey" parts, then look up the values. Something like:
.Where(k => k.StartsWith("Data") && k.EndsWith("Key"))
.ToDictionary(k => coll[k], k => coll[k.Replace("Key", "Value")]);
This assumes that every "Data0Key" also has a "Data0Value" matching pair, otherwise it's going to dereference a key that doesn't exist from coll.
This can be done with a relatively straightforward LINQ query:
var data = new Dictionary<string,object> {
["Data0Key"] = "category"
, ["Data0Value"] = "123"
, ["Data1Key"] = "location"
, ["Data1Value"] = "21"
, ["Data7Key"] = "area"
, ["Data7Value"] = "test"
, ["SomethingElse"] = "irrelevent"
};
var kvp = data
.Where(p => p.Key.StartsWith("Data") && (p.Key.EndsWith("Key") || p.Key.EndsWith("Value")))
.Select(p => new {
Key = p.Key.Substring(0, p.Key.Length - (p.Key.EndsWith("Key") ? 3 : 5))
, IsKey = p.Key.EndsWith("Key")
, p.Value
})
.GroupBy(p => p.Key)
.Where(g => g.Count(p => p.IsKey) == 1 && g.Count(p => !p.IsKey) == 1)
.ToDictionary(g => (string)g.Single(p => p.IsKey).Value, g => g.Single(p => !p.IsKey).Value);
foreach (var p in kvp) {
Console.WriteLine("{0} - {1}", p.Key, p.Value);
}
Here is a line-by-line explanation of what is done:
First, irrelevant items are filtered out by ensuring that only "Data" prefixes are kept, and that the suffixes are "Key" or "Value"
Next, group key is extracted by removing "Key" or "Value" suffix; the Value is added to the list, along with IsKey flag indicating if an item was a key or a value
Items are grouped by the group key ("Data0", "Data7", etc.)
Each group is checked to contain exactly one key and one value; incomplete groups are discarded
Finally, groups are converted to a dictionary of key-value pairs.

Dictionary change of order

I have a dictionary.
Dictionary<int, string> inboxMessages = new Dictionary<int, string>();
This dictionary contains messages with their own unique ID (the newer the message, the higher the ID). I put the messages in a picker (xamarin) but it shows the oldest messages first. How can I change this?
The Picker:
inboxPicker = new Picker
{
WidthRequest = 320,
};
foreach (string inboxMessage in inboxMessages.Values)
{
inboxPicker.Items.Add(inboxMessage);
}
How i get my messages:
private async Task getMessages()
{
await Task.Run(async () => {
MailModel[] mails = await api.GetMails(App.userInfo.user_id);
foreach (MailModel mail in mails)
{
inboxMessages.Add(mail.message_id,mail.sender_user_id +" "+ mail.subject +" "+ mail.time_send);
}
});
}
The Values property of a dictionary is not ordered. Quote from the documentation:
The order of the values in the Dictionary<TKey, TValue>.ValueCollection is unspecified [...]
If you want to retrieve the values in some specific order, you need to sort it yourself. For example:
var sorted = inboxMessages.OrderByDescending(kv => kv.Key).Select(kv => kv.Value);
foreach (string inboxMessage in sorted)
{
inboxPicker.Items.Add(inboxMessage);
}
This retrieves the KeyValuePairs from the dictionary, sorts them descending on their int key and then returns an enumeration of the values.
You should sort the dictionary entries while you still have access to their keys:
foreach (string inboxMessage in inboxMessages
.OrderByDescending(m => m.Key)
.Select(m => m.Value)
{
inboxPicker.Items.Add(inboxMessage);
}

Converting Collection of Strings to Dictionary

This is probably a simple question, but the answer is eluding me.
I have a collection of strings that I'm trying to convert to a dictionary.
Each string in the collection is a comma-separated list of values that I obtained from a regex match. I would like the key for each entry in the dictionary to be the fourth element in the comma-separated list, and the corresponding value to be the second element in the comma-separated list.
When I attempt a direct call to ToDictionary, I end up in some kind of loop that appears to kick me of the BackgroundWorker thread I'm in:
var MoveFromItems = matches.Cast<Match>()
.SelectMany(m => m.Groups["args"].Captures
.Cast<Capture>().Select(c => c.Value));
var dictionary1 = MoveFromItems.ToDictionary(s => s.Split(',')[3],
s => s.Split(',')[1]);
When I create the dictionary manually, everything works fine:
var MoveFroms = new Dictionary<string, string>();
foreach(string sItem in MoveFromItems)
{
string sKey = sItem.Split(',')[3];
string sVal = sItem.Split(',')[1];
if(!MoveFroms.ContainsKey(sKey))
MoveFroms[sKey.ToUpper()] = sVal;
}
I appreciate any help you might be able to provide.
The problem is most likely that the keys have duplicates. You have three options.
Keep First Entry (This is what you're currently doing in the foreach loop)
Keys only have one entry, the first one that shows up - meaning you can have a Dictionary:
var first = MoveFromItems.Select(x => x.Split(','))
.GroupBy(x => x[3])
.ToDictionary(x => x.Key, x => x.First()[1]);
Keep All Entries, Grouped
Keys will have more than one entry (each key returns an Enumerable), and you use a Lookup instead of a Dictionary:
var lookup = MoveFromItems.Select(x => x.Split(','))
.ToLookup(x => x[3], x => x[1]);
Keep All Entries, Flattened
No such thing as a key, simply a flattened list of entries:
var flat = MoveFromItems.Select(x => x.Split(','))
.Select(x => new KeyValuePair<string,string>(x[3], x[1]));
You could also use a tuple here (Tuple.Create(x[3], x[1]);) instead.
Note: You will need to decide where/if you want the keys to be upper or lower case in these cases. I haven't done anything related to that yet. If you want to store the key as upper, just change x[3] to x[3].ToUpper() in everything above.
This splits each item and selects key out of the 4th split-value, and value out of the 2nd split-value, all into a dictionary.
var dictionary = MoveFromItems.Select(s => s.Split(','))
.ToDictionary(split => split[3],
split => split[1]);
There is no point in splitting the string twice, just to use different indices.
This would be just like saving the split results into a local variable, then using it to access index 3 and 1.
However, if indeed you don't know if keys might reoccur, I would go for the simple loop you've implemented, without a doubt.
Although you have a small bug in your loop:
MoveFroms = new Dictionary<string, string>();
foreach(string sItem in MoveFromItems)
{
string sKey = sItem.Split(',')[3];
string sVal = sItem.Split(',')[1];
// sKey might not exist as a key
if (!MoveFroms.ContainsKey(sKey))
//if (!MoveFroms.ContainsKey(sKey.ToUpper()))
{
// but sKey.ToUpper() might exist!
MoveFroms[sKey.ToUpper()] = sVal;
}
}
Should do ContainsKey(sKey.ToUpper()) in your condition as well, if you really want the key all upper cases.
This will Split each string in MoveFromItems with ',' and from them make 4th item (3rd Index) as Key and 2nd item(1st Index) as Value.
var dict = MoveFromItems.Select(x => x.Split(','))
.ToLookup(x => x[3], x => x[1]);

Categories