2011-06-28 49 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