I have a problem like here:
Write a program to read a multiple line text file and write the 'N' longest lines to stdout. Where the file to be read is specified on the command line.
Now I wrote my program like this:
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
int a,k,n,i=0,j;
int part;
char ar[1000][1000],str[1000];
/* char file[200]; */
/* scanf("%s",file); */
FILE *f = fopen(argv[1],"r");
if ( f == NULL || argc < 2)
{
return 0;
}
fscanf(f,"%d",&a);
while (fscanf(f,"%s",str)==1)
{
strcpy(ar[i++],str);
for ( k = 0 ; k < i ; k++ )
{
for ( j = k ; j < i ; j++)
{
if ( strlen(ar[k]) < strlen(ar[j]))
{
strcpy(str,ar[k]);
strcpy(ar[k],ar[j]);
strcpy(ar[j],str);
}
}
}
}
for ( j = 0 ; j < a ; j++ )
{
puts(ar[j]);
}
return 0;
}
First of all it is working well for me but on submission it is giving me runtime error.
Secondly I want to do it using pointers and dynamic allocation of memory. How can I do that?
Sorry, I went to bed for some time. Can you explain me what's wrong with my code. Why it is not working. I think no one explained me where I am doing wrong. Please let me know how can I draw attention of people after a few hours from posting my question. Again thanks a lot for all of you for giving me so much time.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char *argv[]){
int a, n=0, i;
char **ar, str[1000];
int *lens;
FILE *f = fopen(argv[1],"r");
if ( f == NULL || argc < 2){
return 0;
}
fscanf(f,"%d",&a);
if(NULL==(ar=calloc(a, sizeof(char*))) || NULL==(lens=calloc(a, sizeof(int)))){
perror("malloc");
return -1;
}
while (fscanf(f, " %999[^\n]", str)==1){
++n;
int len = strlen(str);
for(i = 0;i < a;++i){
if(lens[i] < len){
free(ar[a-1]);
memmove(&lens[i+1], &lens[i], (a-i-1)*sizeof(int));
memmove(&ar[i+1], &ar[i], (a-i-1)*sizeof(char*));
ar[i]=strdup(str);
lens[i]=len;
break;
}
}
}
fclose(f);
for (i = 0 ; i < n && i < a ; i++ ){
puts(ar[i]);
free(ar[i]);
}
free(ar);free(lens);
return 0;
}
If you really want to conserve memory then you should not copy any contents of the file, but instead mmap it into your process and only save the offsets into the longest lines in your code. On longer files this should give you significantly more performance since it outsources memory management to the kernel.
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/stat.h>
int main(int argc, char ** argv) {
char * filename = argv[1];
if (!filename) {
fprintf(stderr, "Usage: %s filename\n", argv[0]);
exit(-1);
}
int fd = open(filename, O_RDONLY);
if (fd < 0) {
perror("open");
abort();
}
struct stat file_stats;
if(fstat(fd, &file_stats)) {
perror("fstat");
abort();
}
char * file_contents = mmap(NULL // anywhere
, file_stats.st_size + 1 // file length + 1 byte for null terminator
, PROT_READ // we only need read only
, MAP_PRIVATE // this doesn't really matter since we are read only
, fd // from this file descriptor
, 0); // from beginning
if (file_contents == MAP_FAILED) {
perror("mmap");
abort();
}
// optional
// Expect page references in sequential order.
// Hence, pages can be aggressively read ahead, and may be freed soon after they are accessed.
madvise(file_contents, file_stats.st_size + 1, MADV_SEQUENTIAL);
struct {
size_t start;
size_t end;
} longest_lines[10]; // struct to hold ofsets for longest lines
memset(longest_lines, 0, sizeof(longest_lines)); // initialise
int shortest_line_id = 0;
char * start = file_contents;
char * next;
// while we are not at the end
while (start < file_contents + file_stats.st_size) {
if (!(next = strchr(start, '\n'))) // if line ternimator wasn't found, then go to the end
next = file_contents + file_stats.st_size;
size_t line_length = next - start;
// if this line is longer then our shortest
if (line_length > longest_lines[shortest_line_id].end - longest_lines[shortest_line_id].start) {
longest_lines[shortest_line_id].start = start - file_contents;
longest_lines[shortest_line_id].end = next - file_contents;
// determine new shortest line
int i;
for (i = 0; i < sizeof(longest_lines)/sizeof(*longest_lines); i++) {
if (
longest_lines[i].end - longest_lines[i].start
<
longest_lines[shortest_line_id].end - longest_lines[shortest_line_id].start
)
shortest_line_id = i;
}
}
// next line starts at this offset
start = next + 1;
}
int i; // print them
for (i = 0; i < sizeof(longest_lines)/sizeof(*longest_lines); i++) {
printf("%.*s\n", (int)(longest_lines[i].end - longest_lines[i].start), file_contents + longest_lines[i].start);
}
return 0;
}
Because it seems like some kind of homework or sth. I won't provide a full solution. But I will try to give you some peaces that you can start with.
You should divide your task by its responsibilities and try to create your programm a little more modular.
The several tasks seem to be:
Get the count of lines in your file
You can use a loop with fgets and a counter-variable to do this
Allocate a char*-Array (char**)
You can use malloc() with the counter-variable
Read all lines of your file into the allocated memory-sections and allocate memory for each line
You can use getline() to get a pointer to an allocated memory section
Really getline takes a lot work from you, but you can also combine the following functions to to the same:
fgets
malloc
realloc
strlen
Sort your char** by the length of your line
You can use strlen and some swap-function to archieve this (by simple apply sth. like bubblesort)
Output the first N lines of the sortet char** with printf
Don't forget to free all allocated memory with free()!
For Hints see the following post.
Note that the steps I provide do not optimize for memory or cpu-usage. But if your program works and you understoud what you've done this could be the next step.
Also this task shows the great benefits of modern programming languages where such a task would be a five line script. Here's the F# version that performs what you want to archieve:
open System
open System.IO
open System.Linq
[<EntryPoint>]
let main args =
try
let linesToPrint = Int32.Parse(args.ElementAt(0))
let path = #"C:\Users\XYZ\Desktop\test.txt"
File.ReadAllLines(path).OrderByDescending(fun line -> line.Length).Take(linesToPrint).ToArray()
|> Array.iter(printfn "%s")
with
| ex -> eprintfn "%s" ex.Message
0
Note the sortness and the readability of the solution. Nevertheless it is worth to program such a task in c or maybe also in assembler to get a deeper understanding for how computers work.
If you're happy sticking with your maximum line length of 1000 you can do:
char (*ar)[1000] = malloc(a * sizeof *ar);
if (!ar)
// error handling....
and then use ar as you used it before.
There is a problem : fscanf(f,"%s",str). This reads a single word, and does no length checking. I guess you actually want to read a whole line, and not overflow your buffer:
fgets(str, sizeof str, f);
If you want to you can remove the newline from str after this but actually it will make no difference to your program.
There is currently a problem with your algorithm; you read every line into ar. Instead you should just make ar's size be a (I presume that a is meant to be N), take out the line strcpy(ar[i++],str);, and only insert the line if it is bigger than the current smallest member.
Related
I want to approach the string as an array, cut it to a specific length, and store it in a two-dimensional array. For example, I have 20 lines of text file. like this "input.txt"
www.google.com
www.naver.com
kbphonemall.com
kbplant.com
k-bplus.com
kbpointreestore.com
kbprint.com
kbprism.com
kbprivatebanking.com
kbpstore.com
kbr9rtudaf5ppy.com
kbrafting.com
kbraille.com
kbrainbank.com
kbrainbow.com
kbrainc.com
kbrainglocal.com
kbrandexpo.com
kbrandingschool.com
kbrandmall.com
and Then, I read this file and tried to crop it on each line using "\n as the key.
For example If you want to cut four lines at a time, you should cut it to "kbplant.com" first. And the truncated string looks like this.
www.google.com\nwww.naver.com\nkbphonemall.com\nkbplant.com\n
and It will then be stored in a pointer array. like this
char *cutting[n];
cutting[0] = "www.google.com\nwww.naver.com\nkbphonemall.com\nkbplant.com\n"
cutting[1] = "k-bplus.com\nkbpointreestore.com\nkbprint.com\nkbprism.com\n"
.... more
So far, that's the explanation of the functions I want to implement and I'll show you the code.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define IPATH "input.txt"
int main(int argc, char *argv[]) {
char *ListBuffer;
int ListSize;
FILE *InputFile = fopen(IPATH, "r");
fseek(InputFile, 0, SEEK_END);
ListSize = ftell(InputFile);
ListBuffer = malloc(ListSize);
memset(ListBuffer, 0, ListSize);
fseek(InputFile, 0, SEEK_SET);
fread(ListBuffer, ListSize, 1, InputFile);
int count = 0;
ListBuffer[ListSize] = '\0'; //add NULL word
for (int i = 0; i <= ListSize; i++) {
if (ListBuffer[i] == '\n') {
count++;
if (count == 4) {
printf("c%d\n", i);
count = 0;
}
}
if (ListBuffer[i] == 0) {
printf("c%d\n", i);
count = 0;
}
}
fclose(InputFile);
free(ListBuffer);
ListBuffer = NULL;
}
this is my code I have used various functions such as strcpy function, strtok function, memcpy function, etc., but it was difficult to implement the desired function. Is there a better way or algorithm?
If you need more explanation, I'll answer it quickly.
I would appreciate it if you could reply. Have a good day.
It is unclear what you are trying to achieve in the main loop, but there are more problems:
you must allocate one extra byte to set the null terminator at ListSize:
ListBuffer = malloc(ListSize + 1);
it is useless to set the array to 0 with memset: allocating with calloc(1, ListSize + 1) would be more efficient for this purpose, but since you read the contents into the array, clearing it first is useless.
fread might return a short count, for example in text mode on legacy systems, converting CR/LR sequences to newline bytes \n reduces the number of bytes read:
ListSize = fread(ListBuffer, 1, ListSize, InputFile);
ListBuffer[ListSize] = '\0'; // set the null terminator
I need to parse a string in C by removing all non-alphabetic characters from it. To do this I am checking the ascii value of every char and making sure its within the correct bounds. It works just the way I want it to, so that's not the problem. What I am having trouble with, however, is storing the resulting strings after the parse is completed. (I am 3 weeks into C by the way) Also if you notice that I used weird sizes for the arrays, that's because I purposely made them bigger than they needed to be.
char * carry[2]; // This is to simulate argv
carry[1] = "hello1whats2up1"; // 0 is title so I placed at 1
char array[strlen(carry[1])]; // char array of string length
strcpy(array, carry[1]); // copied string to char array
char temp[strlen(carry[1]) + 1]; // Reusable char array
char * finalAnswer[10];
int m = 0, x = 0; // Indexes
if ((sizeof(carry))/8 > 1) { // We were given arguments
printf("Array: %lu\n\n", sizeof(array));
for (int i = 0; i < sizeof(array); i++)
{
if(isalpha(array[i])) { // A-Z & a-z
//printf("%s\n", temp);
temp[x] = array[i]; // Placing chars in temp array
x++;
}
else {
printf("String Length: %lu \nString Name: %s \nWord Index: %d \n\n",
strlen(temp), temp, m); // Testing Purposes
strcpy(finalAnswer[m], temp); // Copies temp into the final answer *** Source of Error
for(int w = 0; w < sizeof(temp); w++) { temp[w] = '\0'; } // Clears temp
x = 0;
m++;
}
}
printf("String Length: %lu \nString Name: %s \nWord Index: %d \n",
strlen(temp), temp, m); // Testing Purposes
strcpy(finalAnswer[m], temp);
for(int w = 0; w < sizeof(temp); w++) { temp[w] = '\0'; } // Clears temp
x = 0;
}
else { printf("No Arguments Given\n"); }
printf("\n");
** Edit
The error I keep getting is when I try copying temp to finalAnswer
** Edit 2
I solved the problem I was having with char * finalAnswer[10]
When I was trying to use strcpy on finalAnswer, I never specified the size that was needed to store the particular string. Works fine after I did it.
Since you have solved the actual string parsing, your last comment, I shall take as the actual requirement.
"... I want to create a list of words with varying length that can be accessed by index ..."
That is certainly not a task to be solved easily if one is "three weeks into C". Data structure that represents that is what main() second argument is:
// array (of unknown size)
// of pointers to char
char * argv[] ;
This can be written as an pointer to pointer:
// same data structure as char * []
char ** list_of_words ;
And this is pushing you straight into the deep waters of C. An non trivial C data structure. As a such it might require a bit more than four weeks of C.
But we can be creative. There is "inbuilt in C" one non trivial data structure we might use. A file.
We can write the words into the file. One word one line. And that is our output: list of words, separated by new line character, stored in a file.
We can even imagine and write a function that will read the word from that result "by index". As you (it seems) need.
// hint: there is a FILE * behind
int words_count = result_size () ;
const char * word = result_get_word(3) ;
Now, I have boldly gone ahead and have written "all" of it, beside that last "crucial" part. After all, I am sure you would like to contribute too.
So the working code (minus the result_size) and result_get_word() ) is alive and kicking here: https://wandbox.org/permlink/uLpAplNl6A3fgVGw
To avoid the "Wrath of Khan" I have also pasted it here:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
/*
task: remove all non alpha chars from a given string, store the result
*/
int process_and_save (FILE *, const char *) ;
int dump_result(FILE *) ;
int main( const int argc, const char * argv[] )
{
const char * filename = "words.txt";
const char * to_parse = "0abra123ka456dabra789" ;
(void)(&argc) ; (void)argv ; // pacify the compiler warnings
printf("\nInput: %s", to_parse ) ;
int retval = process_and_save(fopen(filename, "w"), to_parse ) ;
if ( EXIT_FAILURE != retval )
{
printf("\n\nOutput:\n") ;
retval = dump_result(fopen(filename, "r"));
}
return retval ;
}
int process_and_save (FILE * fp, const char * input )
{
if(!fp) {
perror("File opening failed");
return EXIT_FAILURE;
}
//
char * walker = (char *)(input) ;
while ( walker++ )
{
if ( ! *walker ) break ;
if ( isalpha(*walker) ) {
fprintf( fp, "%c", *walker ) ;
// I am alpha but next one is not
// so write word end, next
if ( ! isalpha(*(walker +1) ) )
fprintf( fp, "\n" ) ;
}
}
fclose(fp);
return EXIT_SUCCESS;
}
int dump_result(FILE* fp )
{
if(!fp) {
perror("\nFile opening failed");
return EXIT_FAILURE;
}
int c; while ((c = fgetc(fp)) != EOF) { putchar(c); }
if (ferror(fp))
puts("\nI/O error when reading");
fclose(fp);
return EXIT_SUCCESS;
}
I think this is functional and does the job of parsing and storing the result. Not in the complex data structure but in the simple file. The rest should be easy. If need help please do let me know.
I'm extremelly new to System Calls. I think I can use Index but not quite sure how to implemented. Let's say I want to look up on a file with data for every ":" and '>"
for example I have a myfile.txt with the following:
hello: this is a testing
for (1=zero i>10);
this line should be written to the two files because it contains both the : and the >
so I'm gonna open two files for writing and one for reading (myfile.txt) and when i find a line containing ":" I'll write it to the colon.txt file. When the line contains ">" character I'll write it to the greaterthen.txt file.
more comments:
Yes I need to implement buffer.
Sorry, I misread the question.
You're going to need to consider several things. Yes, you need a buffer but you also have handle if your line is larger than your buffer. So you need two buffers, one to read into and another to assemble the whole line. While possible to do in one buffer it seemed overly complicated.
My C is pretty rusty but this seems to work. It needs error handling and I'm sure it doesn't match your coding style, but my once & done test seemed to work. The read buffer is abnormally small to demonstrate the condition where a line is larger than the buffer -- in the real world your read buffer would be much larger.
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
int findchar(char*, int, char);
int main()
{
size_t dataIn = open("myfile.txt", O_RDONLY);
size_t gtOut = open("greaterthan.txt", O_CREAT | O_WRONLY);
size_t coOut = open("colon.txt", O_CREAT | O_WRONLY);
char buffer[12];
int iReadCnt;
char* lineBuffer = NULL;
int lineBufferSize = 0;
// read input file until no more input
while (iReadCnt = read(dataIn, buffer, sizeof(buffer)))
{
int x;
int n;
char* b;
// add buffer to dynamic line buffer
n = lineBufferSize + iReadCnt;
b = malloc(n);
memcpy(b, lineBuffer, lineBufferSize);
memcpy(&b[lineBufferSize], buffer, iReadCnt);
// free old buffer if exists
if (lineBuffer)
{
free(lineBuffer);
}
lineBufferSize = n;
lineBuffer = b;
// look for end of line
x = findchar(lineBuffer, lineBufferSize, '\n');
if (x >= 0)
{
int gtPos;
int coPos;
int n;
char* b;
// look for gt
gtPos = findchar(lineBuffer, x, '>');
if (gtPos >= 0)
{
write(gtOut, lineBuffer, x + 1);
}
// look for colon
coPos = findchar(lineBuffer, x, ':');
if (coPos >= 0)
{
write(coOut, lineBuffer, x + 1);
}
// remove line from buffer
n = lineBufferSize - (x + 1);
b = malloc(n);
memcpy(b, &lineBuffer[x + 1], n);
free(lineBuffer);
lineBufferSize = n;
lineBuffer = b;
}
}
// close files
close(dataIn);
close(gtOut);
close(coOut);
exit(0);
}
// simple function to find a character in a buffer
int findchar(char* buffer, int len, char c)
{
int i;
for (i = 0; i < len; ++i)
{
if (buffer[i] == c)
{
return i;
}
}
return -1;
}
this problem I think is solely a lack of memory allocation issue.
(maybe skip to the bottom and read the final question for some simple suggestions)
I am writing this program that reads file(s) entered by the user. If the file 'includes' other files, then they will be read also. To check if another file includes a file, I parse the first word of the string. To do this I wrote a function that returns the parsed word, and a pointer is passed in that gets set to the first letter of the next word. For example consider the string:
"include foo" NOTE files can only include 1 other file
firstWord == include, chPtr == f
My algorithm parses firstWord to test for string equality with 'include', it then parses the second word to test for file validity and to see if the file has already been read.
Now, my problem is that many files are being read and chPtr gets overwritten. So, when I return the pointer to the next word. The next word will sometimes contain the last few characters of the previous file. Consider the example files named testfile-1 and bogus:
Let chPtr originally equal testfile-1 and now consider the parsing of 'include bogus':
extracting firstWord will == include, and chPtr will be overwritten to point to the b in bogus. So, chPtr will equal b o g u s '\0' l e - 1. the l e - 1 is the last few characters of testfile-1 since chPtr points to the same address of memory each time my function is called. This is a problem for me because when I parse bogus, chPtr will point to the l. Here is the code for my function:
char* extract_word(char** chPtr, char* line, char parseChar)
//POST: word is returned as the first n characters read until parseChar occurs in line
// FCTVAL == a ptr to the next word in line
{
int i = 0;
while(line[i] != parseChar && line[i] != '\0')
{
i++;
}
char* temp = Malloc(i + 1); //I have a malloc wrapper to check validity
for(int j = 0; j < i; j++)
{
temp[j] = line[j];
}
temp[i+1] = '\0';
*chPtr = (line + i + 1);
char* word = Strdup(temp); //I have a wrapper for strdup too
return word;
So, is my problem diagnosis correct? If so, do I make deep copies of chPtr? Also, how do I make deep copies of chPtr?
Thanks so much!
If I understand this correctly you want to scan a file and when an 'include' directive is encountered you want to scan the file specified in the the 'include' directive and so on ad infinitum for any levels of include i.e. read one file which may include other files which may in turn include other files.....
If that is so (and please correct if I am wrong ) then this is a classic recursion problem. The advantage of recursion is that all variables are created on the stack and are naturally freed when the stack unwinds.
The following code will do this without any need for malloc or free or the need to make copies of anything:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define INCLUDE "include"
#define INCOFFSET 7
static void
process_record (char *name, char *buf)
{
// process record here
printf ("%s:%s\n", name, buf);
}
// change this to detect your particular include
static int
isinclude (char *buf)
{
//printf ("%s:Record %s INCLUDE=%s INCOFFSET=%d\n", __func__, buf, INCLUDE,
// INCOFFSET);
if (!strncmp (buf, INCLUDE, INCOFFSET))
{
//printf ("%s:Record == include", __func__);
return 1;
}
return 0;
}
static int
read_file (char *name)
{
//printf ("%s:File %s\n", __func__, name);
FILE *fd = fopen (name, "r");
if (!fd)
{
printf ("%s:Cannot open %s\n", __func__, name);
return -1;
}
char buf[1024];
ssize_t n;
while (fgets (buf, sizeof (buf), fd))
{
size_t n = strcspn (buf, "\n");
buf[n] = '\0';
//printf ("%s:Buf %s\n", __func__, buf);
if (isinclude (buf))
{
read_file (buf + (INCOFFSET + 1));
}
else
{
process_record (name, buf);
}
}
fclose (fd);
return 0;
}
int
main (int argc, char *argv[])
{
int ret = read_file (argv[1]);
if (ret < 0)
{
exit (EXIT_FAILURE);
}
exit (EXIT_SUCCESS);
}
char* temp = Malloc(i + 1); //I have a malloc wrapper to check validity
for(int j = 0; j < i; j++)
{
temp[j] = line[j];
}
temp[i+1] = '\0'; <------- subscript out of range replace with temp[i] = '\0';
It isn't clear where your problem is. But you might use a tool to help locate it.
Valgrind is one such (free) tool. It will detect a variety of memory access errors. (It likely would not have found your temp[i+1]='\0' error because that isnt "very wrong").
Our CheckPointer tool is another tool. It finds errors Valgrind cannot (e.g., e.g., it should have found your buggy temp assignment). While it is commercial, the evaluation version handles programs of small size, which may work for you. (I'm at home and don't remember the limits).
I love the ideas presented in Brian Kernighan and Rob Pike's book, "The UNIX Programming Environment," where they focus on the point of working within an environment where you can put together many (small, precise, well understood) programs on the command line to accomplish many programming tasks.
I'm brushing up on strict ANSI C conventions and trying to stick to this philosophy. Somewhere in this book (I can get an exact page number if needed) they suggest that all programs in this environment should adhere to the following principles:
If input is presented on the command line, as an argument to the program itself, process that input.
If no input is presented on the command line, process input from stdin.
Here's a C program I wrote that will echo any input (numeric or alphabetic) that is a palindrome. My question specifically:
Is this a well behaved C program? In other words, is this what Kernighan and Pike were suggesting is the 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;
}
Yes, I think that you are following the R&K advice. As Hugo said, you could take the argumentas a filename, bu,t IMHO, for this simple program, I'd say that taking the parameter as the palindrome itself may make more sense.
Also, if you allow me extra advice, I would separate the functionality of reading a string from checking whether it is a palindrome or not, because you have that 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's a palyndrome */
}
Of course, I am pretty sure this can be improved (it may even have a bug, I am typping quite fast), but once that you have your string, be either from command line or user input, you can call this function or a functiom like this.
NOTE: Edited to reflect comment from Mark, thanks a lot, Mark!
One problem that you have is a potential buffer overflow because you are writing an input of arbitrary length into a buffer with a fixed size. You can fix this by rejecting too long inputs or creating an array of the correct size dynamically. I would avoid using scanf.
Regarding the actual algorithm, you don't need to copy the string reversed and then compare the two strings. You could do the check using only a single copy of the string and a pointer at both ends, both moving in towards 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 factor the palindrome checking code out into a separate function.
Regarding the principles you specified, I believe that these tools usually take their arguments as filenames whose content is to be processed. Instead, you are treating them like the input itself.
Take sort, for example. If you don't specify any arguments, the contents from stdin will be sorted. Otherwise, the contents in the file whose filename you specified will be sorted. It is not the arguments themselves that are processed.
The code for this would be something along these lines:
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
Also, you should be careful with the buffer overflow specified by Mark Byers.
You're not handling the string reading correctly. The i_string buffer is not initialized, and even if it were, you're should limit the number of bytes that scanf reads to avoid the mentioned overflow:
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 must always reserve one more byte (1000 vs 999) to account for the NULL string terminator. If you want to allow arbitrary length strings, I think you'll have to dinamically allocate the buffer, and resize it in case bigger strings are present. This would be slightly more complicated.
It is useful for text filters such as a program that prints only lines with palindromes to specify input files via command line arguments e.g., it allows:
$ palindromes input*.txt # file patterns
$ find -name '*.txt' -print0 | xargs -0 palindromes
It is common convention that is supported by many languages. Below are scripts in Perl, Python, C that has the same usage:
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;
}
Exit status is 0 if any line was selected, 1 otherwise;
if any error occurs, the exit status is 2. It uses GNU getline() that allows arbitrary large lines as an input.