Estoy tratando de ver si puedo usar LINQ para resolver un problema que estoy teniendo. Tengo una colección de elementos que contienen una Enumeración (TypeCode) y un objeto de Usuario, y tengo que acoplar a mostrar en un grid. Es difícil de explicar, así que permítanme mostrarles un ejemplo rápido.

Colección tiene elementos como:

TypeCode | User 
---------------
1        | Don Smith  
1        | Mike Jones  
1        | James Ray  
2        | Tom Rizzo  
2        | Alex Homes  
3        | Andy Bates  

Necesito la salida:

1          | 2          | 3  
Don Smith  | Tom Rizzo  | Andy Bates  
Mike Jones | Alex Homes |  
James Ray  |            |  

Gracias a alguien que me pueda ayudar! He intentado hacer esto usando foreach, pero no puedo hacerlo de esa manera porque me gustaría estar en la inserción de nuevos elementos a la colección en el foreach, causando un error.

  • ¿Qué tipo de colección es la fuente? Puede publicar su foreach código (aunque sea mal)? Que nos dará una mejor idea del problema.
  • No estoy seguro de si afecta a la respuesta, pero ¿qué tipo de cuadrícula necesita? Es ASP.NET con una tabla HTML, formularios windows forms con un GridView componente, WPF, algunos motor de generación de informes… ?
InformationsquelleAutor | 2009-06-08

5 Comentarios

  1. 22

    No estoy diciendo que sea un gran manera de pivote – pero es un pivote…

    • La velocidad no es un problema de grandes conjuntos de datos, pero como se ha dicho ‘es un pivote’.
  2. 11

    De Marc respuesta da la matriz dispersa que no puede ser bombeado en Rejilla directamente.

    Traté de ampliar el código de la enlace proporcionado por Vasu de la siguiente manera:

    public static Dictionary<TKey1, Dictionary<TKey2, TValue>> Pivot3<TSource, TKey1, TKey2, TValue>(
    this IEnumerable<TSource> source
    , Func<TSource, TKey1> key1Selector
    , Func<TSource, TKey2> key2Selector
    , Func<IEnumerable<TSource>, TValue> aggregate)
    {
    return source.GroupBy(key1Selector).Select(
    x => new
    {
    X = x.Key,
    Y = source.GroupBy(key2Selector).Select(
    z => new
    {
    Z = z.Key,
    V = aggregate(from item in source
    where key1Selector(item).Equals(x.Key)
    && key2Selector(item).Equals(z.Key)
    select item
    )
    }
    ).ToDictionary(e => e.Z, o => o.V)
    }
    ).ToDictionary(e => e.X, o => o.Y);
    } 
    internal class Employee
    {
    public string Name { get; set; }
    public string Department { get; set; }
    public string Function { get; set; }
    public decimal Salary { get; set; }
    }
    public void TestLinqExtenions()
    {
    var l = new List<Employee>() {
    new Employee() { Name = "Fons", Department = "R&D", Function = "Trainer", Salary = 2000 },
    new Employee() { Name = "Jim", Department = "R&D", Function = "Trainer", Salary = 3000 },
    new Employee() { Name = "Ellen", Department = "Dev", Function = "Developer", Salary = 4000 },
    new Employee() { Name = "Mike", Department = "Dev", Function = "Consultant", Salary = 5000 },
    new Employee() { Name = "Jack", Department = "R&D", Function = "Developer", Salary = 6000 },
    new Employee() { Name = "Demy", Department = "Dev", Function = "Consultant", Salary = 2000 }};
    var result5 = l.Pivot3(emp => emp.Department, emp2 => emp2.Function, lst => lst.Sum(emp => emp.Salary));
    var result6 = l.Pivot3(emp => emp.Function, emp2 => emp2.Department, lst => lst.Count());
    }

    * no se puede decir nada sobre el rendimiento, sin embargo.

  3. 5

    Puede utilizar Linq .ToLookup a grupo en la manera que usted está buscando.

    var lookup = data.ToLookup(d => d.TypeCode, d => d.User);

    Entonces es una cuestión de ponerlo en una forma que su consumo puede llegar a entender. Por ejemplo:

    //Warning: untested code
    var enumerators = lookup.Select(g => g.GetEnumerator()).ToList();
    int columns = enumerators.Count;
    while(columns > 0)
    {
    for(int i = 0; i < enumerators.Count; ++i)
    {
    var enumerator = enumerators[i];
    if(enumator == null) continue;
    if(!enumerator.MoveNext())
    { 
    --columns;
    enumerators[i] = null;
    }
    }
    yield return enumerators.Select(e => (e != null) ? e.Current : null);
    }

    Puesto que en un IEnumerable<> método y (probablemente) devolver una colección (filas) de las colecciones (columna) de Usuario, donde los valores null se colocan en una columna que no tiene datos.

  4. 2

    Supongo que esto es similar al de Marc respuesta, pero voy a publicar ya que me pasó algún tiempo trabajando en él. Los resultados están separados por " | " como en tu ejemplo. También se utiliza el IGrouping<int, string> tipo devuelto por la consulta LINQ, cuando el uso de un grupo, en lugar de construir un nuevo tipo anónimo. Esto es probado, código de trabajo.

    var Items = new[] {
    new { TypeCode = 1, UserName = "Don Smith"},
    new { TypeCode = 1, UserName = "Mike Jones"},
    new { TypeCode = 1, UserName = "James Ray"},
    new { TypeCode = 2, UserName = "Tom Rizzo"},
    new { TypeCode = 2, UserName = "Alex Homes"},
    new { TypeCode = 3, UserName = "Andy Bates"}
    };
    var Columns = from i in Items
    group i.UserName by i.TypeCode;
    Dictionary<int, List<string>> Rows = new Dictionary<int, List<string>>();
    int RowCount = Columns.Max(g => g.Count());
    for (int i = 0; i <= RowCount; i++) //Row 0 is the header row.
    {
    Rows.Add(i, new List<string>());
    }
    int RowIndex;
    foreach (IGrouping<int, string> c in Columns)
    {
    Rows[0].Add(c.Key.ToString());
    RowIndex = 1;
    foreach (string user in c)
    {
    Rows[RowIndex].Add(user);
    RowIndex++;
    }
    for (int r = RowIndex; r <= Columns.Count(); r++)
    {
    Rows[r].Add(string.Empty);
    }
    }
    foreach (List<string> row in Rows.Values)
    {
    Console.WriteLine(row.Aggregate((current, next) => current + " | " + next));
    }
    Console.ReadLine();

    Yo también lo probé con esta entrada:

    var Items = new[] {
    new { TypeCode = 1, UserName = "Don Smith"},
    new { TypeCode = 3, UserName = "Mike Jones"},
    new { TypeCode = 3, UserName = "James Ray"},
    new { TypeCode = 2, UserName = "Tom Rizzo"},
    new { TypeCode = 2, UserName = "Alex Homes"},
    new { TypeCode = 3, UserName = "Andy Bates"}
    };

    Que arrojó los siguientes resultados demostrando que la primera columna no necesita contener la lista más larga. Usted podría utilizar OrderBy para obtener las columnas ordenadas por TypeCode si es necesario.

    1         | 3          | 2
    Don Smith | Mike Jones | Tom Rizzo
    | James Ray  | Alex Homes
    | Andy Bates | 
  5. 1

    @Sanjaya.Tio yo estaba intrigado por su respuesta y creó esta adaptación, el cual minimiza keySelector ejecución. (no probado)

    public static Dictionary<TKey1, Dictionary<TKey2, TValue>> Pivot3<TSource, TKey1, TKey2, TValue>(
    this IEnumerable<TSource> source
    , Func<TSource, TKey1> key1Selector
    , Func<TSource, TKey2> key2Selector
    , Func<IEnumerable<TSource>, TValue> aggregate)
    {
    var lookup = source.ToLookup(x => new {Key1 = keySelector1(x), Key2 = keySelector2(x)});
    List<TKey1> key1s = lookup.Select(g => g.Key.Key1).Distinct().ToList();
    List<TKey2> key2s = lookup.Select(g => g.Key.Key2).Distinct().ToList();
    var resultQuery =
    from key1 in key1s
    from key2 in key2s
    let lookupKey = new {Key1 = key1, Key2 = key2}
    let g = lookup[lookupKey]
    let resultValue = g.Any() ? aggregate(g) : default(TValue)
    select new {Key1 = key1, Key2 = key2, ResultValue = resultValue};
    Dictionary<TKey1, Dictionary<TKey2, TValue>> result = new Dictionary<TKey1, Dictionary<TKey2, TValue>>();
    foreach(var resultItem in resultQuery)
    {
    TKey1 key1 = resultItem.Key1;
    TKey2 key2 = resultItem.Key2;
    TValue resultValue = resultItem.ResultValue;
    if (!result.ContainsKey(key1))
    {
    result[key1] = new Dictionary<TKey2, TValue>();
    }
    var subDictionary = result[key1];
    subDictionary[key2] = resultValue; 
    }
    return result;
    }

Dejar respuesta

Please enter your comment!
Please enter your name here