1

So I want to implement a for statement whose 'length' and condition depend on the value of the number-of-entries variable I give it. The loop is reading a file primarily.

For example, if the command line input for number-of-entries is a positive integer, I want the loop to run for number-of-entries iterations or until the end of the file is reached; if the input for number-of-entries is -1, I want the loop to run until the end of the file.

Is there a way to do this without writing the for loop twice, each one nested in an if statement; is there a more concise way? Asking because the statements inside the for loop are the same; the only difference is that condition in the for argument.

Here's what I know I can do:

if ( number_of_entries > 0 ) {
    for ( i = 0; i < number_of_entries; i++ ){
        // set of for statements
        // check to see if end of file is reached
        // this case stops when i reaches number_of_entries or EOF
    }
}

else if ( number_of_entries < 0 ) {
    for ( i = 0; i > number_of_entries; i++ ){
        // identical set of for statements
        // check to see if end of file is reached
        // this case stops at EOF because i will never reach number_of_entries
    }
}

Just wondering if I can do this while keeping only one set of for statements; because they'd be the same in either case.

Edit: Let me clarify the i++ in the second case: it should still be i++; the second loop only ends when the end of file is reached. i will keep increasing until then. The only acceptable inputs for number_of_entries are -1 or any positive integer (there is a check for this in the program).

C. Pat
  • 115
  • 1
  • 11

5 Answers5

8

how about this using short-circuit so i is only tested when number_of_entries is positive:

for ( i = 0; number_of_entries < 0 || i < number_of_entries; i++ ){
        // set of for statements
        // check to see if end of file is reached
    }

if number_of_entries is negative, the for loop is an infinite loop (and you have to use a break internally when detecting the end of the file)

So that's an extra test each time if number_of_entries is positive, but given the contents of the loop (file read) the performance shouldn't suffer much. Concision vs raw speed.

Jean-François Fabre
  • 137,073
  • 23
  • 153
  • 219
  • Notwithstanding that the OP has an error in their second loop. They need `--i` or similar. Sadly then the elegance of this answer is the lost. – Bathsheba Jul 10 '17 at 15:42
  • 2
    "Edit: Let me clarify the i++ in the second case: it should still be i++; the second loop only ends when the end of file is reached". OP doesn't have any error "for ( i = 0; i > number_of_entries; i++ ){" is an infinite loop too. – Jean-François Fabre Jul 10 '17 at 15:44
  • 1
    thanks :) check my comment change: OP doesn't have an error: he just creates an infinite loop by testing i against a negative. but he wants to avoid copy/paste. Of course, testing 2 conditions is more concise but less performant. Obviously the contents of the loop is big so it doesn't matter... – Jean-François Fabre Jul 10 '17 at 15:46
  • 1
    Yes the question is now very dull indeed. I'll leave my answer up for the time being and let the community decide if it should be removed. – Bathsheba Jul 10 '17 at 15:47
  • 1
    Works nice, even if `i` is an unsigned type. – chux - Reinstate Monica Jul 10 '17 at 16:00
  • This works, thank you. Feel slightly more stupid for not thinking of this, and slightly less stupid now that I know this. – C. Pat Jul 10 '17 at 16:01
2

If you find that number_of_entries is negative, you can reset it to a very large value like INT_MAX so that it keeps going until you hit EOF.

if (number_of_entries < 0) {
    number_of_entries = INT_MAX;
}
for ( i = 0; i < number_of_entries; i++ ){
    // set of for statements
    // check to see if end of file is reached
}
dbush
  • 205,898
  • 23
  • 218
  • 273
1

Accepting you have a typo in your second snippet: you mean i--, this would do it:

for (
    i = 0;
    i < abs(number_of_entries);
    i += (number_of_entries > 0) - (number_of_entries < 0)
)

(number_of_entries > 0) - (number_of_entries < 0) is the idiomatic way of testing the sign of a number in C.

Reference: Fast sign of integer in C

Bathsheba
  • 231,907
  • 34
  • 361
  • 483
1

According to your edited question, you could state that in case of negative number_of_entries it should run to at least a large number. This expresses your intent probably more clearly, and it avoids UB due to integer overflows:

for (int i = 0; i < (number_of_entries < 0 ? INT_MAX : number_of_entries); i++ ) {
        // set of for statements
        // check to see if end of file is reached
        // this case stops when i reaches number_of_entries or EOF
}
Stephan Lechner
  • 34,891
  • 4
  • 35
  • 58
0

You can always use a ternary operator for the test condition:

for (i = 0; ne > 0 ? i < ne : i > ne; i++)

which encapsulates the test in a single for condition.

Here is a short example:

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

#define MAXC 256

int main (int argc, char **argv) {

    char buf[MAXC] = "";
    int ne = argc > 1 ? atoi(argv[1]) : 10;
    FILE *fp = argc > 2 ? fopen (argv[2], "r") : stdin;

    if (!fp) {  /* validate file open for reading */
        fprintf (stderr, "error: file open failed '%s'.\n", argv[1]);
        return 1;
    }

    /* if ne > 0, read ne lines, otherwise read entire file */
    for (int i = 0; ne > 0 ? i < ne : i > ne; i++) {
        if (!fgets (buf, sizeof buf, fp))
            break;
        printf ("%2d : %s", i + 1, buf);
    }

    if (fp != stdin) fclose (fp);     /* close file if not stdin */

    return 0;
}

Example Use/Output

$ ./bin/for_dual_cond 8 <~/tmp/foo.txt
 1 : after block/auto unblock
 2 : remember >> next time!
 3 : after manual unblock w/menu - restored.
 4 : signal blocked saving
 5 : comparing file names.
 6 :
 7 : $ ./bin/gtkwrite
 8 : adding app->mfp_handler: 667

with a negative value for ne:

$ ./bin/for_dual_cond -8 <~/tmp/foo.txt
 1 : after block/auto unblock
 2 : remember >> next time!
 3 : after manual unblock w/menu - restored.
 4 : signal blocked saving
 5 : comparing file names.
 6 :
 7 : $ ./bin/gtkwrite
 8 : adding app->mfp_handler: 667
 9 : blocking changed (667)
...
95 : Testing foreign modification: Yes
96 :
97 :
98 : new test after mod
David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
  • Didn't downvote you, sorry (can't even do it below 125 rep). Also a valid answer. – C. Pat Jul 10 '17 at 16:10
  • 1
    It's funny, I see others this is done to and you just have to chock it up to someone that lacks the integrity to stand behind their downvote -- and the phase of the moon. `:)` – David C. Rankin Jul 10 '17 at 16:13
  • I downvoted since the ternary based solution is ...well.. sub-par compared to Fabre's answer. It's much harder to grasp, especially for a beginner. – Bjorn A. Jul 10 '17 at 16:47
  • There are more than one way to accomplish the same thing in C. You will find the community much more receptive if you explain downvotes of technically correct answers, since what the compiler does with the statements may produce optimal code. I appreciate you having the integrity to speak to your downvote. And to add, *downvotes* are generally reserved for *incorrect* code or code that produced *undefined behavior*, otherwise, the voting system is designed to float the better answers to the top, not take away from correct answers. – David C. Rankin Jul 10 '17 at 16:51
  • @DavidC.Rankin Thanks for the feedback. I'll keep that in mind and maybe go for a comment instead of a downvote the next time. BTW, I find the whole question a bit silly. Personally, I'd written the code along the lines of "if (nentries < 0) cat(file); else head(file, nentries);" This double-loop construct isn't the way to go, IMO. – Bjorn A. Jul 10 '17 at 17:01
  • Sure, we all have to go along and get along. I've always looked at downvotes as something to use sparingly to point out invalid code or code potentially leading to UB. Comments are always a good place to point out where things can be improved or done better. Thanks. – David C. Rankin Jul 10 '17 at 17:06