Created
January 11, 2024 06:20
-
-
Save loopyd/c6fb7431be59e56ab7393c861aed8281 to your computer and use it in GitHub Desktop.
[python] Number list tool
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import os | |
import sys | |
from typing import Callable, Sequence, Union | |
from argparse import ArgumentParser, Namespace | |
def filter_number_list(items: list[int | float], min_value: int | float = None, max_value: int | float = None, unique_values: bool = False, sort_order: str = "ascending") -> list[int | float]: | |
""" | |
Filter a list of numbers. | |
Arguments: | |
- items (list[float | int]): List of numbers, could be either float or int. | |
- min_value (int | float, optional): Minimum value. Defaults to None. | |
- max_value (int | float, optional): Maximum value. Defaults to None. | |
- unique_values (bool, optional): Unique values only. Defaults to False. | |
- sort_order (str, optional): Sort order. Defaults to "ascending". | |
Returns: | |
- list[float | int]: List of numbers, could be either float or int. | |
""" | |
if unique_values is True: | |
items = list(set(items)) | |
if min_value is not None: | |
items = [x for x in items if x >= min_value] | |
if max_value is not None: | |
items = [x for x in items if x <= max_value] | |
if sort_order == "ascending": | |
items.sort(reverse=False if sort_order == "ascending" else True) | |
return items | |
def serialize_number_list(items: list[int | float], delim: str = ',', line_terminator: str = '\n', items_per_line: int = 10) -> str: | |
""" | |
Serialize a list of numbers to a string. | |
Arguments: | |
- items (list[float | int]): List of numbers, could be either float or int. | |
- delim (str, optional): Delimiter. Defaults to a single space. | |
- line_terminator (str, optional): Line terminator. Defaults to "\n" for Unix-like systems. | |
- items_per_line (int, optional): Number of items per line. Defaults to 10. | |
Returns: | |
- str: Serialized list of numbers. | |
""" | |
return line_terminator.strip().join([delim.join([str(x) for x in items[i:i+items_per_line]]) for i in range(0, len(items), items_per_line)]) | |
def deserialize_number_list(serialized: str, delim: str = ',', line_terminator: str = '\n') -> list[int | float]: | |
""" | |
Deserialize a list of numbers from a string. | |
Arguments: | |
- serialized (str): Serialized list of numbers. | |
- delim (str, optional): Delimiter. Defaults to a single space. | |
- line_terminator (str, optional): Line terminator. Defaults to "\\n" for Unix-like systems. | |
Returns: | |
- list[float | int]: Deserialized list of numbers. | |
""" | |
return [x for line in serialized.split(line_terminator) for x in map(lambda x: int(x) if x.isdigit() else float(x), line.strip().split(delim))] | |
def load_number_list(file_path: str, delim: str = ' ', line_terminator: str = '\n', filter_func: Callable = None, *_filterargs, **_filterwargs) -> Union[list[int | float], None]: | |
""" | |
Get a list of numbers from a file. | |
Arguments: | |
- file_path (str): Path to the file. | |
- delim (str, optional): Delimiter. Defaults to a single space. | |
- line_terminator (str, optional): Line terminator. Defaults to "\n" for Unix-like systems. | |
- filter_func (Callable): Filter function. Defaults to None. | |
- *_filterargs (Any): Filter function arguments. | |
- **_filterkwargs (Any): Filter function keyword arguments. | |
Returns: | |
- Union[list[float | int]]: List of numbers, could be either float or int, or None if an error occurred. | |
""" | |
try: | |
if not os.path.exists(file_path): | |
raise FileNotFoundError(f"File not found: {file_path}") | |
file_contents = None | |
with open(file=file_path, mode='r') as file: | |
file_contents = f'{delim}'.join([x.strip() for x in file.readlines() if x.strip() != '']) | |
if file_contents is None or len(file_contents) == 0: | |
raise Exception(f"File is empty: {file_path}") | |
items = deserialize_number_list(serialized=file_contents, delim=delim, line_terminator=line_terminator) | |
if filter_func is not None: | |
items = [x for x in filter_func(items=items, *_filterargs, **_filterwargs)] | |
return items | |
except Exception as e: | |
print(f"Error: {file_path}: {e}", file=sys.stderr) | |
return None | |
def save_number_list(file_path: str, items: list[int | float], delim: str = ' ', line_terminator: str = '\n', items_per_line: int = 10, filter_func: Callable = None, overwrite: bool = False, *_filterargs, **_filterkwargs) -> Union[None, list[int | float]]: | |
""" | |
Save a list of numbers to a file. | |
Arguments: | |
- file_path (str): Path to the file. | |
- items (list[float | int]): List of numbers, could be either float or int. | |
- delim (str, optional): Delimiter. Defaults to a single space. | |
- line_terminator (str, optional): Line terminator. Defaults to "\n" for Unix-like systems. | |
- items_per_line (int, optional): Number of items per line. Defaults to 10. | |
- overwrite (bool, optional): Overwrite the file if it exists. Defaults to False. | |
- filter_func (Callable): Filter function. Defaults to None. | |
- *_filterargs (Any): Filter function arguments. | |
- **_filterkwargs (Any): Filter function keyword arguments. | |
Returns: | |
- list[float | int]: Processed List of numbers with the optional filter_func applied, or None if an error occurred. | |
""" | |
try: | |
if os.path.exists(file_path) and overwrite is False: | |
raise FileExistsError(f"File already exists: {file_path}") | |
if filter_func is not None: | |
items = [x for x in filter_func(items, *_filterargs, **_filterkwargs)] | |
file_contents = serialize_number_list(items=items, delim=delim, line_terminator=line_terminator, items_per_line=items_per_line) | |
with open(file=file_path, mode='w') as file: | |
for line in file_contents.split(line_terminator): | |
file.write(f'{line.strip()}\n') | |
return items | |
except Exception as e: | |
print(f"Error: {file_path}: {e}") | |
return None | |
def parse_args(args: Sequence[str] = None) -> Union[None, Namespace]: | |
""" | |
Parse arguments. | |
Arguments: | |
- args (Sequence[str], optional): Arguments. Defaults to None. | |
Returns: | |
- Namespace: Parsed arguments as a Namespace object, or None if an error occurred. | |
""" | |
arg_parser = ArgumentParser( | |
prog="list_tool.py", | |
description="Python program to work on a list of numbers.", | |
epilog="Created by DeityDurg#4733 | Python Discord 2024", | |
allow_abbrev=True, | |
add_help=False, | |
exit_on_error=False | |
) | |
core_group = arg_parser.add_argument_group("Core") | |
core_group.add_argument("-h", "--help", action="help", help="Show this help message and exit.") | |
core_group.add_argument("-v", "--version", action="version", version="%(prog)s 1.0.0", help="Show program's version number and exit.") | |
options_group = arg_parser.add_argument_group("File Options") | |
options_group.add_argument("-i", "--input", type=str, metavar="PATH", required=True, dest="input_path", help="File to work with.") | |
options_group.add_argument("-o", "--output", type=str, metavar="PATH", dest="output_path", help="Save the list after processing to a file.") | |
options_group.add_argument("-p", "--print", action="store_true", default=False, dest="print", help="Print the list after processing to stdout.") | |
options_group.add_argument("-x", "--overwrite", action="store_true", default=False, dest="overwrite", help="Overwrite the file if it exists when saving.") | |
serialize_group = arg_parser.add_argument_group("Serialization Options") | |
serialize_group.add_argument("-d", "--item-delimiter", type=str, metavar="STR", default=" ", dest="item_delimiter", help="Item delimiter.") | |
serialize_group.add_argument("-t", "--line-terminator", type=str, metavar="STR", default='\n', dest="line_terminator", help="Line terminator.") | |
serialize_group.add_argument("-P", "--items-per-line", type=int, metavar="N", default=10, dest="items_per_line", help="Number of items per line, only used when saving/serializing.") | |
filter_group = arg_parser.add_argument_group("Filter") | |
filter_group.add_argument("-m", "--min-value", type=int, metavar="N", default=None, dest="min_value", help="Minimum value.") | |
filter_group.add_argument("-M", "--max-value", type=int, metavar="N", default=None, dest="max_value", help="Maximum value.") | |
filter_group.add_argument("-u", "--unique", action="store_true", default=False, dest="unique", help="Unique values only.") | |
filter_group.add_argument("-O", "--sort-order", metavar="ORDER", type=str, choices=["ascending", "descending", "none"], default="none", dest="sort_order", help="Sort order, can be either ascending, descending or none to skip sorting.") | |
namespace = arg_parser.parse_args(args) | |
if namespace.min_value is not None and namespace.max_value is not None and namespace.min_value > namespace.max_value: | |
print("Error: --min-value must be less than or equal to --max-value", sys.stderr) | |
return None | |
if namespace.min_value is None and namespace.max_value is not None: | |
print("Error: Cannot use --max-value without --min-value", sys.stderr) | |
return None | |
if namespace.max_value is None and namespace.min_value is not None: | |
print("Error: Cannot use --min-value without --max-value", sys.stderr) | |
return None | |
if namespace.output_path is None and namespace.overwrite is True: | |
print("Error: Cannot use --overwrite without using --save", sys.stderr) | |
return None | |
return namespace | |
def main(args: Sequence[str] = sys.argv[:1]) -> int: | |
""" | |
Main function. | |
Arguments: | |
- args (Sequence[str], optional): Program Arguments. Defaults to sys.argv[:1]. | |
Returns: | |
- int: Exit code. | |
""" | |
namespace = parse_args(args) | |
if namespace is None: | |
return 2 | |
filter_dict = { | |
'min_value': namespace.min_value, | |
'max_value': namespace.max_value, | |
'unique_values': namespace.unique, | |
'sort_order': namespace.sort_order | |
} | |
items = load_number_list( | |
file_path=namespace.input_path, | |
delim=namespace.item_delimiter, | |
line_terminator=namespace.line_terminator, | |
filter_func=filter_number_list, | |
**filter_dict | |
) | |
if items is None: | |
return 3 | |
print(f"Loaded {len(items)} items from {namespace.input_path}", file=sys.stderr) | |
if namespace.output_path is not None: | |
items = save_number_list( | |
file_path=namespace.output_path, | |
items=items, | |
delim=namespace.item_delimiter, | |
line_terminator=namespace.line_terminator, | |
items_per_line=namespace.items_per_line, | |
filter_func=None, | |
overwrite=namespace.overwrite, | |
**filter_dict | |
) | |
if items is None: | |
return 4 | |
print(f"Saved {len(items)} items to {namespace.output_path}", file=sys.stderr) | |
if namespace.print is True: | |
_ = [print(line, file=sys.stdout) for line in serialize_number_list( | |
items=items, | |
delim=namespace.item_delimiter, | |
line_terminator=namespace.line_terminator, | |
items_per_line=namespace.items_per_line | |
).split( | |
sep=f'{namespace.line_terminator}' | |
)] | |
return 0 | |
if __name__=="__main__": | |
exit_code = main(sys.argv[1:]) | |
if exit_code is None or exit_code != 0: | |
print("Program exited with status code {exit_code}", file=sys.stderr) | |
else: | |
print("Program ran successfully.", file=sys.stderr) | |
exit(exit_code) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment