如何从List<T>中获取每第n个项?

134
我正在使用.NET 3.5,希望能够从列表中获取每个第 *n* 项。 我不关心它是使用 lambda 表达式还是 LINQ 实现的。 编辑 看起来这个问题引起了很多争论(这是一件好事,对吧?)。我学到的主要是,当你认为你知道如何做某件事情时(即使是像这样简单的事情),请再想想!

1
你用“sure”替换了“fussed”,但它们根本不是同义词。 - mqp
看起来是这样。确定也没有意义,除非是“我不确定是否可以使用...” - Samuel
我只在我的地区很少听到/看到它,它往往与乡村方言更密切相关。我不能假设OP从哪里得到它。 - TheTXI
我会说它并不常用。看一下定义:http://www.thefreedictionary.com/fussed。用法:thedorko在他的帖子变得更易读时小题大做。 - Samuel
MartinStettner 的想法是正确的。请注意,Linq 很棒,但除法的代价很高。这是一个例子,迭代比使用利用除法的谓词要便宜得多。如果必须使用 Linq,则 MartinStettner 提供的范围示例最好,因为它只需要进行一次除法运算。 - Real John Connor
显示剩余6条评论
10个回答

230
return list.Where((x, i) => i % nStep == 0);

8
请注意,这将实际给出第n-1个元素。如果您想要实际的第n个元素(跳过第一个),那么您需要在i上加1。 - casperOne
2
是的,我想这在某种程度上取决于你对“nth”的理解,但你的解释可能更为普遍。根据你的需求增加或减少i的值。 - mqp
5
请注意:使用Linq/Lambda的解决方案在性能上将远不如带有固定增量的简单循环。 - MartinStettner
5
不一定,使用延迟执行时,它可以用于foreach循环,并且只会对原始列表进行一次循环。 - Samuel
2
这取决于你所说的“实用”。如果你需要在用户点击按钮时快速获取30个项目列表中的每个其他项目,我会说这同样实用。有时性能真的不再重要。当然,有时候它确实很重要。 - mqp
显示剩余6条评论

41

我知道这很“老派”,但为什么不只是使用步长为n的for循环呢?


那基本上就是我的想法。 - Mark Pim
2
@Michael Todd: 它可以工作,但问题在于您必须在任何地方都复制该功能。通过使用LINQ,它将成为组合查询的一部分。 - casperOne
9
@casperOne:我相信程序员发明了子程序来处理这个问题;在一个真正的程序中,尽管有聪明的LINQ版本,我可能会使用循环,因为循环意味着你不必迭代每个元素(将索引增加N)。 - mqp
1
我同意采用老派的解决方案,而且我甚至猜想这样做会表现更好。 - Jesper Fyhr Knudsen
容易被新的花哨语法所吸引,但这确实很有趣。 - Ronnie

39

听起来像是

IEnumerator<T> GetNth<T>(List<T> list, int n) {
  for (int i=0; i<list.Count; i+=n)
    yield return list[i]
}

这种方法可以解决问题。我认为不需要使用Linq或lambda表达式。

编辑:

将其做成

public static class MyListExtensions {
  public static IEnumerable<T> GetNth<T>(this List<T> list, int n) {
    for (int i=0; i<list.Count; i+=n)
      yield return list[i];
  }
}

你写得像使用 LINQ 一样

from var element in MyList.GetNth(10) select element;

第二次编辑:

为了让它更像LINQ

from var i in Range(0, ((myList.Length-1)/n)+1) select list[n*i];

2
我喜欢使用this[] getter方法而不是Where()方法,后者实际上会迭代IEnumerable的每个元素。如果您有IList/ICollection类型,这是更好的方法,以我的看法。 - spoulson
不确定列表如何工作,但为什么要使用循环并返回list[i]而不是只返回list[n-1] - Juan Carlos Oropeza
@JuanCarlosOropeza 他返回每个第n个元素(例如0,3,6 ...),而不仅仅是列表的第n个元素。 - alfoks
除了手动迭代集合之外,我最喜欢这种方法,因为你永远不会重复迭代相同的值。 - Emperor Eto
这很不错,但我会为 int start = 0 的扩展添加一个额外的参数,以便可以多次使用,比如 .Concat() - The Thirsty Ape

33

你可以使用带有索引的 Where 重载方法,以便将索引与元素一起传递。

var everyFourth = list.Where((x,i) => i % 4 == 0);

1
我必须说,我很喜欢这种方法。 - Quintin Robinson
1
我总是忘记你可以这样做 - 非常好。 - Stephen Newman

10

For循环

for(int i = 0; i < list.Count; i += n)
    //Nth Item..

Count将评估可枚举对象。如果以linq友好的方式完成,则可以惰性评估并获取前100个值,例如
source.TakeEvery(5).Take(100)
如果底层源的评估代价很高,则您的方法将导致评估每个元素。
- RhysC
1
@RhysC 说得好,对于一般的可枚举对象来说是这样。但是问题确切地指定了 List<T>,所以 Count 被定义为廉价操作。 - ToolmakerSteve

6

我认为如果您提供一个Linq扩展,应该能够在最不具体的接口(即IEnumerable接口)上进行操作。当然,如果您想要更快的速度,特别是对于大N值,可以提供一个索引访问的重载。后者可以减少迭代不必要的大量数据的需要,并且比Where子句快得多。提供这两个重载可以让编译器选择最合适的变体。

public static class LinqExtensions
{
    public static IEnumerable<T> GetNth<T>(this IEnumerable<T> list, int n)
    {
        if (n < 0)
            throw new ArgumentOutOfRangeException("n");
        if (n > 0)
        {
            int c = 0;
            foreach (var e in list)
            {
                if (c % n == 0)
                    yield return e;
                c++;
            }
        }
    }
    public static IEnumerable<T> GetNth<T>(this IList<T> list, int n)
    {
        if (n < 0)
            throw new ArgumentOutOfRangeException("n");
        if (n > 0)
            for (int c = 0; c < list.Count; c += n)
                yield return list[c];
    }
}

这适用于任何List吗?因为我尝试在自定义类的List中使用它,并返回一个IEnumarted<class>而不是<class>,并强制转换(class)List.GetNth(1)也不起作用。 - Juan Carlos Oropeza
我的错,我必须包含 GetNth(1).FirstOrDefault(); - Juan Carlos Oropeza

4

我不确定是否可以使用LINQ表达式来实现,但我知道你可以使用Where扩展方法来实现。例如,要获取每五个项目:

List<T> list = originalList.Where((t,i) => (i % 5) == 0).ToList();

这将获取第一个项目和从那里开始的每五个项目。如果您想从第五个项目而不是第一个项目开始,您将与4进行比较而不是与0进行比较。


1

我认为没有一个正确的答案。所有的解决方案都从0开始。但是我想要真正的第n个元素。

public static IEnumerable<T> GetNth<T>(this IList<T> list, int n)
{
    for (int i = n - 1; i < list.Count; i += n)
        yield return list[i];
}

1

@belucha 我喜欢这个,因为客户端代码非常易读,编译器会选择最有效的实现。我想在此基础上进一步简化要求,只需要 IReadOnlyList<T> 并将 Division 保留给高性能 LINQ:

    public static IEnumerable<T> GetNth<T>(this IEnumerable<T> list, int n) {
        if (n <= 0) throw new ArgumentOutOfRangeException(nameof(n), n, null);
        int i = n;
        foreach (var e in list) {
            if (++i < n) { //save Division
                continue;
            }
            i = 0;
            yield return e;
        }
    }

    public static IEnumerable<T> GetNth<T>(this IReadOnlyList<T> list, int n
        , int offset = 0) { //use IReadOnlyList<T>
        if (n <= 0) throw new ArgumentOutOfRangeException(nameof(n), n, null);
        for (var i = offset; i < list.Count; i += n) {
            yield return list[i];
        }
    }

0
private static readonly string[] sequence = "1,2,3,4,5,6,7,8,9,10,11,12,13,14,15".Split(',');

static void Main(string[] args)
{
    var every4thElement = sequence
      .Where((p, index) => index % 4 == 0);

    foreach (string p in every4thElement)
    {
        Console.WriteLine("{0}", p);
    }

    Console.ReadKey();
}

输出

enter image description here


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接