Utilice LINQ para mover el elemento al principio de la lista

81

¿Hay alguna forma de mover un elemento de, por ejemplo, id = 10 como el primer elemento de una lista usando LINQ?

Artículo A - id = 5
Artículo B - id = 10
Elemento C - id = 12
Elemento D - id = 1

En este caso, ¿cómo puedo mover elegantemente el artículo C a la parte superior de mi List<T>colección?

Esto es lo mejor que tengo ahora mismo:

var allCountries = repository.GetCountries();
var topitem = allCountries.Single(x => x.id == 592);  
var finalList = new List<Country>();
finalList.Add(topitem);
finalList = finalList.Concat(allCountries.Where(x=> x.id != 592)).ToList();
qui
fuente
¿Desea intercambiar el elemento con el elemento superior o rotar los elementos empujando todos los elementos hasta que el elemento encontrado hacia abajo?
AnthonyWJones
didn ; t finalList .insert (0, "cosas nuevas"); trabajo
Rohit Kumar

Respuestas:

52

LINQ es fuerte para consultar colecciones, crear proyecciones sobre consultas existentes o generar nuevas consultas basadas en colecciones existentes. No pretende ser una herramienta para reordenar colecciones existentes en línea. Para ese tipo de operación, es mejor usar el tipo a mano.

Suponiendo que tiene un tipo con una definición similar a la siguiente

class Item {
  public int Id { get; set; }
  ..
}

Entonces intente lo siguiente

List<Item> list = GetTheList();
var index = list.FindIndex(x => x.Id == 12);
var item = list[index];
list[index] = list[0];
list[0] = item;
JaredPar
fuente
4
+1 funciona bien para el escenario de intercambio, tengo que sentir que en realidad se requiere una rotación aunque '
AnthonyWJones
Esto es más o menos lo que hice de cualquier manera, pero gracias por la explicación de por qué aparentemente no hay una mejor manera :)
qui
6
Para el manejo de errores, tenga en cuenta que debe verificar el FindIndexvalor del resultado, es -1 si el elemento no se encuentra en la lista.
schnaader
¿No es esto solo intercambiar el primer elemento con el índice del elemento de destino en lugar de mover el elemento de destino a la parte superior y mover todo lo demás hacia abajo?
frostshoxx
143

¿Por qué desea ordenar, además del artículo superior conocido? Si no le importa, puede hacer esto:

var query = allCountries.OrderBy(x => x.id != 592).ToList();

Básicamente, "falso" viene antes que "verdadero" ...

Es cierto que no sé qué hace esto en LINQ to SQL, etc. Es posible que deba evitar que haga el pedido en la base de datos:

var query = allCountries.AsEnumerable()
                        .OrderBy(x => x.id != 592)
                        .ToList();
Jon Skeet
fuente
1
no funciona como se esperaba para LINQ to SQL. Lo acabo de probar.
Yasser Shaikh
5
+1 Gracias Jon. Quería ordenar por nombre pero mantener el artículo con id = 0 en la parte superior, así que hice esto: allCountries.OrderBy (x => x.id == 0? "00000": x.Name) .ToList (); el rendimiento no es un problema porque la lista es pequeña.
nima
3
Para alguien que revise el código más tarde, puede que no sea obvio que los valores booleanos estén ordenados como "falso, verdadero". Recomendaría las soluciones más detalladas sobre esto.
rymdsmurf
1
¡Hermoso! Quería una Namelista pequeña en la parte superior o en el índice 0, en LINQ to Entities, y funcionó gracias +1. db.Systms.Where(s => s.IsActive == true).OrderBy(x => x.SystemName != "Portal Administration").ToList();
Irfan
¡Buena cosa! Una bonita y sencilla solución. Gracias,
Hugo Nava Kopp
43

Linq generalmente trabaja en Enumerables, por lo que no lo hace ahora que el tipo subyacente es una colección. Entonces, para mover el elemento en la parte superior de la lista, sugeriría usar algo como (si necesita preservar el orden)

var idx = myList.FindIndex(x => x.id == 592);
var item = myList[idx];
myList.RemoveAt(idx);
myList.Insert(0, item);

Si su función devuelve solo un IEnumerable, puede usar el ToList()método para convertirlo en una Lista primero

Si no conserva el orden, simplemente puede intercambiar los valores en la posición 0 y la posición idx

Oso pardo
fuente
Esto es perfecto para el escenario de rotación hacia abajo en lugar de simplemente intercambiar los valores.
Bradley Mountford
35
var allCountries = repository.GetCountries();
allCountries.OrderByDescending(o => o.id == 12).ThenBy(o => o.id) 

Esto insertará el objeto con id = 12 en la parte superior de la lista y rotará el resto hacia abajo, preservando el orden.

Nick Gillum
fuente
Me encanta este proceso de pensamiento, pero D tiene un ID de 1, entonces, ¿no lo ordenaría como C, D, A, B?
David
3
@PhatWrat Claro, así que si quisieras evitar eso, dirías .ThenBy (o => o.name) o algo similar
Nick Gillum
¡Debería ser la mejor respuesta! Gracias
Mantisimo
10

A continuación, se muestra un método de extensión que quizás desee utilizar. Mueve los elementos que coinciden con el predicado dado a la parte superior, conservando el orden.

public static IEnumerable<T> MoveToTop(IEnumerable<T> list, Func<T, bool> func) {
    return list.Where(func)
               .Concat(list.Where(item => !func(item)));
}

En términos de complejidad, creo que haría dos pases en la colección, haciéndola O (n), como la versión Insertar / Eliminar, pero mejor que la sugerencia OrderBy de Jon Skeet.

configurador
fuente
2

Puede "agrupar por" en dos grupos con la clave booleana y luego ordenarlos

var finalList= allCountries
                .GroupBy(x => x.id != 592)
                .OrderBy(g => g.Key)
                .SelectMany(g => g.OrderBy(x=> x.id ));
Filip
fuente
2

Sé que esta es una vieja pregunta pero lo hice así

class Program
{
    static void Main(string[] args)
    {
        var numbers = new int[] { 5, 10, 12, 1 };

        var ordered = numbers.OrderBy(num => num != 10 ? num : -1);

        foreach (var num in ordered)
        {
            Console.WriteLine("number is {0}", num);
        }

        Console.ReadLine();
    }
}

esto imprime:

el número es 10 el
número es 1 el
número es 5 el
número es 12

Gaotter
fuente
1
public static IEnumerable<T> ServeFirst<T>(this IEnumerable<T> source, 
    Predicate<T> p)
{
    var list = new List<T>();

    foreach (var s in source)
    {
        if (p(s))
            yield return s;
        else
            list.Add(s);
    }

    foreach (var s in list)
        yield return s;
}
Grozz
fuente
1

Es interesante la cantidad de enfoques que encuentras cuando intentas resolver un problema.

var service = AutogateProcessorService.GetInstance();
var allConfigs = service.GetAll();
allConfigs = allConfigs.OrderBy(c => c.ThreadDescription).ToList();
var systemQueue = allConfigs.First(c => c.AcquirerId == 0);
allConfigs.Remove(systemQueue);
allConfigs.Insert(0, systemQueue);
Adeola Ojo Gabriel
fuente
1

Para verificar también si el elemento se encontró sin excepción, algo como:

var allCountries = repository.GetCountries();
var lookup = allCountries.ToLookup(x => x.id == 592);  
var finalList = lookup[true].Concat(lookup[false]).ToList();
if ( lookup[true].Count() != 1 ) YouAreInTrouble();
Slai
fuente
0

Escribí un método de extensión estática para hacer esto. Tenga en cuenta que esto no conserva el orden, simplemente intercambia el artículo. Si necesita conservar el orden, debe hacer una rotación, no un simple intercambio.

/// <summary>
/// Moves the item to the front of the list if it exists, if it does not it returns false
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="collection"></param>
/// <param name="predicate"></param>
/// <returns></returns>
public static bool MoveToFrontOfListWhere<T>(this List<T> collection, Func<T, bool> predicate)
{
    if (collection == null || collection.Count <= 0) return false;

    int index = -1;
    for (int i = 0; i < collection.Count; i++)
    {
        T element = collection.ElementAt(i);
        if (!predicate(element)) continue;
        index = i;
        break;
    }

    if (index == -1) return false;

    T item = collection[index];
    collection[index] = collection[0];
    collection[0] = item;
    return true;
}
rollos
fuente