c# - Remove last n elements from Dictionary - c#

How do I remove the last 2 keyValuePairs from a Dictionary of string, string where the key starts with "MyKey_" ?
var myDict = new Dictionary<string, string>();
myDict.Add("SomeKey1", "SomeValue");
myDict.Add("SomeKey2", "SomeValue");
myDict.Add("MyKey_" + Guid.NewGuid(), "SomeValue");
myDict.Add("MyKey_" + Guid.NewGuid(), "SomeValue");
myDict.Add("MyKey_" + Guid.NewGuid(), "SomeValue");
EDIT:
var noGwInternal = myDict.Where(o => !o.Key.StartsWith("MyKey_")).ToDictionary(o => o.Key, o => o.Value);
var gwInternal = myDict.Where(o => o.Key.StartsWith("MyKey_")).ToDictionary(o => o.Key, o => o.Value);
How to move forward from here? Need to remove 2 of the items from gwInternal and then put noGwInternal + gwInternal into a new Dictionary together

Not sure what you mean 'last', since this is a dictionary (there is no order), but this code will remove the last 2 in the order they were encountered in the loop.
List<string> toRemove = new List<string>();
foreach(KeyValuePair pair in myDict.Reverse())
{
if(pair.key.StartsWith("MyKey_"))
{
toRemove.Add(pair.key);
toRemoveCount--;
}
if(toRemove.Count == 2)
{
break;
}
}
foreach(string str in toRemove)
{
myDict.Remove(str);
}

This should do what you're trying to do (according to what you posted in your comment).
(edit: It looks like you replaced your comment, now I'm not sure you're going for alphabetical...)
var myDict = new Dictionary<string, string>();
myDict.Add("SomeKey1", "SomeValue");
myDict.Add("SomeKey2", "SomeValue");
myDict.Add("MyKey_B" + Guid.NewGuid(), "SomeValue");
myDict.Add("MyKey_A" + Guid.NewGuid(), "SomeValue");
myDict.Add("MyKey_C" + Guid.NewGuid(), "SomeValue");
var pairsToRemove = myDict.Where(x => x.Key.StartsWith("MyKey_"))
.OrderByDescending(x => x.Key)
.Take(2);
foreach (var pair in pairsToRemove)
{
myDict.Remove(pair.Key);
}
foreach (var pair in myDict)
{
Console.WriteLine(pair);
}
Output: (MyKey_B and MyKey_C are removed)
[SomeKey1, SomeValue]
[SomeKey2, SomeValue]
[MyKey_Ad6c3a25d-5d8c-44e4-9651-39164c0496fc, SomeValue]
I like what tevemadar mentioned about the OrderedDictionary... I'm not sure it'll work for what you're trying to do, but it's worth a look.

Related

getting error "The given key was not present in the dictionary" due to "SubKey2" not present for "BaseKey2"

I have common subkeys (SubKey1/SubKey2, etc) for each set of BaseKey1, BaseKey2, .... etc, but occurrence of all subkeys for each basekey is NOT fixed. In below example "SubKey2" not present for "BaseKey2".
var model = new Dictionary<string, Dictionary<string, Model>>
{
{
"BaseKey1",
new Dictionary<string, Model>
{
{"SubKey1", new Model { Time = new DateTime(2020, 09, 15)}},
{"SubKey2", new Model { Time = new DateTime(2020, 12, 15) }}
}
},
{
"BaseKey2",
new Dictionary<string, Model>
{
{"SubKey1", new Model { Time = new DateTime(2020, 11, 15) }},
}
}
};
I need to pull Min Time for each subkey from all basekey and to do so I am doing below,
var query = model.Values
.SelectMany(d => d.Keys)
.Distinct()
.Select(key => new
{
Key = key,
Time = model.Values.Min(v => v[key].Time)
})
.ToList();
But it's giving error "The given key was not present in the dictionary" due to "SubKey2" not present for "BaseKey2". What could be solution here? I need below output. Thanks!
You're not going to be able to do this without some loops -- the fact that you're using a dictionary is more or less irrelevant.
My understanding is that you want something like this:
var result = new Dictionary<string, Model>();
// For each "BaseKey" dictionary...
foreach (var subDictionary in model.Values)
{
// For each "SubKey" and its corresponding value
foreach (var (key, value) in subDictionary)
{
// If we haven't yet recorded a value for this SubKey, add this value
// If we have, but it's higher than the value for this SubKey, replace it
if (!result.TryGetValue(key, out var existingValue) || value.Time < existingValue.Time)
{
result[key] = value;
}
}
}
See it working here.
You can sprinkle in a bit of LINQ to remove one of the loops quite easily:
var result = new Dictionary<string, Model>();
foreach (var (key, value) in model.Values.SelectMany(x => x))
{
if (!result.TryGetValue(key, out var existingValue) || value.Time < existingValue.Time)
{
result[key] = value;
}
}
See it working here.
If you really want to LINQ it up, you'll want something like:
var result = model.Values
.SelectMany(x => x)
.GroupBy(x => x.Key)
.ToDictionary(
x => x.Key,
x => x.Select(m => m.Value)
.MinBy(m => m.Time));
... where MinBy is provided by e.g. this answer. That's probably going to be measurably slower though, and isn't any shorter.
See it working here.
Check this
Dictionary<string, Model> flatSubDict = new Dictionary<string, Model>();
foreach(var key in model.Keys)
{
foreach(var mKey in model[key].Keys)
{
if(flatSubDict.TryGetValue(mKey, out var m))
{
// if existing time greater than new time then replace
if(m.Time > model[key][mKey].Time)
{
flatSubDict[mKey] = m;
}
}
else
flatSubDict[mKey] = m;
}
}
return flatSubDict.Values;

How to use LINQ to find a sum?

I have this structure:
private readonly Dictionary<string, Dictionary<string, int>> _storage =
new Dictionary<string, Dictionary<string, int>>();
key: Firmware(string): key: Device(string) : value CountOfUsers (int)
I need to get the total of users for each device, but I really don't know how to do it with LINQ. Already tried a lot of variants. Please, help!
For now, I just use a whole function for it
private XlsRow2 GetTotalPerDevice(Dictionary<string, Dictionary<string, int>> storage)
{
XlsRow2 totalPerDeviceRow = new XlsRow2();
totalPerDeviceRow._Name = "Grand Total";
totalPerDeviceRow.UseBorders = true;
foreach (var deviceModel in _allDeviceModels)
{
foreach (var firmware in storage)
{
foreach (var device in firmware.Value)
{
var countOfUsers = 0;
if (deviceModel == device.Key)
{
countOfUsers += device.Value;
if (!_totalsPerDevice.ContainsKey(deviceModel))
{
_totalsPerDevice.Add(deviceModel, countOfUsers);
}
else
{
_totalsPerDevice[deviceModel] += countOfUsers;
}
}
}
}
}
foreach (var deviceModel in _allDeviceModels)
{
if (_totalsPerDevice.ContainsKey(deviceModel))
{
totalPerDeviceRow._AddColumn(_totalsPerDevice.First(k => k.Key == deviceModel.ToString()).Value.ToString());
}
else
{
totalPerDeviceRow._AddColumn("");
}
}
return totalPerDeviceRow;
}
Something like this for example?
var result = _storage.SelectMany(x => x.Value)
.GroupBy(x => x.Key)
.Select(x => new { Device = x.Key, Total = x.Sum(y => y.Value) });
Since the keys for the data that you would like to aggregate is in the second-level dictionary, a good first step would be to dump all key-value pairs from inner dictionaries into a flat sequence. After that all you need is to aggregate the counts, like this:
var res = _storage
.SelectMany(d => d.Value)
.GroupBy(kvp => kvp.Key)
.ToDictionary(g => g.Key, g => g.Sum(kvp => kvp.Value));
A Dictionary implements IEnumerable<KeyValuePair<TKey,TValue> which means you can use LINQ on it. In this case you have a dictionary of dictionaries and need to group by the second level key. To do that, you need to flatten the dictionaries, something that can be done with SelectMany
_storage.Selectmany(pair=>pair.Value);
Once you have the leaf-level entries, you can group by their keys:
_storage.Selectmany(pair=>pair.Value)
.GroupBy(leaf=>leaf.Key);
And calculate the sum per group:
var totals=_storage.SelectMany(pair=>pair.Value)
.GroupBy(leaf=>leaf.Key)
.Select(grp=>new {
Device = grp.Key,
TotalUsers =grp.Sum(leaf=>leaf.Value)
});
The equivalent query is rather cleaner:
var totals2 = from frm in _storage
from dev in frm.Value
group dev by dev.Key into grp
select new {
Device = grp.Key,
Total=grp.Sum(leaf=>leaf.Value)
};
Given the following dictionary:
var _storage = new Dictionary<string, Dictionary<string, int>> {
["Frm1"]=new Dictionary<string, int> {
["Device1"]=4,
["Device2"]=5
},
["Frm2"]=new Dictionary<string, int> {
["Device1"]=41,
["Device3"]=5
}
};
Both queries return the same values
foreach(var total in totals)
{
Console.WriteLine ($"{total.Device} = {total.Total}");
}
------------------
Device1 = 45
Device2 = 5
Device3 = 5
You can do this like:
Dictionary<string, Dictionary<string, int>> _storage = new Dictionary<string, Dictionary<string, int>>();
Dictionary<string, int> x = new Dictionary<string, int>();
x.Add("x", 2);
x.Add("z", 2);
x.Add("y", 2);
_storage.Add("x", x);
_storage.Add("z", x);
_storage.Add("y", x);
var b = _storage.SelectMany(keyValuePair => keyValuePair.Value)
.GroupBy(keyValuePair => keyValuePair.Key)
.ToDictionary(valuePairs => valuePairs.Key, grouping => grouping.Sum(kvp => kvp.Value));
result will be like:

List Duplicates Ignored in LINQ Merge

Following on from this post is it possible to create a new list of the duplicate records found (and excluded) in the merge? I want to let the user know which records were excluded. The code I have is working in that it is correctly merging the data and excluding any duplicate keys, but I want to be able to show the keys excluded after the merge.
var fileLocation = #"D:\TFS2010-UK\Merge_Text\PM.INX";
var fileContents =
File.ReadLines(FileLocation, Encoding.Default)
.Select(line => line.Split(','))
.ToDictionary(line => line[0].Replace("\"", ""), line => line[1] + ',' + line[2] + ',' + line[3]);
// define an array of items to be added...
var newContent = new Dictionary<string, string>
{
{ "XYZ789", "\"XYZ789\",1,123.789" },
{ "GHI456", "\"GHI456\",2,123.456" },
{ "ABC123", "\"ABC123\",1,123.123" }
};
var uniqueElements = fileContents.Concat(newContent.Where(kvp => !fileContents.ContainsKey(kvp.Key)))
.OrderBy(x => x.Key)
.ToDictionary(y => y.Key, z => z.Value);
// append new lines to the existing file...
using (var writer = new StreamWriter(fileLocation))
{
// loop through the data to be written...
foreach (var pair in uniqueElements)
{
// and write it to the file...
writer.WriteLine("\"{0}\",{1}", pair.Key, pair.Value);
}
}
Many thanks. Martin
var removedKeys = newContent.Where(kvp => fileContents.ContainsKey(kvp.Key))
.Select(kvp => kvp.Key);
Simply select the keys in newContent that are already contained in fileContents.

Checking a dictionary if there are same values for a key

I have a dictionary object like this:
CustomKeys<int, string>
eg;
1000, F1
1001, F2
1002, F1
1003, F4
1004, F2
I want to know if I have more than 1 of same values in this dictionary. I would also want to keep a note of which keys(unique id) has duplicates.
Is that possible?
It is possible using GroupBy and than Count() > 1 to keep track of which values that have duplicates.
var q = dic.GroupBy(x => x.Value)
.Select (x => new { Item = x, HasDuplicates = x.Count() > 1 });
You can find all key values they had the same values like this;
Dictionary<int, string> d = new Dictionary<int, string>();
d.Add(1000, "F1");
d.Add(1001, "F2");
d.Add(1002, "F1");
d.Add(1003, "F4");
d.Add(1004, "F2");
var dublicate = d.ToLookup(x => x.Value, x => x.Key).Where(x => x.Count() > 1);
foreach (var i in dublicate)
{
Console.WriteLine(i.Key);
}
Here is a DEMO.
But if you want to get a boolean value since your item's has a same value, look at Magnus's answer which is great.
I'm not sure by what you mean by "keeping note of which has duplicate values". If you mean keeping note of the keys, you could do this:
var keys = new Dictionary<int, string>();
keys.Add(1000, "F1");
keys.Add(1001, "F2");
keys.Add(1002, "F1");
keys.Add(1003, "F4");
keys.Add(1004, "F2");
var duplicates = keys.GroupBy(i => i.Value).Select(i => new
{
keys = i.Select(x => x.Key),
value = i.Key,
count = i.Count()
});
foreach (var duplicate in duplicates)
{
Console.WriteLine("Value: {0} Count: {1}", duplicate.value, duplicate.count);
foreach (var key in duplicate.keys)
{
Console.WriteLine(" - {0}", key);
}
}
If you mean keeping track of the duplicate values only, see Sonor's answer.
Another solution could be:
var duplicates = dictionary.GroupBy( g => g.Value )
.Where( x => x.Count( ) > 1 )
.Select( x => new { Item = x.First( ), Count = x.Count( ) } )
.ToList( );

Avoiding same key error in dictionary with a suffix

I can't have the same key. But a simple (and effective) solution is put a suffix after key.
But as I'm in a foreach, I was wondering a fast and clean way to add a number suffix to duplicated keys.
e.g.:
My foreach is that:
foreach (Item item in items) {
dic.Add(item.SomeKey, item.SomeValue);
}
But I don't want duplicated keys, so I need to 'handle' SomeKey to Origin become Result
SomeKey Origin: key, clave, clave, chave, chave, chave
SomeKey Result: key, clave, clave1, chave, chave1, chave2
Edit:
My answer to #KooKiz explain better the question.
I have few duplicated entries. I'm just trying to figure out how to then increment the suffix until you find no item. Sounds like reinvent wheels, so I was wondering if someone know a good way to do that
That may not be the fastest, but that's the more readable I can think of:
var source = new List<Tuple<string, string>>
{
new Tuple<string, string>("a", "a"),
new Tuple<string, string>("a", "b"),
new Tuple<string, string>("b", "c"),
new Tuple<string, string>("b", "d"),
};
var groups = source.GroupBy(t => t.Item1, t => t.Item2);
var result = new Dictionary<string, string>();
foreach (var group in groups)
{
int index = 0;
foreach (var value in group)
{
string key = group.Key;
if (index > 0)
{
key += index;
}
result.Add(key, value);
index++;
}
}
foreach (var kvp in result)
{
Console.WriteLine("{0} => {1}", kvp.Key, kvp.Value);
}
If you want a key with several "sub" items, Try this
Dictionary<string, List<string>> myList = new Dictionary<string, List<string>>();
foreach (Item item in items)
{
if (myList[item.SomeKey] == null)
myList.Add(item.SomeKey, new List<string>());
myList[item.SomeKey].Add(item.SomeValue);
}
var a = items.GroupBy(p => p.SomeKey)
.SelectMany(q => q.Select((value, index) =>
new Item { SomeKey = (q.Count() > 1 && index > 0) ? value.SomeKey + (index) :
value.SomeKey, SomeValue = value.SomeValue }))
.ToDictionary(p => p.SomeKey, q => q.SomeValue);

Categories