Başka bir liste kimliklerinden bir listeyi sıralama


158

Bunun gibi bazı tanımlayıcıları içeren bir listem var:

List<long> docIds = new List<long>() { 6, 1, 4, 7, 2 };

Üstelik, <T>yukarıda açıklanan kimliklerle temsil edilen başka bir öğe listem var .

List<T> docs = GetDocsFromDb(...)

Her iki koleksiyonda da aynı sırayı korumam gerekiyor, böylece içindeki öğelerin List<T>ilkinden aynı konumda olması gerekiyor (arama motoru puanlama nedenlerinden dolayı). Ve bu işlem GetDocsFromDb()işlevde yapılamaz .

Gerekirse, ikinci listeyi başka bir yapıya değiştirmek mümkündür ( Dictionary<long, T>örneğin), ancak onu değiştirmemeyi tercih ederim.

LINQ ile bunu "bazı kimliklere bağlı olarak" yapmanın basit ve etkili bir yolu var mı?


Her şeyin docIdtam olarak bir kez gerçekleştiğinden emin misiniz docs, hangi mülkte Idbir seçici Func<T, long>bulunacak veya bir seçici gerekli olacaktır?
Jodrell

İlk liste bir "ana liste" yi mi temsil ediyor? Başka bir deyişle, ikinci liste, birinci listenin bir bölümünü (veya tamamını) temsil eden bir alt küme mi olacak?
code4life

Yanıtlar:


347
docs = docs.OrderBy(d => docsIds.IndexOf(d.Id)).ToList();

@Kaf bu yüzden ben de oy verdim, belge kimliği özelliğinin çağrıldığını bilmeye güveniyor Id. Soruda belirtilmedi.
Jodrell

3
@ BorjaL López, hızlı bir not. Sorunuzda verimlilikten bahsediyorsunuz. IndexOförneğiniz için tamamen kabul edilebilir ve hoş ve basittir. Çok fazla veriniz varsa, cevabım daha uygun olabilir. stackoverflow.com/questions/3663014/…
Jodrell

2
@DenysDenysenko Harika. Çok teşekkürler; tam olarak aradığım şey.
silkfire

3
sipariş listesinde kimlikleri olmayan dokümanlardaki öğeleriniz varsa çalışmaz
Dan Hunex

4
Oldukça verimsiz - IndexOf, kaynak koleksiyondaki her öğe için çağrılıyor ve OrderBy'nin öğeleri sıralaması gerekiyor. @Jodrell'in çözümü çok daha hızlı.
sdds

28

Eğer belirtmediğinden T,

IEnumerable<T> OrderBySequence<T, TId>(
       this IEnumerable<T> source,
       IEnumerable<TId> order,
       Func<T, TId> idSelector)
{
    var lookup = source.ToDictionary(idSelector, t => t);
    foreach (var id in order)
    {
        yield return lookup[id];
    }
}

İstediğiniz şey için genel bir uzantıdır.

Uzantıyı şu şekilde kullanabilirsiniz, belki,

var orderDocs = docs.OrderBySequence(docIds, doc => doc.Id);

Daha güvenli bir versiyon olabilir

IEnumerable<T> OrderBySequence<T, TId>(
       this IEnumerable<T> source,
       IEnumerable<TId> order,
       Func<T, TId> idSelector)
{
    var lookup = source.ToLookup(idSelector, t => t);
    foreach (var id in order)
    {
        foreach (var t in lookup[id])
        {
           yield return t;
        }
    }
}

sourcetam olarak sıkıştırılmazsa işe yarayacaktır order.


Bu çözümü kullandım ve işe yaradı. Sadece bu, yöntemi statik ve sınıfı statik yapmalıydım.
vinmm

5

Jodrell'in cevabı en iyisi, ama aslında yeniden uyguladı System.Linq.Enumerable.Join. Join ayrıca Lookup'ı kullanır ve kaynak sırasını tutar.

    docIds.Join(
      docs,
      i => i,
      d => d.Id,
      (i, d) => d);

Aradığımız cevap bu
Orace

4
Bu sadece Join'in anlaşılmasının çok zor olduğunu kanıtlıyor çünkü herkes yeniden yazmanın daha kolay olduğu konusunda hemfikir.
PRMan

-3

Basit bir yaklaşım, sipariş sırasını sıkıştırmaktır:

List<T> docs = GetDocsFromDb(...).Zip(docIds, Tuple.Create)
               .OrderBy(x => x.Item2).Select(x => x.Item1).ToList();

neden bir fermuar sonrası sipariş?
Jodrell

Çünkü Zip her bir dizini (bir Tuple olarak) ilgili listede aynı konumda bulunan belge ile birleştirir. Sonra OrderBy, Tupleları indeks kısmına göre sıralar ve ardından seçme, şimdi sıralanan listedeki dokümanlarımızı kazar.
Albin Sunnanbo

ancak GetDocsFromDb'nin sonucu sıralanmamış olduğundan Item1, ile ilgisiz olan Tuple'lar oluşturacaksınız Item2.
Jodrell

1
Bence bu, endeks yerine id uppon gerçekleştiren siparişten dolayı yanlış sonuçlar üretecektir.
Michael Logutov
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.