Làm cách nào tôi có thể lấy từng mục thứ n từ Danh sách <T>?


114

Tôi đang sử dụng .NET 3.5 và muốn có thể lấy mọi mục * n* từ một Danh sách. Tôi không bận tâm về việc liệu nó có đạt được bằng cách sử dụng biểu thức lambda hay LINQ hay không.

Biên tập

Có vẻ như câu hỏi này đã gây ra khá nhiều tranh luận (đó là một điều tốt, phải không?). Điều chính tôi đã học được là khi bạn nghĩ rằng bạn biết mọi cách để làm điều gì đó (thậm chí đơn giản như thế này), hãy suy nghĩ lại!


Tôi đã không chỉnh sửa bất kỳ ý nghĩa nào đằng sau câu hỏi ban đầu của bạn; Tôi chỉ làm sạch nó và sử dụng Viết hoa và chấm câu một cách chính xác. (.NET được viết hoa, LINQ ở dạng chữ Caps, và nó không phải là 'lambda', đó là 'biểu thức lambda'.)
George Stocker

1
Bạn đã thay thế "fussed" bằng "chắc chắn", đây không phải là từ đồng nghĩa.
mqp

Có vẻ như vậy. Có chắc chắn cũng không có ý nghĩa gì, trừ khi là "Tôi không chắc liệu nó có thể đạt được bằng cách sử dụng ..."
Samuel

Vâng, theo tôi hiểu, điều đó đúng.
mqp

có lẽ tốt nhất nên được thay thế bằng "lo ngại" để nó có nội dung "Tôi không quan tâm đến việc liệu nó có đạt được bằng cách sử dụng biểu thức lambda hay LINQ hay không."
TheTXI

Câu trả lời:


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

5
@mquander: Lưu ý rằng điều này thực sự sẽ cung cấp cho bạn phần tử thứ n - 1. Nếu bạn muốn các phần tử thứ n thực sự (bỏ qua phần đầu tiên) thì bạn sẽ phải thêm 1 vào i.
casperOne

2
Có, tôi cho rằng nó phụ thuộc vào ý bạn nói "thứ n", nhưng cách hiểu của bạn có thể phổ biến hơn. Thêm hoặc bớt từ i để phù hợp với nhu cầu của bạn.
mqp

5
Chỉ cần lưu ý: Giải pháp Linq / Lambda sẽ kém hiệu suất hơn nhiều so với một vòng lặp đơn giản với số gia cố định.
MartinStettner

5
Không nhất thiết, với thực thi hoãn lại, nó có thể được sử dụng trong một vòng lặp foreach và chỉ lặp lại danh sách ban đầu một lần.
Samuel

1
Nó phụ thuộc vào những gì bạn có nghĩa là "thực tế." Nếu bạn cần một cách nhanh chóng để lấy tất cả các mục khác trong danh sách 30 mục khi người dùng nhấp vào một nút, tôi muốn nói rằng điều này rất thực tế. Đôi khi hiệu suất thực sự không còn quan trọng nữa. Tất nhiên, đôi khi nó có.
mqp

37

Tôi biết đó là "old school", nhưng tại sao không chỉ sử dụng vòng lặp for với step = n?


Về cơ bản đó là suy nghĩ của tôi.
Mark Pim

1
@Michael Todd: Nó hoạt động, nhưng vấn đề với điều đó là bạn phải sao chép chức năng đó ở mọi nơi. Bằng cách sử dụng LINQ, nó trở thành một phần của truy vấn đã soạn.
casperOne

8
@casperOne: Tôi tin rằng các lập trình viên đã phát minh ra thứ gọi là chương trình con để giải quyết vấn đề này;) Trong một chương trình thực, tôi có thể sử dụng một vòng lặp, mặc dù phiên bản LINQ thông minh, vì một vòng lặp có nghĩa là bạn không phải lặp lại mọi phần tử ( tăng chỉ số thêm N.)
mqp

Tôi đồng ý với giải pháp cũ và thậm chí tôi đoán điều này sẽ hoạt động tốt hơn.
Jesper Fyhr Knudsen

Dễ dàng thực hiện với cú pháp mới lạ mắt. Tuy nhiên, niềm vui của nó.
Ronnie

34

Nghe như là

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

sẽ thực hiện thủ thuật. Tôi không thấy cần thiết phải sử dụng biểu thức Linq hoặc lambda.

BIÊN TẬP:

Làm cho nó

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

và bạn viết theo cách LINQish

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

Chỉnh sửa lần 2 :

Để làm cho nó nhiều hơn nữa LINQish

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

2
Tôi thích phương pháp này vì sử dụng phương thức this [] getter thay vì phương thức Where (), về cơ bản nó sẽ lặp lại mọi phần tử của IEnumerable. Nếu bạn có kiểu IList / ICollection, thì đây là cách tiếp cận tốt hơn, IMHO.
spoulson

Không chắc danh sách hoạt động như thế nào, nhưng tại sao bạn lại sử dụng vòng lặp và quay lại list[i]thay vì chỉ trả về list[n-1]?
Juan Carlos Oropeza,

@JuanCarlosOropeza anh ta trả về mọi phần tử thứ n (ví dụ: 0, 3, 6 ...), không chỉ phần tử thứ n của danh sách.
alfoks

27

Bạn có thể sử dụng quá tải Nơi chuyển chỉ mục cùng với phần tử

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

1
Phải nói rằng tôi là một fan hâm mộ của phương pháp này.
Quintin Robinson

1
Tôi cứ quên rằng bạn có thể làm điều đó - rất tuyệt.
Stephen Newman

10

Đối với vòng lặp

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

Số đếm sẽ đánh giá có thể liệt kê. nếu điều này được thực hiện theo cách thân thiện với linq thì bạn có thể lười biếng đánh giá và lấy giá trị 100 đầu tiên, ví dụ: `` source.TakeEvery (5). yếu tố được đánh giá
RhysC

1
@RhysC Điểm tốt, cho các bảng liệt kê nói chung. OTOH, Câu hỏi đã nêu rõ List<T>, vì vậy Countđược định nghĩa là rẻ.
ToolmakerSteve

3

Tôi không chắc liệu có thể thực hiện với biểu thức LINQ hay không, nhưng tôi biết rằng bạn có thể sử dụng Wherephương thức mở rộng để thực hiện. Ví dụ để nhận được mọi vật phẩm thứ năm:

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

Điều này sẽ nhận được mục đầu tiên và mỗi thứ năm từ đó. Nếu bạn muốn bắt đầu ở mục thứ năm thay vì mục đầu tiên, bạn so sánh với 4 thay vì so sánh với 0.


3

Tôi nghĩ rằng nếu bạn cung cấp một phần mở rộng linq, bạn sẽ có thể hoạt động trên giao diện ít cụ thể nhất, do đó trên IEnumerable. Tất nhiên, nếu bạn tăng tốc độ, đặc biệt là cho N lớn, bạn có thể cung cấp quá tải cho truy cập được lập chỉ mục. Mệnh đề thứ hai loại bỏ sự cần thiết phải lặp lại một lượng lớn dữ liệu không cần thiết và sẽ nhanh hơn nhiều so với mệnh đề Where. Việc cung cấp cả hai mức quá tải cho phép trình biên dịch chọn biến thể phù hợp nhất.

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

Điều này làm việc cho bất kỳ Danh sách? bởi vì tôi cố gắng sử dụng trong một Danh sách cho một lớp tùy chỉnh và trả về một <lớp> IEnumarted thay vì <lớp> và buộc che phủ (lớp) List.GetNth (1) cũng không hoạt động.
Juan Carlos Oropeza

Là lỗi của tôi, tôi phải bao gồm GetNth (1) .FirstOrDefault ();
Juan Carlos Oropeza

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

đầu ra

nhập mô tả hình ảnh ở đây


0

Imho không có câu trả lời là đúng. Tất cả các giải pháp bắt đầu từ 0. Nhưng tôi muốn có phần tử thứ n thực

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

0

@belucha Tôi thích điều này, vì mã máy khách rất dễ đọc và Trình biên dịch chọn Cách triển khai hiệu quả nhất. Tôi sẽ xây dựng dựa trên điều này bằng cách giảm các yêu cầu xuống IReadOnlyList<T>và để dành Bộ phận cho LINQ hiệu suất cao:

    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];
        }
    }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.