4

This was an interview question and I thought I'd share it with the rest of you.

How does one efficiently add to the tail of a linked list without using an "if"?

Remove the if from this function. (? is still an if).

typedef struct tNode_ tNode;

struct tNode_ {
  tNode* pNext;
  int data;
};

tNode* pHead = NULL;
tNode* pTail = NULL;   

AddTail(tNode* pNode){
  if(!pTail) {
    pHead = pNode;
  }
  else {
    pTail->pNext = pNode;
  }
  pTail = pNode;
  pTail->pNext = NULL;
}
templatetypedef
  • 362,284
  • 104
  • 897
  • 1,065
kdubs
  • 1,596
  • 1
  • 21
  • 36
  • 2
    This strikes me as an extremely silly interview question - why would anyone disallow you use of a fundamental programming construct? Additionally, can you please post more code here? You haven't defined `pHead`, `pTail`, or the shape of the struct `tNode`. – templatetypedef Jan 02 '13 at 01:58
  • the idea was to think about what you are actually trying to accomplish. There's a simple solution if you look and the true end goal. – kdubs Jan 02 '13 at 02:01
  • @templatetypedef: See this question about [branch prediction](http://stackoverflow.com/questions/11227809/why-is-processing-a-sorted-array-faster-than-an-unsorted-array). – krlmlr Jan 02 '13 at 02:01
  • @user946850- That strikes me as an egregious microoptimiation. Unless this code is absolutely time-critical, this seems like an unnecessary complication. – templatetypedef Jan 02 '13 at 02:02
  • @templatetypedef: It might be, you never know. Stamping this question as silly is, well, you know... let's say, ignoring its beauty. – krlmlr Jan 02 '13 at 02:03
  • @user946850 If that function is called frequently enough for the `if` to potentially matter, the branch predictor will have a perfect playing field. – Daniel Fischer Jan 02 '13 at 02:04
  • @DanielFischer: What about add^n + remove^n, n varying between 1 and, say, 5? – krlmlr Jan 02 '13 at 02:06
  • 3
    I think the interviewer was looking for you to add a sentinel object, ensuring that `pHead` is never `NULL`. – Sergey Kalinichenko Jan 02 '13 at 02:10
  • 1
    @user946850 Then any half-sane person would use a circular buffer, and not a linked list. – Daniel Fischer Jan 02 '13 at 02:10
  • If you are allowed to change the node/list structures, you could use something similar to [AmigaOS linked lists](http://gega.homelinux.net/AmigaDevDocs/lib_23.html). The head and tail pointers are always valid, so adding a node to the head or the tail of the list is very efficient. – Some programmer dude Jan 02 '13 at 02:15

4 Answers4

2

One (admittedly silly) way to do this is to use the behavior of the short-circuiting logical operators && and ||. For example, you could do this:

  AddTail(tNode* pNode){
      pTail || (pHead = pNode);
      !pTail || (pTail->pNext = pNode);

      pTail = pNode;
      pTail->pNext = NULL;
 }

This works because if ptail is null, the first part of the first statement will evaluate to false, forcing evaluation of the second half of the || statement. If it's non-null, the second half of the statement will not be evaluated. Similar logic works for the next statement.

That said, this is a very silly interview question and I honestly don't know what they're getting at. Personally, I would question the wisdom of someone trying to assess your ability to write code like this.

Hope this helps!

templatetypedef
  • 362,284
  • 104
  • 897
  • 1,065
1
typedef struct tNode_ tNode;

struct tNode_ {
  tNode* pNext;
  int data;
};

/* normal initialization for pHead */

tNode* pHead = NULL;

/* the trick is to point at where you want the next pointer to go
 * instead of saving a pointer to the node.
 */
tNode** ppTail = &pHead;

AddTail(tNode* pNode){
  pNode->pNext = NULL;
  /* update the next pointer */
  *ppTail = pNode;
  /* save the address of the next pointer */
  ppTail = &pNode->pNext;
}

main(){
  int cnt;

  tNode* pNew;

  for(cnt=0; cnt<10; cnt++){
    pNew = malloc(sizeof(tNode));
    pNew->data = cnt;
    AddTail(pNew);
  }

  for(pNew = pHead; pNew; pNew = pNew->pNext){
    printf("%d\n", pNew->data);
  }
}
kdubs
  • 1,596
  • 1
  • 21
  • 36
  • What is the benefit of introducing an extra layer of indirection when the current version works perfectly well? I'm skeptical that this is a fundamentally more elegant solution to the problem. – templatetypedef Jan 02 '13 at 02:03
  • getting rid of a branch is a good thing for branch prediction. But the question is an attempt to get one to think about the end goal. – kdubs Jan 02 '13 at 02:10
  • @kdubs- I'm not a hardware expert, but isn't the cost of a pointer indirection that isn't in cache much greater than the cost of a branch misprediction? (See the numbers here: https://gist.github.com/2841832). But more importantly - I fundamentally disagree with the idea that this is trying to get you to think about "the end goal." The code you've posted in the original question is absolutely standard linked list manipulation code. Saying that the use of an `if` statement misses sight of "the end goal" seems very misguided. But that's just my opinion. – templatetypedef Jan 02 '13 at 02:12
  • it's the same about of hardware access. either way you have to access the memory at that next pointer. This way you avoid a read, and only do a write. – kdubs Jan 02 '13 at 03:59
1

I would use a dummy element (or sentinel node as they call it) for each list. Appending to the head becomes appending to the dummy, appending to the tail is just appending to the last element which then by definition exists.

typedef struct tNode_ tNode;

struct tNode_ {
  tNode* pNext;
  int data;
};

// The Remove operation must never remove this dummy!
tNode* pDummy = (tNode*)calloc(1, sizeof(tNode));
tNode* pHead = pDummy;
tNode* pTail = pDummy;

AddTail(tNode* pNode){
  pTail->pNext = pNode;
  pTail = pNode;
  pTail->pNext = NULL;
}
krlmlr
  • 25,056
  • 14
  • 120
  • 217
1
tNode pHead;
tNode pTail;

void init() {
  pHead.pNext = &pTail;
  pTail.pNext = &pHead;
}

AddTail(tNode* pNode){
  pTail.pNext->pNext = pNode;
  pNode->pNext = &pHead;
  pTail.pNext = pNode;
}
perreal
  • 94,503
  • 21
  • 155
  • 181