【问题标题】:Find count of each consecutive characters查找每个连续字符的计数
【发布时间】:2020-02-14 17:58:21
【问题描述】:

需要查找一行中每个连续字符的计数。 例如:aaaabbccaa 输出:4a2b2c2a

字符可以重复,但只需要计算连续的字符。我还需要保持原来的顺序。

我尝试了关注,但它对所有字符进行了分组,所以没有用。

str.GroupBy(c => c).Select(g => new { g.Key, Count = g.Count() }).ToList().ForEach(x => str+= x.Count + "" + x.Key)

【问题讨论】:

  • 这能回答你的问题吗? Run-length encoding of a given string
  • 现在你说你不想使用循环;你能说一下为什么你拒绝使用简单、直接的编程技术来解决你的问题吗?出于某种原因,您似乎想让这个问题变得更难,我不清楚为什么。
  • 如果您的问题是“如何诊断性能问题?”然后问那个问题
  • @Brian:在有限的情况下,正则表达式可以在摊销的情况下获胜,因为一些正则表达式引擎允许您编译您将经常使用的正则表达式,然后生成优化代码以匹配表达。但是你是对的,绝大多数时候,一次性正则表达式会慢得多。也就是说,足够快的定义就是足够快。我们应该通过更多的指标来判断解决方案,而不仅仅是原始速度。
  • 一个快速基准测试表明,对于 10,000 个字符的字符串,正则表达式方法比我的(非常通用的)LINQ 扩展方法 (GroupByRuns) 花费的时间长 1.09 倍,并且比直接的方法慢 12 倍for 循环实现,虽然正则表达式可以通过一些小的优化变得更接近一些。

标签: c# linq


【解决方案1】:

正则表达式救援?

var myString = "aaaabbccaa";

var pattern = @"(\w)\1*";
var regExp = new Regex(pattern);
var matches = regExp.Matches(myString);

var tab = matches.Select(x => String.Format("{0}{1}", x.Value.First(), x.Value.Length));
var result = String.Join("", tab);

【讨论】:

  • 既然MatchCollection没有实现IEnumerable<Match>,难道你不需要用matches.Cast<Match>().Select替换matches.Select吗?
  • 另外,对于那些想知道它是如何工作的人:(\w) 捕获单个单词字符,\1 表示捕获组 1,* 表示 0 或更多匹配。因此,\1* 表示“匹配(\w) 的字符的0 个或多个副本”。因此,正则表达式强制每个匹配是一串相同的字符。其余代码很简单:获取每个匹配项并打印出匹配的第一个字符和匹配项的长度。
【解决方案2】:

这是一个 LINQ 解决方案:

var input = "aaaabbccaa";
var result = string.IsNullOrEmpty(input) ? "" : string.Join("",input.Skip(1)
        .Aggregate((t:input[0].ToString(),o:Enumerable.Empty<string>()),
           (a,c)=>a.t[0]==c ? (a.t+c,a.o) : (c.ToString(),a.o.Append(a.t)),
           a=>a.o.Append(a.t).Select(p => $"{p.Length}{p[0]}")));

这里是迭代器解决方案:

var result = RleString("aaaabbccaa");

private static IEnumerable<(char chr, int count)> Rle(string s)
{
    if (string.IsNullOrEmpty(s)) yield break;

    var lastchar = s.First(); // or s[0]
    var count = 1;
    foreach (char letter in s.Skip(1))
    {
        if (letter != lastchar)
        {
            yield return (lastchar, count);
            lastchar = letter;
            count = 0;
        }
        count++;
    }
    if (count > 0)
        yield return (lastchar, count);
}
private static string RleString(string s)
{
    return String.Join("",Rle(s).Select(z=>$"{z.count}{z.chr}"));
}

【讨论】:

  • +1 使用 Aggregate 和 +1 使用 4 参数版本,但输出应该在字母之前有计数,并且使用 o 作为元组元素名称只是一个坏主意。或者在不清楚时甚至是单个字母。
  • 是的,LINQ 版本可以大幅清理。我只是把它扔在一起,但我不建议使用它。更新以修复输出先计数然后字符。
【解决方案3】:

非LINQ解决方案(dotnetfiddle):

using System;
using System.Text;

public class Program
{
    public static void Main()
    {
        // produces 4a2b2c2a
        Console.WriteLine(GetConsecutiveGroups("aaaabbccaa"));
    }

    private static string GetConsecutiveGroups(string input)
    {       
        var result = new StringBuilder();
        var sb = new StringBuilder();

        foreach (var c in input)
        {
            if (sb.Length == 0 || sb[sb.Length - 1] == c)
            {
                sb.Append(c);
            }
            else
            {
                result.Append($"{sb.Length}{sb[0]}");
                sb.Clear();
                sb.Append(c);
            }
        }

        if (sb.Length > 0)
        {
            result.Append($"{sb.Length}{sb[0]}");
        }

        return result.ToString();
    }
}

【讨论】:

    【解决方案4】:

    这个小程序可以解决问题,但它不是一行漂亮的 linq 语句。只是我的两分钱。

    using System;
    using System.Linq;
    using System.Collections.Generic;
    
    public class Simple {
    
      public static void Main() {    
    
    var text = "aaaabbccaa"; //output: 4a3b2c2a
    var lista = new List<string>();
    var previousLetter = text.Substring(1,1);
    var item = string.Empty;
    foreach (char letter in text)
    {
        if (previousLetter == letter.ToString()){
            item += letter.ToString();          
        }
        else
        {
            lista.Add(item);
            item = letter.ToString();           
        }
        previousLetter = letter.ToString();
    }
    lista.Add(item);    
    foreach (var i in lista)
         Console.WriteLine(i.Substring(1,1) + i.Select(y => y).ToList().Count().ToString());
      }
    }
    

    【讨论】:

      【解决方案5】:

      这是我的非 LINQ 版本,与 LINQ 或 Regex 相比相当快:

          var prevChar = str[0];
          var ct = 1;
          var s = new StringBuilder();
          var len = str.Length;
          for (int j2 = 1; j2 < len; ++j2) {
              if (str[j2] == prevChar)
                  ++ct;
              else {
                  s.Append(ct);
                  s.Append(prevChar);
                  ct = 1;
                  prevChar = str[j2];
              }
          }
          s.Append(ct);
          s.Append(prevChar);
          var final = s.ToString();
      }
      

      我的 LINQ 版本看起来像这样,但使用了我已经拥有的几个扩展方法:

      var ans = str.GroupByRuns().Select(s => $"{s.Count()}{s.Key}").Join();
      

      【讨论】:

        【解决方案6】:

        你可以在你的 Linq 范围之外有一个字符 var 和一个计数器 var 来跟踪前一个字符和当前计数,然后使用 linq foreach,但我和其他人一样好奇你为什么坚持这样做.即使您这样做了,该解决方案也可能不像迭代版本那样易于阅读,并且如果其他人想要阅读它,那么可读性和维护开销非常重要。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2019-11-23
          • 2019-12-13
          • 2021-07-30
          • 1970-01-01
          • 2020-04-08
          • 2022-01-01
          • 1970-01-01
          相关资源
          最近更新 更多