Cross Training Home Development Chat Log Concept #5 — String Manipulation
TNT  ›  Cross Training  ›  String Manipulation
Concept #5

String Manipulation

Visit every character. Test it. Swap it if it qualifies. Accumulate the result.
The building block of every text transform — seen here in three languages.

What Conlexia Is Really Doing

The letters b, d, p, q are mirror-images of each other — identical shapes rotated or flipped in space. For some readers, distinguishing them requires deliberate effort. The Conlexia app uses that confusion intentionally: it takes any phrase, finds every confusable letter, and swaps it with a randomly chosen different member of the same set.

The core of that app is a single function — scrambleTargetLetters — that does two things every programmer does constantly: iterate over a string (visit each character one by one) and build a new string (accumulate a result character by character, conditionally). Every text transform you will ever write — search-and-replace, censoring, encoding, formatting — uses some version of these two operations.

This concept page strips away the UI, the textarea, and the buttons, and shows just the function — in JavaScript, Java, and Python 3. The algorithm is identical. The vocabulary is not.

The two things this page demonstrates are inseparable: how each language lets you walk through a string one character at a time, and how each language accumulates a result string efficiently.

The concept, before any language

// targets = "bdpq", message = any phrase
DEFINE scramble(message, targets):
result ← “” // start with empty string

FOR EACH character IN message:
lower ← lowercase(character)

IF lower IS IN targets:
  pool ← targets MINUS lower
  pick ← RANDOM element from pool
  IF character was uppercase: pick ← UPPERCASE(pick)
  result ← result + pick
ELSE:
  result ← result + character

RETURN result

Every code example below is a direct translation of this pseudocode. The concept is identical — only the string vocabulary differs.

The Same Idea in Three Languages

Same algorithm. Same targets. Same sample phrase. Study how each language iterates a string, tests membership, and accumulates a result.

JavaScript Vanilla JS
// str[i] — bracket indexing; strings
// behave like arrays
// str.includes() — membership test
// str.replace()  — removes first occurrence
// Math.random()  — no import needed

var targets = "bdpq";
var sample  = "Peter Piper picked a peck of " +
              "pickled peppers quite quickly " +
              "didn't he, and better than his " +
              "buddy Bobby Budden!";

function scrambleTargetLetters(
        message, targets) {
    var result = "";

    for (var i = 0; i < message.length; i++) {
        var symbol = message[i];
        var lower  = symbol.toLowerCase();

        if (targets.includes(lower)) {
            var pool = targets.replace(lower, "");
            var pick = pool[
                Math.floor(Math.random() * pool.length)];
            result += (symbol === symbol.toUpperCase())
                ? pick.toUpperCase()
                : pick;
        } else {
            result += symbol;
        }
    }

    return result;
}

var enc = scrambleTargetLetters(sample, targets);
console.log("Original : " + sample);
console.log("Encrypted: " + enc);
Java Java 17+
// charAt() returns char (primitive, not String)
// StringBuilder — efficient loop accumulation
// Character.* — case ops on char primitives
// String.valueOf(c) — converts char to String
//                     (required for contains())

import java.util.Random;

public class ConlexiaDemoDriver {

    public static String scrambleTargetLetters(
            String message, String targets) {
        StringBuilder result = new StringBuilder();
        Random rng = new Random();

        for (int i = 0; i < message.length(); i++) {
            char symbol   = message.charAt(i);
            char lower    = Character.toLowerCase(symbol);
            String lowerStr = String.valueOf(lower);

            if (targets.contains(lowerStr)) {
                String pool = targets.replace(lowerStr, "");
                char pick = pool.charAt(
                        rng.nextInt(pool.length()));
                result.append(Character.isUpperCase(symbol)
                        ? Character.toUpperCase(pick)
                        : pick);
            } else {
                result.append(symbol);
            }
        }

        return result.toString();
    }

    public static void main(String[] args) {
        String targets = "bdpq";
        String sample  = "Peter Piper picked a peck of " +
            "pickled peppers quite quickly " +
            "didn't he, and better than his " +
            "buddy Bobby Budden!";

        String enc = scrambleTargetLetters(sample, targets);
        System.out.println("Original : " + sample);
        System.out.println("Encrypted: " + enc);
    }
}
Python 3 Python 3.x
# for symbol in message — direct iteration
#   no index, no len(), no [i]
# lower in targets — 'in' operator
# random.choice(pool) — picks randomly
#   from any sequence; no index math
# str.isupper() / str.upper() — case
#   as methods on the string itself

import random

def scramble_target_letters(message, targets):
    result = ""

    for symbol in message:     # direct iteration
        lower = symbol.lower()

        if lower in targets:   # 'in' reads naturally
            pool = targets.replace(lower, "")
            pick = random.choice(pool)  # no index math
            result += pick.upper() if symbol.isupper() else pick
        else:
            result += symbol

    return result

def main():
    targets = "bdpq"
    sample  = ("Peter Piper picked a peck of "
               "pickled peppers quite quickly "
               "didn't he, and better than his "
               "buddy Bobby Budden!")

    enc = scramble_target_letters(sample, targets)
    print("Original : " + sample)
    print("Encrypted: " + enc)

if __name__ == "__main__":
    main()

Why Does Each Version Look Different?

The guiding question for every Cross Training page: “What does each version reveal about the language’s personality?”

JavaScript

Strings behave like arrays. In JavaScript, message[i] gives you the character at position i just as it would for an array element. The language was designed with this convenience built in — no special method required. This means the index-based for loop that already worked for arrays transfers directly to strings without any new syntax.

String.prototype.includes() tests whether a substring exists anywhere in a string. Here we use it to check whether a single character belongs to the target set. JavaScript inherited includes() from the array API — the same method works on both arrays and strings, which is no coincidence. JavaScript’s philosophy: if the behavior makes sense, expose it everywhere.

String.prototype.replace(string, string) replaces the first occurrence only. For our target set where each letter appears once, this is always correct — but it is a detail worth noting. The Java and Python versions replace all occurrences; with a well-formed target string, the result is the same. JavaScript quietly does less and trusts you to know.

Java

Strings and characters are different things. String.charAt(i) returns a char — a 16-bit primitive, not an object. Java's case operations (Character.toLowerCase(), Character.isUpperCase()) live on the Character utility class because they operate on primitives, not on String objects. String.contains() requires a CharSequence, so you must first convert the char primitive to a String via String.valueOf(lower). This is not accidental overhead — it is the compiler enforcing type safety at each step.

StringBuilder vs. +=. Java strings are immutable objects. Every result += symbol in a loop creates a brand-new String object in memory, discarding the previous one. For a 100-character message that is 100 discarded objects. StringBuilder.append() modifies a resizable internal buffer in place — one object, no copies. JavaScript and Python handle this for you behind the scenes. Java makes the trade-off visible and asks you to make the efficient choice explicitly.

Python 3

Direct string iteration. for symbol in message visits each character without an index, a length check, or bracket notation. Python treats a string as a directly iterable sequence of characters. The loop says what it means: “for each symbol in this message.” JavaScript and Java both require the programmer to maintain an index variable; Python makes that invisible.

random.choice(pool) picks a random element from any sequence. JavaScript needs pool[Math.floor(Math.random() * pool.length)] — five tokens to express “pick one randomly.” Python has the tool already. random.choice() works on strings, lists, tuples, or any sequence. One word, right intention. Python’s standard library is dense with these conveniences precisely because the language was designed to make common operations look obvious.

Case as a method. symbol.isupper() and pick.upper() are called on the string itself. In Java, case operations live on a separate Character class because they operate on a primitive type. In Python, everything is an object — including individual characters — so case operations live where they belong: on the thing being transformed.