Alphabetic Listing

I need to split the list into several lists, which are grouped according to the first character of the string property. Here is an example.

class Program { static void Main(string[] args) { var makes = new List<VehicleMake> { new VehicleMake {Name = "Acura"}, new VehicleMake {Name = "AMG"}, new VehicleMake {Name = "Audi"}, new VehicleMake {Name = "BMW"}, new VehicleMake {Name = "Chevrolet"}, new VehicleMake {Name = "Datsun"}, new VehicleMake {Name = "Eagle"}, new VehicleMake {Name = "Fiat"}, new VehicleMake {Name = "Honda"}, new VehicleMake {Name = "Infiniti"}, new VehicleMake {Name = "Jaguar"} }; var balancedLists = makes.Balance(new List<BalancedListGroup> { new BalancedListGroup { RangeStart = 'A', RangeEnd = 'C'}, new BalancedListGroup { RangeStart = 'D', RangeEnd = 'F'}, new BalancedListGroup { RangeStart = 'G', RangeEnd = 'J'}, }); foreach (var balancedList in balancedLists) { foreach (var vehicleMake in balancedList) { Console.WriteLine(vehicleMake.Name); } Console.WriteLine("---"); } Console.ReadLine(); } } public class VehicleMake { public string Name { get; set; } } public static class VehicleMakeListBalancer { public static List<List<VehicleMake>> Balance(this List<VehicleMake> list, List<BalancedListGroup> groups) { var letters = new List<string> { "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "y", "z" }; var balancedLists = new List<List<VehicleMake>>(); foreach (var group in groups) { var groupList = new List<VehicleMake>(); for (var i = letters.IndexOf(group.RangeStart.ToString().ToLower()); i <= letters.IndexOf(group.RangeEnd.ToString().ToLower()); i++) { groupList.AddRange(list.Where(l => l.Name.ToLower().StartsWith(letters[i].ToString())).ToList()); } balancedLists.Add(groupList); } return balancedLists; } } public class BalancedListGroup { public char RangeStart { get; set; } public char RangeEnd { get; set; } } 

What outputs:

 Acura AMG Audi BMW Chevrolet --- Datsun Eagle Fiat --- Honda Infiniti Jaguar --- 

This algorithm works, but feels very awkward. Is there a more elegant way to do this?

+7
source share
6 answers

The following extension method uses linq to select all vehicles whose name begins in a range of characters.

  public static List<VehicleMake> GetInRange(this List<VehicleMake> vehicleList, char RangeStart, char RangeEnd) { var vehiclesInRange = from vm in vehicleList where vm.Name[0] >= RangeStart && vm.Name[0] <= RangeEnd select vm; return vehiclesInRange.ToList(); } 

EXAMPLE OF USE

  static class Program { static void Main(string[] args) { var makes = new List<VehicleMake> { new VehicleMake { Name = "Acura" }, new VehicleMake { Name = "AMG" }, new VehicleMake { Name = "Audi" }, new VehicleMake { Name = "BMW" }, new VehicleMake { Name = "Chevrolet" }, new VehicleMake { Name = "Datsun" }, new VehicleMake { Name = "Eagle" }, new VehicleMake { Name = "Fiat" }, new VehicleMake { Name = "Honda" }, new VehicleMake { Name = "Infiniti" }, new VehicleMake { Name = "Jaguar" } }; var atoc = makes.GetInRange('A', 'C'); atoc.Print(); var dtom = makes.GetInRange('D', 'M'); dtom.Print(); var mtoz = makes.GetInRange('M', 'Z'); mtoz.Print(); Console.ReadLine(); } static List<VehicleMake> GetInRange(this List<VehicleMake> vehicleList, char RangeStart, char RangeEnd) { var vehiclesInRange = from vm in vehicleList where vm.Name[0] >= RangeStart && vm.Name[0] <= RangeEnd select vm; return vehiclesInRange.ToList(); } static void Print(this List<VehicleMake> vehicles) { Console.WriteLine(); vehicles.ForEach(v => Console.WriteLine(v.Name)); } } 
+4
source

You can use GroupBy() to achieve what you want - a group by the first letter of your car, then make a list of them:

 var balancedLists = makes.GroupBy(x => x.Name[0]).Select( x=> x.ToList()) .ToList(); 

However, this will create groups that span only one letter — to change the grouping behavior, you can provide your own GetGroup( char c) method, which returns an integer to identify the group.

Alternatively, if all you need is balanced groups, you can use the index to group vehicles into groups of the same size:

 var balancedLists = makes.Select((vehicle, index) => new { Index = index, Vehicle = vehicle }) .GroupBy(x => x.Index / 3) .Select(g => g.Select(x => x.Vehicle).ToList()) .ToList(); 
+2
source

If you have an object with the following invariants:

  • May contain multiple balancelistgroup elements
  • For a given vehicle transferred in returns, for example. an integer that is the same for any vehicle falling within a given range

Then you can group your list of vehicles using this object:

 var groups = vehicles.GroupBy(x => rangeContainer.GroupKey(x)) 
+1
source

I believe this request will do what you need:

 var letterGroupTuples = from blGroup in groups from letter in Enumerable.Range (blGroup.RangeStart, blGroup.RangeEnd - blGroup.RangeStart + 1) select new { Letter = char.ToLower((char)letter), BlGroup = blGroup }; var groupsForLetters = letterGroupTuples.ToDictionary (a => a.Letter, a => a.BlGroup); var query = from vehicleMake in list let key = vehicleMake.Name.ToLower().First() where groupsForLetters.ContainsKey(key) group vehicleMake by groupsForLetters[key] into bucket select bucket.ToList(); return query.ToList(); 

The idea is as follows:

  • Create a hash table of valid letters in the appropriate bucket.
  • Group items in the right bucket using a hash table, filtering out those items that do not have the corresponding bucket.
+1
source

Another Linq option if you want.

  var makes = new List<VehicleMake> { new VehicleMake { Name = "Acura" }, new VehicleMake { Name = "AMG" }, new VehicleMake { Name = "Audi" }, new VehicleMake { Name = "BMW" }, new VehicleMake { Name = "Chevrolet" }, new VehicleMake { Name = "Datsun" }, new VehicleMake { Name = "Eagle" }, new VehicleMake { Name = "Fiat" }, new VehicleMake { Name = "Honda" }, new VehicleMake { Name = "Infiniti" }, new VehicleMake { Name = "Jaguar" } }; var balancedLists = new List<BalancedListGroup> { new BalancedListGroup { RangeStart = 'A', RangeEnd = 'C' }, new BalancedListGroup { RangeStart = 'D', RangeEnd = 'F' }, new BalancedListGroup { RangeStart = 'G', RangeEnd = 'J' }, }; List<List<VehicleMake>> brandedMakes = new List<List<VehicleMake>>(); foreach (var x in balancedLists) { brandedMakes.Add(makes.Where(a => a.Name.Substring(0, 1)[0] >= x.RangeStart && a.Name.Substring(0, 1)[0] < x.RangeEnd).ToList()); } 
+1
source

I will put 2p in:

I started by taking very badly to simplify my code. Namely, that you are working with uppercase ascii characters for your initial letters. Obviously, my code can be modified to work with your range structures.

So my starting ranges are:

 var initialGroups = new List<IEnumerable<char>> { Enumerable.Range((int)'A', 3).Select(i => (char)i) , Enumerable.Range((int)'D', 3).Select(i => (char)i) , Enumerable.Range((int)'G', 4).Select(i => (char)i) }; 

And the method for getting groups:

 IEnumerable<IEnumerable<string>> GroupByInitial(List<string> cars, List<IEnumerable<char>> initialGroups) { var groups = from grp in initialGroups from car in cars where grp.Contains(car[0]) select new {grp, car}; return groups.GroupBy(group => group.grp).Select(group => group.Select(grouping => grouping.car)); } 
+1
source

All Articles