web-dev-qa-db-ja.com

for eachループで反復しながらリストの項目を削除する

NeededListという名前のリストがあります。このリストの各アイテムをチェックして、データベースに存在するかどうかを確認する必要があります。データベースに存在する場合は、リストから削除する必要があります。しかし、繰り返し処理している間はリストを変更できません。どうすればこれを機能させることができますか?

これまでの私のコードは次のとおりです。

For Each Needed In NeededList
        Dim Ticker = Needed.Split("-")(0).Trim()
        Dim Year = Needed.Split("-")(1).Trim()
        Dim Period = Needed.Split("-")(2).Trim()
        Dim Table = Needed.Split("-")(3).Trim()
        Dim dr As OleDbDataReader
        Dim cmd2 As New OleDb.OleDbCommand("SELECT * FROM " & Table & " WHERE Ticker = ? AND [Year] = ? AND Period = ?", con)
        cmd2.Parameters.AddWithValue("?", Ticker)
        cmd2.Parameters.AddWithValue("?", Year)
        cmd2.Parameters.AddWithValue("?", Period)
        dr = cmd2.ExecuteReader
        If dr.HasRows Then
            NeededList.Remove(Needed)
        End If
Next
16
gromit1

いいえ、for eachを使用してそれを行うことはできませんが、昔ながらのfor ..ループを使用してそれを行うことができます。
コツは、最後から始めて後方にループすることです。

For x = NeededList.Count - 1 to 0 Step -1
    ' Get the element to evaluate....
    Dim Needed = NeededList(x)
    .....
    If dr.HasRows Then
        NeededList.RemoveAt(x)
    End If
Next

現在の要素が削除されているため要素をスキップするリスクがないため、この方法でループにアプローチする必要があります。

たとえば、コレクションの4番目の要素を削除するとします。その後、5番目の要素が4番目になります。ただし、インデクサーは5まで上がります。このようにして、前の5番目の要素(現在は4番目の位置にある)は評価されません。もちろん、インデクサーの値を変更しようとすることもできますが、これは常に悪いコードと発生するのを待っているバグで終わります。

34
Steve

安全のために、ToList()を使用してコピーを作成します。

For Each Needed In NeededList.ToList()
    Dim Ticker = Needed.Split("-")(0).Trim()
    ...
    If dr.HasRows Then
        NeededList.Remove(Needed)
    End If
Next
15
Henk Holterman

ステップ-1を使用して、すべてのインデックスを反復するForループを使用できます。

For i as Integer = NeededList.Count - 1 to 0 Step -1

    Dim Needed = NeededList(i)

    'this is a copy of your code
    Dim Ticker = Needed.Split("-")(0).Trim()
    Dim Year = Needed.Split("-")(1).Trim()
    Dim Period = Needed.Split("-")(2).Trim()
    Dim Table = Needed.Split("-")(3).Trim()

    Dim dr As OleDbDataReader
    Dim cmd2 As New OleDb.OleDbCommand("SELECT * FROM " & Table & " WHERE Ticker = ? AND [Year] = ? AND Period = ?", con)
    cmd2.Parameters.AddWithValue("?", Ticker)
    cmd2.Parameters.AddWithValue("?", Year)
    cmd2.Parameters.AddWithValue("?", Period)
    dr = cmd2.ExecuteReader

    'MODIFIED CODE
    If dr.HasRows Then NeededList.RemoveAt(i)

Next i
4
tezzo

配列の内容(またはFor Eachで高速に列挙できるものはFor Eachループでは変更できません。単純なForループを使用して、すべてのインデックスを反復処理する必要があります。

ヒント:インデックスを削除するので、最後のインデックスから始めて、最初のインデックスに向かって作業を進めることをお勧めします。これにより、インデックスを削除するたびにスキップされないようにします。

3
nhgrif

リストは最後から拡大および縮小するため、リストをreverseの順序で反復することで問題を解決できます。


理論:

コレクションを元に戻すことは、コピーを返すよりも速くなります。したがって、速度が必要な場合は、コレクションを操作する前にlist.Reverse()を使用してください。


テストされたパフォーマンス:

ReverseToList:   00:00:00.0005484
CopyWithToList:  00:00:00.0017638
CopyWithForeach: 00:00:00.0141009

実装:

For Each Needed In NeededList.Reverse()
    Dim Ticker = Needed.Split("-")(0).Trim()
    '...
    If dr.HasRows Then
        NeededList.Remove(Needed)
    End If
Next

以下のコードは、メソッドのパフォーマンスをテストするために使用されました。

using System;
using System.Linq;
using System.Collections.Generic;
using System.Diagnostics;

public class Program
{
    public static void Main()
    {
        List<int> items = new List<int>();

        items = Enumerable.Range(0, 1000000).ToList();

        Reverse(items);
        CopyWithToList(items);
        CopyWithForeach(items);
    }

    public static void Reverse<T>(List<T> list) 
    {
        var sw = Stopwatch.StartNew();
        list.Reverse();
        sw.Stop();
        Console.WriteLine("ReversedList:    {0}", sw.Elapsed);
    }

    public static void CopyWithToList<T>(List<T> list) 
    {
        var sw = Stopwatch.StartNew();
        List<T> copy = list.ToList();
        sw.Stop();
        Console.WriteLine("CopyWithToList:  {0}", sw.Elapsed);
    }

    public static void CopyWithForeach<T>(List<T> list)
    {
        var sw = Stopwatch.StartNew();
        List<T> copy = new List<T>();
        foreach (T item in list) {
            copy.Add(item);
        }
        sw.Stop();

        Console.WriteLine("CopyWithForeach: {0}", sw.Elapsed);
    }

}
1
Nick4814

リストの要素の順序を逆にしても、For EachおよびReverse拡張機能を使用してIEnumerable Castを使用できます。

List(Of String)を使用した簡単な例:

For Each Needed In NeededList.Cast(Of List(Of String)).Reverse()
    If dr.HasRows Then
        NeededList.Remove(Needed)
    End If
Next
0
mikro

これはどうですか(イテレーションは必要ありません):

NeededList = (NeededList.Where(Function(Needed) IsNeeded(Needed)).ToList

Function IsNeeded(Needed As ...) As Boolean
    ...
    Return Not dr.HasRows
End Function
0
C66

いいえ、作業中のリストから削除することはできません。各Strの文字列としてlistOfStringsの文字列ListOfStrings If Str.Equals( "Pat")Then Dim index = listOfStrings.IndexOf(Str)listOfStrings .RemoveAt(index)End If Next

しかし、この方法はあなたのリストのコピーを作成し、そこから削除します。各Str For As String In listOfStrings If Str.Equals( "Pat")Then Dim index = listOfStringsCopy.IndexOf(Str)listOfStringsCopy.RemoveAt(index)End If Next

0
Pec1983