1

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);
nathan liang
  • 1,000
  • 2
  • 11
  • 22
S0A1E2
  • 53
  • 1
  • 8

4 Answers4

0

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
WJS
  • 36,363
  • 4
  • 24
  • 39
0

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();
    }
Petre Popescu
  • 1,950
  • 3
  • 24
  • 38
0

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.

Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100
-1

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
Mushroomator
  • 6,516
  • 1
  • 10
  • 27
  • Just one loop and no complex structures (like a linkedHasMap) can make the solution more efficient. – Petre Popescu Mar 26 '22 at 21:33
  • Yes, true. But you will just be changing constants (which may or may not be large). It won't change the asymptotic runtime to lower than `O(n)` as you need to look at every character at least once thus `O(n)` is the fastest it can get, again asymptotically. That's all I claim :) – Mushroomator Mar 26 '22 at 21:36
  • It may not matter for small texts, however, for bigger ones it will certainly matter. Not only you have to traverse the text and the resulting linked map at the end, but you will also be creating far more objects (and complex ones I must add), a creation that is costly and needs to be garbage-collected. – Petre Popescu Mar 26 '22 at 21:39
  • True, no arguments here. Still, my point remains valid, as this does not affect the runtime in terms of BigO notation. I've edited my answer to clearly state that now :) – Mushroomator Mar 26 '22 at 21:43
  • There are many of us at SO who believe it is unprofessional for a member to ask that their answer be selected, especially when it is done shortly after the question has been posted and the OP is obviously new to SO and therefore may not be familiar with the rules and etiquette of the forum. Nor should the OP be encouraged to select an answer quickly. If, after some hours or days the OP has not selected an answer there is nothing wrong with telling the OP that if they found any of the answers helpful they should [select](https://stackoverflow.com/help/accepted-answer) the one they liked best. – Cary Swoveland Mar 27 '22 at 06:33