Skip to content

Commit b3c2a4a

Browse files
authored
Add Minimum Spanning Tree in M4 (#5243)
1 parent 17484ea commit b3c2a4a

File tree

1 file changed

+266
-0
lines changed

1 file changed

+266
-0
lines changed
Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
divert(-1)
2+
define(`show_usage',
3+
`Usage: please provide a comma-separated list of integers
4+
m4exit(`1')')
5+
6+
dnl Reference: https://www.gnu.org/software/m4/manual/m4.html#index-array
7+
dnl array_get(varname, idx)
8+
define(`array_get', `defn(format(``%s[%s]'', `$1', `$2'))')
9+
10+
dnl array_set(varname, 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 3D versions of "array_get" and "array_set"
21+
dnl array3_get(varname, idx1, idx2, idx3)
22+
define(`array3_get', `defn(format(``%s[%s][%s][%s]'', `$1', `$2', `$3', `$4'))')
23+
24+
dnl array3_set(varname, idx1, idx2, idx3, value)
25+
define(`array3_set', `define(format(``%s[%s][%s][%s]'', `$1', `$2', `$3', `$4'), `$5')')
26+
27+
dnl is_valid(n)
28+
define(`is_valid', `eval(regexp(`$1', `^\s*-?[0-9]+\s*$') >= 0)')
29+
30+
dnl parse_int_list(varname, args):
31+
dnl varname["length"] = 0
32+
dnl foreach arg in args:
33+
dnl if not is_valid(arg):
34+
dnl Return 0
35+
dnl varname[varname["length"]] = arg
36+
dnl varname["length"] = varname["length"] + 1
37+
dnl Return 1
38+
define(`parse_int_list',
39+
`array_set(`$1', `length', 0)dnl
40+
_parse_int_list(`$1', $2)'dnl
41+
)
42+
define(`_parse_int_list',
43+
`ifelse(is_valid(`$2'), 0, `0',
44+
`array_set(`$1', array_get(`$1', `length'), `$2')dnl
45+
array_set(`$1', `length', incr(array_get(`$1', `length')))dnl
46+
ifelse(eval($# > 2), 1, `_parse_int_list(`$1', shift(shift($@)))', `1')'dnl
47+
)'dnl
48+
)
49+
50+
dnl Reference: https://en.wikipedia.org/wiki/Integer_square_root#Algorithm_using_Newton's_method
51+
dnl isqrt(n):
52+
dnl if n < 2:
53+
dnl return n
54+
dnl prev2 = -1
55+
dnl prev1 = 1
56+
dnl while 1:
57+
dnl x1 = (prev1 + n // prev1) // 2
58+
dnl
59+
dnl // Case 1: converged (steady value)
60+
dnl if x1 == prev1:
61+
dnl return x1
62+
dnl
63+
dnl // Case 2: oscillation (2-cycle)
64+
dnl if x1 == prev2 and x1 != prev1:
65+
dnl return min(prev1, x1)
66+
dnl
67+
dnl // Set up next values
68+
dnl prev2, prev1 = prev1, x1
69+
define(`isqrt', `ifelse(eval($1 < 2), 1, $1,
70+
`pushdef(`x1', 0)dnl
71+
_isqrt($1, -1, 1)dnl
72+
popdef(`x1')dnl
73+
'dnl
74+
)'dnl
75+
)
76+
77+
dnl n=$1, prev2=$2, prev1=$3
78+
define(`_isqrt',
79+
`define(`x1', eval(($3 + $1 / $3) >> 1))dnl
80+
ifelse(eval(x1 == $3), 1, x1,
81+
`ifelse(eval(x1 == $2 && x1 != $3), 1, `min(`$3', x1)', `_isqrt(`$1', `$3', x1)')'dnl
82+
)'dnl
83+
)
84+
85+
dnl min(a, b)
86+
define(`min', `ifelse(eval($1 < $2), 1, $1, $2)')
87+
88+
dnl create_graph(weights_varname, num_vertices, graph_varname):
89+
dnl // Create nodes
90+
dnl graph_varname["length"] = num_vertices
91+
dnl for i = 0 to num_vertices - 1:
92+
dnl graph_varname[i]["length"] = 0
93+
dnl
94+
dnl // Connect children to nodes
95+
dnl idx = 0
96+
dnl for i = 0 to num_vertices - 1:
97+
dnl for j = 0 to num_vertices - 1:
98+
dnl if weight_mtx_varname[idx] > 0:
99+
dnl graph_varname[i]["edge"][graph_varname[i]["length"]] = j
100+
dnl graph_varname[i]["weight"][graph_varname[i]["length"]] = weights_varname[idx]
101+
dnl graph_varname[i]["length"] = graph_varname[i]["length"] + 1
102+
dnl
103+
dnl idx = idx + 1
104+
define(`create_graph',
105+
`array_set(`$3', `length', `$2')dnl
106+
_create_nodes(`$3', `$2', 0)dnl
107+
_connect_children(`$1', `$2', `$3', 0, 0)dnl
108+
'dnl
109+
)
110+
111+
dnl graph_varname=$1, num_vertices=$2, i=$3
112+
define(`_create_nodes',
113+
`ifelse(eval($3 < $2), 1,
114+
`array2_set(`$1', `$3', `length', 0)dnl
115+
_create_nodes(`$1', `$2', incr($3))'dnl
116+
)'dnl
117+
)
118+
119+
dnl weights_varname=$1, num_vertices=$2, graph_varname=$3, i=$4, idx=$5
120+
define(`_connect_children',
121+
`ifelse(eval($4 < $2), 1,
122+
`_connect_children_inner(`$1', `$2', `$3', `$4', `$5', 0)dnl
123+
_connect_children(`$1', `$2', `$3', incr($4), eval($5 + $2))'dnl
124+
)'dnl
125+
)
126+
127+
dnl weights_varname=$1, num_vertices=$2, graph_varname=$3, i=$4, idx=$5, j=$6
128+
define(`_connect_children_inner',
129+
`ifelse(eval($6 < $2), 1,
130+
`ifelse(eval(array_get(`$1', `$5') > 0), 1,
131+
`array3_set(`$3', `$4', `edge', array2_get(`$3', `$4', `length'), `$6')dnl
132+
array3_set(`$3', `$4', `weight', array2_get(`$3', `$4', `length'), array_get(`$1', $5))dnl
133+
array2_set(`$3', `$4', `length', incr(array2_get(`$3', `$4', `length')))'dnl
134+
)dnl
135+
_connect_children_inner(`$1', `$2', `$3', `$4', incr($5), incr($6))'dnl
136+
)'dnl
137+
)
138+
139+
dnl M4 does not have infinity so choose largest integer value as infinity
140+
define(`INF', 2147483647)
141+
142+
dnl Prim's Minimum Spanning Tree (MST) Algorithm based on C implementation of
143+
dnl https://www.geeksforgeeks.org/prims-minimum-spanning-tree-mst-greedy-algo-5/
144+
dnl prim_mst(graph_varname, mst_varname):
145+
dnl // Note: mst_set is a string that looks like this: "|<node1>||<node2>|..."
146+
dnl // Since M4 does not have anything like a set, this is the easiest way to
147+
dnl // represent it, and the "index" function can be used to see if a node is
148+
dnl // in the MST (non-negative if so)
149+
dnl
150+
dnl // Indicate nothing in MST, and initialize key values to infinite
151+
dnl mst_set = ""
152+
dnl mst_varname["length"] = graph_varname["length"]
153+
dnl for i = 0 to mst_varname["length"] - 1:
154+
dnl mst_varname[i]["key"] = INF
155+
dnl
156+
dnl // Include first vertex in MST
157+
dnl mst_varname[0]["key"] = 0
158+
dnl mst_varname[0]["parent"] = -1 // Root has no parent
159+
dnl
160+
dnl // For each vertex
161+
dnl for i = 1 to mst_varname["length"] - 1
162+
dnl // Pick index of the minimum key value not already in MST
163+
dnl u = find_min_key(mst_varname)
164+
dnl
165+
dnl // Add picked vertex to MST set
166+
dnl mst_set = mst_set + "|" + u + "|"
167+
dnl
168+
dnl // Update key values and parent indices of picked adjacent
169+
dnl // vertices. Only consider vertices not yet in MST set
170+
dnl for j = 0 to graph[u]["length"] - 1:
171+
dnl v = graph[u]["edge"][j]
172+
dnl w = graph[u]["weight"][j]
173+
dnl if ("|" + v + "|") not in mst_set and w < mst_varname[v]["key"]:
174+
dnl mst_varname[v]["parent"] = u
175+
dnl mst_varname[v]["key"] = w
176+
define(`prim_mst',
177+
`pushdef(`u', `')dnl
178+
pushdef(`v', `')dnl
179+
pushdef(`w', `')dnl
180+
pushdef(`mst_set', `')dnl
181+
array_set(`$2', `length', array_get(`$1', `length'))dnl
182+
_init_mst(`$2', 0)dnl
183+
array2_set(`$2', 0, `key', 0)dnl
184+
array2_set(`$2', 0, `parent', -1)dnl
185+
_prim_mst_outer(`$1', `$2', 1)dnl
186+
popdef(`mst_set')dnl
187+
popdef(`w')dnl
188+
popdef(`v')dnl
189+
popdef(`u')dnl
190+
'dnl
191+
)
192+
193+
dnl mst_varname=$1, i=$2
194+
define(`_init_mst',
195+
`ifelse(eval($2 < array_get(`$1', `length')), 1,
196+
`array2_set(`$1', `$2', `key', INF)dnl
197+
_init_mst(`$1', incr($2))'dnl
198+
)'dnl
199+
)
200+
201+
dnl graph_varname=$1, mst_varname=$2, i=$3
202+
define(`_prim_mst_outer',
203+
`ifelse(eval($3 < array_get(`$2', `length')), 1,
204+
`define(`u', find_min_key(`$2'))dnl
205+
define(`mst_set', mst_set`|'u`|')dnl
206+
_prim_mst_inner(`$1', `$2', 0)dnl
207+
_prim_mst_outer(`$1', `$2', incr($3))dnl
208+
'dnl
209+
)'dnl
210+
)
211+
212+
dnl graph_varname=$1, mst_varname=$2, j=$3
213+
define(`_prim_mst_inner',
214+
`ifelse(eval($3 < array2_get(`$1', u, `length')), 1,
215+
`define(`v', array3_get(`$1', u, `edge', `$3'))dnl
216+
define(`w', array3_get(`$1', u, `weight', `$3'))dnl
217+
ifelse(eval(index(mst_set, `|'v`|') < 0 && w < array2_get(`$2', v, `key')), 1,
218+
`array2_set(`$2', v, `parent', u)dnl
219+
array2_set(`$2', v, `key', w)dnl
220+
')dnl
221+
_prim_mst_inner(`$1', `$2', incr($3))dnl
222+
'dnl
223+
)'dnl
224+
)
225+
226+
dnl find_min_key(mst_varname):
227+
dnl min_key = INF
228+
dnl min_index = -1
229+
dnl for i = 0 to mst_varname["length"] - 1:
230+
dnl if ("|" + i + "|") not in mst_set and mst_varname[i]["key"] < min_dist:
231+
dnl min_key = mst_varname[i]["key"]
232+
dnl min_index = i
233+
dnl return min_index
234+
define(`find_min_key', `_find_min_key(`$1', INF, -1, 0)')
235+
236+
dnl mst_varname=$1, min_key=$2, min_index=$3, i=$4
237+
define(`_find_min_key',
238+
`ifelse(eval($4 >= array_get(`$1', `length')), 1, `$3',
239+
`ifelse(eval(index(mst_set, `|$4|') < 0 && array2_get(`$1', `$4', `key') < $2), 1,
240+
`_find_min_key(`$1', array2_get(`$1', `$4', `key'), `$4', incr($4))',
241+
`_find_min_key(`$1', `$2', `$3', incr($4))'dnl
242+
)'dnl
243+
)'dnl
244+
)
245+
246+
dnl get_total_mst_weight(mst_varname):
247+
dnl total = 0
248+
dnl for i = 1 to mst_varname["length"] - 1:
249+
dnl total = total + mst_varname[i]["key"]
250+
dnl return total
251+
define(`get_total_mst_weight', `_get_total_mst_weight(`$1', 0, 1)')
252+
253+
dnl mst_varname=$1, total=$2, i=$3
254+
define(`_get_total_mst_weight',
255+
`ifelse(eval($3 >= array_get(`$1', `length')), 1, `$2',
256+
`_get_total_mst_weight(`$1', eval($2 + array2_get(`$1', `$3', `key')), incr($3))'dnl
257+
)'dnl
258+
)
259+
260+
divert(0)dnl
261+
ifelse(eval(ARGC < 1 || !parse_int_list(`weights', ARGV1)), 1, `show_usage()')dnl
262+
define(`num_vertices', isqrt(array_get(`weights', `length')))dnl
263+
ifelse(eval(array_get(`weights', `length') != num_vertices * num_vertices), 1, `show_usage()')dnl
264+
create_graph(`weights', num_vertices, `graph')dnl
265+
prim_mst(`graph', `mst')dnl
266+
get_total_mst_weight(`mst')

0 commit comments

Comments
 (0)