commit 3159a4e46ac50bfec2c9624826a60e33a525ec94 Author: Febbweiss Date: Fri Sep 22 09:57:04 2017 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..114ff4f --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.classpath +.project +.settings \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..04d1878 --- /dev/null +++ b/pom.xml @@ -0,0 +1,22 @@ + + 4.0.0 + fr.pavnay + scrabble-resolver + 0.0.1-SNAPSHOT + + + + commons-cli + commons-cli + 1.4 + + + + junit + junit + 4.12 + test + + + \ No newline at end of file diff --git a/src/main/java/fr/pavnay/scrabble/DictionaryBuilder.java b/src/main/java/fr/pavnay/scrabble/DictionaryBuilder.java new file mode 100644 index 0000000..bae9e54 --- /dev/null +++ b/src/main/java/fr/pavnay/scrabble/DictionaryBuilder.java @@ -0,0 +1,116 @@ +package fr.pavnay.scrabble; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class DictionaryBuilder { + private static Map resolvers = new HashMap(); + + public static Resolver generateResolver(Resolver resolver, File dictionary, int minimalSize, int maximalSize) + throws IOException { + BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(dictionary), "UTF-8")); + String line = null; + + int total = 0; + int valid = 0; + while ((line = br.readLine()) != null) { + int size = line.length(); + total++; + if ((size >= minimalSize) && (size <= maximalSize) && (!line.contains("-")) && (!line.contains(".")) + && (!line.contains("'"))) { + String cleaned = StringUtils.toLowerCaseASCII(line); + if (cleaned != null) { + valid++; + + char[] sortedLetters = StringUtils.sortLetters(cleaned); + + char currentLetter = sortedLetters[0]; + + Node currentNode = resolver.getNode(currentLetter); + for (int i = 1; i < size; i++) { + currentLetter = sortedLetters[i]; + resolver.updateStatistics(currentLetter); + if (i == size - 1) { + currentNode.getNode(currentLetter).addWord(cleaned); + } else { + currentNode = currentNode.getNode(currentLetter); + } + } + } + } + } + br.close(); + + System.out.println("Total words in file : " + total + " - Valid words : " + valid); + return resolver; + } + + public static Enigma generateEnigma(String language, int minLength, int maxLength) { + Resolver resolver; + try { + resolver = loadResolver(language); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + return resolver.generateEnigma(minLength, maxLength); + } + + public static void generateResolver(String language) throws IOException { + File file = new File( + "C:/Users/viaduc105.smallbusiness/Documents/workspace-sts/SocialArena/resources/" + language); + if (!file.exists()) { + System.out.println("No dictionary for " + language); + return; + } + Resolver resolver = generateResolver(new Resolver(), file, 3, 7); + resolver.computeStatistics(); + + ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream( + new File("C:/Users/viaduc105.smallbusiness/Documents/workspace-sts/SocialArena/resources/resolver/" + + language))); + oos.writeObject(resolver); + oos.flush(); + oos.close(); + } + + public static void loadResolvers() throws IOException, ClassNotFoundException { + List languages = ScrabbleUtils.loadLanguages(); + for (String language : languages) { + long t1 = System.currentTimeMillis(); + loadResolver(language); + System.out.println("Resolver in " + language + " loaded in " + (System.currentTimeMillis() - t1) + "ms."); + } + } + + private static Resolver loadResolver(String language) + throws FileNotFoundException, IOException, ClassNotFoundException { + Resolver resolver = (Resolver) resolvers.get(language); + if (resolver == null) { + long t1 = System.currentTimeMillis(); + String path = DictionaryBuilder.class.getResource("/resolvers/" + language).getPath(); + File file = new File(path); + if (!file.exists()) { + System.out.println("No resolver for " + language); + return null; + } + ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file)); + resolver = (Resolver) ois.readObject(); + ois.close(); + + resolvers.put(language, resolver); + System.out.println("Resolver in " + language + " loaded in " + (System.currentTimeMillis() - t1) + "ms."); + } + return resolver; + } +} diff --git a/src/main/java/fr/pavnay/scrabble/Enigma.java b/src/main/java/fr/pavnay/scrabble/Enigma.java new file mode 100644 index 0000000..28816ad --- /dev/null +++ b/src/main/java/fr/pavnay/scrabble/Enigma.java @@ -0,0 +1,61 @@ +package fr.pavnay.scrabble; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class Enigma implements Serializable { + private static final long serialVersionUID = 7542854436817730656L; + private int min; + private int max; + private char[] letters; + private Map> words; + + public Enigma(char[] letters, Map> words, int min, int max) { + this.letters = letters; + this.words = words; + this.min = min; + this.max = max; + } + + public List getLetters() { + List lLetters = new ArrayList(); + if (this.letters == null) { + return lLetters; + } + for (int i = 0; i < this.letters.length; i++) { + lLetters.add(Character.toString(this.letters[i])); + } + return lLetters; + } + + public Map> getWords() { + return this.words; + } + + public boolean isValid(String word) { + if (StringUtils.isBlank(word)) { + return false; + } + List lWords = this.words.get(Integer.valueOf(word.length())); + return lWords.contains(word); + } + + public String toString() { + StringBuilder sb = new StringBuilder("Letters : "); + if (this.letters != null) { + sb.append(new String(this.letters)); + } else { + sb.append("-"); + } + sb.append("\nWords between ").append(this.min).append(" and ").append(this.max).append(" letters : \n"); + for (int i = this.max; i > this.min - 1; i--) { + List lWords = this.words.get(Integer.valueOf(i)); + if (lWords != null) { + sb.append("\twith ").append(i).append(" letters (" + lWords.size() + "): ").append(lWords).append('\n'); + } + } + return sb.toString(); + } +} diff --git a/src/main/java/fr/pavnay/scrabble/Main.java b/src/main/java/fr/pavnay/scrabble/Main.java new file mode 100644 index 0000000..7d95bbb --- /dev/null +++ b/src/main/java/fr/pavnay/scrabble/Main.java @@ -0,0 +1,167 @@ +package fr.pavnay.scrabble; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.util.List; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.CommandLineParser; +import org.apache.commons.cli.DefaultParser; +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; + +public class Main { + + private final static List availableLanguages = ScrabbleUtils.loadLanguages(); + private final static Options helpOptions = configHelpParameter(); + private final static Options options = configParameters(); + + public static void main(String[] args) { + + final CommandLineParser parser = new DefaultParser(); + CommandLine firstLine = null; + try { + firstLine = parser.parse(helpOptions, args, true); + } catch (ParseException e1) { + e1.printStackTrace(); + System.exit(1); + } + + boolean helpMode = firstLine.hasOption("help"); + if (helpMode) { + final HelpFormatter formatter = new HelpFormatter(); + formatter.printHelp("Main", options, true); + System.exit(0); + } + + try { + CommandLine line = parser.parse(options, args); + if( line.hasOption( "build" ) ) { + generate(line); + } else { + getEnigma(line); + } + } catch (ParseException e) { + System.err.println(e.getMessage()); + System.exit(1); + } + + } + + private static void generate(CommandLine line) { + final String source = line.getOptionValue("build", ""); + final String language = line.getOptionValue("lang", ""); + final int min = Integer.parseInt(line.getOptionValue("min", "3")); + final int max = Integer.parseInt(line.getOptionValue("max", "7")); + + System.out.println(String.format("Generating %s resolver from %s.\nWords size: [%d, %d]", language, source, min, max )); + + File file = new File(source); + if (!file.exists()) + { + System.err.println("Source file not found " + source); + return; + } + + try { + Resolver resolver = DictionaryBuilder.generateResolver(new Resolver(), file, min, max); + resolver.computeStatistics(); + + ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("src/main/resources/resolvers/" + language))); + oos.writeObject(resolver); + oos.flush(); + oos.close(); + } catch( IOException e) { + System.err.println(e.getMessage()); + } + } + + private static void getEnigma(CommandLine line) { + final String language = line.getOptionValue("lang", ""); + final int min = Integer.parseInt(line.getOptionValue("min", "3")); + final int max = Integer.parseInt(line.getOptionValue("max", "7")); + + try { + manageLanguage(language); + Enigma enigma = DictionaryBuilder.generateEnigma(language, min, max); + System.out.println(enigma); + } catch(IllegalArgumentException e) { + System.err.println(e.getMessage()); + } + } + + private static Options configHelpParameter() { + final Option helpFileOption = Option.builder("h") + .longOpt("help") + .desc("Display help message").build(); + + final Options firstOptions = new Options(); + + firstOptions.addOption(helpFileOption); + + return firstOptions; + } + + private static Options configParameters() { + + final Option buildOption = Option.builder("b") + .longOpt("build") // + .desc("Build a new dictionary from the source file (a Linux words file)") + .hasArg(true) + .argName("source") + .required(false) + .build(); + + final Option languageOption = Option.builder("l") + .longOpt("lang") + .desc("Language (in " + availableLanguages + ")") + .hasArg(true) + .argName("language") + .required(true) + .build(); + + final Option helpFileOption = Option.builder("h") + .longOpt("help") + .desc("Display help message").build(); + + final Option minOption = Option.builder("min") + .longOpt("min") + .desc("Minimum word length (default : 3)") + .hasArg(true) + .argName("min") + .required(false) + .build(); + + final Option maxOption = Option.builder("max") + .longOpt("max") + .desc("Maximum word length (default : 7)") + .hasArg(true) + .argName("max") + .required(false) + .build(); + + final Options options = new Options(); + + options.addOption(buildOption); + options.addOption(helpFileOption); + options.addOption(languageOption); + options.addOption(minOption); + options.addOption(maxOption); + + return options; + } + + private static void manageLanguage(String language) { + if( StringUtils.isBlank(language) ) { + throw new IllegalArgumentException("No language provided"); + } + + if( !availableLanguages.contains(language.toLowerCase()) ) { + throw new IllegalArgumentException(String.format("Unknown %s language. Available languages : %s", language, availableLanguages.toString())); + } + } +} diff --git a/src/main/java/fr/pavnay/scrabble/Node.java b/src/main/java/fr/pavnay/scrabble/Node.java new file mode 100644 index 0000000..eab84a2 --- /dev/null +++ b/src/main/java/fr/pavnay/scrabble/Node.java @@ -0,0 +1,73 @@ +package fr.pavnay.scrabble; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class Node implements Serializable { + private static final long serialVersionUID = 3539215265338287172L; + private Node[] nodes = new Node[26]; + private List words; + private String parent; + + public Node(String parent) { + this.parent = parent; + } + + public void addWord(String word) { + if (this.words == null) { + this.words = new ArrayList(); + } + if (!this.words.contains(word)) { + this.words.add(word); + } + } + + public List getWords() { + if (this.words == null) { + return null; + } + Collections.sort(this.words); + return this.words; + } + + public Node getNode(char character) { + Node node = this.nodes[(character - 'a')]; + if (node == null) { + node = new Node(this.parent + character); + this.nodes[(character - 'a')] = node; + } + return node; + } + + public int getWordsCount() { + int total = 0; + for (int i = 0; i < 26; i++) { + Node current = this.nodes[i]; + if (current != null) { + total += current.getWordsCount(); + } + if (this.words != null) { + total += this.words.size(); + } + } + return total; + } + + public String toString() { + StringBuilder sb = new StringBuilder("Parent " + this.parent); + if (this.words != null) { + sb.append(this.words.toString()); + } else { + sb.append('-'); + } + for (int i = 0; i < 26; i++) { + Node current = this.nodes[i]; + if (current != null) { + sb.append(current); + } + } + return sb.toString(); + } +} diff --git a/src/main/java/fr/pavnay/scrabble/Resolver.java b/src/main/java/fr/pavnay/scrabble/Resolver.java new file mode 100644 index 0000000..3193dd9 --- /dev/null +++ b/src/main/java/fr/pavnay/scrabble/Resolver.java @@ -0,0 +1,185 @@ +package fr.pavnay.scrabble; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; + +public class Resolver implements Serializable { + private static final long serialVersionUID = 8267126995323709570L; + private Node[] nodes = new Node[26]; + private float[] statistics = new float[26]; + private float totalCharacter = 0.0F; + + public Node getNode(char character) { + Node node = this.nodes[(character - 'a')]; + this.statistics[(character - 'a')] += 1.0F; + this.totalCharacter += 1.0F; + if (node == null) { + node = new Node(Character.toString(character)); + this.nodes[(character - 'a')] = node; + } + return node; + } + + public void updateStatistics(char character) { + this.statistics[(character - 'a')] += 1.0F; + this.totalCharacter += 1.0F; + } + + public void computeStatistics() { + for (int i = 0; i < 26; i++) { + this.statistics[i] = (this.statistics[i] * 100.0F / this.totalCharacter); + System.out.println((char) (i + 97) + "=" + this.statistics[i] + "%"); + } + } + + public void displayStatistics() { + for (int i = 0; i < 26; i++) { + System.out.println((char) (i + 97) + "=" + this.statistics[i] + "%"); + } + } + + public float[] getStatistics() { + return this.statistics; + } + + public Enigma generateEnigma(int minimalLettersCount, int maximalLettersCount) { + boolean mustRun = false; + char[] letters; + Map> allWords; + do { + letters = getRandomSet(maximalLettersCount); + allWords = getWords(letters, minimalLettersCount); + + int wordsCount = 0; + for (int i = minimalLettersCount; i < maximalLettersCount + 1; i++) { + List words = allWords.get(Integer.valueOf(i)); + if (words != null) { + wordsCount += words.size(); + } + } + mustRun = wordsCount < (maximalLettersCount + minimalLettersCount) * 0.7D; + } while (mustRun); + return new Enigma(letters, allWords, minimalLettersCount, maximalLettersCount); + } + + private char[] getRandomSet(int maxLetter) { + float[] statistics = getStatistics(); + float max = 0.0F; + for (int i = 0; i < 26; i++) { + max += statistics[i]; + } + Random randomizer = new Random(); + char[] letters = new char[maxLetter]; + for (int i = 0; i < maxLetter; i++) { + letters[i] = getRandomLetter(randomizer, max, statistics); + } + return letters; + } + + private char getRandomLetter(Random randomizer, float max, float[] statistics) { + float r = randomizer.nextFloat() * (int) max; + max = 0.0F; + for (int i = 0; i < 26; i++) { + max += statistics[i]; + if (r < max) { + return (char) (i + 97); + } + } + return 'z'; + } + + private Map> getWords(char[] letters, int minimum) { + List words = new ArrayList(); + for (int i = letters.length; i > 2; i--) { + List newWords = getWords(letters, minimum, i); + if (newWords != null) { + for (String word : newWords) { + if (!words.contains(word)) { + words.add(word); + } + } + } + } + Map> lists = new HashMap>(); + for (String word : words) { + List list = lists.get(Integer.valueOf(word.length())); + if (list == null) { + list = new ArrayList(); + lists.put(Integer.valueOf(word.length()), list); + } + list.add(word); + } + for (int i = letters.length; i > 2; i--) { + List list = lists.get(Integer.valueOf(i)); + if (list != null) { + Collections.sort(list); + } + } + return lists; + } + + public List getWords(char[] letters, int minimum, int size) { + if (size < minimum) { + return null; + } + String set = new String(letters); + char[] sortLetters = StringUtils.sortLetters(set); + + int setSize = letters.length; + if (size == setSize) { + Node currentNode = this.nodes[(sortLetters[0] - 'a')]; + if (currentNode == null) { + return null; + } + for (int i = 1; i < setSize; i++) { + currentNode = currentNode.getNode(sortLetters[i]); + if (currentNode == null) { + return null; + } + if (i == setSize - 1) { + return currentNode.getWords(); + } + } + return null; + } + String nString = new String(sortLetters); + List results = null; + for (int i = 0; i < setSize - size; i++) { + for (int j = 0; j < setSize; j++) { + List words = getWords( + (nString.substring(0, j) + nString.subSequence(j + 1, setSize)).toCharArray(), minimum, + size - i); + if (words != null) { + if (results == null) { + results = new ArrayList(); + } + for (String word : words) { + if (!results.contains(word)) { + results.add(word); + } + } + } + } + } + if (results != null) { + Collections.sort(results); + } + return results; + } + + public int countWords() { + int total = 0; + for (int i = 0; i < 26; i++) { + Node current = this.nodes[i]; + if (current != null) { + total += current.getWordsCount(); + } + } + return total; + } +} diff --git a/src/main/java/fr/pavnay/scrabble/ScrabbleUtils.java b/src/main/java/fr/pavnay/scrabble/ScrabbleUtils.java new file mode 100644 index 0000000..d7a7787 --- /dev/null +++ b/src/main/java/fr/pavnay/scrabble/ScrabbleUtils.java @@ -0,0 +1,17 @@ +package fr.pavnay.scrabble; + +import java.io.File; +import java.net.URL; +import java.util.Arrays; +import java.util.List; + +public class ScrabbleUtils { + + public static List loadLanguages() { + URL url = Main.class.getClassLoader().getResource("resolvers"); + File resolver = new File(url.getPath()); + String[] files = resolver.list(); + return Arrays.asList(files); + } + +} diff --git a/src/main/java/fr/pavnay/scrabble/StringUtils.java b/src/main/java/fr/pavnay/scrabble/StringUtils.java new file mode 100644 index 0000000..22c7387 --- /dev/null +++ b/src/main/java/fr/pavnay/scrabble/StringUtils.java @@ -0,0 +1,37 @@ +package fr.pavnay.scrabble; + +import java.text.Normalizer; +import java.util.Arrays; + +public final class StringUtils { + + public static String toLowerCaseASCII(String s) { + if (isBlank(s)) { + return ""; + } + return stripAccents(s).trim().toLowerCase(); + } + + public static boolean isBlank(String s) { + return (s == null) || (s.trim().length() == 0); + } + + public static char[] sortLetters(String s) { + if (s == null) { + return null; + } + if (isBlank(s)) { + return s.toCharArray(); + } + char[] sortedLetters = s.toCharArray(); + Arrays.sort(sortedLetters); + + return sortedLetters; + } + + public static String stripAccents(String s) { + s = Normalizer.normalize(s, Normalizer.Form.NFD); + s = s.replaceAll("[\\p{InCombiningDiacriticalMarks}]", "").replaceAll("[^\\p{ASCII}]", ""); + return s; + } +} diff --git a/src/main/resources/resolvers/english b/src/main/resources/resolvers/english new file mode 100644 index 0000000..72d1b80 Binary files /dev/null and b/src/main/resources/resolvers/english differ diff --git a/src/main/resources/resolvers/french b/src/main/resources/resolvers/french new file mode 100644 index 0000000..a490ee0 Binary files /dev/null and b/src/main/resources/resolvers/french differ diff --git a/target/classes/META-INF/MANIFEST.MF b/target/classes/META-INF/MANIFEST.MF new file mode 100644 index 0000000..80718f8 --- /dev/null +++ b/target/classes/META-INF/MANIFEST.MF @@ -0,0 +1,5 @@ +Manifest-Version: 1.0 +Built-By: FECAILLE +Build-Jdk: 1.8.0_77 +Created-By: Maven Integration for Eclipse + diff --git a/target/classes/META-INF/maven/fr.pavnay/scrabble-resolver/pom.properties b/target/classes/META-INF/maven/fr.pavnay/scrabble-resolver/pom.properties new file mode 100644 index 0000000..b34c75d --- /dev/null +++ b/target/classes/META-INF/maven/fr.pavnay/scrabble-resolver/pom.properties @@ -0,0 +1,7 @@ +#Generated by Maven Integration for Eclipse +#Fri Sep 22 09:55:44 CEST 2017 +version=0.0.1-SNAPSHOT +groupId=fr.pavnay +m2e.projectName=scrabble-resolver +m2e.projectLocation=D\:\\Workspace_JADE\\scrabble-resolver +artifactId=scrabble-resolver diff --git a/target/classes/META-INF/maven/fr.pavnay/scrabble-resolver/pom.xml b/target/classes/META-INF/maven/fr.pavnay/scrabble-resolver/pom.xml new file mode 100644 index 0000000..04d1878 --- /dev/null +++ b/target/classes/META-INF/maven/fr.pavnay/scrabble-resolver/pom.xml @@ -0,0 +1,22 @@ + + 4.0.0 + fr.pavnay + scrabble-resolver + 0.0.1-SNAPSHOT + + + + commons-cli + commons-cli + 1.4 + + + + junit + junit + 4.12 + test + + + \ No newline at end of file diff --git a/target/classes/fr/pavnay/scrabble/DictionaryBuilder.class b/target/classes/fr/pavnay/scrabble/DictionaryBuilder.class new file mode 100644 index 0000000..97c9177 Binary files /dev/null and b/target/classes/fr/pavnay/scrabble/DictionaryBuilder.class differ diff --git a/target/classes/fr/pavnay/scrabble/Enigma.class b/target/classes/fr/pavnay/scrabble/Enigma.class new file mode 100644 index 0000000..5eaf381 Binary files /dev/null and b/target/classes/fr/pavnay/scrabble/Enigma.class differ diff --git a/target/classes/fr/pavnay/scrabble/Main.class b/target/classes/fr/pavnay/scrabble/Main.class new file mode 100644 index 0000000..0b76933 Binary files /dev/null and b/target/classes/fr/pavnay/scrabble/Main.class differ diff --git a/target/classes/fr/pavnay/scrabble/Node.class b/target/classes/fr/pavnay/scrabble/Node.class new file mode 100644 index 0000000..9e6cb3f Binary files /dev/null and b/target/classes/fr/pavnay/scrabble/Node.class differ diff --git a/target/classes/fr/pavnay/scrabble/Resolver.class b/target/classes/fr/pavnay/scrabble/Resolver.class new file mode 100644 index 0000000..05fd08d Binary files /dev/null and b/target/classes/fr/pavnay/scrabble/Resolver.class differ diff --git a/target/classes/fr/pavnay/scrabble/ScrabbleUtils.class b/target/classes/fr/pavnay/scrabble/ScrabbleUtils.class new file mode 100644 index 0000000..1c98848 Binary files /dev/null and b/target/classes/fr/pavnay/scrabble/ScrabbleUtils.class differ diff --git a/target/classes/fr/pavnay/scrabble/StringUtils.class b/target/classes/fr/pavnay/scrabble/StringUtils.class new file mode 100644 index 0000000..1d5655f Binary files /dev/null and b/target/classes/fr/pavnay/scrabble/StringUtils.class differ diff --git a/target/classes/resolvers/english b/target/classes/resolvers/english new file mode 100644 index 0000000..72d1b80 Binary files /dev/null and b/target/classes/resolvers/english differ diff --git a/target/classes/resolvers/french b/target/classes/resolvers/french new file mode 100644 index 0000000..a490ee0 Binary files /dev/null and b/target/classes/resolvers/french differ