2012-07-13 64 views
96

Tôi đang cố chia danh sách thành một loạt danh sách nhỏ hơn.Chia danh sách thành các danh sách nhỏ hơn có kích thước N

Vấn đề của tôi: Chức năng tách danh sách của tôi không chia chúng thành danh sách có kích thước chính xác. Nó nên chia chúng thành các danh sách có kích thước 30 nhưng thay vào đó nó chia chúng thành các danh sách có kích thước 114?

Làm cách nào để chức năng của tôi tách danh sách thành X số Danh sách có kích thước 30 hoặc nhỏ hơn?

public static List<List<float[]>> splitList(List <float[]> locations, int nSize=30) 
{  
    List<List<float[]>> list = new List<List<float[]>>(); 

    for (int i=(int)(Math.Ceiling((decimal)(locations.Count/nSize))); i>=0; i--) { 
     List <float[]> subLocat = new List <float[]>(locations); 

     if (subLocat.Count >= ((i*nSize)+nSize)) 
      subLocat.RemoveRange(i*nSize, nSize); 
     else subLocat.RemoveRange(i*nSize, subLocat.Count-(i*nSize)); 

     Debug.Log ("Index: "+i.ToString()+", Size: "+subLocat.Count.ToString()); 
     list.Add (subLocat); 
    } 

    return list; 
} 

Nếu tôi sử dụng các chức năng trên một danh sách các kích thước 144 thì đầu ra là:

Index: 4, Kích thước: 120
Index: 3, Kích thước: 114
Index: 2 , Kích thước: 114
Index: 1, Kích thước: 114
Index: 0, Kích thước: 114

+1

Nếu một giải pháp LINQ được chấp nhận, [câu hỏi này có thể có một số trợ giúp] (http://stackoverflow.com/questions/419019/split) -list-into-sublists-với-linq). –

+0

Câu trả lời cụ thể của Sam Saffron về câu hỏi trước đó. Và trừ khi điều này là cho một bài tập ở trường, tôi sẽ chỉ sử dụng mã của anh ấy và dừng lại. – jcolebrand

Trả lời

110
public static List<List<float[]>> splitList(List<float[]> locations, int nSize=30) 
{   
    var list = new List<List<float[]>>(); 

    for (int i=0; i < locations.Count; i+= nSize) 
    { 
     list.Add(locations.GetRange(i, Math.Min(nSize, locations.Count - i))); 
    } 

    return list; 
} 

Generic phiên bản:

public static IEnumerable<List<T>> splitList<T>(List<T> locations, int nSize=30) 
{   
    for (int i=0; i < locations.Count; i+= nSize) 
    { 
     yield return locations.GetRange(i, Math.Min(nSize, locations.Count - i)); 
    } 
} 
+1

Loveee 'yield return' – lostmylogin

+0

Vì vậy, nếu tôi có một danh sách chiều dài zillion, và tôi muốn chia thành các danh sách nhỏ hơn 30, và từ mỗi danh sách nhỏ hơn tôi chỉ muốn Take (1), sau đó tôi vẫn tạo danh sách 30 mặt hàng mà tôi vứt đi 29 vật phẩm. Điều này có thể được thực hiện thông minh hơn! –

24

thế nào về:

while(locations.Any()) 
{  
    list.Add(locations.Take(nSize).ToList()); 
    locations= locations.Skip(nSize).ToList(); 
} 
+0

Điều này sẽ tiêu tốn rất nhiều bộ nhớ? Mỗi vị trí.Skip.ToList xảy ra tôi tự hỏi nếu có nhiều bộ nhớ được cấp phát và các mục không được trả lời được tham chiếu bởi một danh sách mới. – Zasz

+0

có danh sách mới được tạo trên mỗi vòng lặp. Vâng, nó tiêu thụ trí nhớ. Nhưng nếu bạn đang gặp vấn đề về bộ nhớ thì đây không phải là nơi để tối ưu hóa vì các cá thể của các danh sách đó đã sẵn sàng để được thu thập trên vòng lặp tiếp theo. Bạn có thể giao dịch hiệu suất cho bộ nhớ bằng cách bỏ qua 'ToList' nhưng tôi sẽ không bận tâm cố gắng tối ưu hóa nó - nó rất tầm thường và khó có thể là một nút cổ chai. Lợi ích chính từ việc thực hiện này là tầm thường của nó, nó rất dễ hiểu. Nếu bạn muốn bạn có thể sử dụng câu trả lời được chấp nhận, nó không tạo ra các danh sách đó nhưng phức tạp hơn một chút. – Rafal

+0

'.Skip (n)' lặp lại trên các phần tử 'n' mỗi khi nó được gọi, trong khi điều này có thể được, điều quan trọng là phải xem xét cho mã quan trọng về hiệu suất. http://stackoverflow.com/questions/20002975/performance-of-skip-and-similar-functions-like-take – Chakrava

5

Tôi có một phương pháp chung chung mà phải mất bất kỳ loại bao gồm phao, và đơn vị thử nghiệm nó được, hy vọng nó sẽ giúp:

/// <summary> 
    /// Breaks the list into groups with each group containing no more than the specified group size 
    /// </summary> 
    /// <typeparam name="T"></typeparam> 
    /// <param name="values">The values.</param> 
    /// <param name="groupSize">Size of the group.</param> 
    /// <returns></returns> 
    public static List<List<T>> SplitList<T>(IEnumerable<T> values, int groupSize, int? maxCount = null) 
    { 
     List<List<T>> result = new List<List<T>>(); 
     // Quick and special scenario 
     if (values.Count() <= groupSize) 
     { 
      result.Add(values.ToList()); 
     } 
     else 
     { 
      List<T> valueList = values.ToList(); 
      int startIndex = 0; 
      int count = valueList.Count; 
      int elementCount = 0; 

      while (startIndex < count && (!maxCount.HasValue || (maxCount.HasValue && startIndex < maxCount))) 
      { 
       elementCount = (startIndex + groupSize > count) ? count - startIndex : groupSize; 
       result.Add(valueList.GetRange(startIndex, elementCount)); 
       startIndex += elementCount; 
      } 
     } 


     return result; 
    } 
+0

Cảm ơn. Tự hỏi liệu bạn có thể cập nhật các nhận xét với định nghĩa tham số maxCount không? Một mạng lưới an toàn? –

222

Tôi khuyên bạn nên sử dụng phương pháp mở rộng này để tách danh sách nguồn thành các danh sách phụ theo kích thước chunk được chỉ định:

/// <summary> 
/// Helper methods for the lists. 
/// </summary> 
public static class ListExtensions 
{ 
    public static List<List<T>> ChunkBy<T>(this List<T> source, int chunkSize) 
    { 
     return source 
      .Select((x, i) => new { Index = i, Value = x }) 
      .GroupBy(x => x.Index/chunkSize) 
      .Select(x => x.Select(v => v.Value).ToList()) 
      .ToList(); 
    } 
} 

Ví dụ, nếu bạn bỏ qua danh sách 18 mục theo 5 mục cho mỗi đoạn, nó sẽ cung cấp cho bạn danh sách 4 danh sách con với các mục sau đây bên trong: 5-5-5-3.

+7

giải pháp tuyệt vời – MonsterMMORPG

+3

Trước khi bạn sử dụng giải pháp này trong sản xuất, hãy đảm bảo bạn hiểu ý nghĩa của thời gian chạy cho bộ nhớ và hiệu suất là gì. Chỉ vì LINQ có thể ngắn gọn, không có nghĩa đó là một ý tưởng hay. – Nick

+3

Chắc chắn, @Nick tôi sẽ đề nghị nói chung để suy nghĩ trước khi làm bất cứ điều gì. Chunking với LINQ không phải là một hoạt động thường xuyên lặp đi lặp lại hàng ngàn lần. Thông thường, bạn cần phải xếp các danh sách để xử lý các mục theo lô và/hoặc song song. –

9

giải pháp Serj-Tm là tốt, cũng có thể đây là phiên bản generic như phương pháp khuyến nông cho các danh sách (đặt nó vào một lớp tĩnh):

public static List<List<T>> Split<T>(this List<T> items, int sliceSize = 30) 
{ 
    List<List<T>> list = new List<List<T>>(); 
    for (int i = 0; i < items.Count; i += sliceSize) 
     list.Add(items.GetRange(i, Math.Min(sliceSize, items.Count - i))); 
    return list; 
} 
6

Tôi tìm thấy câu trả lời được chấp nhận (Serj-Tm) mạnh mẽ nhất, nhưng tôi muốn đề xuất một phiên bản chung.

public static List<List<T>> splitList<T>(List<T> locations, int nSize = 30) 
    { 
     var list = new List<List<T>>(); 

     for (int i = 0; i < locations.Count; i += nSize) 
     { 
      list.Add(locations.GetRange(i, Math.Min(nSize, locations.Count - i))); 
     } 

     return list; 
    } 
1

Thư viện MoreLinq có phương pháp gọi là Batch

List<int> ids = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 }; // 10 elements 
int counter = 1; 
foreach(var batch in ids.Batch(2)) 
{ 
    foreach(var eachId in batch) 
    { 
     Console.WriteLine("Batch: {0}, Id: {1}", counter, eachId); 
    } 
    counter++; 
} 

Kết quả là

Batch: 1, Id: 1 
Batch: 1, Id: 2 
Batch: 2, Id: 3 
Batch: 2, Id: 4 
Batch: 3, Id: 5 
Batch: 3, Id: 6 
Batch: 4, Id: 7 
Batch: 4, Id: 8 
Batch: 5, Id: 9 
Batch: 5, Id: 0 

ids được tách thành 5 khối với 2 yếu tố.

+0

Cảm ơn bạn đã nói về [ModeLinq] (https://morelinq.github.io/). Đó là một thư viện tuyệt vời. –

2

Mặc dù hầu hết các giải pháp đều có thể hoạt động, tôi nghĩ chúng không hiệu quả lắm. Giả sử nếu bạn chỉ muốn một vài mục đầu tiên của một vài đoạn đầu tiên. Sau đó, bạn sẽ không muốn lặp qua tất cả (zillion) mục trong chuỗi của bạn.

Sau đây sẽ liệt kê tối đa hai lần: một lần cho phần Lấy và một lần cho Bỏ qua. Nó sẽ không liệt kê thêm bất kỳ yếu tố nào bạn sẽ sử dụng:

public static IEnumerable<IEnumerable<TSource>> ChunkBy<TSource> 
    (this IEnumerable<TSource> source, int chunkSize) 
{ 
    while (source.Any())      // while there are elements left 
    { // still something to chunk: 
     yield return source.Take(chunkSize); // return a chunk of chunkSize 
     source = source.Skip(chunkSize);  // skip the returned chunk 
    } 
} 
Các vấn đề liên quan