Echo All palindromes in C

I like the ideas presented in the book by Brian Kernighan and Rob Pike, “UNIX Programming Environment,” where they focus on working in an environment where you can put together many (small, accurate, well-understood) command-line programs to run many programming tasks.

I understand the strict ANSI C conventions and try to stick with this philosophy. Somewhere in this book (I can get the exact page number, if necessary) they assume that all programs in this environment should adhere to the following principles:

  • If the input is presented on the command line as an argument to the program itself, process that input.

  • If no input is entered on the command line, process the input from stdin.

Here the C program that I wrote will display any input (numeric or alphabetical) that is a palindrome. My question is specifically:

Is this a good C program? In other words, is this what Kernigan and Pike have suggested is optimal behavior for a command line application like this?

#include <stdio.h> #include <string.h> /* for strlen */ int main(int argc, char* argv[]) { char r_string[100]; if (argc > 1) { int length = (int)strlen(argv[1]); int i = 0; int j = length; r_string[j] = (char)NULL; j--; for (i = 0; i < length; i++, j--) { r_string[j] = argv[1][i]; } if (strcmp(argv[1], r_string) == 0) { printf("%s\n", argv[1]); } } else { char* i_string; while (scanf("%s", i_string) != EOF) { int length = (int)strlen(i_string); int i = 0; int j = length; r_string[j] = (char)NULL; j--; for (i = 0; i < length; i++, j--) { r_string[j] = i_string[i]; } if (strcmp(i_string, r_string) == 0) { printf("%s\n", i_string); } } } return 0; } 
+4
source share
4 answers

Yes, I think that you follow the advice of R & K. As Hugo said, you can take the argument - the file name, bu, t IMHO, for this simple program, I would say that accepting the parameter as the palindrome itself can make more sense.

Also, if you let me get some more advice, I would separate the line reading functionality from checking if it is a palindrome or not, because you have this code duplicated right now.

 int ispalindrome(const char* c) { size_t len = strlen(c); size_t limit = len/2; size_t i; for (i = 0; i < limit; i++) { if(c[i]!=c[len-i-1]) break; /* Different character found */ } return i==limit; /* If we reached limit, it a palyndrome */ } 

Of course, I’m sure that this can be improved (maybe even a mistake, I write pretty quickly), but as soon as you have your line, whether from the command line or user input, you can call this a function or functional like this.

NOTE. Edited to reflect a comment from Mark, thanks a lot, Mark!

+3
source

One of the problems you are facing is overflowing a potential buffer, because you are writing input of arbitrary length to a buffer with a fixed size. You can fix this by rejecting inputs that are too long or by dynamically creating an array of the correct size. I would not use scanf .

As for the real algorithm, you do not need to copy the string in reverse order and then compare two strings. You can perform a check using only one copy of the line and a pointer at both ends, both of which move to the middle.

Here is some code to show the principle:

 char* a = /* pointer to first character in string */; char* b = /* pointer to last character in string (excluding the null terminator) */; while (a < b && *a == *b) { a++; b--; } if (a >= b) { // Is palindrome. } 

I agree with Javier that you pass the palindrome verification code to a separate function.

+3
source

Regarding the principles that you have indicated, I believe that these tools usually take their arguments as the names of the files whose content should be processed. Instead, you see them as the entrance itself.

Take sort , for example. If you do not specify any arguments, the contents from stdin will be sorted. Otherwise, the contents of the file whose file name you specified will be sorted. The arguments themselves are not processed.

The code for this will be as follows:

 FILE * input = stdin; if (argc > 1) { input = fopen(argv[1], "r"); // handle possible errors from the fopen } while (fscanf(input, "%s", i_string) != EOF) // check if i_string is a palindrome and output to stdout 

In addition, you should be careful with the buffer overflow indicated by Mark Byers.

You are processing the string incorrectly. The i_string buffer is not initialized, and even so, you must limit the number of bytes that scanf reads to avoid the overflow mentioned:

 char i_string[1000]; while (scanf("999%s", i_string) != EOF) if (is_palindrome(i_string)) /* Use any function defined in the other answers */ printf("%s\n", i_string); 

You should always reserve another byte (1000 versus 999) to allow for the terminator of the NULL string. If you want to allow arbitrary strings of length, I think you will have to dynamically allocate a buffer and resize it if large strings are present. It will be a little harder.

+1
source

This is useful for text filters, such as a program that prints only lines with palindromes to specify input files through command line arguments, for example, it allows:

 $ palindromes input*.txt # file patterns $ find -name '*.txt' -print0 | xargs -0 palindromes 

This is a common convention that is supported by many languages. The following are scripts in Perl, Python, C that have the same use:

  Usage: palindromes [FILE]
 Print lines that are polindromes in each FILE.

 With no FILE, or when FILE is -, read standard input. 

in perl

 #!/usr/bin/perl -w while (<>) { # read stdin or file(s) specified at command line $line = $_; s/^\s+//; # remove leading space s/\s+$//; # remove trailing space print $line if $_ eq reverse $_; # print line with a palindrome } 

in python

 #!/usr/bin/env python import fileinput, sys for line in fileinput.input(): # read stdin or file(s) specified at command line s = line.strip() # strip whitespace characters if s == s[::-1]: # is palindrome sys.stdout.write(line) 

in C

 #!/usr/local/bin/tcc -run -Wall #include <ctype.h> #include <errno.h> #include <stdbool.h> #include <stdio.h> #include <string.h> enum { MATCH, NO_MATCH, ERROR }; bool is_palindrome(char *first, char *last) { /** Whether a line defined by range [first, last) is a palindrome. `last` points either to '\0' or after the last byte if there is no '\0'. Leading and trailing spaces are ignored. All characters including '\0' are allowed */ --last; // '\0' for ( ; first < last && isspace(*first); ++first); // skip leading space for ( ; first < last && isspace(*last); --last); // skip trailing space for ( ; first < last; ++first, --last) if (*first != *last) return false; return true; } int palindromes(FILE *fp) { /** Print lines that are palindromes from the file. Return 0 if any line was selected, 1 otherwise; if any error occurs return 2 */ int ret = NO_MATCH; char *line = NULL; size_t line_size = 0; // line size including terminating '\0' if any ssize_t len = -1; // number of characters read, including '\n' if any, // . but not including the terminating '\0' while ((len = getline(&line, &line_size, fp)) != -1) { if (is_palindrome(line, line + len)) { if (printf("%s", line) < 0) { ret = ERROR; break; } else ret = MATCH; } } if (line) free(line); else ret = ERROR; if (!feof(fp)) ret = ERROR; return ret; } int main(int argc, char* argv[]) { int exit_code = NO_MATCH; if (argc == 1) // no input file; read stdin exit_code = palindromes(stdin); else { // process each input file FILE *fp = NULL; int ret = 0; int i; for (i = 1; i < argc; i++) { if (strcmp(argv[i], "-") == 0) ret = palindromes(stdin); else if ((fp = fopen(argv[i], "r")) != NULL) { ret = palindromes(fp); fclose(fp); } else { fprintf(stderr, "%s: %s: could not open: %s\n", argv[0], argv[i], strerror(errno)); exit_code = ERROR; } if (ret == ERROR) { fprintf(stderr, "%s: %s: error: %s\n", argv[0], argv[i], strerror(errno)); exit_code = ERROR; } else if (ret == MATCH && exit_code != ERROR) // return MATCH if at least one line is a MATCH, propogate error exit_code = MATCH; } } return exit_code; } 

The output state is 0 if any row is selected, 1 otherwise; if any error occurs, the exit status is 2. It uses GNU getline() , which allows arbitrary large lines to enter data.

+1
source

All Articles