Skip to content

Instantly share code, notes, and snippets.

@loopyd
Created January 11, 2024 06:20
Show Gist options
  • Save loopyd/c6fb7431be59e56ab7393c861aed8281 to your computer and use it in GitHub Desktop.
Save loopyd/c6fb7431be59e56ab7393c861aed8281 to your computer and use it in GitHub Desktop.
[python] Number list tool
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