-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathprime_multiset.py
159 lines (121 loc) · 5.94 KB
/
prime_multiset.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# -*- coding: utf-8 -*-
import functools
import math
import operator
from typing import Iterable
class PrimeMultiset:
"""
Multiset of prime numbers, stored as a single integer. It only
supports a couple of operations compared to a regular multiset
class. Storing anything else than prime numbers won't produce
an error (prime checks are expensive), but the semantics in
such a case aren't guaranteed.
Operations are based on the fact that any positive integer can
represent a multiset of prime number, an that specific integer
operations can be used to perform prime multiset operations.
"""
def __init__(self, sequence: Iterable[int]=(), initial_value: int=1):
"""
Initialize the multiset with an initial value and a list of
primes. The initial_value parameter can be any positive integer
and corresponds to a prime multiset to which elements will be
added. The sequence parameter corresponds to an iterable of prime
numbers to be added to the multiset.
If both sequence and initial_value are provided, the prime numbers
in sequence will be added to initial_value.
:param sequence: iterable sequence of prime numbers
:param initial_value: initial multiset value
"""
self.value = functools.reduce(operator.mul, sequence, initial_value)
##################################################
# Usual multiset operations
def __and__(self, other: 'PrimeMultiset') -> 'PrimeMultiset':
return PrimeMultiset(initial_value=math.gcd(self.value, other.value))
def __iand__(self, other: 'PrimeMultiset') -> 'PrimeMultiset':
self.value = math.gcd(self.value, other.value)
return self
def __or__(self, other: 'PrimeMultiset') -> 'PrimeMultiset':
return PrimeMultiset(initial_value=self.__lcm(self.value, other.value))
def __ior__(self, other: 'PrimeMultiset') -> 'PrimeMultiset':
self.value = self.__lcm(self.value, other.value)
return self
def __add__(self, other: 'PrimeMultiset') -> 'PrimeMultiset':
return PrimeMultiset(initial_value=self.value * other.value)
def __iadd__(self, other: 'PrimeMultiset') -> 'PrimeMultiset':
self.value *= other.value
return self
def __sub__(self, other: 'PrimeMultiset') -> 'PrimeMultiset':
return PrimeMultiset(initial_value=self.value // (self & other).value)
def __isub__(self, other: 'PrimeMultiset') -> 'PrimeMultiset':
self.value //= (self & other).value
return self
##################################################
# Multiset equality operations
def __eq__(self, other: 'PrimeMultiset') -> bool:
return self.value == other.value
def __ne__(self, other: 'PrimeMultiset') -> bool:
return self.value != other.value
##################################################
# Multiset inclusion operations
def __lt__(self, other: 'PrimeMultiset') -> bool:
return self <= other and self != other
def __le__(self, other: 'PrimeMultiset') -> bool:
return other.value % self.value == 0
def __gt__(self, other: 'PrimeMultiset') -> bool:
return self >= other and self != other
def __ge__(self, other: 'PrimeMultiset') -> bool:
return self.value % other.value == 0
##################################################
# Modifying operations
def add(self, element: int) -> None:
self.value *= element
def remove(self, element: int) -> None:
quotient, remainder = divmod(self.value, element)
if remainder != 0:
raise KeyError(element)
self.value = quotient
def discard(self, element: int) -> None:
quotient, remainder = divmod(self.value, element)
if remainder == 0:
self.value = quotient
def clear(self) -> None:
self.value = 1
##################################################
# Non-modifying operations
def is_empty(self) -> bool:
"""
Returns whether the multiset is empty or not.
Computing the length of a prime multiset is expensive since it
requires prime decomposition, so we can't use the idiomatic
len(prime_multiset) to check whether the multiset is empty.
"""
return self.value == 1
def __contains__(self, element: int) -> bool:
return self.value % element == 0
##################################################
# Helper functions
@staticmethod
def __lcm(lhs: int, rhs: int) -> int:
# Least common multiple
return lhs * rhs // math.gcd(lhs, rhs)
if __name__ == '__main__':
# Test usual multiset operations
assert PrimeMultiset([2, 3, 3, 3]) & PrimeMultiset([2, 2, 2, 3]) == PrimeMultiset([2, 3])
assert PrimeMultiset([2, 2, 2]) | PrimeMultiset([2, 2, 3]) == PrimeMultiset([2, 2, 2, 3])
assert PrimeMultiset([2, 3, 3, 23]) + PrimeMultiset([3, 5, 37]) == PrimeMultiset([2, 3, 3, 3, 5, 23, 37])
assert PrimeMultiset([2, 2, 2, 7, 11, 11, 13]) - PrimeMultiset([2, 7, 11, 13]) == PrimeMultiset([2, 2, 11])
assert PrimeMultiset([2, 3, 5, 7, 11]) - PrimeMultiset([3, 7, 13]) == PrimeMultiset([2, 5, 11])
# Test multiset inclusion operations
assert PrimeMultiset([2, 3]) < PrimeMultiset([2, 2, 2, 3])
assert not PrimeMultiset([2, 3, 7]) < PrimeMultiset([2, 3, 7])
assert PrimeMultiset([2, 3]) <= PrimeMultiset([2, 2, 2, 3])
assert PrimeMultiset([2, 3, 7]) <= PrimeMultiset([2, 3, 7])
assert PrimeMultiset([2, 3, 7, 7, 11]) > PrimeMultiset([2, 7, 11])
assert not PrimeMultiset([2, 3, 7]) > PrimeMultiset([2, 3, 7])
assert PrimeMultiset([2, 3, 7, 7, 11]) >= PrimeMultiset([2, 7, 11])
assert PrimeMultiset([2, 3, 7]) >= PrimeMultiset([2, 3, 7])
# Test non-modifying operations
assert PrimeMultiset().is_empty()
assert not PrimeMultiset([2, 3, 7]).is_empty()
assert 7 in PrimeMultiset([2, 3, 7, 7, 11])
assert 5 not in PrimeMultiset([2, 3, 7, 7, 11])