I was asked to write a code in c language that checks a certain alphabetical order from the input and determines how many "legal" orders are there. the order goes like this: I receive multiple inputs of both numbers(from 1-9) and letters(from A-Z) followed by '@' that determines the end of the input. once a number is received I should check the following letters (meaning if a received the number 3 I should check the next 3 letters and so on), these letters should be organized in ascending alphabetical order. for example ABC3DEF@ (ABC- is not a legal sequence. However, 3DEF is a legal sequence and so in total, I have one legal sequence.)
I tried something but it doesn't work, the output is always 0! (note: I am only allowed to use loops and condition statements, meaning no Array, functions, pointers...). Is there an idea that I am missing? or was my idea wrong?
int i, x, sum = 0;
char cha, c = 0;
while((cha = getchar()) != '@') {
scanf("%d %c", &x, &cha);
cha = c;
if(x >= 1 && x <= 9) {
for(i = 0; i < x; i++) {
if(c == cha - i) {
sum++;
}
c--;
}
}
}
As pointed out in the comments, mixing scanf
and getchar
is full of pitfalls. Take your code as an example and your goal to parse the string "ABC3DEF@"
. Your calls to getchar
and scanf
exemplify the problem.
while((cha = getchar()) != '@') {
scanf("%d %c", &x, &cha);
...
}
When you read cha
, the test (cha = getchar()) != '@'
will be TRUE for every character except '@'
. So for your sequence on first read cha = 'A'
and "BC3DEF@"
remains in stdin
. You then attempt to read with scanf("%d %c", &x, &cha);
and a matching failure occurs because 'B'
is not a valid start of an integer value, and character extraction from stdin
ceases leaving 'B'
unread in stdin
. Neither x
or cha
are assigned values.
You then attempt:
cha = c;
if(x >= 1 && x <= 9) {
Which sets cha = 0;
and invokes Undefined Behavior by accessing the value of x
(a variable with automatic storage duration) while the value is indeterminate. See C11 Standard - 6.3.2.1 Lvalues, arrays, and function designators(p2).
So all bets are off at that point. You loop again, this time reading 'B'
with getchar
and the matching failure and all subsequent errors repeat.
Further, the type for cha
must be int
as that is the proper return type for getchar
and necessary to evaluate whether EOF
has been reached (which you do not check). See: man 3 getchar
Instead, eliminate scanf
from your code entirely and simply loop with:
while ((c = getchar()) != EOF && c != '\n') {
...
}
(Note:, I have used int c;
where you used char cha;
)
You can then handle everything else that needs to be done with three primary conditions, e.g.
while ((c = getchar()) != EOF && c != '\n') {
if (isdigit (c)) { /* if c is digit */
...
}
else if (isalpha(c)) { /* if c is [A-Za-z] */
...
}
else if (c == '@') { /* if c is end character */
...
}
}
From there you simply define several variable that help you track whether you are in
a valid sequence, whether the sequence is currently legal (lgl
), the number of characters read as part of the sequence (nchr
) and the integer number (num
) converted at the beginning of the sequence, in addition to keeping track of the previous (prev
) character, e.g.
char buf[MAXC];
int c, in = 0, lgl = 1, nchr = 0, num = 0, prev = 0;
With that you can simply read a character-at-a-time keeping track of the current "state" of operations within your loop,
while ((c = getchar()) != EOF && c != '\n') {
if (isdigit (c)) { /* if c is digit */
if (!in) /* if not in seq. set in = 1 */
in = 1;
num *= 10; /* build number from digits */
num += c - '0';
}
else if (isalpha(c)) { /* if c is [A-Za-z] */
if (in) {
if (prev >= c) /* previous char greater or equal? */
lgl = 0; /* not a legal sequence */
prev = c; /* hold previous char in order */
if (nchr < MAXC - 1) /* make sure there is room in buf */
buf[nchr++] = c; /* add char to buf */
}
}
else if (c == '@') { /* if c is end character */
/* if in and legal and at least 1 char and no. char == num */
if (in && lgl && nchr && nchr == num && num < MAXC) {
buf[num] = 0; /* nul-terminate buf */
printf ("legal: %2d - %s\n", num, buf); /* print result */
}
lgl = 1; /* reset all values */
in = nchr = num = prev = 0;
}
}
Putting it altogether in a short example that will save the characters for each legal sequence in buf
to allow outputting the sequence when the '@'
is reached, you could do something similar to the following (that will handle sequences up to 8191
characters):
#include <stdio.h>
#include <ctype.h>
#define MAXC 8192 /* if you need a constant, #define one (or more) */
/* (don't skimp on buffer size!) */
int main (void) {
char buf[MAXC];
int c, in = 0, lgl = 1, nchr = 0, num = 0, prev = 0;
while ((c = getchar()) != EOF && c != '\n') {
if (isdigit (c)) { /* if c is digit */
if (!in) /* if not in seq. set in = 1 */
in = 1;
num *= 10; /* build number from digits */
num += c - '0';
}
else if (isalpha(c)) { /* if c is [A-Za-z] */
if (in) {
if (prev >= c) /* previous char greater or equal? */
lgl = 0; /* not a legal sequence */
prev = c; /* hold previous char in order */
if (nchr < MAXC - 1) /* make sure there is room in buf */
buf[nchr++] = c; /* add char to buf */
}
}
else if (c == '@') { /* if c is end character */
/* if in and legal and at least 1 char and no. char == num */
if (in && lgl && nchr && nchr == num && num < MAXC) {
buf[num] = 0; /* nul-terminate buf */
printf ("legal: %2d - %s\n", num, buf); /* print result */
}
lgl = 1; /* reset all values */
in = nchr = num = prev = 0;
}
}
}
(note: you can adjust MAXC
to change the number of characters as needed)
Now you can go exercise the code and make sure it will handle your conversion needs (you can adjust the output to fit your requirements). While care has been taken in putting the logic together, any corner-cases that may need to be handled are left to you.
Example Use/Output
$ echo "ABC3DEF@11abcdefghijk@4AZaz@3AbC@" | ./bin/sequences
legal: 3 - DEF
legal: 11 - abcdefghijk
legal: 4 - AZaz
or
$ echo "ABC3DEF@11abcdefghijk@3AbC@4AZaz@" | ./bin/sequences
legal: 3 - DEF
legal: 11 - abcdefghijk
legal: 4 - AZaz
or without a final '@'
even an otherwise legal sequence will be discarded
$ echo "ABC3DEF@11abcdefghijk@3AbC@4AZaz" | ./bin/sequences
legal: 3 - DEF
legal: 11 - abcdefghijk
Note: if you did want to accept the final legal sequence even though there is no closing '@'
before EOF
, you could simply add an additional conditional after your while
loop terminates, e.g.
/* handle final sequence before EOF */
if (in && lgl && nchr && nchr == num && num < MAXC) {
buf[num] = 0; /* nul-terminate */
printf ("legal: %2d - %s\n", num, buf); /* print result */
}
With that change the final example above would match the output of the others.