Cryptanalysis Word Patterns in Java


The challenge

In cryptanalysis, words patterns can be a useful tool in cracking simple ciphers.

A word pattern is a description of the patterns of letters occurring in a word, where each letter is given an integer code in order of appearance. So the first letter is given the code 0, and second is then assigned 1 if it is different to the first letter or 0 otherwise, and so on.

As an example, the word “hello” would become “0.1.2.2.3”. For this task case-sensitivity is ignored, so “hello”, “helLo” and “heLlo” will all return the same word pattern.

Your task is to return the word pattern for a given word. All words provided will be non-empty strings of alphabetic characters only, i.e. matching the regex “[a-zA-Z]+”.

Test cases

import org.junit.Test;
import static org.junit.Assert.assertEquals;
import org.junit.runners.JUnit4;

public class SolutionTest {
    @Test
    public void exampleTests() {
        String[][] tests = {
            {"hello", "0.1.2.2.3"},
            {"heLlo", "0.1.2.2.3"},
            {"helLo", "0.1.2.2.3"},
            {"Hippopotomonstrosesquippedaliophobia", "0.1.2.2.3.2.3.4.3.5.3.6.7.4.8.3.7.9.7.10.11.1.2.2.9.12.13.14.1.3.2.0.3.15.1.13"},
        };
      for(String[] arr: tests)
        assertEquals(arr[1],Crypto.wordPattern(arr[0]));
    }
}

The solution in Java

Option 1:

import java.util.*;

class Crypto {
  public static String wordPattern(final String word){
    var h = new HashMap<Integer,Integer>();
    String[] arr = word.toLowerCase().chars()
                                     .mapToObj( c-> "" + h.computeIfAbsent(c,k->h.size()) )
                                     .toArray(String[]::new);
    return String.join(".", arr);
  }
}

Option 2 (using streams):

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

class Crypto {
  
  static String wordPattern(String word) {
    AtomicInteger nextCode = new AtomicInteger();
    Map<Integer, Integer> codes = new HashMap<>();
    return word.chars()
        .map(Character::toLowerCase)
        .map(ch -> codes.computeIfAbsent(ch, $ -> nextCode.getAndIncrement()))
        .mapToObj(String::valueOf)
        .collect(Collectors.joining("."));
  }

}

Option 3:

import java.util.ArrayList;
import java.util.List;

class Crypto {
  
  public static String wordPattern(final String word){
        String newword = word.toLowerCase();
        List<Character> characterList = new ArrayList<>();
        String result = "";
        for (int i = 0; i < newword.length(); i++) {
            if (!characterList.contains( newword.charAt( i ) )) {
                characterList.add( newword.charAt( i ) );
            }
            if (i != 0) {
                result += ".";
            }
            result += characterList.indexOf( newword.charAt( i ) );
        }

        return result;
    }
}