Read from CSV and convert to dictionary - c#

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

Related

C# LINQ Query / Projection from FormCollection

I have a dynamic form which I cannot use MVC's binding with. When I post this FormCollection to my controller, simplified, I have the following data in the form collection:
public ActionResult Foo(FormCollection coll)
...
coll["Data0Key"] contains "category"
coll["Data0Value"] contains "123"
coll["Data1Key"] contains "location"
coll["Data1Value"] contains "21"
coll["Data7Key"] contains "area"
coll["Data7Value"] contains "test"
coll["SomethingElse"] contains "irrelevent"
.. I have an unknown number of these and would like to create key-value pairs from the seperate key and value objects in the collection
I have been attempting along the lines of;
var settings = coll.AllKeys
.Where(k => k.StartsWith("Data"))
.ToDictionary(k => k, k => coll[k]);
which gives me a dictionary of:
Data0Key, category
Data0Value, 123
Data1Key, location
Data1Value, 21
Data7Key, area
Data7Value, test
What I would really like to have is a collection of key value pairs structured like;
category, 123
location, 21
area, test
Is what I am trying to achieve possible at all, or do I need to find a different approach?
I think you want to only iterate over the "DataxKey" parts, then look up the values. Something like:
.Where(k => k.StartsWith("Data") && k.EndsWith("Key"))
.ToDictionary(k => coll[k], k => coll[k.Replace("Key", "Value")]);
This assumes that every "Data0Key" also has a "Data0Value" matching pair, otherwise it's going to dereference a key that doesn't exist from coll.
This can be done with a relatively straightforward LINQ query:
var data = new Dictionary<string,object> {
["Data0Key"] = "category"
, ["Data0Value"] = "123"
, ["Data1Key"] = "location"
, ["Data1Value"] = "21"
, ["Data7Key"] = "area"
, ["Data7Value"] = "test"
, ["SomethingElse"] = "irrelevent"
};
var kvp = data
.Where(p => p.Key.StartsWith("Data") && (p.Key.EndsWith("Key") || p.Key.EndsWith("Value")))
.Select(p => new {
Key = p.Key.Substring(0, p.Key.Length - (p.Key.EndsWith("Key") ? 3 : 5))
, IsKey = p.Key.EndsWith("Key")
, p.Value
})
.GroupBy(p => p.Key)
.Where(g => g.Count(p => p.IsKey) == 1 && g.Count(p => !p.IsKey) == 1)
.ToDictionary(g => (string)g.Single(p => p.IsKey).Value, g => g.Single(p => !p.IsKey).Value);
foreach (var p in kvp) {
Console.WriteLine("{0} - {1}", p.Key, p.Value);
}
Here is a line-by-line explanation of what is done:
First, irrelevant items are filtered out by ensuring that only "Data" prefixes are kept, and that the suffixes are "Key" or "Value"
Next, group key is extracted by removing "Key" or "Value" suffix; the Value is added to the list, along with IsKey flag indicating if an item was a key or a value
Items are grouped by the group key ("Data0", "Data7", etc.)
Each group is checked to contain exactly one key and one value; incomplete groups are discarded
Finally, groups are converted to a dictionary of key-value pairs.

Store value in dictionary or retrieve from concatened key

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"}]

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 returning rows matching all words in array

I need to build a query using linq to return the rows matching all words in an array:
Example array (splitKeywords):
{string[2]}
[0]: "RENAULT"
[1]: "CLIO"
KeywordSearch table:
public class KeywordSearch
{
// Primary properties
public int Id { get; set; }
public string Name { get; set; }
public Keyword Keyword { get; set; }
}
This table has the records:
Id: 1
Name: "RENAULT"
Keyword_Id: 3503
Id: 2
Name: "CLIO"
Keyword_Id: 3503
I want to get all the Keyword_Ids which match all the words in the array.
So far I have:
EDIT:
var keywordSearchQuery = _keywordSearchRepository.Query;
var keywordIds = from k in keywordSearchQuery
where splitKeywords.All(word => word.Equals((k.Name)))
select k.Keyword.Id;
But It's not working. Any ideas?
Thanks
First you need to combine all of the records with the same Keyword.Id into a single record. Do that with GroupBy.
Once you have them grouped, you can filter out the groups (KeywordIds), where all of the items (the individual records, the names), don't match at least one of the splitKeyWords.
Original - This checks that all Name values match a value in splitKeyWords
var results = keywordSearchQuery
.GroupBy(k => k.Keyword_Id)
.Where(g => g.All(k => splitKeyWords.Any(w => w.Equals(k.Name))))
.Select(g => g.Key);
Updated - This checks that all values in splitKeyWords match a Name.
var results = keywordSearchQuery
.GroupBy(k => k.Keyword_Id)
.Where(g => splitKeyWords.All(w => g.Any(k => w.Equals(k.Name))))
.Select(g => g.Key);
Note that if you have a Keyword.Id with names RENAULT, CLIO, and ASDF, it will match.
This will go through your array and find the matching key for each element, if this is what you're looking for?
var ids splitKeywords.Select(k => keywordSearchQuery.Single(q => q.Name == k).Id).ToArray();
You'd have to do a bit more validation if the Name wasn't unique or if you weren't sure you'd get a match.

LINQ using C# question

I have a List<T> that contains some user defined class data.
I want to find the unique instances of a combination of 2 data fields.
For example, if the entries contain the fields Name and Age, I want the unique cases of the Name and Age combination, e.g. Darren 32, should only be retrieved once, even if it is in the list multiple times.
Can this be achieved with LINQ?
Thanks.
You need to extract only these data fields and make them unique:
var result = list
.Select(x => new { Age = a.Age, Name = x.Name})
.Distinct();
This creates a IEnumerable of a anonymous type which contains a Age and Name property.
If you need to find the items behind the unique data, you need GroupBy. This will provide the list with the single items behind each group.
var result = list
.GroupBy(x => new { Age = a.Age, Name = x.Name});
foreach (var uniqueItem in result )
{
var age = uniqueItem.Key.Age;
var name = uniqueItem.Key.Name;
foreach (var item in uniqueItem)
{
//item is a single item which is part of the group
}
}
myList.Select(l => new { l.Name, l.Age })
.Distinct()
.ToDictionary(x => x.Name, x => x.Age)
You'll have to write your own equality comparer, and use Linq's Distinct function.
Have a look at the Distinct extension method
Easy:
var people = new List<Person>();
// code to populate people
var uniqueNameAges =
(from p in people
select new { p.Name, p.Age }).Distinct();
And then to a dictionary:
var dictionary =
uniqueNameAges
.ToDictionary(x => x.Name, x => x.Age);
Or to a lookup (very much like Dictionary<string, IEnumerable<int>> in this case):
var lookup =
uniqueNameAges
.ToLookup(x => x.Name, x => x.Age);
If you then have people named "John" with distinct ages then you could access them like so:
IEnumerable<int> ages = lookup["John"];

Categories