Skip to content

Commit cb07486

Browse files
authored
Add Convex Hull in M4 (#5248)
1 parent a46bf5f commit cb07486

1 file changed

Lines changed: 194 additions & 0 deletions

File tree

archive/m/m4/convex-hull.m4

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
divert(-1)
2+
define(`show_usage',
3+
`Usage: please provide at least 3 x and y coordinates as separate lists (e.g. "100, 440, 210")
4+
m4exit(`1')')
5+
6+
dnl Reference: https://www.gnu.org/software/m4/manual/m4.html#index-array
7+
dnl array_get(var_name, idx)
8+
define(`array_get', `defn(format(``%s[%s]'', `$1', `$2'))')
9+
10+
dnl array_set(var_name, idx, value)
11+
define(`array_set', `define(format(``%s[%s]'', `$1', `$2'), `$3')')
12+
13+
dnl 2D versions of "array_get" and "array_set"
14+
dnl array2_get(varname, idx1, idx2)
15+
define(`array2_get', `defn(format(``%s[%s][%s]'', `$1', `$2', `$3'))')
16+
17+
dnl array2_set(varname, idx1, idx2, value)
18+
define(`array2_set', `define(format(``%s[%s][%s]'', `$1', `$2', `$3'), `$4')')
19+
20+
dnl is_valid(n)
21+
define(`is_valid', `eval(regexp(`$1', `^\s*-?[0-9]+\s*$') >= 0)')
22+
23+
dnl parse_int_list(varname, args):
24+
dnl varname[length] = 0
25+
dnl foreach arg in args:
26+
dnl if not is_valid(arg):
27+
dnl Return 0
28+
dnl varname[varname[length]] = arg
29+
dnl varname[length] = varname[length] + 1
30+
dnl Return 1
31+
define(`parse_int_list',
32+
`array_set(`$1', `length', 0)dnl
33+
_parse_int_list(`$1', $2)'dnl
34+
)
35+
define(`_parse_int_list',
36+
`ifelse(is_valid(`$2'), 0, `0',
37+
`array_set(`$1', array_get(`$1', `length'), `$2')dnl
38+
array_set(`$1', `length', incr(array_get(`$1', `length')))dnl
39+
ifelse(eval($# > 2), 1, `_parse_int_list(`$1', shift(shift($@)))', `1')'dnl
40+
)'dnl
41+
)
42+
43+
dnl form_points(x_varname, y_varname, points_varname):
44+
dnl points_varname["length"] = x_varname["length"]
45+
dnl for i = 0 to points_varname["length"] - 1:
46+
dnl points_varname[i]["x"] = x_varname[i]
47+
dnl points_varname[i]["y"] = y_varname[i]
48+
define(`form_points',
49+
`array_set(`$3', `length', array_get(`$1', `length'))dnl
50+
_form_points(`$1', `$2', `$3', 0)dnl
51+
'dnl
52+
)
53+
54+
dnl x_varname=$1, y_varname=$2, points_varname=$3, i=$4
55+
define(`_form_points',
56+
`ifelse(eval($4 < array_get(`$1', `length')), 1,
57+
`array2_set(`$3', `$4', `x', array_get(`$1', `$4'))dnl
58+
array2_set(`$3', `$4', `y', array_get(`$2', `$4'))dnl
59+
_form_points(`$1', `$2', `$3', incr($4))dnl
60+
'dnl
61+
)'dnl
62+
)
63+
64+
dnl show_points(points_varname):
65+
dnl for i = 0 to points_varname["length"] - 1
66+
dnl Output "(" + points_varname[i]["x"] + ", " + points_varname[i]["y"] + ")"
67+
define(`show_points', `_show_points(`$1', 0)')
68+
69+
dnl points_varname=$1, i=$2
70+
define(`_show_points',
71+
`ifelse(eval($2 < array_get(`$1', `length')), 1,
72+
`(array2_get(`$1', `$2', `x'), array2_get(`$1', `$2', `y'))
73+
_show_points(`$1', incr($2))dnl
74+
'dnl
75+
)'dnl
76+
)
77+
78+
dnl Find Convex Hull using Jarvis' algorithm
79+
dnl Source: https://www.geeksforgeeks.org/convex-hull-using-jarvis-algorithm-or-wrapping/
80+
dnl convex_hull(points_varname, hull_points_varname):
81+
dnl // The first point is the leftmost point with the highest y-coord in the
82+
dnl // event of a tie
83+
dnl l = find_leftmost_point(points_varname)
84+
dnl
85+
dnl // Repeat until wrapped around to first hull point
86+
dnl p = l
87+
dnl i = 0
88+
dnl do
89+
dnl // Store convex hull point
90+
dnl hull_points_varname[i]["x"] = points_varname[p]["x"]
91+
dnl hull_points_varname[i]["y"] = points_varname[p]["y"]
92+
dnl i = i + 1
93+
dnl hull_points_varname["length"] = i
94+
dnl
95+
dnl q = (p + 1) % points_varname["length"]
96+
dnl for j = 0 to points_varname["length"] - 1:
97+
dnl // If point j is counter-clockwise, then update end point (q)
98+
dnl if orientation(points_varname, p, j, q) < 0:
99+
dnl q = j
100+
dnl
101+
dnl p = q
102+
dnl while p != l
103+
define(`convex_hull',
104+
`pushdef(`l', `find_leftmost_point(`$1')')dnl
105+
pushdef(`p', l)dnl
106+
pushdef(`q', `')dnl
107+
_convex_hull_outer(`$1', `$2', 0)dnl
108+
popdef(`q')dnl
109+
popdef(`p')dnl
110+
popdef(`l')dnl
111+
'dnl
112+
)
113+
114+
dnl points_varname=$1, hull_points_varname=$2, i=$3
115+
define(`_convex_hull_outer',
116+
`array2_set(`$2', `$3', `x', array2_get(`$1', p, `x'))dnl
117+
array2_set(`$2', `$3', `y', array2_get(`$1', p, `y'))dnl
118+
array_set(`$2', `length', incr($3))dnl
119+
define(`q', eval((p + 1) % array_get(`$1', `length')))dnl
120+
_convex_hull_inner(`$1', 0)dnl
121+
define(`p', q)dnl
122+
ifelse(eval(p != l), 1, `_convex_hull_outer(`$1', `$2', incr($3))')dnl
123+
'dnl
124+
)
125+
126+
dnl points_varname=$1, j=$2
127+
define(`_convex_hull_inner',
128+
`ifelse(eval($2 < array_get(`$1', `length')), 1,
129+
`ifelse(eval(orientation(`$1', p, `$2', q) < 0), 1, `define(`q', `$2')')dnl
130+
_convex_hull_inner(`$1', incr($2))dnl
131+
'dnl
132+
)'dnl
133+
)
134+
135+
dnl Get orientation of three points
136+
dnl
137+
dnl 0 = points are in a line
138+
dnl > 0 = points are clockwise
139+
dnl < 0 = points are counter-clockwise
140+
dnl orientation(points_varname, p, q, r):
141+
dnl return (points_varname[q]["y"] - points_varname[p]["y"]) *
142+
dnl (points_varname[r]["x"] - points_varname[q]["x"]) -
143+
dnl (points_varname[q]["x"] - points_varname[p]["x"]) *
144+
dnl (points_varname[r]["y"] - points_varname[q]["y"])
145+
define(`orientation',
146+
`eval(
147+
(array2_get(`$1', `$3', `y') - array2_get(`$1', `$2', `y')) *
148+
(array2_get(`$1', `$4', `x') - array2_get(`$1', `$3', `x')) -
149+
(array2_get(`$1', `$3', `x') - array2_get(`$1', `$2', `x')) *
150+
(array2_get(`$1', `$4', `y') - array2_get(`$1', `$3', `y'))
151+
)'dnl
152+
)
153+
154+
dnl M4 does not have infinity so choose smallest integer value as minus infinity
155+
dnl and the largest integer value for infinity
156+
define(`MINUS_INF', -2147483648)
157+
define(`INF', 2147483647)
158+
159+
dnl find_leftmost_point(points_varname):
160+
dnl x_min = INF
161+
dnl y_max = MINUS_INF
162+
dnl min_idx = -1
163+
dnl for i = 0 to points_varname["length"] - 1:
164+
dnl if points_varname[i]["x"] < x_min or
165+
dnl (points_varname[i]["x"] == x_min and points_varname[i]["y"] > y_max):
166+
dnl x_min = points_varname[i]["x"]
167+
dnl y_max = points_varname[i]["y"]
168+
dnl min_idx = i
169+
dnl return min_idx
170+
define(`find_leftmost_point', `_find_leftmost_point(`$1', INF, MINUS_INF, -1, 0)')
171+
172+
dnl points_varname=$1, x_min=$2, y_max=$3, min_idx=$4, i=$5
173+
define(`_find_leftmost_point',
174+
`ifelse(eval($5 >= array_get(`$1', `length')), 1, `$4',
175+
`ifelse(eval(array2_get(`$1', `$5', `x') < $2 ||
176+
(array2_get(`$1', `$5', `x') == $2 && array2_get(`$1', `$5', `y') > $3)), 1,
177+
`_find_leftmost_point(`$1', array2_get(`$1', `$5', `x'), array2_get(`$1', `$5', `y'), $5, incr($5))',
178+
`_find_leftmost_point(`$1', `$2', `$3', `$4', incr($5))'dnl
179+
)'dnl
180+
)'dnl
181+
)
182+
183+
divert(0)dnl
184+
ifelse(eval(ARGC < 2 ||
185+
!parse_int_list(`x_values', ARGV1) ||
186+
!parse_int_list(`y_values', ARGV2)
187+
), 1, `show_usage()')dnl
188+
ifelse(eval(array_get(`x_values', `length') < 3 ||
189+
array_get(`y_values', `length') < 3 ||
190+
array_get(`x_values', `length') != array_get(`y_values', `length')
191+
), 1, `show_usage()')dnl
192+
form_points(`x_values', `y_values', `points')dnl
193+
convex_hull(`points', `hull_points')dnl
194+
show_points(`hull_points')dnl

0 commit comments

Comments
 (0)