2

I want to write a program, at first I input a number N, then I want to get the name (can consist of multiple words) and price of N items one by one. Example:

3
item a // the name can consist of multiple words
25.00
item b
12.50
item c
8.12

Next I want to process this data, however i got stuck on the scanning part. my code looks like this:

 #include <stdio.h>

int main(){

  int n;
  char name[50];
  int price;

  scanf("%d\n", &n);

  for(int i = 0; i < n; i++){
    scanf("%s\n%d",name,&price);
    printf("%s , %d", name, price);
  }
  printf("end");

}

This works for a single word item, but if the item has a space in it will not continue scanning. I tried using the gets() function, however I still don't have the right result. the code:

  for(int i = 0; i < n; i++){
    gets(name);
    scanf("%d\n",&price);
    printf("%s , %d\n", name, price);
  }
  printf("end");

returns:

3             // Input 3 items
item a        // name of first item
1             // price of item 1
item b        // name of item 2
item a , 1    // the print of the first item
2             // price of item 2
item c        // name of item 3
item b , 2    // print of item 2
3             // price of item 3
word          // no clue where this new input came from
end           // end of scanning

My question is, how would I go about correctly scanning an input such as this? I also tried changing the scan function into while((c = getchar()) != '\n');, but got the same result...

likoli
  • 23
  • 3
  • Does this answer your question? [how to read scanf with spaces](https://stackoverflow.com/questions/2718819/how-to-read-scanf-with-spaces) – user14063792468 Jan 10 '21 at 21:13
  • not really, unless I dont understand how to use fgets(). putting fgets(name, 50, stdin) also does not print out the string correctly. – likoli Jan 10 '21 at 21:21
  • Sorry I was looking at your last code block and had a thouhgt that that is your input. I am used to `dba` formatting. – user14063792468 Jan 10 '21 at 21:24
  • Well, I took time to compile and run your program. It does not produce output you did mention. – user14063792468 Jan 10 '21 at 21:29
  • It works on a single word name, however it does not work with multiple word names – likoli Jan 10 '21 at 21:37
  • If your definition of "works" is a random output, then yes, it works. How do you differentiate between outputs of a different invocations? – user14063792468 Jan 10 '21 at 21:39

5 Answers5

2

Mixing gets(), scanf() is bad as scanf() tends to leave the trailing '\n' in stdin.

Using gets() is bad.

scanf("%s", ...) is not useful for reading a line of info with spaces meant to be saved.


how would I go about correctly scanning an input such as this?

A simple alternative is to read each line into a string and then parse the string.

#include <stdio.h>

int main() {
  char line[100];
  int n;

  fgets(line, sizeof line, stdin);
  sscanf(line, "%d", &n);

  for(int i = 0; i < n; i++){
    char name[50];
    // int price;
    double price;
    fgets(line, sizeof line, stdin);
    sscanf(line, " %49[^\n]", name);
    fgets(line, sizeof line, stdin);
    // sscanf(line, "%d", &price);
    sscanf(line, "%lf", &price);
    printf("%s , %.2f\n", name, price);
  }
  printf("end");

}

Advanced: Better code would check the return values of fgets(), sscanf(). Maybe replace sscanf(line, "%lf",... with strtod().

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • 1
    This is exactly it! Thank you. Also learned not to use gets() in my code... – likoli Jan 10 '21 at 22:01
  • A red flag with this solution is that if the length of *name* is changed the rest won't follow since the format string is hard-coded. Also there is no validation of the input. – August Karlstrom Jan 10 '21 at 22:17
  • 1
    @August it can be sorted out as well. First of all `#define NAME_LEN 50` followed by something like `sprintf(format, " %%49[^\n]", NAME_LEN-1);` and finally `sscanf(line, format, name);` – Roberto Caboni Jan 10 '21 at 22:58
  • 1
    @RobertoCaboni I find this [Specifying the maximum string length to scanf dynamically](https://stackoverflow.com/a/28460242/2410359) solution interesting. – chux - Reinstate Monica Jan 10 '21 at 23:06
1

I think you could probably figure out what's happening here pretty easily if you added some more output. Let's try shall we?

Also, first of all you're really looking for a double input - not an integer. With an integer, your scan function won't match properly based on what you're looking for. But let's add some more output!

#include <stdio.h>

int main()
{
    int n;
    char name[50];
    double price;

    printf("Enter count: ");
    scanf("%d", &n);

    for (int i = 0; i < n; i++) {
        printf("%s\n", "Please type a name followed by a newline followed by a number and then press enter:");
        scanf("%s\n%lf", name, &price);
        printf("%s , %lf", name, price);
    }

    return 0;
}

So I think what was happening in your initial attempt was a combination of things. Mostly that you were possibly pressing enter after the output from the first iteration? That will break the scanf call - since it isn't expecting to start with a \n but it is immediately expecting you to enter a name.

Second, I don't know the number you entered in the first iteration, because you didn't supply the output of it while still using scanf - so I have nothing other than speculation. You possibly used an integer on the first go, and subsequently on the remaining iterations you chose to use decimals? Again, this is only a guess.

  • I tried your code and it runs well when the name consists of one word. However when i try naming an item "Item A", it starts to bug out, does not wait for the number input and instead immediately prints out the first item. – likoli Jan 10 '21 at 21:34
  • Oh I see. Then you should probably break apart the `scanf()` call. It is literally taking something before the specified space as the name! That's what the function does –  Jan 10 '21 at 21:39
1

As chux said in the comments of this answer: "All scan specifiers, except %c, %[, %n consumes leading white-spaces." So you don't need to account for them.

scanf("%s%d",name,&price);

And looking at your input, you should use float or double for the price.

scanf("%s%lf",name,&price);

Note that this works only if items are made of one word. If they can be of two or more words, you'd better use fgets

EDIT: for items made of more words you should use fgets

fgets(name, 50, stdin);
scanf("%lf",&price);
anotherOne
  • 1,513
  • 11
  • 20
1

Replace your for loop with this:

for (int i = 0; i < n; i++) {
        scanf(" %[^\n]\n%d", name, &price);
        printf("%s , %d\n", name, price);
    }

The first space skips leading spaces, and the [^\n] allows you to get more than one word as string input.

Deepak
  • 2,660
  • 2
  • 8
  • 23
1

Sometimes it is easier to just write your own functions with error checking. Below is a suggestion. You may also want to check that the numbers are non-negative.

#include <assert.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>

void ReadLine(char result[], int resultLen)
{
    int ch, i;

    assert(resultLen > 0);

    i = 0;
    ch = getchar();
    while ((ch != '\n') && (ch != EOF)) {
        if (i < resultLen - 1) {
            result[i] = ch;
            i++;
        }
        ch = getchar();
    }
    result[i] = '\0';
}


void ReadInteger(int *i)
{
    int ch, count;

    count = scanf("%d", i);
    if (count == 1) {
        do {
            ch = getchar();
        } while (isspace(ch) && (ch != '\n'));
    } else {
        fprintf(stderr, "invalid input, integer expected\n");
        exit(EXIT_FAILURE);
    }
}



void ReadReal(double *x)
{
    int ch, count;

    count = scanf("%lf", x);
    if (count == 1) {
        do {
            ch = getchar();
        } while (isspace(ch) && (ch != '\n'));
    } else {
        fprintf(stderr, "invalid input, real number expected\n");
        exit(EXIT_FAILURE);
    }
}


int main(void)
{
    char name[50];
    int n;
    double price;

    ReadInteger(&n);
    for (int i = 0; i < n; i++) {
        ReadLine(name, sizeof name);
        ReadReal(&price);
        printf("%s, %.2f\n", name, price);
    }
    return 0;
}
August Karlstrom
  • 10,773
  • 7
  • 38
  • 60