-1

i wonder to delete last 2 lines from some string i have that always change:

Hello How Are you ?
im fine Thanks
some info
some info

for the example lets say i have the string above i want to delete the last 2 lines "some info"

thats what i tried below as i said the last two lines have always ranom text thats why i used 'Environment.NewLine'

string sem = mtstring = mtstring (mtstring .TrimEnd().LastIndexOf(Environment.NewLine));
            string Good = sem = sem.Remove(sem.TrimEnd().LastIndexOf(Environment.NewLine));
Johnny
  • 8,939
  • 2
  • 28
  • 33
Wellzar
  • 51
  • 7
  • `Environment.NewLine != Random.Text` - to be clear, a 'new line' is simply that, ala `\r\n` in a way. Not a bunch of random text. – gravity Apr 03 '19 at 19:19
  • 1
    Possible duplicate of [How to delete last line in a text file?](https://stackoverflow.com/questions/4264117/how-to-delete-last-line-in-a-text-file) – gravity Apr 03 '19 at 19:22
  • i didnt mean to Random Class ... – Wellzar Apr 03 '19 at 19:32

7 Answers7

2

Here's a oneliner:

string good = string.Join(Environment.NewLine, mtstring.Split(Environment.NewLine).Reverse().Skip(2).Reverse());

Edit:

I did the following test to test the time efficiency, which shows that (as pointed out by @TheodorZoulias) this approach is quite ineffecient, so use with causion on larger data sets.

class Program
{
  static void Main(string[] args)
  {
    var testString = string.Join(Environment.NewLine, Enumerable.Range(0, 1000000));
    var oldArray = testString.Split();

    Stopwatch stopwatch = Stopwatch.StartNew();
    var newString = string.Join(Environment.NewLine, oldArray);
    stopwatch.Stop();
    Console.WriteLine($"String length: {newString.Length}");
    Console.WriteLine($"Time elapsed: {stopwatch.ElapsedMilliseconds} ms");

    stopwatch = Stopwatch.StartNew();
    newString = string.Join(Environment.NewLine, oldArray.Reverse().Skip(2).Reverse());
    stopwatch.Stop();
    Console.WriteLine($"String length: {newString.Length}");
    Console.WriteLine($"Time elapsed: {stopwatch.ElapsedMilliseconds} ms");
  }
}

Output:

String length: 9888886

Time elapsed: 45 ms

String length: 9888876

Time elapsed: 188 ms

To summarize, the double Reverse call is quite expensive, quadrupling the execution time compared to just splitting and joining the string, which in itself is no inexpensive task.

Community
  • 1
  • 1
  • This seems rather inefficient. Reverse creates a buffer of the source, and you are calling it twice. – Theodor Zoulias Apr 03 '19 at 22:27
  • I don't think you're right, as `Reverse` uses deferred execution, and so the object doesn't actually contain all the data, just the data needed to execute it. I did a test, see my edited answer. – Kristoffer Lerbæk Pedersen Apr 04 '19 at 07:50
  • You can view the implementation of Reverse [here](https://referencesource.microsoft.com/system.core/system/linq/Enumerable.cs.html). What it does is buffer all the elements of the source, and then enumerate the buffer backwards. – Theodor Zoulias Apr 04 '19 at 07:57
  • You're right, so you may run out of memory on extremely large sets of data. In this case, though, it should be quick and swift. – Kristoffer Lerbæk Pedersen Apr 04 '19 at 08:02
  • Yes, for occasional use with small texts your method is fine. But someone could see your answer being the accepted answer and conclude that it is fine to use this method for heavy processing of million line files. :-) – Theodor Zoulias Apr 04 '19 at 08:09
  • Btw I replicated your test for removing 2 lines from a 1000000 lines string, and it took 850 msec in my machine. A non-LINQ implementation did the same thing in 30 msec. – Theodor Zoulias Apr 04 '19 at 18:30
  • Since my example took less than 1 ms, I suspect that the bottleneck was not the double `Reverse` call, but instead the `Split`, since that returns an array, and is hence not deferred. That does not diminish the performance issue with this implementation, though. – Kristoffer Lerbæk Pedersen Apr 05 '19 at 09:55
  • Your example took 1 ms because it did zero calculations. You can't measure the performance of a LINQ query without consuming the enumerable! – Theodor Zoulias Apr 05 '19 at 10:08
  • While I agree, all I aimed to test was your postulate that calling `Reverse` (twice) would be inefficient. It seems to me that this is not the inefficient part. – Kristoffer Lerbæk Pedersen Apr 05 '19 at 20:32
  • `Reverse` starts doing actuall work at the moment you start consuming the enumerable, like all other LINQ methods. Before that moment you have only instantiated a small state machine. If all you intend to do is chain some LINQ methods without traversing the enumerable, then this still is not efficient. There are more efficient ways to do nothing, like not writing any code at all. :-) – Theodor Zoulias Apr 05 '19 at 21:45
  • 1
    You are, of course, correct. Thank you for correcting me, I've updated my test and conclusion. I did know all the facts provided during this discussion, but reached all the wrong conclusions from some additional (and unmerited) assumptions. – Kristoffer Lerbæk Pedersen Apr 07 '19 at 19:24
  • 1
    Ha ha! Yeah, the deferred execution of LINQ is tricky. It's still bites me from time to time. :-) Your updated answer is now valuable, upvoted. – Theodor Zoulias Apr 07 '19 at 23:18
1

If you add in Microsoft's "System.Interactive" NuGet Package, you can do this:

string Good =
    String.Join(
        Environment.NewLine,
        mtstring.Split(Environment.NewLine.ToArray()).SkipLast(2));
Enigmativity
  • 113,464
  • 11
  • 89
  • 172
  • Cool. The implementation of [SkipLast](https://github.com/dotnet/reactive/blob/master/Ix.NET/Source/System.Interactive/System/Linq/Operators/SkipLast.cs) is interesting. It uses a buffer the size of the lines to be skipped. – Theodor Zoulias Apr 04 '19 at 05:19
0

change to:

string sem = mtstring.Remove(mtstring.LastIndexOf(Environment.NewLine));
string Good = sem.Remove(sem.LastIndexOf(Environment.NewLine));
Matt.G
  • 3,586
  • 2
  • 10
  • 23
0

The assumption is that you have Environment.NewLine as a separator but could be changed to use any separator as well

var str = "Hello How Are you ?" + Environment.NewLine +
          "im fine Thanks" + Environment.NewLine +
          "some info" + Environment.NewLine +
          "some info";

var split = str.Split(new [] { Environment.NewLine }, StringSplitOptions.None).ToList();

Console.WriteLine(string.Join(Environment.NewLine, split.Take(split.Count - 2)));
// Hello How Are you ?
// im fine Thanks

n.b. Be careful about the new line, on Windows, it is \r\n. Unix and Mac \n and \r respectively.

Johnny
  • 8,939
  • 2
  • 28
  • 33
0

You could try something along the lines of:

//Create list of strings for each line in mtstring
List<string> lines = mtstring.Split(
                        new string[] {"\n", "\r"}, 
                        StringSplitOptions.None
                ).ToList();

//Take a subset of these lines (Total - 2)
lines = lines.GetRange(0, lines.Count-2);

//Rejoin the list as a string
string Good = string.Join("\n", lines);

Output:

Hello How Are you ?

im fine Thanks
Duncan Palmer
  • 2,865
  • 11
  • 63
  • 91
0

Fast (to write, not execute) solution is to use Linq:

var input = "Hello How Are you ?\r\nim fine Thanks\r\nsome info\r\nsome info";
var res1 = string.Join(Environment.NewLine, input
    .Split(Environment.NewLine.ToCharArray(), StringSplitOptions.RemoveEmptyEntries)
    .Reverse()
    .Skip(2)
    .Reverse());

More optimal solution is to use a StringBuilder:

var sb = new StringBuilder();
var temp = input.Split(Environment.NewLine.ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
for (var i = 0; i < temp.Length - 2; ++i)
    sb.AppendLine(temp[i]);

var res2 = sb.ToString();

Bot res1 and res2 will be:

Hello How Are you ?
im fine Thanks

Aleks Andreev
  • 7,016
  • 8
  • 29
  • 37
-1

Implementation for efficiency. Hopefully it's not buggy.

public static string RemoveLinesFromTheEnd(string source, int linesToRemove)
{
    var separators = new char[] { '\r', '\n' };
    int pos = source.Length;
    for (int i = 0; i < linesToRemove; i++)
    {
        pos = source.LastIndexOfAny(separators, pos - 1);
        if (pos > 0 && source[pos - 1] == '\r' && source[pos] == '\n') pos--;
        if (pos <= 0) break;
    }
    return pos < 0 ? "" : source.Substring(0, pos);
}

Usage:

var cleanString = RemoveLinesFromTheEnd(mtstring, 2);
Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104