2011-04-08 13 views
7

Necesito dividir una lista en varias listas que están agrupadas de acuerdo con el primer carácter de una propiedad de cadena. Aquí hay un ejemplo.División de una lista en rangos alfabéticos

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; } 
} 

que da salida:

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

Este algoritmo funciona, pero se siente muy torpe. ¿Hay una manera más elegante de hacer esto?

Respuesta

4

siguiente método de extensión utiliza LINQ para seleccionar todas las marcas de vehículos cuya el nombre comienza en el rango de caracteres.

 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(); 
     } 

Ejemplo de uso

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)); 
     } 
    } 
+0

Ugh, estaba tan cerca de esto cuando comencé a escribir este algoritmo, excepto que traté de hacerlo en toda la cadena y no solo en el primer carácter. Esto es perfecto sin embargo. ¡Gracias! –

2

Puede utilizar GroupBy() para lograr lo que quiere - grupo por la primera letra de su vehículo, a continuación, hacer listas secundarias de aquellos:

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

Esto sin embargo crear grupos que sólo cubren una letra cada uno - a modificar el comportamiento de agrupación puede proporcionar un método personalizado GetGroup(char c) que devuelve un número entero para identificar el grupo.

otra parte, si lo que quieres es equilibradas grupos, se puede utilizar el índice para agrupar los vehículos en grupos del mismo tamaño:

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(); 
+0

Gracias, pero este ejemplo sólo los grupos de la primera letra. Necesito que los grupos estén alineados. En mi ejemplo, estoy esperando 3 listas. Una de todas las marcas comienza con A-C, una con todas las marcas comenzando con D-F, y una con todas las marcas comenzando con G-J. Las líneas con tres hypens en la salida de muestra delimitan cada lista que mi algoritmo genera. –

1

Si desea tener un objeto con las siguientes invariantes:

  • puede albergar una serie de artículos balancedlistgroup
  • de un vehículo determinado pasado en los retornos, por ejemplo un entero que es el mismo para cualquier vehículo que cae dentro de un rango dado

Luego se pueden agrupar su lista de vehículos con la ayuda de ese objeto:

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

Creo que esta consulta hará lo que usted necesita de manera eficiente:

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(); 

La idea es:

  1. Cree una tabla hash a partir del cartas válidas al cubo asociado.
  2. Agrupe los elementos en el cubo correcto utilizando la tabla hash, filtrando los elementos que no tienen un cubo correspondiente.
1

Otra opción de Linq si lo desea.

 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

voy a tirar mi 2p en:

empecé a cabo asumiendo una cosa muy mala, para simplificar el código.A saber, que está trabajando con caracteres ascii en mayúscula para sus letras iniciales. Obviamente, mi código se puede modificar para que funcione con las estructuras de su rango.

Así que le ponga las gamas iniciales de este modo:

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) 
        }; 

Y el método para obtener los grupos es:

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)); 
} 
Cuestiones relacionadas