1

During this stayhome period I decided to dive into TypeScript and started to practice it by implementing some basic data structures. I am trying to implement a custom stack that uses custom nodes.

My StackNodes are defined like this:

class StackNode {

  private val: any;
  private nxt: StackNode | undefined = undefined;

  constructor(val: any, nxt?: StackNode | undefined) {
    this.val = val;
    this.nxt = nxt || undefined;
  }

  get value(): any {
    return this.value;
  }

  get next(): StackNode | undefined {
    return this.next;
  }

}

export default StackNode;


And the actual Stack:

class Stack {

  private capacity!: number;
  private top?: StackNode | undefined = undefined;
  private size: number = 0;

  constructor(capacity: number, initialValues?: Array<any>) {
    this.capacity = capacity;

    if (initialValues) {
      this.size = initialValues.length;
      this.top = this._initStack(initialValues, initialValues.length - 1);
    }

  };

  private _initStack = (array: Array<any>, idx: number): StackNode => {
    if (idx == 0) {
      return new StackNode(array[idx], undefined);
    } else {
      return new StackNode(array[idx], this._initStack(array, idx-1));
    }
  }

  pop(): any {
    const value = this.top?.value();
    this.top = this.top?.next();
    return value;
  }

}

export default Stack;

The problem here is the line with the optional chaining operator in pop-method this.top = this.top?.next()

What I have understood is that the expression this.top?.next() should be equivalent to

(this.top === null || this.top === undefined)? undefined : this.top.next()

but I still get the error

Cannot invoke an object which is possibly 'undefined'.ts(2722)

when the call is made even though it shouldn't be undefined anymore at that stage.

Why's that? What am I missing here? Both the StackNode.nxt and Stack.top are allowed to be undefined. I have tried to do it in the old way like this:

if (this.top !== null || this.top !== undefined) {
  const value = this.top.value()
  this.top = this.top.next()
}

But I still get the same error, even though here it should be sure that the this.top can not be undefined, but has to be, or at least should be, of type StackNode.

How this should work is that when popping from empty stack, pop method would return undefined and when popping the last element, its next, that is undefined, is set as the top of the stack.

I am using TS 3.8.3

zaplec
  • 1,681
  • 4
  • 23
  • 51
  • I've edited my answer to include an example of generics to solve this problem, as you are learning typescript. Wish you well! – Rubydesic Apr 05 '20 at 18:04

1 Answers1

1

You define next as a getter, so it must be accessed like so: this.top = this.top?.next

The only reason that const value = this.top?.value(); even compiles is because you use 'any' (DONT DO THAT, EVER!!), and typescript assumes that get value might return a function that you are invoking.

You should define StackNode using generics. For example,

class StackNode<T> {

  private val: T;
  private nxt: StackNode<T> | undefined = undefined;

  constructor(val: T, nxt?: StackNode<T> | undefined) {
    this.val = val;
    this.nxt = nxt || undefined;
  }

  get value(): T {
    return this.value;
  }

  get next(): StackNode<T> {
    return this.next;
  }

}


class Stack<T> {

  private capacity!: number;
  private top?: StackNode<T> | undefined = undefined;
  private size: number = 0;

  constructor(capacity: number, initialValues?: Array<any>) {
    this.capacity = capacity;

    if (initialValues) {
      this.size = initialValues.length;
      this.top = this._initStack(initialValues, initialValues.length - 1);
    }

  };

  private _initStack = (array: Array<any>, idx: number): StackNode<T> => {
    if (idx == 0) {
      return new StackNode(array[idx], undefined);
    } else {
      return new StackNode(array[idx], this._initStack(array, idx-1));
    }
  }

  pop(): T | undefined {
    const value = this.top?.value(); //doesn't compile
    this.top = this.top?.next(); //doesn't compile either
    return value;
  }

}

Then, const value = this.top?.value(); would not have compiled either.

Rubydesic
  • 3,386
  • 12
  • 27
  • Oh okay, that's something I have completely missed! Thought getters are accessed like any other methods. – zaplec Apr 05 '20 at 18:06
  • What it comes to the 'any', there's very probably a better way to do it, but at the moment I don't know what it is. – zaplec Apr 05 '20 at 18:08
  • 1
    @zaplec Reload your page, I edited in the better way to do it :) – Rubydesic Apr 05 '20 at 18:08
  • And right when I posted that, you had edited your answer with the details :D Thanks a lot! I will check the generics too. Much appreciated answer! – zaplec Apr 05 '20 at 18:09