|
| 1 | +"""Invert a given de/compression algorithm.""" |
| 2 | + |
| 3 | +from problems import Problem, register, get_problems |
| 4 | +from typing import List |
| 5 | + |
| 6 | + |
| 7 | +def _compress_LZW(text): # for development |
| 8 | + index = {chr(i): i for i in range(256)} |
| 9 | + |
| 10 | + seq = [] |
| 11 | + buffer = "" |
| 12 | + for c in text: |
| 13 | + if buffer + c in index: |
| 14 | + buffer += c |
| 15 | + continue |
| 16 | + seq.append(index[buffer]) |
| 17 | + index[buffer + c] = len(index) + 1 |
| 18 | + buffer = c |
| 19 | + |
| 20 | + if text != "": |
| 21 | + seq.append(index[buffer]) |
| 22 | + |
| 23 | + return seq |
| 24 | + |
| 25 | + |
| 26 | +def _decompress_LZW(seq: List[int]): # for development |
| 27 | + index = [chr(i) for i in range(256)] |
| 28 | + pieces = [""] |
| 29 | + for i in seq: |
| 30 | + pieces.append(pieces[-1] + pieces[-1][0] if i == len(index) else index[i]) |
| 31 | + index.append(pieces[-2] + pieces[-1][0]) |
| 32 | + return "".join(pieces) |
| 33 | + |
| 34 | + |
| 35 | +@register |
| 36 | +class LZW(Problem): |
| 37 | + """Find a (short) compression that decompresses to the given string. |
| 38 | + We have provided a simple version of the *decompression* algorithm of |
| 39 | + [Lempel-Ziv-Welch](https://en.wikipedia.org/wiki/Lempel%E2%80%93Ziv%E2%80%93Welch) |
| 40 | + so the solution is the *compression* algorithm. |
| 41 | + """ |
| 42 | + |
| 43 | + # _compress_LZW("Hellooooooooooooooooooooo world!") is length-17 |
| 44 | + |
| 45 | + @staticmethod |
| 46 | + def sat(seq: List[int], compressed_len=17, text="Hellooooooooooooooooooooo world!"): |
| 47 | + index = [chr(i) for i in range(256)] |
| 48 | + pieces = [""] |
| 49 | + for i in seq: |
| 50 | + pieces.append((pieces[-1] + pieces[-1][0]) if i == len(index) else index[i]) |
| 51 | + index.append(pieces[-2] + pieces[-1][0]) |
| 52 | + return "".join(pieces) == text and len(seq) <= compressed_len |
| 53 | + |
| 54 | + @staticmethod |
| 55 | + def sol(compressed_len, text): # compressed_len is ignored |
| 56 | + index = {chr(i): i for i in range(256)} |
| 57 | + seq = [] |
| 58 | + buffer = "" |
| 59 | + for c in text: |
| 60 | + if buffer + c in index: |
| 61 | + buffer += c |
| 62 | + continue |
| 63 | + seq.append(index[buffer]) |
| 64 | + index[buffer + c] = len(index) + 1 |
| 65 | + buffer = c |
| 66 | + |
| 67 | + if text != "": |
| 68 | + seq.append(index[buffer]) |
| 69 | + |
| 70 | + return seq |
| 71 | + |
| 72 | + def gen(self, _target_num_problems): |
| 73 | + self.add({"text": "", "compressed_len": 0}) |
| 74 | + self.add({"text": "c" * 1000, "compressed_len": len(_compress_LZW("c" * 1000))}) |
| 75 | + |
| 76 | + def gen_random(self): |
| 77 | + max_len = self.random.choice([10, 100, 1000]) |
| 78 | + text = self.random.pseudo_word(0, max_len) |
| 79 | + self.add({"text": text, "compressed_len": len(_compress_LZW(text))}) |
| 80 | + |
| 81 | + |
| 82 | +@register |
| 83 | +class LZW_decompress(Problem): |
| 84 | + """Find a string that compresses to the target sequence for the provided simple version of |
| 85 | + [Lempel-Ziv-Welch](https://en.wikipedia.org/wiki/Lempel%E2%80%93Ziv%E2%80%93Welch) |
| 86 | + so the solution is the *decompression* algorithm. |
| 87 | + """ |
| 88 | + |
| 89 | + @staticmethod |
| 90 | + def sat(text: str, seq=[72, 101, 108, 108, 111, 32, 119, 111, 114, 100, 262, 264, 266, 263, 265, 33]): |
| 91 | + index = {chr(i): i for i in range(256)} |
| 92 | + seq2 = [] |
| 93 | + buffer = "" |
| 94 | + for c in text: |
| 95 | + if buffer + c in index: |
| 96 | + buffer += c |
| 97 | + continue |
| 98 | + seq2.append(index[buffer]) |
| 99 | + index[buffer + c] = len(index) + 1 |
| 100 | + buffer = c |
| 101 | + |
| 102 | + if text != "": |
| 103 | + seq2.append(index[buffer]) |
| 104 | + |
| 105 | + return seq2 == seq |
| 106 | + |
| 107 | + @staticmethod |
| 108 | + def sol(seq): |
| 109 | + index = [chr(i) for i in range(256)] |
| 110 | + pieces = [""] |
| 111 | + for i in seq: |
| 112 | + pieces.append(pieces[-1] + pieces[-1][0] if i == len(index) else index[i]) |
| 113 | + index.append(pieces[-2] + pieces[-1][0]) |
| 114 | + return "".join(pieces) |
| 115 | + |
| 116 | + def gen(self, _target_num_problems): |
| 117 | + for s in ['', 'a', 'b' * 1000, 'ab' * 1000 + '!']: |
| 118 | + self.add({"seq": _compress_LZW(s)}) |
| 119 | + |
| 120 | + def gen_random(self): |
| 121 | + max_len = self.random.choice([10, 100, 1000]) |
| 122 | + text = self.random.pseudo_word(0, max_len) |
| 123 | + self.add({"seq": _compress_LZW(text)}) |
| 124 | + |
| 125 | + |
| 126 | +@register |
| 127 | +class PackingHam(Problem): |
| 128 | + """Pack a certain number of binary strings so that they have a minimum hamming distance between each other. |
| 129 | +
|
| 130 | + This is a [classic problem](https://en.wikipedia.org/wiki/Sphere_packing#Other_spaces) in coding theory.""" |
| 131 | + |
| 132 | + @staticmethod |
| 133 | + def sat(words: List[str], num=100, bits=100, dist=34): |
| 134 | + assert len(words) == num and all(len(word) == bits and set(word) <= {"0", "1"} for word in words) |
| 135 | + return all(sum([a != b for a, b in zip(words[i], words[j])]) >= dist for i in range(num) for j in range(i)) |
| 136 | + |
| 137 | + @staticmethod |
| 138 | + def sol(num, bits, dist): |
| 139 | + import random # key insight, use randomness! |
| 140 | + r = random.Random(0) |
| 141 | + while True: |
| 142 | + seqs = [r.getrandbits(bits) for _ in range(num)] |
| 143 | + if all(bin(seqs[i] ^ seqs[j]).count("1") >= dist for i in range(num) for j in range(i)): |
| 144 | + return [bin(s)[2:].rjust(bits, '0') for s in seqs] |
| 145 | + |
| 146 | + def gen_random(self): |
| 147 | + bits = self.random.randrange(1, self.random.choice([10, 100])) |
| 148 | + num = self.random.randrange(2, self.random.choice([10, 100])) |
| 149 | + |
| 150 | + def score(seqs): |
| 151 | + return min(bin(seqs[i] ^ seqs[j]).count("1") for i in range(num) for j in range(i)) |
| 152 | + |
| 153 | + # best of 5 |
| 154 | + seqs = min([[self.random.getrandbits(bits) for _ in range(num)] for _ in range(5)], key=score) |
| 155 | + dist = score(seqs) |
| 156 | + if dist > 0: |
| 157 | + self.add(dict(num=num, bits=bits, dist=dist)) |
| 158 | + |
| 159 | + |
| 160 | +if __name__ == "__main__": |
| 161 | + for problem in get_problems(globals()): |
| 162 | + problem.test() |
0 commit comments