c# como - ¿Cómo agregar elementos a una colección mientras la consume?





visual tiempo (10)


Probablemente también pueda crear una función recursiva, como esta (no probada):

IEnumerable<string> GetUrl(string url)
{
  foreach(string u in GetUrl(url))
    yield return u;
  foreach(string ret_url in WHERE_I_GET_MY_URLS)
    yield return ret_url;
}

List<string> MyEnumerateFunction()
{
  return new List<string>(GetUrl("http://www.google.com"));
}

En este caso, no tendrá que crear dos listas, ya que GetUrl hace todo el trabajo.

Pero puede haber perdido el punto de su programa.

El siguiente ejemplo arroja InvalidOperationException, "La colección fue modificada, la operación de enumeración puede no ejecutarse". al ejecutar el código.

var urls = new List<string>();
urls.Add("http://www.google.com");

foreach (string url in urls)
{
    // Get all links from the url
    List<string> newUrls = GetLinks(url);

    urls.AddRange(newUrls); // <-- This is really the problematic row, adding values to the collection I'm looping
}

¿Cómo puedo reescribir esto de una mejor manera? Supongo que una solución recursiva?




El enfoque de Jon es correcto; una cola es la estructura de datos correcta para este tipo de aplicación.

Suponiendo que finalmente le gustaría que su programa termine, sugeriría otras dos cosas:

  • no use string para sus URL, use System.Web.Uri : proporciona una representación de cadena canónica de la URL. Esto será útil para la segunda sugerencia, que es ...
  • ponga la representación de cadena canónica de cada URL que procesa en un diccionario. Antes de enrutar una URL, verifique primero si está en el diccionario.



Supongo que quieres iterar sobre toda la lista y cada elemento que agregas a ella. Si es así, sugeriría la recursión:

var urls = new List<string>();
var turls = new List<string();
turls.Add("http://www.google.com")

iterate(turls);

function iterate(List<string> u)
{
    foreach(string url in u)
    {
        List<string> newUrls = GetLinks(url);

        urls.AddRange(newUrls);

        iterate(newUrls);
    }
}



Es difícil mejorar el código sin saber qué hace GetLinks (). En cualquier caso, esto evita la recursión. La expresión estándar es que no altera una colección cuando está enumerando sobre ella. Si bien el tiempo de ejecución podría haberle permitido hacerlo, el razonamiento es que es una fuente de error, por lo que es mejor crear una nueva colección o controlar la iteración usted mismo.

  1. crea una cola con todas las URL.
  2. cuando dequeueing, estamos diciendo que lo hemos procesado, así que agrégalo al resultado.
  3. Si GetLinks () devuelve algo, agréguelos a la cola y trátelos también.

.

public List<string> ExpandLinksOrSomething(List<string> urls)
{
    List<string> result = new List<string>();
    Queue<string> queue = new Queue<string>(urls);

    while (queue.Any())
    {
        string url = queue.Dequeue();
        result.Add(url);

        foreach( string newResult in GetLinks(url) )
        {
            queue.Enqueue(newResult);
        }

    }

    return result;
}

La implementación ingenua supone que GetLinks() no devolverá referencias circulares. por ejemplo, A devuelve B y B devuelve A. Esto puede ser arreglado por:

        List<string> newItems = GetLinks(url).Except(result).ToList();
        foreach( string newResult in newItems )
        {
            queue.Enqueue(newResult);
        }

* Como otros señalan que usar un diccionario puede ser más eficiente dependiendo de la cantidad de elementos que procesa.

Me resulta extraño que GetLinks () devuelva un valor y luego lo resuelva en más Url. Quizás todo lo que quieras hacer es expansión de 1 nivel. Si es así, podemos deshacernos de la cola por completo.

public static List<string> StraightProcess(List<string> urls)
{
    List<string> result = new List<string>();

    foreach (string url in urls)
    {
        result.Add(url);
        result.AddRange(GetLinks(url));
    }

    return result;
}

Decidí reescribirlo porque, aunque otras respuestas usaban colas, no era evidente que no se ejecutaran para siempre.




No cambies la colección por la que atraviesas para cada uno. Simplemente use un ciclo while en la propiedad Count de la lista y acceda a List items by index. De esta forma, incluso si agrega elementos, la iteración debería recoger los cambios.

Editar: De nuevo, depende de si DESEA que los elementos nuevos que agregó sean recogidos por el ciclo. Si no, entonces esto no ayudará.

Edición 2: supongo que la manera más fácil de hacerlo sería simplemente cambiar tu ciclo por: foreach (url de cadena en urls.ToArray ())

Esto creará una copia de Array de su lista, y recorrerá esto en lugar de la lista original. Esto tendrá el efecto de no pasar por encima de los elementos agregados.




No puedes, básicamente. Lo que realmente quieres aquí es una cola:

var urls = new Queue<string>();
urls.Enqueue("http://www.google.com");

while(urls.Count != 0)
{
    String url = url.Dequeue();
    // Get all links from the url
    List<string> newUrls = GetLinks(url);
    foreach (string newUrl in newUrls)
    {
        queue.Enqueue(newUrl);
    }
}

Es un poco feo porque no hay un método AddRange en Queue<T> pero creo que es básicamente lo que quieres.




Considere usar una cola con un ciclo while (mientras q.Count> 0, url = q.Dequeue ()) en lugar de iteración.




Usa foreach con un lambda, ¡es más divertido!

var urls = new List<string>();
var destUrls = new List<string>();
urls.Add("http://www.google.com");
urls.ForEach(i => destUrls.Add(GetLinks(i)));
urls.AddRange(destUrls);



alternativamente, podría tratar la colección como una cola

IList<string> urls = new List<string>();
urls.Add("http://www.google.com");
while (urls.Count > 0)
{
    string url = urls[0];
    urls.RemoveAt(0);
    // Get all links from the url
    List<string> newUrls = GetLinks(url);
    urls.AddRange(newUrls);
}



Escribir en un archivo temporal, cuando termine de escribir renombre / mueva el archivo a la ubicación y / o nombre que el lector está buscando.





c# .net