2011-06-28 9 views
9

Estoy iterando a través de una colección con un patrón tipo visitante y necesito acceder al elemento actual y siguiente de la lista. Por el momento lo estoy haciendo a través de un método de extensión como estoLinq - 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)); 
     }  
    }  
} 

Me preguntaba si hay otra/mejores maneras/más elegantes para lograr esto? Por el momento, creo que solo necesito tener acceso a los elementos actuales y siguientes de la lista, pero me pregunto si puedo encontrar situaciones en las que deba buscar los siguientes 'n' elementos, por ejemplo.

+1

Tu código ni siquiera compilará. No hay ninguna propiedad llamada 'Count' en' IEnumerable '(hay un grupo de métodos). – jason

+0

@Jason - thx - Escribí el original en VB (que parece ser compatible con Count on a IEnumerable!) Y convertí - ¡eso me enseñará! Thx –

Respuesta

9

Suponiendo que está utilizando .NET 4, puede utilizar Zip a lograr lo mismo:

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

Esto hará iterar sobre la secuencia dos veces sin embargo. Una alternativa más agradable a su método de extensión actual (que no creo que funcione, por cierto, ya que IEnumerable no tiene una propiedad Count, y está tratando de llamar al theList también como método ...) sería algo como:

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

una búsqueda hacia delante más general es más difícil, para ser honesto ... te gustaría algún tipo de buffer circular, sospecho ... probablemente una colección personalizada.

+0

@Jon Skeet - thx para esto. ¿Puedes explicar tu pregunta en la última línea? Mi razonamiento era que si la lista solo tiene 1 artículo, aún quisiera que el visitante lo procese. Pero quizás me falta algo –

+0

@Simon: Bueno, significa que no se puede distinguir en el visitante entre un par donde un elemento pasa a ser el valor predeterminado (nulo, 0, lo que sea) y el último par donde solo un elemento es real". –

+0

@Jon - ah, sí. Buen punto. ¿Entonces necesitaría procesar el elemento final después de visitar la lista? ¿O estabas pensando de alguna otra manera - bandera/valor especial o algo así? –

1

Parece que está utilizando un tipo incorrecto. El acto de indexar una secuencia lo iterará hasta que llegue al índice especificado cada vez. ¿Por qué no usar IList<T> o ReadOnlyCollection<T>?

1

No probado, pero creo que esto funciona? Cuando la visita exceda los límites, pasa al principio de la lista.

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 
} 

Puedes usarla como:

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 para esto. thx vm –

4

Cuando nos encontramos con una tarea similar que ha definido un métodos de extensión:

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

Estas funciones ayudan a producir una salida elementos de una ventana de elementos de entrada.

Véase también LINQ extensions.