Skip to content

Commit e6ace5c

Browse files
AlfredChesterMr-Python-in-ChinaCopilot
authored
Added range query. (#158)
* Added range query. * Import range query from __init__.py * Formatted code according to yapf_style.cfg * Fixed query io. * Remove the last endl in to_str * Fixed range_query_test.py * write some doc for query.py * Fix range_query_test.py * Initialize position_range in cyaron/query.py:get_one_query when nothing is given. * Set up all supported versions of pythons. * Use older version of poerty-core * Fixed expected time complexity. * Use random.sample to enhance performance. Removed unused parameter in noipstyle.py * Rollback to while loop to enhance performance. * Rollback test.yml * Set TEST_LEN to 20000 to save time. * Support weight generator in range query. * Fixed test for weighted range query * Again fixed tests for weighted range query * Fixed mismodify to test.yml. * Added frequency of big queries query.py * Updated test.yml. * Fixed a stupid mistake. * improve type hints * test python 3.6 on windows * fix docs Co-authored-by: Copilot <[email protected]> --------- Co-authored-by: Mr. Python <[email protected]> Co-authored-by: Copilot <[email protected]>
1 parent 7056a5a commit e6ace5c

File tree

7 files changed

+378
-16
lines changed

7 files changed

+378
-16
lines changed

.github/workflows/test.yml

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,26 +8,27 @@ jobs:
88
build:
99
strategy:
1010
matrix:
11-
platform: [ubuntu-20.04, windows-latest]
11+
platform: [ubuntu-22.04, windows-latest]
1212
runs-on: ${{ matrix.platform }}
1313
steps:
1414
- uses: actions/checkout@v3
15-
- name: Set up Python 3.6
15+
- name: Set up Python 3.6 (Windows)
16+
if: matrix.platform == 'windows-latest'
1617
uses: actions/setup-python@v4
1718
with:
18-
python-version: '3.6'
19+
python-version: "3.6"
1920
- name: Set up Python 3.8
2021
uses: actions/setup-python@v4
2122
with:
22-
python-version: '3.8'
23+
python-version: "3.8"
2324
- name: Set up Python 3.10
2425
uses: actions/setup-python@v4
2526
with:
26-
python-version: '3.10'
27+
python-version: "3.10"
2728
- name: Set up Python 3.12
2829
uses: actions/setup-python@v4
2930
with:
30-
python-version: '3.12'
31+
python-version: "3.12"
3132
- name: Install dependencies
3233
run: |
3334
python -m pip install --upgrade pip

cyaron/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
from random import choice, randint, random, randrange, uniform
1010

11-
#from .visual import visualize
11+
# from .visual import visualize
1212
from . import log
1313
from .compare import Compare
1414
from .consts import *
@@ -21,3 +21,4 @@
2121
from .string import String
2222
from .utils import *
2323
from .vector import Vector
24+
from .query import RangeQuery

cyaron/graders/noipstyle.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,18 @@ def noipstyle(content, std):
2121
i + 1, j + 1, content_lines[i][j:j + 5],
2222
std_lines[i][j:j + 5]))
2323
if len(std_lines[i]) > len(content_lines[i]):
24-
return False, TextMismatch(content, std,
25-
'Too short on line {}.', i + 1,
26-
j + 1, content_lines[i][j:j + 5],
27-
std_lines[i][j:j + 5])
24+
return False, TextMismatch(
25+
content,
26+
std,
27+
'Too short on line {}.',
28+
i + 1,
29+
)
2830
if len(std_lines[i]) < len(content_lines[i]):
29-
return False, TextMismatch(content, std,
30-
'Too long on line {}.', i + 1,
31-
j + 1, content_lines[i][j:j + 5],
32-
std_lines[i][j:j + 5])
31+
return False, TextMismatch(
32+
content,
33+
std,
34+
'Too long on line {}.',
35+
i + 1,
36+
)
3337

3438
return True, None

cyaron/query.py

Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
"""
2+
This module provides a `RangeQuery` class for generating queries
3+
based on limits of each dimension.
4+
5+
Classes:
6+
RangeQueryRandomMode: Enum to control how random range endpoints are generated.
7+
RangeQuery: A class for generating random queries.
8+
9+
Usage:
10+
n = randint(1, 10)
11+
q = randint(1, 10)
12+
Q = RangeQuery.random(q, [(1, n)])
13+
io.input_writeln(Q)
14+
"""
15+
16+
import random
17+
from enum import IntEnum
18+
from typing import Optional, Union, Tuple, List, Callable, TypeVar, overload, Generic, Any, Sequence
19+
20+
from .utils import list_like
21+
22+
23+
class RangeQueryRandomMode(IntEnum):
24+
"""Control how random range endpoints are generated for range queries."""
25+
LESS = 0 # disallow l = r
26+
ALLOW_EQUAL = 1 # allow l = r
27+
28+
29+
WeightT = TypeVar('WeightT', bound=Tuple[Any, ...])
30+
31+
32+
class RangeQuery(Generic[WeightT], Sequence[Tuple[List[int], List[int],
33+
WeightT]]):
34+
"""A class for generating random queries."""
35+
result: List[Tuple[List[int], List[int], WeightT]] # Vector L, R, weights.
36+
37+
def __init__(self):
38+
self.result = []
39+
40+
def __len__(self):
41+
return len(self.result)
42+
43+
@overload
44+
def __getitem__(self, item: int) -> Tuple[List[int], List[int], WeightT]:
45+
...
46+
47+
@overload
48+
def __getitem__(self,
49+
item: slice) -> List[Tuple[List[int], List[int], WeightT]]:
50+
...
51+
52+
def __getitem__(self, item: Union[int, slice]):
53+
return self.result[item]
54+
55+
def __str__(self):
56+
"""__str__(self) -> str
57+
Return a string to output the queries.
58+
The string contains all the queries with l and r in a row, splits with "\\n".
59+
"""
60+
return self.to_str()
61+
62+
def to_str(self):
63+
"""
64+
Return a string to output the queries.
65+
The string contains all the queries with l and r (and w if generated) in a row, splits with "\\n".
66+
"""
67+
res = ''
68+
for l, r, w in self.result:
69+
l_to_str = [str(x) for x in l]
70+
r_to_str = [str(x) for x in r]
71+
w_to_str = [str(x) for x in w]
72+
res += ' '.join(l_to_str) + ' ' + ' '.join(r_to_str)
73+
if len(w_to_str) > 0:
74+
res += ' ' + ' '.join(w_to_str)
75+
res += '\n'
76+
return res[:-1] # remove the last '\n'
77+
78+
@staticmethod
79+
@overload
80+
def random(
81+
num: int = 1,
82+
position_range: Optional[Sequence[Union[int, Tuple[int, int]]]] = None,
83+
*,
84+
mode: RangeQueryRandomMode = RangeQueryRandomMode.ALLOW_EQUAL,
85+
weight_generator: None = None,
86+
big_query: float = 0.2,
87+
) -> "RangeQuery[Tuple[()]]":
88+
...
89+
90+
@staticmethod
91+
@overload
92+
def random(
93+
num: int = 1,
94+
position_range: Optional[Sequence[Union[int, Tuple[int, int]]]] = None,
95+
*,
96+
mode: RangeQueryRandomMode = RangeQueryRandomMode.ALLOW_EQUAL,
97+
weight_generator: Callable[[int, List[int], List[int]], WeightT],
98+
big_query: float = 0.2,
99+
) -> "RangeQuery[WeightT]":
100+
...
101+
102+
@staticmethod
103+
def random(
104+
num: int = 1,
105+
position_range: Optional[Sequence[Union[int, Tuple[int, int]]]] = None,
106+
*,
107+
mode: RangeQueryRandomMode = RangeQueryRandomMode.ALLOW_EQUAL,
108+
weight_generator: Optional[Callable[[int, List[int], List[int]],
109+
WeightT]] = None,
110+
big_query: float = 0.2,
111+
):
112+
"""
113+
Generate `num` random queries with dimension limit.
114+
Args:
115+
num: the number of queries
116+
position_range: a list of limits for each dimension
117+
single number x represents range [1, x]
118+
list [x, y] or tuple (x, y) represents range [x, y]
119+
mode: the mode queries generate, see Enum Class RangeQueryRandomMode
120+
weight_generator: A function that generates the weights for the queries. It should:
121+
- Take the index of query (starting from 1), starting and ending positions as input.
122+
- Return a list of weights of any length.
123+
big_query: a float number representing the probability for generating big queries.
124+
"""
125+
ret = RangeQuery()
126+
127+
for i in range(num):
128+
ret.result.append(
129+
RangeQuery.get_one_query(position_range,
130+
big_query=big_query,
131+
mode=mode,
132+
weight_generator=weight_generator,
133+
index=i + 1))
134+
return ret
135+
136+
@staticmethod
137+
@overload
138+
def get_one_query(
139+
position_range: Optional[Sequence[Union[int, Tuple[int,
140+
int]]]] = None,
141+
*,
142+
big_query: float = 0.2,
143+
mode: RangeQueryRandomMode = RangeQueryRandomMode.ALLOW_EQUAL,
144+
weight_generator: None = None,
145+
index: int = 1) -> Tuple[List[int], List[int], Tuple[()]]:
146+
...
147+
148+
@staticmethod
149+
@overload
150+
def get_one_query(
151+
position_range: Optional[Sequence[Union[int, Tuple[int,
152+
int]]]] = None,
153+
*,
154+
big_query: float = 0.2,
155+
mode: RangeQueryRandomMode = RangeQueryRandomMode.ALLOW_EQUAL,
156+
weight_generator: Callable[[int, List[int], List[int]], WeightT],
157+
index: int = 1) -> Tuple[List[int], List[int], WeightT]:
158+
...
159+
160+
@staticmethod
161+
def get_one_query(
162+
position_range: Optional[Sequence[Union[int, Tuple[int,
163+
int]]]] = None,
164+
*,
165+
big_query: float = 0.2,
166+
mode: RangeQueryRandomMode = RangeQueryRandomMode.ALLOW_EQUAL,
167+
weight_generator: Optional[Callable[[int, List[int], List[int]],
168+
WeightT]] = None,
169+
index: int = 1):
170+
"""
171+
Generate a pair of query lists (query_l, query_r, w) based on the given position ranges and mode.
172+
Args:
173+
position_range (Optional[List[Union[int, Tuple[int, int]]]]): A list of position ranges. Each element can be:
174+
- An integer, which will be treated as a range from 1 to that integer.
175+
- A tuple of two integers, representing the lower and upper bounds of the range.
176+
mode (RangeQueryRandomMode): The mode for generating the queries. It can be:
177+
- RangeQueryRandomMode.ALLOW_EQUAL: Allow the generated l and r to be equal.
178+
- RangeQueryRandomMode.LESS: Ensure that l and r are not equal.
179+
weight_generator: A function that generates the weights for the queries. It should:
180+
- Take the index of query (starting from 1), starting and ending positions as input.
181+
- Return a list of weights of any length.
182+
Returns:
183+
Tuple[List[int], List[int]]: A tuple containing two lists:
184+
- query_l: A list of starting positions.
185+
- query_r: A list of ending positions.
186+
Raises:
187+
ValueError: If the upper-bound is smaller than the lower-bound.
188+
ValueError: If the mode is set to less but the upper-bound is equal to the lower-bound.
189+
"""
190+
if position_range is None:
191+
position_range = [10]
192+
193+
dimension = len(position_range)
194+
query_l: List[int] = []
195+
query_r: List[int] = []
196+
for i in range(dimension):
197+
cur_range: Tuple[int, int]
198+
pr = position_range[i]
199+
if isinstance(pr, int):
200+
cur_range = (1, pr)
201+
elif len(pr) == 1:
202+
cur_range = (1, pr[0])
203+
else:
204+
cur_range = pr
205+
206+
if cur_range[0] > cur_range[1]:
207+
raise ValueError(
208+
"upper-bound should be larger than lower-bound")
209+
if mode == RangeQueryRandomMode.LESS and cur_range[0] == cur_range[
210+
1]:
211+
raise ValueError(
212+
"mode is set to less but upper-bound is equal to lower-bound"
213+
)
214+
215+
if random.random() < big_query:
216+
# Generate a big query
217+
cur_l = cur_range[1] - cur_range[0] + 1
218+
lb = max(2 if mode == RangeQueryRandomMode.LESS else 1,
219+
cur_l // 2)
220+
ql = random.randint(lb, cur_l)
221+
l = random.randint(cur_range[0], cur_range[1] - ql + 1)
222+
r = l + ql - 1
223+
else:
224+
l = random.randint(cur_range[0], cur_range[1])
225+
r = random.randint(cur_range[0], cur_range[1])
226+
# Expected complexity is O(1)
227+
# We can use random.sample, But it's actually slower according to benchmarks.
228+
while mode == RangeQueryRandomMode.LESS and l == r:
229+
l = random.randint(cur_range[0], cur_range[1])
230+
r = random.randint(cur_range[0], cur_range[1])
231+
if l > r:
232+
l, r = r, l
233+
234+
query_l.append(l)
235+
query_r.append(r)
236+
if weight_generator is None:
237+
return (query_l, query_r, ())
238+
return (query_l, query_r, weight_generator(index, query_l, query_r))

cyaron/tests/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@
55
from .compare_test import TestCompare
66
from .graph_test import TestGraph
77
from .vector_test import TestVector
8+
from .range_query_test import TestRangeQuery
89
from .general_test import TestGeneral

0 commit comments

Comments
 (0)