This seems to be one of the questions that invite a lot of fresh solutions that are rewritten from scratch. Here's one that allows any number of cities up to a certain maximum and that enforces matching city names.
The core is a home-brew function getcell
that is similar to scanf("%s", ...)
, but that emits a special return value when one or more new-line characters were read. This allows to get theb rows and columns right without having to read the whole line, which may be very long.
Because the function reads from the file directly and because both whitespace and tokens must be looked at, the first non-matching character is consumed. To avoid this, ungetc
is used, but never more than once. I don't think that this is especially good style, but I've left it as is. (That style is effortless when you work with strings and pointers, but not with files.)
The code to read the distances checks consistency between rows and cols and cities aggressivly, but skips the checks for file I/O and allocation in order not to clutter the code more.
The city names must be single words (LeMans
or Los_Angeles
) and are stored in a separate, fixed-size array. (That fixed size is the reason why there is a max. number of cities.) The distances are stored in a dynamically allocated array of doubles
.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#define MAX_CITY 256 // Max. number of cities
#define MAX_NAME 24 // Buffer allocated for a name
#define NEWLINE -2 // Special token: end of line was read
/*
* Short-cut acro for string comparison
*/
#define is(a, b) (strcmp(a, b) == 0)
/*
* Quick-and-dirty exit macro with message
*/
#define die(...) exit((printf(__VA_ARGS__), putchar('\n'), 1))
/*
* Read a cell of at most (max - 1) characters and return its length.
* When the end of input is read, return the special value EOF; when
* one ore more new-line characters are read, return the special
* value NEWLINE. On EOF and NEWLINE, the contents of buf are
* undefined.
*/
int getcell(FILE *f, char *buf, size_t max)
{
size_t len = 0;
int nl = 0;
int c;
/*
* Skip leading whitespace and account for newlines
*/
for (;;) {
c = fgetc(f);
if (c == EOF) {
if (nl) break;
return EOF;
}
if (!isspace(c)) break;
if (c == '\n') nl++;
}
ungetc(c, f);
if (nl) return NEWLINE;
/*
* Store the token proper
*/
for (;;) {
c = fgetc(f);
if (c == EOF || isspace(c)) break;
if (len + 1 < max) buf[len++] = c;
}
ungetc(c, f);
buf[len] = '\0';
return len;
}
int main()
{
FILE *f = fopen("dist.txt", "r");
int nrow = -1;
int ncol = -1;
char city[MAX_CITY][MAX_NAME];
int ncity = 0;
double *data; // contiguous data block
double **dist; // Pointers into that block
for (;;) {
char buf[MAX_NAME];
int len = getcell(f, buf, sizeof(buf));
if (len == EOF) break;
if (len == NEWLINE) {
if (nrow >= 0 && ncol < ncity) {
die("Insufficient data for %s.", city[nrow]);
}
nrow++;
ncol = -1;
continue;
}
if (nrow < 0) {
if (ncol < 0) {
if (!is(buf, "Distance")) die("Wrong file format");
} else {
if (ncol >= MAX_CITY) {
die("Can have at most %d cities", MAX_CITY);
}
strcpy(city[ncity++], buf);
}
ncol++;
continue;
}
if (ncol < 0) {
if (nrow > ncity) {
die("Too many rows, expected only %d.", ncity);
}
if (!is(buf, city[nrow])) {
die("Expected '%s' in row %d.", city[nrow], nrow);
}
if (nrow == 0) {
// First-touch allocation
data = malloc(ncity * ncity * sizeof(*data));
dist = malloc(ncity * sizeof(*dist));
for (int i = 0; i < ncity; i++) {
dist[i] = &data[i * ncity];
}
}
} else {
if (nrow == ncol) {
if (!is(buf, "-")) {
die("Distance of %s to itself isn't '-'.", city[nrow]);
}
dist[nrow][ncol] = 0.0;
} else {
double d = strtod(buf, NULL);
if (ncol >= ncity) {
die("Too many columns for %s.", city[nrow]);
}
dist[nrow][ncol] = d;
}
}
ncol++;
}
if (nrow < ncity) die("Got only %d rows, expected %d.", nrow, ncity);
/*
* Print distance matrix
*/
printf("Distance");
for (ncol = 0; ncol < ncity; ncol++) {
printf(", %s", city[ncol]);
}
puts("");
for (nrow = 0; nrow < ncity; nrow++) {
printf("%s", city[nrow]);
for (ncol = 0; ncol < ncity; ncol++) {
printf(", %g", dist[nrow][ncol]);
}
puts("");
}
free(dist);
free(data);
return 0;
}