Heinz Kessler
Heinz Kessler

Reputation: 1670

LINQ How to Remove Empty Elements at End of Collection

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

Answers (4)

Farhad Zamani
Farhad Zamani

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

David Browne - Microsoft
David Browne - Microsoft

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

pinkfloydx33
pinkfloydx33

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 nulls):

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>();

Removing Elements from Array

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

Guru Stron
Guru Stron

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

Related Questions