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