使用LINQ将项目移到列表顶部


80

有没有办法使用LINQ将id = 10的项作为列表中的第一项移动?

项目A-id = 5
项目B-id = 10
项目C-id = 12
项目D-id = 1

在这种情况下,如何优雅地将商品C移到List<T>收藏夹的顶部?

这是我目前拥有的最好的:

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

您是要与顶部项目交换项目,还是要通过推所有项目直到找到的项目向下旋转项目。
AnthonyWJones

没有finalList .insert(0,“ neww stuff”); 工作
Rohit Kumar,

Answers:


51

LINQ在查询集合,在现有查询上创建投影或基于现有集合生成新查询方面很强大。它并不意味着是对现有集合进行内联重新排序的工具。对于这种类型的操作,最好使用方便的类型。

假设您有一个具有类似以下定义的类型

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

然后尝试以下

List<Item> list = GetTheList();
var index = list.FindIndex(x => x.Id == 12);
var item = list[index];
list[index] = list[0];
list[0] = item;

4
+1在交换方案中效果很好,我觉得“实际上需要进行轮换操作”
AnthonyWJones,

这或多或少是我所做的任何事情,但是感谢您对为什么似乎没有更好的方法的解释:)
qui

6
对于错误处理,请注意,您应该检查FindIndex结果值,如果在列表中未找到该项目,则为-1。
schnaader

这不是只是将第一个元素与目标元素索引交换,而不是将目标元素移至顶部并将其他所有元素向下移吗?
frostshoxx

140

除了已知的顶级商品,您还想订购什么?如果您不在乎,则可以执行以下操作:

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

基本上,“ false”先于“ true” ...

诚然,我不知道这在LINQ to SQL等中的作用。您可能需要阻止它在数据库中进行排序:

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

1
对于LINQ to SQL,它无法正常工作。我刚刚测试过。
Yasser Shaikh 2012年

5
+1谢谢乔恩。我想按名称排序,但将id = 0的商品放在最上面,所以我这样做:allCountries.OrderBy(x => x.id == 0?“ 00000”:x.Name).ToList(); 性能不是问题,因为列表很小。
尼玛(Nima)

3
对于以后查看代码的人来说,布尔值的顺序为“ false,true”可能并不明显。我会建议更详细的解决方案。
rymdsmurf

1
美丽!我想Name在LINQ to Entities的顶部或索引0的小列表中找到一个,并且完成了“谢谢+1”的技巧。 db.Systms.Where(s => s.IsActive == true).OrderBy(x => x.SystemName != "Portal Administration").ToList();
Irfaan '16

好东西!一个不错的简单解决方案。谢谢,
雨果·纳瓦·科普

43

Linq通常可在Enumerables上使用,因此现在基本类型不是集合。因此,为了将商品移至列表顶部,我建议使用类似的内容(如果您需要保留订单)

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

如果您的函数仅返回IEnumerable,则可以使用该ToList()方法先将其转换为List

如果您不保留订单,则可以简单地交换位置0和位置idx的值


这是向下旋转方案的理想选择,而不仅仅是交换值。
布拉德利·芒特福德

35
var allCountries = repository.GetCountries();
allCountries.OrderByDescending(o => o.id == 12).ThenBy(o => o.id) 

这会将id = 12的对象插入列表的顶部,并将其余部分向下旋转,从而保留顺序。


我喜欢这个思考过程,但是D的ID为1,所以不以此顺序将其作为C,D,A,B吗?
大卫,

3
@PhatWrat当然,如果您想避免这种情况,可以说.ThenBy(o => o.name)或类似名称
Nick Gillum

应该是最好的答案!谢谢
Mantisimo

10

这是您可能要使用的扩展方法。它将与给定谓词匹配的元素移到顶部,并保持顺序。

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

就复杂性而言,我认为它将对集合进行两次传递,使其像插入/删除版本一样成为O(n),但优于Jon Skeet的OrderBy建议。


2

您可以使用布尔键将“分组依据”分为两组,然后对其进行排序

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

2

我知道这是一个老问题,但我是这样做的

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

打印:

数字是10
数字是1
数字是5
数字是12


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

1

有趣的是,您尝试解决问题时会发现许多方法。

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

1

还可以检查是否找到了没有异常的项目,例如:

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

0

我写了一个静态扩展方法来做到这一点。请注意,这不会保留订单,只是将项目换出。如果需要保留订单,则应该轮换而不是简单的交换。

/// <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;
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.