mirror of
git://git.tartarus.org/simon/puzzles.git
synced 2025-04-21 16:05:44 -07:00
Files

This is the main bulk of this boolification work, but although it's making the largest actual change, it should also be the least disruptive to anyone interacting with this code base downstream of me, because it doesn't modify any interface between modules: all the inter-module APIs were updated one by one in the previous commits. This just cleans up the code within each individual source file to use bool in place of int where I think that makes things clearer.
754 lines
21 KiB
C
754 lines
21 KiB
C
/*
|
|
* Implementation of matching.h.
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
|
|
#include "puzzles.h"
|
|
#include "matching.h"
|
|
|
|
struct scratch {
|
|
/*
|
|
* Current contents of the in-progress matching. LtoR is an array
|
|
* of nl integers, each of which holds a value in {0,1,...,nr-1},
|
|
* or -1 for no current assignment. RtoL is exactly the reverse.
|
|
*
|
|
* Invariant: LtoR[i] is non-empty and equal to j if and only if
|
|
* RtoL[j] is non-empty and equal to i.
|
|
*/
|
|
int *LtoR, *RtoL;
|
|
|
|
/*
|
|
* Arrays of nl and nr integer respectively, giving the layer
|
|
* assigned to each integer in the breadth-first search step of
|
|
* the algorithm.
|
|
*/
|
|
int *Llayer, *Rlayer;
|
|
|
|
/*
|
|
* Arrays of nl and nr integers respectively, used to hold the
|
|
* to-do queues in the breadth-first search.
|
|
*/
|
|
int *Lqueue, *Rqueue;
|
|
|
|
/*
|
|
* An augmenting path of vertices, alternating between L vertices
|
|
* (in the even-numbered positions, starting at 0) and R (in the
|
|
* odd positions). Must be long enough to hold any such path that
|
|
* never repeats a vertex, i.e. must be at least 2*min(nl,nr) in
|
|
* size.
|
|
*/
|
|
int *augpath;
|
|
|
|
/*
|
|
* Track the progress of the depth-first search at each
|
|
* even-numbered layer. Has one element for each even-numbered
|
|
* position in augpath.
|
|
*/
|
|
int *dfsstate;
|
|
|
|
/*
|
|
* Store a random permutation of the L vertex indices, if we're
|
|
* randomising the dfs phase.
|
|
*/
|
|
int *Lorder;
|
|
};
|
|
|
|
size_t matching_scratch_size(int nl, int nr)
|
|
{
|
|
size_t n;
|
|
int nmin = (nl < nr ? nl : nr);
|
|
|
|
n = (sizeof(struct scratch) + sizeof(int)-1)/sizeof(int);
|
|
n += nl; /* LtoR */
|
|
n += nr; /* RtoL */
|
|
n += nl; /* Llayer */
|
|
n += nr; /* Rlayer */
|
|
n += nl; /* Lqueue */
|
|
n += nr; /* Rqueue */
|
|
n += 2*nmin; /* augpath */
|
|
n += nmin; /* dfsstate */
|
|
n += nl; /* Lorder */
|
|
return n * sizeof(int);
|
|
}
|
|
|
|
int matching_with_scratch(void *scratchv,
|
|
int nl, int nr, int **adjlists, int *adjsizes,
|
|
random_state *rs, int *outl, int *outr)
|
|
{
|
|
struct scratch *s = (struct scratch *)scratchv;
|
|
int L, R, i, j;
|
|
|
|
/*
|
|
* Set up the various array pointers in the scratch space.
|
|
*/
|
|
{
|
|
int *p = scratchv;
|
|
int nmin = (nl < nr ? nl : nr);
|
|
|
|
p += (sizeof(struct scratch) + sizeof(int)-1)/sizeof(int);
|
|
s->LtoR = p; p += nl;
|
|
s->RtoL = p; p += nr;
|
|
s->Llayer = p; p += nl;
|
|
s->Rlayer = p; p += nr;
|
|
s->Lqueue = p; p += nl;
|
|
s->Rqueue = p; p += nr;
|
|
s->augpath = p; p += 2*nmin;
|
|
s->dfsstate = p; p += nmin;
|
|
s->Lorder = p; p += nl;
|
|
}
|
|
|
|
/*
|
|
* Set up the initial matching, which is empty.
|
|
*/
|
|
for (L = 0; L < nl; L++)
|
|
s->LtoR[L] = -1;
|
|
for (R = 0; R < nr; R++)
|
|
s->RtoL[R] = -1;
|
|
|
|
while (1) {
|
|
/*
|
|
* Breadth-first search starting from the unassigned left
|
|
* vertices, traversing edges from left to right only if they
|
|
* are _not_ part of the matching, and from right to left only
|
|
* if they _are_. We assign a 'layer number' to all vertices
|
|
* visited by this search, with the starting vertices being
|
|
* layer 0 and every successor of a layer-n node being layer
|
|
* n+1.
|
|
*/
|
|
int Lqs, Rqs, layer, target_layer;
|
|
|
|
for (L = 0; L < nl; L++)
|
|
s->Llayer[L] = -1;
|
|
for (R = 0; R < nr; R++)
|
|
s->Rlayer[R] = -1;
|
|
|
|
Lqs = 0;
|
|
for (L = 0; L < nl; L++) {
|
|
if (s->LtoR[L] == -1) {
|
|
s->Llayer[L] = 0;
|
|
s->Lqueue[Lqs++] = L;
|
|
}
|
|
}
|
|
|
|
layer = 0;
|
|
while (1) {
|
|
bool found_free_R_vertex = false;
|
|
|
|
Rqs = 0;
|
|
for (i = 0; i < Lqs; i++) {
|
|
L = s->Lqueue[i];
|
|
assert(s->Llayer[L] == layer);
|
|
|
|
for (j = 0; j < adjsizes[L]; j++) {
|
|
R = adjlists[L][j];
|
|
if (R != s->LtoR[L] && s->Rlayer[R] == -1) {
|
|
s->Rlayer[R] = layer+1;
|
|
s->Rqueue[Rqs++] = R;
|
|
if (s->RtoL[R] == -1)
|
|
found_free_R_vertex = true;
|
|
}
|
|
}
|
|
}
|
|
layer++;
|
|
|
|
if (found_free_R_vertex)
|
|
break;
|
|
|
|
if (Rqs == 0)
|
|
goto done;
|
|
|
|
Lqs = 0;
|
|
for (j = 0; j < Rqs; j++) {
|
|
R = s->Rqueue[j];
|
|
assert(s->Rlayer[R] == layer);
|
|
if ((L = s->RtoL[R]) != -1 && s->Llayer[L] == -1) {
|
|
s->Llayer[L] = layer+1;
|
|
s->Lqueue[Lqs++] = L;
|
|
}
|
|
}
|
|
layer++;
|
|
|
|
if (Lqs == 0)
|
|
goto done;
|
|
}
|
|
|
|
target_layer = layer;
|
|
|
|
/*
|
|
* Vertices in the target layer are only interesting if
|
|
* they're actually unassigned. Blanking out the others here
|
|
* will save us a special case in the dfs loop below.
|
|
*/
|
|
for (R = 0; R < nr; R++)
|
|
if (s->Rlayer[R] == target_layer && s->RtoL[R] != -1)
|
|
s->Rlayer[R] = -1;
|
|
|
|
/*
|
|
* Choose an ordering in which to try the L vertices at the
|
|
* start of the next pass.
|
|
*/
|
|
for (L = 0; L < nl; L++)
|
|
s->Lorder[L] = L;
|
|
if (rs)
|
|
shuffle(s->Lorder, nl, sizeof(*s->Lorder), rs);
|
|
|
|
/*
|
|
* Now depth-first search through that layered set of vertices
|
|
* to find as many (vertex-)disjoint augmenting paths as we
|
|
* can, and for each one we find, augment the matching.
|
|
*/
|
|
s->dfsstate[0] = 0;
|
|
i = 0;
|
|
while (1) {
|
|
/*
|
|
* Find the next vertex to go on the end of augpath.
|
|
*/
|
|
if (i == 0) {
|
|
/* In this special case, we're just looking for L
|
|
* vertices that are not yet assigned. */
|
|
if (s->dfsstate[i] == nl)
|
|
break; /* entire DFS has finished */
|
|
|
|
L = s->Lorder[s->dfsstate[i]++];
|
|
|
|
if (s->Llayer[L] != 2*i)
|
|
continue; /* skip this vertex */
|
|
} else {
|
|
/* In the more usual case, we're going through the
|
|
* adjacency list for the previous L vertex. */
|
|
L = s->augpath[2*i-2];
|
|
j = s->dfsstate[i]++;
|
|
if (j == adjsizes[L]) {
|
|
/* Run out of neighbours of the previous vertex. */
|
|
i--;
|
|
continue;
|
|
}
|
|
if (rs && adjsizes[L] - j > 1) {
|
|
int which = j + random_upto(rs, adjsizes[L] - j);
|
|
int tmp = adjlists[L][which];
|
|
adjlists[L][which] = adjlists[L][j];
|
|
adjlists[L][j] = tmp;
|
|
}
|
|
R = adjlists[L][j];
|
|
|
|
if (s->Rlayer[R] != 2*i-1)
|
|
continue; /* skip this vertex */
|
|
|
|
s->augpath[2*i-1] = R;
|
|
s->Rlayer[R] = -1; /* mark vertex as visited */
|
|
|
|
if (2*i-1 == target_layer) {
|
|
/*
|
|
* We've found an augmenting path, in the form of
|
|
* an even-sized list of vertices alternating
|
|
* L,R,...,L,R, with the initial L and final R
|
|
* vertex free and otherwise each R currently
|
|
* connected to the next L. Adjust so that each L
|
|
* connects to the next R, increasing the edge
|
|
* count in the matching by 1.
|
|
*/
|
|
for (j = 0; j < 2*i; j += 2) {
|
|
s->LtoR[s->augpath[j]] = s->augpath[j+1];
|
|
s->RtoL[s->augpath[j+1]] = s->augpath[j];
|
|
}
|
|
|
|
/*
|
|
* Having dealt with that path, and already marked
|
|
* all its vertices as visited, rewind right to
|
|
* the start and resume our DFS from a new
|
|
* starting L-vertex.
|
|
*/
|
|
i = 0;
|
|
continue;
|
|
}
|
|
|
|
L = s->RtoL[R];
|
|
if (s->Llayer[L] != 2*i)
|
|
continue; /* skip this vertex */
|
|
}
|
|
|
|
s->augpath[2*i] = L;
|
|
s->Llayer[L] = -1; /* mark vertex as visited */
|
|
i++;
|
|
s->dfsstate[i] = 0;
|
|
}
|
|
}
|
|
|
|
done:
|
|
/*
|
|
* Fill in the output arrays.
|
|
*/
|
|
if (outl) {
|
|
for (i = 0; i < nl; i++)
|
|
outl[i] = s->LtoR[i];
|
|
}
|
|
if (outr) {
|
|
for (j = 0; j < nr; j++)
|
|
outr[j] = s->RtoL[j];
|
|
}
|
|
|
|
/*
|
|
* Return the number of matching edges.
|
|
*/
|
|
for (i = j = 0; i < nl; i++)
|
|
if (s->LtoR[i] != -1)
|
|
j++;
|
|
return j;
|
|
}
|
|
|
|
int matching(int nl, int nr, int **adjlists, int *adjsizes,
|
|
random_state *rs, int *outl, int *outr)
|
|
{
|
|
void *scratch;
|
|
int size;
|
|
int ret;
|
|
|
|
size = matching_scratch_size(nl, nr);
|
|
scratch = malloc(size);
|
|
if (!scratch)
|
|
return -1;
|
|
|
|
ret = matching_with_scratch(scratch, nl, nr, adjlists, adjsizes,
|
|
rs, outl, outr);
|
|
|
|
free(scratch);
|
|
|
|
return ret;
|
|
}
|
|
|
|
#ifdef STANDALONE_MATCHING_TEST
|
|
|
|
/*
|
|
* Diagnostic routine used in testing this algorithm. It is passed a
|
|
* pointer to a piece of scratch space that's just been used by
|
|
* matching_with_scratch, and extracts from it a labelling of the
|
|
* input graph that acts as a 'witness' to the maximality of the
|
|
* returned matching.
|
|
*
|
|
* The output parameter 'witness' should be an array of (nl+nr)
|
|
* integers, indexed such that witness[L] corresponds to an L-vertex (for
|
|
* L=0,1,...,nl-1) and witness[nl+R] corresponds to an R-vertex (for
|
|
* R=0,1,...,nr-1). On return, this array will assign each vertex a
|
|
* label which is either 0 or 1, and the following properties will
|
|
* hold:
|
|
*
|
|
* + all vertices not paired up by the matching are type L0 or R1
|
|
* + every L0->R1 edge is used by the matching
|
|
* + no L1->R0 edge is used by the matching.
|
|
*
|
|
* The mere existence of such a labelling is enough to prove the
|
|
* maximality of the matching, because if there is any larger matching
|
|
* then its symmetric difference with this one must include at least
|
|
* one 'augmenting path', which starts at a free L-vertex and ends at
|
|
* a free R-vertex, traversing only unused L->R edges and only used
|
|
* R->L edges. But that would mean it starts at an L0, ends at an R1,
|
|
* and never follows an edge that can get from an 0 to a 1.
|
|
*/
|
|
static void matching_witness(void *scratchv, int nl, int nr, int *witness)
|
|
{
|
|
struct scratch *s = (struct scratch *)scratchv;
|
|
int i, j;
|
|
|
|
for (i = 0; i < nl; i++)
|
|
witness[i] = s->Llayer[i] == -1;
|
|
for (j = 0; j < nr; j++)
|
|
witness[nl + j] = s->Rlayer[j] == -1;
|
|
}
|
|
|
|
/*
|
|
* Standalone tool to run the matching algorithm.
|
|
*/
|
|
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <time.h>
|
|
|
|
#include "tree234.h"
|
|
|
|
int nl, nr, count;
|
|
int **adjlists, *adjsizes;
|
|
int *adjdata, *outl, *outr, *witness;
|
|
void *scratch;
|
|
random_state *rs;
|
|
|
|
void allocate(int nl_, int nr_, int maxedges)
|
|
{
|
|
nl = nl_;
|
|
nr = nr_;
|
|
adjdata = snewn(maxedges, int);
|
|
adjlists = snewn(nl, int *);
|
|
adjsizes = snewn(nl, int);
|
|
outl = snewn(nl, int);
|
|
outr = snewn(nr, int);
|
|
witness = snewn(nl+nr, int);
|
|
scratch = smalloc(matching_scratch_size(nl, nr));
|
|
}
|
|
|
|
void deallocate(void)
|
|
{
|
|
sfree(adjlists);
|
|
sfree(adjsizes);
|
|
sfree(adjdata);
|
|
sfree(outl);
|
|
sfree(outr);
|
|
sfree(witness);
|
|
sfree(scratch);
|
|
}
|
|
|
|
void find_and_check_matching(void)
|
|
{
|
|
int i, j, k;
|
|
|
|
count = matching_with_scratch(scratch, nl, nr, adjlists, adjsizes,
|
|
rs, outl, outr);
|
|
matching_witness(scratch, nl, nr, witness);
|
|
|
|
for (i = j = 0; i < nl; i++) {
|
|
if (outl[i] != -1) {
|
|
assert(0 <= outl[i] && outl[i] < nr);
|
|
assert(outr[outl[i]] == i);
|
|
j++;
|
|
|
|
for (k = 0; k < adjsizes[i]; k++)
|
|
if (adjlists[i][k] == outl[i])
|
|
break;
|
|
assert(k < adjsizes[i]);
|
|
}
|
|
}
|
|
assert(j == count);
|
|
|
|
for (i = j = 0; i < nr; i++) {
|
|
if (outr[i] != -1) {
|
|
assert(0 <= outr[i] && outr[i] < nl);
|
|
assert(outl[outr[i]] == i);
|
|
j++;
|
|
}
|
|
}
|
|
assert(j == count);
|
|
|
|
for (i = 0; i < nl; i++) {
|
|
if (outl[i] == -1)
|
|
assert(witness[i] == 0);
|
|
}
|
|
for (i = 0; i < nr; i++) {
|
|
if (outr[i] == -1)
|
|
assert(witness[nl+i] == 1);
|
|
}
|
|
for (i = 0; i < nl; i++) {
|
|
for (j = 0; j < adjsizes[i]; j++) {
|
|
k = adjlists[i][j];
|
|
|
|
if (outl[i] == k)
|
|
assert(!(witness[i] == 1 && witness[nl+k] == 0));
|
|
else
|
|
assert(!(witness[i] == 0 && witness[nl+k] == 1));
|
|
}
|
|
}
|
|
}
|
|
|
|
struct nodename {
|
|
const char *name;
|
|
int index;
|
|
};
|
|
|
|
int compare_nodes(void *av, void *bv)
|
|
{
|
|
const struct nodename *a = (const struct nodename *)av;
|
|
const struct nodename *b = (const struct nodename *)bv;
|
|
return strcmp(a->name, b->name);
|
|
}
|
|
|
|
int node_index(tree234 *n2i, tree234 *i2n, const char *name)
|
|
{
|
|
struct nodename *nn, *nn_prev;
|
|
char *namedup = dupstr(name);
|
|
|
|
nn = snew(struct nodename);
|
|
nn->name = namedup;
|
|
nn->index = count234(n2i);
|
|
|
|
nn_prev = add234(n2i, nn);
|
|
if (nn_prev != nn) {
|
|
sfree(nn);
|
|
sfree(namedup);
|
|
} else {
|
|
addpos234(i2n, nn, nn->index);
|
|
}
|
|
|
|
return nn_prev->index;
|
|
}
|
|
|
|
struct edge {
|
|
int L, R;
|
|
};
|
|
|
|
int compare_edges(void *av, void *bv)
|
|
{
|
|
const struct edge *a = (const struct edge *)av;
|
|
const struct edge *b = (const struct edge *)bv;
|
|
if (a->L < b->L) return -1;
|
|
if (a->L > b->L) return +1;
|
|
if (a->R < b->R) return -1;
|
|
if (a->R > b->R) return +1;
|
|
return 0;
|
|
}
|
|
|
|
void matching_from_user_input(FILE *fp, const char *filename)
|
|
{
|
|
tree234 *Ln2i, *Li2n, *Rn2i, *Ri2n, *edges;
|
|
char *line = NULL;
|
|
struct edge *e;
|
|
int i, lineno = 0;
|
|
int *adjptr;
|
|
|
|
Ln2i = newtree234(compare_nodes);
|
|
Rn2i = newtree234(compare_nodes);
|
|
Li2n = newtree234(NULL);
|
|
Ri2n = newtree234(NULL);
|
|
edges = newtree234(compare_edges);
|
|
|
|
while (sfree(line), lineno++, (line = fgetline(fp)) != NULL) {
|
|
char *p, *Lname, *Rname;
|
|
|
|
p = line;
|
|
while (*p && isspace((unsigned char)*p)) p++;
|
|
if (!*p)
|
|
continue;
|
|
|
|
Lname = p;
|
|
while (*p && !isspace((unsigned char)*p)) p++;
|
|
if (*p)
|
|
*p++ = '\0';
|
|
while (*p && isspace((unsigned char)*p)) p++;
|
|
|
|
if (!*p) {
|
|
fprintf(stderr, "%s:%d: expected 2 words, found 1\n",
|
|
filename, lineno);
|
|
exit(1);
|
|
}
|
|
|
|
Rname = p;
|
|
while (*p && !isspace((unsigned char)*p)) p++;
|
|
if (*p)
|
|
*p++ = '\0';
|
|
while (*p && isspace((unsigned char)*p)) p++;
|
|
|
|
if (*p) {
|
|
fprintf(stderr, "%s:%d: expected 2 words, found more\n",
|
|
filename, lineno);
|
|
exit(1);
|
|
}
|
|
|
|
e = snew(struct edge);
|
|
e->L = node_index(Ln2i, Li2n, Lname);
|
|
e->R = node_index(Rn2i, Ri2n, Rname);
|
|
if (add234(edges, e) != e) {
|
|
fprintf(stderr, "%s:%d: duplicate edge\n",
|
|
filename, lineno);
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
allocate(count234(Ln2i), count234(Rn2i), count234(edges));
|
|
|
|
adjptr = adjdata;
|
|
for (i = 0; i < nl; i++)
|
|
adjlists[i] = NULL;
|
|
for (i = 0; (e = index234(edges, i)) != NULL; i++) {
|
|
if (!adjlists[e->L])
|
|
adjlists[e->L] = adjptr;
|
|
*adjptr++ = e->R;
|
|
adjsizes[e->L] = adjptr - adjlists[e->L];
|
|
}
|
|
|
|
find_and_check_matching();
|
|
|
|
for (i = 0; i < nl; i++) {
|
|
if (outl[i] != -1) {
|
|
struct nodename *Lnn = index234(Li2n, i);
|
|
struct nodename *Rnn = index234(Ri2n, outl[i]);
|
|
printf("%s %s\n", Lnn->name, Rnn->name);
|
|
}
|
|
}
|
|
}
|
|
|
|
void test_subsets(void)
|
|
{
|
|
int b = 8;
|
|
int n = 1 << b;
|
|
int i, j, nruns, expected_size;
|
|
int *adjptr;
|
|
int *edgecounts;
|
|
struct stats {
|
|
int min, max;
|
|
double n, sx, sxx;
|
|
} *stats;
|
|
static const char seed[] = "fixed random seed for repeatability";
|
|
|
|
/*
|
|
* Generate a graph in which every subset of [b] = {1,...,b}
|
|
* (represented as a b-bit integer 0 <= i < n) has an edge going
|
|
* to every subset obtained by removing exactly one element.
|
|
*
|
|
* This graph is the disjoint union of the corresponding graph for
|
|
* each layer (collection of same-sized subset) of the power set
|
|
* of [b]. Each of those graphs has a matching of size equal to
|
|
* the smaller of its vertex sets. So we expect the overall size
|
|
* of the output matching to be less than n by the size of the
|
|
* largest layer, that is, to be n - binomial(n, floor(n/2)).
|
|
*
|
|
* We run the generation repeatedly, randomising it every time,
|
|
* and we expect to see every possible edge appear sooner or
|
|
* later.
|
|
*/
|
|
|
|
rs = random_new(seed, strlen(seed));
|
|
|
|
allocate(n, n, n*b);
|
|
adjptr = adjdata;
|
|
expected_size = 0;
|
|
for (i = 0; i < n; i++) {
|
|
adjlists[i] = adjptr;
|
|
for (j = 0; j < b; j++) {
|
|
if (i & (1 << j))
|
|
*adjptr++ = i & ~(1 << j);
|
|
}
|
|
adjsizes[i] = adjptr - adjlists[i];
|
|
if (adjsizes[i] != b/2)
|
|
expected_size++;
|
|
}
|
|
|
|
edgecounts = snewn(n*b, int);
|
|
for (i = 0; i < n*b; i++)
|
|
edgecounts[i] = 0;
|
|
|
|
stats = snewn(b, struct stats);
|
|
|
|
nruns = 0;
|
|
while (nruns < 10000) {
|
|
nruns++;
|
|
find_and_check_matching();
|
|
assert(count == expected_size);
|
|
|
|
for (i = 0; i < n; i++)
|
|
for (j = 0; j < b; j++)
|
|
if ((i ^ outl[i]) == (1 << j))
|
|
edgecounts[b*i+j]++;
|
|
|
|
if (nruns % 1000 == 0) {
|
|
for (i = 0; i < b; i++) {
|
|
struct stats *st = &stats[i];
|
|
st->min = st->max = -1;
|
|
st->n = st->sx = st->sxx = 0;
|
|
}
|
|
|
|
for (i = 0; i < n; i++) {
|
|
int pop = 0;
|
|
for (j = 0; j < b; j++)
|
|
if (i & (1 << j))
|
|
pop++;
|
|
pop--;
|
|
|
|
for (j = 0; j < b; j++) {
|
|
if (i & (1 << j)) {
|
|
struct stats *st = &stats[pop];
|
|
int x = edgecounts[b*i+j];
|
|
if (st->max == -1 || st->max < x)
|
|
st->max = x;
|
|
if (st->min == -1 || st->min > x)
|
|
st->min = x;
|
|
st->n++;
|
|
st->sx += x;
|
|
st->sxx += (double)x*x;
|
|
} else {
|
|
assert(edgecounts[b*i+j] == 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
printf("after %d runs:\n", nruns);
|
|
for (j = 0; j < b; j++) {
|
|
struct stats *st = &stats[j];
|
|
printf("edges between layers %d,%d:"
|
|
" min=%d max=%d mean=%f variance=%f\n",
|
|
j, j+1, st->min, st->max, st->sx/st->n,
|
|
(st->sxx - st->sx*st->sx/st->n) / st->n);
|
|
}
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
static const char stdin_identifier[] = "<standard input>";
|
|
const char *infile = NULL;
|
|
bool doing_opts = true;
|
|
enum { USER_INPUT, AUTOTEST } mode = USER_INPUT;
|
|
|
|
while (--argc > 0) {
|
|
const char *arg = *++argv;
|
|
|
|
if (doing_opts && arg[0] == '-' && arg[1]) {
|
|
if (!strcmp(arg, "--")) {
|
|
doing_opts = false;
|
|
} else if (!strcmp(arg, "--random")) {
|
|
char buf[64];
|
|
int len = sprintf(buf, "%lu", (unsigned long)time(NULL));
|
|
rs = random_new(buf, len);
|
|
} else if (!strcmp(arg, "--autotest")) {
|
|
mode = AUTOTEST;
|
|
} else {
|
|
fprintf(stderr, "matching: unrecognised option '%s'\n", arg);
|
|
return 1;
|
|
}
|
|
} else {
|
|
if (!infile) {
|
|
infile = (!strcmp(arg, "-") ? stdin_identifier : arg);
|
|
} else {
|
|
fprintf(stderr, "matching: too many arguments\n");
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mode == USER_INPUT) {
|
|
FILE *fp;
|
|
|
|
if (!infile)
|
|
infile = stdin_identifier;
|
|
|
|
if (infile != stdin_identifier) {
|
|
fp = fopen(infile, "r");
|
|
if (!fp) {
|
|
fprintf(stderr, "matching: could not open input file '%s'\n",
|
|
infile);
|
|
return 1;
|
|
}
|
|
} else {
|
|
fp = stdin;
|
|
}
|
|
|
|
matching_from_user_input(fp, infile);
|
|
|
|
if (infile != stdin_identifier)
|
|
fclose(fp);
|
|
}
|
|
|
|
if (mode == AUTOTEST) {
|
|
if (infile) {
|
|
fprintf(stderr, "matching: expected no filename argument "
|
|
"with --autotest\n");
|
|
return 1;
|
|
}
|
|
|
|
test_subsets();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#endif /* STANDALONE_MATCHING_TEST */
|