-1

Trying to "compress" a string by replacing duplicate characters with numbers (For example, the string aabcccccaaa would become a2blc5a3). I tried to use a Linked HashMap to solve the problem because the input order needs to be preserved, but the counter I created doesn't seem to be incrementing properly. Any insight would be greatly appreciated.

public class StringCompression {
    
    public static void main(String[] args) {
        
        String s = "aabcccccaaa";
        System.out.println(compString(s));
        
    }
    
    public static String compString(String str) {
        
        LinkedHashMap <Character, Integer> alphabet = new LinkedHashMap<>();
        StringBuilder strbdr = new StringBuilder();     
        
        for(int i = 0; i < str.length(); i++) {
            
            if(alphabet.containsKey(str.charAt(i))) {
                alphabet.put(str.charAt(i), alphabet.get(str.charAt(i))+1);
            }
            
            alphabet.put(str.charAt(i), 1);
        }
        
//      System.out.println(alphabet.entrySet());
        
        for(var entry : alphabet.entrySet()) {
            strbdr.append(entry.getKey());
            strbdr.append(entry.getValue());
        }
        
        return strbdr.toString();
    }
}
azro
  • 53,056
  • 7
  • 34
  • 70

3 Answers3

3

Problem 1

The line alphabet.put(str.charAt(i), 1) keeps reseting each value to 1 , you need to put it in a else

for (int i = 0; i < str.length(); i++) {
    if (alphabet.containsKey(str.charAt(i))) {
        alphabet.put(str.charAt(i), alphabet.get(str.charAt(i)) + 1);
    } else {
        alphabet.put(str.charAt(i), 1);
    }
}

Problem 2

First fix leads to a5b1c5 as maps have unique keys, so you can't count the a at the beginning and the a at the end


Just keep track of the previous char seen, and a counter

public static String compString(String str) {
    StringBuilder sb = new StringBuilder();
    char prev = '\0';
    int count = 0;
    for (char letter : str.toCharArray()) {
        if (prev == '\0') {
            prev = letter;
        } else if (prev != letter) {
            sb.append(prev).append(count);
            count = 0;
            prev = letter;
        }
        count += 1;
    }
    return sb.append(prev).append(count).toString();
}
azro
  • 53,056
  • 7
  • 34
  • 70
0
alphabet.put(str.charAt(i), 1);

is not in an else statement and is executed everytime

HopefullyHelpful
  • 1,652
  • 3
  • 21
  • 37
0

As mentioned above, a map-based solution would be needed if the frequencies of all characters are counted, but this task is similar to run-length encoding.

It's worth mentioning that adding frequency 1 after each single letter seems to be redundant because in this case the length of the string is doubled: abc -> a1b1c1.

Also, if the input string contains other digits, the compressed string should contain some "escape" character to distinguish the digit from the count number.

The following code snippet addreses the mentioned issues:

static String compress(String str) {
    if (null == str || str.isEmpty()) {
        return str;
    }
    StringBuilder sb = new StringBuilder();
    for (int i = 0, n = str.length(); i < n; ) {
        char c = str.charAt(i);
        int count = 1;
        for (int j = i + 1; j < n && str.charAt(j) == c; j++) count++;
        if (Character.isDigit(c) || '~' == c) sb.append('~'); // indicate digit
        sb.append(c);
        if (count > 1) { // skip output of frequency = 1
            sb.append(count);
        }
        i += count;
    }
    return sb.toString();
}

Test:

Stream.of("abc", "aaabccdddd", "1111111111123334553aaa", "111111111112333455~~~aaa")
    .forEach(s -> System.out.printf("%-24s (%2d) -> %s (%d)%n", 
        s, s.length(), compress(s), compress(s).length()
    ));

Output:

abc                      ( 3) -> abc (3)
aaabccdddd               (10) -> a3bc2d4 (7)
1111111111123334553aaa   (22) -> ~111~2~33~4~52~3a3 (18)
111111111112333455~~~aaa (24) -> ~111~2~33~4~52~~3a3 (19)
Nowhere Man
  • 19,170
  • 9
  • 17
  • 42