2011-06-28 20 views
9

Tôi đang lặp lại thông qua một bộ sưu tập sử dụng mẫu kiểu khách truy cập và cần truy cập mục hiện tại và tiếp theo trong danh sách. Hiện tại, tôi đang làm điều đó thông qua một phương pháp mở rộng như thế nàyLINQ - lookahead Iteration

public void Visit<TItem>(this IEnumerable<TItem> theList, Action<TItem, TItem> visitor) 
{ 
    for (i = 0; i <= theList.Count - 1; i++) { 
     if (i == theList.Count - 1) { 
      visitor(theList(i), null); 
     } else { 
      visitor(theList(i), theList(i + 1)); 
     }  
    }  
} 

Tôi đã tự hỏi liệu có những cách nào khác/tốt hơn/thanh lịch hơn để đạt được điều này không? Tại thời điểm này tôi nghĩ rằng tôi chỉ cần có quyền truy cập vào các mục hiện tại và tiếp theo trong danh sách, nhưng tôi tự hỏi liệu tôi có thể gặp phải các tình huống mà tôi có thể cần phải xem xét các mục 'n' tiếp theo, ví dụ.

+1

Mã của bạn thậm chí sẽ không biên dịch. Không có thuộc tính nào có tên 'Count' trên' IEnumerable '(có một nhóm phương pháp). – jason

+0

@ Jason - thx - Tôi đã viết bản gốc bằng VB (có vẻ như hỗ trợ Đếm trên IEnumerable!) Và được chuyển đổi - điều đó sẽ dạy tôi! Thx –

Trả lời

9

Giả sử bạn đang sử dụng .NET 4, bạn có thể sử dụng Zip để thực hiện điều tương tự:

var query = original.Zip(original.Skip(1), 
         (current, next) => new { current, next }); 

này sẽ lặp qua chuỗi hai lần mặc dù. Một thay thế đẹp hơn cho phương pháp mở rộng hiện tại của bạn (mà tôi không tin là sẽ hoạt động, btw, vì IEnumerable không có tài sản Count và bạn đang cố gắng gọi theList làm phương thức ...) sẽ là thứ gì đó như:

public static void Visit<TItem>(this IEnumerable<TItem> theList, 
         Action<TItem, TItem> visitor) 
{ 
    TItem prev = default(TItem); 
    using (var iterator = theList.GetEnumerator()) 
    { 
     if (!iterator.MoveNext()) 
     { 
      return; 
     } 
     prev = iterator.Current; 
     while (iterator.MoveNext()) 
     { 
      TItem current = iterator.Current; 
      visitor(prev, current); 
      prev = current; 
     } 
    } 
    visitor(prev, default(TItem)); // Are you sure you want this? 
} 

Một lookahead tổng quát hơn là phức tạp hơn, phải trung thực ... bạn muốn một số loại đệm tròn, tôi nghi ngờ ... có thể là một bộ sưu tập tùy chỉnh.

+0

@Jon Skeet - thx cho việc này. Bạn có thể giải thích câu hỏi của mình ở dòng cuối cùng không? Lý do của tôi là nếu danh sách chỉ có 1 mục, tôi vẫn muốn khách truy cập xử lý nó. Nhưng có lẽ tôi đang thiếu một cái gì đó –

+0

@ Simon: Vâng, nó có nghĩa là bạn không thể phân biệt được khách truy cập giữa một cặp trong đó một mục xảy ra là giá trị mặc định (null, 0, bất kỳ) và cặp cuối cùng chỉ có một mục là thật". –

+0

@Jon - ah, vâng. Điểm tốt. Vì vậy, bạn cần xử lý phần tử cuối cùng sau khi truy cập danh sách? Hay bạn đang nghĩ về một số cách khác - cờ/giá trị đặc biệt hay gì đó? –

1

Có vẻ như bạn đang sử dụng sai loại. Hành động lập chỉ mục một chuỗi sẽ lặp lại nó cho đến khi nó đạt đến chỉ mục được chỉ định mỗi lần. Tại sao không sử dụng IList<T> hoặc ReadOnlyCollection<T>?

1

Không được thử nghiệm, nhưng tôi nghĩ điều này có hiệu quả không? Khi lượt truy cập vượt quá giới hạn, nó sẽ lặp lại phía trước danh sách.

public class FriendlyEnumerable<T> : IEnumerable<T> 
{ 
    private IEnumerable<T> _enum; 

    public FriendlyEnumerable(IEnumerable<T> enumerable) 
    {    
     _enum = enumerable; 
    } 

    public void VisitAll(Action<T, T> visitFunc) 
    { 
     VisitAll(visitFunc, 1); 
    } 

    public void VisitAll(Action<T, T> visitFunc, int lookahead) 
    { 
     int index = 0; 
     int length = _enum.Count(); 
     _enum.ToList().ForEach(t => 
     { 
      for (int i = 1; i <= lookahead; i++) 
       visitFunc(t, _enum.ElementAt((index + i) % length)); 
      index++; 
     }); 
    } 

    #region IEnumerable<T> Members 
    public IEnumerator<T> GetEnumerator() 
    { 
     return _enum.GetEnumerator(); 
    } 
    #endregion 
} 

Bạn có thể sử dụng nó như:

List<string> results = new List<string>(); 
List<string> strings = new List<string>() 
    { "a", "b", "c", "d", "a", "b", "c", "d" }; 
FriendlyEnumerable<string> fe = new FriendlyEnumerable<string>(strings); 
Action<string, string> compareString = 
    new Action<string,string>((s1, s2) => 
     { 
      if (s1 == s2) 
       results.Add(s1 + " == " + s2); 
     }); 
fe.VisitAll(compareString); 
//no results 
fe.VisitAll(compareString, 4); 
//8 results 
1
public static void VisitLookAhead<TItem>(
    this IEnumerable<TItem> source, 
    Action<IEnumerable<TItem>> visitor, 
    int targetSize 
) 
{ 
    if (targetSize <= 1) 
    { 
    throw new Exception("invalid targetSize for VisitLookAhead"); 
    } 

    List<List<TItem>> collections = new List<List<TItem>>(); 

// after 6th iteration with targetSize 6 
//1, 2, 3, 4, 5, 6 <-- foundlist 
//2, 3, 4, 5, 6 
//3, 4, 5, 6 
//4, 5, 6 
//5, 6 
//6 
    foreach(TItem x in source) 
    { 
    collections.Add(new List<TItem>()); 
    collections.ForEach(subList => subList.Add(x)); 
    List<TItem> foundList = collections 
     .FirstOrDefault(subList => subList.Count == targetSize); 
    if (foundList != null) 
    { 
     collections.Remove(foundList); 
     visitor(foundList); 
    } 
    } 

    //generate extra lists at the end - when lookahead will be missing items. 
    foreach(int i in Enumerable.Range(1, targetSize) 
    { 
    collections.ForEach(subList => subList.Add(default(TItem))); 
    List<TItem> foundList = collections 
     .FirstOrDefault(subList => subList.Count == targetSize); 
    if (foundList != null) 
    { 
     collections.Remove(foundList); 
     visitor(foundList); 
    } 
    } 
} 
+0

+1 cho điều này. thx vm –

4

Khi chúng tôi chạy vào một nhiệm vụ tương tự, chúng tôi đã xác định một phương pháp khuyến nông:

/// <summary> 
/// Projects a window of source elements in a source sequence into target sequence. 
/// Thus 
/// target[i] = 
///  selector(source[i], source[i - 1], ... source[i - window + 1]) 
/// </summary> 
/// <typeparam name="T">A type of elements of source sequence.</typeparam> 
/// <typeparam name="R">A type of elements of target sequence.</typeparam> 
/// <param name="source">A source sequence.</param> 
/// <param name="window">A size of window.</param> 
/// <param name="lookbehind"> 
/// Indicate whether to produce target if the number of source elements 
/// preceeding the current is less than the window size. 
/// </param> 
/// <param name="lookahead"> 
/// Indicate whether to produce target if the number of source elements 
/// following current is less than the window size. 
/// </param> 
/// <param name="selector"> 
/// A selector that derives target element. 
/// On input it receives: 
/// an array of source elements stored in round-robing fashon; 
/// an index of the first element; 
/// a number of elements in the array to count. 
/// </param> 
/// <returns>Returns a sequence of target elements.</returns> 
public static IEnumerable<R> Window<T, R>(
    this IEnumerable<T> source, 
    int window, 
    bool lookbehind, 
    bool lookahead, 
    Func<T[], int, int, R> selector) 
{ 
    var buffer = new T[window]; 
    var index = 0; 
    var count = 0; 

    foreach(var value in source) 
    { 
    if (count < window) 
    { 
     buffer[count++] = value; 

     if (lookbehind || (count == window)) 
     { 
     yield return selector(buffer, 0, count); 
     } 
    } 
    else 
    { 
     buffer[index] = value; 
     index = index + 1 == window ? 0 : index + 1; 

     yield return selector(buffer, index, count); 
    } 
    } 

    if (lookahead) 
    { 
    while(--count > 0) 
    { 
     index = index + 1 == window ? 0 : index + 1; 

     yield return selector(buffer, index, count); 
    } 
    } 
} 

/// <summary> 
/// Projects a window of source elements in a source sequence into a 
/// sequence of window arrays. 
/// </summary> 
/// <typeparam name="T">A type of elements of source sequence.</typeparam> 
/// <typeparam name="R">A type of elements of target sequence.</typeparam> 
/// <param name="source">A source sequence.</param> 
/// <param name="window">A size of window.</param> 
/// <param name="lookbehind"> 
/// Indicate whether to produce target if the number of source elements 
/// preceeding the current is less than the window size. 
/// </param> 
/// <param name="lookahead"> 
/// Indicate whether to produce target if the number of source elements 
/// following current is less than the window size. 
/// </param> 
/// <returns>Returns a sequence of windows.</returns> 
public static IEnumerable<T[]> Window<T>(
    this IEnumerable<T> source, 
    int window, 
    bool lookbehind, 
    bool lookahead) 
{ 
    return source.Window(
    window, 
    lookbehind, 
    lookahead, 
    (buffer, index, count) => 
    { 
     var result = new T[count]; 

     for(var i = 0; i < count; ++i) 
     { 
     result[i] = buffer[index]; 
     index = index + 1 == buffer.Length ? 0 : index + 1; 
     } 

     return result; 
    }); 
} 

Những chức năng giúp đỡ để tạo ra sản lượng các phần tử từ cửa sổ các phần tử đầu vào.

Xem thêm LINQ extensions.