Użyj LINQ, aby przenieść element na górę listy

81

Czy istnieje sposób na przeniesienie elementu powiedzmy id = 10 jako pierwszego elementu na liście przy użyciu LINQ?

Pozycja A - id = 5
Pozycja B - id = 10
Pozycja C - id = 12
Pozycja D - id = 1

W takim przypadku jak mogę elegancko przenieść element C na początek mojej List<T>kolekcji?

Oto najlepsze, jakie mam teraz:

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
źródło
Czy chciałbyś zamienić przedmiot na element znajdujący się na górze, czy też obrócić elementy, popychając wszystkie elementy, aż znaleziony element będzie w dół.
AnthonyWJones
didn; t finalList .insert (0, "nowe rzeczy"); praca
Rohit Kumar

Odpowiedzi:

52

LINQ jest skuteczny w wykonywaniu zapytań dotyczących kolekcji, tworzeniu prognoz na istniejące zapytania lub generowaniu nowych zapytań na podstawie istniejących kolekcji. Nie jest to narzędzie do ponownego zamawiania istniejących kolekcji w trybie inline. Do tego typu operacji najlepiej używać tego typu.

Zakładając, że masz typ o podobnej definicji jak poniżej

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

Następnie spróbuj wykonać następujące czynności

List<Item> list = GetTheList();
var index = list.FindIndex(x => x.Id == 12);
var item = list[index];
list[index] = list[0];
list[0] = item;
JaredPar
źródło
4
+1 Działa dobrze w scenariuszu zamiany, czuję, że rotacja jest faktycznie wymagana,
mimo że
To mniej więcej to, co zrobiłem w jakikolwiek sposób, ale dzięki za wyjaśnienie, dlaczego pozornie nie ma lepszego sposobu :)
qui
6
W celu obsługi błędów pamiętaj, że powinieneś sprawdzić FindIndexwartość wyniku, jest to -1, jeśli element nie zostanie znaleziony na liście.
schnaader
Czy nie jest to po prostu zamiana pierwszego elementu z indeksem elementu docelowego, a nie przeniesienie elementu docelowego na górę i przeniesienie wszystkiego innego w dół?
frostshoxx
143

Co chcesz zamówić, oprócz znanego topowego przedmiotu? Jeśli cię to nie obchodzi, możesz to zrobić:

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

Zasadniczo „fałsz” występuje przed „prawda” ...

Wprawdzie nie wiem, co to robi w LINQ to SQL itp. Być może trzeba będzie powstrzymać go od wykonywania kolejności w bazie danych:

var query = allCountries.AsEnumerable()
                        .OrderBy(x => x.id != 592)
                        .ToList();
Jon Skeet
źródło
1
jego nie działa zgodnie z oczekiwaniami w przypadku LINQ to SQL. Właśnie to przetestowałem.
Yasser Shaikh
5
+1 dzięki Jon. Chciałem zamówić według nazwy, ale zostawiłem element z id = 0 na górze, więc zrobiłem to: allCountries.OrderBy (x => x.id == 0? "00000": x.Name) .ToList (); wydajność nie jest problemem, ponieważ lista jest niewielka.
nima
3
Dla kogoś, kto później przegląda kod, może nie być oczywiste, że wartości logiczne są uporządkowane jako „fałsz, prawda”. Poleciłbym bardziej rozwlekłe rozwiązania.
rymdsmurf
1
Piękny! Chciałem mieć Namemałą listę na górze lub indeks 0, w LINQ to Entities, i to załatwiło sprawę dzięki +1. db.Systms.Where(s => s.IsActive == true).OrderBy(x => x.SystemName != "Portal Administration").ToList();
Irfan
Świetne rzeczy! Ładne proste rozwiązanie. Dzięki,
Hugo Nava Kopp
43

Linq generalnie działa na Enumerables, więc teraz nie jest, że typem bazowym jest kolekcja. Więc do przeniesienia pozycji na górę listy sugerowałbym użycie czegoś takiego (jeśli chcesz zachować kolejność)

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

Jeśli funkcja zwraca tylko element IEnumerable, możesz użyć ToList()metody, aby najpierw przekonwertować ją na Listę

Jeśli nie zachowasz kolejności, możesz po prostu zamienić wartości na pozycji 0 i pozycji idx

Siwy
źródło
Jest to idealne rozwiązanie dla scenariusza rotacji w dół zamiast zwykłej zamiany wartości.
Bradley Mountford
36
var allCountries = repository.GetCountries();
allCountries.OrderByDescending(o => o.id == 12).ThenBy(o => o.id) 

Spowoduje to wstawienie obiektu o id = 12 na górze listy i obrócenie reszty w dół, zachowując kolejność.

Nick Gillum
źródło
Uwielbiam ten proces myślowy, ale D ma identyfikator 1, więc czy to nie uporządkuje go jako C, D, A, B?
David
3
@PhatWrat Jasne, więc jeśli chcesz tego uniknąć, możesz powiedzieć .ThenBy (o => o.name) lub coś podobnego
Nick Gillum
1
Powinna być najlepszą odpowiedzią! Dzięki
Mantisimo,
10

Oto metoda rozszerzenia, której możesz chcieć użyć. Przenosi element (y) pasujące do danego predykatu na górę, zachowując kolejność.

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

Jeśli chodzi o złożoność, myślę, że zrobiłby to dwa przejścia w kolekcji, czyniąc ją O (n), podobnie jak wersja Insert / Remove, ale lepiej niż sugestia Jona Skeeta OrderBy.

konfigurator
źródło
2

Możesz „grupować według” w dwie grupy za pomocą klucza boolowskiego, a następnie je sortować

var finalList= allCountries
                .GroupBy(x => x.id != 592)
                .OrderBy(g => g.Key)
                .SelectMany(g => g.OrderBy(x=> x.id ));
Filip
źródło
2

Wiem, że to stare pytanie, ale zrobiłem to w ten sposób

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

to drukuje:

liczba to 10
liczba to 1
liczba to 5
liczba to 12

Gaotter
źródło
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
źródło
1

Interesująca jest liczba podejść, które można znaleźć, próbując rozwiązać problem.

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
źródło
1

Aby również sprawdzić, czy element został znaleziony bez wyjątku, na przykład:

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
źródło
0

W tym celu napisałem statyczną metodę rozszerzenia. Pamiętaj, że to nie zachowuje kolejności, po prostu zamienia element. Jeśli chcesz zachować kolejność, powinieneś wykonać rotację, a nie prostą zamianę.

/// <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;
}
rolki
źródło