views:

210

answers:

7

What is the easiest way to do this?

The results should be:

1: one
2: two
3: 
4:
5: five

Code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace TestLines8833
{
    class Program
    {
        static void Main(string[] args)
        {
            List<string> lines = new List<string>();
            lines.Add("");
            lines.Add("one");
            lines.Add("two");
            lines.Add("");
            lines.Add("");
            lines.Add("five");
            lines.Add("");
            lines.Add("");

            lines.TrimList();
        }
    }

    public static class Helpers
    {
        public static List<string> TrimList(this List<string> list)
        {
            //???
        }
    }
}
A: 

If you wanted to remove the empty strings you could do something like this...

lines = lines.Where(s => ! string.IsNullOrEmpty(s)).ToList();

Update: Sorry just seen your edit that you want internal blanks to remain.

In this case I would just an extension method like the one you mention above as I don't think there is a simpler way of doing it.

MarkB29
+6  A: 

Okay, now I understand the desired results:

public static class Helpers
{
    // Adjust this to use trimming, avoid nullity etc if you you want
    private static readonly Func<string, bool>
        NonBlankLinePredicate = x => x.Length != 0;

    public static List<string> TrimList(this List<string> list)
    {
        int start = list.FindIndex(NonBlankLinePredicate);
        int end = list.FindLastIndex(NonBlankLinePredicate);

        // Either start and end are both -1, or neither is
        if (start == -1)
        {
            return new List<string>();
        }
        return list.GetRange(start, end - start + 1);
    }
}

Note that this doesn't change the existing list - it returns a new list with the desired content. It wasn't clear exactly what behaviour you wanted, given that you've given the method a return type, but your sample calls it without using the result. Personally I prefer non-side-effecting methods, although it may be worth changing the name :)

Jon Skeet
<nitpick>safer to use string.IsNullOrEmpty rather than checking string length</nitpick> I don't mean to teach my grandmother to suck eggs, but . . . Granny make a little hole here and . . .
Binary Worrier
Well, this is pretty much exactly my answer...
Carra
@Binary Worrier: Yes, you could. It depends - does the list containing a null reference represent a bug (in which case an exception is appropriate) or not? I suspect Edward is happy enough to fix that up himself - although I'll edit to make it clearer. @jk: I fixed that a while ago :)
Jon Skeet
@Jon: This is true, our API is called by many and varied client applications, sometimes we get empty strings, sometimes we get nulls. If string.IsNullOrEmpty wasn't provided by the framework we'd have rolled our own :)
Binary Worrier
+5  A: 

What about this:

    public static void TrimList(this List<string> list) {
        while (0 != list.Count && string.IsNullOrEmpty(list[0])) {
            list.RemoveAt(0);
        }
        while (0 != list.Count && string.IsNullOrEmpty(list[list.Count - 1])) {
            list.RemoveAt(list.Count - 1);
        }
    }

Note that the signature has changed from your example (return type is void).

Paolo Tedesco
That's easy but inefficient. Also, it throws an IndexOutOfRangeException if the list only has empty strings.
Guffa
@Guffa: you are right, thanks, I updated the code.
Paolo Tedesco
Ick - why "0 != list.Count" instead of "list.Count != 0"? We're not using C/C++ :)
Jon Skeet
@Jon Skeet: thanks for your remark, but it's too late, "0 !=" is hard-wired in my fingers :)
Paolo Tedesco
@orsogufo: Now it doesn't throw the exception, but it's still inefficient... ;)
Guffa
A: 

There is nothing built in to do something specific like that. You can check the items from the beginning and end and remove the empty strings. To minimise the operations on the list (using RemoveAt repeatedly to remove the first item is rather inefficient), first count the number of items to remove and then use the RemoveRange method to remove them all at once.

To match how you use the method in the code, the extension has to alter the list rather than returning a new list.

public static void TrimList(this List<string> list) {
  int cnt = 0;
  while (cnt < list.Count && list[cnt].Length == 0) cnt++;
  if (cnt > 0) list.RemoveRange(0, cnt);
  cnt = 0;
  while (cnt < list.Count - 1 && list[list.Count - cnt - 1].Length == 0) cnt++;
  if (cnt > 0) list.RemoveRange(list.Count - cnt, cnt);
}
Guffa
+4  A: 

Try this one:

public static List<string> TrimList(this List<string> list)  
    {  
        return list.SkipWhile(l => String.IsNullOrEmpty(l)).Reverse().SkipWhile(l => String.IsNullOrEmpty(l)).Reverse();
    } 
Jens
A: 

Sometime the good old foreach beats linq both from the readability and performance perspective:

public static List<string> TrimList(this List<string> list)
{
    list.TrimListStart();
    vat l = list.Reverse().ToList();
    l.TrimListStart();
    return l;
}

public void TrimListStart(this List<string> list)
{
    foreach(var s in new List(list))
    {
        if(string.string.IsNullOrWhiteSpace(s))
        {
            list.Remove(s);
        }
        else
        {
            break;
        }
    }
}
bitbonk
I can't say I like that in terms of either performance *or* readability. I agree that LINQ isn't necessarily the answer here, but using FindIndex, FindLastIndex and GetRange (as per mine and Carra's answer) seems both more readable and simpler to me...
Jon Skeet
A: 
    int start = stringList.FindIndex((i => i.Trim() != ""));
    int end = stringList.FindLastIndex((i => i.Trim() != ""));
    List<string> range = new List<string>();
    if(start != -1 && end != -1)
        range = stringList.GetRange(start, (end - start + 1));
Carra