2011-06-28 59 views
9

我使用訪問類型模式迭代通過集合,並需要訪問列表中的當前項目和下一個項目。目前,我通過一個擴展方法做這樣Linq - 前瞻迭代

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

我想知道是否有其他/更好/更優雅的方式來實現這一目標?目前我認爲我只需要訪問列表中的當前項目和下一個項目,但是我想知道我是否會遇到需要預測下一個「n」項目的情況。

+1

你的代碼甚至不會編譯。 IEnumerable '沒有名爲'Count'的屬性(有一個方法組)。 – jason

+0

@Jason - thx - 我在VB中編寫了原文(它似乎支持IEnumerable上的Count)並轉換 - 這會教會我! Thx –

回答

9

假設你正在使用.NET 4,你可以使用Zip來完成同樣的事情:

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

遍歷序列雖然兩次。一個更好的替代你當前的擴展方法(我不相信會工作,順便說一句,因爲IEnumerable沒有Count屬性,並且你試圖調用theList作爲一種方法...)將是像:

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

一個更普遍的前瞻是棘手的,說實話......你想要某種循環緩衝區的,我懷疑...也許一個自定義集合。

+0

@Jon Skeet - thx爲此。你能在最後一行解釋你的問題嗎?我的推理是,如果列表只有一個項目,我仍然希望訪問者來處理它。但也許我錯過了一些東西 –

+0

@Simon:這意味着你不能區分訪問者和訪問者之間的一個項目碰巧是默認值(null,0,無論什麼),最後一對只有一個項目是真實的」。 –

+0

@Jon - 嗯,是的。好點子。所以你需要在訪問列表後處理最後的元素?或者你是否想過其他方式 - 特殊標誌/價值或其他? –

1

看起來好像你使用的是錯誤的類型。索引序列的行爲將迭代它,直到它每次都達到指定的索引。爲什麼不使用IList<T>ReadOnlyCollection<T>

1

未經測試,但我認爲這有效?當訪問超過界限時,它會循環到列表的前面。

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 
} 

你可以使用它像:

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。 thx vm –

4

當我們遇到類似的任務,我們定義了一個擴展方法:

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

這些功能有助於產生輸出來自輸入元素窗口的元素。請參閱LINQ extensions