Reputation: 389
Good day,
In my sample scenario, I'm trying to fetching all users from my database and put then on my dto where it has lists of ordering by alphabet letters given the following: A-L, M-Z
Here's my sample code with common OrderBy
by the user name:
var users = await users.Users().ToListAsync();
return users.Select(u=>new CategorizedByLetterUserDto{
...
}).OrderBy(u=>u.Name);
So my sample CategorizedByLetterUserDto
looks like this.
public class CategorizedByLetterUserDto {
public IEnumerable<AtoL> AtoL {get;set}
public IEnumerable<MtoZ> MtoZ {get;set;}
...
}
public class AtoL{
public int Id {get;set;}
public string Name {get;set;}
}
public class MtoZ{
public int Id {get;set;}
public string Name {get;set;}
}
so on and so forth...
So the result will be (array)
{
categorizedByLetterUserDto: {
atoL: [
{
...
}
],
mtoZ: [
{
...
}
]
}
}
Upvotes: 0
Views: 182
Reputation: 39277
Use GroupBy
, something like this:
var grouped = users.GroupBy(u => u.Name.CompareTo("M") < 0).OrderBy(g => g.Key).ToArray();
return new CategorizedByLetterUserDto
{
AtoL = grouped[1].Select(x => new UserDto { Id = x.Id, Name = x.Name }),
MtoZ = grouped[0].Select(x => new UserDto { Id = x.Id, Name = x.Name }),
};
And don't create identical classes, use:
public class UserDto
{
public int Id {get;set;}
public string Name {get;set;}
}
You could use ToLookup
instead and in this case it would be equivalent but see https://stackoverflow.com/a/10215531/224370 for details as to when it's not.
And if you wanted to split on more than one split point you could do something like:
string splits = "EMT";
var grouped = users.GroupBy(u => splits.Count(c => u.Name[0] > c))
.OrderBy(g => g.Key).ToArray(); ...
Note: Purists would prefer a sorted list of split characters and a binary search method to find the index, but for most practical smaller sets of split characters this will be faster and it's less code and it doesn't care what order the splits points are specified in.
Upvotes: 2
Reputation: 52
Better use the List<> Class instead of IEnumerable<> in your CategorizedByLetterUserDto
class to access the .Add
method.. Then try the ff code.
var users = await users.Users().ToListAsync();
char[] listOfAtoL = "ABCDEFGHIJKL".toCharArray();
CategorizedByLetterUserDto cat = new CategorizedByLetterUserDto();
foreach (User u in users.OrderBy(a => a.Name)) {
listOfAtoL.Contains(char.ToUpper(u.Name.toCharArray()[0])) ? cat.AtoL.Add(new AtoL() {id = u.ID, Name = u.Name}) : cat.MtoZ.Add(new MtoZ() {id = u.ID, Name = u.Name});
}
return cat;
Upvotes: 0
Reputation: 74605
I don't think you need to get that involved, you can just use LINQs existing functionality tocreate lookups
List<User> list = ... .OrderBy(u=>u.Name); //from db
ILookup<int, User> lookup = list.ToLookup(
u => (u.Name[0] - 63)/14,
u => new Dto { Id = u.Id, Name = u.Name }
);
The numbers relate to treating a char as an int in the ascii table, A being 65. By subtracting 63, A becomes 2, L becomes 13, Z becomes 27. By then dividing by 14 we reduce this to 0 or 1. Your lookup will now be an enumerable of users indexed by either 0 or 1, the 0 being AtoL. If you want the users in a given list sorted you can call OrderBy on them. I did this with math rather than a bool compare so you can alter it to more columns in future just by altering the numbers, but you can use bool also:
var lookup = list.ToLookup(
u => u.Name[0]<'M',
u => new Dto { Id = u.Id, Name = u.Name }
);
In this the lookup returned, indexed by true would be A to L
Assign to your categorized Dto:
var x = new CatDto(){
aToL=lookup[true],
mToZ=lookup[false]
};
Note that this, in combination with every other answer here, relies on your names being properly formatted with an uppercase ASCII character as the first char.. If your names are messy, or don't always start thus, you should consider doing things like uppercasing them before compare etc
Upvotes: 0