Created
January 1, 2016 05:14
-
-
Save fbparis/5aa470b74ef37edd37bc to your computer and use it in GitHub Desktop.
An augmented version of Library Of Babel using sections to store documents of any size...
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python | |
# -*- coding: utf-8 -*- | |
from random import choice | |
from unidecode import unidecode | |
from math import log10 | |
from tqdm import tqdm | |
class BaseLibraryOfBabel (object): | |
"""A complete, ordered, searchable, browsable and customizable Library of Babel""" | |
def __init__(self, alphabet=" abcdefghijklmnopqrstuvwxyz,.", section=1, shelfs_per_wall=5, volumes_per_shelf=32, name="Babel"): | |
"""Class constructor""" | |
self.name = name | |
if not isinstance(alphabet, unicode): | |
alphabet = alphabet.decode("utf-8") | |
self.alphabet = "".join(sorted(set(unidecode(alphabet)))) | |
self.base = len(self.alphabet) | |
self.ord = range(self.base) | |
self.walls = 4 | |
self.shelfs_per_wall = shelfs_per_wall | |
self.volumes_per_shelf = volumes_per_shelf | |
self.wall_range = range(1, self.walls + 1) | |
self.shelf_range = range(1, self.shelfs_per_wall + 1) | |
self.volume_range = range(1, self.volumes_per_shelf + 1) | |
self.max_digits = 100 | |
self.max_section = 3200000 | |
self.goto_section(section) | |
def _from_string(self, book): | |
"""Return a sequence of codes from a string""" | |
return [self.alphabet.index(c) for c in book] | |
def _to_string(self, seq): | |
"""Return a string from a sequence of codes""" | |
return "".join([self.alphabet[c] for c in seq]) | |
def _filter_string(self, s): | |
"""Return a valid string according to the current alphabet""" | |
if not isinstance(s, unicode): | |
s = s.decode("utf-8") | |
return "".join([c for c in unidecode(s) if c in self.alphabet]) | |
def goto_section(self, section): | |
"""Set the current section""" | |
section = max(1, min(section, self.max_section)) | |
self.book_length = section | |
self.books = self.base ** self.book_length | |
self.hexagones = self.books / (self.walls * self.shelfs_per_wall * self.volumes_per_shelf) | |
def section(self): | |
"""Return current section""" | |
return self.book_length | |
def library_description(self): | |
"""Return a welcome message describing the library""" | |
return "Welcome in the Library of %s.\nIn this library you will find every existing documents using a %d alphabet symbols.\nThe library consists in an infinite number of sections, each section storing every possible documents of a fixed length.\nSections are organized in hexagonal chambers, each containing %d documents on %d shelfs on %d walls.\nNote that only sections 1 to %d are currently opened to the public (you still have %s documents to consult)." % (self.name, self.base, self.volumes_per_shelf, self.shelfs_per_wall, self.walls, self.max_section, self.sci_notation((self.base**(self.max_section + 1) - 1) / (self.base - 1) - 1)) | |
def section_description(self): | |
"""Return a section specific welcome message""" | |
return "You are currently in section %d.\nThis section stores %s documents of %d characters in %s hexagonal chambers." % (self.book_length, self.sci_notation(self.books), self.book_length, self.sci_notation(self.hexagones)) | |
def get_book_from_number(self, n): | |
"""Return the nth document in the library""" | |
if n >= self.books: | |
return None | |
r = [] | |
for i in xrange(self.book_length): | |
r.append(n % self.base) | |
n /= self.base | |
return self._to_string(r[::-1]) | |
def get_book_number(self, book): | |
"""Return the number of a document in the library""" | |
book = self._from_string(book) | |
n = 0 | |
for c in book: | |
n = self.base * n + c | |
return n | |
def book_location(self, n): | |
"""Return the position of the nth document in the library in a printable string""" | |
loc = self.get_location_from_number(n) | |
if loc is None: | |
return "not in the library" | |
section, hexagone, wall, shelf, volume = loc | |
return "section %s, hexagone %s, wall %d, shelf %d, volume %d" % (self.sci_notation(self.book_length), self.sci_notation(hexagone), wall, shelf, volume) | |
def get_location_from_number(self, n): | |
"""Return the position of the nth document in the library""" | |
if n >= self.books: | |
return None | |
hexagone = 1 + n / (self.walls * self.shelfs_per_wall * self.volumes_per_shelf) | |
n = n % (self.walls * self.shelfs_per_wall * self.volumes_per_shelf) | |
wall = 1 + n / (self.shelfs_per_wall * self.volumes_per_shelf) | |
n = n % (self.shelfs_per_wall * self.volumes_per_shelf) | |
shelf = 1 + n / self.volumes_per_shelf | |
volume = 1 + n % self.volumes_per_shelf | |
return (self.book_length, hexagone, wall, shelf, volume) | |
def get_number_from_location(self, section, hexagone, wall, shelf, volume): | |
"""Return the number of a document from its location in the library""" | |
self.goto_section(section) | |
if (hexagone - 1 > self.hexagones) or (wall not in self.wall_range) or (shelf not in self.shelf_range) or (volume not in self.volume_range): | |
return None | |
return (volume - 1) + (shelf - 1) * self.volumes_per_shelf + (wall - 1) * (self.shelfs_per_wall * self.volumes_per_shelf) + (hexagone - 1) * (self.walls * self.shelfs_per_wall * self.volumes_per_shelf) | |
def get_book_from_location(self, section, hexagone, wall, shelf, volume): | |
"""Return a document from its location in the library""" | |
n = self.get_number_from_location(section, hexagone, wall, shelf, volume) | |
if n is None: | |
return None | |
return self.get_book_from_number(n) | |
def sci_notation(self, n): | |
"""Return n or its approximation in scientific notation if n is a very large number (may be used for document's or hexagone's number)""" | |
l = log10(n) | |
if l + 1 <= self.max_digits: | |
return str(n) | |
il = int(l) | |
i = 10**(l-il) | |
return "%se%d (original number has %d digits and is too big to be shown)" % (i, il, il + 1) | |
def random_book(self): | |
"""Return a random document""" | |
return "".join([choice(self.alphabet) for i in xrange(self.book_length)]) | |
def book_from_string(self, s): | |
"""Return a document from given string, switching section if needed""" | |
s = self._filter_string(s) | |
self.goto_section(len(s)) | |
return s[:self.book_length] | |
def search_result_count(self, search_string): | |
"""Return the number of search results for search_string in current section""" | |
search_string = self._filter_string(search_string) | |
search_string_length = len(search_string) | |
if (search_string_length > self.book_length) or (search_string_length == 0): | |
return 0 | |
return self.base**(self.book_length - search_string_length) * (1 + self.book_length - search_string_length) | |
def search_exactmatch_result(self, search_string): | |
"""Return exact match by switching section""" | |
search_string = self._filter_string(search_string) | |
search_string_length = len(search_string) | |
if (search_string_length < 1) or (search_string_length > self.max_section): | |
return None | |
self.goto_section(search_string_length) | |
return search_string | |
def search_firstmatch_result(self, search_string): | |
"""Return the best result for search_string, as a document (this is not really the first)""" | |
search_string = self._filter_string(search_string) | |
search_string_length = len(search_string) | |
if (search_string_length > self.book_length) or (search_string_length == 0): | |
return None | |
elif search_string_length == self.book_length: | |
return search_string | |
r = self.alphabet[0] * self.book_length | |
r = search_string + r[search_string_length:] | |
return r | |
def search_bestmatch_result(self, search_string): | |
"""Return the best result for search_string, as a document""" | |
search_string = self._filter_string(search_string) | |
search_string_length = len(search_string) | |
if (search_string_length > self.book_length) or (search_string_length == 0): | |
return None | |
elif search_string_length == self.book_length: | |
return search_string | |
r = search_string * (1 + self.book_length / search_string_length) | |
return r[:self.book_length] | |
def search_startwith_result(self, search_string): | |
"""Return a random result starting with search_string, as a document""" | |
search_string = self._filter_string(search_string) | |
search_string_length = len(search_string) | |
if (search_string_length > self.book_length) or (search_string_length == 0): | |
return None | |
elif search_string_length == self.book_length: | |
return search_string | |
r = self.random_book() | |
r = search_string + r[search_string_length:] | |
return r | |
def search_inside_result(self, search_string): | |
"""Return a random result containing search_string, as a document""" | |
search_string = self._filter_string(search_string) | |
search_string_length = len(search_string) | |
if (search_string_length >= self.book_length) or (search_string_length == 0): | |
return None | |
r = self.random_book() | |
i = 1 + choice(range(self.book_length - search_string_length)) | |
r = r[:i] + search_string + r[i + search_string_length:] | |
return r | |
class LibraryOfBabel (BaseLibraryOfBabel): | |
"""Extending BaseLibraryOfBabel to use tqdm""" | |
def get_book_from_number(self, n): | |
"""Return the nth document in the library""" | |
if n >= self.books: | |
return None | |
r = [] | |
for i in tqdm(xrange(self.book_length), total=self.book_length): | |
r.append(n % self.base) | |
n /= self.base | |
return self._to_string(r[::-1]) | |
def get_book_number(self, book): | |
"""Return the number of a document in the library""" | |
book = self._from_string(book) | |
n = 0 | |
for c in tqdm(book, total=self.book_length): | |
n = self.base * n + c | |
return n | |
def main(): | |
"""Quick and dirty demo""" | |
lib = LibraryOfBabel(name="FBabel", alphabet="".join([chr(x) for x in range(32, 127) + [10]]), section=140, shelfs_per_wall=2**10, volumes_per_shelf=2**12) | |
print lib.library_description() | |
print lib.section_description() | |
s = "Les meilleurs tirages d'euromillion de l'année 2016, par Philippe Risoli" | |
print "%s documents are mentionning “%s” in section %d" % (lib.sci_notation(lib.search_result_count(s)), s, lib.section()) | |
print "Here is the best match:" | |
book = lib.search_bestmatch_result(s) | |
print book | |
n = lib.get_book_number(book) | |
print "This is the document number %s" % lib.sci_notation(n) | |
print "This document is located at: %s" % lib.book_location(n) | |
book = lib.search_exactmatch_result(s) | |
if book: | |
print "An exact match exists at: %s" % lib.book_location(lib.get_book_number(book)) | |
print "Importing this program's code" | |
with open(__file__) as f: | |
book = lib.book_from_string(f.read().replace("\t", " ")) # OK, cheating a little to replace tabs | |
print book | |
n = lib.get_book_number(book) | |
print "This is the document number %s" % lib.sci_notation(n) | |
print "This document is located at: %s" % lib.book_location(n) | |
print "Checking if document at that location is matching our document..." | |
section, hexagone, wall, shelf, volume = lib.get_location_from_number(n) | |
if lib.get_book_from_location(section, hexagone, wall, shelf, volume) == book: | |
print "YES :)" | |
else: | |
print "NO :(" | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment