Skip to content

Instantly share code, notes, and snippets.

@fbparis
Created January 1, 2016 05:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fbparis/5aa470b74ef37edd37bc to your computer and use it in GitHub Desktop.
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...
#!/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
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
print "An exact match exists at: %s" % lib.book_location(lib.get_book_number(book))
print
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
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