Source code for aerosandbox.tools.string_formatting

import hashlib
import aerosandbox.numpy as np


[docs]def eng_string( x: float, unit: str = "", format='%.3g', si=True, add_space_after_number: bool = None, ) -> str: ''' Taken from: https://stackoverflow.com/questions/17973278/python-decimal-engineering-notation-for-mili-10e-3-and-micro-10e-6/40691220 Returns float/int value <x> formatted in a simplified engineering format - using an exponent that is a multiple of 3. Args: x: The value to be formatted. Float or int. unit: A unit of the quantity to be expressed, given as a string. Example: Newtons -> "N" format: A printf-style string used to format the value before the exponent. si: if true, use SI suffix for exponent. (k instead of e3, n instead of e-9, etc.) Examples: With format='%.2f': 1.23e-08 -> 12.30e-9 123 -> 123.00 1230.0 -> 1.23e3 -1230000.0 -> -1.23e6 With si=True: 1230.0 -> "1.23k" -1230000.0 -> "-1.23M" With unit="N" and si=True: 1230.0 -> "1.23 kN" -1230000.0 -> "-1.23 MN" ''' sign = '' if x < 0: x = -x sign = '-' elif x == 0: return format % 0 elif np.isnan(x): return "NaN" exp = int(np.floor(np.log10(x))) exp3 = exp - (exp % 3) x3 = x / (10 ** exp3) if si and exp3 >= -24 and exp3 <= 24: if exp3 == 0: suffix = "" else: suffix = 'yzafpnμm kMGTPEZY'[(exp3 + 24) // 3] if add_space_after_number is None: add_space_after_number = (unit != "") if add_space_after_number: suffix = " " + suffix + unit else: suffix = suffix + unit else: suffix = f'e{exp3}' if add_space_after_number: add_space_after_number = (unit != "") if add_space_after_number: suffix = suffix + " " + unit else: suffix = suffix + unit return f"{sign}{format % x3}{suffix}"
[docs]def latex_sci_notation_string( x: float, format='%.2e', ) -> str: """ Converts a floating-point number to a LaTeX-style formatted string. Does not include the `$$` wrapping to put you in math mode. Does not use scientific notation if the base would be zero. Examples: latex_sci_notation_string(3000) -> '3 \\times 10^{3}' """ float_str = format % x base, exponent = float_str.split("e") exponent = int(exponent) if exponent == 0: return base else: return r"{0} \times 10^{{{1}}}".format(base, exponent)
[docs]def hash_string(string: str) -> int: """ Hashes a string into a quasi-random 32-bit integer! (Based on an MD5 checksum algorithm.) Usual warnings apply: it's MD5, don't use this for anything intended to be cryptographically secure. """ md5 = hashlib.md5(string.encode('utf-8')) hash_hex = md5.hexdigest() hash_int = int(hash_hex, 16) hash_int64 = hash_int % (2 ** 32) return hash_int64
[docs]def trim_string(string: str, length: int = 80) -> str: """ Trims a string to be less than a given length. If the string would exceed the length, makes it end in ellipses ("…"). Args: string: The string to be trimmed. length: The length to trim the string to, including any ellipses that may be added. Returns: The trimmed string, including ellipses if needed. """ if len(string) > length: return string[:length - 1] + "…" else: return string
[docs]def has_balanced_parentheses(string: str, left="(", right=")") -> bool: """ Determines whether a string has matching parentheses or not. Examples: >>> has_balanced_parentheses("3 * (x + (2 ** 5))") -> True >>> has_balanced_parentheses("3 * (x + (2 ** 5)") -> False Args: string: The string to be evaluated. left: The left parentheses. Can be modified if, for example, you need to check square brackets. right: The right parentheses. Can be modified if, for example, you need to check square brackets. Returns: A boolean of whether or not the string has balanced parentheses. """ parenthesis_level = 0 for char in string: if char == left: parenthesis_level += 1 elif char == right: parenthesis_level -= 1 return parenthesis_level == 0
[docs]def wrap_text_ignoring_mathtext( text: str, width: int = 70, ) -> str: r""" Reformat the single paragraph in 'text' to fit in lines of no more than 'width' columns, and return a new string containing the entire wrapped paragraph. Tabs are expanded and other whitespace characters converted to space. Similar to `textwrap.fill`, but keeps any mathtext blocks contiguous and unaltered. Mathtext blocks are segments of `text` that are between $ markers, to indicate LaTeX-like formatting. Dollar-sign literals (\$) do not trigger Mathtext, and that is respected here as well. For example: >>> wrap_text_ignoring_mathtext() Args: text: The text to be wrapped. width: The maximum width of wrapped lines (unless break_long_words is false) Returns: A string containing the entire paragraph with line breaks as newline ("\n") characters. """ import textwrap, re # Pattern to match mathtext blocks mathtext_trigger = r"(?<!\\)(?:\\\\)*\$" # Split the text into non-mathtext parts and mathtext parts parts = re.split(mathtext_trigger, text) text_parts = [part for i, part in enumerate(parts) if i % 2 == 0] math_parts = [part for i, part in enumerate(parts) if i % 2 == 1] # Reassemble th result output = "" cursor_position = 0 while len(text_parts) + len(math_parts) > 0: try: text_part = text_parts.pop(0) contribution = textwrap.fill( text_part, width=width, initial_indent=" " * cursor_position, drop_whitespace=False, )[cursor_position:] output += contribution if "\n" in contribution: cursor_position = len(contribution.split("\n")[-1]) else: cursor_position += len(contribution) except IndexError: pass try: math_part = math_parts.pop(0) estimated_space: int = int(np.round(len(math_part) * 0.5)) if cursor_position + estimated_space < width: output += f"${math_part}$" cursor_position += estimated_space else: output += f"\n${math_part}$" cursor_position = estimated_space except IndexError: pass output = "\n".join([line.strip() for line in output.split("\n")]) return output
if __name__ == '__main__': for input in [ r"$ax^2+bx+c$", r"Photon flux $\phi$", r"Photon flux $\phi$ is given by $\phi = \frac{c}{\lambda}$", r"Earnings for 2022 $M\$/year$", r"$ax^2+bx+c$ and also $3x$" ]: print(wrap_text_ignoring_mathtext(input, width=10))