Медленная реализация известного алгоритма сжатия (LZ78) - PullRequest
2 голосов
/ 15 июля 2009

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

Проблема в том, что для больших входов это занимает часы. Это так, как я это реализовал?

/**
 * Uses the LZ78 compression algorithm to approximate the Kolmogorov
 * complexity of a String
 * 
 * @param s
 * @return the approximate Kolmogorov complexity
 */
public double kComplexity(String s) {

    ArrayList<String> dictionary = new ArrayList<String>();
    StringBuilder w = new StringBuilder();
    double comp = 0;
    for (int i = 0; i < s.length(); i++) {
        char c = s.charAt(i);
        if (dictionary.contains(w.toString() + c)) {
            w.append(c);
        } else {
            comp++;
            dictionary.add(w.toString() + c);
            w = new StringBuilder();
        }
    }
    if (w.length() != 0) {
        comp++;
    }

    return comp;
}

UPDATE: Использование

HashSet<String> dictionary = new HashSet<String>();

вместо

ArrayList<String> dictionary = new ArrayList<String>();

делает это намного быстрее

Ответы [ 5 ]

4 голосов
/ 15 июля 2009

Я думаю, что могу сделать лучше (извините, немного долго):

import java.io.File;
import java.io.FileInputStream;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class LZ78 {
    /**
     * Uses the LZ78 compression algorithm to approximate the Kolmogorov
     * complexity of a String
     * 
     * @param s
     * @return the approximate Kolmogorov complexity
     */
    public static double kComplexity(String s) {
        Set<String> dictionary = new HashSet<String>();
        StringBuilder w = new StringBuilder();
        double comp = 0;
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if (dictionary.contains(w.toString() + c)) {
                w.append(c);
            } else {
                comp++;
                dictionary.add(w.toString() + c);
                w = new StringBuilder();
            }
        }
        if (w.length() != 0) {
            comp++;
        }
        return comp;
    }

    private static boolean equal(Object o1, Object o2) {
        return o1 == o2 || (o1 != null && o1.equals(o2));
    }

    public static final class FList<T> {
        public final FList<T> head;
        public final T tail;
        private final int hashCodeValue;

        public FList(FList<T> head, T tail) {
            this.head = head;
            this.tail = tail;
            int v = head != null ? head.hashCodeValue : 0;
            hashCodeValue = v * 31 + (tail != null ? tail.hashCode() : 0);
        }

        @Override
        public boolean equals(Object obj) {
            if (obj instanceof FList<?>) {
                FList<?> that = (FList<?>) obj;
                return equal(this.head, that.head)
                        && equal(this.tail, that.tail);
            }
            return false;
        }

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

        @Override
        public String toString() {
            return head + ", " + tail;
        }
    }

    public static final class FListChar {
        public final FListChar head;
        public final char tail;
        private final int hashCodeValue;

        public FListChar(FListChar head, char tail) {
            this.head = head;
            this.tail = tail;
            int v = head != null ? head.hashCodeValue : 0;
            hashCodeValue = v * 31 + tail;
        }

        @Override
        public boolean equals(Object obj) {
            if (obj instanceof FListChar) {
                FListChar that = (FListChar) obj;
                return equal(this.head, that.head) && this.tail == that.tail;
            }
            return false;
        }

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

        @Override
        public String toString() {
            return head + ", " + tail;
        }
    }

    public static double kComplexity2(String s) {
        Map<FList<Character>, FList<Character>> dictionary = 
            new HashMap<FList<Character>, FList<Character>>();
        FList<Character> w = null;
        double comp = 0;
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            FList<Character> w1 = new FList<Character>(w, c);
            FList<Character> ex = dictionary.get(w1);
            if (ex != null) {
                w = ex;
            } else {
                comp++;
                dictionary.put(w1, w1);
                w = null;
            }
        }
        if (w != null) {
            comp++;
        }
        return comp;
    }

    public static double kComplexity3(String s) {
        Map<FListChar, FListChar> dictionary = 
            new HashMap<FListChar, FListChar>(1024);
        FListChar w = null;
        double comp = 0;
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            FListChar w1 = new FListChar(w, c);
            FListChar ex = dictionary.get(w1);
            if (ex != null) {
                w = ex;
            } else {
                comp++;
                dictionary.put(w1, w1);
                w = null;
            }
        }
        if (w != null) {
            comp++;
        }
        return comp;
    }

    public static void main(String[] args) throws Exception {
        File f = new File("methods.txt");
        byte[] data = new byte[(int) f.length()];
        FileInputStream fin = new FileInputStream(f);
        int len = fin.read(data);
        fin.close();
        final String test = new String(data, 0, len);

        final int n = 100;
        ExecutorService exec = Executors.newFixedThreadPool(1);
        exec.submit(new Runnable() {
            @Override
            public void run() {
                long t = System.nanoTime();
                double value = 0;
                for (int i = 0; i < n; i++) {
                    value += kComplexity(test);
                }
                System.out.printf("kComplexity: %.3f; Time: %d ms%n",
                        value / n, (System.nanoTime() - t) / 1000000);
            }
        });
        exec.submit(new Runnable() {
            @Override
            public void run() {
                long t = System.nanoTime();
                double value = 0;
                for (int i = 0; i < n; i++) {
                    value += kComplexity2(test);
                }
                System.out.printf("kComplexity2: %.3f; Time: %d ms%n", value
                        / n, (System.nanoTime() - t) / 1000000);
            }
        });
        exec.submit(new Runnable() {
            @Override
            public void run() {
                long t = System.nanoTime();
                double value = 0;
                for (int i = 0; i < n; i++) {
                    value += kComplexity3(test);
                }
                System.out.printf("kComplexity3: %.3f; Time: %d ms%n", value
                        / n, (System.nanoTime() - t) / 1000000);
            }
        });
        exec.shutdown();
    }
}

Результаты:

kComplexity: 41546,000; Time: 17028 ms
kComplexity2: 41546,000; Time: 6555 ms
kComplexity3: 41546,000; Time: 5971 ms

Редактировать Давление со стороны сверстников: Как это работает?

Фрэнки, понятия не имею, просто это был хороший способ ускорить процесс. Я тоже должен это выяснить, так что поехали.

Было замечено, что в исходном коде было много добавлений строк, однако замена его на LinkedList<String> не помогла бы, так как существует постоянное давление при поиске коллекций в хеш-таблице - каждый раз, когда hashCode () и equals () используются для обхода всего списка.

Но как я могу убедиться, что код не выполняет этого ненужного? Ответ: неизменяемость - если ваш класс неизменен, то, по крайней мере, хэш-код является константой, поэтому его можно предварительно вычислить. Проверка на равенство также может быть ускорена, но в худшем случае она все равно будет проходить через всю коллекцию.

Это хорошо, но как вы «модифицируете» неизменный класс? Нет, вы создаете новый каждый раз, когда требуется другой контент. Однако, если вы внимательно посмотрите на содержимое словаря, вы обнаружите, что оно содержит избыточную информацию: []a, [abc]d, [abc]e, [abcd]f. Так почему бы просто не сохранить голову в качестве указателя на ранее увиденный шаблон и иметь хвост для текущего символа?

Точно. Используя неизменяемость и обратные ссылки, вы можете сэкономить пространство и время, и даже сборщик мусора будет любить вас тоже. Одна из особенностей моего решения заключается в том, что в F # и Haskell список использует подпись head: [tail] - head - это тип элемента, а tail - указатель на следующую коллекцию. В этом вопросе требовалось обратное, поскольку списки растут в хвостовой части.

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

Один недостаток моего решения заключается в том, что оно использует рекурсию при расчете аспектов списка. Для относительно небольшого списка это хорошо, но более длинный список может потребовать от вас увеличения размера стека потоков, в котором выполняются ваши вычисления. Теоретически, с помощью оптимизации хвостовых вызовов Java 7 мой код может быть переписан таким образом, чтобы он позволял JIT выполнять оптимизацию (или это уже так? Трудно сказать).

2 голосов
/ 15 июля 2009

На мой взгляд, ArrayList не лучшая структура данных для хранения словаря, содержащего только добавления и добавления.

EDIT

Попробуйте использовать HashSet , который хранит свои элементы в хеш-таблице, является наиболее эффективной реализацией Set-интерфейса ; однако это не дает никаких гарантий относительно порядка итерации

1 голос
/ 15 июля 2009

Поскольку вы всегда проверяете префикс + c, я думаю, что хорошей структурой данных может быть дерево, в котором у каждого дочернего элемента есть префикс:

           root
        /       |
       a        b
      /  |     /  | 
     an  ap   ba bo
         |         
         ape

Другой, возможно, более простой подход - использовать отсортированный список, а затем использовать бинарный поиск, чтобы найти искомую строку. Я все еще думаю, что древовидный подход будет быстрее, хотя.

1 голос
/ 15 июля 2009

ArrayList будет иметь O (N) сложность поиска. Используйте структуру данных, такую ​​как хеш-таблица или словарь.

0 голосов
/ 15 июля 2009

Другая микрооптимизация, которую вы можете попробовать, - это обмен объектами коллекций с этими реализациями fastutil http://fastutil.dsi.unimi.it/ - это в основном бесплатное ускорение.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...