4
\$\begingroup\$

I wrote working random name generator in Java. Here's my code:

NameGenerator.java:

package pl.hubot.dev.nameGenerator;

import java.util.*;
import java.util.stream.Collectors;

public class NameGenerator {
    private static final String CHARACTERS = "abcdefghijklmnopqrstuvwxyz";
    private static Random random = new Random();

    void generate() {
        WordChecker wordChecker = new WordChecker();
        StringSimilarity stringSimilarity = new StringSimilarity();
        Dictionary dictionary = new Dictionary();
        final String filepath = "/usr/share/dict/american-english";
        dictionary.build(filepath);
        List<String> syllabes = combination(CHARACTERS, 3).parallelStream()
                .filter((x) -> wordChecker.countSyllables(x) > 1).collect(Collectors.toList());
        String word = "";
        while (stringSimilarity.similarity(word, dictionary.getRandomEntries(1)[0]) < 0.6) {
            word = syllabes.get(random.nextInt(syllabes.size())) + syllabes.get(random.nextInt(syllabes.size()));
        }
        System.out.println(word);
    }

    private Set<String> combination(String input, int n) {
        List<Character> letters = new ArrayList<Character>();
        for (int i = 0; i < input.length(); ++i)
            letters.add(input.charAt(i));
        Set<String> results = new HashSet<String>();
        combination("", letters, n, results);
        return results;
    }

    private void combination(String soFar, List<Character> letters, int n,
                            Set<String> results) {
        if (n == 0) {
            results.add(soFar);
            return;
        }

        int startIndex = soFar.length();
        if (startIndex >= letters.size())
            return;

        for (int i = startIndex; i < letters.size(); ++i) {
            // ch is the next candidate to add to the result that we're
            // building (soFar)
            char ch = letters.get(i);
            // Swap the characters at positions i and position startIndex.
            char temp = letters.get(startIndex);
            letters.set(i, temp);
            letters.set(startIndex, ch);

            // add ch to soFar, compute combinations of length n-1.
            // as startIndex is essentially soFar.length() this means that
            // the recursive call will only process the remainder of the list.
            combination(soFar + ch, letters, n - 1, results);

            // Swap the characters back - restore the original state.
            letters.set(i, ch);
            letters.set(startIndex, temp);
        }
    }
}

Dictionary.java:

package pl.hubot.dev.nameGenerator;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class Dictionary {
    private int M = 1319; //prime number
    final private Bucket[] array;

    public Dictionary() {
        array = new Bucket[M];
        for (int i = 0; i < M; i++) {
            array[i] = new Bucket();
        }
    }

    private int hash(String key) {
        return (key.hashCode() & 0x7fffffff) % M;
    }

    // call hash() to decide which bucket to put it in, do it.
    private void add(String key) {
        array[hash(key)].put(key);
    }

    // call hash() to find what bucket it's in, get it from that bucket.
    public boolean contains(String input) {
        input = input.toLowerCase();
        return array[hash(input)].get(input);
    }

    public void build(String filePath) {
        try {
            BufferedReader reader = new BufferedReader(new FileReader(filePath));
            String line;
            while ((line = reader.readLine()) != null) {
                add(line);
            }
        } catch (IOException ioe) {
            ioe.printStackTrace();
        }

    }

    // this method is used in my unit tests
    public String[] getRandomEntries(int num){
        String[] toRet = new String[num];
        for (int i = 0; i < num; i++){
            //pick a random bucket, go out a random number
            Bucket.Node n = array[(int) (Math.random() * M)].first;
            int rand = (int) (Math.random() * (int) Math.sqrt(num));

            for (int j = 0; j < rand && n.next != null; j++) n = n.next;
            toRet[i] = n.word;
        }
        return toRet;
    }

    class Bucket {
        private Node first;

        private boolean get(String in) {         //return key true if key exists
            Node next = first;
            while (next != null) {
                if (next.word.equals(in)) {
                    return true;
                }
                next = next.next;
            }
            return false;
        }

        private void put(String key) {
            for (Node curr = first; curr != null; curr = curr.next) {
                if (key.equals(curr.word)) {
                    return;                     //search hit: return
                }
            }
            first = new Node(key, first); //search miss: add new node
        }

        class Node {
            String word;
            Node next;

            private Node(String key, Node next) {
                this.word = key;
                this.next = next;
            }
        }
    }
}

StringSimilarity.java:

package pl.hubot.dev.nameGenerator;

public class StringSimilarity {

    /**
     * Calculates the similarity (a number within 0 and 1) between two strings.
     */
    double similarity(String s1, String s2) {
        String longer = s1, shorter = s2;
        if (s1.length() < s2.length()) { // longer should always have greater length
            longer = s2; shorter = s1;
        }
        int longerLength = longer.length();
        if (longerLength == 0) { return 1.0; /* both strings are zero length */ }
        /* // If you have StringUtils, you can use it to calculate the edit distance:
        return (longerLength - StringUtils.getLevenshteinDistance(longer, shorter)) /
                               (double) longerLength; */
        return (longerLength - editDistance(longer, shorter)) / (double) longerLength;

    }

    void printSimilarity(String s, String t) {
        System.out.println(String.format(
                "%.3f is the similarity between \"%s\" and \"%s\"", similarity(s, t), s, t));
    }

    // Example implementation of the Levenshtein Edit Distance
    // See http://rosettacode.org/wiki/Levenshtein_distance#Java
    private int editDistance(String s1, String s2) {
        s1 = s1.toLowerCase();
        s2 = s2.toLowerCase();

        int[] costs = new int[s2.length() + 1];
        for (int i = 0; i <= s1.length(); i++) {
            int lastValue = i;
            for (int j = 0; j <= s2.length(); j++) {
                if (i == 0)
                    costs[j] = j;
                else {
                    if (j > 0) {
                        int newValue = costs[j - 1];
                        if (s1.charAt(i - 1) != s2.charAt(j - 1))
                            newValue = Math.min(Math.min(newValue, lastValue),
                                    costs[j]) + 1;
                        costs[j - 1] = lastValue;
                        lastValue = newValue;
                    }
                }
            }
            if (i > 0)
                costs[s2.length()] = lastValue;
        }
        return costs[s2.length()];
    }
}

WordChecker.java:

package pl.hubot.dev.nameGenerator;

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

public class WordChecker {
    int countSyllables(String word) {
        int count = 0;
        word = word.toLowerCase();
        for (int i = 0; i < word.length(); i++) {
            if (word.charAt(i) == '\"' || word.charAt(i) == '\'' || word.charAt(i) == '-' || word.charAt(i) == ',' || word.charAt(i) == ')' || word.charAt(i) == '(') {
                word = word.substring(0,i)+word.substring(i+1, word.length());
            }
        }
        boolean isPrevVowel = false;
        for (int j = 0; j < word.length(); j++) {
            if (word.contains("a") || word.contains("e") || word.contains("i") || word.contains("o") || word.contains("u")) {
                if (isVowel(word.charAt(j)) && !((word.charAt(j) == 'e') && (j == word.length()-1))) {
                    if (!isPrevVowel) {
                        count++;
                        isPrevVowel = true;
                    }
                } else {
                    isPrevVowel = false;
                }
            } else {
                count++;
                break;
            }
        }
        return count;
    }

    boolean checkIfAreOnlyVowels(String input) {
        return checkForCharacters(input, "aeiouy");
    }

    boolean checkIfAreOnlyConsonants(String input) {
        return checkForCharacters(input, "bcdfghjklmnpqrstvwxz");
    }

    boolean checkForCharacters(String input, String chars) {
        List<Boolean> logicalArray = new ArrayList<Boolean>();

        for (int i = 0; i < input.length(); i++) {
            boolean isChar = false;
            for (int j = 0; j < chars.toCharArray().length; j++) {
                if (input.charAt(i) == chars.toCharArray()[j]) isChar = true;
            }
            logicalArray.add(isChar);
        }

        boolean areGivenChars = false;
        if (logicalArray.size() == input.length()) {
            for (boolean logicalValue : logicalArray) {
                if (logicalValue) areGivenChars = true;
                else return false;
            }
        }

        return areGivenChars;
    }

    private boolean isVowel(char c) {
        return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u';
    }
}

Generated names are based on English dictionary and there are composites of random syllables. Generated names are checked for similarity with specific English words. If we gain similarity coefficient determined by while loop condition, then generated name will be more similar to specific English words until identity. We can also adjust a number of syllables in filter method at List<String> syllabes = combination(CHARACTERS, 3).parallelStream().filter((x) -> wordChecker.countSyllables(x) > 1).collect(Collectors.toList());.

Could I ask about review of this code?

\$\endgroup\$
  • \$\begingroup\$ You need to add references to Dictionary, StringSimilarity, and WordChecker otherwise this is a broken stub. \$\endgroup\$ – Legato Jun 13 '17 at 16:40
  • \$\begingroup\$ @Legato Ok, I did. \$\endgroup\$ – user120313 Jun 13 '17 at 16:48
  • \$\begingroup\$ What's the point of count syllables? \$\endgroup\$ – Legato Jun 13 '17 at 17:00
  • \$\begingroup\$ Got it, was looking at only the WordChecker class. \$\endgroup\$ – Legato Jun 13 '17 at 17:17
3
\$\begingroup\$

Consider Regex

Your WordChecker class has numerous helper methods that all bubble up to just pattern matching. This is exactly why Regex exists, Java has Pattern and Matcher classes in java.util.regex that can help you do this fairly succinctly. For example, to check for only vowels or only consonants you can have this:

public class WordChecker {
    private static final Pattern VOWEL_PATTERN = Pattern.compile("^[aeiouy]+$");
    private static final Pattern CONSONANT_PATTERN = Pattern.compile("^[bcdfghjklmnpqrstvwxz]+$");

    private static boolean onlyVowels(String input) {
        return VOWEL_PATTERN.matcher(input).matches();
    }

    private static boolean onlyConsonants(String input) {
        return CONSONANT_PATTERN.matcher(input).matches();
    }
}

Do note, however, that I make these methods static, so call them with WordChecker.onlyVowels("..."); As it stands all the content consists of helper functions independent of any particular implementation so being static fits well.

For further reference look at the documentation here.

Use meaningful names; avoid deceptive names.

Naming things can be surprisingly difficult especially when striving to keep it short and indicate full meaning, yet only returning 0 or 1 seems completely unexpected from a method that is meant to return the similarity between two things (similarity implies a multitude of outputs on a range, the way you apply it here essentially turn it into a boolean).

Leverage the API

One of the premier merits of using any language in particular is the available libraries. Unless this was done as an exercise the majority of the Dictionary class can be eliminated by using a HashMap.

\$\endgroup\$

Your Answer

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