First commit

This commit is contained in:
Juhani Krekelä 2018-04-29 15:00:35 +03:00
commit d91c5731ce
5 changed files with 383 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
__pycache__
*.swp

1
README Normal file
View File

@ -0,0 +1 @@
Random playing around with sets and set-based natural numbers

24
UNLICENSE Normal file
View File

@ -0,0 +1,24 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to [http://unlicense.org]

176
arithmetic.py Normal file
View File

@ -0,0 +1,176 @@
import functools
import time
import sets
# The encoding used is something I've seen referred to as Von Neuman indices
# Basically, an empty set is 0 and {a, b, c} is 2^a + 2^b + 2^c
# The numbers in the set are also encoded the same way
# For example:
# 13 = 2³ + 2² + 2⁰
# → {3, 2, 0}
# 3 = 2¹ + 2⁰
# 2 = 2¹
# → {{1, 0}, {1}, 0}
# 1 = 2⁰
# → {{{0}, 0}, {{0}}, 0}
# → {{{∅}, ∅}, {{∅}}, ∅}
empty = sets.set()
def I(x):
"""Converts a number from set representation to an integer"""
# Algorith works by recursively calling itself
# I(∅) = 0
# I({e₁, e₂, e₃, …}) = 2^I(e₁) + 2^I(e₂) + 2^I(e₃) + …
# For example:
# I({{{∅}, ∅}, {{∅}}, ∅})
# → 2^I({{∅}, ∅}) + 2^I({{∅}}) + 2^I(∅)
# → 2^(2^I({∅}) + 2^I(∅)) + 2^2^I({∅}) + 2^0
# → 2^(2^2^I(∅) + 2^0) + 2^2^2^I(∅) + 2^0
# → 2^(2^2^0 + 2^0) + 2^2^2^0 + 2^0
# → 2^(2^1 + 1) + 2^2^1 + 1
# → 2^(2 + 1) + 2^2 + 1
# → 2^3 + 2^2 + 1
# → 8 + 4 + 1
# → 13
if I == empty:
return 0
else:
return sum(2**I(i) for i in x)
def powers_of_two(x):
"""Lists the powers of two the number is composed of"""
assert type(x) == int
# Look for a power of two bigger than the one we were given
power = 0
number = 1
while number < x:
number *= 2
power += 1
# Find all smaller powers of two
powers = []
while x > 0:
if number <= x:
powers.append(power)
x -= number
number //= 2
power -= 1
return powers
def J(x):
"""Converts an integer into set representation"""
powers = powers_of_two(x)
return sets.set(J(i) for i in powers)
def lesser(x, y):
only_x = x.difference(y)
only_y = y.difference(x)
if only_x is empty and only_y is not empty:
return True
elif only_y is empty:
return False
else:
x_max_power = functools.reduce(lambda a, b: b if lesser(a,b) else a, only_x)
y_max_power = functools.reduce(lambda a, b: b if lesser(a,b) else a, only_y)
return lesser(x_max_power, y_max_power)
def lshift(x, y):
return sets.set(add(i, y) for i in x)
def double(x):
return lshift(x, J(1))
def rshift(x, y):
return sets.set(sub(i, y) for i in x)
def add(x, y):
only_x = x.difference(y)
only_y = y.difference(x)
one = only_x.union(only_y)
both = x.intersection(y)
if both is empty:
return one
else:
return add(one, double(both))
def sub(x, y):
assert not lesser(x, y)
only_x = x.difference(y)
only_y = y.difference(x)
if only_y is empty:
return only_x
else:
return sub(add(only_x, only_y), double(only_y))
def mul(x, y):
total = J(0)
for power in x:
total = add(total, lshift(y, power))
return total
def divmod(x, y):
assert y is not empty
powers = []
shift_amount = J(0)
shifted_divisor = y
while lesser(shifted_divisor, x):
shift_amount = add(shift_amount, J(1))
shifted_divisor = double(shifted_divisor)
remaining_dividend = x
while True:
if not lesser(remaining_dividend, shifted_divisor):
powers.append(shift_amount)
remaining_dividend = sub(remaining_dividend, shifted_divisor)
if shift_amount is empty:
break
shift_amount = sub(shift_amount, J(1))
shifted_divisor = rshift(shifted_divisor, J(1))
return sets.set(powers), remaining_dividend
def fibonacci():
a = J(0)
b = J(1)
yield a
while True:
yield b
a, b = b, add(a, b)
def py_fibonacci():
a = 0
b = 1
yield a
while True:
yield b
a, b = b, a + b
if __name__ == '__main__':
start_time = time.monotonic()
limit = J(2**128)
for number in fibonacci():
if lesser(limit, number):
break
#print(I(number), tuple(map(I, number)), str(number))
print(time.monotonic() - start_time)
start_time = time.monotonic()
limit = 2**128
for number in py_fibonacci():
if limit < number:
break
print(time.monotonic() - start_time)

180
sets.py Normal file
View File

@ -0,0 +1,180 @@
import functools
import threading
import weakref
set_table = weakref.WeakValueDictionary()
set_table_lock = threading.Lock()
@functools.total_ordering
class InternedSetObject:
def __init__(self, elements):
assert type(elements) == tuple
self.elements = elements
def __contains__(self, element):
for i in self.elements:
if i == element:
return True
return False
def __iter__(self):
return iter(self.elements)
def union(self, other):
assert isinstance(other, InternedSetObject)
# Since the elements of both sets are already sorted, we can just join them
elements = []
own_index = 0
other_index = 0
while True:
if own_index == len(self.elements):
# Ran out of own elements, add those of the other and exit
elements.extend(other.elements[other_index:])
break
elif other_index == len(other.elements):
# Ran out of other's elements, add own and exit
elements.extend(self.elements[own_index:])
break
elif self.elements[own_index] == other.elements[other_index]:
# Both have the element, add it once (this takes care of deduplication)
elements.append(self.elements[own_index])
own_index += 1
other_index += 1
elif self.elements[own_index] < other.elements[other_index]:
# Our element goes first, add it
elements.append(self.elements[own_index])
own_index += 1
else:
# Other's element goes first, add it
elements.append(other.elements[other_index])
other_index += 1
return _new_set(tuple(elements))
def intersection(self, other):
assert isinstance(other, InternedSetObject)
# This works with the same basic idea as union
# The only difference here is that only duplicate elements get added
elements = []
own_index = 0
other_index = 0
while True:
if own_index == len(self.elements):
# Ran out of own elements, exit (since we don't have other's remaining elements)
break
elif other_index == len(other.elements):
# Ran out of other's elements, exit (since other doesn't have our remaining elements)
break
elif self.elements[own_index] == other.elements[other_index]:
# Both have the element, add it
elements.append(self.elements[own_index])
own_index += 1
other_index += 1
elif self.elements[own_index] < other.elements[other_index]:
# Our element goes first, skip it (since other doesn't have it)
own_index += 1
else:
# Other's element goes first, skip it (since we don't have it)
other_index += 1
return _new_set(tuple(elements))
def difference(self, other):
assert isinstance(other, InternedSetObject)
# This works with the same basic ide as union
# The only difference here is that we never add anything from the other
elements = []
own_index = 0
other_index = 0
while True:
if own_index == len(self.elements):
# Ran out of own elements, exit (since we don't want other's elements)
break
elif other_index == len(other.elements):
# Ran out of other's elements, add own and exit
elements.extend(self.elements[own_index:])
break
elif self.elements[own_index] == other.elements[other_index]:
# Both have the element, skip it
own_index += 1
other_index += 1
elif self.elements[own_index] < other.elements[other_index]:
# Our element goes first, add it
elements.append(self.elements[own_index])
own_index += 1
else:
# Other's element goes first, skip it (since we don't want its element in the final)
other_index += 1
return _new_set(tuple(elements))
def __eq__(self, other):
assert (id(self) == id(other) and self is other) or id(self) != id(other)
return self is other
def __lt__(self, other):
return id(self) < id(other)
def __hash__(self):
return hash(id(self))
def __repr__(self):
name = 'InternedSetObject'
if __name__ != '__main__':
name = '%s.%s' % (__name__, name)
return '%s(%s)' % (name, repr(self.elements))
def __str__(self):
if len(self.elements) > 0:
return '{%s}' % ', '.join(map(str, self.elements))
else:
return ''
def dedup_elements(elements):
"""Deduplicates a sorted iterable and returns it as a tuple"""
deduplicated = []
for element in elements:
if len(deduplicated) > 0 and element == deduplicated[-1]:
continue
else:
deduplicated.append(element)
return tuple(deduplicated)
def _new_set(elements):
"""Returns a set corresponding to elements list that is already sorted and deduped"""
global set_table, set_table_lock
with set_table_lock:
if elements in set_table:
return set_table[elements]
else:
set_object = InternedSetObject(elements)
set_table[elements] = set_object
return set_object
def set(elements = None):
"""Returns an InternedSetObject. The same object will be returned for all equivalent sets."""
if elements is not None:
elements = dedup_elements(sorted(elements))
else:
elements = ()
return _new_set(elements)