【问题标题】:take the last n lines of a string c#取字符串的最后 n 行 c#
【发布时间】:2012-08-10 04:32:01
【问题描述】:

我有一串未知长度的字符串

它的格式

\nline
\nline
\nline

不知道它有多长,我怎么能只取字符串的最后 10 行 以“\n”分隔的行

【问题讨论】:

    标签: c# string string-parsing


    【解决方案1】:

    Split()\n 上的字符串,并取结果数组的最后 10 个元素。

    【讨论】:

    • 轰隆隆。比我写的好多了。
    • 快速简单,只要它说字符串不是很大。
    • 如何从数组中取出最后 10 个元素(没有 for 循环)btw 字符串是巨大的
    • @user1588670:只循环最后 10 个元素的 for 循环有什么问题? for(int i=arr.Length-10;i<arr.Length;i++)String line=arr[i];
    【解决方案2】:
    var result = text.Split('\n').Reverse().Take(10).ToArray();
    

    【讨论】:

    • +1 虽然这会颠倒可能无人看管的行的顺序。您可以在末尾附加另一个 ReverseToArray() 是多余的,因为 OP 没有提到他想要一个数组。
    • @codesparkle:因为Skip 枚举了整个(巨大的)数组,只是为了取最后 10 个元素。 Reverse.Take 将像 For-loop 一样实现,它只以相反的顺序循环最后 10 个元素,这样更有效,也更具可读性。
    • @TimSchmelter 每天都在学习有关 LINQ 的新知识;)感谢您的解释。
    • @user1588670 考虑到您提到输入字符串是“HUGE”,这根本不是正确答案。您需要一个不复制数据的解决方案。我相信这实际上会根据 Reverse 的实施方式制作 2 个数据副本。
    • @Mike,你是绝对正确的,很明显我的回答不会提供最佳性能。但是,我不能同意您的陈述的一般性:创建副本可能有问题,也可能没有问题 - 取决于字符串的长度和执行此操作的频率。有时需要维护的代码行更少比过早优化更重要。该问题以最简单的形式陈述,无需考虑任何上下文或任何性能要求。因此,完成这项工作的最简单的解决方案是一个有效的解决方案,甚至可能是最好的解决方案。
    【解决方案3】:

    如果这是在一个文件中并且文件特别大,您可能希望有效地执行此操作。一种方法是向后读取文件,然后只读取前 10 行。您可以查看使用 Jon Skeet 的 MiscUtil 库来执行此操作的示例 here

    var lines = new ReverseLineReader(filename);
    var last = lines.Take(10);
    

    【讨论】:

    • OP 没有提到该字符串来自文件。
    【解决方案4】:

    这是一种方法,其优点是它不会创建整个源字符串的副本,因此相当有效。大多数代码将与其他通用扩展方法一起放在一个类中,因此最终结果是您可以用 1 行代码完成

    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;
            }
        }
    }
    

    编辑:有人指出这可能更有效,因为它会迭代整个字符串。我还认为带有列表的 RemoveAt(0) 也可能效率低下。为了解决这个问题,可以修改代码以向后搜索字符串。这将消除对 TakeLast 函数的需求,因为我们可以只使用 Take。

    【讨论】:

      【解决方案5】:

      随着字符串变大,避免处理无关紧要的字符变得更加重要。任何使用string.Split 的方法都是低效的,因为必须处理整个字符串。一个有效的解决方案必须从后面穿过字符串。这是一个正则表达式方法。

      请注意,它返回一个List&lt;string&gt;,因为在返回之前需要反转结果(因此使用Insert 方法)

      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;
      }
      

      【讨论】:

      • 我无法投票,但在尝试了所有解决方案之后,这就是要走的路,速度很快,谢谢西蒙,你是一个了不起的程序员。
      • @SimonMcKenzie 不错的解决方案。 RegEx 是 C# 的一个非常强大但经常被忽视的特性。
      【解决方案6】:

      节省空间的方法

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

      【讨论】:

        猜你喜欢
        • 2021-07-22
        • 2017-12-28
        • 2014-08-24
        • 1970-01-01
        • 1970-01-01
        • 2011-12-19
        • 1970-01-01
        • 2016-05-06
        相关资源
        最近更新 更多