Finding differences between 3 complicated dictionaries in C# - c#

I am having trouble juggling 3 dictionaries to find differences such as missing entries and also property values.
The dictionaries key and values are these objects:
public class Car
{
public string CarBrand{ get; set; }
public string RentingCompany{ get; set; }
public string CompanyPhone{ get; set; }
public ComplexObj MyObj { get; set; } //This is a deserialized XML
}
public class Drivers
{
public string DriverID{ get; set; } // Unique
public string Name{ get; set; }
public string LastName{ get; set; }
}
public Dictionary<Drivers, List<Car>> List1 = new Dictionary<Drivers, List<Car>>();
public Dictionary<Drivers, List<Car>> List2 = new Dictionary<Drivers, List<Car>>();
public Dictionary<Drivers, List<Car>> List3 = new Dictionary<Drivers, List<Car>>();
I need to search in List1.Values all the CarBrands that are not in list2 or List3 and the save the entire KeyValue pair Driver and Value into a new dictionary.
I would gladly accept any guidance on what would be an optimal way to approach this.
Thanks

This approach is going to be much much faster than comparing every car brand in list1 with every car brand in list2 and list3. The approach shown in the other answer has high computational complexity, it would scale badly for large amounts of data.
I haven't really tested it, but I think it is correct.
// only the distinct car brands throughout list2 and list3 remain in this hash set
HashSet<string> carBrandsInlist2and3 = new(
List2.Values
.Concat(List3.Values)
.SelectMany(cars => cars.Select(car => car.CarBrand)));
// using a hashset for IsSubsetOf below is going to be much quicker than
// using a 'simple' IEnumerable because set operations have optimizations in place in case
// both sets are of the same type and have the same IEqualityComparer
HashSet<string> tempHashset = new();
Dictionary<Drivers, List<Car>> newDictionary = new(
List1
.Where(keyValuePair =>
{
// fill the temporary hashset with car brands of this kvp
tempHashset.Clear();
tempHashset.UnionWith(keyValuePair.Value.Select(car => car.CarBrand));
// if tempHashset is not a subset of carBrandsInlist2and3
// then in this keyValuePair there is a car brand that does not exist in list2 or list3
return !tempHashset.IsSubsetOf(carBrandsInlist2and3);
}));

var brands2 = List2.Values.SelectMany(v => v.Select(c => c.CarBrand));
var brands3 = List3.Values.SelectMany(v => v.Select(c => c.CarBrand));
var allBrands = brands2.Concat(brands3);
var keyValuePairsToSave = List1
.Where(pair => !pair.Value.Any(car => allBrands.Any(brand => brand == car.CarBrand)))
// or
//.Where(pair => !allBrands.Any(brand => pair.Value.Any(c => c.CarBrand == brand)))
.ToArray();
//or
//.ToDictionary(pair => pair.Key, pair => pair.Value);

Related

convert Dictionary<string, myClass> to List<double[]> using c# linq

I have a dictionary of <string,Rate>
where Rate is defined as:
class Rate
{
public string Name { get; set;}
public int Tier{ get; set;}
public double Value { get; set;}
public Rate(string name,int tr,double val)
{ Name = name; Tier = tr; Value = val;}
}
I wish to convert this dictionary to List where the List is broken up (grouped by) Tier from Rate above and the double array holds the Values from Rate as well.
Rate rate1 = new Rate("One",1,1.1);
Rate rate2 = new Rate("Two",1,2.2);
Rate rate3 = new Rate("Three",2,3.3);
so if..
Dictionary<string,Rate> rates = new Dictionary<string,Rate> { {rate1.Name,rate1},{rate2.Name,rate2},{rate3.Name,rate3}};
List<double[]> myList = (linq result from Rates separated list of arrays by tier)
then myList[0] would countain 1.1 and 2.2, and myList[1] would countain 3.3.
Answers were good but I need to modify question to provide the list sorted by the tier.
List<double[]> list = rates
.GroupBy(pair => pair.Value.Class, pair => pair.Value.Value)
.Select(doubles => doubles.Select(d => d).ToArray())
.ToList();
List<double[]> myList = rates.GroupBy(p => p.Value.Tier)
.Select(g => g.Select(p => p.Value.Value).ToArray())
.ToList();

How to convert a list of objects to two level dictionary?

I have a list of customer objects (e.x: List customers)
public class Customer
{
public int ID { get; set; }
public string City { get; set; }
public bool DidLive { get; set; }
}
What I need to do is to convert this "customers" collection into a dictionary like follows,
"Dictionary<int, Dictionary<string, bool>>"
Where the outer key is the "ID" and the inner key is the "City".
Can this be done using "GroupBy" or "ToDictionary" extension methods of "IEnumerable<T>"?
I'm assuming here that you have multiple Customer objects with the same Id but with different Cities (if this isn't the case and the inner dictionary will always contain one item, go with #oleksii's answer).
var result = customers.GroupBy(c => c.Id)
.ToDictionary(group => group.Key,
group => group.ToDictionary(c => c.City,
c => c.DidLive));
Of course, this will throw an exception if there multiple customers with identical Ids and Cities.
That's a place to start
var data = new List<Customer>();
data.ToDictionary(item => item.ID,
item => new Dictionary<string, bool>
{
{item.City,item.DidLive}
});

Merging two classes into a Dictionary using LINQ

I have two classes which share two common attributes, Id and Information.
public class Foo
{
public Guid Id { get; set; }
public string Information { get; set; }
...
}
public class Bar
{
public Guid Id { get; set; }
public string Information { get; set; }
...
}
Using LINQ, how can I take a populated list of Foo objects and a populated list of Bar objects:
var list1 = new List<Foo>();
var list2 = new List<Bar>();
and merge the Id and Information of each into a single dictionary:
var finalList = new Dictionary<Guid, string>();
Thank you in advance.
Sounds like you could do:
// Project both lists (lazily) to a common anonymous type
var anon1 = list1.Select(foo => new { foo.Id, foo.Information });
var anon2 = list2.Select(bar => new { bar.Id, bar.Information });
var map = anon1.Concat(anon2).ToDictionary(x => x.Id, x => x.Information);
(You could do all of this in one statement, but I think it's clearer this way.)
var finalList = list1.ToDictionary(x => x.Id, y => y.Information)
.Union(list2.ToDictionary(x => x.Id, y => y.Information))
.ToDictionary(x => x.Key, y => y.Value);
Make sure the ID's are unique. If not they will be overwritten by the first dictionary.
EDIT: added .ToDictionary(x => x.Key, y => y.Value);

how to remove objects from list by multiple variables using linq

I want to remove objects from a list using linq,
for example :
public class Item
{
public string number;
public string supplier;
public string place;
}
Now i want to remove all of the items with the same number and supplier that appear more then once.
Thanks
This would be slightly tedious to do in-place, but if you don't mind creating a new list, you can use LINQ.
The easiest way to specify your definition of item-equality is to project to an instance of an anonymous-type - the C# compiler adds a sensible equality-implementation on your behalf:
List<Item> items = ...
// If you mean remove any of the 'duplicates' arbitrarily.
List<Item> filteredItems = items.GroupBy(item => new { item.number, item.supplier })
.Select(group => group.First())
.ToList();
// If you mean remove -all- of the items that have at least one 'duplicate'.
List<Item> filteredItems = items.GroupBy(item => new { item.number, item.supplier })
.Where(group => group.Count() == 1)
.Select(group => group.Single())
.ToList();
If my first guess was correct, you can also consider writing an IEqualityComparer<Item> and then using the Distinct operator:
IEqualityComparer<Item> equalityComparer = new NumberAndSupplierComparer();
List<Item> filteredItems = items.Distinct(equalityComparer).ToList();
Btw, it's not conventional for types to expose public fields (use properties) or for public members to have camel-case names (use pascal-case). This would be more idiomatic:
public class Item
{
public string Number { get; set; }
public string Supplier { get; set; }
public string Place { get; set; }
}

Easiest way to merge two List<T>s

I've got two List<Name>s:
public class Name
{
public string NameText {get;set;}
public Gender Gender { get; set; }
}
public class Gender
{
public decimal MaleFrequency { get; set; }
public decimal MaleCumulativeFrequency { get; set; }
public decimal FemaleCumulativeFrequency { get; set; }
public decimal FemaleFrequency { get; set; }
}
If the NameText property matches, I'd like to take the FemaleFrequency and FemaleCumulativeFrequency from the list of female Names and the MaleFrequency and MaleCumulativeFrequency values from the list of male Names and create one list of Names with all four properties populated.
What's the easiest way to go about this in C# using .Net 3.5?
Are you attempting to sum each of the values when you merge the lists? If so, try something like this:
List<Name> list1 = new List<Name>();
List<Name> list2 = new List<Name>();
List<Name> result = list1.Union(list2).GroupBy(x => x.NameText).Select(x => new
Name
{
NameText = x.Key,
Gender = new Gender
{
FemaleCumulativeFrequency = x.Sum(y => y.Gender.FemaleCumulativeFrequency),
FemaleFrequency = x.Sum(y => y.Gender.FemaleFrequency),
MaleCumulativeFrequency = x.Sum(y => y.Gender.MaleCumulativeFrequency),
MaleFrequency = x.Sum(y => y.Gender.MaleFrequency)
}
}).ToList();
What this does is the following:
Unions the lists, creating an IEnumerable<Name> that contains the contents of both lists.
Groups the lists by the NameText property, so if there are duplicate Names with the same NameText, they'll show up in the same group.
Selects a set of new Name objects, with each grouped Name's properties summed... you can also use Average if that makes more sense.
Converts the entire query to a List<Name> by calling the "ToList()" method.
Edit: Or, as you've said below, you simply want to merge the two lists... do this:
List<Name> allNames = femaleNames.Union(maleNames).ToList();
This looks a lot like the census name frequency data, right? Gender is a bit of a misnomer for the class you have it's more like "FrequencyData".
In effect you want a Dictionary so you can lookup any name and get the four values for it. You could simply take the males and do ToDictionary(...) on it and then iterate over the females, if the name exists in the dictionary, replace the female probabilities on it, if it doesn't exist, create a new dictionary entry.
My own approach to this same data was to create a Table in a database with all four values attached.
Here's some code for your scenario ...
Dictionary<string, Gender> result;
result = males.ToDictionary(x => x.NameText, x => x.Gender);
foreach (var female in females)
{
if (result.ContainsKey(female.NameText))
{
result[female.NameText].FemaleCumulativeFrequency = female.Gender.FemaleCumulativeFrequency;
result[female.NameText].FemaleFrequency = female.Gender.FemaleFrequency;
}
else
result.Add(female.NameText, female.Gender);
}
I think this could be what you want although I'm not sure if it handles the cumulative frequencies as you'd expect:
var mergedNameList = maleNames
.Concat(femaleNames)
.GroupBy(n => n.NameText)
.Select(nameGroup => new Name
{
NameText = nameGroup.Key,
Gender = new Gender
{
MaleFrequency = nameGroup.Sum(n => n.Gender.MaleFrequency),
MaleCumulativeFrequency = nameGroup.Sum(n => n.Gender.MaleCumulativeFrequency),
FemaleFrequency = nameGroup.Sum(n => n.Gender.FemaleFrequency),
FemaleCumulativeFrequency = nameGroup.Sum(n => n.Gender.FemaleCumulativeFrequency)
}
}.ToList();

Categories