visual usuario tiempo eliminar elementos ejecucion crear control como agregar c# .net

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



panel visual c# (11)

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?


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


Crearía dos listas para agregar en el segundo y luego actualizaría la referencia de esta manera:

var urls = new List<string>(); var destUrls = new List<string>(urls); urls.Add("http://www.google.com"); foreach (string url in urls) { // Get all links from the url List<string> newUrls = GetLinks(url); destUrls.AddRange(newUrls); } urls = destUrls;


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.

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.


Hay tres estrategias que puedes usar.

  1. Copie la Lista <> a una segunda colección (lista o matriz - quizás use ToArray ()). Pasa por esa segunda colección, añadiendo URLs a la primera.
  2. Cree una segunda lista <> y recorra su lista de urls <> agregando nuevos valores a la segunda lista. Copie esos a la lista original cuando termine de buclear.
  3. Use un bucle for en lugar de un bucle foreach . Toma tu conteo por adelantado. La lista debe dejar las cosas indexadas correctamente, por lo que debe agregar cosas que irán al final de la lista.

Prefiero # 3 ya que no tiene ninguno de los gastos generales asociados con # 1 o # 2. Aquí hay un ejemplo:

var urls = new List<string>(); urls.Add("http://www.google.com"); int count = urls.Count; for (int index = 0; index < count; index++) { // Get all links from the url List<string> newUrls = GetLinks(urls[index]); urls.AddRange(newUrls); }

Editar: el último ejemplo (n. ° 3) asume que no desea procesar URL adicionales ya que se encuentran en el ciclo. Si desea procesar URL adicionales a medida que se encuentran, solo use urls.Count en el ciclo for en lugar de la variable de conteo local mencionada por el configurador en los comentarios para esta respuesta.


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.


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.


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


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