Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

avc278 - 3.01 - JavaScript #76

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
164 changes: 164 additions & 0 deletions JavaScript/chapter03/p01_three_in_one/avc278.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
// Three in One: Describe how you could use a single array to implement three stacks.
const assert = require("assert");
const { Stack, arrayToStack } = require("../../lib/avc278/stacksAndQueues");

/**
* We start off by assuming we cannot use the benefits that dynamic languages like JavaScript bring to the table, so as
* to provide a language-agnostic solution. Hence, upon creation of an array, we must declare the size, and performing a
* `push()` on the array does not allocate additional memory.
*
* Say we have three stacks of different lengths; we should first start off by storing the length of the longest stack,
* and creating a pseudo array whose length is 3 times that length, which we'll refer to as N. Then, at indices 0, N/3,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* and creating a pseudo array whose length is 3 times that length, which we'll refer to as N. Then, at indices 0, N/3,
* and creating an array whose length is 3 times that length, which we'll refer to as N. Then, at indices 0, N/3,

the array that is being created is pretty real. nothing pseudo about it.

also, what if instead of asking for the total array length, the maximum length of each stack is passed as parameter? would that simplify stuff?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm yeah definitely changing the signature seems to be the way to go here. I'll tinker around and see what I can come up with

* and 2N/3, we declare the starting positions of the stacks. At this point, we need to be careful when defining peek,
* pop, and push stack-like behavior for this pseudo array class we created. We need to make sure that should we ever
* pop() or push() outside the bounds of the allotted space for the subject stack in the array, we do nothing (ideally,
* we should raise a verbose exception explaining why the expected behavior never happened).
*
* The following is an implementation of the above description:
*/

class _Array {
constructor(arrLength) {
this.arr = new Array(arrLength).fill(null);
this.stackOneStartIdx = 0;
this.stackOneCurrIdx = 0;
this.stackTwoStartIdx = arrLength / 3;
this.stackTwoCurrIdx = arrLength / 3;
this.stackThreeStartIdx = 2 * arrLength / 3;
this.stackThreeCurrIdx = 2 * arrLength / 3;
Comment on lines +23 to +28
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if instead of implementing three stacks you had to implement four, what would need to be changed?

(spoiler alert: next question is going to be "how about five?")

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

D: oh boy.

Alright, so before I go into coding, I'm going to write down a few notes just to get my brain going:
so, if our array can store N stacks, then on instantiation of the array, we need to tell it some more stuff.
And its class properties would be slightly different. perhaps three arrays:

  1. the regular array this.arr
  2. an array storing starting indices this.stackStartIdx
  3. an array storying curr indices this.stackCurrIdx
    and this way, we can accommodate for the case where we want to store N stacks of different lengths :)

};

pop(stackNum) {
let poppedVal;
switch (stackNum) {
case 1:
if (this.arr[this.stackOneCurrIdx] === null) return;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what if i really wanted to store a null?

poppedVal = this.arr[this.stackOneCurrIdx];
this.arr[this.stackOneCurrIdx] = null;
this.stackOneCurrIdx = Math.max(this.stackOneCurrIdx - 1, this.stackOneStartIdx);
return poppedVal;
case 2:
if (this.arr[this.stackTwoCurrIdx] === null) return;
poppedVal = this.arr[this.stackTwoCurrIdx];
this.arr[this.stackTwoCurrIdx] = null;
this.stackTwoCurrIdx = Math.max(this.stackTwoCurrIdx - 1, this.stackTwoStartIdx);
return poppedVal;
case 3:
if (this.arr[this.stackThreeCurrIdx] === null) return;
poppedVal = this.arr[this.stackThreeCurrIdx];
this.arr[this.stackThreeCurrIdx] = null;
this.stackThreeCurrIdx = Math.max(this.stackThreeCurrIdx - 1, this.stackThreeStartIdx);
return poppedVal;
default:
return;
}
};
peek(stackNum) {
switch (stackNum) {
case 1:
return this.arr[this.stackOneCurrIdx];
case 2:
return this.arr[this.stackTwoCurrIdx];
case 3:
return this.arr[this.stackThreeCurrIdx];
default:
return;
}
};
push(stackNum, val) {
switch (stackNum) {
case 1:
if (this.stackOneCurrIdx === this.stackTwoStartIdx - 1) return;
avc278 marked this conversation as resolved.
Show resolved Hide resolved
if (this.arr[this.stackOneCurrIdx] !== null) this.stackOneCurrIdx += 1;
this.arr[this.stackOneCurrIdx] = val;
break;
case 2:
if (this.stackTwoCurrIdx === this.stackThreeStartIdx) return;
if (this.arr[this.stackTwoCurrIdx] !== null) this.stackTwoCurrIdx += 1;
this.arr[this.stackTwoCurrIdx] = val;
break;
case 3:
if (this.stackThreeCurrIdx === this.arr.length) return;
if (this.arr[this.stackThreeCurrIdx] !== null) this.stackThreeCurrIdx += 1;
this.arr[this.stackThreeCurrIdx] = val;
break;
default:
return;
}
};
};

// Helper function to convert a stack from A -> B -> C to C -> B -> A, and return its length
const invertStackAndGetLength = (stack) => {
const res = new Stack();
let length = 0;
while (!stack.isEmpty()) {
length += 1;
res.push(stack.pop())
}
return [ res, length ];
}

/**
* @param {Stack} A input stack
* @param {Stack} B input stack
* @param {Stack} C input stack
* @return {_Array} the array containing three stacks, with stack methods
*
* In implementing threeInOne, we need to know the longest stack length, which we'll call N, and the others M and P.
* As we traverse through these stacks, we do so one after the other, which results in O(N + M + P), which is really
* just O(N) as we can remove smaller terms. Creating the inverse stacks requires a maximum of O(N) space for the same
* reason.
* Now, when creating the output array (of type _Array), we require 3 * N additional time and space for traversal.
* Again, however, this results in O(N) time and O(N) space as we can ignore the leading term.
*
* Runtime: O(N)
* Space: O(N)
*
*/
const threeInOne = (A, B, C) => {
const [ invA, lenA ] = invertStackAndGetLength(A);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

normally, queue and stack implementations have an O(1) .length()/.size() operation (e.g. https://en.cppreference.com/w/cpp/container/stack/size). and an array-to-stack-then-inverted is essentially a queue, so some of the complexity needed to set this up can be avoided.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh interesting!
I'll have to add the size() method to the library file in a future commit.
I'm wondering how I would solve this problem without the use of the queue class. Only using the given stacks, and the arrays.... Maybe as I'm pushing elements from the stacks into the arrays, I do so from the end to the start! That might work and resolve the issue of the unnecessary added complexity. I'll try that and send it in a future commit

const [ invB, lenB ] = invertStackAndGetLength(B);
const [ invC, lenC ] = invertStackAndGetLength(C);

const maxSize = Math.max(lenA, lenB, lenC);
const arr = new _Array(maxSize * 3);
while (!invA.isEmpty()) {
arr.push(1, invA.pop());
}
while (!invB.isEmpty()) {
arr.push(2, invB.pop());
}
while (!invC.isEmpty()) {
arr.push(3, invC.pop());
}

return arr;
};

describe(module.filename, () => {
it("should return an array containing the three input stacks.", () => {
const A = arrayToStack([1,2,3]);
const B = arrayToStack([4,5]);
const C = arrayToStack([6,7,8,9,10]);
const stackArr = threeInOne(A, B, C);

const expectedStackArr = [1,2,3,null,null,4,5,null,null,null,6,7,8,9,10];
assert.deepStrictEqual(stackArr.arr, expectedStackArr);
});
it("should behave like a stack when pushing, popping, and peeking any of the containing stack", () => {
const A = arrayToStack([1,2,3]);
const B = arrayToStack([4,5]);
const C = arrayToStack([6,7,8,9,10]);
const stackArr = threeInOne(A, B, C);

assert.strictEqual(stackArr.pop(1), 3);
assert.strictEqual(stackArr.pop(1), 2);
assert.strictEqual(stackArr.peek(1), 1);
stackArr.push(1, 100);
assert.strictEqual(stackArr.peek(1), 100);

assert.strictEqual(stackArr.peek(2), 5);
assert.strictEqual(stackArr.peek(3), 10);
})
});
126 changes: 126 additions & 0 deletions JavaScript/lib/avc278/stacksAndQueues.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
const assert = require("assert");

class Stack {
constructor() {
this.top;
}
pop() {
if (!this.top) return;
const poppedVal = this.top.val;
this.top = this.top.next;
return poppedVal;
}
push(val) {
const newNode = new StackNode(val);
if (this.top) {
newNode.next = this.top;
this.top = newNode;
} else {
this.top = newNode;
}
}
peek() {
if (!this.top) return;
return this.top.val;
}
isEmpty() {
return !this.top;
}
}

class StackNode {
constructor(val, next) {
this.val = val === undefined ? null : val;
this.next = next === undefined ? null : next;
}
}

const arrayToStack = (arr) => {
const stack = new Stack();
for (const el of arr) {
stack.push(el);
}
return stack;
};

class Queue {
constructor() {
this.start;
this.end;
}
add(val) {
const newNode = new QueueNode(val);
if (this.end) {
this.end.next = newNode;
}
this.end = newNode;
if (!this.start) {
this.start = this.end;
}
}
remove() {
if (!this.start) return;
const removedVal = this.start.val;
this.start = this.start.next;
if (!this.start) {
this.end = null;
}
return removedVal;
}
peek() {
return this.start.val;
}
isEmpty() {
return !this.start;
}
}

class QueueNode {
constructor(val, next) {
this.val = val === undefined ? null : val;
this.next = next === undefined ? null : next;
}
}

const arrayToQueue = (arr) => {
const queue = new Queue();
for (const el of arr) {
queue.add(el);
}
return queue;
};

describe(`${module.filename} - Stack`, () => {
it("should create a stack and perform stack methods correctly", () => {
const arr = [1, 2, 3, 4, 5];
const stack = arrayToStack(arr);
assert.strictEqual(stack.peek(), 5);
assert.strictEqual(stack.pop(), 5);
assert.strictEqual(stack.peek(), 4);
assert.ok(!stack.isEmpty());
assert.strictEqual(stack.pop(), 4);
assert.strictEqual(stack.pop(), 3);
assert.strictEqual(stack.pop(), 2);
assert.strictEqual(stack.pop(), 1);
assert.ok(stack.isEmpty());
stack.push(10);
assert.strictEqual(stack.peek(), 10);
});
it("should create a queue and perform queue methods correctly", () => {
const arr = [1, 2, 3, 4, 5];
const queue = arrayToQueue(arr);
assert.strictEqual(queue.peek(), 1);
assert.strictEqual(queue.remove(), 1);
assert.strictEqual(queue.peek(), 2);
assert.ok(!queue.isEmpty());
assert.strictEqual(queue.remove(), 2);
assert.strictEqual(queue.remove(), 3);
assert.strictEqual(queue.remove(), 4);
assert.strictEqual(queue.remove(), 5);
assert.ok(queue.isEmpty());
queue.add(10);
assert.strictEqual(queue.peek(), 10);
});
});

module.exports = { arrayToQueue, arrayToStack, Queue, Stack };