From a7e4b2326a74067404339b1147c1ff40568ee4c0 Mon Sep 17 00:00:00 2001 From: Manuel Di Lullo <39048927+manueldilullo@users.noreply.github.com> Date: Sun, 1 May 2022 11:45:08 +0200 Subject: [PATCH] Add prefix conversions for strings (#5453) * First commit for add_prefix_conversion * Class names in CamelCase, str.format() to f-string * Fixed following pre-commit guidelines * solved issues with mypy and enum.Enum * Rename add_prefix_conversion.py to prefix_conversions_string.py Co-authored-by: John Law --- conversions/prefix_conversions_string.py | 121 +++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 conversions/prefix_conversions_string.py diff --git a/conversions/prefix_conversions_string.py b/conversions/prefix_conversions_string.py new file mode 100644 index 000000000..3255eae6f --- /dev/null +++ b/conversions/prefix_conversions_string.py @@ -0,0 +1,121 @@ +""" +* Author: Manuel Di Lullo (https://github.com/manueldilullo) +* Description: Convert a number to use the correct SI or Binary unit prefix. + +Inspired by prefix_conversion.py file in this repository by lance-pyles + +URL: https://en.wikipedia.org/wiki/Metric_prefix#List_of_SI_prefixes +URL: https://en.wikipedia.org/wiki/Binary_prefix +""" + +from __future__ import annotations + +from enum import Enum, unique +from typing import Type, TypeVar + +# Create a generic variable that can be 'Enum', or any subclass. +T = TypeVar("T", bound="Enum") + + +@unique +class BinaryUnit(Enum): + yotta = 80 + zetta = 70 + exa = 60 + peta = 50 + tera = 40 + giga = 30 + mega = 20 + kilo = 10 + + +@unique +class SIUnit(Enum): + yotta = 24 + zetta = 21 + exa = 18 + peta = 15 + tera = 12 + giga = 9 + mega = 6 + kilo = 3 + hecto = 2 + deca = 1 + deci = -1 + centi = -2 + milli = -3 + micro = -6 + nano = -9 + pico = -12 + femto = -15 + atto = -18 + zepto = -21 + yocto = -24 + + @classmethod + def get_positive(cls: Type[T]) -> dict: + """ + Returns a dictionary with only the elements of this enum + that has a positive value + >>> from itertools import islice + >>> positive = SIUnit.get_positive() + >>> inc = iter(positive.items()) + >>> dict(islice(inc, len(positive) // 2)) + {'yotta': 24, 'zetta': 21, 'exa': 18, 'peta': 15, 'tera': 12} + >>> dict(inc) + {'giga': 9, 'mega': 6, 'kilo': 3, 'hecto': 2, 'deca': 1} + """ + return {unit.name: unit.value for unit in cls if unit.value > 0} + + @classmethod + def get_negative(cls: Type[T]) -> dict: + """ + Returns a dictionary with only the elements of this enum + that has a negative value + @example + >>> from itertools import islice + >>> negative = SIUnit.get_negative() + >>> inc = iter(negative.items()) + >>> dict(islice(inc, len(negative) // 2)) + {'deci': -1, 'centi': -2, 'milli': -3, 'micro': -6, 'nano': -9} + >>> dict(inc) + {'pico': -12, 'femto': -15, 'atto': -18, 'zepto': -21, 'yocto': -24} + """ + return {unit.name: unit.value for unit in cls if unit.value < 0} + + +def add_si_prefix(value: float) -> str: + """ + Function that converts a number to his version with SI prefix + @input value (an integer) + @example: + >>> add_si_prefix(10000) + '10.0 kilo' + """ + prefixes = SIUnit.get_positive() if value > 0 else SIUnit.get_negative() + for name_prefix, value_prefix in prefixes.items(): + numerical_part = value / (10 ** value_prefix) + if numerical_part > 1: + return f"{str(numerical_part)} {name_prefix}" + return str(value) + + +def add_binary_prefix(value: float) -> str: + """ + Function that converts a number to his version with Binary prefix + @input value (an integer) + @example: + >>> add_binary_prefix(65536) + '64.0 kilo' + """ + for prefix in BinaryUnit: + numerical_part = value / (2 ** prefix.value) + if numerical_part > 1: + return f"{str(numerical_part)} {prefix.name}" + return str(value) + + +if __name__ == "__main__": + import doctest + + doctest.testmod()