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();
Related
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);
I'm using Entity Framework 6.3 and LINQ to query from a table and populate the DTO object below:
public class MyObject
{
public string Name { get; set; }
public List<int> Number { get; set; }
public MyClass()
{
Number = new List<int>();
}
}
In this particular table (legacy project), the Name can have multiple Numbers, hence my need to use it as a list.
The table looks like this:
Name Number
abc 15371
abc 15079
abc 15371
abc 30392
xyz 30373
xyz 13141
My code, for now, is as follow:
List<MyObjetct> myObject = new List<MyObjetct>();
myObject = contex.TableName.Where(x => listOfNames.Contains(x.Name))
.Select( y => new MyObject()
{
name = y.name
number = *populate the list of numbers*
});
I was hoping to populate a list of MyObjects with names and a list o numbers.
Try this:
myObject = contex.TableName.GroupBy(t => t.Name)
.Where(g => listOfNames.Contains(g.Key))
.Select(g => new MyObject()
{
name = g.Key,
number = g.Select(n => n.Number).ToList()
}).ToList();
Output:
Name / Numbers
abc / 15371,15079,15371,30392
xyz / 30373,13141
I noticed your data sample has repeat name/number combinations. Not sure if it's just a typo or if you need to account for that.
Let's say we have the List<Item> data, and the Item class is defined as below:
public class Item
{
public double Station { get; set; }
public double Area { get; set; }
...
// other properties present in the class
}
Now how can we create a new List<T> out of List<Item>, so that T is anonymous type having only double Station and double Area as its properties.
I know how to extract all of the station and area values into a List<double> data2 but what I want above is different.
var data2 = data
.SelectMany(x => new[] { x.Station, x.Area })
.ToList();
I think what you need is...
var data2 = data
.Select(x => new { x.Station, x.Area })
.ToList();
You don't need SelectMany here, usually it is used to flatten the hierarchy.
I have a list of items with multiple columns and would like to group them by some fields depending on a boolean:
I have the following class:
public class Item
{
public string Group { get; set; }
public string Person { get; set; }
public string Currency { get; set; }
public string Country { get; set; }
public string County { get; set; }
public string OtherAdd { get; set; }
public string Income { get; set; }
}
which is part of a List:
var results = items.ToList(); //items is IEnumerable<Item>
if int type = 1, then I want to group by more elements:
results = results
.GroupBy(e => new { e.Group, e.Person, e.Branch, e.Currency, e.Country, e.County, e.OtherAdd})
.Select(g => new Item
{
Group = g.Key.Group,
Person = g.Key.Person,
Currency = g.Key.Currency,
Currency = g.Key.Country,
Currency = g.Key.County,
Currency = g.Key.OtherAdd,
Income = g.Sum(p => double.Parse(p.Income, System.Globalization.CultureInfo.InvariantCulture)).ToString("0.00", System.Globalization.CultureInfo.InvariantCulture)
})
.ToList();
if int type = 2, then I want to group by fewer elements (e.g. because OtherAdd would be an empty String):
results = results
.GroupBy(e => new { e.Group, e.Person, e.Branch, e.Currency})
.Select(g => new Item
{
Group = g.Key.Group,
Person = g.Key.Person,
Currency = g.Key.Currency,
Income = g.Sum(p => double.Parse(p.Income, System.Globalization.CultureInfo.InvariantCulture)).ToString("0.00", System.Globalization.CultureInfo.InvariantCulture)
})
.ToList();
etc.
Is there a way for me to change the GroupBy key depending on my integer type without repeating the code?
Well, you could use the old SQL trick, conditional values:
.GroupBy(e => new { e.Group, Person = (e.Type == 1 ? e.Person : Guid.NewGuid().ToString()), ... }
While this will still include the columns in the group by, all the items will have unique keys, so it doesn't quite matter. Sadly, I don't think there's a way around generating the unique keys, unlike in SQL (where you could just use NULL).
A better way might be to implement your own grouping class, instead of using an anonymous type. You could then use your own equality and hashing semantics, to make sure whether you include all the fields or not. However, that is arguably going to be more work than just having the similar code repeated.
Or, you might want to revise your whole design. It doesn't sound like what you're trying to do makes much sense - it's already quite suspicious that you're using the same type for two different things, and using strings for all the fields doesn't help either. Maybe you could try a different object design?
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();