Tengo una Lista de Lista que puede ser de ancho variable pero repetido. Por ejemplo:

var test = new List<List<string>>();
test.Add(new List<string> {"1","2","3"});
test.Add(new List<string> {"1","4","12"});
test.Add(new List<string> {"1","2","9"});
test.Add(new List<string> {"1","4","5"});
test.Add(new List<string> {"6","7","8"});

Pero también podría ser:

var test = new List<List<string>>();
test.Add(new List<string> {"1","2","3","3","3"});
test.Add(new List<string> {"1","4","12","1","7"});
test.Add(new List<string> {"1","2","9","9","4"});
test.Add(new List<string> {"1","4","5","8","5"});
test.Add(new List<string> {"6","7","8","2","7"});

Nunca será:

var test = new List<List<string>>();
test.Add(new List<string> {"1"});
test.Add(new List<string> {"1","5"});
test.Add(new List<string> {"1","2","3"});
test.Add(new List<string> {"1","5"});
test.Add(new List<string> {"6","7","8"});

Y me gustaría tener la lista ordenada de la columna izquierda a la columna derecha como:

["1","2","3"];
["1","2","9"];
["1","4","5"];
["1","4","12"];
["6","7","8"];

La siguiente es una pequeña prueba que configuré para ver qué se me ocurre ( https://dotnetfiddle.net/B5ljig):

var test = new List<List<string>>();
test.Add(new List<string> {"1","2","3"});
test.Add(new List<string> {"1","4","5"});
test.Add(new List<string> {"1","2","3"});
test.Add(new List<string> {"1","4","5"});
test.Add(new List<string> {"6","7","8"});

var query = test.AsQueryable();
query = query.OrderBy(a=>a[0]);
var max = categories.Select(a=>a.Count()).Max();
for (int i = 1; i < max; i++)
{
    query = query.ThenBy(a=>a[i]); // Error Here
}
var sorted = query.ToList();

Lamentablemente, los errores de línea comentados con

'IQueryable>' no contiene una definición para 'ThenBy' y no se puede encontrar ningún método de extensión accesible 'ThenBy' que acepte un primer argumento de tipo 'IQueryable>' (¿falta una directiva using o una referencia de ensamblado?)

¿Algunas ideas? Pensamientos? Mejores maneras

2
Andrew Harris 15 may. 2020 a las 16:02

3 respuestas

La mejor respuesta

Si desea Sort cualquier cosa usando sus propias reglas , puede implementar un comparador (IComparer<T>) personalizado, IComparer<IList<string>> en este caso particular:

   public class MyListComparer : IComparer<IList<string>> {
      private static int CompareItems(string left, string right) {
        if (left.StartsWith("-"))
          if (right.StartsWith("-"))
            return -CompareItems(left.TrimStart('-'), right.TrimStart('-'));
          else
            return -1;
        else if (right.StartsWith("-"))
          return 1;

        left = left.TrimStart('0');
        right = right.TrimStart('0');  

        int result = left.Length.CompareTo(right.Length);

        if (result != 0)
          return result;

        for (int i = 0; i < left.Length; ++i) {
          result = left[i] - right[i];

          if (result != 0)
            return result;
        }

        return 0;
      }

      public int Compare(IList<string> x, IList<string> y) {
        if (ReferenceEquals(x, y))
          return 0;
        else if (null == x)
          return -1;
        else if (null == y)
          return 1;

        for (int i = 0; i < Math.Min(x.Count, y.Count); ++i) {
          int result = CompareItems(x[i], y[i]);

          if (result != 0)
            return result;
        }

        return x.Count.CompareTo(y.Count);
      }
    }

Luego ordena:

  var test = new List<List<string>>();

  test.Add(new List<string> { "1", "2", "3" });
  test.Add(new List<string> { "1", "4", "12" });
  test.Add(new List<string> { "1", "2", "9" });
  test.Add(new List<string> { "1", "4", "5" });
  test.Add(new List<string> { "6", "7", "8" });

  // Time to sort with a custom comparer
  test.Sort(new MyListComparer());

  string report = string.Join(Environment.NewLine, test
    .Select(line => string.Join(", ", line)));

  Console.Write(report);

Salir:

  1, 2, 3
  1, 2, 9
  1, 4, 5
  1, 4, 12
  6, 7, 8

Puede usar el comparador con la consulta Linq también:

  var sorted = test.OrderBy(new MyListComparer());
3
Dmitry Bychenko 15 may. 2020 a las 13:46

Hay dos problemas con su código. Uno es un problema de sintaxis y el otro es un problema de lógica. Para eliminar el error de compilación que está viendo, la variable de consulta debe ser un IOrderedQueryable en lugar del IQueryable que ha enumerado. Si combina la definición de la variable de consulta y el orden inicial en una línea como la siguiente, su problema debería resolverse.

var query = test.AsQueryable().OrderBy(a => a[0]);

También puede usar IOrderedEnumerable en su lugar usando

var query = test.OrderBy(a => a[0]);

El problema lógico es que su código no producirá el resultado que espera. Está ordenando la lista de la lista de cadenas por su primer valor antes de ordenar cada lista de cadenas. En otras palabras, su Orderby inicial debe estar debajo de su ciclo for. Por simplicidad, estoy simplificando esta expresión de Linq:

var sorted = test
    .Select(x => x.OrderBy(y => y).ToList())
    .OrderBy(x => x[0])
    .ToList();
1
TeaBaerd 15 may. 2020 a las 13:18

El problema es que

1) el uso excesivo de IQueryable, no lo necesitas,

2) el hecho de que i en realidad está capturado , y cuando se ejecuta la consulta, tiene todos "entonces por" que usan el mismo i == 3, el último valor después del final del bucle for! (Por lo tanto, una excepción fuera de límites en tiempo de ejecución)

Aquí hay una versión funcional (dotnetFiddle):

    var query = test.OrderBy(a=>a[0]);
    //var max = test.Select(a=>a.Count()).Max(); // If you say all lists have the same length, use `First(a => a.Count())` instead! And if they don't, then this will lead to an exception.

    for (int i = 1; i < max; i++)
    {
        var j = i; // Intermediary variable so that 'global' i is not captured.
        query = query.ThenBy(a=>a[j]);
    };
    var sorted = query.ToList();

En nota adicional, hay otras soluciones que utilizan diferentes enfoques, ya dados, creo que se sienten más "idiomáticos" para C # con el IComparer

2
Pac0 15 may. 2020 a las 15:46