Issue with program output

2019-10-04 13:06发布

问题:

Below is my program that determines the perimeter and area of a polygon given a certain amount of (x,y) coordinates but I seem to be getting the wrong output and I can't see why.

The input is:

  3  12867  1.0 2.0  1.0 5.0  4.0 5.0
  5  15643  1.0 2.0  4.0 5.0  7.8 3.5  5.0 0.4  1.0 0.4

With the first entry being the number of points (points) and the second entry being the polygon ID, anything after that is a set of coordinates.

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#define MAX_PTS 100
#define MAX_POLYS 100
#define END_INPUT 0

struct Point {
    double x, y;
};

double getDistance(struct Point a, struct Point b) {
    double distance;
    distance = sqrt((a.x - b.x) * (a.x - b.x) + (a.y-b.y) *(a.y-b.y));
    return distance;
}

double polygon_area(int length, double x[], double y[]) {
    double area = 0.0;
    for (int i = 0; i < length; ++i) {
       int j = (i + 1) % length;
       area += (x[i] * y[j] - x[j] * y[i]);
    }
area = area / 2;
area = (area > 0 ? area : -1 * area);

return (area);
}

int main(int argc, char *argv[]) {
    int npoints, poly_id;
    struct Point a, b;

    if(scanf("%d %d", &npoints, &poly_id)) {
        int iteration = 0;
        scanf("%lf %lf", &a.x, &a.y);
        struct Point initialPoint = a;
        double perimeter = 0;  // i start with 0 value of parameter.     
        for (iteration = 1; iteration < npoints; ++iteration) {
            scanf("%lf %lf", &b.x, &b.y);  // take input for new-point.
            perimeter += getDistance(a, b); // add the perimeter.
        // for next iteration, new-point would be first-point in getDistance
            a = b; 
        }
        // now complete the polygon with last-edge joining the last-point
        // with initial-point.
        perimeter += getDistance(a, initialPoint);

        printf("First polygon is %d\n", poly_id);
        printf("perimeter = %2.2lf m\n", perimeter);

        scanf("%d %d", &npoints, &poly_id);
        double x[MAX_PTS], y[MAX_PTS];
        double area = 0;
        for (iteration = 0; iteration < npoints; ++iteration) {
            scanf("%lf %lf", &(x[iteration]), &(y[iteration]));
        }
        area = polygon_area(npoints, x, y); 

        printf("First polygon is %d\n", poly_id);
        printf("area = %2.2lf m^2\n", area);

    } else if(scanf("%d", &npoints)==0) {
        exit(EXIT_SUCCESS);
    }

    return 0;
}

My output I keep getting is:

First polygon is 12867
perimeter = 10.24 m
First polygon is 15643
area = 19.59 m^2

But the output I want is:

First polygon is 12867
perimeter = 10.24 m
First polygon is 12867
area = 4.50 m^2 

Or alternatively:

First polygon is 12867
perimeter = 10.24 m
area = 4.50 m^2

Would be appreciated if someone could just point out where I've gone wrong.

回答1:

If you haven't sorted it out yet, your problem is obvious. You read the data for the first polygon and then you calculate the perimeter:

    perimeter += getDistance(a, initialPoint);

    printf("First polygon is %d\n", poly_id);
    printf("perimeter = %2.2lf m\n", perimeter);

Then, inexplicably, you read data for your second polygon before calculating the area:

    scanf("%d %d", &npoints, &poly_id);
    double x[MAX_PTS], y[MAX_PTS];
    double area = 0;
<snip>
    area = polygon_area(npoints, x, y); 

How you expect to get the area for the first polygon using data for the second polygon is a bit bewildering.

What you need to do is calculate both the perimeter and the area for each polygon before reading the data for the next. Rather than your if block of code, you would usually prompt to enter the number of polygons to process and then do something like:

for (i = 0; i < npolys; i++)
{
    <read data for poly>
    <calculate perimeter>
    <calculate area>
    <display results>
}

Next, when you expect data from a user, prompt for it. Don't just leave the user looking at a blinking cursor wondering if your program hung, etc.. A simple printf asking for the number of points and polygon id works fine. Then a like prompt to enter each x/y pair goes a long way to eliminating confusion. Taking all the above into consideration, your code could be re-written as:

int main (void) {

    size_t npoints, poly_id;
    size_t npolys = 0;
    size_t it = 0;
    struct Point a, b;

    printf ("\nNumber of polygons to enter: ");
    scanf (" %zu", &npolys);

    for (it = 0; it < npolys; it++)
    {
        double x[MAX_PTS], y[MAX_PTS];
        double perimeter = 0;
        double area = 0;
        size_t iter = 0;

        printf ("\nEnter npoints & poly_id    : ");
        scanf("%zu %zu", &npoints, &poly_id);

        printf ("Enter the first point X & Y: ");
        scanf("%lf %lf", &a.x, &a.y);

        x[iter] = a.x;
        y[iter] = a.y;

        struct Point initialPoint = a;

        for (iter = 1; iter < npoints; ++iter) 
        {
            printf ("           next point X & Y: ");
            scanf("%lf %lf", &b.x, &b.y);   /* input for new-point. */

            x[iter] = b.x;
            y[iter] = b.y;

            perimeter += getDistance(a, b); /* add the perimeter.   */
            a = b;                          /* new-pt is first-pt   */
        }

        /* complete polygon joining the last-point with initial-point. */
        perimeter += getDistance (b, initialPoint);
        area = polygon_area (npoints, x, y); 

        printf("\nPolygon %zu is %zu\n", it, poly_id);
        printf("perimeter = %2.2lf m\n", perimeter);
        printf("     area = %2.2lf m^2\n", area);

    }

    return 0;
}

But why not write an input routine for your code to read your data file and eliminate the error prone user input? It takes a little time, but it's not hard. That way you can completely separate the input and processing functions of your code.

That frees you to actually concentrate on laying the processing part of your code out in a logical manner, instead of having the processing logic sprinkled with user input. The following is an example of how separating input from the logic of your code can help keep your code clean and readable. It reads all data into an array of structures before beginning the first computation.

When you keep your code separated into logical function, it makes maintaining any individual computation like perimeter or area a simple matter of adjusting the logic of a single function. Take a look at the following and let me know if you have questions:

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <math.h>

#define MAXPTS 100
#define MAXPOLYS 100

typedef struct point {
    double x, y;
} point;

typedef struct polygon {
    size_t sz;
    size_t id;
    point *vertex;
} polygon;

double get_distance (point a, point b);
double poly_perim (polygon a);
double polygon_area (polygon pg);
polygon *read_data (char *fn);

int main (int argc, char **argv)
{
    if (argc < 2 ) {
        fprintf (stderr, "error: insufficient input, usage: %s filename\n", argv[0]);
        return 1;
    }

    size_t it = 0;
    size_t idx = 0;
    polygon *pg = read_data (argv[1]);

    if (!pg) return 1;

    while (pg[idx].sz)
    {
        printf ("\n id: %zu   points: %zu  perimeter: %6.2lf  area: %6.2lf\n\n", 
                pg[idx].id, pg[idx].sz, poly_perim (pg[idx]), polygon_area (pg[idx]));

        for (it = 0; it < pg[idx].sz; it++)
            printf ("  %5.2lf  %5.2lf\n", pg[idx].vertex[it].x, pg[idx].vertex[it].y);

        idx++;
    }

    return 0;
}

double get_distance (point a, point b)
{
    double distance;
    distance = sqrt ((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
    return distance;
}

double poly_perim (polygon a)
{
    int i = 0;
    double perim = get_distance (a.vertex[0], a.vertex[a.sz -1]);

    for (i = 1; i < a.sz; i++)
        perim += get_distance (a.vertex[i-1], a.vertex[i]);

    return perim;
}

double polygon_area (polygon pg)
{
    double area = 0.0;
    int i = 0;

    for (i = 0; i < pg.sz; ++i) 
    {
        int j = (i + 1) % pg.sz;
        area += (pg.vertex[i].x * pg.vertex[j].y - pg.vertex[j].x * pg.vertex[i].y);
    }

    area /= 2.0;
    area = area > 0 ? area : -1 * area;

    return area;
}

polygon *read_data (char *fn)
{
    char *ln = NULL;
    size_t n = 0;
    size_t it = 0;
    size_t idx = 0;
    ssize_t nchr = 0;
    FILE *fp = NULL;
    polygon *pg = NULL;

    if (!(fp = fopen (fn, "r"))) {
        fprintf (stderr, "%s() error: file open failed '%s'.\n", __func__, fn);
        exit (EXIT_FAILURE);
    }

    if (!(pg = calloc (MAXPOLYS, sizeof *pg))) {
        fprintf (stderr, "%s() error: virtual memory allocation failed.\n", __func__);
        exit (EXIT_FAILURE);
    }

    while ((nchr = getline (&ln, &n, fp)) != -1)
    {
        char *p = ln;
        char *ep = NULL;
        long lnum = 0;
        double dnum = 0;

        errno = 0;
        lnum = strtol (p, &ep, 10);
        if (errno == 0 && (p != ep && lnum != 0))
            pg[idx].sz = (size_t)lnum;
        else {
            fprintf (stderr, "%s() error: file read failure '%s'.\n", __func__, fn);
            exit (EXIT_FAILURE);
        }

        p = ep;
        errno = 0;
        lnum = strtol (p, &ep, 10);
        if (errno == 0 && (p != ep && lnum != 0))
            pg[idx].id = (size_t)lnum;
        else {
            fprintf (stderr, "%s() error: file read failure '%s'.\n", __func__, fn);
            exit (EXIT_FAILURE);
        }

        pg[idx].vertex = calloc (pg[idx].sz, sizeof *(pg[idx].vertex));
        if (!pg[idx].vertex) {
            fprintf (stderr, "%s() error: virtual memory allocation failed.\n", __func__);
            exit (EXIT_FAILURE);
        }

        for (it = 0; it < pg[idx].sz; it++)
        {
            p = ep;
            errno = 0;
            dnum = strtod (p, &ep);
            if (errno == 0 && (p != ep && lnum != 0))
                pg[idx].vertex[it].x = dnum;
            else {
                fprintf (stderr, "%s() error: file read failure '%s'.\n", __func__, fn);
                exit (EXIT_FAILURE);
            }

            p = ep;
            errno = 0;
            dnum = strtod (p, &ep);
            if (errno == 0 && (p != ep && lnum != 0))
                pg[idx].vertex[it].y = dnum;
            else {
                fprintf (stderr, "%s() error: file read failure '%s'.\n", __func__, fn);
                exit (EXIT_FAILURE);
            }
        }
        idx++;
        if (idx == MAXPOLYS) {
            fprintf (stderr, "%s() warning: MAXPOLYS reached in file '%s'.\n", __func__, fn);
            break;
        }
    }

    fclose (fp);
    if (ln) free (ln);

    return pg;
}

Input

$ cat dat/poly.txt
  3  12867  1.0 2.0  1.0 5.0  4.0 5.0
  5  15643  1.0 2.0  4.0 5.0  7.8 3.5  5.0 0.4  1.0 0.4

Output

$ ./bin/poly dat/poly.txt

 id: 12867   points: 3  perimeter:  10.24  area:   4.50

   1.00   2.00
   1.00   5.00
   4.00   5.00

 id: 15643   points: 5  perimeter:  18.11  area:  19.59

   1.00   2.00
   4.00   5.00
   7.80   3.50
   5.00   0.40
   1.00   0.40