2

This questions applies to the JavaScript language broadly, but the specific context I encountered this issue with was while solving Linked List problems on LeetCode. In 206. Reverse LInked List, you can use Javascript's Destructuring Assignment feature to avoid using temp variables as you shuffle around pointers between current and previous, for example this is an accepted solution where destructuring assignment correctly changes the .next pointers in a singly linked object:

/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */
var reverseList = function(head) {
    let [prev, curr] = [null, head]
    
    while (curr != null) {
        [curr.next, curr, prev] = [prev, curr.next, curr]
    }
    
    return prev
}

Then why does this solution work for 141. Linked List Cycle...

var hasCycle = function(head) {
    if (!head) return false
    
    let [slow, fast] = [head, head]
    while (fast) {
        if (!fast.next) return false
            
        slow = slow.next
        fast = fast.next.next
        // [slow, fast] = [slow.next, fast.next.next]
        
        if (slow == fast) return true
    }
    
    return false
};

but the almost identical solution, which attempts to use destructuring assignment to merely save space, fails to update the pointer value (causing false positives as the initial values pointing to head are never changed)? Also, the act of console logging before using destructuring assignment changes the behavior - instead of the false positive, now the code throws a runtime error for TypeError: Cannot set property '#<ListNode>' of undefined

var hasCycle = function(head) {
    if (!head) return false
    
    let [slow, fast] = [head, head]
    while (fast) {
        if (!fast.next) return false
            
        // slow = slow.next
        // fast = fast.next.next
        console.log(slow.val, fast.val)
        [slow, fast] = [slow.next, fast.next.next]
        
        if (slow == fast) return true
    }
    
    return false
};

The only difference between the failed usage in 141 and the successful usage in 206 is using .next.next instead of simply .next, which leads me to believe that destructuring assignment is failing for a reason that is related to nested objects or pointers, but I can't find a solution on StackOverflow or by googling.

Why does the line before Javascript Destructuring Assignment fail to automatically insert a semicolon using ASI?

auphynne
  • 78
  • 8
  • just strictly based on the error message, it seems that the value of `fast.next` or `fast.next.next` seems to be `undefined`, and i believe this happens at the very end of your iterations, have you checked using a debugger on which case this fails? – tsamridh86 Oct 03 '21 at 03:03
  • @SamridhTuladhar fair question, but I can confirm this does *not* happen at the end of my iterations. if I do not use destructuring, I can log afterwards to confirm that slow and fast are updated to point to different linked list nodes, while logging after using destructuring does not throw (as opposed to logging before, as shown) and reveals that slow and fast are not updated at all, and still point to `head` from their initialization. I can't explain why it throws, but I am confident it is not because it's out of bounds of the end of the array. – auphynne Oct 03 '21 at 18:10
  • if destructing had to fail `let [slow, fast] = [head, head]` would have failed in the first place, let's gather some debugging data first to understand why this is happening, please check in your debugger, and let us know what you find out – tsamridh86 Oct 04 '21 at 01:06
  • 1
    I've realized that by adding a semicolon after the if statement guard preceding the destructuring assignment fixes this bug, and the code runs as expected. So changing `if (!fast.next) return false` to `if (!fast.next) return false;` is a fix. This does not answer the question, why does this happen? I'm now wondering if this behavior is not at all about destructuring assignment, but perhaps an inline if, or the return functionality.. I'll try poking around in VS Code debugging soon, just haven't gotten around to manually creating the linked list data structure sorry – auphynne Oct 20 '21 at 23:09
  • 1
    I misunderstood the source of this behavior - this appears to be a result of Automatic Semicolon Insertion (ASI) not inserting a semicolon in a place I had assumed it would. This seems to be a known behavior in JavaScript - I'm educating myself more on the cases where it is necessary to insert a semicolon manually, but this resolves my question. This article seems to shine light on known edge cases: https://flaviocopes.com/javascript-automatic-semicolon-insertion/ – auphynne Oct 20 '21 at 23:32

1 Answers1

1

Automatic Semicolon Insertion (ASI) does not insert a semicolon on the end of a line of code when combining the following line would result in "valid" javascript code.

More specifically, common examples would be when the following line begins with either a ( or [, which javascript may interpret as a call to a function or index of an array, respectively.

Examples illustrating when a semicolon needs to be inserted include:

let a = 0
let b = a
(0 == false) ? findTruth() : contemplate()
// interpreted as
// let a = a(0 == false) ...

or, within the context of the example,

let a = 0
let b = 1
[a, b] = [b, a]
// interpreted as 
// let b = 1[a, b] = [b, a]

I found that this reference was illustrative on the types of edge cases where ASI will not insert semicolons: https://flaviocopes.com/javascript-automatic-semicolon-insertion/

auphynne
  • 78
  • 8