Решение полных анаграмм со словарем

Я решаю классическую задачу. Чтобы быть точным, я пытаюсь решить полную анаграмму.

Анаграммой является любое слово или фраза, точно воспроизводящие буквы в другом порядке.

У меня есть анаграмма, словарь и хэш. Мне нужно придумать фразу, которая изначально была хеширована, поэтому программа должна сгенерировать все перестановки, используя заданный словарь, и проверить, является ли какая-либо из них ответом.

В общем, кто-то спрятал для меня сообщение, и мне нужно его взломать!

образец ввода:

scofriybaarae dict.txt FD8D80332CCA32905F11860FB866CA92

Все приведенные ниже фразы являются допустимой анаграммой слова scofriybaarae, поэтому слова, из которых они состоят, могут быть другими или располагаться в другом порядке.

фриско-а-бай

район фриско залив

район залива Фриско

Однако только последний является ответом. Это связано с тем, что MD5 для области залива Фриско совпадает с MD5, заданным в качестве параметра.

Мы можем разделить задачу на обработку словаря, генерацию комбинаций и проверку md5.

Я использую дерево букв, некоторые узлы этого дерева могут обозначать и слово. Конец ветки всегда конец слова. Это означает, что одна ветвь может представлять множество слов, например. жирные буквы указывают на полноту

айрпорт

В приведенном выше примере сохраняются два слова, поэтому при просмотре можно легко вычеркнуть использованные буквы.

Моя программа может довольно быстро построить дерево из словаря, хотя я не доволен производительностью решателя.

Проблема, которую я обозначил, это просто огромное количество комбинаций, которые я не знаю, как облегчить. Например, даны 13 букв и ряд словарных слов длиной от 1 до 13. В этом случае имеется 6227020800 комбинаций однобуквенных слов, и вы можете себе представить, сколько еще комбинаций может быть.

Я заметил, что чем больше коротких слов я добавляю, тем медленнее он становится.

Мне интересно, на правильном ли я пути или это просто концептуально неправильно?

Должен ли я использовать механизм базы данных?

Для вашего удобства есть кусок моего словаря:

бухта ара аэра фбаер фриско фрискоб фрискоба африскоар фрискобай байфриско абсефорси

package margana;

import java.io.*;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Solution {

    private final String givenLetterSet;
    private String file;
    private final ExecutorService executorService = Executors.newFixedThreadPool(16);

    LetterNode root = new LetterNode('\u03A9', null); // omega root node
    private Map<Character, Long> countedOriginalLetters = new HashMap<Character, Long>();

    /**
     * Mixed Anatree class
     */
    public static class LetterNode implements Comparable<LetterNode> {
        private final char letter;// does not matter for the root node
        private boolean ending;
        private Map<Character, LetterNode> leaves = new HashMap<Character, LetterNode>();
        private LetterNode motherNode;
        private String wholeCachedWord;
        private int length = 1;

        public LetterNode(char oneLetter, LetterNode mom) {
            letter = oneLetter;
            if (mom != null) {
                if (mom.motherNode != null) {
                    length += mom.length;// all consecutive nodes minus mom length
                }
                motherNode = mom;
            }
        }

        public char getLetter() {
            return letter;
        }

        public Character getCharacter() {
            return Character.valueOf(letter);
        }

        public boolean isEnding() {
            return ending;
        }

        public void setEnding(boolean ending) {
            this.ending = ending;
        }

        public Map<Character, LetterNode> getLeaves() {
            return leaves;
        }

        public int getLength() {
            return length;
        }

        public LetterNode getMotherNode() {
            return motherNode;
        }

        public String compileNodesIntoWord() {
            if (wholeCachedWord != null) {
                return wholeCachedWord;
            }
            LetterNode node = motherNode;
            StringBuilder buffer = new StringBuilder(length);
            buffer.append(letter);
            while (node.motherNode != null) {
                buffer.insert(0, node.letter);
                if (node.motherNode.motherNode == null) {
                    break;
                }
                node = node.motherNode;
            }
            wholeCachedWord = buffer.toString();
            return wholeCachedWord;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }
            LetterNode that = (LetterNode) o;
            if (letter != that.letter) {
                return false;
            }
            return true;
        }

        @Override
        public int hashCode() {
            return (int) letter;
        }

        @Override
        public int compareTo(LetterNode o) {
            return Character.compare(letter, o.letter);
        }

        @Override
        public String toString() {
            if (ending) {
                return compileNodesIntoWord();
            }
            return String.valueOf(letter);
        }
    }

    public Solution(String anagram, String dictionaryFile) {
        file = dictionaryFile;
        byte[] tempArray = anagram.toLowerCase().replaceAll(" ", "").getBytes();
        Arrays.sort(tempArray);
        givenLetterSet = new String(tempArray);
        for (char oneChar : anagram.toLowerCase().toCharArray()) {
            Long numberOfOccurrences = countedOriginalLetters.get(Character.valueOf(oneChar));
            if (numberOfOccurrences == null) {
                countedOriginalLetters.put(new Character(oneChar), new Long(1));
            } else {
                countedOriginalLetters.put(new Character(oneChar), new Long(numberOfOccurrences.longValue() + 1));
            }
        }
    }

    /**
     * Rule out rubbish words
     *
     * @param oneWord
     * @return
     */
    private boolean invalidAgainstGivenSentence(String oneWord) {
        if (oneWord.length() > givenLetterSet.length()) {
            return true;
        }
        for (char oneChar : oneWord.toLowerCase().toCharArray()) {
/*            if (oneChar == "'".charAt(0)) {// to regards ' as a letter
                continue;
            }*/
            Long amountOfParticularLetter = countedOriginalLetters.get(Character.valueOf(oneChar));
            if (amountOfParticularLetter == null) {
                return true;
            }
        }
        return false;
    }

    public void growTree() throws IOException {
        BufferedReader br = new BufferedReader(new FileReader(file));
        String oneWord;
        long depth = 0; // for fun
        long candidate = 0;
        boolean isNewWord = false;
        while ((oneWord = br.readLine()) != null) {
            if (invalidAgainstGivenSentence(oneWord)) {
                continue;//is not a valid chunk of the given anagram
            }
            LetterNode previousNode = root;
            isNewWord = false;
            for (char one : oneWord.toCharArray()) {
                LetterNode currentLetter = previousNode.getLeaves().get(Character.valueOf(one));
                if (currentLetter == null) {// letter does not exists, let us add it
                    LetterNode newNode = new LetterNode(one, previousNode);
                    previousNode.getLeaves().put(Character.valueOf(one), newNode);
                    currentLetter = newNode;
                    isNewWord = true;
                }
                previousNode = currentLetter;
            }
            if (isNewWord) {
                candidate += 1;
            }
            previousNode.setEnding(true);
            depth = Math.max(depth, previousNode.getLength());
        }
        System.out.println("Created an anatree comprising of " + candidate + " words, and " + depth + " levels");
        br.close();
    }

    public void solve(String md5) throws NoSuchAlgorithmException {
        List<LetterNode> foundWords = new ArrayList<LetterNode>();
        LinkedList<Character> input = new LinkedList<Character>();
        Set<Character> inputSet = new HashSet<Character>();
        for (Character one : givenLetterSet.toCharArray()) {
            input.add(one);
            inputSet.add(one);
        }
        NavigableSet<LetterNode> firstLevel = new TreeSet(root.getLeaves().values()).descendingSet();
        for (LetterNode node: firstLevel) {
            if (inputSet.contains(node.getCharacter())) {
                executorService.execute(new SolverRunnable(foundWords, input, node, md5.toLowerCase()));
            }
        }
        executorService.shutdown();
    }

    class SolverRunnable implements Runnable {
        private List<LetterNode> initialWords;
        private List<Character> spareCharacters;
        private LetterNode initialNode;
        private String md5Hash;

        public SolverRunnable(List<LetterNode> foundWords, List<Character> spareLetters, LetterNode route, String md5) {
            initialNode = route;
            initialWords = foundWords;
            spareCharacters = spareLetters;
            md5Hash = md5;
        }

        public void run() {
            System.out.println("Started solving branch '" + initialNode.getCharacter() + "' from root ");
            try {
                solve(initialWords, spareCharacters, initialNode, md5Hash);
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            }
        }
    }

    private void solve(List<LetterNode> foundWords, List<Character> spareLetters, LetterNode route, String md5) throws NoSuchAlgorithmException {
        List<LetterNode> localFoundWords = new ArrayList<LetterNode>(foundWords);
        List<Character> workspace = new LinkedList<Character>();
        LetterNode current = route;
        workspace.addAll(spareLetters);
        while (!current.getLeaves().isEmpty()) {
            if (!workspace.contains(current.getCharacter())) {
                break;
            }
            workspace.remove(current.getCharacter());
            if (current.getLeaves().size() > 1) {// start solving recursively then quit
                for (LetterNode node: new TreeSet<LetterNode>(current.getLeaves().values())) {//checking every branch
                    if (workspace.contains(node.getCharacter())) {
                        solve(localFoundWords, workspace, node, md5);
                    }
                }
                break;//we solve routes without forks
            }
            if (workspace.isEmpty()) {
                break;
            }
            if (current.isEnding()) {//recursively solving a shorter word first then continue
                localFoundWords.add(current);
                startOver(workspace, localFoundWords, md5);
                localFoundWords.remove(current);
            }
            current = (LetterNode) current.getLeaves().values().toArray()[0];
        }
        if (current.isEnding()) {
            localFoundWords.add(current);
            workspace.remove(current.getCharacter());
            if (workspace.isEmpty()) {
                check(localFoundWords, md5);
                return;
            }
            startOver(workspace, localFoundWords, md5);
        }
    }

    private void check(List<LetterNode> localFoundWords, String md5) throws NoSuchAlgorithmException {
        if (isPreliminaryValid(localFoundWords)) {
            String phrase = concatenateNodesWithSpaces(localFoundWords);
            if (md5.equalsIgnoreCase(digest(phrase))) {
                System.out.println(phrase);
                executorService.shutdownNow();
                System.exit(0);
            }
        }
    }

    private void startOver(List<Character> workspace, List<LetterNode> localFoundWords, String md5) throws NoSuchAlgorithmException {
        for (LetterNode node: root.getLeaves().values()) {
            if (workspace.contains(node.getCharacter())) {
                solve(localFoundWords, workspace, node, md5);
            }
        }
    }

    public boolean isPreliminaryValid(List<LetterNode> words) {
        StringBuilder builder = new StringBuilder();
        int total = 0;
        for (LetterNode word : words) {
            builder.append(word.compileNodesIntoWord());
            total += word.length;
        }
        if (total != givenLetterSet.length()) {
            return false;
        }
        char[] letters = builder.toString().toCharArray();
        Arrays.sort(letters);
        return new String(letters).equals(givenLetterSet);
    }

    public static String concatenateNodesWithSpaces(List<LetterNode> words) {
        StringBuilder builder = new StringBuilder();
        int spaces = words.size() - 1;
        for (LetterNode word : words) {
            builder.append(word.compileNodesIntoWord());
            if (spaces > 0) {
                spaces--;
                builder.append(" ");
            }
        }
        return builder.toString();
    }

    public static String digest(String original) throws NoSuchAlgorithmException {
        MessageDigest md = MessageDigest.getInstance("MD5");
        md.update(original.getBytes());
        StringBuilder sb = new StringBuilder(34);
        for (byte b : md.digest()) {
            sb.append(String.format("%02x", b & 0xff));
        }
        return sb.toString();
    }

    public static void main(String[] args) throws IOException, NoSuchAlgorithmException {
        Solution s = new Solution(args[0], args[1]);
        s.growTree();
/*
        s.solve("BE2B1B1409746B5416F44FB6D9C16A55");// cop pop
        //s.solve("493DF2D8AC7EDB14CD50CA07A539A805");// cop p'op
*/
        s.solve(args[2]); //frisco bay area
    }

}

person Anonymous programmer    schedule 10.03.2015    source источник
comment
Я не совсем понимаю, что это за проблема. Можете ли вы представить некоторые примеры входных и выходных данных? Я не понимаю, какое отношение к этому имеет хэш. Вы просто пытаетесь решить анаграмму?   -  person Vivin Paliath    schedule 11.03.2015
comment
Возможно, будет быстрее полностью игнорировать вашу анаграмму (за исключением длины) и просто создавать комбинации слов в вашем словаре, пока вы не получите соответствующий хэш.   -  person GregHNZ    schedule 11.03.2015
comment
Пожалуйста, проверьте мое обновление вопроса.   -  person Anonymous programmer    schedule 11.03.2015
comment
В задаче может ли это быть любая анаграмма или полученная фраза состоит из proper слов, разделенных пробелом? Ваш словарь, кажется, состоит из множества ненужных слов, таких как «aabceforsy» и afriscoar. Уберите их, и вы уменьшите проблемное пространство. Вы также можете взвесить свой словарь так, чтобы общие слова стояли на первом месте.   -  person M.P. Korstanje    schedule 11.03.2015
comment
Мы должны считать правильным все, что имеет равное или меньшее количество одинаковых букв. Дело в том, что мы не знаем, что было зашифровано изначально, поэтому все слова допустимы.   -  person Anonymous programmer    schedule 12.03.2015
comment
Теоретически, скрытая фраза может быть I love you o u, означающая, что используются только однобуквенные слова.   -  person Anonymous programmer    schedule 13.03.2015


Ответы (1)


Возможное решение (узлы):

var
  md5 = require('MD5'),
  fs = require('fs');

function createIndex(str) {
  var i, index = {}, chr;
  for (i = 0; i < str.length; i++) {
    chr = str[i];
    index[chr] = (index[chr] || 0) + 1;
  }
  return index;
}

function indexContains(index, subIndex) {
  var chr;
  for (chr in subIndex) {
    if (subIndex.hasOwnProperty(chr) && (!index.hasOwnProperty(chr) || subIndex[chr] > index[chr])) {
      return false;
    }
  }
  return true;
}

function excludeIndex(index, subIndex) {
  var newIndex = {}, chr, value, empty = true;
  for (chr in index) {
    if (index.hasOwnProperty(chr)) {
      value = index[chr];
      if (subIndex.hasOwnProperty(chr)) {
        value -= subIndex[chr];
      }
      if (value) {
        newIndex[chr] = value;
        empty = false;
      }
    }
  }
  return empty ? null : newIndex;
}

function uniqueByProperty(items, property) {
  return items.filter(function (item, index) {
    var i, value = item[property];
    for (i = 0; i < index; i++) {
      if (items[i][property] === value) {
        return false;
      }
    }
    return true;
  });
}

function findAnagram(charsIndex, dict, prevWords, targetHash) {
  var i, item, nextCharsIndex, result, words;  
  dict = dict.filter(function (item) {
    return indexContains(charsIndex, item.index);
  });  
  if (!prevWords.length) {    
    dict = uniqueByProperty(dict, 'word');
  }  
  for (i = 0; i < dict.length; i++) {
    item = dict[i];
    nextCharsIndex = excludeIndex(charsIndex, item.index);
    words = prevWords.concat(item.word);
    if (nextCharsIndex) {
      result = findAnagram(nextCharsIndex, dict, words, targetHash);
      if (result) {
        return result;
      }
    } else {
      result = words.join(' ');
      if (md5(result) === targetHash) {
        return result;
      }
    }
  }
  return null;
}

var      
  dict = fs.readFileSync('./data/wordlist.txt', 'utf8').split('\n')
    .filter(function (str) {
      return str.replace(/ /, '');
    })
    .map(function (word) {
      return {word: word, index: createIndex(word)};
    }),
  initialStr = "poultry outwits ants",
  finalMD5 = "4624d200580677270a54ccff86b9610e",    
  result = findAnagram(createIndex(initialStr.replace(/ /, '')), dict, [], finalMD5); 

console.log(result);
person blazkovicz    schedule 13.03.2015
comment
Хорошо, спасибо за публикацию. Вы уже знаете об апострофе. - person Anonymous programmer; 13.03.2015
comment
На самом деле, можно работать с независимыми от позиции хэшами, такими как хеши, основанные на простых числах. Каждое слово может быть представлено как сумма его букв. Прелесть такого подхода в том, что продукт всегда не является простым числом. Что касается структуры данных, можно построить дерево или граф сумм, а затем выполнить поиск маршрута, создающего исходную хеш-сумму. - person Anonymous programmer; 13.03.2015