简体   繁体   中英

Find count of each consecutive characters

Need to find the count of each consecutive characters in a row. Ex: aaaabbccaa output: 4a2b2c2a

Character may repeat but need to count only consecutive ones. I also need to maintain original sequence.

I tried following but it groups all characters so was not useful.

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

Here is a LINQ solution:

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]}")));

Here is the iterator solution:

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

Regular expression to the rescue ?

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

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

This small program will do the trick, but it's not a single line nice linq statement. Just my two cents.

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

Here is my non-LINQ version that is quite fast compared to LINQ or 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();
}

My LINQ version looks like this, but uses a couple of extension methods I already had:

var ans = str.GroupByRuns().Select(s => $"{s.Count()}{s.Key}").Join();
var chars = "aaaabbccaa".ToCharArray();
int counter = 1;
for (var i = 0; i < chars.Count(); i++)
{
    if (i + 1 >= chars.Count() || chars[i] != chars[i + 1])
    {
        Console.Write($"{counter}{chars[i]}");
        counter = 1;
    }
    else
    {
        counter++;
    }
}

You could have a character var and a counter var outside your Linq scope to keep track of the previous character and the current count and then use linq foreach, but I am just as curious as the rest to why you insist on doing this. Even if you do, the Solution may not be as easy to read as an iterative version and readability and maintenance overhead is very import if anyone else is ever going to read it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM