Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into pr/OranPie/137
Browse files Browse the repository at this point in the history
  • Loading branch information
Mr-Python-in-China committed Oct 12, 2024
2 parents a0700ec + c92f60d commit a0d2919
Show file tree
Hide file tree
Showing 10 changed files with 250 additions and 74 deletions.
36 changes: 36 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: Python package

on:
- push
- pull_request

jobs:
build:
strategy:
matrix:
platform: [ubuntu-20.04, windows-latest]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v3
- name: Set up Python 3.6
uses: actions/setup-python@v4
with:
python-version: '3.6'
- name: Set up Python 3.8
uses: actions/setup-python@v4
with:
python-version: '3.8'
- name: Set up Python 3.10
uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: Set up Python 3.12
uses: actions/setup-python@v4
with:
python-version: '3.12'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install tox
- name: Test with tox
run: python -m tox
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -138,3 +138,5 @@ venv/

# VS Code
.vscode

.python-version
2 changes: 1 addition & 1 deletion .pylintrc
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[MASTER]
py-version=3.5
disable=R0902,R0913,R0917
disable=R0902,R0903,R0913,R0917,R0912
50 changes: 27 additions & 23 deletions cyaron/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,43 +8,44 @@
import re
import subprocess
import tempfile
from typing import Union, overload
from typing import Union, overload, Optional
from io import IOBase
from . import log
from .utils import list_like, make_unicode


class IO:
"""Class IO: IO tool class. It will process the input and output files."""
"""IO tool class. It will process the input and output files."""

@overload
def __init__(self,
input_file: Union[IOBase, str, int, None] = None,
output_file: Union[IOBase, str, int, None] = None,
data_id: Union[str, None] = None,
input_file: Optional[Union[IOBase, str, int]] = None,
output_file: Optional[Union[IOBase, str, int]] = None,
data_id: Optional[int] = None,
disable_output: bool = False,
make_dirs: bool = False):
...

@overload
def __init__(self,
data_id: Union[str, None] = None,
file_prefix: Union[str, None] = None,
input_suffix: Union[str, None] = '.in',
output_suffix: Union[str, None] = '.out',
data_id: Optional[int] = None,
file_prefix: Optional[str] = None,
input_suffix: str = '.in',
output_suffix: str = '.out',
disable_output: bool = False,
make_dirs: bool = False):
...

def __init__(self,
input_file: Union[IOBase, str, int, None] = None,
output_file: Union[IOBase, str, int, None] = None,
data_id: Union[str, None] = None,
file_prefix: Union[str, None] = None,
input_suffix: Union[str, None] = '.in',
output_suffix: Union[str, None] = '.out',
disable_output: bool = False,
make_dirs: bool = False):
def __init__( # type: ignore
self,
input_file: Optional[Union[IOBase, str, int]] = None,
output_file: Optional[Union[IOBase, str, int]] = None,
data_id: Optional[int] = None,
file_prefix: Optional[str] = None,
input_suffix: str = '.in',
output_suffix: str = '.out',
disable_output: bool = False,
make_dirs: bool = False):
"""
Args:
input_file (optional): input file object or filename or file descriptor.
Expand Down Expand Up @@ -198,7 +199,6 @@ def __write(self, file: IOBase, *args, **kwargs):
file.write(make_unicode(arg))
if arg == "\n":
self.is_first_char[file] = True


def __clear(self, file: IOBase, pos: int = 0):
"""
Expand Down Expand Up @@ -252,6 +252,8 @@ def output_gen(self, shell_cmd, time_limit=None):
time_limit: the time limit (seconds) of the command to run.
None means infinity. Defaults to None.
"""
if self.output_file is None:
raise ValueError("Output file is disabled")
self.flush_buffer()
origin_pos = self.input_file.tell()
self.input_file.seek(0)
Expand All @@ -260,16 +262,16 @@ def output_gen(self, shell_cmd, time_limit=None):
shell_cmd,
shell=True,
timeout=time_limit,
stdin=self.input_file,
stdout=self.output_file,
stdin=self.input_file.fileno(),
stdout=self.output_file.fileno(),
universal_newlines=True,
)
else:
subprocess.check_call(
shell_cmd,
shell=True,
stdin=self.input_file,
stdout=self.output_file,
stdin=self.input_file.fileno(),
stdout=self.output_file.fileno(),
universal_newlines=True,
)
self.input_file.seek(origin_pos)
Expand All @@ -284,6 +286,8 @@ def output_write(self, *args, **kwargs):
*args: the elements to write
separator: a string used to separate every element. Defaults to " ".
"""
if self.output_file is None:
raise ValueError("Output file is disabled")
self.__write(self.output_file, *args, **kwargs)

def output_writeln(self, *args, **kwargs):
Expand Down
74 changes: 57 additions & 17 deletions cyaron/sequence.py
Original file line number Diff line number Diff line change
@@ -1,33 +1,73 @@
from .utils import *
"""
This module provides a `Sequence` class for generating sequences
based on a given formula and initial values.
Classes:
Sequence: A class for creating and managing sequences.
"""

from typing import Callable, Dict, List, Optional, Tuple, TypeVar, Union
from typing import cast as typecast

from .utils import list_like

T = TypeVar('T')


class Sequence:
"""Class Sequence: the tool class for sequences.
"""
"""A class for creating and managing sequences."""

def __init__(self, formula, initial_values=()):
"""__init__(self, formula, initial_values=() -> None
Create a sequence object.
int formula(int, function) -> the formula function ...
def __init__(self,
formula: Callable[[int, Callable[[int], T]], T],
initial_values: Union[List[T], Tuple[T, ...], Dict[int,
T]] = ()):
"""
Initialize a sequence object.
Parameters:
formula: A function that defines the formula for the sequence.
initial_values (optional): Initial values for the sequence.
Can be a list, tuple, or dictionary. Defaults to an empty tuple.
"""
if not callable(formula):
raise Exception("formula must be a function")
raise TypeError("formula must be a function")
self.formula = formula
if list_like(initial_values):
self.values = dict(enumerate(initial_values))
self.values = dict(
enumerate(
typecast(Union[List[T], Tuple[T, ...]], initial_values)))
elif isinstance(initial_values, dict):
self.values = initial_values
else:
raise Exception("Initial_values must be either a list/tuple or a dict.")
raise TypeError(
"Initial_values must be either a list/tuple or a dict.")

def __get_one(self, i):
def get_one(self, i: int):
"""
Retrieve the value at the specified index, computing it if necessary.
Args:
i (int): The index of the value to retrieve.
Returns:
The value at the specified index.
If the value at the specified index is not already computed, it will be
calculated using the provided formula and stored for future access.
"""
if i in self.values:
return self.values[i]

self.values[i] = self.formula(i, self.__get_one)
self.values[i] = self.formula(i, self.get_one)
return self.values[i]

def get(self, left_range, right_range=None):
def get(self, left_range: int, right_range: Optional[int] = None):
"""
Retrieve a sequence of elements within the specified range.
If only `left_range` is provided, a single element is retrieved.
If both `left_range` and `right_range` are provided, a list of elements
from `left_range` to `right_range` (inclusive) is retrieved.
Args:
left_range: The starting index or the single index to retrieve.
right_range (optional): The ending index for the range retrieval. Defaults to None.
Returns:
A single element if `right_range` is None, otherwise a list of elements.
"""
if right_range is None:
return self.__get_one(left_range)

return [self.__get_one(i) for i in range(left_range, right_range+1)]
return self.get_one(left_range)
return [self.get_one(i) for i in range(left_range, right_range + 1)]
1 change: 1 addition & 0 deletions cyaron/tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
from .polygon_test import TestPolygon
from .compare_test import TestCompare
from .graph_test import TestGraph
from .vector_test import TestVector
33 changes: 33 additions & 0 deletions cyaron/tests/vector_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import unittest
from cyaron.vector import *


def has_duplicates(lst: list):
return len(lst) != len(set(lst))


class TestVector(unittest.TestCase):
def test_unique_vector(self):
v = Vector.random(10**5, [10**6])
self.assertFalse(has_duplicates(list(map(lambda tp: tuple(tp), v))))
self.assertTrue(all(map(lambda v: 0 <= v[0] <= 10**6, v)))
v = Vector.random(1000, [(10**5, 10**6)])
self.assertTrue(all(map(lambda v: 10**5 <= v[0] <= 10**6, v)))
with self.assertRaises(
Exception,
msg="1st param is so large that CYaRon can not generate unique vectors",
):
v = Vector.random(10**5, [10**4])

def test_repeatable_vector(self):
v = Vector.random(10**5 + 1, [10**5], VectorRandomMode.repeatable)
self.assertTrue(all(map(lambda v: 0 <= v[0] <= 10**5, v)))
self.assertTrue(has_duplicates(list(map(lambda tp: tuple(tp), v))))
v = Vector.random(1000, [(10**5, 10**6)], VectorRandomMode.repeatable)
self.assertTrue(all(map(lambda v: 10**5 <= v[0] <= 10**6, v)))

def test_float_vector(self):
v = Vector.random(10**5, [10**5], VectorRandomMode.float)
self.assertTrue(all(map(lambda v: 0 <= v[0] <= 10**5, v)))
v = Vector.random(10**5, [(24, 25)], VectorRandomMode.float)
self.assertTrue(all(map(lambda v: 24 <= v[0] <= 25, v)))
12 changes: 9 additions & 3 deletions cyaron/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ def list_like(data):
Judge whether the object data is like a list or a tuple.
object data -> the data to judge
"""
return isinstance(data, tuple) or isinstance(data, list)
return isinstance(data, (tuple, list))


def int_like(data):
Expand All @@ -36,6 +36,7 @@ def strtolines(str):
def make_unicode(data):
return str(data)


def unpack_kwargs(funcname, kwargs, arg_pattern):
rv = {}
kwargs = kwargs.copy()
Expand All @@ -55,7 +56,12 @@ def unpack_kwargs(funcname, kwargs, arg_pattern):
except KeyError as e:
error = True
if error:
raise TypeError('{}() missing 1 required keyword-only argument: \'{}\''.format(funcname, tp))
raise TypeError(
'{}() missing 1 required keyword-only argument: \'{}\''.
format(funcname, tp))
if kwargs:
raise TypeError('{}() got an unexpected keyword argument \'{}\''.format(funcname, next(iter(kwargs.items()))[0]))
raise TypeError(
'{}() got an unexpected keyword argument \'{}\''.format(
funcname,
next(iter(kwargs.items()))[0]))
return rv
Loading

0 comments on commit a0d2919

Please sign in to comment.