Remove item from dictionary where value is empty list - c#

What is the best way to remove item from dictionary where the value is an empty list?
IDictionary<int,Ilist<T>>

var foo = dictionary
.Where(f => f.Value.Count > 0)
.ToDictionary(x => x.Key, x => x.Value);
This will create a new dictionary. If you want to remove in-place, Jon's answer will do the trick.

Well, if you need to perform this in-place, you could use:
var badKeys = dictionary.Where(pair => pair.Value.Count == 0)
.Select(pair => pair.Key)
.ToList();
foreach (var badKey in badKeys)
{
dictionary.Remove(badKey);
}
Or if you're happy creating a new dictionary:
var noEmptyValues = dictionary.Where(pair => pair.Value.Count > 0)
.ToDictionary(pair => pair.Key, pair => pair.Value);
Note that if you get a chance to change the way the dictionary is constructed, you could consider creating an ILookup instead, via the ToLookup method. That's usually simpler than a dictionary where each value is a list, even though they're conceptually very similar. A lookup has the nice feature where if you ask for an absent key, you get an empty sequence instead of an exception or a null reference.

Alternative provided just for completeness.
Alternatively (and depending entirely on your usage), do it at the point of amending the list content on the fly as opposed to a batch at a particular point in time. At a time like this it is likely you'll know the key without having to iterate:
var list = dictionary[0];
// Do stuff with the list.
if (list.Count == 0)
{
dictionary.Remove(0);
}
The other answers address the need to do it ad-hoc over the entire dictionary.

Related

How to create dictionary<int, string> from IEnumearble<IEnumerable<int, string>>

have a complex Linq expression right here. At the moment I'm only able to create IEnumearble, but I need to parse to IEnumerable<int, string>. I have no clue how to take that 'string'.
IEnumerable<IEnumerable<int>> uniqueColumns = test
.Where(e => e.ToolParameters != null)
.Select(x => x.ToolParameters.Select(u => u.ToolParameterTypeId))
.Distinct();
uniqueColumnIds = uniqueColumns
.SelectMany(a => a)
.Distinct();
As you can see, I check if 'ToolParameters' is null. If it is not, then I select ' ToolParametersTypeId', but I also need to select 'ToolParameteresTypeName', but have no idea how...
After that, I need to parse that Ienumearble<Ienumearble<>> to only one Ienumerable because my goals is to have unique values from a bunch of lists. But I need to make a dictionary which consists of unique key,string values from a bunch of lists...
I have uniqueColumnIds, but I also need to get uniqueColumnNames and put them together to get a dictionary of key,value pairs. Maybe someone has any ideas on how to do that?
Dictionary is not the correct structure, as each key can only appear once. If a ToolParametersTypeId has more than one corresponding ToolParameteresTypeName then you will need to discard all but one ToolParameteresTypeName. A more appropriate structure may be IEnumerable<(int, string)>:
IEnumerable<(int ToolParameterTypeId, string ToolParameteresTypeName)> uniqueColumns =
test
.Where(e => e.ToolParameters != null)
.SelectMany(e => e.ToolParameters.Select(tp =>
(tp.ToolParameterTypeId, tp.ToolParameteresTypeName)))
.Distinct();
SelectMany flattens the nested IEnumerables, and the nested Select projects each item into a ValueTuple.
Using Distinct on an IEnumerable<ValueTuple> then checks for equality based on the value of each tuple component, not by reference, so you will end with distinct pairs of ToolParameterTypeId, and ToolParameteresTypeName.
It is easy.
IEnumerable<IEnumerable<(int key, string value)>> target = default; // replace default.
var dict = target.SelectMany(x => x).ToDictionary(x => x.key, x => x.value);

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.

Select highest value for the key in dictionary that is highest among the keys lower than given "at" value

Is there a better way of writing this, with LINQ instead of custom method.
Sorted Dictionary is not an option.
I think that Dictionary.Where(x => x.Key < at) is fine but
.OrderBy(x => x.Key).Last() will be slow, as I need only one value I am wasting
CPU for ordering.
Dictionary.Where(x => x.Key < at).OrderBy(x => x.Key).Last().Value;
maybe:
Dictionary[Dictionary.Where(x => x.Key < at).Max(x => x.Key)];
The second option is good. It first finds the max Key and then the Value that goes with it.
Alternatively, you could write a MaxBy method.
Or just use the one already implemented in MoreLinq:
http://code.google.com/p/morelinq/
Dictionary[Dictionary.Keys.Where(key=> key < at).Max()]

Dictionary of dictionaries

I have a resource file I grab like this:
var rs = <somewhere>.FAQ.ResourceManager
.GetResourceSet(CultureInfo.CurrentUICulture, true, true);
and I want to parse it into a dictionary of dictionaries but I can't figure out quite how. this is what I'm trying:
var ret = rs.OfType<DictionaryEntry>()
.Where(x => x.Key.ToString().StartsWith("Title"))
.ToDictionary<string, Dictionary<String, string>>(
k => k.Value.ToString(),
v => rs.OfType<DictionaryEntry>()
.Where(x => x.Key.ToString().StartsWith(v.Value.ToString().Replace("Title", "")))
.ToDictionary<string, string>(
key => key.Value,
val => val.Value
)
);
so if I understand this correctly, k should refer to a DictionaryEntry and thus I should be able to dereference it like k.Value and to manufacture my dictionary in each of the outer dictionary's entries I do another query against the resource file, thus key and val should also be of type DictionaryEntry.
In referencing val.Value I get the error "Cannot choose method from method group. Did you intend to invoke the method?" though that should be a property, not a method.
help?
p.s. as an explanation, my resource file looks sort of like this:
TitleUser: User Questions
TitleCust: Customer Questions
User1: Why does something happen? Because…
User2: How do I do this? Start by…
Cust1: Where can I find…? It is located at…
Cust2: Is there any…? yes, look for it…
which means I first get a list of sections (by looking for all keys that start with "Title") and for each I look for a list of questions
so the answer turns out to be that the compiler knows better as to the types involved. leaving out the specifiers makes it work, though quite why my specifiers were wrong I don't yet get.
var ret = rs.OfType<DictionaryEntry>()
.Where(x => x.Key.ToString().StartsWith("Title"))
.ToDictionary(
k => k.Value.ToString(),
v => rs.OfType<DictionaryEntry>()
.Where(x => x.Key.ToString().StartsWith(v.Key.ToString().Replace("Title", "")))
.ToDictionary(
x => x.Value.ToString().Split('?')[0] + "?",
x => x.Value.ToString().Split('?')[1]
)
);
(I've made some changes to actually make it do what I intended for it to do).

Categories

Resources