'Unable to convert class syntax to function syntax for a Linked List

Okay so I'm learning Data Structures where my tutorial is following class syntax. So now I am trying to convert it to function declarations as I want to practice Linked Lists with that notation. Can anybody help me with the conversion? I need only one method to be converted and I'll be able to do the rest of them.

Here's the code I have with class syntax:

class Node {
    constructor( val ) {
        this.val = val;
        this.next = null;
    }
}

class SinglyLinkedList {
    constructor () {
        this.head = null;
        this.tail = null;
        this.length = 0;
    }

    push( val ) {
        const newNode = new Node( val );

        if ( !this.head ) {
            this.head = newNode;
            this.tail = this.head;
        } else {
            this.tail.next = newNode;
            this.tail = newNode;
        }
        this.length++
        return this;
    }

}

const list = new SinglyLinkedList();

list.push( 'HELLO' );
list.push( 'WORLD' );

Here's what I tried so far to achieve function-based approach:

function Node( val ) {
  this.val = val;
  this.next = null;
}

let head = null;
let tail = null;
let length = 0;

const SinglyLinkedListFunc = function() {
    const newNode = new Node( 'HELLO' );
};

SinglyLinkedListFunc()

This is what I could achieve so far. And I'm stuck with it and I don't know what to do next. Someone please help



Solution 1:[1]

functional heritage

The linked list is a functional structure and so using it with functional style yields the best results. This means avoiding things like mutation and variable reassignments. Below we implement a functional-style immutable linked list in an object-oriented interface -

class Node {
  constructor(head, tail) {
    this.head = head
    this.tail = tail
  }
}

const empty = Symbol("empty")
  
class List {
  constructor(t = empty) {
    this.t = t
  }
  push(v) {
    return new List(new Node(v, this.t))
  }
  toString() {
    if (this.t == empty)
      return "?"
    else
      return `${this.t.head} -> ${new List(this.t.tail)}`
  }
}

const l = (new List).push("a").push("b").push("c")
console.log(String(l))
console.log(String(l.push("d").push("e").push("f")))
console.log(String(l))

Notice how each call to .push returns a new, unmodified list -

c -> b -> a -> ?
f -> e -> d -> c -> b -> a -> ?
c -> b -> a -> ?

modules preferred, classes optional

But I think you can do a lot better than that. By mixing functional designs and concerns directly with class-oriented semantics, we end up with strange and often inefficient programs. In functional style we write modules with ordinary functions. If an object-oriented interface is desired, only a thin wrapper around our plain functions is needed -

// list.js
const empty = Symbol("empty")

const pair = (left, right) =>
  ({ left, right })

const push = (t, v) =>
  pair(v, t)

const list = (...args) =>
  args.reduce(push, empty)

const toString = t =>
  t == empty
    ? "?"
    : `${t.left} -> ${toString(t.right)}`
  
class List {
  constructor(t = empty) { this.t = t }
  static empty() { return new List() }
  static of(...args) { return new List(list(...args)) }
  push(v) { return new List(push(this.t, v)) }
  toString() { return toString(this.t) }
}

export { default:List, empty, pair, push, list, toString }

In your main module we will import list from our list module -

// main.js
import List from "./list.js"

const l = List.empty().push("a").push("c").push("d")
console.log(String(l))
console.log(String(l.push("d").push("e").push("f")))
console.log(String(l))
c -> b -> a -> ?
f -> e -> d -> c -> b -> a -> ?
c -> b -> a -> ?

Expand the snippet below to verify the results in your browser -

// list.js
const empty = Symbol("empty")

const pair = (left, right) =>
  ({ left, right })

const push = (t, v) =>
  pair(v, t)

const list = (...args) =>
  args.reduce(push, empty)

const toString = t =>
  t == empty
    ? "?"
    : `${t.left} -> ${toString(t.right)}`
  
class List {
  constructor(t = empty) { this.t = t }
  static empty() { return new List() }
  static of(...args) { return new List(list(...args)) }
  push(v) { return new List(push(this.t, v)) }
  toString() { return toString(this.t) }
}

// main.js
const l = List.empty().push("a").push("c").push("d")
console.log(String(l))
console.log(String(l.push("d").push("e").push("f")))
console.log(String(l))

Solution 2:[2]

Here's the functional approach:

function Node( val, next ) {
  this.val = val;
  this.next = null;
}
 
const SinglyLinkedListFunc = function() {
    this.head = null;
    this.tail = null;
    this.length = 0;
};

SinglyLinkedListFunc.prototype.push = function( val ) {

  const newNode = new Node( val );

  if( !this.head ){
    this.head = newNode;
    this.tail = newNode;
  } else{
    this.tail.next = newNode;
    this.tail = newNode;
  }

    this.length++;
    return this;
}


let list = new SinglyLinkedListFunc();
list.push('HELLO');
list.push('WORLD');

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 s.khan