Best method to find element which throws exception in ToDictionary() - c#

I have a list of items which is populated by some larger configuration file.
List<TextEntrtry> localTextEntries;
with elements of type TextEntry:
public class TextEntry
{
public Guid Id { get; set; }
....
This list is converted to a dictionary:
Dictionary<Guid, TextEntry> textEntries;
and this line throws an exception 'Element with same key already exists':
textEntries = localTextEntries.ToDictionary(x => x.Id);
Obviously my list contains two elements with the same Id.
My question: what is the best way to find out which elements cause the exception?
(Which would allow me to produce a meaningfull error message)

You can re-write ToDictionary to use your own implementation that includes the key in the exception message:
//TODO come up with a slightly better name
public static Dictionary<TKey, TValue> MyToDictionary<TSource, TKey, TValue>(
this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector,
Func<TSource, TValue> valueSelector,
IEqualityComparer<TKey> comparer)
{
comparer = comparer ?? EqualityComparer<TKey>.Default;
var dictionary = new Dictionary<TKey, TValue>(comparer);
foreach (var item in source)
{
var key = keySelector(item);
try
{
dictionary.Add(key, valueSelector(item));
}
catch (ArgumentException ex)
{
throw new ArgumentException("Missing key: " + key, ex);
}
}
return dictionary;
}
You'd want to create overloads without a comparer or value selector, in which default values for those parameters are used.
You may also want to create a new type of Exception that stores the key as a property, rather than including the string value of the key in the exception message (in the event there is no good string representation of the object).

Run this on your collection to get the ones with duplicate entries:
var duplicateEntries = localTextEntries.GroupBy(k = > k.Id)
.Where(g = > g.Count() > 1)
.Select(g = > g.Key);
You can also always add an extension method and get distinct values from your source
IEnumerable <TextEntry> distinctList = localTextEntries.DistinctBy(x = > x.Id);
public static IEnumerable<TSource> Distinctify<TSource, TKey>(this IEnumerable<TSource> inSrc_, Func<TSource, TKey> keyFunct_)
{
var uniqueSet = new HashSet<TKey>();
return inSrc_.Where(tmp => uniqueSet.Add(keyFunct_(tmp)));
}

You can use group by to check for repeated or not repeated items. To create the dictionary run the ToDictionary method on the grouped items:
// get repeated items
var repeated = localTextEntries.GroupBy(t => t.Id).Where(g => g.Count() > 1).Select(i => i);
// get not repeated items
var notRepeated = localTextEntries.GroupBy(t => t.Id).Where(g => g.Count() == 1).Select(i => i.First());
// find not repeated items and create a dictionary
var dictFromNotRepeated = localTextEntries.GroupBy(t => t.Id)
.Where(g => g.Count() == 1)
.ToDictionary(g => g.Key, g => g.First());

Finally the approach of #Servy worked best for me. I just chose a matching overload and added some better error handling:
public static Dictionary<TKey, TSource> ToDictionary2<TSource, TKey>(
this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector,
IEqualityComparer<TKey> comparer = null)
{
comparer = comparer ?? EqualityComparer<TKey>.Default;
Dictionary<TKey, TSource> dictionary =
new Dictionary<TKey, TSource>(comparer);
foreach (var item in source)
{
var key = keySelector(item);
try
{
dictionary.Add(key, item);
}
catch (Exception ex)
{
string msg = string.Format("Problems with key {0} value {1}",
key,
item);
throw new Exception(msg, ex);
}
}
return dictionary;
}

Related

Why Linq Distinct() does not work for my ConsultantList?

I've tried to return distinct values in my ConsultantList, but it is still returning duplicates.
ConsultantList = p.ProjectExtensions
.SelectMany(pext => pext.Consultants)
.Distinct()
.Select(c =>
new ConsultantItemDTO
{
EmployeeId = c.ConsultantId,
EmployeeName = c.Employee.Firstname + " " + c.Employee.Lastname
}),
Here is the entire method:
Get Project Method
In the documentation says:
If you want to return distinct elements from sequences of objects of some custom data type, you have to implement the IEquatable generic interface in the class.
Or you can extend the IEnumerable:
public static IEnumerable<TSource> DistinctBy<TSource, TKey>
(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
HashSet<TKey> seenKeys = new HashSet<TKey>();
foreach (TSource element in source)
{
if (seenKeys.Add(keySelector(element)))
{
yield return element;
}
}
}
Example of use:
.Select(p => p.Employee)
.DistinctBy(p => p.EmployeeId)

LINQ: Group by index and value [duplicate]

This question already has answers here:
linq group by contiguous blocks
(5 answers)
Closed 4 years ago.
Lets say I have an list of strings with the following values:
["a","a","b","a","a","a","c","c"]
I want to execute a linq query that will group into 4 groups:
Group 1: ["a","a"] Group 2: ["b"] Group 3: ["a","a","a"] Group 4:
["c","c"]
Basically I want to create 2 different groups for the value "a" because they are not coming from the same "index sequence".
Anyone has a LINQ solution for this?
You just need key other than items of array
var x = new string[] { "a", "a", "a", "b", "a", "a", "c" };
int groupId = -1;
var result = x.Select((s, i) => new
{
value = s,
groupId = (i > 0 && x[i - 1] == s) ? groupId : ++groupId
}).GroupBy(u => new { groupId });
foreach (var item in result)
{
Console.WriteLine(item.Key);
foreach (var inner in item)
{
Console.WriteLine(" => " + inner.value);
}
}
Here is the result: Link
Calculate the "index sequence" first, then do your group.
private class IndexedData
{
public int Sequence;
public string Text;
}
string[] data = [ "a", "a", "b" ... ]
// Calculate "index sequence" for each data element.
List<IndexedData> indexes = new List<IndexedData>();
foreach (string s in data)
{
IndexedData last = indexes.LastOrDefault() ?? new IndexedData();
indexes.Add(new IndexedData
{
Text = s,
Sequence = (last.Text == s
? last.Sequence
: last.Sequence + 1)
});
}
// Group by "index sequence"
var grouped = indexes.GroupBy(i => i.Sequence)
.Select(g => g.Select(i => i.Text));
This is a naive foreach implementation where whole dataset ends up in memory (probably not an issue for you since you do GroupBy):
public static IEnumerable<List<string>> Split(IEnumerable<string> values)
{
var result = new List<List<string>>();
foreach (var value in values)
{
var currentGroup = result.LastOrDefault();
if (currentGroup?.FirstOrDefault()?.Equals(value) == true)
{
currentGroup.Add(value);
}
else
{
result.Add(new List<string> { value });
}
}
return result;
}
Here comes a slightly complicated implementation with foreach and yield return enumerator state machine which keeps only current group in memory - this is probably how this would be implemented on framework level:
EDIT: This is apparently also the way MoreLINQ does it.
public static IEnumerable<List<string>> Split(IEnumerable<string> values)
{
var currentValue = default(string);
var group = (List<string>)null;
foreach (var value in values)
{
if (group == null)
{
currentValue = value;
group = new List<string> { value };
}
else if (currentValue.Equals(value))
{
group.Add(value);
}
else
{
yield return group;
currentValue = value;
group = new List<string> { value };
}
}
if (group != null)
{
yield return group;
}
}
And this is a joke version using LINQ only, it is basically the same as the first one but is slightly harder to understand (especially since Aggregate is not the most frequently used LINQ method):
public static IEnumerable<List<string>> Split(IEnumerable<string> values)
{
return values.Aggregate(
new List<List<string>>(),
(lists, str) =>
{
var currentGroup = lists.LastOrDefault();
if (currentGroup?.FirstOrDefault()?.Equals(str) == true)
{
currentGroup.Add(str);
}
else
{
lists.Add(new List<string> { str });
}
return lists;
},
lists => lists);
}
Using an extension method based on the APL scan operator, that is like Aggregate but returns intermediate results paired with source values:
public static IEnumerable<KeyValuePair<TKey, T>> ScanPair<T, TKey>(this IEnumerable<T> src, TKey seedKey, Func<KeyValuePair<TKey, T>, T, TKey> combine) {
using (var srce = src.GetEnumerator()) {
if (srce.MoveNext()) {
var prevkv = new KeyValuePair<TKey, T>(seedKey, srce.Current);
while (srce.MoveNext()) {
yield return prevkv;
prevkv = new KeyValuePair<TKey, T>(combine(prevkv, srce.Current), srce.Current);
}
yield return prevkv;
}
}
}
You can create extension methods for grouping by consistent runs:
public static IEnumerable<IGrouping<int, TResult>> GroupByRuns<TElement, TKey, TResult>(this IEnumerable<TElement> src, Func<TElement, TKey> key, Func<TElement, TResult> result, IEqualityComparer<TKey> cmp = null) {
cmp = cmp ?? EqualityComparer<TKey>.Default;
return src.ScanPair(0,
(kvp, cur) => cmp.Equals(key(kvp.Value), key(cur)) ? kvp.Key : kvp.Key + 1)
.GroupBy(kvp => kvp.Key, kvp => result(kvp.Value));
}
public static IEnumerable<IGrouping<int, TElement>> GroupByRuns<TElement, TKey>(this IEnumerable<TElement> src, Func<TElement, TKey> key) => src.GroupByRuns(key, e => e);
public static IEnumerable<IGrouping<int, TElement>> GroupByRuns<TElement>(this IEnumerable<TElement> src) => src.GroupByRuns(e => e, e => e);
public static IEnumerable<IEnumerable<TResult>> Runs<TElement, TKey, TResult>(this IEnumerable<TElement> src, Func<TElement, TKey> key, Func<TElement, TResult> result, IEqualityComparer<TKey> cmp = null) =>
src.GroupByRuns(key, result).Select(g => g.Select(s => s));
public static IEnumerable<IEnumerable<TElement>> Runs<TElement, TKey>(this IEnumerable<TElement> src, Func<TElement, TKey> key) => src.Runs(key, e => e);
public static IEnumerable<IEnumerable<TElement>> Runs<TElement>(this IEnumerable<TElement> src) => src.Runs(e => e, e => e);
And using the simplest version, you can get either an IEnumerable<IGrouping>>:
var ans1 = src.GroupByRuns();
Or a version that dumps the IGrouping (and its Key) for an IEnumerable:
var ans2 = src.Runs();

Convert a generic IEnumerable<T> to IEnumerable<KeyValuePair> (C#)

In the following code, I need to explicitly mention CountryId and CountryName but I would like to avoid that and trying to create a generic method.
public struct KeyValueStruct
{
public int Key { get; set; }
public string Value { get; set; }
}
private static IEnumerable<KeyValueStruct> ConvertPocoToKeyValueList(IEnumerable<CountryPoco> list)
{
var result = new List<KeyValueStruct>();
if (list != null)
{
foreach (var item in list)
{
result.Add(new KeyValueStruct()
{
Key = item.CountryId,
Value = item.CountryName
});
}
}
return result;
}
I know from the list that first property is always integer (which is CountryId in this example) and second property would be String.
I was thinking to implement using Generics but am not sure is this the best approach, see my proposed code (it's not working though).
private static IEnumerable<KeyValueStruct> ConvertPocoToKeyValueList<T>(T list)
{
var result = new List<KeyValueStruct>();
if (list != null)
{
foreach (var item in list)
{
result.Add(new KeyValueStruct()
{
Key = item.CountryId,
Value = item.CountryName
});
}
}
return result;
}
If you have a better idea to achieve the same result, then please propose.
You can make that generic by passing the properties to be used as Key and value. I think using the generic struct named KeyValuePair<Tkey, TValue> is better than reinventing the wheel yourself:
private static IEnumerable<KeyValuePair<Tkey, TValue>>
ConvertPocoToKeyValueList<TSource, Tkey, TValue>
(IEnumerable<TSource> list,
Func<TSource, Tkey> keySelector,
Func<TSource, TValue> valueSelector)
{
return list.Select(item => new KeyValuePair<Tkey, TValue>
(keySelector(item), valueSelector(item)));
}
Usage:
var result = ConvertPocoToKeyValueList(list, x=> x.CountryId, x=> x.CountryName);
You can even do that without using this generic method by using directly:
var result = list.Select(item => new KeyValuePair<Tkey, TValue>
(item.CountryId, item.CountryName));

C#, ToDictionary() with ContainsKey Check as parameter [duplicate]

I have a list of Person objects. I want to convert to a Dictionary where the key is the first and last name (concatenated) and the value is the Person object.
The issue is that I have some duplicated people, so this blows up if I use this code:
private Dictionary<string, Person> _people = new Dictionary<string, Person>();
_people = personList.ToDictionary(
e => e.FirstandLastName,
StringComparer.OrdinalIgnoreCase);
I know it sounds weird but I don't really care about duplicates names for now. If there are multiple names I just want to grab one. Is there anyway I can write this code above so it just takes one of the names and doesn't blow up on duplicates?
LINQ solution:
// Use the first value in group
var _people = personList
.GroupBy(p => p.FirstandLastName, StringComparer.OrdinalIgnoreCase)
.ToDictionary(g => g.Key, g => g.First(), StringComparer.OrdinalIgnoreCase);
// Use the last value in group
var _people = personList
.GroupBy(p => p.FirstandLastName, StringComparer.OrdinalIgnoreCase)
.ToDictionary(g => g.Key, g => g.Last(), StringComparer.OrdinalIgnoreCase);
If you prefer a non-LINQ solution then you could do something like this:
// Use the first value in list
var _people = new Dictionary<string, Person>(StringComparer.OrdinalIgnoreCase);
foreach (var p in personList)
{
if (!_people.ContainsKey(p.FirstandLastName))
_people[p.FirstandLastName] = p;
}
// Use the last value in list
var _people = new Dictionary<string, Person>(StringComparer.OrdinalIgnoreCase);
foreach (var p in personList)
{
_people[p.FirstandLastName] = p;
}
Here's the obvious, non linq solution:
foreach(var person in personList)
{
if(!myDictionary.ContainsKey(person.FirstAndLastName))
myDictionary.Add(person.FirstAndLastName, person);
}
If you don't mind always getting the last one added, you can avoid the double lookup like this:
foreach(var person in personList)
{
myDictionary[person.FirstAndLastName] = person;
}
A Linq-solution using Distinct() and and no grouping is:
var _people = personList
.Select(item => new { Key = item.Key, FirstAndLastName = item.FirstAndLastName })
.Distinct()
.ToDictionary(item => item.Key, item => item.FirstFirstAndLastName, StringComparer.OrdinalIgnoreCase);
I don't know if it is nicer than LukeH's solution but it works as well.
This should work with lambda expression:
personList.Distinct().ToDictionary(i => i.FirstandLastName, i => i);
You can create an extension method similar to ToDictionary() with the difference being that it allows duplicates. Something like:
public static Dictionary<TKey, TElement> SafeToDictionary<TSource, TKey, TElement>(
this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector,
Func<TSource, TElement> elementSelector,
IEqualityComparer<TKey> comparer = null)
{
var dictionary = new Dictionary<TKey, TElement>(comparer);
if (source == null)
{
return dictionary;
}
foreach (TSource element in source)
{
dictionary[keySelector(element)] = elementSelector(element);
}
return dictionary;
}
In this case, if there are duplicates, then the last value wins.
You can also use the ToLookup LINQ function, which you then can use almost interchangeably with a Dictionary.
_people = personList
.ToLookup(e => e.FirstandLastName, StringComparer.OrdinalIgnoreCase);
_people.ToDictionary(kl => kl.Key, kl => kl.First()); // Potentially unnecessary
This will essentially do the GroupBy in LukeH's answer, but will give the hashing that a Dictionary provides. So, you probably don't need to convert it to a Dictionary, but just use the LINQ First function whenever you need to access the value for the key.
To handle eliminating duplicates, implement an IEqualityComparer<Person> that can be used in the Distinct() method, and then getting your dictionary will be easy.
Given:
class PersonComparer : IEqualityComparer<Person>
{
public bool Equals(Person x, Person y)
{
return x.FirstAndLastName.Equals(y.FirstAndLastName, StringComparison.OrdinalIgnoreCase);
}
public int GetHashCode(Person obj)
{
return obj.FirstAndLastName.ToUpper().GetHashCode();
}
}
class Person
{
public string FirstAndLastName { get; set; }
}
Get your dictionary:
List<Person> people = new List<Person>()
{
new Person() { FirstAndLastName = "Bob Sanders" },
new Person() { FirstAndLastName = "Bob Sanders" },
new Person() { FirstAndLastName = "Jane Thomas" }
};
Dictionary<string, Person> dictionary =
people.Distinct(new PersonComparer()).ToDictionary(p => p.FirstAndLastName, p => p);
In case we want all the Person (instead of only one Person) in the returning dictionary, we could:
var _people = personList
.GroupBy(p => p.FirstandLastName)
.ToDictionary(g => g.Key, g => g.Select(x=>x));
The issue with most of the other answers is that they use Distinct, GroupBy or ToLookup, which creates an extra Dictionary under the hood. Equally ToUpper creates extra string.
This is what I did, which is an almost an exact copy of Microsoft's code except for one change:
public static Dictionary<TKey, TSource> ToDictionaryIgnoreDup<TSource, TKey>
(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IEqualityComparer<TKey> comparer = null) =>
source.ToDictionaryIgnoreDup(keySelector, i => i, comparer);
public static Dictionary<TKey, TElement> ToDictionaryIgnoreDup<TSource, TKey, TElement>
(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector, IEqualityComparer<TKey> comparer = null)
{
if (keySelector == null)
throw new ArgumentNullException(nameof(keySelector));
if (elementSelector == null)
throw new ArgumentNullException(nameof(elementSelector));
var d = new Dictionary<TKey, TElement>(comparer ?? EqualityComparer<TKey>.Default);
foreach (var element in source)
d[keySelector(element)] = elementSelector(element);
return d;
}
Because a set on the indexer causes it to add the key, it will not throw, and will also do only one key lookup. You can also give it an IEqualityComparer, for example StringComparer.OrdinalIgnoreCase
DataTable DT = new DataTable();
DT.Columns.Add("first", typeof(string));
DT.Columns.Add("second", typeof(string));
DT.Rows.Add("ss", "test1");
DT.Rows.Add("sss", "test2");
DT.Rows.Add("sys", "test3");
DT.Rows.Add("ss", "test4");
DT.Rows.Add("ss", "test5");
DT.Rows.Add("sts", "test6");
var dr = DT.AsEnumerable().GroupBy(S => S.Field<string>("first")).Select(S => S.First()).
Select(S => new KeyValuePair<string, string>(S.Field<string>("first"), S.Field<string>("second"))).
ToDictionary(S => S.Key, T => T.Value);
foreach (var item in dr)
{
Console.WriteLine(item.Key + "-" + item.Value);
}
Using LINQ's equivalent of foldLeft functionality
persons.Aggregate(new Dictionary<string,Person>(StringComparer.OrdinalIgnoreCase),
(acc, current) => {
acc[current.FirstAndLastName] = current;
return acc;
});
Starting from Carra's solution you can also write it as:
foreach(var person in personList.Where(el => !myDictionary.ContainsKey(el.FirstAndLastName)))
{
myDictionary.Add(person.FirstAndLastName, person);
}

set unique values to lookup edit data source

I'm trying to put non duplicated values in a lookup edit using the code below but the variable unique is always null, and I don't know where is the problem.
Any help please?
List<VueItemItemUnit> liste = ObjReservation.LoadAllFamilles();
var unique =
from element in liste
group element by element.FA_CODE into Group
where Group.Count() == 1
select Group.Key;
lookUpFamille.Properties.DataSource = unique;
Try this :
var unique = liste.Distinct(element => element.FA_CODE).Select(element => element.FA_CODE);
I suggest you use the following approach:
lookUpFamille.Properties.DataSource = list.DistinctBy(e => e.FA_CODE).ToList();
//...
// DistinctBy<T,TKey> extension
static class EnumerableHelper {
public static IEnumerable<T> DistinctBy<T, TKey>(this IEnumerable<T> source, Func<T, TKey> keySelector) {
return source.Distinct(new EqualityComparer<T, TKey>(keySelector));
}
class EqualityComparer<T, TKey> : IEqualityComparer<T> {
readonly Func<T, TKey> keySelector;
public EqualityComparer(Func<T, TKey> keySelector) {
this.keySelector = keySelector;
}
bool IEqualityComparer<T>.Equals(T x, T y) {
return Equals(keySelector(x), keySelector(y));
}
int IEqualityComparer<T>.GetHashCode(T obj) {
return keySelector(obj).GetHashCode();
}
}
}

Categories