Reputation: 37
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
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:
char *tok = NULL;
char *endPtr = NULL;
int inx = 0;
double input = 0.0;
int size = 0;
scanf()
user input for value of size
eg scanf("%d", &size);
size
to create and a variable length array (VLA) eg char buffer[size];
scanf("%lf", &input);
statements to collect input into double
variable eg input
sprintf(buffer, "%l0.5f,", input)
to concatenate inputs into temporary storage.inx++
) once for each iteration of the input loopinx
upon exit to create a VLA eg double array[inx];
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
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
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
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