I have a string of unknown length
it is in the format
\nline
\nline
\nline
with out know how long it is how can i just take the last 10 lines of the string a line being separated by "\n"
I have a string of unknown length
it is in the format
\nline
\nline
\nline
with out know how long it is how can i just take the last 10 lines of the string a line being separated by "\n"
As the string gets larger, it becomes more important to avoid processing characters that don't matter. Any approach using string.Split
is inefficient, as the whole string will have to be processed. An efficient solution will have to run through the string from the back. Here's a regular expression approach.
Note that it returns a List<string>
, because the results need to be reversed before they're returned (hence the use of the Insert
method)
private static List<string> TakeLastLines(string text, int count)
{
List<string> lines = new List<string>();
Match match = Regex.Match(text, "^.*$", RegexOptions.Multiline | RegexOptions.RightToLeft);
while (match.Success && lines.Count < count)
{
lines.Insert(0, match.Value);
match = match.NextMatch();
}
return lines;
}
var result = text.Split('\n').Reverse().Take(10).ToArray();
Split()
the string on \n
, and take the last 10 elements of the resulting array.
If this is in a file and the file is particularly large, you may want to do this efficiently. A way to do it is to read the file backwards, and then only take the first 10 lines. You can see an example of using Jon Skeet's MiscUtil library to do this here.
var lines = new ReverseLineReader(filename);
var last = lines.Take(10);
Here's one way to do it that has the advantage that it doesn't create copies of the entire source string so is fairly efficient. Most of the code would be placed in a class along with other general purpose extension methods so the end result is that you can do it with 1 line of code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
string x = "a\r\nb\r\nc\r\nd\r\ne\r\nf\r\ng\r\nh\r\ni\r\nj\r\nk\r\nl\r\nm\r\nn\r\no\r\np";
foreach(var line in x.SplitAsEnumerable("\r\n").TakeLast(10))
Console.WriteLine(line);
Console.ReadKey();
}
}
static class LinqExtensions
{
public static IEnumerable<string> SplitAsEnumerable(this string source)
{
return SplitAsEnumerable(source, ",");
}
public static IEnumerable<string> SplitAsEnumerable(this string source, string seperator)
{
return SplitAsEnumerable(source, seperator, false);
}
public static IEnumerable<string> SplitAsEnumerable(this string source, string seperator, bool returnSeperator)
{
if (!string.IsNullOrEmpty(source))
{
int pos = 0;
do
{
int newPos = source.IndexOf(seperator, pos, StringComparison.InvariantCultureIgnoreCase);
if (newPos == -1)
{
yield return source.Substring(pos);
break;
}
yield return source.Substring(pos, newPos - pos);
if (returnSeperator) yield return source.Substring(newPos, seperator.Length);
pos = newPos + seperator.Length;
} while (true);
}
}
public static IEnumerable<T> TakeLast<T>(this IEnumerable<T> source, int count)
{
List<T> items = new List<T>();
foreach (var item in source)
{
items.Add(item);
if (items.Count > count) items.RemoveAt(0);
}
return items;
}
}
}
EDIT: It has been pointed out that this could be more efficient because it iterates the entire string. I also think that RemoveAt(0) with a list is probably inefficient also. To resolve this the code could be modified to search through the string backwards. This would eliminate the need for the TakeLast function as we could just use Take.
space efficient approach
private static void PrintLastNLines(string str, int n)
{
int idx = str.Length - 1;
int newLineCount = 0;
while (newLineCount < n)
{
if (str[idx] == 'n' && str[idx - 1] == '\\')
{
newLineCount++;
idx--;
}
idx--;
}
PrintFromIndex(str, idx + 3);
}
private static void PrintFromIndex(string str, int idx)
{
for (int i = idx; i < str.Length; i++)
{
if (i < str.Length - 1 && str[i] == '\\' && str[i + 1] == 'n')
{
Console.WriteLine();
i++;
}
else
{
Console.Write(str[i]);
}
}
Console.WriteLine();
}