I have a Dictionary
Dictionary<string, List<Employee>> employees
that contains a list of employees. And I want to allow the user to display the list of employees in alphabetical order based on the state they are in.
Console.WriteLine($"If you would like to sort this list please enter one of the following choices..\n" +
$"'STATE', 'INCOME', 'ID', 'NAME', 'TAX' other wise enter any key.");
var sort = Console.ReadLine().ToUpper();
var employees = EmployeeRecord.employees;
List<Employee> sortedEmps = new List<Employee>();
if (sort.Contains("STATE"))
foreach (var list in employees.Values) {
var columnQuery = list.OrderBy(x => x.stateCode).ToList();
sortedEmps.AddRange(columnQuery);
}
}
//Print out the newly ordered list
foreach (Employee r in sortedEmps) {
Console.WriteLine($"ID: {r.iD} Name: {r.name} State: {r.stateCode} Income:{r.income} Tax Due: {r.taxDue}");
}
However, it still prints out the list without ordering it. How can I get it to order alphabetically by the state code?
Try sorting when you have all data merged.
if (sort.Contains("STATE")) {
foreach (var list in employees.Values) {
sortedEmps.AddRange(list);
}
sortedEmps = sortedEmps.OrderBy(x => x.stateCode).ToList();
}
Also you can shorten a little the code with SelectMany as #Robert Harvey suggested
if (sort.Contains("STATE")) {
sortedEmps = employees.Values
.SelectMany(x => x)
.ToList().OrderBy(o => o.stateCode).ToList();
}
Related
I'm trying to merge the results from two tables in to a list and then display it in a dictionary where the key is the domain and the value is a list of urls from both tables.
class Source1Entity { string Domain {get;} string PageUrl {get;} /* more properties */ }
class Source2Entity { string Domain {get;} string PageUrl {get;} /* more properties */ }
I've got this far:
Dictionary<string, List<string>> results =
from firstSource in context.Source1
join secondSource in context.Source2 on firstSource.Domain equals secondSource.Domain
group firstSource by firstSource.Domain into g
...
It's not entirely clear, but I suspect you actually want to treat these two tables as equivalents - so you can project to a common form and then use Concat, then call ToLookup:
var projectedSource1 = context.Source1.Select(x => new { x.Domain, x.PageUrl });
var projectedSource2 = context.Source2.Select(x => new { x.Domain, x.PageUrl });
var results = projectedSource1
.Concat(projectedSource2)
.ToLookup(x => x.Domain, x => x.PageUrl);
Then:
// For a particular domain - you'll get an empty sequence it the
// domain isn't represented
foreach (var url in results[domain])
or
foreach (var entry in results)
{
Console.WriteLine("Domain: {0}", entry.Key);
foreach (var url in entry)
{
Console.WriteLine(" Url: {0}", url);
}
}
While you could use a Dictionary for this, a Lookup is generally more suitable for single-key-multiple-value queries.
You need a GroupJoin.
Try this...
join secondSource in context.Source on firstSource.Domain equals secondSource.Domain into group_join
Suppose we have Hotels that have rooms, and each room has a room number, how can I display all of the room numbers starting from context.Hotels using Linq instead of foreach ?
Illustration:
var Rooms = context.Hotels.Select(e => e.Rooms);
foreach (var room in Rooms)
{
var list = room.Select(r => r.Number);
foreach(number in list)
{
Console.WriteLine(number);
}
}
Obviously I can directly display the numbers from the room entity but this isn't my actual code just to illustrate what I want to do.
How about simply selecting the room number, using SelectMany, as in:
var roomNumbers = context.Hotels.SelectMany(h => h.Rooms.Select(r => r.Number));
Or in case they need to be distinct numbers:
var roomNumbers = context.Hotels.SelectMany(h => h.Rooms.Select(r => r.Number)).Distinct();
What I want is better explained with code. I have this query:
var items = context.Items.GroupBy(g => new {g.Name, g.Model})
.Where(/*...*/)
.Select(i => new ItemModel{
Name=g.Key.Name,
SerialNumber = g.FirstOrDefault().SerialNumber //<-- here
});
Is there a better way to get the serial number or some other property that is not used in the key? The only way I could think of is to use FirstOrDefault.
Why not just include the serial number as part of the key via the anonymous type you're declaring:
var items = context.Items.GroupBy(g => new {g.Name, g.Model, g.SerialNumber })
.Where(/*...*/)
.Select(i => new ItemModel {
Name=g.Key.Name,
SerialNumber = g.FirstOrDefault().SerialNumber //<-- here
});
Or, alternatively, make your object the key:
var items = context.Items.Where(...).GroupBy(g => g)
.Select(i => new ItemModel {...});
Sometimes it can be easier to comprehend the query syntax (here, I've projected the Item object as part of the key):
var items = from i in context.Items
group i by new { Serial = g.Serialnumber, Item = g } into gi
where /* gi.Key.Item.GetType() == typeof(context.Items[0]) */
select new ItemModel {
Name = gi.Key.Name,
SerialNumber = gi.Key.Serial
/*...*/
};
EDIT: you could try grouping after projection like so:
var items = context.Items.Where(/*...*/).Select(i => new ItemModel { /*...*/})
.GroupBy(g => new { g.Name, g.Model });
you get an
IGrouping<AnonymousType``1, IEnumerable<ItemModel>> from this with your arbitrary group by as the key, and your ItemModels as the grouped collection.
I would strongly advise against what you're doing. The serial number is being chosen arbitrarily since you do no ordering in your queries. It would be better if you specified exactly which serial number to choose that way there are no surprises if the queries return items in a different ordering than "last time".
With that said, I think it would be cleaner to project the grouping and select the fields you need and take the first result. They all will have the same key values so that will stay the same, then you can add on any other fields you want.
var items = context.Items.GroupBy(i => new { i.Name, i.Model })
.Where(/*...*/)
.Select(g =>
g.OrderBy(i => i.Name).Select(i => new ItemModel
{
Name = i.Name,
SerialNumber = i.SerialNumber,
}).FirstOrDefault()
);
Since you need all the data, you need to store all the group data into your value (in the KeyValuePair).
I don't have the exact syntax in front of me, but it would look like:
/* ... */
.Select(g => new {
Key = g.key,
Values = g
});
After that, you can loop through the Key to get your Name group. Inside of that loop, include a loop through the Values to get your ItemModel (I guess that's the object containing 1 element).
It would look like:
foreach (var g in items)
{
Console.WriteLine("List of SerialNumber in {0} group", g.Key);
foreach (var i in g.Values)
{
Console.WriteLine(i.SerialNumber);
}
}
Hope this helps!
You might want to look at Linq 101 samples for some help on different queries.
if the serial number is unique to the name and model, you should include it in your group by object.
If it is not, then you have a list of serials per name and model, and selecting firstordefault is probably plain wrong, that is, I can think of no scenario you would want this.
I have a known list of strings like the following:
List<string> groupNames = new List<string>(){"Group1","Group2","Group3"};
I also have a list of strings that is not known in advance that will be something like this:
List<string> dataList = new List<string>()
{
"Group1.SomeOtherText",
"Group1.SomeOtherText2",
"Group3.MoreText",
"Group2.EvenMoreText"
};
I want to do a LINQ statement that will take the dataList and convert it into either an anonymous object or a dictionary that has a Key of the group name and a Value that contains a list of the strings in that group. With the intention of looping over the groups and inner looping over the group list and doing different actions on the strings based on which group it is in.
I would like a data structure that looks something like this:
var grouped = new
{
new
{
Key="Group1",
DataList=new List<string>()
{
"Group1.SomeOtherText",
"Group1.SomeOtherText2"
}
},
new
{
Key="Group2",
DataList=new List<string>()
{
"Group2.EvenMoreText"
}
}
...
};
I know I can just loop through the dataList and then check if each string contains the group name then add them to individual lists, but I am trying to learn the LINQ way of doing such a task.
Thanks in advance.
EDIT:
Just had another idea... What if my group names were in an Enum?
public enum Groups
{
Group1,
Group2,
Group3
}
How would I get that into a Dictionary>?
This is what I am trying but i am not sure how to form the ToDictionary part
Dictionary<Groups,List<string>> groupedDictionary = (from groupName in Enum.GetNames(typeof(Groups))
from data in dataList
where data.Contains(groupName)
group data by groupName).ToDictionary<Groups,List<string>>(...NOT SURE WHAT TO PUT HERE....);
EDIT 2:
Found the solution to the Enum question:
var enumType = typeof(Groups);
Dictionary<Groups,List<string>> query = (from groupName in Enum.GetValues(enumType).Cast<Groups>()
from data in dataList
where data.Contains(Enum.GetName(enumType, groupName))
group data by groupName).ToDictionary(x => x.Key, x=> x.ToList());
That looks like:
var query = from groupName in groupNames
from data in dataList
where data.StartsWith(groupName)
group data by groupName;
Note that this isn't a join, as potentially there are overlapping group names "G" and "Gr" for example, so an item could match multiple group names. If you could extract a group name from each item (e.g. by taking everything before the first dot) then you could use "join ... into" to get a group join. Anyway...
Then:
foreach (var result in query)
{
Console.WriteLine("Key: {0}", result.Key);
foreach (var item in result)
{
Console.WriteLine(" " + item);
}
}
If you really need the anonymous type, you can do...
var query = from groupName in groupNames
from data in dataList
where data.StartsWith(groupName)
group data by groupName into g
select new { g.Key, DataList = g.ToList() };
Assuming I had a collection like this..
var list = new List<Item>
{
new Item
{
Name = "Software",
Price = 100
},
new Item
{
Name = "Software",
Price = 200
},
new Item
{
Name = "Hardware",
Price = 100
}
};
And the 'Names' are not going to be known, I want to write a LINQ query that will return a list of everything with a matching name. I cannot use "Select", because the names, again, are not known at design time.
Any ideas?
I'm not entirely sure whether you want to filter, or group the results.
If you want to filter, you can use Where with a runtime-supplied name:
string nameToFind = GetTheNameToFind(); // Can come from user, config, etc
var matches = list.Where(item => item.Name == nameToFind);
If you want to group by all of the names (ie: have 2 software + 1 hardware element), you could use Enumerable.GroupBy:
var groups = list.GroupBy(item => item.Name);
foreach(var group in groups)
{
Console.WriteLine("Group {0}:", group.Key);
foreach(var item in group)
Console.WriteLine(" Item: {0} - {1}", item.Name, item.Price);
}
Are you looking for this? You can use Where method to filter enumerable.
name variable can be defined at runtime.
string name = "Software";
List<Item> newList = list.Where(item => item.Name == name).ToList();