简体   繁体   中英

Code that takes a string and recognizes the number of consecutive letters

To do my job, I need a code that takes a word from the user, then recognizes the number of consecutive letters and outputs it in such a way that it prints the letter and the number of times it is repeated.

Example 1 input:

hhhttrew

Example 1 output:

h3t2rew

Example 2 input:

uuuuuuhhhaaajqqq

Example 2 output:

u6h3a3jq3
String text = sc.nextLine();
            int len = text.length();

            int repeat = 0;

            char[] chars = new char[len];

            // To convert string to char
            for (int h = 0; h < len; h++)
            {
                chars[h] = text.charAt(h);
            }

            String finaly = "";

            for (char ignored : chars)
            {
                for (int j = 0 ; j <len ; j++ )
                {
                    if (chars[j] == chars[j+1])
                    {
                        finaly = String.valueOf(chars[j]);

                        repeat++;

                        finaly = String.valueOf(repeat);
                    }
                    else
                    {
                        j++;
                    }
                }
            }
            System.out.println(finaly);

Here is one way. You only need a single loop. The inner loop does the work. The outer loop simply supplies test cases.

  • assign the first character
  • and set count to 1 for that character
  • then iterate until adjacent characters are different
  • append count if > 1 and append the different character
  • set count to 0 for next run.
String[] data = { "uuuuuuhhhaaajqqq", 
        "hhhttrew","abbcccddddeeeeeffffffggggggg" };

for (String s : data) {
    String result = "" + s.charAt(0);
    int count = 1;
    for (int i = 1; i < s.length(); i++) {
        if (s.charAt(i - 1) != s.charAt(i)) {
            result += count <= 1 ? "" : count;
            result += s.charAt(i);
            count = 0;
        }
        count++;
        if (i == s.length() - 1) {
            result += count <= 1 ? "" : count;
        }
    }
    System.out.printf("%-15s <-- %s%n", result, s);
}

prints

u6h3a3jq3       <-- uuuuuuhhhaaajqqq
h3t2rew         <-- hhhttrew
ab2c3d4e5f6g7   <-- abbcccddddeeeeeffffffggggggg

In a comment (now deleted) you had enquired how to reverse the process. This is one way to do it.

  • allocate a StringBuilder to hold the result.
  • initialize count and currentChar
  • as the string is processed,
    • save a character to currentChar
    • then while the next char(s) is a digit, build the count
  • if the count is still 0, then the next character was a digit so bump count by one and copy the currentChar to the buffer
  • otherwise, use the computed length.
String[] encoded =
        { "u6h3a3jq3", "h3t2rew", "ab2c3d4e5f6g7" };

for (String s : encoded) {
    
    StringBuilder sb = new StringBuilder();
    int count = 0;
    char currentChar = '\0';
    for (int i = 0; i < s.length();) {
        if (Character.isLetter(s.charAt(i))) {
            currentChar = s.charAt(i++);
        }
        while (i < s.length()
                && Character.isDigit(s.charAt(i))) {
            count = count * 10 + s.charAt(i++) - '0';
        }
        count = count == 0 ? 1 : count;
        sb.append(Character.toString(currentChar)
                .repeat(count));
        count = 0;
    }
    System.out.println(s + " --> " + sb);
}

prints

u6h3a3jq3 --> uuuuuuhhhaaajqqq
h3t2rew --> hhhttrew
ab2c3d4e5f6g7 --> abbcccddddeeeeeffffffggggggg

Your solution can be greatly improved by just having one loop. Also, this does not use any complex data structures, so it is very efficient.

public static String consecutiveLetters(String text) {
        if (text == null || text.length() == 0) return "";

        // We start with he first letter
        char currentChar = text.charAt(0);
        int position = 1;
        int letterCount = 1;
        StringBuilder outputBuilder = new StringBuilder();

        while (position < text.length()) {
            // get the letter at the current position
            char charAtCurrentPos = text.charAt(position);

            // if it is different than what we had previously, we store the letter and the number of times it appeared. Reset the counter for the new letter
            if (charAtCurrentPos != currentChar) {
                outputBuilder.append(currentChar);
                currentChar = charAtCurrentPos;
                if (letterCount > 1) {
                    outputBuilder.append(letterCount);
                }
                letterCount = 1;
            } else {
                letterCount++;
            }
            position++;
        }

        // Add the last character as well
        outputBuilder.append(currentChar);
        if (letterCount > 1) {
            outputBuilder.append(letterCount);
        }

        return outputBuilder.toString();
    }

You could obtain matches of the regular expression

(.)\1*

This produces an array of strings of contiguous characters. For example,

"uuuuuuhhhaaajqqq" => ["uuuuuu", "hhh", "aaa", "j", "qqq"]

It is then a simple matter to convert each element to its desired form:

["uuuuuu", "hhh", "aaa", "j", "qqq"] => ["u6", "h3", "a3", "j", "q3"]

and then join the elements of the latter array (with the joining string being empty) to form the desired string:

"u6h3a3jq3"

The regular expression can be broken down as follows:

(      # begin capture group 1
  .    # match any character
)      # end capture group 1
\1*    # match the contents of capture group 1 zero one or more times

As an alternative to extracting matches of the regular expression above, one could split the string on matches of the the regular expression

(?<=(.))(?!\1)

This produces the same array as before:

"uuuuuuhhhaaajqqq" => ["uuuuuu", "hhh", "aaa", "j", "qqq"]

This regular expression has the following elements.

(?<=   # begin positive lookbehind
  (    # begin capture group 1
    .  # match any character
  )    # end capture group 1
)      # end positive lookbehind
(?!    # begin a negative lookahead
  \1   # match the contents of capture group 1
)      # end negative lookahead

Matches of the this expression are zero-width , meaning that they match locations between adjacent characters. If the string were "abbc" , for example, it would match the locations between 'a' and 'b' and between 'b' and 'c' , splitting the string into three parts.

I would take a different approach. The following algorithm will run in O(n) and thus be asymptotically (!!!) optimal. To make this clear as this seems to have caused some confusion. There are more efficient solutions with smaller constants.

Essentially the idea is to loop over all characters and store the number of occurrences in a Map . If an value that is not yet in the Map is encountered, we add it to the Map giving it a count of 1. If we have already encountered the value before we get that value and increase it by one. In the end we only need to iterate through the map once in order of insertion and get the key and value.

IMPORTANT : It is crucial for this implementation to use a LinkedHashMap as the insertion order MUST be preserved. A HashMap of TreeMap will give you a String which has the values in undefined order. See this thread for more details on this.

The runtime for this algorithm will be O(n) as all operations on the LinkedHashMap take O(1) and the rest of the time we are just looping over the n characters.

import java.util.*;

public class Application {
    public static void main(String[] args) {
        var result1 = compress("hhhttrew");
        var result2 = compress("uuuuuuhhhaaajqqq");
        System.out.println(result1);
        System.out.println(result2);
    }

    public static String compress(String uncompressed) {
        var charArr = uncompressed.toCharArray();
        // LinkedHashMap is required here to preserve the order of insertion
        var occurrences = new LinkedHashMap<Character, Integer>();
        // iterate over all characters and count all occurrences 
        for (var character : charArr) {
            if (occurrences.containsKey(character)) occurrences.put(character, occurrences.get(character) + 1);
            else occurrences.put(character, 1);
        }
        // Create the target String by iterating over the map in order of insertion and concatenate key and value. Don't add the number of occurrence when the character occurs only once
        var sb = new StringBuilder();
        for (var entry : occurrences.entrySet()) {
            sb.append(entry.getKey());
            if(entry.getValue() != 1) sb.append(entry.getValue());
        }
        return sb.toString();
    }

}

Expected output:

h3t2rew
u6h3a3jq3

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