web-dev-qa-db-ja.com

List <object> .RemoveAll-適切な述語を作成する方法

これはちょっとした質問です-私はまだC#とジェネリックにかなり新しく、述語、デリゲート、ラムダ式にはまったく新しいです...

「車両」と呼ばれる別のクラスの一般的なリストを含む「問い合わせ」クラスがあります。親EnquiryからVehicleを追加/編集/削除するコードを作成しています。そして現在、私は具体的に削除を検討しています。

これまで読んだことから、Vehicles.RemoveAll()を使用して、特定のVehicleIDのアイテムまたは特定のEnquiryIDのすべてのアイテムを削除できるようです。私の問題は、.RemoveAll正しい述語をフィードする方法を理解することです-私が見た例は単純すぎます(または、述語、デリゲート、ラムダ式の知識が不足しているため、おそらくあまりにも単純すぎます)。

各車両にEnquiryIDがある_List<Of Vehicle> Vehicles_がある場合、特定のEnquiryIDのすべての車両を削除するにはVehicles.RemoveAll()をどのように使用しますか?

これにはいくつかのアプローチがあることを理解しているので、アプローチの違いを聞きたいと思っています。何かを機能させるために必要なだけでなく、これも学習課題です。

補足的な質問として、ジェネリックリストはこれらのオブジェクトの最適なリポジトリですか?私の最初の傾向はコレクションに向いていましたが、私は時代遅れのようです。確かにジェネリックが好まれるようですが、私は他の選択肢について興味があります。

40
CJM

RemoveAll()メソッドはPredicate<T>デリゲート(ここまで新しいものはありません)。述語は、単にtrueまたはfalseを返すメソッドを指します。もちろん、RemoveAllは、述語を適用してTrueを返すすべてのTインスタンスをコレクションから削除します。

C#3.0では、開発者はいくつかのメソッドを使用して、述語をRemoveAllメソッドに渡すことができます(これだけではありません...)。次を使用できます。

ラムダ式

vehicles.RemoveAll(vehicle => vehicle.EnquiryID == 123);

匿名メソッド

vehicles.RemoveAll(delegate(Vehicle v) {
  return v.EnquiryID == 1;
});

通常の方法

vehicles.RemoveAll(VehicleCustomPredicate);
private static bool
VehicleCustomPredicate (Vehicle v) {
    return v.EnquiryID == 1; 
}
84
AngeloBad

Tの述語は、Tを取り込んでブール値を返すデリゲートです。 List <T> .RemoveAllは、述語の呼び出しがtrueを返すリスト内のすべての要素を削除します。単純な述語を提供する最も簡単な方法は通常 lambda式 ですが、 匿名メソッド または実際のメソッドを使用することもできます。

{
    List<Vehicle> vehicles;
    // Using a lambda
    vehicles.RemoveAll(vehicle => vehicle.EnquiryID == 123);
    // Using an equivalent anonymous method
    vehicles.RemoveAll(delegate(Vehicle vehicle)
    {
        return vehicle.EnquiryID == 123;
    });
    // Using an equivalent actual method
    vehicles.RemoveAll(VehiclePredicate);
}

private static bool VehiclePredicate(Vehicle vehicle)
{
    return vehicle.EnquiryID == 123;
}
15
Quartermeister

これは動作するはずです(enquiryIdは照合する必要があるIDです):

vehicles.RemoveAll(vehicle => vehicle.EnquiryID == enquiryId);

これにより、リスト内の各車両がラムダ述語に渡され、述語が評価されます。述部がtrueを返す場合(つまり、vehicle.EnquiryID == enquiryId)、現在の車両がリストから削除されます。

コレクション内のオブジェクトのタイプがわかっている場合は、汎用コレクションを使用する方が適切です。コレクションからオブジェクトを取得する際のキャストを回避しますが、コレクション内のアイテムが値タイプである場合のボックス化を回避することもできます(パフォーマンスの問題を引き起こす可能性があります)。

6
adrianbanks

私はこれまでのところ答えのどれも持っていない何かに対処したかった:

私はこれまで何を読んでから、私が特定のVehicleIDで項目を削除するVehicles.RemoveAll()を使用することができることが表示されます。 [sic]補足質問として、汎用リストはこれらのオブジェクトの最適なリポジトリですか?

名前が示すようにVehicleIDが一意であると仮定すると、削除(およびFindなどの他のメソッド)はまだO( n)。代わりにHashSet<Vehicle>を見てください。O(1)除去(および他のメソッド)を使用して、以下を使用します:

int GetHashCode(Vehicle vehicle){return vehicle.VehicleID;}
int Equals(Vehicle v1, Vehicle v2){return v1.VehicleID == v2.VehicleID;}

特定のEnquiryIDを持つすべての車両を削除するには、この方法ですべての要素を反復処理する必要があります。そのため、頻繁に行う操作に応じて、代わりにGetHashCodeを返すEnquiryIDを検討できます。ただし、多くのビークルが同じEnquiryIDを共有する場合、これには多くの衝突のマイナス面があります。

この場合、EnquiryIDをVehiclesにマップし、車両を追加/削除するときにそれを最新の状態に保つDictionary<int, List<Vehicle>>を作成することをお勧めします。 HashSetのからこれらの車両を削除すると、その後でO(m)操作、mは特定EnquiryID有する車両の数です。

1
Wolfzoon

トピックから少し外れていますが、リストからすべての2を削除したいと言います。これは非常にエレガントな方法です。

void RemoveAll<T>(T item,List<T> list)
{
    while(list.Contains(item)) list.Remove(item);
}

述語あり:

void RemoveAll<T>(Func<T,bool> predicate,List<T> list)
{
    while(list.Any(predicate)) list.Remove(list.First(predicate));
}

+1は、学習目的でここに答えを残すことを奨励する場合にのみ使用してください。また、トピックから外れていることについても正しいですが、例をここに残しておくことには大きな価値があるため、厳密に学習するために、私はそのことについて丁寧に説明しません。一連のコメントとして投稿するのは手に負えないため、この回答を編集として投稿しています。

あなたの例は短くてコンパクトですが、どちらも効率の点ではエレガントではありません。最初はO(nが悪い2)、2番目、O(nで絶対にひどい3)。 O(nのアルゴリズム効率2)不良であり、可能な限り、特に汎用コードでは避けてください。 O(nの効率3)は恐ろしく、nが常に非常に小さいことがわかっている場合を除き、すべての場合に使用しないでください。 「時期尚早な最適化はすべての悪の根源」というバトル軸を無視するかもしれませんが、大きなデータセットを処理する必要のあるアルゴリズムをコーディングしたことがないため、二次成長の結果を真に理解していないため、ナイーブです。その結果、それらの小さなデータセット処理アルゴリズムは、一般に実行可能な速度よりも遅くなり、高速に実行できるとは考えていません。効率的なアルゴリズムと非効率的なアルゴリズムの違いはしばしば微妙ですが、パフォーマンスの違いは劇的です。アルゴリズムのパフォーマンスを理解するための鍵は、使用することを選択したプリミティブのパフォーマンス特性を理解することです。

最初の例では、list.Contains()Remove()は両方ともO(n)なので、述部に1つ、本体にもう1つを含むwhile()ループはO(n2);まあ、技術的にはO(m * n)ですが、O(n2)削除される要素の数(m)がリストの長さ(n)に近づくにつれて。

2番目の例はさらに悪い:O(n3)、Remove()を呼び出すたびに、First(predicate)も呼び出すため、これもO(n)です。考えてみてください:Any(predicate)リストをループするpredicate()がtrueを返す要素を探します。最初のそのような要素が見つかると、trueを返します。 while()ループの本体では、list.First(predicate)を呼び出します。これはリストを2回ループしますlist.Any(predicate)によって既に見つかった同じ要素を探します。 First()が見つかると、list.Remove()に渡される要素を返します。この要素は、回目のリストのループで、以前にAny()First()で見つかった同じ要素をもう一度見つけます。最終的にそれを削除します。削除されると、プロセス全体が少し短いリストで最初からやり直されます、すべてのループを実行します毎回最初から何度も何度も繰り返されます述部に一致する要素がさらに残ります。 2番目の例のパフォーマンスは、O(m * m * n)またはO(n3)mがnに近づくにつれて。

何らかの述語に一致するすべての項目をリストから削除する最善の方法は、総称リストの独自のList<T>.RemoveAll(predicate)メソッドを使用することです。これは、述語がO(1)である限りO(n)です。削除する各要素に対してfor()を呼び出してリストを1回だけ渡すlist.RemoveAt()ループ手法は、seemになるO(n)このようなソリューションisは、最初の例よりも効率的ですが、アルゴリズムの効率の観点からは無視できる定数係数のみです。for()ループ実装でもO(m * n)Remove()の各呼び出しはO(n)なので、for()ループ自体はO(n)であり、Remove()ループをm回呼び出すため、for()ループの成長はO(n)です。2)mがnに近づくにつれて。

1
Raz Megrelidze