Disclaimer: I'm allowed to talk about the interview question since I already was rejected by the company and never had to sign an NDA before taking the interview test anyhow.
Also, this isn't a "Write my code for me" post. I'm just curious about understanding the setup of this problem. It wanted me to fill in the body of a function that removes an element from a linked list:
typedef struct
{
int val;
node * next;
} node;
void remove_val(node ** arr, int i)
{
/* Write the procedure here */
}
I was confused about why the problem had a pointer-to-a-pointer as a parameter. I would expect that the parameter to a function would be the root of the list, which would be a pointer to a node. Right? Any idea what the first parameter was supposed to be???
What happens if you remove the head of the list? You need some way to communicate that back to the calling code. This function signature allows you to change the head of the list to point to its next, if necessary.
Related
I'm fairly new to coding in c and have stumbled upon a problem. The code below is what I have so far.
The trouble I'm running into is the syntax, more specifically on how to loop through the list. I've spent the past week looking at different resources but none explained how to work with a similar struct data type. The way I've attempted to loop through the list results in an error, and I'm having trouble adding the job in the right place since the list is in increasing order - based on the reference number.
The other problem I've run into is within function 5. I'm not entirely too sure of how to return both an integer and character value through a char pointer, unless you return just the entire job_node. As of now, I have casted the value of the node to be a char.
Since I haven't coded much in c - any other suggestions on how to make the code more efficient without changing the values of the functions would be extremely helpful.
A linked list is a container of nodes, and each node has some data payload. Often each data record has a key, a value used to compare two records. In students' assignments in general the data is a simple letter or a number. In your case is a pointer to a "job post".
Below I will show you an alternative way to write his. But first...
It is important to note that the list is a container of nodes, not a list of job posts. A list is not a node, a node is not a list, and the data is not a node.
Mixing these things like you did in your program leads only to problems: you will not be able to use the list to contain other data, for example. You will have trouble changing the implementation of the list without changing the data. You will need to change the list functions if you change a field in the job post. Only problems.
example
bool job_insert(int refNum, char job_name[], struct job_list * list);
should be like
int insert_l( Job* job_post ,List* the_list ,int(*compare_function)(void*,void*));
Why?
Using the former way you can use just a pointer to Job --- in general you should use a void* so you can use anything inside the data record without the need to change the arguments list.
this way if you add a field to Job or change perhaps the reference number to a char[] to add a check-digit you do not need to change a thing here
compare_function() compare two Job and return -1 if the 1st is greater, 1 if the 2nd is greater, or 0. This way you can change the sorting criteria at any time, just writing a new 10-line function, and have records inserted by new criteria. C qsort() uses this, C++ sort() uses this, Borland Turbo Pascal used this in the 80;s. It is simple and flexible.
the function can be as simples as
int compare_reference(void* a, void* b)
{
int first = ((Job*) a)->reference_number;
int second = ((Job*) b)->reference_number;
if (first < second ) return 1;
if (first > second ) return -1;
return 0;
};
and used like this
int res = insert_l( one_job, new_jobs, compare_reference );
and if res is 0 one_job was inserted in new_jobs list at the correct position. It is also easier to read: insert record into list according to criteria.
And if at any time you need to use descending order or sort by another criteria you just need to pass a new function. This means that you can use the same List-related functions to contain any data. This is the reason to use only void pointers to the data element also...
Back to the program
This could be a test file to drive your tests:
1 engineer
2 magician
3 astronaut
4 actress
5 truck driver
6 writer
The structures
typedef struct
{
int reference_number;
char* job_name;
} Job;
typedef struct node_
{
Job* info; // pointer to data record
struct node_ *next;
struct node_ *prev;
} Node;
typedef struct
{
unsigned size; // actual size
unsigned capacity; // limit in # of nodes
Node* head;
Node* tail;
} List;
And you have a List that is a collection of Node. Each Node has a pointer info that point to a Job, that is the data record. Inside a Job you have the reference number and the job's name. reference_number is called key because it is the field used to compare Job records.
The List has no reference to Job, only to Node. Node has no data, just a pointer. So to use List to contain songs or cars or books like the next assignment in line is easy ;)
the functions
List* create_l(unsigned); // return new List
List* delete_l(List*); // erase one
int insert_l( Job*,List*,int(*)(void*,void*)); // insert based on function
List* merge_l(List*,List*,int(*)(void*,void*) ); // merge 2 lists into a 3rd
int remove_by_ref_l(int,List*); // delete a record
char* search_job_l(int,List*); // search for job description for a reference#
I believe that now you see some added convenience:
insert() receives just a pointer to a Job and to a List, and the name of a function to compare 2.
create() returns a pointer to a new List with the limit set to a max # of nodes
remove() gets a reference number and if it is found in List removes the record and returns 0
search() works the same way
I suggest you write this way. If you need help I can provide more code. I had no time today to write a full example.
Seemingly simple C code is seemingly not allowing me to remove the first element from a linked list. I can, however, successfully remove any other individual element and can successfully delete the whole linked list.
typedef struct list{
int data;
struct list * next;
} list;
void remove_element(list * node, unsigned int index){
if (node == NULL)
exit(-1);
list *currElem = node;
if (index == 0) {
node = node->next;
currElem->next = NULL;
free(currElem);
return;
}
Produces the follwing:
"free(): invalid pointer: 0xbfabb964"
I've followed the same format for all of my other manipulation functions with no issues. Similar threads on forums don't seem to be dealing with this particular problem.
You can read the explanation in this pdf on the Push function which explains it:
http://cslibrary.stanford.edu/103/
This is where c gets funky pschologically. You instinctively want to label a pointer as a pointer, which it is. But it is a pointer value, not a pointer reference. It's like the holy spirit of the C divinty. The triumvirate. C passed arguments to functions by value, not by address/reference. So, what do you do to pass a variable by reference? Remember, the solution is so obvious, it really didn't make sense to me for a week, I swear to god.
In sys/queue.h there defines a data structure TAILQ. It is very popularly used throughout Linux kernel. Its definition is like this:
#define TAILQ_ENTRY(type) \
struct { \
struct type *tqe_next; /* next element */ \
struct type **tqe_prev; /* address of previous next element */ \
}
I am a little baffled at this code: what is the advantage to have tqe_prev pointing the tqe_next of the previous node? If it was me, I would have tqe_prev directly pointing to the previous node, similar to tqe_next pointing to the next node.
One reason I'd think of, when we insert a node, we directly operate on the pointer to be updated, we do not need to go through its owning node first. But is that it? Any other advantages?
I am wondering how we can travel backwards of the queue? When we have a pointer to a node, since its tqe_prev does not point to the previous node, we have no way to go through the queue till the head. Or such backward travel is by design not supported by TAILQ?
Oh, interesting. I didn't know this technique had any other users (I came up with it myself).
The reason to do things this way is that there may not be a "previous node": The first element does not have a predecessor, but it does have a pointer pointing to it.
This simplifies several operations. For example, if you want to delete a node given only a pointer to it, you can do this:
void delete(struct node *p) {
*p->tqe_prev = p->tqe_next;
if (p->tqe_next) {
p->tqe_next->tqe_prev = p->tqe_prev;
}
free(p);
}
If you had a pointer to the preceding node, you'd have to write this:
void delete(struct node *p) {
if (p->tqe_prev) {
p->tqe_prev->tqe_next = p->tqe_next;
} else {
???
}
if (p->tqe_next) {
p->tqe_next->tqe_prev = p->tqe_prev;
}
free(p);
}
... but now you're stuck: You can't write the ??? part without knowing where the root of the list is.
Similar arguments apply to insert operations.
Backwards traversal is indeed not a priority for this kind of structure. But it can be done if must be (but only if you know for sure that you are not at the root, i.e. you know there actually is a previous node):
#include <stddef.h>
struct node *prev(struct node *p) {
return (struct node *)((unsigned char *)p->tqe_prev - offsetof(struct node, tqe_next));
}
We know that p->tqe_prev is the address of a .tqe_next slot within a struct node. We cast this address to (unsigned char *) so we can do bytewise pointer arithmetic. We subtract the (byte) offset of .tqe_next within the struct node structure (offsetof macro courtesy of <stddef.h>). This gives us the address of the beginning of the struct node structure, which we finally cast to the right type.
Linus answered the question in https://meta.slashdot.org/story/12/10/11/0030249/linus-torvalds-answers-your-questions.
The quote is as follows:
At the opposite end of the spectrum, I actually wish more people understood the really core low-level kind of coding. Not big, complex stuff like the lockless name lookup, but simply good use of pointers-to-pointers etc. For example, I've seen too many people who delete a singly-linked list entry by keeping track of the "prev" entry, and then to delete the entry, doing something like
if (prev)
prev->next = entry->next;
else
list_head = entry->next;
and whenever I see code like that, I just go "This person doesn't understand pointers". And it's sadly quite common.
People who understand pointers just use a "pointer to the entry pointer", and initialize that with the address of the list_head. And then as they traverse the list, they can remove the entry without using any conditionals, by just doing a "*pp = entry->next".
I have linklist c-program which is working correctly, but a late requirement was that no global variables must be use. So I had to redesigned everything to avoid passing out parameters, but there are parts which is hard to redesign and some of the functions are calling functions and another functions, one example is on the display.
int main() {
display(&head, &tail);
}
void display(myrec **head, myrec **tail) {
sort(&*head, &*tail)
then sort again call function swap
}
My question is how to correctly pass out parameters multiple times. Program is not working correct using sort(&*head, &*tail), there is no syntax error but there are now missing entries on the data. I tried to code everything on the display function just to check and it does, so I am guessing that I am doing something wrong in passing the variables. thanks.
Its not recursion, the head and tail are variables that holds the state of the linklist, so in swap it is change, I also need to reflect the changes to the root caller and other part of the program need the new state of the head and tail.
Re-passing the out parameters to another function is adding "&" again and again.
exiting display will make head and tail variable reflect the changes made on the other functions. Not sure if its the best way but it is working.
Ex.
display(&head, &tail);
void display(myrec **head, myrec **tail) {
sort(&*head, &*tail)
}
void sort(myrec **head, myrec **tail) {
swap(&*head, &*tail)
}
I'm trying to write a program to play "Pangolin" (like this guy - it asks yes/no questions, walking down a binary tree until it gets to a leaf node. It then "guesses", and if the user says the answer was wrong, asks the user what they were thinking of and for a question that distinguishes that from the incorrect gues. It then adds the new data to the tree).
This is the my struct for a tree node. NodeType is QUESTION_NODE for nodes containing a question or OBJECT_NODE for nodes containing an "object" - that is the thing the program deduces the user to be thinking of. Question nodes have pointers to child nodes - one for yes and one for no.
typedef struct _TreeNode {
NodeType type;
union {
char* question;
char* objectName;
} nodeString;
//children for yes and no answers: will be invalid when type is OBJECT_NODE
struct _TreeNode* yes;
struct _TreeNode* no;
} TreeNode;
As this is a learning exercise, I'm trying to do it with double pointers. Here is the function that is supposed to add a question node to the tree:
void addData(TreeNode** replace, char* wrongGuess) {
//create a new object node for what the user was thinking of
// ... (code to get user input and build the new object node struct) ... //
//create a new question node so we don't suck at pangolin so much
// ... (code to get a question from the user and put it in a question node struct) ... //
//link the question node up to its yes and no
printf("What is the answer for %s?\n", newObjectName);
if (userSaysYes()) {
newQuestionNodePtr->yes = newObjectNodePtr;
newQuestionNodePtr->no = *replace;
}
else {
newQuestionNodePtr->no = newObjectNodePtr;
newQuestionNodePtr->yes = *replace;
}
//redirect the arc that brought us to lose to the new question
*replace = newQuestionNodePtr;
}
The addData function is then called thus:
void ask(node) {
//(... ask the question contained by "node" ...)//
//get a pointer to the pointer that points to the yes/no member pointer
TreeNode** answerP2p;
answerP2p = userSaysYes() ? &(node.yes) : &(node.no);
//(... the user reports that the answer we guessed was wrong ...)//
puts("I am defeated!");
//if wrong, pass the pointer to pointer
addData(answerP2p, answerNode.nodeString.objectName);
My (presumably wrong) understanding is this:
In "ask()", I am passing addData a pointer which points to "node"'s member "yes" (or no). That member is in turn a pointer. When, in addData, I assign to "*replace", this should modify the struct, redirecting its "yes" (or no) member pointer to point to the new question node I have created.
I have debugged and found that the newQuestionNode and newObjectNode are created successfully. newQuestionNode's children are correctly assigned. However the new question node is not inserted into the tree. The "*replace = newQuestionNodePtr" line does not have the effect I would expect, and the node referred to by "node" in the "ask" scope does not have its child pointer redirected.
Can anyone see what is wrong in my understanding? Or perhaps a way in which I haven't expressed it right in my code? Sorry this question is so long.
You should not declare the pointer you pass to the function as a double pointer. Instead pass the address of a single pointer to the function:
TreeNode* answerP2p;
answerP2p = userSaysYes() ? node.yes : node.no;
addData(&answerP2p, answerNode.nodeString.objectName);
Unfortunately I don't quite understan'd Joachim Pileborg's answer above, but I eventually sussed my problem and I guess it's a fairly common mistake for new C-farers[1], so I'll post it here in my own terms.
In my hasty transition from Java to C I had told myself "OK, structs are just objects without methods". Assessing the validity of this simplification is left as an exercise to the reader. I also extended this assumption to "when an argument is of a struct type, it is automatically passed by reference". That's obviously false, but I hadn't even thought about it. Stupid.
So the real problem here is that I was passing ask() a variable of type TreeNode for its node argument. This entire struct was being passed by value (of course). When I passed answerP2p to addData(), it was actually working correctly, but it was modifying ask()'s local copy of the TreeNode. I changed ask() to take a TreeNode* and lo, there was a tree.
C what I did there[1]?