Remove list elements at given indices - c#

I have a list which contains some items of type string.
List<string> lstOriginal;
I have another list which contains idices which should be removed from first list.
List<int> lstIndices;
I'd tried to do the job with RemoveAt() method ,
foreach(int indice in lstIndices)
{
lstOriginal.RemoveAt(indice);
}
but it crashes and said me that "index is Out of Range."

You need to sort the indexes that you would like to return from largest to smallest in order to avoid removing something at the wrong index.
foreach(int indice in lstIndices.OrderByDescending(v => v))
{
lstOriginal.RemoveAt(indice);
}
Here is why: let's say have a list of five items, and you'd like to remove items at indexes 2 and 4. If you remove the item at 2 first, the item that was at index 4 would be at index 3, and index 4 would no longer be in the list at all (causing your exception). If you go backwards, all indexes would be there up to the moment when you're ready to remove the corresponding item.

How are you populating the list of indices? There's a much more efficient RemoveAll method that you might be able to use. For example, instead of this:
var indices = new List<int>();
int index = 0;
foreach (var item in data)
if (SomeFunction(data))
indices.Add(index++);
//then some logic to remove the items
you could do this:
data.RemoveAll(item => SomeFunction(item));
This minimizes the copying of items to new positions in the array; each item is copied only once.
You could also use a method group conversion in the above example, instead of a lambda:
data.RemoveAll(SomeFunction);

The reason this is happening is because when you remove an item from the list, the index of each item after it effectively decreases by one, so if you remove them in increasing index order and some items near the end of the original list were to be removed, those indices are now invalid because the list becomes shorter as the earlier items are removed.
The easiest solution is to sort your index list in decreasing order (highest index first) and then iterate across that.

for (int i = 0; i < indices.Count; i++)
{
items.RemoveAt(indices[i] - i);
}

My in-place deleting of given indices as handy extension method. It copies all items only once so it is much more performant if large amount of indicies is to be removed.
It also throws ArgumentOutOfRangeException in case where index to remove is out of bounds.
public static class ListExtensions
{
public static void RemoveAllIndices<T>(this List<T> list, IEnumerable<int> indices)
{
//do not remove Distinct() call here, it's important
var indicesOrdered = indices.Distinct().ToArray();
if(indicesOrdered.Length == 0)
return;
Array.Sort(indicesOrdered);
if (indicesOrdered[0] < 0 || indicesOrdered[indicesOrdered.Length - 1] >= list.Count)
throw new ArgumentOutOfRangeException();
int indexToRemove = 0;
int newIdx = 0;
for (int originalIdx = 0; originalIdx < list.Count; originalIdx++)
{
if(indexToRemove < indicesOrdered.Length && indicesOrdered[indexToRemove] == originalIdx)
{
indexToRemove++;
}
else
{
list[newIdx++] = list[originalIdx];
}
}
list.RemoveRange(newIdx, list.Count - newIdx);
}
}

var array = lstOriginal.ConvertAll(item => new int?(item)).ToArray();
lstIndices.ForEach(index => array[index] = null);
lstOriginal = array.Where(item => item.HasValue).Select(item => item.Value).ToList();

lstIndices.OrderByDescending(p => p).ToList().ForEach(p => lstOriginal.RemoveAt((int)p));
As a side note, in foreach statements, it is better not to modify the Ienumerable on which foreach is running. The out of range error is probably as a result of this situation.

Related

Problem finding indexes of items in a list

I'm trying to find indexes of items in a list.
For example number 0 in a list of numbers.
With this code I have found index of zeros only when there one zero in the list.
When zeros are two or more, the second index doesn't get correct.
Isn't method IndexOf() the correct one to use?
How can I find all the indexes of an item, not only the first?
Thanks
var zerosInList = list.FindAll(x => x == 0);
if (zerosInList.Count > 0)
{
foreach (var item in zerosInList) //finding indexes of zeros
{
indexes.Add(list.IndexOf(item));
Console.Write("found zero in position: "); PrintList(indexes);
}
The List<T>.IndexOf compares elements using the default equality comparer of T. For integers, it uses value equality. All zeros in your zerosInList collection are considered to be the same. In other words, the "second zero" or "third zero" in your foreach loop is considered no different than the "first zero", therefore the IndexOf method always returns the index of the first 0 it encounters, not the particular zero that's the subject of the foreach loop's current iteration.
You can make a collection of all the zero's indexes in your original list this way:
var indexesOfZeros = new List<int>();
for (int i = 0; i < list.Count; i++)
{
if (list[i] == 0)
{
indexesOfZeros.Add(i);
}
}
IndexOf method in C# searches one string for another. It returns the index of the string part, if one is found.
But you can try this:
for(int i=0;i<yourFirstList.Count;i++)
//yourFirstList is the list with all the numbers
{
if(yourFirstList.ElementAt(i).value==0){
indexes.Add(i);
}
}
Console.Write("found zero in position: "); PrintList(indexes);
You could also use the following Linq expression :
List<int> zeroList = intList.Select((val,idx) => new {val,idx})
.Where(t => t.val == 0)
.Select(pp => pp.idx).ToList();
the first creates an anonymous type containing the original value & offset, the where clause filters out all instances where the value is 0 & the final select returns the index in the original list.
I like Lambdas :D
List<int> indexes = list.Select((item, index) =>
item == 0 ? index : -1
).Where(i => i != -1).ToList();
Console.Write("found zero in position: "); PrintList(indexes);
List.FindAll method returns items that match your requisite, not their index. I mean if you have lets say
list[0] = 0;
list[1] = 4;
list[2] = 0;
and apply FindAll( x => x == 0); to list, you get a list which contains 2 zero values, and I think you expect indexes to contain 0 and 2 in this case. You could use .IndexOf() to accomplish your task, iterating through your items until you get a -1:
int indexOfItem = 0;
while(indexOfItem != -1 && indexOfItem < list.Count) {
indexOfItem = list.IndexOf(0, indexOfItem);
if (indexOfItem != -1) {
indexes.Add(indexOfItem);
indexOfItem++;
}
}

Lambda Expression to keep only modulo N index items? (without creating a new list)

I'm trying to pare a list down to just the modulo N items, i.e., keep only in List A, each item that remains, call it item with index i satisfying i%N == 0
My current solution is to create a new list (listB), and loop through the old list for items that meet this condition. (I feel that there is a better way than to create a new list?)
List<string> listA ; /* the list is not actually a string,
but for our test case let's use this (populated with M=31 items for example)*/
List<string> listB = new List<string>();
int N = 3;
for(int i=0;i<listA.Count;i++){
if(i%N == 0)listB.Add(listA[i]);
}
Is there a better (performant?) way to write this in basically "one line" using lambda expressions? (Without needing to declare a new list)
You could create an extension method combining the efficiency of only handling the items you're interested in while also avoiding creating a new list (though if you create the list with the known size before hand it shouldn't be that bad performance wise).
public static class ListExtensions
{
public static IEnumerable<T> GetNthItems<T>(this List<T> source, int n)
{
for (var i = 0; i < source.Count; i += n)
{
yield return source[i];
}
}
}
And you can then enumerate them directly:
foreach (var myItem in listA.GetNthItems(3))
{
//do something
}
But admittedly I doubt there is that much performance gain if you declare listB like listB = new List<string>((listA.Count / N)+1); and use i += N inside the loop.
Edit:
As requested a way to edit the existing list in place
public static void ClearAllButNthItems<T>(this List<T> source, int n)
{
var i = 1;
for (; i * n < source.Count; i++)
{
source[i] = source[i * n];
}
source.RemoveRange(i, source.Count - i);
}
Which can then be used like:
listA.ClearAllButNthItems(100);
This first moves all nth items in order to the front of the list, then removes all remaining items (this is a constant time action* since we're removing the end of the list so no items require moving)
Edit 2:
*It seems the removal is not actually constant time since the list seems to internally create a new array during the RemoveRange, however the list itself stays the same.
A one-liner:
myList.Where((item, index) => index % N == 0)
A performant solution:
var resultList = new List<SomeType>();
for (var n = 0; n < myList.Count; n += N)
resultList.Add(myList[n]);

Nesting loop to get one of each

I'm trying to make a look to print each of every value once:
Something like this.
Lets pretend that the object letters contains "one,two ...ten"
And then there is the object numbers that contains "1,2,3,4...10"
Now if I want the loop to print:
One
1
Two
2
Three
3
How would the loop look like?
I tried it like this:
foreach (var i in letters)
{
Console.WriteLine(i);
foreach(var a in numbers)
{
Console.WriteLine(a);
}
}
But this returns:
One
1
2
Two
1
2
Three
1
2
And that result isn't what I want..
How can I nest the loops to make it print the way I want it?
Maybe you can use IEnumerable<T>.Zip(...), see here, to make combinations.
var data = letters.Zip(numbers, (l, n) => new { letter = l, number = n})
foreach (var item in data) {
Console.Writeline(item.letter);
Console.Writeline(item.number);
}
use forloop insted of foreach use it like this
for (int i=0;i<letters.length;i++)
{
Console.WriteLine(letters[i]);
Console.WriteLine(numbers[i]);
}
Don't do two nested loops, they are for enumerating over all possible pairs from two collections.
Instead, make a loop on the index, and use it for both collections, or use LINQ's Zip method:
foreach (var pair in letters.Zip(numbers, (l, n) => new {Letter=l, Number=n})) {
Console.WriteLine("{0} - {1}", pair.Letter, pair.Number);
}
Assuming your Numbers and Letters are collections that derive from IEnumerable, you could do something like this:
var zipped = letters.Zip(numbers, Tuple.Create);
foreach (var tuple in zipped)
{
Console.WriteLine(tuple.Item1);
Console.WriteLine(tuple.Item2);
}
You need a single loop to iterate over both lists:
for (int index = 0; index < letters.Count; index++)
{
Console.WriteLine(letters[index]);
Console.WriteLine(numbers[index]);
}
This presupposes that your lists are the same length. If they're not you'd have to set the upper limit to the length of the shorter list.
for (int index = 0; index < Math.Min(letters.Count, numbers.Count); index++)
You're close - the second loop should not be within the first, but you should use one loop to iterate over both arrays. Try:
for (int i = 0; i < letters.size(); i++) {
Console.WriteLine(letters.getItem(i));
Console.WriteLine(numbers.getItem(i));
}
Note that this assumes a size() method to return the number of items and a getItem() method to return a specific item from the object.
What's happening is that for every time your outside loop runs, the inside one runs twice.
That's because your loops are nested, there's no getting around this.
If you absolutely must use nested loops for this, you'd have to add a check whether your number has been printed yet
Something like:
foreach(var i in letters)
{
Console.WriteLine(i);
foreach(var a in numbers)
{
if (a.isPrinted) //if it has been printed already
{
continue; //skip ahead
}
else
{
Console.WriteLine(a.number);
a.isPrinted = true;
break; //stop and jump out of the foreach loop
}
}
}
This also means that each number is actually an object that holds the bool isPrinted and int number
I wouldn't doing it like that, it's ridiculously inefficient.
You should do what others have already suggested.

Exception on list.remove() even numbers

I have a list of numbers and I’d like to remove all the even ones. I think my code is right:
System.Collections.Generic.List<int> list = ...
foreach (int i in list)
{
if (i % 2 == 0)
list.Remove(i);
}
but when I run it I get an exception. What am I doing wrong?
You can't modify a collection in a foreach loop, that being said,
you can't remove an item from a list that you're iterating over in a foreach loop.
Instead of the foreach loop, just use this single line of code:
list.RemoveAll(i => i % 2 == 0);
You cannot modify the collection during a foreach loop. A foreach loop uses an enumerator to loop through the collection, and when the collection is modified this is what happens to the enumerator:
An enumerator remains valid as long as the collection remains
unchanged. If changes are made to the collection, such as adding,
modifying, or deleting elements, the enumerator is irrecoverably
invalidated and its behavior is undefined.
You can use a regular for loop.
for (int i = 0; i < list.Count; i++)
{
int n = list[i];
if (n % 2 == 0)
{
list.RemoveAt(i--);
}
}
The foreach uses an IEnumerator under the covers, when an element in your list is removed, it leaves the enumerator in a potentially inconsistent state. The 'safest' thing for it to do is throw an exception.
To work around this, make a local copy of your collection first:
var local = new List<int>(list);
foreach (int i in local) { if (i % 2 == 0) list.Remove(i); }
If you're removing from a list of anything (or even an array) you should iterate backward through it as removing an item shifts all items after it down by one position. Iterating forward will cause you to skip over the next item each time.
Which exception did you get? Sometimes foreach will lock an item to where it can't be edited when it's used in the foreach. Instead, use for (and go backwards!)
for(int i = list.Length - 1 ; i > -1 ; i--)
to follow #Chris Filstow's method....
this will take your list, and replace it with a new one where the elements meet your criteria:
System.Collections.Generic.List<int> list = ...
list = list.Where( n=> n % 2 == 0 ).ToList();
You could try something like this instead. (It creates a new list of just the even numbers rather than removing the odds from the existing list, so it depends on what you're looking to do.)
var numbers = Enumerable.Range(1, 100);
var evens = numbers.Where(n => n % 2 == 1);
All your getting out of the foreach loop is readonly if you try to change the items in the list it explains why you get an exception.
This article right here explains why.
You could alway switch to a for loop.
for (int i = 1 ; i < list.lenght; i++)
{
if (i % 2 == 0)
list.Remove(i);
}

What's the best way to remove items from an ordered collection?

I have a list of items to remove from an ordered collection in C#.
what's the best way in going about this?
If I remove an item in the middle, the index changes but what If I want to remove multiple items?
To avoid index changes, start at the end and go backwards to index 0.
Something along these lines:
for(int i = myList.Count - 1; i >= 0; i++)
{
if(NeedToDelete(myList[i]))
{
myList.RemoveAt(i);
}
}
What is the type of the collection? If it inherits from ICollection, you can just run a loop over the list of items to remove, then call the .Remove() method on the collection.
For Example:
object[] itemsToDelete = GetObjectsToDeleteFromSomewhere();
ICollection<object> orderedCollection = GetCollectionFromSomewhere();
foreach (object item in itemsToDelete)
{
orderedCollection.Remove(item);
}
If the collection is a List<T> you can also use the RemoveAll method:
list.RemoveAll(x => otherlist.Contains(x));
Assuming that the list of items to delete is relatively short, you can first sort the target list. Than traverse the source list and keep an index in the target list which corresponds to the item which you deleted.
Supposed that the source list is haystack and list of items to delete is needle:
needle.Sort(); // not needed if it's known that `needle` is sorted
// haystack is known to be sorted
haystackIdx = 0;
needleIdx = 0;
while (needleIdx < needle.Count && haystackIdx < haystack.Count)
{
if (haystack[haystackIdx] < needle[needleIdx])
haystackIdx++;
else if (haystack[haystackIdx] > needle[needleIdx])
needleIdx++;
else
haystack.RemoveAt(haystackIdx);
}
This way you have only 1 traversal of both haystack and needle, plus the time of sorting the needle, provided the deletion is O(1) (which is often the case for linked lists and the collections like that). If the collection is a List<...>, deletion will need O(collection size) because of data shifts, so you'd better start from the end of both collections and move to the beginning:
needle.Sort(); // not needed if it's known that `needle` is sorted
// haystack is known to be sorted
haystackIdx = haystack.Count - 1;
needleIdx = needle.Count - 1;
while (needleIdx >= 0 && haystackIdx >= 0)
{
if (haystack[haystackIdx] > needle[needleIdx])
haystackIdx--;
else if (haystack[haystackIdx] < needle[needleIdx])
needleIdx--;
else
haystack.RemoveAt(haystackIdx--);
}

Categories

Resources