mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-07-04 16:23:04 +02:00
Replaced for loop with implicit continue with a list comprehension using walrus operator. This avoids the coverage.py limitation with partial branch detection on for loop continue statements. Coverage: 99% -> 100%
116 lines
3.5 KiB
Python
116 lines
3.5 KiB
Python
"""Randomize numbers by applying a random percentage variation."""
|
|
|
|
import contextlib
|
|
import logging
|
|
import re
|
|
import secrets
|
|
import sys
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
# Use cryptographically secure random number generator
|
|
_rng = secrets.SystemRandom()
|
|
|
|
DEFAULT_MIN_PERCENTAGE = 1
|
|
DEFAULT_MAX_PERCENTAGE = 20
|
|
|
|
|
|
def randomize_numbers(
|
|
nums: list[float],
|
|
min_pct: float = DEFAULT_MIN_PERCENTAGE,
|
|
max_pct: float = DEFAULT_MAX_PERCENTAGE,
|
|
) -> list[float]:
|
|
"""Apply random percentage variation to a list of numbers."""
|
|
result = []
|
|
for number in nums:
|
|
percentage = _rng.uniform(min_pct, max_pct) / 100
|
|
if _rng.choice([True, False]):
|
|
new_number = number + (number * percentage)
|
|
else:
|
|
new_number = number - (number * percentage)
|
|
result.append(new_number)
|
|
return result
|
|
|
|
|
|
def parse_input(text: str) -> tuple[list[float], list[int]]:
|
|
"""Parse a string of numbers and return floats with decimal counts."""
|
|
# Replace commas with dots and remove non-numeric characters
|
|
# except dots, commas, and digits
|
|
cleaned_input = re.sub(r"[^\d.,\s]", "", text).replace(",", ".")
|
|
# Split the cleaned input into individual numbers
|
|
number_strings = cleaned_input.split()
|
|
# Convert the number strings to floats, filtering out invalid ones
|
|
parsed_results = [
|
|
result
|
|
for num_str in number_strings
|
|
if (result := _parse_single_number(num_str)) is not None
|
|
]
|
|
nums = [float_num for float_num, _ in parsed_results]
|
|
decimals = [digits_count for _, digits_count in parsed_results]
|
|
return nums, decimals
|
|
|
|
|
|
def _parse_single_number(num_str: str) -> tuple[float, int] | None:
|
|
"""Parse a single number string into float and decimal count.
|
|
|
|
Args:
|
|
num_str: The number string to parse.
|
|
|
|
Returns:
|
|
Tuple of (float value, decimal count) or None if invalid.
|
|
"""
|
|
try:
|
|
float_num = float(num_str)
|
|
digits_count = len(num_str.split(".")[-1]) if "." in num_str else 0
|
|
except ValueError:
|
|
return None
|
|
return float_num, digits_count
|
|
|
|
|
|
MIN_ARGS = 2
|
|
|
|
|
|
def main() -> None:
|
|
"""Run the number randomizer from command line arguments."""
|
|
if len(sys.argv) < MIN_ARGS:
|
|
_logger.info(
|
|
"Usage: python random_digits.py <number1> <number2> ... "
|
|
"[min_percentage max_percentage]"
|
|
)
|
|
sys.exit(1)
|
|
|
|
args_string = " ".join(sys.argv[1:])
|
|
numbers, decimal_counts = parse_input(args_string)
|
|
|
|
if not numbers:
|
|
_logger.error("No valid numbers provided.")
|
|
sys.exit(1)
|
|
|
|
min_pct = DEFAULT_MIN_PERCENTAGE
|
|
max_pct = DEFAULT_MAX_PERCENTAGE
|
|
|
|
try:
|
|
if len(sys.argv) > len(numbers) + 1:
|
|
with contextlib.suppress(ValueError):
|
|
min_pct = float(sys.argv[len(numbers) + 1])
|
|
if len(sys.argv) > len(numbers) + 2:
|
|
with contextlib.suppress(ValueError):
|
|
max_pct = float(sys.argv[len(numbers) + 2])
|
|
|
|
randomized = randomize_numbers(numbers, min_pct, max_pct)
|
|
formatted_numbers = []
|
|
for i, num in enumerate(randomized):
|
|
format_str = f".{decimal_counts[i]}f"
|
|
formatted_numbers.append(float(format(num, format_str)))
|
|
|
|
_logger.info("Original numbers: %s", numbers)
|
|
_logger.info("Randomized numbers: %s", formatted_numbers)
|
|
except ValueError:
|
|
_logger.exception("Error processing numbers")
|
|
_logger.exception("Please provide valid numbers and percentages.")
|
|
sys.exit(1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|