d8s-math 0.8.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- d8s_math/__init__.py +5 -0
- d8s_math/maths.py +674 -0
- d8s_math-0.8.0.dist-info/METADATA +311 -0
- d8s_math-0.8.0.dist-info/RECORD +7 -0
- d8s_math-0.8.0.dist-info/WHEEL +4 -0
- d8s_math-0.8.0.dist-info/licenses/COPYING +674 -0
- d8s_math-0.8.0.dist-info/licenses/COPYING.LESSER +165 -0
d8s_math/__init__.py
ADDED
d8s_math/maths.py
ADDED
|
@@ -0,0 +1,674 @@
|
|
|
1
|
+
import collections
|
|
2
|
+
import functools
|
|
3
|
+
import itertools
|
|
4
|
+
import math
|
|
5
|
+
import numbers
|
|
6
|
+
from typing import Any, List, NamedTuple, Tuple, Union
|
|
7
|
+
|
|
8
|
+
import sympy
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class integerTupleType(NamedTuple):
|
|
12
|
+
base: int
|
|
13
|
+
digits: Tuple[int, ...]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
StrOrNumberType = Union[str, int, float, integerTupleType]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# TODO: write prime factorization function
|
|
20
|
+
# TODO: write a function to determine if a number is prime or not
|
|
21
|
+
# TODO: write function for degrees to radians and visa-versa (`math.radians(33.1)`)
|
|
22
|
+
|
|
23
|
+
# def percentOf(percent, number):
|
|
24
|
+
# """Return the given percent of the given number."""
|
|
25
|
+
# pass
|
|
26
|
+
|
|
27
|
+
# is / of = % / 100
|
|
28
|
+
|
|
29
|
+
# (I want to write functions for "is", "of", and "%" based on the above diagram)
|
|
30
|
+
|
|
31
|
+
# # TODO: may want to write a function to standardize percentages (e.g. 0.1 and 10 and '10' and '10%')
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
IntegerTuple = collections.namedtuple("IntegerTuple", ["base", "digits"])
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def fibonacci_sequence(n: int) -> List[int]:
|
|
38
|
+
"""Return the first n digits of the fibonacci sequence."""
|
|
39
|
+
nums = [fibonacci(i) for i in range(n)]
|
|
40
|
+
return nums
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def fibonacci(n: int) -> int:
|
|
44
|
+
"""Return the value of the Fibonacci sequence at index n."""
|
|
45
|
+
if n <= 1:
|
|
46
|
+
return 1
|
|
47
|
+
else:
|
|
48
|
+
return fibonacci(n - 1) + fibonacci(n - 2)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def number_closest(a, b, target):
|
|
52
|
+
"""Return a or b, whichever is closest to the target."""
|
|
53
|
+
if abs(a - target) <= abs(b - target):
|
|
54
|
+
return a
|
|
55
|
+
else:
|
|
56
|
+
return b
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def number_furthest(a, b, target):
|
|
60
|
+
"""Return a or b, whichever is furthest to the target."""
|
|
61
|
+
if abs(a - target) >= abs(b - target):
|
|
62
|
+
return a
|
|
63
|
+
else:
|
|
64
|
+
return b
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def cartesian_product(a: Any, *args: Any, repeat: int = 1):
|
|
68
|
+
"""."""
|
|
69
|
+
return list(itertools.product(a, *args, repeat=repeat))
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def sympy_symbol(symbol_name: str):
|
|
73
|
+
"""."""
|
|
74
|
+
return sympy.symbols(symbol_name)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def equation_solve(equation: str, symbols: List[str]):
|
|
78
|
+
"""."""
|
|
79
|
+
sympy_symbols = [sympy_symbol(symbol) for symbol in symbols]
|
|
80
|
+
map(eval, sympy_symbols)
|
|
81
|
+
solution = sympy.solve(equation)
|
|
82
|
+
return solution
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def expression_explore(expression: str, symbol: str, start: int, end: int, step: int):
|
|
86
|
+
"""."""
|
|
87
|
+
for i in range(start, end, step):
|
|
88
|
+
equation = f"Eq({expression}, {i})"
|
|
89
|
+
yield (i, equation_solve(equation, [symbol]))
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _hot_or_cold_encoder(items: list, default_value: int, changed_value: int, *, reverse: bool = False) -> List[list]:
|
|
93
|
+
results = []
|
|
94
|
+
unique_items = list(set(items))
|
|
95
|
+
sorted_items = sorted(unique_items, reverse=reverse)
|
|
96
|
+
max_index = len(unique_items)
|
|
97
|
+
|
|
98
|
+
for item in items:
|
|
99
|
+
encoded_result = [default_value for i in range(0, max_index)]
|
|
100
|
+
index_to_change = sorted_items.index(item)
|
|
101
|
+
encoded_result[index_to_change] = changed_value
|
|
102
|
+
results.append(encoded_result)
|
|
103
|
+
|
|
104
|
+
return results
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def one_cold_encode(items: list, *, reverse: bool = False) -> List[list]:
|
|
108
|
+
return _hot_or_cold_encoder(items, 1, 0, reverse=reverse)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def one_hot_encode(items: list, *, reverse: bool = False) -> List[list]:
|
|
112
|
+
return _hot_or_cold_encoder(items, 0, 1, reverse=reverse)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def is_integer_tuple(possible_integer_tuple: Any) -> bool:
|
|
116
|
+
"""."""
|
|
117
|
+
# I'm doing a more complex check rather than isinstance(possible_integer_tuple, IntegerTuple) because I was unable
|
|
118
|
+
# to get it consistently working when this function was used in other files... the current check works consistently
|
|
119
|
+
is_integer_tuple = (
|
|
120
|
+
isinstance(possible_integer_tuple, tuple)
|
|
121
|
+
and hasattr(possible_integer_tuple, "base")
|
|
122
|
+
and hasattr(possible_integer_tuple, "digits")
|
|
123
|
+
)
|
|
124
|
+
return is_integer_tuple
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def string_to_number(string: str) -> Union[int, float]:
|
|
128
|
+
"""Convert a number as a string into either an integer or float."""
|
|
129
|
+
if not isinstance(string, str):
|
|
130
|
+
return string
|
|
131
|
+
|
|
132
|
+
try:
|
|
133
|
+
return int(string)
|
|
134
|
+
except ValueError:
|
|
135
|
+
try:
|
|
136
|
+
return float(string)
|
|
137
|
+
except ValueError:
|
|
138
|
+
message = f"Unable to convert {string} to a number."
|
|
139
|
+
raise RuntimeError(message)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def first_arg_as_decimal(func):
|
|
143
|
+
"""Convert the first argument to a number (either integer or float)."""
|
|
144
|
+
|
|
145
|
+
@functools.wraps(func)
|
|
146
|
+
def wrapper(*args, **kwargs):
|
|
147
|
+
first_arg = args[0]
|
|
148
|
+
other_args = args[1:]
|
|
149
|
+
|
|
150
|
+
if isinstance(first_arg, str):
|
|
151
|
+
first_arg = string_to_number(first_arg)
|
|
152
|
+
elif is_integer_tuple(first_arg):
|
|
153
|
+
first_arg = integer_tuple_to_decimal(first_arg)
|
|
154
|
+
|
|
155
|
+
return func(first_arg, *other_args, **kwargs)
|
|
156
|
+
|
|
157
|
+
return wrapper
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def arguments_as_decimals(func):
|
|
161
|
+
"""Convert all arguments to numbers (either integers or floats)."""
|
|
162
|
+
|
|
163
|
+
@functools.wraps(func)
|
|
164
|
+
def wrapper(*args, **kwargs):
|
|
165
|
+
new_args = []
|
|
166
|
+
for arg in args:
|
|
167
|
+
if isinstance(arg, str):
|
|
168
|
+
new_args.append(string_to_number(arg))
|
|
169
|
+
elif is_integer_tuple(arg):
|
|
170
|
+
decimal_arg = integer_tuple_to_decimal(arg)
|
|
171
|
+
new_args.append(decimal_arg)
|
|
172
|
+
else:
|
|
173
|
+
new_args.append(arg)
|
|
174
|
+
return func(*new_args, **kwargs)
|
|
175
|
+
|
|
176
|
+
return wrapper
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
@arguments_as_decimals
|
|
180
|
+
def decimal_to_gray_code(num: Union[str, int, float]) -> integerTupleType:
|
|
181
|
+
"""Convert the given number to a gray code.
|
|
182
|
+
|
|
183
|
+
This function was inspired by the code here: https://en.wikipedia.org/wiki/Gray_code#Converting_to_and_from_Gray_code."""
|
|
184
|
+
gray_code = num ^ (num >> 1) # type: ignore[operator]
|
|
185
|
+
binary_gray_code = decimal_to_base(gray_code, 2)
|
|
186
|
+
return binary_gray_code
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
# TODO: should this function only take an integer tuple and not a string (e.g. '111') or int which will be intepreted as binary (e.g. 111) # noqa: E501
|
|
190
|
+
@arguments_as_decimals
|
|
191
|
+
def gray_code_to_decimal(num: integerTupleType) -> int:
|
|
192
|
+
"""Convert the given number to a gray code.
|
|
193
|
+
|
|
194
|
+
This function was inspired by the code here: https://en.wikipedia.org/wiki/Gray_code#Converting_to_and_from_Gray_code."""
|
|
195
|
+
mask = num >> 1 # type: ignore[operator]
|
|
196
|
+
while mask != 0:
|
|
197
|
+
num = num ^ mask # type: ignore[operator, assignment]
|
|
198
|
+
mask = mask >> 1
|
|
199
|
+
return num # type: ignore[return-value]
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def decimal_to_hex(decimal_number):
|
|
203
|
+
"""."""
|
|
204
|
+
return hex(decimal_number)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def hex_to_decimal(hex):
|
|
208
|
+
"""."""
|
|
209
|
+
return integer_to_decimal(hex, 16)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def roman_numeral_to_decimal(roman_numeral: str) -> int:
|
|
213
|
+
"""."""
|
|
214
|
+
from number_tools.converters import roman_to_integer
|
|
215
|
+
|
|
216
|
+
result = roman_to_integer(roman_numeral)
|
|
217
|
+
return result
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def decimal_to_roman_numeral(decimal_number) -> str:
|
|
221
|
+
"""."""
|
|
222
|
+
from number_tools.converters import integer_to_roman
|
|
223
|
+
|
|
224
|
+
result = integer_to_roman(decimal_number)
|
|
225
|
+
return result
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def integer_tuple_to_decimal(integer_tuple: integerTupleType) -> int:
|
|
229
|
+
"""Return the decimal form of the given number (represented as an integer tuple)."""
|
|
230
|
+
decimal_number = 0
|
|
231
|
+
|
|
232
|
+
# flip the digits (so the smallest 'place' is first)
|
|
233
|
+
flipped_digits = reversed(integer_tuple.digits)
|
|
234
|
+
for index, num in enumerate(flipped_digits):
|
|
235
|
+
index_multiplier = integer_tuple.base**index
|
|
236
|
+
decimal_number += index_multiplier * num
|
|
237
|
+
|
|
238
|
+
return decimal_number
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def integer_to_decimal(num: Union[str, int, float], base: int) -> int:
|
|
242
|
+
"""Convert the number of the given base to a decimal number."""
|
|
243
|
+
number = str(num)
|
|
244
|
+
# TODO: the base is currently limited to numbers between 2 and 36... generalize this so that there are no such limitations # noqa: E501
|
|
245
|
+
converted_number = int(number, base=base)
|
|
246
|
+
return converted_number
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def _base_converter_init(alphabet):
|
|
250
|
+
"""."""
|
|
251
|
+
from baseconv import BaseConverter
|
|
252
|
+
|
|
253
|
+
converter = BaseConverter(alphabet)
|
|
254
|
+
return converter
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def decimal_to_base(decimal_number: Union[str, int, float], base: int):
|
|
258
|
+
"""Convert the decimal_number to the given base."""
|
|
259
|
+
if base == 1:
|
|
260
|
+
results = [1 for i in range(0, decimal_number)] # type: ignore[arg-type]
|
|
261
|
+
new_integer = IntegerTuple(base=base, digits=tuple(results))
|
|
262
|
+
return new_integer
|
|
263
|
+
|
|
264
|
+
results = []
|
|
265
|
+
max_value = base - 1
|
|
266
|
+
|
|
267
|
+
floor_divided_value = decimal_number // base # type: ignore[operator]
|
|
268
|
+
if floor_divided_value > max_value:
|
|
269
|
+
update = list(decimal_to_base(floor_divided_value, base).digits)
|
|
270
|
+
results.extend(update)
|
|
271
|
+
elif floor_divided_value != 0:
|
|
272
|
+
results.append(floor_divided_value) # type: ignore[arg-type]
|
|
273
|
+
results.append(decimal_number % base) # type: ignore[arg-type]
|
|
274
|
+
|
|
275
|
+
new_integer = IntegerTuple(base=base, digits=tuple(results))
|
|
276
|
+
return new_integer
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
# # TODO: I don't think the result_as_digit_list argument name is very descriptive; there is probably a better name for that argument # noqa: E501
|
|
280
|
+
# def decimal_to_base(
|
|
281
|
+
# decimal_number: Union[str, int, float],
|
|
282
|
+
# base: int,
|
|
283
|
+
# alphabet: str = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
|
|
284
|
+
# result_as_digit_list: bool = False
|
|
285
|
+
# ):
|
|
286
|
+
# """Convert the decimal_number to the given base."""
|
|
287
|
+
# if base > len(alphabet):
|
|
288
|
+
# result_as_digit_list = True
|
|
289
|
+
|
|
290
|
+
# if result_as_digit_list:
|
|
291
|
+
# pass
|
|
292
|
+
# else:
|
|
293
|
+
# alphabet_for_base = alphabet[:base]
|
|
294
|
+
|
|
295
|
+
# if base == 1:
|
|
296
|
+
# result = alphabet_for_base[0] * decimal_number
|
|
297
|
+
# return result
|
|
298
|
+
# else:
|
|
299
|
+
# base_converter = _base_converter_init(alphabet_for_base)
|
|
300
|
+
# result = base_converter.encode(decimal_number)
|
|
301
|
+
# return result
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def outer_division():
|
|
305
|
+
# not sure if this is a thing... I presume there is a corallary to outer_product
|
|
306
|
+
pass
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
# todo: provide an output variable
|
|
310
|
+
def outer_product(a: int, b: int, a_start: int = 1, b_start: int = 1):
|
|
311
|
+
"""Return a two-dimensional array with the results of range(a_start, a+1) multiplied by range(b_start, b+1)."""
|
|
312
|
+
result = []
|
|
313
|
+
|
|
314
|
+
for i in range(a_start, a + 1):
|
|
315
|
+
result.append([i * j for j in range(b_start, b + 1)])
|
|
316
|
+
|
|
317
|
+
return result
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def multiplication_table(a: int, b: int, a_start: int = 1, b_start: int = 1):
|
|
321
|
+
"""."""
|
|
322
|
+
return outer_product(a, b, a_start=a_start, b_start=b_start)
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def number_evenly_divides(a, b):
|
|
326
|
+
"""Return True if a evenly divides b. Otherwise, return False."""
|
|
327
|
+
b_by_a_remainder = b % a
|
|
328
|
+
evenly_divides = b_by_a_remainder == 0
|
|
329
|
+
return evenly_divides
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def number_evenly_divided_by(a, b):
|
|
333
|
+
"""Return True if a is evenly divided by b. Otherwise, return False."""
|
|
334
|
+
evenly_divides = number_evenly_divides(b, a)
|
|
335
|
+
return evenly_divides
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def fraction_examples(n=10, *, fractions_as_strings: bool = True):
|
|
339
|
+
"""Create n fractions."""
|
|
340
|
+
from hypothesis.strategies import fractions
|
|
341
|
+
from hypothesis_data import hypothesis_get_strategy_results
|
|
342
|
+
|
|
343
|
+
fraction_object_examples = hypothesis_get_strategy_results(fractions, n=n)
|
|
344
|
+
if fractions_as_strings:
|
|
345
|
+
return [str(fraction) for fraction in fraction_object_examples]
|
|
346
|
+
else:
|
|
347
|
+
return fraction_object_examples
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def iterable_differences(iterable):
|
|
351
|
+
"""Find all of the possible differences of all possible orders of the given iterable."""
|
|
352
|
+
differences = []
|
|
353
|
+
|
|
354
|
+
iterable_permutations = permutations(iterable, length=len(iterable))
|
|
355
|
+
for permutation in iterable_permutations:
|
|
356
|
+
diff = permutation[0]
|
|
357
|
+
for i in permutation[1:]:
|
|
358
|
+
diff = diff - i
|
|
359
|
+
differences.append(diff)
|
|
360
|
+
|
|
361
|
+
return differences
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
def combinations(iterable, length=None):
|
|
365
|
+
"""Return all possible combinations of the given length which can be created from the given iterable. If no length is given, we will find all combinations of all lengths for the given iterable.""" # noqa: E501
|
|
366
|
+
if length is None:
|
|
367
|
+
combos = []
|
|
368
|
+
for i in range(1, len(iterable) + 1):
|
|
369
|
+
combos.extend(combinations(iterable, length=i))
|
|
370
|
+
return combos
|
|
371
|
+
else:
|
|
372
|
+
return list(itertools.combinations(iterable, length))
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def combinations_with_replacement(iterable, length=None):
|
|
376
|
+
"""Return all possible combinations of the given length which can be created from the given iterable. If no length is given, we will find all combinations of all lengths for the given iterable.""" # noqa: E501
|
|
377
|
+
if length is None:
|
|
378
|
+
combos = []
|
|
379
|
+
for i in range(1, len(iterable) + 1):
|
|
380
|
+
combos.extend(combinations_with_replacement(iterable, length=i))
|
|
381
|
+
return combos
|
|
382
|
+
else:
|
|
383
|
+
return list(itertools.combinations_with_replacement(iterable, length))
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def prod(iterable):
|
|
387
|
+
"""Get the product of the iterable."""
|
|
388
|
+
import operator
|
|
389
|
+
from functools import reduce
|
|
390
|
+
|
|
391
|
+
# convert all of the items of the iterable to numbers
|
|
392
|
+
number_iterable = map(string_to_number, iterable)
|
|
393
|
+
|
|
394
|
+
return reduce(operator.mul, number_iterable, 1)
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
def permutations(iterable, length=None):
|
|
398
|
+
"""Return all possible permutations of the given iterable. If no length is given, we will find all permutations of all lengths for the given iterable""" # noqa: E501
|
|
399
|
+
if length is None:
|
|
400
|
+
perms = []
|
|
401
|
+
for i in range(1, len(iterable) + 1):
|
|
402
|
+
perms.extend(permutations(iterable, length=i))
|
|
403
|
+
else:
|
|
404
|
+
perms = list(itertools.permutations(iterable, length))
|
|
405
|
+
return perms
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
def _split_fraction(fraction_string: str) -> Tuple[int, int]:
|
|
409
|
+
"""Split up a fraction string and return a numerator and denominator."""
|
|
410
|
+
split_fraction_string = fraction_string.split("/")
|
|
411
|
+
|
|
412
|
+
if len(split_fraction_string) != 2:
|
|
413
|
+
message = f'Unable to handle the input "{fraction_string}" as a fraction. When providing a fraction, please separate the two numbers with a "/" character.' # noqa: E501
|
|
414
|
+
raise ValueError(message)
|
|
415
|
+
else:
|
|
416
|
+
numerator = int(split_fraction_string[0].strip())
|
|
417
|
+
denominator = int(split_fraction_string[1].strip())
|
|
418
|
+
return numerator, denominator
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
def _split_mixed_fraction(fraction_string):
|
|
422
|
+
"""Split up a mixed fraction and return the whole number and the faction (e.g. "1 1/3"). This function requires that the whole number and fraction be separated by a space.""" # noqa: E501
|
|
423
|
+
|
|
424
|
+
split_fraction_string = fraction_string.split(" ")
|
|
425
|
+
if len(split_fraction_string) != 2:
|
|
426
|
+
print(
|
|
427
|
+
f'Unable to handle the input "{fraction_string}" as a mixed fraction. When providing a mixed fraction as an argument, please separate the whole number from the fraction with a space (and do not include spaces in the fraction).' # noqa: E501
|
|
428
|
+
)
|
|
429
|
+
return None, None
|
|
430
|
+
else:
|
|
431
|
+
# TODO: replace all spaces around the "/" character - not sure what this comment means, but I think I fixed it with the strip below... if this comment doesn't make any more sense to you, my future self, go ahead an remove it # noqa: E501
|
|
432
|
+
whole_number = int(split_fraction_string[0].strip())
|
|
433
|
+
fraction = split_fraction_string[1].strip()
|
|
434
|
+
return whole_number, fraction
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
def fraction_simplify(fraction_string):
|
|
438
|
+
"""Simplify the fraction represented as a string."""
|
|
439
|
+
numerator, denominator = _split_fraction(fraction_string)
|
|
440
|
+
gcd_for_fraction = gcd(numerator, denominator)
|
|
441
|
+
|
|
442
|
+
return f"{int(numerator / gcd_for_fraction)}/{int(denominator / gcd_for_fraction)}"
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
def remainder(dividend, divisor):
|
|
446
|
+
return dividend % divisor
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
def floor(number):
|
|
450
|
+
return math.floor(number)
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
def ceiling(number):
|
|
454
|
+
return math.ceil(number)
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
def factorial(number):
|
|
458
|
+
return math.factorial(number)
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
def fraction_complex_to_mixed_fraction(fraction_string):
|
|
462
|
+
"""Simplify the fraction represented as a string."""
|
|
463
|
+
simplified_fraction_string = fraction_simplify(fraction_string)
|
|
464
|
+
numerator, denominator = _split_fraction(simplified_fraction_string)
|
|
465
|
+
|
|
466
|
+
if numerator < denominator:
|
|
467
|
+
return f"{numerator}/{denominator}"
|
|
468
|
+
elif numerator == denominator:
|
|
469
|
+
return "1"
|
|
470
|
+
else:
|
|
471
|
+
fraction_remainder = remainder(numerator, denominator)
|
|
472
|
+
return f"{floor(numerator / denominator)} {fraction_remainder}/{denominator}"
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
def fraction_mixed_to_complex_fraction(fraction_string):
|
|
476
|
+
"""Simplify the fraction represented as a string."""
|
|
477
|
+
whole_number, fraction = _split_mixed_fraction(fraction_string)
|
|
478
|
+
|
|
479
|
+
if not whole_number or not fraction:
|
|
480
|
+
return fraction_string
|
|
481
|
+
|
|
482
|
+
numerator, denominator = _split_fraction(fraction)
|
|
483
|
+
|
|
484
|
+
return f"{(whole_number * denominator) + numerator}/{denominator}"
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
def dot_product(item_a, item_b):
|
|
488
|
+
"""Find the dot product for the two items. See https://en.wikipedia.org/wiki/Dot_product for more details."""
|
|
489
|
+
dot_product = 0
|
|
490
|
+
|
|
491
|
+
for i in zip(item_a, item_b):
|
|
492
|
+
dot_product += i[0] * i[1]
|
|
493
|
+
|
|
494
|
+
return dot_product
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
def percent(ratio):
|
|
498
|
+
"""Return the ratio as a percentage."""
|
|
499
|
+
return round(ratio * 100, 2)
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
def percent_change(old_value: StrOrNumberType, new_value: StrOrNumberType) -> float:
|
|
503
|
+
"""Return the change from the old_value to the new_value (as a percent of the old_value)."""
|
|
504
|
+
difference = new_value - old_value # type: ignore[operator]
|
|
505
|
+
diff_as_ratio = difference / old_value # type: ignore[operator]
|
|
506
|
+
return percent(diff_as_ratio)
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
@arguments_as_decimals
|
|
510
|
+
def gcd(number1, number2):
|
|
511
|
+
"""Return the greatest common divisor."""
|
|
512
|
+
return math.gcd(number1, number2)
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
def ratio(number1, number2):
|
|
516
|
+
"""Return the ratio of the two numbers in the form 1:2. For example, if given 5 and 10, this function would return "1:2". If given 2 and 20, this function would return "1:10".""" # noqa: E501
|
|
517
|
+
divisor = gcd(number1, number2)
|
|
518
|
+
return "{}:{}".format(int(number1 / divisor), int(number2 / divisor))
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
def transpose(matrix):
|
|
522
|
+
"""Transpose the given matrix. See https://en.wikipedia.org/wiki/Transpose."""
|
|
523
|
+
|
|
524
|
+
if not matrix or not matrix[0]:
|
|
525
|
+
print("Empty matrix provided")
|
|
526
|
+
return None
|
|
527
|
+
|
|
528
|
+
transposed_matrix = [[matrix[j][i] for j in range(len(matrix))] for i in range(len(matrix[0]))]
|
|
529
|
+
|
|
530
|
+
return transposed_matrix
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
def number_line(value, min_, max_, interval: int = 1):
|
|
534
|
+
if min_ > max_:
|
|
535
|
+
raise RuntimeError('The minimum value "{}" is greater than the maximum value "{}"'.format(min_, max_))
|
|
536
|
+
if value > max_:
|
|
537
|
+
raise RuntimeError('The value "{}" is greater than the maximum value of "{}"'.format(value, max_))
|
|
538
|
+
if value < min_:
|
|
539
|
+
raise RuntimeError('The value "{}" is less than the minimum value of "{}"'.format(value, min_))
|
|
540
|
+
|
|
541
|
+
# find the length of the numberline between the value and the min_
|
|
542
|
+
length_below_value = int(((value - min_) / interval) - 1)
|
|
543
|
+
# find the length of the numberline between the max_ and the value
|
|
544
|
+
length_above_value = int(((max_ - value) / interval) - 1)
|
|
545
|
+
|
|
546
|
+
# create the numberline
|
|
547
|
+
if length_below_value < 0:
|
|
548
|
+
number_line_string = "({})|{}{}".format(min_, "." * length_above_value, max_)
|
|
549
|
+
elif length_above_value < 0:
|
|
550
|
+
number_line_string = "{}{}|({})".format(min_, "." * length_below_value, max_)
|
|
551
|
+
else:
|
|
552
|
+
number_line_string = "{}{}|{}{}".format(min_, "." * length_below_value, "." * length_above_value, max_)
|
|
553
|
+
|
|
554
|
+
return number_line_string
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
# start numbers_wrapper
|
|
558
|
+
# TODO: add a decorator to convert first arg to integer
|
|
559
|
+
def number_zero_pad(num: StrOrNumberType, length: StrOrNumberType) -> str:
|
|
560
|
+
"""."""
|
|
561
|
+
num = int(num) # type: ignore[arg-type]
|
|
562
|
+
if length < len(str(num)): # type: ignore[operator]
|
|
563
|
+
message = "The length you provided is shorter than the number. Please provide a length that is at least as long as the given number." # noqa: E501
|
|
564
|
+
raise ValueError(message)
|
|
565
|
+
|
|
566
|
+
zero_padded_number = f"{num}"
|
|
567
|
+
|
|
568
|
+
while len(zero_padded_number) < length: # type: ignore[operator]
|
|
569
|
+
zero_padded_number = f"0{zero_padded_number}"
|
|
570
|
+
|
|
571
|
+
return zero_padded_number
|
|
572
|
+
|
|
573
|
+
|
|
574
|
+
def is_number(item):
|
|
575
|
+
"""Return whether or not the item is a number."""
|
|
576
|
+
if isinstance(item, str):
|
|
577
|
+
try:
|
|
578
|
+
int(item)
|
|
579
|
+
except ValueError:
|
|
580
|
+
try:
|
|
581
|
+
float(item)
|
|
582
|
+
except ValueError:
|
|
583
|
+
return False
|
|
584
|
+
else:
|
|
585
|
+
return True
|
|
586
|
+
else:
|
|
587
|
+
return True
|
|
588
|
+
else:
|
|
589
|
+
return isinstance(item, numbers.Number)
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
@arguments_as_decimals
|
|
593
|
+
def number_is_even(number: StrOrNumberType):
|
|
594
|
+
remainder_two = remainder(number, 2)
|
|
595
|
+
is_even = remainder_two == 0
|
|
596
|
+
return is_even
|
|
597
|
+
|
|
598
|
+
|
|
599
|
+
@arguments_as_decimals
|
|
600
|
+
def number_is_odd(number: StrOrNumberType):
|
|
601
|
+
is_even = number_is_even(number)
|
|
602
|
+
return not is_even
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
def number_is_approx(number, approximate_value, *, relative_tolerance=1e-6):
|
|
606
|
+
"""."""
|
|
607
|
+
is_close = math.isclose(number, approximate_value, rel_tol=relative_tolerance)
|
|
608
|
+
return is_close
|
|
609
|
+
|
|
610
|
+
|
|
611
|
+
# TODO: rename this function as `enumerate` is already a python function
|
|
612
|
+
def enumerate_range(range_string, range_split_string: str = "-"):
|
|
613
|
+
"""Enumerate the range specified by the string. For example, `1-3` returns `[1, 2, 3]`."""
|
|
614
|
+
range_sections = range_string.split(range_split_string)
|
|
615
|
+
error_message = "The enumerate_range function expects a string with two integers separated by the character specified by the `range_split_string` argument which can be passed into the enumerate_range function." # noqa: E501
|
|
616
|
+
|
|
617
|
+
if len(range_sections) != 2:
|
|
618
|
+
raise ValueError(error_message)
|
|
619
|
+
|
|
620
|
+
try:
|
|
621
|
+
range_start = int(range_sections[0].strip())
|
|
622
|
+
range_end = int(range_sections[1].strip())
|
|
623
|
+
except ValueError:
|
|
624
|
+
raise ValueError(error_message)
|
|
625
|
+
else:
|
|
626
|
+
return [i for i in range(range_start, range_end + 1)]
|
|
627
|
+
|
|
628
|
+
|
|
629
|
+
def hex_endiness_swap(hex_string):
|
|
630
|
+
"""Credit to: https://stackoverflow.com/questions/27506474/how-to-byte-swap-a-32-bit-integer-in-python."""
|
|
631
|
+
import struct
|
|
632
|
+
|
|
633
|
+
return "{:08x}".format(struct.unpack("<I", struct.pack(">I", hex_string))[0])
|
|
634
|
+
|
|
635
|
+
|
|
636
|
+
def number_to_words(number):
|
|
637
|
+
"""Convert a number to its English representation (e.g. 100 => "One Hundred")."""
|
|
638
|
+
from d8s_strings.strings import _inflect_engine
|
|
639
|
+
|
|
640
|
+
return _inflect_engine().number_to_words(number)
|
|
641
|
+
|
|
642
|
+
|
|
643
|
+
@arguments_as_decimals
|
|
644
|
+
def number_to_scientific_notation(number):
|
|
645
|
+
"""Convert the given number to scientific notation."""
|
|
646
|
+
precision = str(len(str(number)) + 1)
|
|
647
|
+
scientific_notation_number = f"{number:.{precision}E}"
|
|
648
|
+
|
|
649
|
+
# credits for the solution below to https://stackoverflow.com/a/6913576
|
|
650
|
+
return (
|
|
651
|
+
scientific_notation_number.split("E")[0].rstrip("0").rstrip(".")
|
|
652
|
+
+ "E"
|
|
653
|
+
+ scientific_notation_number.split("E")[1]
|
|
654
|
+
)
|
|
655
|
+
|
|
656
|
+
|
|
657
|
+
def number_to_engineering_notation(number):
|
|
658
|
+
"""Convert the given number to engineering notation."""
|
|
659
|
+
import decimal
|
|
660
|
+
|
|
661
|
+
decimal_form = decimal.Decimal(number)
|
|
662
|
+
return decimal_form.normalize().to_eng_string()
|
|
663
|
+
|
|
664
|
+
|
|
665
|
+
def hex_get_bytes(hex_number, number_of_bytes):
|
|
666
|
+
length = len(hex(hex_number)) - 2
|
|
667
|
+
hex_num_bytes = length / 2
|
|
668
|
+
|
|
669
|
+
if (hex_num_bytes < number_of_bytes) or (hex_num_bytes == number_of_bytes):
|
|
670
|
+
return hex(hex_number)
|
|
671
|
+
else:
|
|
672
|
+
hex_number = hex_number >> math.floor((8 * (hex_num_bytes - number_of_bytes)))
|
|
673
|
+
final_hex = hex(hex_number)
|
|
674
|
+
return final_hex
|