着名的压缩算法(LZ78)执行缓慢

时间:2009-07-15 10:11:15

标签: java algorithm optimization compression

我正在编写一种方法,通过遵循LZ78算法来近似字符串的Kolmogorov复杂度,除了不添加到表中我只保留一个计数器,即我只对压缩的大小感兴趣。

问题在于,对于大量输入,需要数小时。这是我实施它的方式吗?

/**
 * 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;
}

更新: 使用

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

而不是

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

让它快得多

5 个答案:

答案 0 :(得分:4)

我想我可以做得更好(抱歉有点长):

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()来遍历整个列表。

但是如何确保代码不执行此操作?答案:不变性 - 如果你的类是不可变的,那么至少hashCode是常量,因此,可以预先计算。同样检查也可以缩短 - 但在最坏的情况下,它仍然会遍历整个集合。

这很好,但是你如何'修改'一个不可变的类。不,你没有,每次需要不同的内容时你都会创建一个新的。但是,当您仔细查看字典的内容时,您会发现它包含多余的信息:[]a[abc]d[abc]e[abcd]f。那么为什么不将头部存储为指向先前看到的模式的指针并为当前角色设置尾部?

完全。使用不变性和反向引用可以节省空间和时间,甚至垃圾收集器也会爱你。我的解决方案的一个特点是在F#和Haskell中,列表使用头部:[tail]签名 - 头部是元素类型,尾部是指向下一个集合的指针。在这个问题中,需要相反的,因为列表在尾部增长。

从这里进一步优化 - 例如,使用具体的char作为尾部类型,以避免泛型版本的常量字符自动装箱。

我的解决方案的一个缺点是它在计算列表的各个方面时利用递归。对于相对较小的列表,它很好,但是更长的列表可能需要您增加计算运行的线程堆栈大小。从理论上讲,使用Java 7的尾部调用优化,我的代码可以通过允许JIT执行优化的方式进行重写(或者它已经是如此?很难说)。

答案 1 :(得分:2)

在我看来,ArrayList不是保持字典只包含和添加的最佳数据结构。

修改

尝试使用HashSet,它将其元素存储在哈希表中,是Set interface的效果最佳的实现;但它不能保证迭代的顺序

答案 2 :(得分:1)

ArrayList将具有O(N)搜索复杂度。使用数据结构,如哈希表或字典。

答案 3 :(得分:1)

因为你总是检查前缀+ c我认为一个好的数据结构可能是一个树,其中每个孩子的父母都有父作为前缀:

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

另一种可能更简单的方法是使用排序列表,然后使用二进制搜索来查找您正在查看的字符串。我仍然认为树的方法会更快。

答案 4 :(得分:0)

您可以尝试的另一个微优化是使用这些fastutil实现http://fastutil.dsi.unimi.it/交换集合对象 - 它基本上是免费的加速。