Cope 2.5.0
My personal "standard library" of all the generally useful code I've written for various projects over the years
Loading...
Searching...
No Matches
misc.py
1"""
2A bunch of miscellaneous functions and classes that might be useful
3"""
4__version__ = '1.0.0'
5
6import re
7from random import randint
8import os
9import io
10import sys
11from typing import *
12# from .debugging import get_metadata
13from itertools import chain
14from inspect import isgenerator
15from rich import print as rprint
16from rich.panel import Panel
17import random
18from rich.console import Console
19
20def available(*args, null=None) -> list:
21 """ Of the parameters passed, returns the parameters which are not `null` """
22 return list(filter(lambda i: i != null, args))
23
24def only1(*args, null=None) -> bool:
25 """ Returns true only if there is only 1 non-`null` parameter """
26 return len(available(*args, null=null)) == 1
27
28def interpret_percentage(percentage:Union[int, float]) -> float:
29 if isinstance(percentage, bool):
30 return float(percentage)
31 elif percentage > 1:
32 return percentage / 100
33 return percentage
34
35def percent(percentage:Union[int, float]):
36 ''' Usage:
37 if (percent(50)):
38 <code that has a 50% chance of running>
39 NOTE: .5 works as well as 50
40 '''
41 return randint(1, 100) < interpret_percentage(percentage)*100
42
43def randbool() -> bool:
44 """ Returns, randomly, either True or False """
45 return bool(randint(0, 1))
46
47def close_enough(a, b, tolerance):
48 """ Returns True if a is within tolerance range of b """
49 return a <= b + tolerance and a >= b - tolerance
50
51def closest(target:SupportsInt, compare:Iterable[SupportsInt], index=False) -> int:
52 """ Finds the value in `compare` that is closest to `target`.
53 Returns the index (if `index` is True), or the value.
54 Uses the - operator.
55 If there are multiple closest values in the list, it returns the first one
56 """
57 val = min(compare, key=lambda i: abs(target - i))
58 if index:
59 return compare.index(val)
60 return val
61
62def furthest(target:SupportsInt, compare:Iterable[SupportsInt], index=False) -> int:
63 """ Finds the value in `compare` that is furthest from `target`.
64 Returns the index (if `index` is True), or the value.
65 If there are multiple furthest values in the list, it returns the first one
66 Uses the - operator.
67 """
68 val = max(compare, key=lambda i: abs(target - i))
69 if index:
70 return compare.index(val)
71 return val
72
73def isPowerOf2(x:int) -> bool:
74 """ Returns true if x is a power of 2 """
75 return (x != 0) and ((x & (x - 1)) == 0)
76
77def between(target, start, end, left_open=False, right_open=False) -> bool:
78 """ Returns True if `target` is between start and end """
79 return (target >= start if left_open else target > start) and \
80 (target <= end if right_open else target < end)
81
82def insert_str(string:str, index:int, inserted:str) -> str:
83 """ Returns the string with `inserted` inserted into `string` at `index` """
84 return string[:index] + inserted + string[index+1:]
85
86def constrain(val, low, high):
87 """ Constrains `val` to be within `low` and `high` """
88 return min(high, max(low, val))
89
90def translate(
91 value:SupportsAbs,
92 from_start:SupportsAbs, from_end:SupportsAbs,
93 to_start:SupportsAbs, to_end:SupportsAbs
94 ) -> SupportsAbs:
95 """ Proportionally maps `value` from being within the `from` range to the `to` range """
96 return ((abs(value - from_start) / abs(from_end - from_start)) * abs(to_end - to_start)) + to_start
97
98def frange(start:float, stop:float, skip:float=1.0, accuracy:int=10000000000000000):
99 # return (x / accuracy for x in range(int(start*accuracy), int(stop*accuracy), int(skip*accuracy)))
100 for x in range(int(start*accuracy), int(stop*accuracy), int(skip*accuracy)):
101 yield x / accuracy
102
104 prompt:str='Continue?',
105 quit:bool=False,
106 quit_msg:str='Exiting...',
107 return_if_invalid:bool=False,
108 include_YN:bool=True,
109 ) -> bool:
110 """ Promt the user to confirm via terminal whether to continue or not. If given an invalid response,
111 it will continue to ask, unless `return_if_invalid` is set to True.
112 """
113 response = input(prompt + (" (y/N): " if include_YN else '')).lower()
114 if response in ('y', 'yes'):
115 return True
116 elif response in ('n', 'no'):
117 if quit:
118 print(quit_msg)
119 exit(1)
120 return False
121 else:
122 print('Invalid Input')
123 if return_if_invalid:
124 return None
125 else:
126 confirm(prompt, include_YN=include_YN, quit=quit, quit_msg=quit_msg)
127
128def cat_file(f:str) -> str:
129 """ Simply return whatever is in the given file path. Just like the Unix `cat` command. """
130 with open(f, 'r') as f:
131 return f.read()
132
133def umpteenth(i:int) -> str:
134 """ Return the string name, i.e. 1st, 2nd, 3rd, etc. for the given integer """
135 # Wow, English does not make any sense.
136 i = str(i)
137 if i[-1] == '1' and not i.endswith('11'):
138 return i + 'st'
139 elif i[-1] == '2' and not i.endswith('12'):
140 return i + 'nd'
141 elif i[-1] == '3' and not i.endswith('13'):
142 return i + 'rd'
143 else:
144 return i + 'th'
145
146def grade(percentage:Union[float, int]) -> str:
147 """ This returns the letter grade given, based on the percentage you have in a class
148 NOTE: This is one scale, that represents general accuracy. Your institution may
149 use a different scale. As far as I know, there isn't a standardized scale for letter grades.
150 """
151 # If we're given a decimal instead of a percentage
152 percentage = interpret_percentage(percentage)
153
154 if percentage < .60:
155 return 'F'
156 elif percentage < .62:
157 return 'D-'
158 elif percentage < .68:
159 return 'D'
160 elif percentage < .70:
161 return 'D+'
162 elif percentage < .73:
163 return 'C-'
164 elif percentage < .78:
165 return 'C'
166 elif percentage < .80:
167 return 'C+'
168 elif percentage < .83:
169 return 'B-'
170 elif percentage < .88:
171 return 'B'
172 elif percentage < .90:
173 return 'B+'
174 elif percentage < .93:
175 return 'A-'
176 # elif percentage <= 100:
177 else:
178 return 'A'
179
180def isiterable(obj, include_str:bool=True) -> bool:
181 """ Returns True if you can iterate over obj. Optionally excludes strings """
182 if isinstance(obj, str):
183 return include_str
184 # A tuple type, for instance has __iter__, but isn't iterable
185 if isinstance(obj, type):
186 return False
187 return (
188 isinstance(obj, Iterable) or
189 isgenerator(obj) or
190 getattr(obj, '__iter__', False) or
191 getattr(obj, '__next__', False)
192 )
193
194def ensure_iterable(iter:Iterable, cast:type=list, ensure_cast_type:bool=True):
195 """ Ensures that `iter` is an iterable, if it isn't already.
196 If `iter` is not an iterable, it'll make it one of type `cast`, and if `ensure_cast_type` is
197 True, it will cast `iter` to `cast` as well. Otherwise it returns `iter` unchanged.
198 Strings, in this context, don't count as iterables.
199 """
200 if not isiterable(iter, include_str=False):
201 print(iter)
202 print(cast)
203 return cast((iter, ))
204 else:
205 if ensure_cast_type:
206 return cast(iter)
207 else:
208 return iter
209ensureIterable = ensure_iterable
210
211# TODO Figure out how to test for non-terminating generators
212def ensure_not_iterable(iter:Iterable):
213 """ Ensures that `iter` is *not* an iterable, IF `iter` only has one element.
214 If `iter` has 0 or more than 1 elements, it returns iter unchanged.
215 This function works with generators, but they must self-terminate.
216 DO NOT PASS INFINITE ITERATORS TO THIS FUNCTION.
217 There is no good way to test for them, and they will cause an infinite loop
218 """
219 if not isiterable(iter):
220 return iter
221
222 if isgenerator(iter):
223 iter = list(iter)
224
225 if len(iter) == 1:
226 return list(iter)[0]
227
228 return iter
229ensureNotIterable = ensure_not_iterable
230
231# TODO
232# def sigfigs(num:float, sigfigs=3) -> str:
233 # """ After all the STEM classes I've taken, I *still* don't understand how sigfigs work. """
234 # NotImplemented
235
236def cp(thing=None, rnd:int=3, show=False, not_iterable=True, evalf=True):
237 """ Quick shortcut for notebooks for copying things to the clipboard in an easy way"""
238 from sympy import latex, Basic, Float
239 from clipboard import copy
240
241 if thing is None:
242 thing = _
243
244 if not_iterable:
245 thing = ensure_not_iterable(thing)
246
247 if isinstance(thing, Basic) and not isinstance(thing, Float) and not evalf:
248 copy(latex(thing))
249 if show:
250 print('latexing')
251 else:
252 try:
253 if evalf:
254 try:
255 thing = thing.evalf()
256 except: pass
257 if rnd:
258 copy(str(round(thing, rnd)))
259 if show:
260 print('rounding')
261 else:
262 raise Exception()
263 except:
264 copy(str(thing))
265 if show:
266 print('stringing')
267 return thing
268
269# TODO: Test manually
270def in_IPython(return_instance=True):
271 try:
272 import IPython
273 except ImportError:
274 return False
275 else:
276 if (instance := IPython.get_ipython()) is not None:
277 return instance if return_instance else True
278 else:
279 return False
280
281def flatten(iter:Iterable, recursive:bool=True) -> list:
282 """ Denest either 1 or all lists inside of `iter` into one big 1 dimentional list. """
283 rtn = list(chain(*[ensure_iterable(i) for i in iter]))
284 if recursive and any(map(isiterable, rtn)):
285 rtn = flatten(rtn, recursive)
286 return rtn
287
288def invert_dict(d:dict) -> dict:
289 """ Returns the dict given, but with the keys as values and the values as keys. """
290 return dict(zip(d.values(), d.keys()))
291
292# Tested manually elsewhere
293# TODO: Add tests here
295 def __init__(self, stdout=None, stderr=None):
296 if isinstance(stdout, io.TextIOBase):
297 self._stdout = stdout
298 else:
299 self._stdout = open(stdout or os.devnull, 'w')
300 if isinstance(stderr, io.TextIOBase):
301 self._stderr = stderr
302 else:
303 self._stderr = open(stderr or os.devnull, 'w')
304
305 def __enter__(self):
306 self.old_stdout, self.old_stderr = sys.stdout, sys.stderr
307 self.old_stdout.flush(); self.old_stderr.flush()
308 sys.stdout, sys.stderr = self._stdout, self._stderr
309
310 def __exit__(self, exc_type, exc_value, traceback):
311 self._stdout.flush(); self._stderr.flush()
312 self._stdout.close(); self._stderr.close()
313 sys.stdout = self.old_stdout
314 sys.stderr = self.old_stderr
315
316
317# TODO: add tests
318def run_notecards(cards:dict):
319 """ A quick function for helping you practice notecards. `cards` is a dict of
320 {card front: card back}.
321 """
322 console = Console()
323 cur_front = ''
324 cur_back = ''
325 print('Press enter for the next card, f to flip the card, b to flip back, and q to quit')
326 while (resp := input()) != 'q':
327 console.clear()
328 if resp == '':
329 while (front := random.choice(list(cards.keys()))) == cur_front: pass
330 cur_front = front
331 cur_back = cards[front]
332 rprint(Panel(front))
333 elif resp == 'f':
334 rprint(Panel(cur_back))
335 elif resp == 'b':
336 rprint(Panel(front))
bool confirm(str prompt='Continue?', bool quit=False, str quit_msg='Exiting...', bool return_if_invalid=False, bool include_YN=True)
Promt the user to confirm via terminal whether to continue or not.
Definition: misc.py:109
str insert_str(str string, int index, str inserted)
Returns the string with inserted inserted into string at index
Definition: misc.py:82
bool between(target, start, end, left_open=False, right_open=False)
Returns True if target is between start and end.
Definition: misc.py:77
str grade(Union[float, int] percentage)
This returns the letter grade given, based on the percentage you have in a class NOTE: This is one sc...
Definition: misc.py:146
bool randbool()
Returns, randomly, either True or False.
Definition: misc.py:43
int furthest(SupportsInt target, Iterable[SupportsInt] compare, index=False)
Finds the value in compare that is furthest from target.
Definition: misc.py:62
def constrain(val, low, high)
Constrains val to be within low and high
Definition: misc.py:86
def percent(Union[int, float] percentage)
Usage: if (percent(50)): <code that has a 50% chance of running> NOTE: .5 works as well as 50.
Definition: misc.py:35
list available(*args, null=None)
Of the parameters passed, returns the parameters which are not null
Definition: misc.py:20
str cat_file(str f)
Simply return whatever is in the given file path.
Definition: misc.py:128
def close_enough(a, b, tolerance)
Returns True if a is within tolerance range of b.
Definition: misc.py:47
dict invert_dict(dict d)
Returns the dict given, but with the keys as values and the values as keys.
Definition: misc.py:288
str umpteenth(int i)
Return the string name, i.e.
Definition: misc.py:133
def cp(thing=None, int rnd=3, show=False, not_iterable=True, evalf=True)
Quick shortcut for notebooks for copying things to the clipboard in an easy way.
Definition: misc.py:236
def ensure_not_iterable(Iterable iter)
Ensures that iter is not an iterable, IF iter only has one element.
Definition: misc.py:212
int closest(SupportsInt target, Iterable[SupportsInt] compare, index=False)
Finds the value in compare that is closest to target.
Definition: misc.py:51
def ensure_iterable(Iterable iter, type cast=list, bool ensure_cast_type=True)
Ensures that iter is an iterable, if it isn't already.
Definition: misc.py:194
bool isPowerOf2(int x)
Returns true if x is a power of 2.
Definition: misc.py:73
def run_notecards(dict cards)
A quick function for helping you practice notecards.
Definition: misc.py:318
bool only1(*args, null=None)
Returns true only if there is only 1 non-null parameter.
Definition: misc.py:24