简体   繁体   中英

Comparing pieces of strings in C# and removing items from collection

I have a piece of code written in C# like this:

foreach (var item in allNegatives)
{
    for (int index = 0; index < _items.Count(); index++)
    {
        if (_items[index].Title.ToLower().IndexOf(item, StringComparison.OrdinalIgnoreCase) >= 0)
        {
            _items.RemoveAt(index);
        }
    }
}

Where the _items collection is a List of strings (long titles)... Now as you can see I have a foreach loop and a for loop inside to check whether a Title inside the _items collections contains one of the "negative" phrases. Negative phrase simply means if the title contains it, I would like to remove that item from the collection...

The problem here is that this doesn't works the way I want it... ?

If I had 100 items in collection and some of them were like this:

Samsung galaxy s8 1200 mAh

Samsung galaxy s8 1200 mAh

Samsung galaxy s7 1200 mAh

Samsung galaxy s7 1200 mAh

Samsung galaxy s6 1200 mAh

And if the negative phrases are:

S8 and S7

The output that I get is:

Samsung galaxy s7 1200 mAh

Samsung galaxy s7 1200 mAh

Samsung galaxy s6 1200 mAh

For some reason it seems that the 2nd time the foreach loops the items containing that phrase aren't removed from the collection, and I'm not sure why.

Can someone help me out ?

You're skipping items.

Imagine this list:

0 s5
1 s8 <-
2 s7
3 s6

You're at index 1. S8 matches, so you remove it from the list, which now looks like this:

0 s5
1 s7 <-
2 s6

You're still at index 1, but it now contains s7. You iterate, and you're now at 2. You've skipped s7 even though it matches.

If you iterate backwards, this won't happen:

foreach (var item in allNegatives)
{
  for (int index = _items.Count() - 1; index >= 0; --index)
  {
      if (_items[index].Title.IndexOf(item, StringComparison.OrdinalIgnoreCase) >= 0)
      {
        _items.RemoveAt(index);
      }
   }
}

John has explained why it doesnt work that way, you are iterating all indexes and in this loop you are removing items. That can cause incorrrect results or exceptions.

Maybe you want to use List.RemoveAll

_items.RemoveAll(i => allNegatives.Any(n => i.Title.IndexOf(n, StringComparison.OrdinalIgnoreCase) >= 0));

This also avoids using Title.ToLower which is unnecessary if you use OrdinalIgnoreCase .

Use LINQ for this. Here is example how you can do this:

List<string> list= new List<string>()
{
    "Samsung galaxy s8 1200 mAh",
    "Samsung galaxy s8 1200 mAh",                
    "Samsung galaxy s7 1200 mAh",
    "Samsung galaxy s7 1200 mAh",
    "Samsung galaxy s6 1200 mAh"
};
List<string> negativePhrs= new List<string>(){"s7","s8"};
list = list.Where(x=> !negativePhrs.Any(y=> x.ToLower().Contains(y.ToLower()))).ToList();
foreach(var a in list)
{
    Console.WriteLine(a);
}

I'm not sure what you are trying to do. Do you want to remove from items or allNegatives?

Does this do what you want to do?

var things = new List<Thing> {new Thing { Title="Bob"}, new Thing {Title="Carol"}, new Thing {Title="Dave"}, new Thing {Title="Alice"} };
var items = new List<string> {"foo", "bar", "Alice", "Dave"};
var result = items.Where(x => !things.Exists(y => y.Title == x)).Select(z=>z);

This prints

foreach(var r in result)
   Console.WriteLine(r);

foo
bar

So it has removed Alice and Dave from items because they are Titles in things. But I think maybe you want to remove from things instead?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM