Recursive function : abort-condition - c

We need to create a binary tree which contains content of textfiles. The pointer selection_a and selection_b pointing to another textfile in the directory.
The structure of the textfiles is following:
line: Title
line: OptionA
line: OptionB
line: Text.
The first file is given as parameter while starting the program. All files should be saved at the beginning of the program. Then the text of the first file shows, and the user can input A or B to continue. Based on the selection, the text of File Option A/B is shown and the user can decide again.
The last file of a tree contains no Options: lines 2 and 3 are "-\n".
The problem is, this code only reads all the option A files of the first tree. It doesn't read in any B-Options. In the end, the program shows a memory access error.
I think the problem is that the readingRows function has no abort condition.
current->selection_a = readingRows(input_selection_a);
current->selection_b = readingRows(input_selection_b);
I know the code may be kind of chaotic, but we are beginners in programming. Hope anybody can help us to write an abort-condition.
The function should be aborted if the content of option A (line 3) is "-\n".
Here is the whole function:
struct story_file* readingRows(FILE *current_file)
{
char *buffer = fileSize(current_file);
char *delimiter = "\n";
char *lines = strtok(buffer, delimiter);
int line_counter = 0;
struct story_file *current = malloc(sizeof(struct story_file));
while(lines != NULL)
{
if(line_counter == 0)
{
current->title = lines;
}
else if(line_counter == 1)
{
char *filename_chapter_a = lines;
FILE *input_selection_a = fopen(filename_chapter_a, "r");
if(input_selection_a)
{
current->selection_a = readingRows(input_selection_a);
}
fclose(input_selection_a);
}
else if(line_counter == 2)
{
char *filename_chapter_b = lines;
FILE *input_selection_b = fopen(filename_chapter_b, "r");
if(input_selection_b)
{
current->selection_b = readingRows(input_selection_b);
}
fclose(input_selection_b);
}
else if (line_counter >= 3)
{
current->text = lines;
}
lines = strtok(NULL, delimiter);
line_counter++;
}
return current;
}

There are two items that define a terminating recursive function:
One or more base cases
Recursive calls that move toward a base case
Your code has one base case: while (lines!=NULL) {} return current;, it breaks the while loop when lines is NULL and returns current. In other words, within any particular call to your function, it only terminates when it reaches the end of a file.
Your code moves toward that base case as long as your files do not refer to each other in a loop. We know this because you always read a line, take an action according to your if-else block, and the read the next line. So you always move toward the end of each file you read.
But as you note, the issue is that you don't have a case to handle "no Options", being when lines 2 or 3 are "-\n". So right now, even though you move through files, you are always opening files in line 2. Unless a file is malformed and does not contain a line 2, your recursive call tree never ends. So you just need to add another base case that looks at whether the beginning of lines matches "-\n", and if it does, return before the recursive call. This will end that branch of your recursive tree.
Inside of your while loop, you will need code along the lines of:
if `line_counter` is `2` or `3`
if `lines` starts with your terminating sequence "-\n"
return current
else
`fopen` and make the recursive call
In the parent function that made the recursive call, it will move to the next line and continue as expected.
P.S. Make sure you use free for each malloc you do.

Related

Find and replace a line in a file

My goal is to search the file line by line until it finds a variable declaration in the format of varName = varValue. Count up the bytes to the beginning of that line, then replace that line with the same varName but a new value.
This is a very simple configuration file handler, I'm writing it from scratch to avoid any dependencies. The reason I'm doing it this way and not just dumping a string[string] associative array is because I want to preserve comments. I also wish to refrain from reading the entire file into memory, as it has the potential to get large.
This is the code I have written, but nothing happens and the file remains unchanged when using setVariable.
import std.stdio: File;
import std.string: indexOf, strip, stripRight, split, startsWith;
import std.range: enumerate;
ptrdiff_t getVarPosition(File configFile, const string varName) {
size_t countedBytes = 0;
foreach (line, text; configFile.byLine().enumerate(1)) {
if (text.strip().startsWith(varName))
return countedBytes;
countedBytes += text.length;
}
return -1;
}
void setVariable(File configFile, const string varName, const string varValue) {
ptrdiff_t varPosition = getVarPosition(configFile, varName);
if (varPosition == -1)
return; // For now, just return. This variable doesn't exist.
// Will handle this later, it needs to append to the bottom of the file.
configFile.seek(varPosition);
configFile.write(varName ~ " = " ~ varValue);
}
There's a few parts of your code missing, which makes diagnosis hard. The most important question may be 'How do you open your config file?'. This code does what I expect it to:
unittest {
auto f = File("foo.txt", "r+");
setVariable(f, "var3", "foo");
f.flush();
}
That is, it finds the line starting with "var3", and replaces part of the file with the new value. However, your getVarPosition function doesn't count newlines, so the offset is wrong. Also, consider what happens when the new varValue is a different length from the old value. If you have "var = hello world", and call setVariable(f, "var", "bye"), you'll end up with "var = byelo world". If it's longer than the existing value, it will overwrite the next variable(s).

What can be used to replace the if statement in this bit of code written in C?

The following is my attempt to scan a file to search and see if an entered username is taken already. The code doesn't work because of the if statement.
for (x = 0; x < 100; x++) {
/* for loop allows the user to keep entering usernames until they come up with an unused username. */
FILE *userlist; /*The userlist file holds all the taken usernames */
userlist = fopen("userlist.txt", "r");
fseek(userlist, x, SEEK_SET); /* This starts the search at the place x, which is incremented each time. */
fscanf(userlist, " %s", &username_ext); /* This scans the file contents after x and stores it in the username_ext variable */
if (strcmp(username, username_ext) == 0) { /* If the username entered and the strings after the x are the same, the loop terminates. */
printf("\nThe username is already taken.");
break;
}
fclose(userlist);
}
The code will never work, especially if each entry in the file is of variable length.
Instead you should open the file before the loop, skip the seeking (which will almost never work in a text file, and especially not the way you show it being used). Then you can read a string (using either fgets or fscanf) and compare to the username given.
Something like the following pseudo-ish code:
file = open_file()
while (fscanf("%s", username_ext) == 1)
{
if (strcmp(username, username_ext) == 0)
{
// Username found
}
}
To explain why the code, as shown in the question, will never work, the fseek call will set the position to the offset x in the file from the start. And that offset is in bytes, not in "records" or "elements".
And if the input file is a text file where each record is of different length, there is simply no way of seeking to a specific record without knowing its position beforehand.

Why does my function return 0 instead of the number of lines in a file?

I have written a function that is supposed to read the number of sentences in a .txt file, but when the function is called and done, it gives me a value of 0.
This program over all has 3 more functions to figure out different properties of the file and I have them working great. This one is laid out the same way I wrote my other functions just looking for some advice on why I am getting 0 as my number of sentences.
void ptrCntS (FILE* sp1, int sCount)
{
char sentence = 'O';
int myChr;
if (!(sp1 = fopen("Hello.txt", "r")))
{
printf("error opening Hello.txt");
return(1);
}
while ((myChr = fgetc(sp1)) != EOF)
{
if ('.')
{
sentence ='O';
}
else if (sentence == 'O')
{
sCount++;
sentence = 'I';
}
}
fclose(sp1);
printf ("Total number of sentences are:\t%d", sCount);
return;
}
instead of return use return(sCount);
and assign the return value to some int variable in calling function
like
int sentCount;
.
.
.
sentCount=ptrCntS (param1,param2);
if ('.') is always true, thus else... code never reached. Use if( myChr == '.' ) instead.
Function compiles now and runs properly. This function is being called from a switch in a previous function where I had my addresses set and included my print statement for the totals so that I would not have to write another function in the end to call on all my counts and print their results. Instead I set my case 'A': to call all of my counting functions(in this case that is what the original code is) and than display my results. I am sorry for any lengthiness or my hard to understand writing I am new to the C language and I am having a hard time grasping the literature but making some process on understanding the syntax.

Where am I going wrong in getting this function to do what I would like?

I have written the following function in my C program. The program loads a text file (Les Miserables Vol. I) as well as another text file of 20 of the characters names. The purpose of this function is to scan the entire file, line by line, and count the number of times any of the 20 names appear.
NumOfNames = 20.
Names is an array of the 20 names stored from Names[1] - Names[20].
MaxName is a global integer variable which I would like to store the total number of name appearances throughout the file (It should be in the hundreds or even thousands).
EDIT: After the function is executed, the value of MaxName is 4. I am completely lost as to where I have made a mistake, but it appears that I have made several mistakes throughout the function. One seems to be that it only executed the first iteration of the for loop i.e. it only searches for Name[1], however the first name appears 196 times in the file, so it still isnt even working correctly for just the first name.
void MaxNameAppearances()
{
char LineOfText[85];
char *TempName;
FILE *fpn = fopen(LesMisFilePath, "r+");
for(i = 1; i<=NumOfNames; i++)
{
while(fgets(LineOfText, sizeof(LineOfText), fpn))
{
TempName = strstr(LineOfText, Names[i]);
if(TempName != NULL)
{
MaxName++;
}
}
}
fclose(fpn);
}
I guess that one problem of the code is that it would have to read the file upon every iteration of i. Try to re-order the loops like this:
while(fgets(LineOfText, sizeof(LineOfText), fpn))
{
for(i = 1; i<=NumOfNames; i++)
{
TempName = strstr(LineOfText, Names[i]);
if(TempName != NULL)
{
MaxName++;
}
}
}
This reads a line, checks the occurrances of all names in that line and then goes on to the next line.
If you do it your way, you will be at the end of file for i == 1 already.

Bus Error on void function return

I'm learning to use libcurl in C. To start, I'm using a randomized list of accession names to search for protein sequence files that may be found hosted here. These follow a set format where the first line is a variable length (but which contains no information I'm trying to query) then a series of capitalized letters with a new line every sixty (60) characters (what I want to pull down, but reformat to eighty (80) characters per line).
I have the call itself in a single function:
//finds and saves the fastas for each protein (assuming on exists)
void pullFasta (proteinEntry *entry, char matchType, FILE *outFile) {
//Local variables
URL_FILE *handle;
char buffer[2] = "", url[32] = "http://www.uniprot.org/uniprot/", sequence[2] = "";
//Build full URL
/*printf ("u:%s\nt:%s\n", url, entry->title); /*This line was used for debugging.*/
strcat (url, entry->title);
strcat (url, ".fasta");
//Open URL
/*printf ("u:%s\n", url); /*This line was used for debugging.*/
handle = url_fopen (url, "r");
//If there is data there
if (handle != NULL) {
//Skip the first line as it's got useless info
do {
url_fread(buffer, 1, 1, handle);
} while (buffer[0] != '\n');
//Grab the fasta data, skipping newline characters
while (!url_feof (handle)) {
url_fread(buffer, 1, 1, handle);
if (buffer[0] != '\n') {
strcat (sequence, buffer);
}
}
//Print it
printFastaEntry (entry->title, sequence, matchType, outFile);
}
url_fclose (handle);
return;
}
With proteinEntry being defined as:
//Entry for fasta formatable data
typedef struct proteinEntry {
char title[7];
struct proteinEntry *next;
} proteinEntry;
And the url_fopen, url_fclose, url_feof, url_read, and URL_FILE code found here, they mimic the file functions for which they are named.
As you can see I've been doing some debugging with the URL generator (uniprot URLs follow the same format for different proteins), I got it working properly and can pull down the data from the site and save it to file in the proper format that I want. I set the read buffer to 1 because I wanted to get a program that was very simplistic but functional (if inelegant) before I start playing with things, so I would have a base to return to as I learned.
I've tested the url_<function> calls and they are giving no errors. So I added incremental printf calls after each line to identify exactly where the bus error is occurring and it is happening at return;.
My understanding of bus errors is that it's a memory access issue wherein I'm trying to get at memory that my program doesn't have control over. My confusion comes from the fact that this is happening at the return of a void function. There's nothing being read, written, or passed to trigger the memory error (as far as I understand it, at least).
Can anyone point me in the right direction to fix my mistake please?
EDIT: As #BLUEPIXY pointed out I had a potential url_fclose (NULL). As #deltheil pointed out I had sequence as a static array. This also made me notice I'm repeating my bad memory allocation for url, so I updated it and it now works. Thanks for your help!
If we look at e.g http://www.uniprot.org/uniprot/Q6GZX1.fasta and skip the first line (as you do) we have:
MNAKYDTDQGVGRMLFLGTIGLAVVVGGLMAYGYYYDGKTPSSGTSFHTASPSFSSRYRY
Which is a 60 characters string.
When you try to read this sequence with:
//Grab the fasta data, skipping newline characters
while (!url_feof (handle)) {
url_fread(buffer, 1, 1, handle);
if (buffer[0] != '\n') {
strcat (sequence, buffer);
}
}
The problem is sequence is not expandable and not large enough (it is a fixed length array of size 2).
So make sure to choose a large enough size to hold any sequence, or implement the ability to expand it on-the-fly.

Resources