I have a list of strings like
A01,B01 ,A02, B12, C15, A12, ...
I want to unflatten the list into List of lists or dicitionary of lists such that
all strings starting with the same letter are group together (using linq)
A -> A01 , A02 , Al2
B -> B01 , B12
C -> C15
or
A -> 01 , 02 , l2
B -> 01 , 12
C -> 15
For now I just iterate the list using for loop and add the values to the approp list from dictionary.
(may not be right!)
Dictionary<string, List<string>> dict = new Dictionary<string, List<string>>();
foreach( string str in stringList)
{
string key = str.Substring(0,1);
if (!dict.ContainsKey(key)){
dict[key] = new List<string>();
}
dict[key].Add(str);
}
Edit :
Oh sorry i forgot to add this ,
I have a list of Category objs , and these are Category names.
I need to retrieve something like Dictionary<string, List<Category>> , going forward i want to bind this to a nested list . (asp.net/ mvc )
Is there a better way to do the same using Linq?
It sounds like you want a Lookup, via the ToLookup extension method:
var lookup = stringList.ToLookup(x => x.Substring(0, 1));
A lookup will let you do pretty much anything you could do with the dictionary, but it's immutable after construction. Oh, and if you ask for a missing key it will give you an empty sequence instead of an error, which can be very helpful.
Coming from the chat room, try this. I know it's not the most elegant solution, there is probably better.
List<string> listOfStrings = {"A01", "B01", "A02", "B12", "C15", "A12"}.ToList();
var res = listOfStrings.Select(p => p.Substring(0, 1)).Distinct().ToList().Select(p =>
new {
Key = p,
Values = listOfStrings.Where(c => c.Substring(0, 1) == p)
}).ToList();
foreach (object el_loopVariable in res) {
el = el_loopVariable;
foreach (object x_loopVariable in el.Values) {
x = x_loopVariable;
Console.WriteLine("Key: " + el.Key + " ; Value: " + x);
}
}
Console.Read();
Gives the following output:
If you want to use dictionary, you may want this
List<String> strList = new List<String>();
strList.Add("Axxxx");
strList.Add("Byyyy");
strList.Add("Czzzz");
Dictionary<String, String> dicList = strList.ToDictionary(x => x.Substring(0, 1));
Console.WriteLine(dicList["A"]);
Related
I have 2 lists below:
// From Below List I want to retrieve it Text of each plan like: foreach(var plan in AvailablePlanNames) and then use plan.Text property.
private IList<IWebElement> AvailablePlanNames =>
_webDriver.FindElementsWithWait(By.XPath("//div[#class='asc-checkbox-group']"));
// From Below list I am going to pull 2 properties like:
foreach(var planDetail in PlanDetails), fetch:
planDetail.GetAttribute("id") and planDetail.GetAttribute("checked")
private IList<IWebElement> PlansDetails => _webDriver.FindElementsWithWait(By.XPath("//div[#class='asc-checkbox-group']/input"));
So first list has: ["Plan A", "Plan B", "Plan C"]
Second list can be: [[Plan A ID , true], [Plan B ID, false], [Plan C ID, null]]
I am trying to make a single list of it like Tuple which will have:
Tuple((Plan A, Plan A ID, true), (Plan B, Plan B ID, false), (Plan C, Plan C ID, null))
I searched several posts and tried multiple solutions but did not get it working.
public IList<string> GetAvailablePlans()
{
var list = new List<(string Text, string, string)>();
foreach (var planName in AvailablePlanNames)
{
foreach (var planDetail in PlansDetails)
{
list.Add((planName.Text,
planDetail.GetAttribute("id"),
planDetail.GetAttribute("checked")));
}
}
return (IList<string>)list;
}
Something like this? This code assumes that PlansDetails[i] is corresponding to the AvailablePlanNames[i] for specified i. If this is not true, you will also need to find corresponding data in PlansDetails for each AvailablePlanNames[i].
The code also use correct return value (list of tuples instead of list of strings).
public List<(string Text, string, bool)> GetAvailablePlans()
{
var list = new List<(string Text, string, string)>();
for (var i; i<AvailablePlanNames.Length;i++)
{
list.Add((AvailablePlanNames[i].Text,
PlansDetails[i].GetAttribute("id"),
PlansDetails[i].GetAttribute("checked")));
}
return list;
}
Instead of looping, you can use LINQ's Zip to combine items from two IEnumerable<T>s. :
var results=AvailablePlanNames
.Zip(PlanDetails)
.Select((first,second)=>
( Text: first.Text,
Id: second.GetAttribute("id"),
Check:second.GetAttrbute("checked")
))
.ToList();
I have dictionary code as follows:
int entry=0;
string[] numbers ={"123","123","123","456","123"};
Dictionary<string, List<string>> dictionary = new Dictionary<string, List<string>>();
foreach (string number in numbers)
{
if (dictionary.ContainsKey("ABC"))
{
}
else if (!dictionary.ContainsKey("ABC") && entry==0)
{
dictionary.Add("ABC", new List<string>());
dictionary["ABC"].Add(number);
entry = 1;
}
else if (!dictionary.ContainsKey("ABC") && entry == 1)
{
dictionary["ABC"].Add(number);
}
}
foreach(KeyValuePair<string,string> kvp in dictionary)
{
Console.WriteLine("Key={0},Value = {1}", kvp.Key,kvp.Value);
}
Console.ReadKey();
I want output like as follows Key="ABC",Value="123,456" i.e. I need to print all the dictionary values only once without repeat. In above string array 123 came 4 times.But I want to print that only one time and need 456 also and also joint that values with comma(",").So I need output like Key="ABC",Value="123,456". Please share your ideas. Thanks in advance.
I need to print all the dictionary values only once without repeat.
Use Distinct method.
joint that values with comma(",")
Use String.Join method.
foreach(var kvp in dictionary)
{
Console.WriteLine("Key={0},Value = {1}",
kvp.Key,
String.Join(", " kvp.Value.Distinct())
);
}
You can try like this:
foreach(var value in dictionary.Values.Distinct())
{
names = String.Join(", ", value);
}
The following for loop variable is incorrect, I think:
foreach(KeyValuePair<string,string> kvp in dictionary)
{
Console.WriteLine("Key={0},Value = {1}", kvp.Key,kvp.Value);
}
It should read: then note the difference in the writeline
foreach(KeyValuePair<string,List<string>> kvp in dictionary)
{
Console.WriteLine("Key={0},Value = {1}", kvp.Key,string.Join(",", kvp.Value.ToArray()));
}
You can use this simple linq to flatten and join all your dictionary contents:
var result = string.Join(" - ", dic.Select(kvp => string.Format("Key={0}, Values={1}", kvp.Key, string.Join(", ",kvp.Value.Distinct()))));
Instead of using this array :
string[] numbers = {"123","123","123","456","123"};
Add another array as :
string[] uniqueNumbers = numbers.Distinct().ToArray();
and now use this array with unique values to add to the dictionary.
I have a list function on a console application on C#. This list function has different items where they look something like 'matt,5' 'matt,7' 'jack,4' 'jack,8' etc...
I want to be able to combine all of the names where I only see their name written once but the number after them are averaged out so it would be like 'jack,5+7/2' which would then display as 'jack,6'.
So far I have this...
currentFileReader = new StreamReader(file);
List<string> AverageList = new List<string>();
while (!currentFileReader.EndOfStream)
{
string text = currentFileReader.ReadLine();
AverageList.Add(text.ToString());
}
AverageList.GroupBy(n => n).Any(c => c.Count() > 1);
Not really sure where to go from here.
What you need is to Split your each string item on , and then group by first element of the returned array and average second element of the array (after parsing it to int) something like:
List<string> AverageList = new List<string> { "matt,5", "matt,7", "jack,4", "jack,8" };
var query = AverageList.Select(s => s.Split(','))
.GroupBy(sp => sp[0])
.Select(grp =>
new
{
Name = grp.Key,
Avg = grp.Average(t=> int.Parse(t[1])),
});
foreach (var item in query)
{
Console.WriteLine("Name: {0}, Avg: {1}", item.Name, item.Avg);
}
and it will give you:
Name: matt, Avg: 6
Name: jack, Avg: 6
But, a better option would be to use a class with Name and Score properties instead of comma separated string values.
(The code above doesn't check for invalid input values).
Firstly you will want to populate your unformatted data into a List, as you can see I called it rawScores. You could then Split each line by the comma delimiting them. You can then check to see if an existing person is in your Dictionary and add his score to it, or if not create a new person.
After that you would simply have to generate the Average of the List.
Hope this helps!
var scores = new Dictionary<string, List<int>>();
var rawScores = new List<string>();
rawScores.ForEach(raw =>
{
var split = raw.Split(',');
if (scores.Keys.All(a => a != split[0]))
{
scores.Add(split[0], new List<int> {Convert.ToInt32(split[1])});
}
else
{
var existing = scores.FirstOrDefault(f => f.Key == split[0]);
existing.Value.Add(Convert.ToInt32(split[1]));
}
});
I want to build a combobox with key->postal and value->city to use as filter for my accomodations.
To limit the number of items in the list I only use the postals I have used when filling up the table tblAccomodations.
For now I do not use a relational table with postals and city's although I'm thinking about an update later on.
Here I build my dictionary:
public static Dictionary<int, string> getPostals()
{
Dictionary<int, string> oPostals = new Dictionary<int, string>();
using (DBReservationDataContext oReservation = new DBReservationDataContext())
{
var oAllPostals = (from oAccomodation in oReservation.tblAccomodations
orderby oAccomodation.Name ascending
select oAccomodation);
foreach (tblAccomodation item in oAllPostals)
{
oPostals.Add(int.Parse(item.Postal), item.City);
}
}
return oPostals;
}
As expected I got an error: some Accomodations are located in the same city, so there are double values for the key. So how can I get a list of unique cities and postals (as key)?
I tried to use
select oAccomodation.Postal.Distinct()
but that didn't work either.
UPDATE: I have found the main problem. There are multiple cities with the same postal ("Subcity"). So I'm gonna filter on "City" and not on "Postal".
I think your looking for 'Distinct'. Gather your list of all postals and then return myPostals.Distinct().
Hope than helps.
change
foreach (tblAccomodation item in oAllPostals)
{
oPostals.Add(int.Parse(item.Postal), item.City);
}
to
foreach (tblAccomodation item in oAllPostals.Distinct(x=>x..Postal)
{
if(!oPostals.ContainsKey(int.Parse(item.Postal)))
oPostals.Add(int.Parse(item.Postal), item.City);
}
BTW, if you have multiple cities in one postal (I am not sure if it is possible in your domain), which one you want to see?
If any of cities will do, then it is easy to just get the first one per postal:
var oAllPostals = oReservation.tblAccomodations
.OrderBy(x=>x.Name)
.ToLookup(x=>x.Postal, x=>x.City)
.ToDictionary(x=>x.Key, x.First());
In the same example if you do .ToList() or even .Distinct().ToList() instead of .First() you will have all of cities in the dictionary of Dictionary<Postal, List<City>>.
Assuming the combination of postal + city is unique you could do the following:
public static Dictionary<int, string> getPostals()
{
Dictionary<int, string> oPostals = new Dictionary<int, string>();
using (DBReservationDataContext oReservation = new DBReservationDataContext())
{
var oAllPostals = (from oAccomodation in oReservation.tblAccomodations
orderby oAccomodation.Name ascending
select oAccomodation);
foreach (tblAccomodation item in oAllPostals)
{
oPostals.Add((item.Postal + item.City).GetHashCode(), item.Postal + " " + item.City);
}
}
return oPostals;
}
Edit:
If you want to use the selected value from the drop box then you can use the following:
public static Dictionary<int, Tuple<string, string>> getPostals()
{
Dictionary<int, string> oPostals = new Dictionary<int, string>();
using (DBReservationDataContext oReservation = new DBReservationDataContext())
{
var oAllPostals = (from oAccomodation in oReservation.tblAccomodations
orderby oAccomodation.Name ascending
select oAccomodation);
foreach (tblAccomodation item in oAllPostals)
{
oPostals.Add((item.Postal + item.City).GetHashCode(), new Tuple<string, string>(item.Postal, item.City));
}
}
return oPostals;
}
The way you bind the following depends on whether you're using asp.net, winforms etc. Here's an example for winforms.
Using .containkey will exclude [1 (postal key) to n (cities relation)]. i.e since Key already exists next city (with the same postal key ) will not get into your dictionary.
However, if you want to map your postal to list of cities, you can represent a dictionary that can contain a collection of values like the following:
Dictionary < String[Postal]> , List < Cities>>
This way you'll have a dictionary that can have multiple values.
Is there a better way to code the Where this:
IDictionary<string, string> p = new Dictionary<string, string>();
p.Add("Apple", "1");
p.Add("Orange", "2");
p.Add("Pear", "3");
p.Add("Grape", "4");
p.Add("Pineapple", "5");
//This is a unique list
var retVal = p.Where(k => k.Key.Contains("Apple") || k.Key.Contains("Pear") || k.Key.Contains("Grape"));
Some History Below
I have a dictionary of strings like the following:
IDictionary<string,string>
The contents look like this:
Apple,1
Orange,2
Pear,3
Grape,4
...many more
How do i return only a few items from my dictionary like so
if (true)
{
//return only 3 apple,pear&grape items out of the dozens in the list into a new variable
}
You can just take the first 3 items...
theDictionary.Take(3);
Or filter and take specific items...
string[] itemsIWant = { "Apple", "Pear", "Grape" };
theDictionary.Where(o => itemsIWant.Contains(o.Key));
Or sort randomly and take 3...
Random r = new Random();
theDictionary.OrderBy(o => r.Next()).Take(3);
check out
LINQ query to return a Dictionary<string, string>
That will really depend on the kind of filtering you want to achieve. But you can achieve it through Linq.
If you just want to get the first 3 items, you can do it like this:
theDictionary.Take(3);
If you want to get the first 3 items that begin with 'G', the you will do this:
theDictionary.Where(kv => kv.Key.StartsWith("G")).Take(3);
If you want to get the first 3 items that begin with 'G' regardless the casing, the you will do this:
theDictionary.Where(kv => kv.Key.ToLower().StartsWith("g")).Take(3);
Last, but not least, if you want to get 3 items randomly, you will do this:
Random rand = new Random();
theDictionary.OrderBy(kv => rand.Next()).Take(3);