13

I'm Learning data structure with javascript

and my focus now on how to implement deque?

Edite: from comments below I get useful directions on how to implement deque based array. Is there a direction how to implement deque based object using class ?

enter image description here

I get understand some points like I need :

  • addFront()
  • removeFront()
  • peekFront()

  • addBack()
  • removeBack()
  • peekBack()

but I'm confused about some points :

  • how many pointers I need ? at least I know from queue I need two(head-tail) pointer but not sure if I need more in deque

  • which data type in javascript convenient in this case as a base? I saw some tutors in youtube talking about circular array for example which unknown for me in JS.

edite2:

I was following a book called: learning javascript data structures and algorithms 3rd edition

in chapter5 of this book the author started to implement Deque based on object only and some variables

but I didn't understand how he did that because the code encrypted but I can still reach to his files from and test his approach github repository

I can say that @trincot answer very close of book author approach

but when I compare the results I get this [1 = author - 2 = @trincot] : enter image description here

according to the book index taking about linked list comes in chapter6 so I didn't expect his solution will be based on something he didn't mentioned before

plz if I miss any point I will be grateful to tell me it ... thanks

Ayman Morsy
  • 1,279
  • 1
  • 10
  • 28
  • 7
    the default JS array already is such a structure: `push()`, `pop()`, `shift()`, `unshift()` as well as standard index accessing should give you all the tools you need. – Sirko Feb 04 '20 at 07:58
  • @Sirko do you mean I don't need `deque` and just `default JS array` do the job ? – Ayman Morsy Feb 04 '20 at 08:09
  • @Sirko I get understand that `default JS array` allow random access to data while `deque` will not allow to add,modify or delete from the middle just from the end – Ayman Morsy Feb 04 '20 at 08:14
  • @AymanMorsy, if that is your concern, then just invalidate all other methods that could be used on your array. – trincot Feb 04 '20 at 08:26
  • 1
    An JS array supports all methods you need. If any other method is to be suppressed, then you will need a wrapper or something similar to restrict access (maybe also to `map()`, ...). – Sirko Feb 04 '20 at 08:34
  • @AymanMorsy - Just use an array as your base data structure (it automatically handles all pointers internally) and wrap it in a class/object to restrict access to the underlying array – slebetman Feb 04 '20 at 08:35
  • this is great things to know, this comments so helpful on how to implement `deque based array` but let me being greedy how to implement `deque based object` also using class and constructor ? – Ayman Morsy Feb 04 '20 at 08:48
  • Which implementation are you looking for? One with pointers or one with circular array? They are different. – trincot Feb 04 '20 at 09:54
  • @AymanMorsy What do you mean by "*deque based object*"? – Bergi Feb 04 '20 at 09:59
  • Deque supports both stack and queue operations and implemented either using a doubly linked list or circular array. So how you want to go its upto you – Manjeet Thakur Feb 04 '20 at 10:02
  • @Bergi I mean using Object as a container of data rather than array like `trincot` answer. could you read my question post again I added more details – Ayman Morsy Feb 04 '20 at 14:34
  • @ManjeetThakur can you take a look on my question post again I added more details... is that a third implementation approach from author ? thanks – Ayman Morsy Feb 04 '20 at 14:38
  • "*I didn't understand how he did that because the code encrypted*" - [no it's not](https://github.com/PacktPublishing/Learning-JavaScript-Data-Structures-and-Algorithms-Third-Edition/blob/master/LearningJavaScriptDataStructuresandAlgorithmsThirdEdition_Code/src/js/data-structures/deque.js). You might have seen the minified code in the demo page though. – Bergi Feb 04 '20 at 14:39
  • @Bergi the code encrypted but you can download it and test it locally – Ayman Morsy Feb 04 '20 at 14:41
  • "*when I compare the results I get this*" - I don't see much of a difference. Sure, he named it `items` instead of `data`, but after all it's quite similar. No linked lists in either implementation. – Bergi Feb 04 '20 at 14:42
  • @AymanMorsy Please follow the link I just posted. There is no "encryption" at all. – Bergi Feb 04 '20 at 14:43
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/207192/discussion-between-ayman-morsy-and-bergi). – Ayman Morsy Feb 04 '20 at 14:43

4 Answers4

14

As stated in comments, JavaScript has native support for deque operations via its Array class/prototype: push, pop, shift, unshift.

If you still want to write your own implementation, then you can go for a doubly linked list, where you just need two "pointers". It should be said that in JavaScript we don't really speak of pointers, but of objects. Variables or properties that get an object as value, are in fact references in JavaScript.

Alternatively, you can go for a circular array. Since in JavaScript standard Arrays are not guaranteed to be consecutive arrays as for example is the case in C, you don't really need to use an Array instance for that. A plain object (or Map) will do.

So here are two possible implementations:

Doubly Linked List

class Deque {
    constructor() {
        this.front = this.back = undefined;
    }
    addFront(value) {
        if (!this.front) this.front = this.back = { value };
        else this.front = this.front.next = { value, prev: this.front };
    }
    removeFront() {
        let value = this.peekFront();
        if (this.front === this.back) this.front = this.back = undefined;
        else (this.front = this.front.prev).next = undefined;
        return value;
    }
    peekFront() { 
        return this.front && this.front.value;
    }
    addBack(value) {
        if (!this.front) this.front = this.back = { value };
        else this.back = this.back.prev = { value, next: this.back };
    }
    removeBack() {
        let value = this.peekBack();
        if (this.front === this.back) this.front = this.back = undefined;
        else (this.back = this.back.next).back = undefined;
        return value;
    }
    peekBack() { 
        return this.back && this.back.value;
    }
}

// demo
let deque = new Deque;
console.log(deque.peekFront()); // undefined
deque.addFront(1);
console.log(deque.peekBack()); // 1
deque.addFront(2);
console.log(deque.removeBack()); // 1
deque.addFront(3);
deque.addFront(4);
console.log(deque.peekBack()); // 2
deque.addBack(5);
deque.addBack(6);
console.log(deque.peekBack()); // 6
console.log(deque.removeFront()); // 4
console.log(deque.removeFront()); // 3
console.log(deque.removeFront()); // 2
console.log(deque.removeFront()); // 5
console.log(deque.removeFront()); // 6
console.log(deque.removeFront()); // undefined

Circular "Array"

class Deque {
    constructor() {
        this.data = {}; // Or Array, but that really does not add anything useful
        this.front = 0;
        this.back = 1;
        this.size = 0;
    }
    addFront(value) {
        if (this.size >= Number.MAX_SAFE_INTEGER) throw "Deque capacity overflow";
        this.size++;
        this.front = (this.front + 1) % Number.MAX_SAFE_INTEGER;
        this.data[this.front] = value;
    }
    removeFront()   {
        if (!this.size) return;
        let value = this.peekFront();
        this.size--;
        delete this.data[this.front];
        this.front = (this.front || Number.MAX_SAFE_INTEGER) - 1;
        return value;
    }
    peekFront()     { 
        if (this.size) return this.data[this.front];
    }
    addBack(value) {
        if (this.size >= Number.MAX_SAFE_INTEGER) throw "Deque capacity overflow";
        this.size++;
        this.back = (this.back || Number.MAX_SAFE_INTEGER) - 1;
        this.data[this.back] = value;
    }
    removeBack()   {
        if (!this.size) return;
        let value = this.peekBack();
        this.size--;
        delete this.data[this.back];
        this.back = (this.back + 1) % Number.MAX_SAFE_INTEGER;
        return value;
    }
    peekBack()     { 
        if (this.size) return this.data[this.back];
    }
}

// demo
let deque = new Deque;
console.log(deque.peekFront()); // undefined
deque.addFront(1);
console.log(deque.peekBack()); // 1
deque.addFront(2);
console.log(deque.removeBack()); // 1
deque.addFront(3);
deque.addFront(4);
console.log(deque.peekBack()); // 2
deque.addBack(5);
deque.addBack(6);
console.log(deque.peekBack()); // 6
console.log(deque.removeFront()); // 4
console.log(deque.removeFront()); // 3
console.log(deque.removeFront()); // 2
console.log(deque.removeFront()); // 5
console.log(deque.removeFront()); // 6
console.log(deque.removeFront()); // undefined

Methods will return undefined, when an attempt is made to retrieve a value from an empty deque.

trincot
  • 317,000
  • 35
  • 244
  • 286
  • I suggest adding `delete` to the remove methods, so that this doesn't leak memory. – kaya3 Feb 04 '20 at 13:12
  • @kaya3, good suggestion! Added `delete` in the second snippet. – trincot Feb 04 '20 at 13:28
  • @trincot you are very close... I made some edite in my Q can you see it and give me an idea is that third possible implementation or just your answer and need some improve or some changing to reach the result I see above ... thanks so much – Ayman Morsy Feb 04 '20 at 14:18
  • thanks alot trincot ... with the help of @Bergi get the source code of the third implementation I hope to help anyone https://github.com/PacktPublishing/Learning-JavaScript-Data-Structures-and-Algorithms-Third-Edition/blob/master/LearningJavaScriptDataStructuresandAlgorithmsThirdEdition_Code/src/js/data-structures/deque.js – Ayman Morsy Feb 04 '20 at 15:34
  • @AymanMorsy, that implementation you refer to is really inferior. It performs a `for` loop to shift all items with one position, whenever you do an `addFront` and `lowestCount` is zero. That is really killing the performance gain you would expect to have from a deque. Moreover, JavaScript has an `unshift` method that does just all that, but more efficiently. An efficient implementation does not need to loop. – trincot Feb 04 '20 at 15:41
  • yes, I heard from @Bergi also that there some performance issue. for me as beginner I can't judge which is better :). I appropriate your opinion so this may help anyone later will read this book and implementing his approach... thanks – Ayman Morsy Feb 04 '20 at 15:50
  • downvoted because native Array does *not* have native deque operations...deque is constant time, Array is constant time for push/pop, but O(N) time for shift/unshift. – Alexander Mills Dec 04 '22 at 03:03
  • @Alexander, time complexity is an aspect of *algorithm*, not of *data structure*. This is also why [Wikipedia](https://en.wikipedia.org/wiki/Double-ended_queue) doesn't mention time complexity as a requirement, but lists compeixity as an attribute of several different *implementations*, and lists JavaScript as having a deque implementation with the methods `push`, `pop`, `unshift`, `shift`. – trincot Dec 04 '22 at 07:41
  • I don't believe deque is hyper-formalized, but this article does mention constant time operations as being indicative - https://en.wikipedia.org/wiki/Double-ended_queue – Alexander Mills Dec 04 '22 at 07:50
  • That's the article I referred to. Like I said, it lists several implementations which do not have the O(1) complexity for all four methods, and includes JavaScript as having an implementation. – trincot Dec 04 '22 at 07:56
  • So, I reverted your edit to my answer, as in my opinion (and intent of writing the answer), we *can* say JavaScript implements a deque, despite what we might think of the time complexity. – trincot Dec 04 '22 at 08:16
7

Dequeue implementation in a simple way:

const dequeue = [];

// push element from rear end
dequeue.push(3); // [3]
dequeue.push(8); // [3, 8]

// push element from front end
dequeue.unshift(5); // [5, 3, 8]
dequeue.unshift(11); // [11, 5, 3, 8]     

// pop element from rear end
dequeue.pop(); // [11, 5, 3]

// pop element from front end
dequeue.shift(); // [5, 3]
Ashish Kumar
  • 401
  • 4
  • 11
2

The tricot's Doubly Linked List, but refactored and typed:

type DequeNode<T> = {
  value: T;
  prev?: DequeNode<T>;
  next?: DequeNode<T>;
};

class Deque<T = any> {
  front?: DequeNode<T>;
  back?: DequeNode<T>;

  constructor(...initialValues: T[]) {
    initialValues.forEach(initialValue => {
      this.addBack(initialValue);
    });
  }

  addFront(value: T) {
    if (!this.front) {
      this.front = this.back = { value };
      return;
    }

    this.front = this.front.next = { value, prev: this.front };
  }

  removeFront() {
    if (!this.front) {
      return;
    }

    const value = this.peekFront();

    if (this.front === this.back) {
      this.front = undefined;
      this.back = undefined;
      return value;
    }

    (this.front = this.front.prev!).next = undefined;
    return value;
  }

  peekFront() {
    return this.front?.value;
  }

  addBack(value: T) {
    if (!this.front) {
      this.front = this.back = { value };
      return;
    }

    this.back = this.back!.prev = { value, next: this.back };
  }

  removeBack() {
    if (!this.back) {
      return;
    }

    const value = this.peekBack();

    if (this.front === this.back) {
      this.front = undefined;
      this.back = undefined;
      return value;
    }

    (this.back = this.back.next!).prev = undefined;
    return value;
  }

  peekBack() {
    return this.back?.value;
  }
}
Eugene P.
  • 1,517
  • 12
  • 16
0

As with any other attempt at understanding new stuff, it is helpful to have a comparative approach.

JS arrays are deques because you can modify the front out-of-the-box. You don't get this in Python where the out-of-the-box list structure supports only modification in the back (calling it append and pop). If you need to start adding and removing front items you need to explicitly add support for deques (by adding from collections import deque at the top of the module ) and create the object with a dedicated constructor (d = deque([1,2,3]).Only then you can perform popleft and appendleft operations (called unshift and shift in JS)

Again, none of this is required in JS, the implementation of JS array supports this OOTB. To get a feeling for terminology across the language landscape see the Wikipedia's table at

https://en.wikipedia.org/wiki/Double-ended_queue#Operations

pavol.kutaj
  • 401
  • 1
  • 7
  • 14
  • 4
    The concern is the time-complexity of JavaScript's array operations. Popping an item from the front of a true Deque would have a complexity of `O(1)`, whereas popping from the front of a simple array is `O(n)` due to each successive item needing to be shifted by one position left. – Todd May 05 '22 at 14:24
  • for more on the above, see https://leetcode.com/explore/learn/card/fun-with-arrays/525/inserting-items-into-an-array/3244/ – pavol.kutaj May 09 '22 at 06:54