Store value in dictionary or retrieve from concatened key - c#

I have a dictionary that aggregates the same items and sum a specific value from:
var test = list.GroupBy(x => new { ID=x.ItemID, Uti=x.UtilityName })
.ToDictionary(x => x.Key,
x => x.Sum(t => Convert.ToDecimal(t.EnergyConsumptionQt)
));
This returns a dictionary with a key value that is a concatenation of string ID and string Uti.
I would like to either:
Create a Dictionary<string, decimal> in which the key is the combination / concatenation ItemID+UtilityName (ID + Uti), or a way to get the values from the test variable above, as the current one I do not know how to specify the value I want from as everything that I try returns: cannot convert 'X' to ''.

Do it this way:
var test = list.GroupBy(x => new { ID=x.ItemID, Uti=x.UtilityName })
.ToDictionary(x => x.Key.ID.ToString()+x.Key.Uti,
x => x.Sum(t => Convert.ToDecimal(t.EnergyConsumptionQt)
));

The problem with your initial approach is that the annonymous type is a reference type so the dictionary uses the reference as a key (rather than the values you want to be combined).
As such if you subsequently try to accesss an element with for example:
var elem = test[new {ID=1, Uti="myUtiName"}]
then you actually create a NEW object which does not exist in test so you would get a KeyNotFoundException thrown.
If you use a value type for your dictionary key instead then the dictionary will behave as you are expecting. Perhaps have a struct - something like this maybe:
struct DictKey
{
public string UtilityName { get; set; }
public int Id { get; set; }
}
then your test variable can be initialised with:
var test = list
.GroupBy(x => new DictKey { Id=x.ItemID, UtilityName=x.UtilityName })
.ToDictionary(x => x.Key,
x => x.Sum(t => Convert.ToDecimal(t.EnergyConsumptionQt)));
and you can access it's elements with something like:
var elem = test[new DictKey{Id=1, UtilityName="myUtiName"}]

Related

Read from CSV and convert to dictionary

I have a CSV file having the below values. I would like to read CSV and create a dictionary in C#. Dictionary Key = Name and Value in List<Product> such as Dictionary<string, List<Product>>
Note: In CSV, the Name can be duplicated (laptop). So I have to get a distinct Name first to get the key for the dictionary and build the Product list.
For example, in below sample data dictionary will have two key
laptop (with two values)
mobile (with one value)
I have found the below code but not sure how to build a dictionary based on the above requirements.
var dict = File.ReadLines("textwords0.csv").Select(line => line.Split(',')).ToDictionary(line => line[0], line => line[1]);
Name Code Price
laptop , 9999, 100.00
Mobile , 7777, 500.00
laptop , 9999, 100.00
public class Product
{
string Name {get;set;}
string code {get;set;}
string Price {get;set;}
}
As the dictionary key is non-duplicate, you need a .GroupBy() to group by Name first.
.GroupBy() - Group by Name, and return an IEnumerable of anonymous type with { string Name, List<Product> Products }.
.ToDictionary() - Convert previous IEnumerable data to key-value pair.
var dict = File.ReadLines("textwords0.csv")
.Select(line => line.Split(','))
.GroupBy(
x => x[0],
(k, x) => new
{
Name = k,
Products = x.Select(y => new Product
{
Name = y[0],
code = y[1],
Price = y[2]
})
.ToList()
})
.ToDictionary(x => x.Name, x => x.Products);
Sample .NET Fiddle Demo

Dictionary cannot convert from '<anonymous type>'

Here's my code:
var groupedDataDictionary = products
.Where(p => p.ProductType == ProductType.ValueType)
.GroupBy(p => (p.OfficeDebitId, p.OfficeDebitDate, p.PaymentMethod))
.ToDictionary(x => x.Key, x => x.Sum(p => p.Amount));
var result = products.Select(p => new ResponseDto()
{
customer_id = p.CustomerId,
office_debit_date = p.OfficeDebitDate.Value.ToString(),
office_debit_id = p.OfficeDebitId.ToString(),
office_debit_total = groupedDataDictionary[new { p.OfficeDebitId, p.OfficeDebitDate, p.PaymentMethod }].Sum().ToString(), // this line causes error
payment_method = p.PaymentMethod.Value.ToString(),
}).ToList();
When assiging office_debit_total on that line it says:
Error CS1503 Argument 1: cannot convert from '<anonymous type: string
OfficeDebiId, System.DateTime? OfficeDebitDate,
Enumerations.PaymentMethod? PaymentMethod>' to '(string
OfficeDebitId, System.DateTime? OfficeDebitDate,
Enumerations.PaymentMethod? PaymentMethod)'
You should use either Tuples or Anonymous Types, but do not combine them. If you want to use Tuples for keys then you should also use Tuples to get dictionary values by keys:
var result = products.Select(p => new ResponseDto()
{
customer_id = p.CustomerId,
office_debit_date = p.OfficeDebitDate.Value.ToString(),
office_debit_id = p.OfficeDebitId.ToString(),
// Here you should use Tuple to get value by Key.
office_debit_total = groupedDataDictionary[(p.OfficeDebitId, p.OfficeDebitDate, p.PaymentMethod)].ToString(),
payment_method = p.PaymentMethod.Value.ToString(),
}).ToList();
Also note that I deleted invocation of the method Sum(), because dictionary groupedDataDictionary already contains aggregated data: x.Sum(p => p.Amount).

LinQ ofType in Value

I have the following Dictionary:
public Dictionary<string,object> Items;
Now I need to get all Items where the Value of the Dictionary-item is from a specific type. (e.g. "int")
var intValues = Items.OfType<KeyValuePair<string,int>> simply does not work.
Code without LinQ would be something like:
var intValues=new Dictionary<string,int>()
foreach (var oldVal in Items) {
if (oldVal.Value is int) {
intValues.add(oldVal.Key, oldVal.Value);
}
}
(Update) my example should show the basic idea. But if possible I would avoid to create a new Dictionary as a result.
The direct translation of your foreach would be the following in LINQ:
var intValues = Items.Where(item => item.Value is int)
.ToDictionary(item => item.Key, item => (int)item.Value);
So basically, you filter first for where the item.Value is an int, and then you create a dictionary from it using ToDictionary where you cast those values to int to make sure that the resulting dictionary is a Dictionary<string, int>. Since we filtered non-integers already, this type cast will always succeed.
You can use the is operator on the Value property:
var intValues = Items.Where(x => x.Value is int);
If you want an actual Dictionary<string,int> at the end just add:
.ToDictionary(v=> v.Key, v=> (int)v.Value)
Try with this:
var intValue = Items
.Where(x => x.Value is int) // filter per Value is int
.ToDictionary(x => x.Key, x => (int)x.Value); // create a new dictionary converting Value to int
You can do
var result = Items.Where(x => x.Value is int)
.ToDictionary(x => x.Key, x => x.Value);

Convert List to Dictionary<string, string[]>

I have a simple custom object:
class CertQuestion
{
public string Field {get;set;}
public string Value {get;set;}
}
Subsequently I find myself with a List in some code. I'm trying to figure out how to format a list of CertQuestions into a corresponding Dictionary with similar Field names grouped together. For instance, given the following list:
List<CertQuestion> certQuestions = new List<CertQuestion>()
{
new CertQuestion("Key", "Value1"),
new CertQuestion("Key", "Value2"),
new CertQuestion("Key2", "Value"),
new CertQuestion("Key2", "Value2")
};
I would like to convert that (trying to use LINQ) into a Dictionary with two entries such as
{{"Key", "Value1, Value2"}, {"Key2", "Value, Value2"}}
Group the questions by field, then convert to dictionary by selecting key, then value. Value becomes the grouping's list.
certQuestions.GroupBy(c => c.Field)
.ToDictionary(k => k.Key, v => v.Select(f => f.Value).ToList())
Or for an array:
certQuestions.GroupBy(c => c.Field)
.ToDictionary(k => k.Key, v => v.Select(f => f.Value).ToArray())
Edit based on question in comment:
class CertTest
{
public string TestId {get;set;}
public List<CertQuestion> Questions {get;set;}
}
var certTests = new List<CertTest>();
You would use the SelectMany extension method. It is designed to aggregate a property list object that is in each element of the original list:
certTests.SelectMany(t => t.Questions)
.GroupBy(c => c.Field)
.ToDictionary(k => k.Key, v => v.Select(f => f.Value).ToList())
Your requirement was for a comma-separated list of values, that can be done like this:
var dict = certQuestions.GroupBy(c => c.Field)
.ToDictionary(k => k.Key, v => String.Join(", ", v.Select(x => x.Value)))
Live example: http://rextester.com/LXS58744
(You should consider whether what you actually want is the values to be an Array or List<string> - see other answers)

linq query to filter textvalue pair and remove duplicates by value

I'm trying to filter the results I'm getting by removing some of the items I have in my custom dictionary by their value. So if there are multiple items with the same value I would like to have only one sample of that pair.
This is the custom class I have where I'm storing the values:
public class ValuePair
{
public string Text { get; set; }
public string Value { get; set; }
}
Here is how I'm retrieving the values:
List<ValuePair> items = GetResults(db)
.AsEnumerable()
.Distinct()
.Select(v => new TextValuePair
{
Text = ToTitleCase(v.NameOfTown),
Value = v.NameOfTown
})
.ToList();
I would like to know how I can refresh the results and get only one sample of the items filtered by the value, not by the key.
How can I do that?
You can group by Value then take the first item of the grouped items.
List<ValuePair> items = GetResults(db)
.AsEnumerable()
.Distinct()
.Select(v => new TextValuePair
{
Text = ToTitleCase(v.NameOfTown),
Value = v.NameOfTown
})
.GroupBy(x => x.Value)
.Where(x => x.Key == "filter") // filter by Value (the prop name is Key)
.Select(x => x.First())
.ToList();
you can use this https://code.google.com/p/morelinq/ librarry it has an extension method called
DistictBy and then you can
List<ValuePair> items = GetResults(db)
.AsEnumerable()
.Select(v => new TextValuePair
{
Text = ToTitleCase(v.NameOfTown),
Value = v.NameOfTown
}).DisrinctBy(c=>c.Value)
.ToList();

Categories