Bot Fred
Bot Fred

Reputation: 37

getting input of variable length in C

So I'm writing a program for my C lab that gets user input for co-ordinates, in such a format:

-1.0 5.0

3.2 4.7

-2.0 3.4

1.0 4.4

and so on.

They are instructing us to use scanf() specifically, to get the input. Anyways, the program will then do some calculations with these. However, I am stuck on how exactly to determine the size of the array I will store these co-ordinates in, if I am reading them with scanf() and storing them in the same function. Do I just create an array of size 99? What if they give 101 points then? Is there a way to read all the input first, count the amount of newlines, and then set that as the size of my array(s)?

This is my attempt:

void readPointsFindBoundingBox(double *bottomLeftX, double *bottomLeftY, double *widthp, double *heightp)
{
  printf("Enter coordinates: ");
double xvaltemp[25],yvaltemp[25];
int i=0;
while(scanf("%f %f/n", &xvaltemp[i], &yvaltemp[i])!=EOF)
{
    i++;
}
double xval[i],yval[i];
for(int x=0;x<i;x++)
{
    xval[x]=xvaltemp[x];
    yval[x]=yvaltemp[x];
}
for(int y=0;y<i;y++){
printf("%f",xval[y]);
printf("%f",yval[y]);
}

}

Upvotes: 1

Views: 1397

Answers (4)

ryyker
ryyker

Reputation: 23218

"...However, I am stuck on how exactly to determine the size of the array I will store these co-ordinates in, if I am reading them with scanf() and storing them in the same function., ...Is there a way to read all the input first, count the amount of newlines, and then set that as the size of my array(s)?"

Yes, there is a way to do this. And actually, as comments and other answers clearly show, there are many ways to do this task, even while being limited to using scanf(). The following set of steps are one way. They illustrate code that will accommodate any number of inputs, without knowing how many ahead of time the user will input. A count of user inputs is tracked and preserved in an index variable, inx which upon exiting the input loop is used to create an array of inx double, into which the text inputs are parsed into...

The steps:

  • create char *tok = NULL;
  • create char *endPtr = NULL;
  • create index int inx = 0;
  • create double input = 0.0;
  • create input size variable int size = 0;
  • scanf() user input for value of size eg scanf("%d", &size);
  • use size to create and a variable length array (VLA) eg char buffer[size];
  • loop on scanf("%lf", &input); statements to collect input into double variable eg input
  • use sprintf(buffer, "%l0.5f,", input) to concatenate inputs into temporary storage.
  • increment index ( inx++ ) once for each iteration of the input loop
  • exit loop when flag input is entered
  • use value of inx upon exit to create a VLA eg double array[inx];
  • use the following code to populate the array by parsing the string:

example:

int i = 0;
tok = strtok(buffer, ", ");//delimiters are space and comma
while(tok)
{
    array[i++] = strtod(tok, &endPtr);
    //error checking for strtod would be advised here
    tok = strtok(NULL, ", ");
}

Note this is a rough framework for an algorithm and is light on error checking but works for simple user input tasks when number of inputs is not known at compile-time.

Here is complete working illustration of the above steps:

int main(void)
{
    int size = 0;
    int i = 0;
    int inx = 0;
    char *tok = NULL;
    char *endPtr = NULL;
    double input = 1.0;//to avoid tripping flag value test
    double flag = 9999.0;//enter this value to exit input loop
    
    printf("Enter desired size of input buffer:\n");
    scanf("%d", &size);
    char buffer[size];
    char tempbuf[size];//to prevent collision in sprintf later
    memset(buffer, 0, sizeof buffer);
    memset(tempbuf, 0, sizeof tempbuf);
    printf("enter input value:\n");
    scanf("%lf", &input);
    while(fabs(input - flag) > 0.000)//using flag value as exit criteria
    {
        inx++;
        sprintf(tempbuf, "%s", buffer);
        sprintf(buffer, "%s,%0.5lf", tempbuf, input);//concatenate comma
                                                     //separated inputs string
        printf("enter input value:\n");
        scanf("%lf", &input);
    }
    double array[inx];
    memset(array, 0, sizeof array);
    //parse comma separated inputs string into array of double
    tok = strtok(buffer, ", ");
    while(tok)
    {
        array[i++] = strtod(tok, &endPtr);
        tok = strtok(NULL, ", ");
    }
    return 0;
}
        

Upvotes: 1

David C. Rankin
David C. Rankin

Reputation: 84561

If I understand what you are doing, and you need to determine the lower-left coordinates and the width and height of a bounding box to surround the x, y coordinates entered by the user, then you do not need an array at all. All you need do is keep track of the lower-left and lower-right coordinates and use a couple of temporary double values to capture the max x, y coordinates entered by the user and then your width and height can be set by subtracting the lower-left coordinates from the max values.

(note: you may need to adjust your comparisons based on whether you are using a normal Cartesian coordinate system (x, y are 0 at the center) or Screen coordinates (x, y are 0 at top-left of screen). We will leave that to you)

You cannot use any input function correctly unless you check the return. scanf() returns the number of successful conversions that take place based on the conversion-specifiers you include in the format-string. It can return less than the total if EOF is encountered before all conversions take place or if a matching-failure occurs. (e.g. users enters "banana" instead of a coordinate). When a matching failure occurs, character extraction from stdin ceases and the characters causing the failure are left in stdin unread. (just waiting to bite you on your next attempted input).

scanf() returns EOF on an input-failure (EOF encountered before the first successful conversion takes place). In addition to a matching or input failure, you must also handle additional or extraneous characters entered by the user beyond what you ask for (e.g. the user enters "10.2 13.8 and other nonsense". Otherwise a matching failure will occur on the next input attempt.

For further detail on the conditions you must check every time scanf() is called, the the answer to this question C For loop skips first iteration and bogus number from loop scanf.

Implementing your bounding box function along with the needed validations is fairly straight-forward. However, ending the input is a bit tricky with scanf(). In order to avoid setting some MagicNumbers for the user to enter to signify done, the user should generate a Manual EOF by pressing Ctrl + d (or Ctrl + z on windows). Then you can process the EOF normally while still retaining the full range of possible inputs. (note: using the recommended method of input fgets()/sscanf(), the user need only press Enter on an empty line to indicate they are done)

With that in mind, you can implement your function as follows:

#include <float.h>      /* for `DBL_MAX` macro */
...
void readPointsFindBoundingBox (double *bottomLeftX, double *bottomLeftY, 
                                double *widthp, double *heightp)
{
    /* temp values for parameters, initialized to max/min of range */
    double x = DBL_MAX, y = DBL_MAX, x_max = DBL_MIN, y_max = DBL_MIN;
    
    *bottomLeftX = *bottomLeftY = DBL_MAX;  /* initialize parameter values */
    *widthp = *heightp = 0;
    
    puts ("Press Ctrl+D (or Ctrl+z on windows twice) when done\n");
    
    for (;;) {      /* loop continually taking input */
        
        int rtn;    /* variable to save scanf() return */
        
        fputs ("Enter coordinates (\"X Y\"): ", stdout);    /* prompt for coordinates */
        
        rtn = scanf ("%lf %lf", &x, &y);    /* read input, save return */
        
        if (rtn == EOF) {                   /* check if user done */
            puts ("(input done)");
            break;
        }
        /* empty remaining characters from stdin */
        for (int c = getchar(); c != '\n'; c = getchar()) {}
        
        /* validate EVERY user-input. 2 valid conversions required. */
        if (rtn != 2) { /* handle matching-failure */
            fputs ("  error: invalid double values.\n", stderr);
            continue;
        }
        
        if (x < *bottomLeftX)           /* set bottomLeftX/Y as minimum coordinate  */
            *bottomLeftX = x;           /* assumes lower-left is minimum coordinate */
        if (y < *bottomLeftY)           /* adjust '>' if Y increases as you go down */
            *bottomLeftY = y;
        
        if (x > x_max)                  /* track maximum bounding coordinates   */
            x_max = x;                  /* to calculate width, height           */
        if (y > y_max)                  /* adjust y compare if Y increases down */
            y_max = y;
    }
    
    *widthp  = x_max - *bottomLeftX;    /* set width and height based on */
    *heightp = y_max - *bottomLeftY;    /* coordinates input by user    */
}

(note: this presumes the pointers double *bottomLeftX, double *bottomLeftY, double *widthp, double *heightp are all simple double values back in the calling function for which the address has been passed to your function)

To test your function you can write a short main() and add #include <stdio.h> to the top, e.g.

#include <stdio.h>
/* additional include and your function go here */

int main (void) {
    
    double bottomleft, bottomright, width, height;
    
    readPointsFindBoundingBox (&bottomleft, &bottomright, &width, &height);
    
    printf ("\nBounding Box\n"
            "  Lower Left Coordinates (x, y):  (%.2lf, %.2lf)\n"
            "  Height, Width (height, width):  (%.2lf, %.2lf)\n",
            bottomleft, bottomright, width, height);
}

Example Use/Output

$ ./bin/boundingboxfn
Press Ctrl+D (or Ctrl+z on windows twice) when done

Enter coordinates ("X Y"): 5.2 3
Enter coordinates ("X Y"): 9.1 -1
Enter coordinates ("X Y"): 2.2 3.3
Enter coordinates ("X Y"): .5 -.1
Enter coordinates ("X Y"): -1.4 8.1
Enter coordinates ("X Y"): my dog has fleas
  error: invalid double values.
Enter coordinates ("X Y"): 1.1 1.1
Enter coordinates ("X Y"): 4 -2
Enter coordinates ("X Y"): (input done)

Bounding Box
  Lower Left Coordinates (x, y):  (-1.40, -2.00)
  Height, Width (height, width):  (10.50, 10.10)

If you pick through the numbers, the function does provide the correct lower-left coordinates and the width and height of the bounding box required to enclose all coordinates. (recall the earlier note about adjustments needed if you are working in Screen Coordinates instead of a normal X, Y coordinate system with X and Y increasing to the right and up, respectively)

Let me know if I understood your issue correctly, and if not, drop a comment and I can help further. Let me know if you have any questions with what is done above as well.

Upvotes: 1

Govind Parmar
Govind Parmar

Reputation: 21542

You could use malloc then realloc to resize your array. This simple example shows you how it works. Note that I don't know how your user is supposed to indicate how they're done entering data, or what your data type/struct name is called, so you will have to modify this.

int i = 0;
size_t cb = sizeof(Point);
Point *points = malloc(cb), *tmp = NULL;

while(user_isnt_done_yet)
{
    scanf(" %...", points + i);
    cb += sizeof(Point);
    tmp = realloc(points, cb);
    if(tmp != NULL)
    {
        points = tmp;
    }
    else
    {
        // Out of memory error
    }
    i++;
}

free(points);
points = NULL;

Also note reallocating on every single entry is inefficient. I'd prefer to would start with a predefined size, and reallocate that by multiplying its size by 2 whenever necessary.

Upvotes: 1

Blindy
Blindy

Reputation: 67380

You have two ways of doing this:

  • Increase the array size as needed. This is what most everyone does, static-sized arrays are nothing but memory corruption waiting to happen.

  • Read the file twice, once to count the number of items and a second time to actually read the numbers in a properly-sized array. This is what most non-I/O C-style APIs do (DirectX, Vulkan, etc).

Upvotes: 1

Related Questions