First Variation on Caesar Cipher in Java


The challenge

The action of a Caesar cipher is to replace each plaintext letter (plaintext letters are from ‘a’ to ‘z’ or from ‘A’ to ‘Z’) with a different one a fixed number of places up or down the alphabet.

This program performs a variation of the Caesar shift. The shift increases by 1 for each character (on each iteration).

If the shift is initially 1, the first character of the message to be encoded will be shifted by 1, the second character will be shifted by 2, etc…

Coding: Parameters and return of function “movingShift”

param s: a string to be coded

param shift: an integer giving the initial shift

The function “movingShift” first codes the entire string and then returns an array of strings containing the coded string in 5 parts (five parts because, to avoid more risks, the coded message will be given to five runners, one piece for each runner).

If possible the message will be equally divided by message length between the five runners. If this is not possible, parts 1 to 5 will have subsequently non-increasing lengths, such that parts 1 to 4 are at least as long as when evenly divided, but at most 1 longer. If the last part is the empty string this empty string must be shown in the resulting array.

For example, if the coded message has a length of 17 the five parts will have lengths of 4, 4, 4, 4, 1. The parts 1, 2, 3, 4 are evenly split and the last part of length 1 is shorter. If the length is 16 the parts will be of lengths 4, 4, 4, 4, 0. Parts 1, 2, 3, 4 are evenly split and the fifth runner will stay at home since his part is the empty string. If the length is 11, equal parts would be of length 2.2, hence parts will be of lengths 3, 3, 3, 2, 0.

You will also implement a “demovingShift” function with two parameters

Decoding: parameters and return of function “demovingShift”

  1. an array of strings: s (possibly resulting from “movingShift”, with 5 strings)

  2. an int shift

“demovingShift” returns a string.

Example:

u = "I should have known that you would have a perfect answer for me!!!"

movingShift(u, 1) returns :

v = ["J vltasl rlhr ", "zdfog odxr ypw", " atasl rlhr p ", "gwkzzyq zntyhv", " lvz wp!!!"]

(quotes added in order to see the strings and the spaces, your program won’t write these quotes, see Example Test Cases)

and demovingShift(v, 1) returns u.

Reference

Caesar Cipher : http://en.wikipedia.org/wiki/Caesar_cipher

The solution in Java code

Option 1:

import java.util.Arrays;
import java.util.stream.Collectors;
import java.util.List;
import java.util.ArrayList;

public class CaesarCipher {
  public static List<String>  movingShift(String s, int shift) {
    int firstPartsSize;
    if(s.length()%5==0) firstPartsSize=s.length()/5;
    else firstPartsSize=s.length()/5+1;
    
    char[] chars = s.toCharArray();
    for(int i = 0; i<chars.length; i++){
      chars[i] = Character.isLetter(chars[i])
                 ?Character.isUpperCase(chars[i])
                  ?(char)((((int)chars[i]-(int)'A')+shift+i)%26+(int)'A')
                  :(char)((((int)chars[i]-(int)'a')+shift+i)%26+(int)'a')
                 :chars[i];
    }
    String coded = new String(chars);
    return new ArrayList<String>(){
      {
        add(coded.substring(0,firstPartsSize));
        add(coded.substring(firstPartsSize,2*firstPartsSize));
        add(coded.substring(2*firstPartsSize,3*firstPartsSize));
        add(coded.substring(3*firstPartsSize,4*firstPartsSize));
        add(coded.substring(4*firstPartsSize));
      }
    };
  }
  
  public static String demovingShift(List<String> s, int shift) {
    String coded = s.stream().collect(Collectors.joining());
    char[] chars = coded.toCharArray();
    for(int i = 0; i<chars.length; i++){
      chars[i] = Character.isLetter(chars[i])
                 ?Character.isUpperCase(chars[i])
                  ?(char)((((int)chars[i]-(int)'A')-shift-i+(1+i/26)*26)%26+(int)'A')
                  :(char)((((int)chars[i]-(int)'a')-shift-i+(1+i/26)*26)%26+(int)'a')
                 :chars[i];
    }
    return new String(chars);
  }

}

Option 2:

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

public class CaesarCipher {
  private static String upAlpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ", downAlpha = "abcdefghijklmnopqrstuvwxyz";
  public static List<String> movingShift(String s, int dist) {return split(shift(s, dist, 1));}
  public static String demovingShift(List<String> s, int dist) {return shift(String.join("", s), -dist, -1);}
  private static String shift(String s, int dist, int step) {
    AtomicInteger count = new AtomicInteger();
    return s.chars().mapToObj(i -> (char)i).map(c -> {
      boolean upC = upAlpha.contains("" + c), downC = downAlpha.contains("" + c);
      if(!upC && !downC) {
        count.getAndIncrement();
        return c.toString();
      }
      String lib = upC ? upAlpha : downAlpha;
      int loc = (count.getAndIncrement() * step + lib.indexOf(c) + dist) % 26;
      loc = loc >= 0 ? loc : 26 + loc;
      return "" + lib.charAt(loc);
    }).collect(Collectors.joining());
  }
  
  private static List<String> split(String s) {
    int sz = (int) Math.ceil(s.length() / 5.0);
    List<String> out = new ArrayList<>();
    out.add(s.substring(0, sz)); out.add(s.substring(sz, sz * 2)); out.add(s.substring(sz * 2, sz * 3));
    out.add(s.substring(sz * 3, sz * 4)); out.add(s.substring(sz * 4));
    return out;
  }
}

Option 3:

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

public class CaesarCipher {

  public static List<String>  movingShift(String s, int shift) {
    s = convert(s, shift, 1);
    
    List<String> ans = new ArrayList<String>();
    int parts = (int) Math.ceil(s.length()/5.0);
    for (int i = 0 ; i< 3*parts+1 ; i+=parts)
        ans.add( s.substring(i, i+parts) );
    ans.add( s.substring(4*parts) );
    
    return ans;
  }
  
  public static String  demovingShift(List<String> s, int shift) {
    return convert(String.join("",s), shift, -1);
  }

  private static String convert(String s, int shift, int sens) {
      StringBuilder sb = new StringBuilder(s);
      int delta = 0;
      int mod4Neg = 26*(sb.length()/26 + 1);
      
      for (int i = 0 ; i < sb.length() ; i++) {
          if      (Character.isUpperCase(sb.charAt(i))) delta = 65;
          else if (Character.isLowerCase(sb.charAt(i))) delta = 97;
          else                                          continue;
          sb.setCharAt(i, (char) (((int) sb.charAt(i) - delta + mod4Neg + (shift+i) * sens) % 26 + delta) );
      }
      return sb.toString();
  }
}

Test cases to validate our solution

import static org.junit.Assert.*;
import org.junit.Test;
import java.util.*;

public class CaesarCipherTest {

	@Test
	public void test1() {
		String u = "I should have known that you would have a perfect answer for me!!!";
		List<String> v = Arrays.asList("J vltasl rlhr ", "zdfog odxr ypw", " atasl rlhr p ", "gwkzzyq zntyhv", " lvz wp!!!");
		assertEquals(v, CaesarCipher.movingShift(u, 1));
    assertEquals(u, CaesarCipher.demovingShift(CaesarCipher.movingShift(u, 1), 1)); 
	}
}

Additional test cases

import static org.junit.Assert.*;
import org.junit.Test;
import java.util.*;

public class CaesarCipherTest {

  @Test
  public void test1() {
    String u = "I should have known that you would have a perfect answer for me!!!";
    List<String> v = Arrays.asList("J vltasl rlhr ", "zdfog odxr ypw", " atasl rlhr p ", "gwkzzyq zntyhv", " lvz wp!!!");
    assertEquals(u, CaesarCipher.demovingShift(CaesarCipher.movingShift(u, 1), 1)); 
    assertEquals(v, CaesarCipher.movingShift(u, 1));
  }
  @Test
  public void test2() {
    String u = "O CAPTAIN! my Captain! our fearful trip is done;";
    assertEquals(u, CaesarCipher.demovingShift(CaesarCipher.movingShift(u, 1), 1)); 
  }
  @Test
  public void test3() {
    String u = "For you bouquets and ribbon'd wreaths--for you the shores a-crowding;";
    assertEquals(u, CaesarCipher.demovingShift(CaesarCipher.movingShift(u, 1), 1)); 
  }
  @Test
  public void test4() {
    String u = "Exult, O shores, and ring, O bells! But I, with mournful tread, Walk the deck my Captain lies, Fallen cold and dead. ";
    assertEquals(u, CaesarCipher.demovingShift(CaesarCipher.movingShift(u, 1), 1)); 
  }
  @Test
  public void testa() {
    String u = " uoxIirmoveNreefckgieaoiEcooqo";
    List<String> v = Arrays.asList(" xscOp", "zvygqA", "ftuwud", "adaxmh", "Edqrut");
    assertEquals(u, CaesarCipher.demovingShift(CaesarCipher.movingShift(u, 2), 2)); 
    assertEquals(v, CaesarCipher.movingShift(u, 2));
  }
  @Test
  public void testb() {
    String u = "uaoQop jx eh osr okaKv vzagzwpxagokBKriipmc U";
    List<String> v = Arrays.asList("wdsVuw sh", " qu dii h", "evGs uzbi", "caudhoxuM", "Wewxfdu O");
    assertEquals(u, CaesarCipher.demovingShift(CaesarCipher.movingShift(u, 2), 2)); 
    assertEquals(v, CaesarCipher.movingShift(u, 2));
  }
  @Test
  public void testc() {
    String u = "kgpiqislyhvmffdzlyehjiIteAaaotcoapk bbMgaHlda";
    List<String> v = Arrays.asList("mjtnwpaui", "shztutqdr", "ycffGseBc", "dsyiviyu ", "noAvqYdwu");
    assertEquals(u, CaesarCipher.demovingShift(CaesarCipher.movingShift(u, 2), 2)); 
    assertEquals(v, CaesarCipher.movingShift(u, 2));
  }
  @Test
  public void testd() {
    String u = "abcdefghjuty";
    List<String> v = Arrays.asList("bdf", "hjl", "nps", "eek", "");
    assertEquals(u, CaesarCipher.demovingShift(CaesarCipher.movingShift(u, 1), 1)); 
    assertEquals(v, CaesarCipher.movingShift(u, 1));
  }
  @Test
  public void teste() {
    String u = "abcdefghjuty1234";
    List<String> v = Arrays.asList("bdfh", "jlnp", "seek", "1234", "");
    assertEquals(u, CaesarCipher.demovingShift(CaesarCipher.movingShift(u, 1), 1)); 
    assertEquals(v, CaesarCipher.movingShift(u, 1));
  }
}