3
\$\begingroup\$

Write a method leetSpeak that accepts two parameters: a Scanner representing an input file, and a PrintStream representing an output file. Your method should convert the input file's text to "leet speak" (aka 1337 speak), an internet dialect where various letters are replaced by other letters/numbers. Output the leet version of the text to the given output file. Preserve the original line breaks from the input. Also wrap each word of input in parentheses. Perform the following replacements:

table of characters

For example, if the input file lincoln.txt contains the following text:

four score and seven years ago our

fathers brought forth on this continent a new nation And your method is called in the following way:

Scanner input = new Scanner(new File("lincoln.txt"));

PrintStream output = new PrintStream(new File("leet.txt"));

leetSpeak(input,output);

Then after the call, the output file leet.txt should contain the following text:

(f0ur) (sc0r3) (4nd) (s3v3n) (y34rZ) (4g0) (0ur)

(f47h3rZ) (br0ugh7) (f0r7h) (0n) (7hiZ) (c0n7in3n7) (4) (n3w) (n47i0n)

You may assume that each token from the input file is separated by exactly one space.

Hint: You may want to use the String object's replace method, which is used as follows:

String str = "mississippi"; str = str.replace("s", "*"); // str = "miiippi"

I'm looking for general feedback, and other ways to accomplish the task of solving this problem. My hope is that there will be some interesting feedback on this that I can apply to my future coding. If there are any bugs in my code, please let me know.

public static void leetSpeak (Scanner sc, PrintStream ps) {
    while (sc.hasNextLine()) {
        String line = new String(sc.nextLine());
        Scanner tokenScanner = new Scanner(line);
        while (tokenScanner.hasNext()) {
            String token = new String(tokenScanner.next());
            token = token.replace("o", "0");
            token = token.replace("l", "1");
            token = token.replace("e", "3");        
            token = token.replace("a", "4");
            token = token.replace("t", "7");
            if (token.substring(token.length() - 1).equals("s")) {
                    token = new String(token.substring(0, token.length() - 1));
                    token += "Z";
            } 
            ps.print("(" + token + ")" + " ");
        }
    ps.println();
    }
}
\$\endgroup\$
  • \$\begingroup\$ @ShubhamSrivastava Just FYI: in the documentation for Java SE 7 String replace method: String replace(char oldChar, char newChar) If the character oldChar does not occur in the character sequence represented by this String object, then a reference to this String object is returned. There are only a few minor differences between my code and your link to another person's code; mainly that in the code you linked there's redundant checking if the string contains that substring. \$\endgroup\$ – cody.codes Nov 30 '17 at 17:06
2
\$\begingroup\$

I tested your code with a main that calls

leetSpeak(new Scanner(System.in), System.out);

Your code has three calls to new String(String). You may remove all three calls; they only make a copy of the string, and you don't need the copy. Every instance of java.lang.String is immutable; the contents of the string never change, so you never need to copy a java.lang.String to keep its contents.

Your code prints an extra space at the end of each line. It's only one space, but it has a slight chance to make a line too long. Some displays (like my terminal window) split a long line into two lines, so I might see an extra line with just the space.


I would use regular expressions. I have used regexps in Perl and Ruby; regular expressions in Java aren't too different from Perl. The code with regexps is

public static void leetSpeak(Scanner sc, PrintStream ps) {
    while (sc.hasNextLine()) {
        String line = sc.nextLine()
            .replace('o', '0')
            .replace('l', '1')
            .replace('e', '3')
            .replace('a', '4')
            .replace('t', '7')
            .replaceAll("s(?=\\s|$)", "Z")
            .replaceAll("\\S+", "($0)");
        ps.println(line);
    }
}

I copied your while loop to read line by line. I don't split the line into tokens, because the regexps work on the whole line. The regexps are really s(?=\s|$) and \S+, but Java's string literals need "\\" to put a single \ in the regexp.

The regexp s(?=\s|$) looks for s at the end of a token. The (?=XXX) looks ahead of the s; it looks for whitespace \s or | end of line $. The look-ahead isn't part of the match, so the regexp only matches s, and the replacement changes s to Z.

The regexp \S+ looks for tokens. \S is the opposite of \s, and + means to match one or more \S. Then ($0) wraps each token in parentheses, because $0 is the matched text.


What is a word? The code changes players. into (p14y3rs.). It isn't (p14y3rZ). because the code puts all non-whitespace in the word. This is because, "You may assume that each token from the input file is separated by exactly one space."

The code adds a line break after the last line if it wasn't there. So it does "preserve the original line breaks from the input", but it might also add one more line break. This is because Java's nextLine always chomps off the line break, so we don't know whether the last line ended with a line break.

\$\endgroup\$
4
\$\begingroup\$

For the sake of efficiency, I suggest you use a StringBuilder. The problem here is that String.replace will have to copy a this string applying the character replacement several times. With a StringBuilder you can apply those character replacements in a single pass over the input string. I had this in mind:

public static String to13375p34k(String word) {
    StringBuilder sb = new StringBuilder(word);

    for (int i = 0; i < sb.length(); ++i) {
        char currentCharacter = sb.charAt(i);

        switch (currentCharacter) {
            case 'o':
                sb.setCharAt(i, '0');
                break;

            case 'l':
                sb.setCharAt(i, '1');
                break;

            case 'e':
                sb.setCharAt(i, '3');
                break;

            case 'a':
                sb.setCharAt(i, '4');
                break;

            case 't':
                sb.setCharAt(i, '7');
                break;
        }
    }

    if (sb.charAt(sb.length() - 1) == 's') {
        sb.setCharAt(sb.length() - 1, 'Z');
    }

    return sb.toString();
}
\$\endgroup\$
  • \$\begingroup\$ I chose the other answer because I felt like it would benefit me the most while I'm learning CS. But in the real world, I'll be using the StringBuilder class whenever I need to optimize for efficiency. \$\endgroup\$ – cody.codes Dec 1 '17 at 19:23
  • 1
    \$\begingroup\$ @cody.codes Fair enough. Just remember that whenever manipulating many strings StringBuilder is your best friend. \$\endgroup\$ – coderodde Dec 2 '17 at 9:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.