Skip to content

Commit eb197fb

Browse files
committed
Merge branch 'master' of https://github.com/luogu-dev/cyaron
2 parents 01f82e0 + a918dca commit eb197fb

File tree

7 files changed

+249
-2
lines changed

7 files changed

+249
-2
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ By Luogu
1414
- 建一个随机图(简单图或者非简单图,有向图或无向图,带权图或者无权图)
1515
- 建一个随机树(链状、随机树、或者菊花图,而且可以设定树的强弱)
1616
- 生成一组允许相同或者互相不同的多维向量(可以较快速度生成10^6组、范围到10^9的向量或者数列)
17+
- 根据函数解析式生成数列
18+
- 生成一些随机多边形,并且可以求面积、周长等
19+
- 从字典生成随机字符串、单词、句子、段落
1720

1821
**快速上手指南**
1922

cyaron/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,6 @@
1111
from .utils import *
1212
from .consts import *
1313
from .vector import Vector
14+
from .polygon import Polygon
1415
from random import randint, randrange, uniform, choice, random
1516

cyaron/polygon.py

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
from .utils import *
2+
from .consts import *
3+
import random
4+
import math
5+
6+
class Polygon:
7+
def __init__(self,points=[]):
8+
if not list_like(points):
9+
raise Exception("polygon must be constructed by a list of points")
10+
self.points = points
11+
12+
def __str__(self):
13+
buf = []
14+
for point in self.points:
15+
buf.append(str(point[0]) + " " + str(point[1]))
16+
return '\n'.join(buf)
17+
18+
def perimeter(self):
19+
ans = 0
20+
for i in range(0, len(self.points)):
21+
a = self.points[i]
22+
b = self.points[(i + 1) % len(self.points)]
23+
ans = ans + math.sqrt((a[0] - b[0]) * (a[0] - b[0]) +
24+
(a[1] - b[1]) * (a[1] - b[1]))
25+
return ans
26+
27+
def area(self):
28+
ans = 0
29+
for i in range(0, len(self.points)):
30+
a = self.points[i]
31+
b = self.points[(i + 1) % len(self.points)]
32+
ans = ans + a[0] * b[1] - a[1] * b[0]
33+
if ans < 0:
34+
ans = -ans
35+
ans = ans / 2
36+
return ans
37+
38+
#generate a convex hull with n points
39+
#it's possible to have even edges
40+
@staticmethod
41+
def convex_hull(n, **kwargs):
42+
# fx, fy are functions which map [0,1] to int or float
43+
fx = kwargs.get("fx", lambda x: x)
44+
fy = kwargs.get("fy", lambda x: x)
45+
sz = n * 2
46+
result = []
47+
while len(result) < n:
48+
points = []
49+
# about 10 points will be randomized
50+
randomize_prob = int(sz / 10) + 1
51+
if randomize_prob < 10:
52+
randomize_prob = 10
53+
for i in range(0, sz):
54+
angle = random.uniform(0, 2 * PI)
55+
x = 0.5 + math.cos(angle) * 0.48
56+
y = 0.5 + math.sin(angle) * 0.48
57+
if random.randint(0, randomize_prob) == 0:
58+
x = x + random.uniform(-0.005, 0.005)
59+
y = y + random.uniform(-0.005, 0.005)
60+
points.append([fx(x), fy(y)])
61+
# compute convex hull for points and store in rst
62+
points = sorted(points)
63+
# unique
64+
tmp = []
65+
for i in range(0, len(points)):
66+
if i == 0 or points[i - 1] != points[i]:
67+
tmp.append(points[i])
68+
points = tmp
69+
st = [] # stack
70+
for i in range(0, len(points)):
71+
while len(st) >= 2:
72+
a = st[len(st) - 1]
73+
b = points[i]
74+
o = st[len(st) - 2]
75+
if (a[0] - o[0]) * (b[1] - o[1]) - \
76+
(a[1] - o[1]) * (b[0] - o[0]) >= 0:
77+
break
78+
st.pop()
79+
st.append(points[i])
80+
g = len(st) + 1
81+
for i in range(0, len(points) - 1)[::-1]:
82+
while len(st) >= g:
83+
a = st[len(st) - 1]
84+
b = points[i]
85+
o = st[len(st) - 2]
86+
if (a[0] - o[0]) * (b[1] - o[1]) - \
87+
(a[1] - o[1]) * (b[0] - o[0]) >= 0:
88+
break
89+
st.pop()
90+
st.append(points[i])
91+
st.pop()
92+
result = st
93+
sz = int(sz * 1.7) + 3 # if failed, increase size and try again
94+
ids = [i for i in range(0, len(result))]
95+
random.shuffle(ids)
96+
ids = ids[0:n]
97+
ids = sorted(ids)
98+
output = [result[ids[i]] for i in range(0, n)]
99+
poly = Polygon(output)
100+
return poly
101+
102+
# find a path from points[0] to points[1] and cross all points in [points]
103+
@staticmethod
104+
def __conquer(points):
105+
if len(points) <= 2:
106+
return points
107+
if len(points) == 3:
108+
(points[1],points[2])=(points[2],points[1])
109+
return points
110+
divide_id = random.randint(2, len(points) - 1)
111+
divide_point1 = points[divide_id]
112+
divide_k = random.uniform(0.01, 0.99)
113+
divide_point2 = [divide_k * (points[1][0] - points[0][0]) + points[0][0],
114+
divide_k * (points[1][1] - points[0][1]) + points[0][1]]
115+
# path: points[0]->points[divide]->points[1]
116+
# dividing line in the form Ax+By+C=0
117+
divide_line = [divide_point2[1] - divide_point1[1],
118+
divide_point1[0] - divide_point2[0],
119+
-divide_point1[0] * divide_point2[1]
120+
+ divide_point1[1] * divide_point2[0]]
121+
p0 = (divide_line[0] * points[0][0] + divide_line[1] * points[0][1] + divide_line[2] >= 0)
122+
p1 = (divide_line[0] * points[1][0] + divide_line[1] * points[1][1] + divide_line[2] >= 0)
123+
if p0 == p1: # the divide point isn't good enough...
124+
return Polygon.__conquer(points)
125+
s = [[], []]
126+
s[p0].append(points[0])
127+
s[p0].append(divide_point1)
128+
s[not p0].append(divide_point1)
129+
s[not p0].append(points[1])
130+
for i in range(2, len(points)):
131+
if i == divide_id:
132+
continue
133+
pt = (divide_line[0] * points[i][0] + divide_line[1] * points[i][1] + divide_line[2] >= 0)
134+
s[pt].append(points[i])
135+
pa = Polygon.__conquer(s[p0])
136+
pb = Polygon.__conquer(s[not p0])
137+
pb.pop(0)
138+
return pa + pb
139+
140+
# generate simple polygon from given points (int[2] or float[2])
141+
# O(nlogn)~O(n^2)
142+
@staticmethod
143+
def simple_polygon(points):
144+
if not list_like(points):
145+
raise Exception("source point is not a list")
146+
random.shuffle(points)
147+
if len(points) <= 3:
148+
return points
149+
# divide by points[0], points[1]
150+
divide_line = [points[1][1] - points[0][1],
151+
points[0][0] - points[1][0],
152+
-points[0][0] * points[1][1]
153+
+ points[0][1] * points[1][0]]
154+
s = [[], []]
155+
s[0].append(points[0])
156+
s[0].append(points[1])
157+
s[1].append(points[1])
158+
s[1].append(points[0])
159+
for i in range(2, len(points)):
160+
pt = (divide_line[0] * points[i][0] + divide_line[1] * points[i][1] + divide_line[2] >= 0)
161+
s[pt].append(points[i])
162+
pa = Polygon.__conquer(s[0])
163+
pb = Polygon.__conquer(s[1])
164+
pa.pop(0)
165+
pb.pop(0)
166+
poly = Polygon(pa + pb)
167+
return poly

cyaron/tests/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
from .sequence_test import TestSequence
22
from .io_test import TestIO
3-
from .str_test import TestString
3+
from .str_test import TestString
4+
from .polygon_test import TestPolygon

cyaron/tests/polygon_test.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import unittest
2+
from cyaron import Polygon,Vector
3+
class TestPolygon(unittest.TestCase):
4+
def test_convex_hull(self):
5+
hull = Polygon.convex_hull(300, fx=lambda x:int(x*100000), fy=lambda x:int(x*100000))
6+
points = hull.points
7+
points = sorted(points)
8+
# unique
9+
tmp = []
10+
for i in range(0, len(points)):
11+
if i == 0 or points[i - 1] != points[i]:
12+
tmp.append(points[i])
13+
points = tmp
14+
st = [] # stack
15+
for i in range(0, len(points)):
16+
while len(st) >= 2:
17+
a = st[len(st) - 1]
18+
b = points[i]
19+
o = st[len(st) - 2]
20+
if (a[0] - o[0]) * (b[1] - o[1]) - \
21+
(a[1] - o[1]) * (b[0] - o[0]) >= 0:
22+
break
23+
st.pop()
24+
st.append(points[i])
25+
g = len(st) + 1
26+
for i in range(0, len(points) - 1)[::-1]:
27+
while len(st) >= g:
28+
a = st[len(st) - 1]
29+
b = points[i]
30+
o = st[len(st) - 2]
31+
if (a[0] - o[0]) * (b[1] - o[1]) - \
32+
(a[1] - o[1]) * (b[0] - o[0]) >= 0:
33+
break
34+
st.pop()
35+
st.append(points[i])
36+
st.pop()
37+
self.assertEqual(len(st), len(hull.points))
38+
def test_perimeter_area(self):
39+
poly = Polygon([[0,0],[0,1],[1,1],[1,0]])
40+
self.assertEqual(poly.perimeter(),4)
41+
self.assertEqual(poly.area(),1)
42+
def test_simple_polygon(self):
43+
poly = Polygon.simple_polygon(Vector.random(300, [1000, 1000]))
44+
points = poly.points
45+
for i in range(0,len(points)):
46+
for j in range(i+2,len(points)):
47+
if j==len(points)-1 and i==0:
48+
continue
49+
a=points[i]
50+
b=points[(i+1)%len(points)]
51+
c=points[j]
52+
d=points[(j+1)%len(points)]
53+
prod=lambda x,y: x[0]*y[1]-x[1]*y[0]
54+
t1=prod([c[0]-a[0],c[1]-a[1]],[d[0]-a[0],d[1]-a[1]])\
55+
*prod([c[0]-b[0],c[1]-b[1]],[d[0]-b[0],d[1]-b[1]])
56+
t2=prod([a[0]-c[0],a[1]-c[1]],[b[0]-c[0],b[1]-c[1]])\
57+
*prod([a[0]-d[0],a[1]-d[1]],[b[0]-d[0],b[1]-d[1]])
58+
self.assertFalse(t1<=1e-9 and t2<=1e-9)

examples/test_polygon.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#!/usr/bin/env python
2+
from cyaron import *
3+
print "random convex hull of size 300:"
4+
hull=Polygon.convex_hull(300, fx=lambda x:int(x*1000), fy=lambda x:int(x*1000))
5+
print hull
6+
print "perimeter:"
7+
print hull.perimeter()
8+
print "area:"
9+
print hull.area()
10+
points = Vector.random(300, [1000, 1000])
11+
print "random simple polygon of size 300:"
12+
poly = Polygon.simple_polygon(points)
13+
print poly
14+
print "perimeter:"
15+
print poly.perimeter()
16+
print "area:"
17+
print poly.area()

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
setup(
44
name='cyaron',
5-
version='0.1.6',
5+
version='0.1.7',
66
keywords='olympic informatics luogu aqours cyaron lovelive sunshine online judge',
77
description='CYaRon: Yet Another Random Olympic-iNformatics test data generator, A library for automatically generating test data for Online Judge, Olympic Informatics or automatic application testing',
88
license='LGPLv3',

0 commit comments

Comments
 (0)