First commit
This commit is contained in:
commit
d91c5731ce
|
@ -0,0 +1,2 @@
|
||||||
|
__pycache__
|
||||||
|
*.swp
|
|
@ -0,0 +1 @@
|
||||||
|
Random playing around with sets and set-based natural numbers
|
|
@ -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]
|
|
@ -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)
|
|
@ -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)
|
Loading…
Reference in New Issue