Reputation: 1670
I have an array of strings that has a fixed size of 10. Some elements may be null
. I want to use LINQ to remove the elements with the value null
from the array, but only at the end.
Input:
"Hello", null, "World", null, null, null, null, null, null, null
Output:
"Hello", null, "World"
So the output array contains all elements except for the trailing ones containing null
.
I thought of reversing the IEnumerable, then doing a SkipWhile and reversing it again. This would be possible and would be not much of a performance penalty in my case, but I am curious if there's a more elegant solution.
Upvotes: 2
Views: 1691
Reputation: 5861
You can get last index of not null or empty value from the list then get all values before last index
List<string> data = new List<string> { "Hello", null, "World", null, null, null, null, null, null, null };
int lastIndex = data.FindLastIndex(a => a != null);
data = data.Where((value, index) => index <= lastIndex).ToList();
Result : "Hello", null, "World"
Upvotes: 0
Reputation: 89091
Here's a method that doesn't require allocations, random-access collections, or iterating the collection multiple times.
public static class EnumerableExtensions
{
public static IEnumerable<T> TrimEnd<T>(this IEnumerable<T> col)
{
int nulls = 0;
foreach (var e in col)
{
if ( e == null || e.Equals(default(T)) )
{
nulls += 1;
}
else
{
while (nulls > 0)
{
nulls -= 1;
yield return default(T);
}
yield return e;
}
}
}
}
Upvotes: 1
Reputation: 12739
For a different LINQ variant that works for any enumerable (string[]
, List<string>
) you can try the following which uses Reverse
and SkipWhile
:
using System.Linq;
string[] strings = { "Hello", null, "World", null, null, null, null, null, null };
var result = strings.Reverse()
.SkipWhile(c => c is null)
.Reverse()
.ToArray();
Note that this is definitely not the most performant, I'm just offering a different version than those already posted.
Since you have an "array of strings", I assume you mean literally a string[]
and not a List<string>
. As an alternative to LINQ you can use operations over the array with Array.FindLastIndex
+ AsSpan
+ ToArray
(note that all solutions here forward assume that it's possible for your array to be all null
s):
string[] strings = { "Hello", null, "World", null, null, null, null, null, null };
var idx = Array.FindLastIndex(strings, c => c != null);
var result = idx >= 0 ? strings.AsSpan(0, idx + 1).ToArray()
: Array.Empty<string>();
If Span<T>
is not available to you, we could combine with LINQ's Take
:
var idx = Array.FindLastIndex(strings, c => c != null);
var result = idx >= 0 ? strings.Take(idx + 1).ToArray()
: Array.Empty<string>();
Or without LINQ:
var idx = Array.FindLastIndex(strings, c => c != null);
var result = Array.Empty<string>();
if (idx >= 0)
{
result = new string[idx +1 ];
Array.Copy(strings, 0, result, 0, idx + 1);
}
If you have later C# features, we can use System.Range
/System.Index
and create a sub-array of the existing array:
string[] strings = { "Hello", null, "World", null, null, null, null, null, null };
var idx = Array.FindLastIndex(strings, c => c != null);
var result = idx >= 0 ? strings[..(idx + 1)]
: Array.Empty<string>();
Or as a (kind of hard to read) one-liner if that's what you are after:
var result = Array.FindLastIndex(strings, c => c != null) is int idx && idx >= 0 ? strings[..(idx + 1)] : Array.Empty<string>();
You're title asks to remove elements. While I'm fairly sure you're looking to create a new collection (given your mention of LINQ), I figured I'd point out that you can't really remove elements from an array--they are fixed in size. However you can make use of Array.Resize
to "crop" your array:
var idx = Array.FindLastIndex(strings, c => c != null);
if (idx >= 0)
Array.Resize(ref strings, idx + 1);
Upvotes: 1
Reputation: 142008
If your collection is a List
you can use FindLastIndex
:
var x = new List<string> { "Hello", null, "World", null, null, null, null, null, null, null};
x = x.Take(x.FindLastIndex(s => s != null) + 1).ToList();
If you are working with another collection type you can try something like this:
x.Take(x.Select((s, index) => (s, index))
.Aggregate(-1, (acc, curr) => curr.s != null ? curr.index : acc) + 1)
So it is definitely not an elegant one.
If your collection is guaranteed to have at least one not null element you can make it a little bit prettier:
x.Take(x.Select((s, index) => (s, index)).Last(t => t.s != null).index + 1)
Or just hide the ugliness by writing your own extension method which will implement FindLastIndex
logic for IEnumerable
.
Upvotes: 3