Parsing loops in a javascript interpreter - javascript

I've been exploring writing a very basic / limited interpreter in javascript as an exercise. All has been going well until I introduced the concept of LOOPs.
Given the following script:
LOOP 2
A
LOOP 3
B
END
LOOP 4
C
LOOP 5
D
END
E
END
F
END
The algorithm should visit the inner tokens in the following sequence:
ABBBCDDDDDECDDDDDECDDDDDECDDDDDEFABBBCDDDDDECDDDDDECDDDDDECDDDDDEF
The following does the trick, but it requires lots of iterating over the tokens. It's an improvement over a previous slicing approach I used that manually expanded the loops, but is far from optimal.
/**
* In practice, we'll grab each token as we read the script,
* but to keep this simple and focus on the loop algorithm,
* we can cheat and make an array of all the tokens.
*/
const getTokens = (s) => s.replace(/[\W_]+/g, " ").split(" ").filter(Boolean);
/* Temp vars - ideally, I'd like to solve this with arrays. */
const start = []; // Loop start indices
const end = []; // Loop end indices
const counts = []; // Times to loop
const completed = []; // Loops completed
for (let i = 0; i < tokens.length; i++) {
const token = tokens[i];
if (token === "LOOP") {
if (start.length == 0 || i > start[start.length - 1]) {
// Add new loop index if we haven't seen it before
start.push(i); // Store the loop index
counts.push(Number(tokens[i + 1])); // The loop count is always next LOOP token
completed.push(0); // Initialize with 0 completed at index
// Find the end index for the loop
// Note: This is the slowest part.
let skip = 0;
for (let j = i + 2; j < tokens.length; j++) {
if (tokens[j] == "LOOP") {
skip++; // Increase nest depth
} else if (tokens[j] == "END") {
if (skip == 0) {
end.push(j); // Found matching loop close
break;
}
skip--;
}
}
}
i++; // Skip over the loop count
continue;
} else if (token === "END") {
let j;
for (j = 0; j < end.length; j++) {
if (end[j] == i) break; // Found matching end index
}
const isCompleted = completed[j] == counts[j] - 1;
if (!isCompleted) {
i = start[j] + 1;
completed[j]++;
for (let k = j + 1; k < start.length; k++) {
completed[k] = 0; // Reset nested loops in between
}
}
continue;
}
console.log(tokens[i]);
}
https://jsfiddle.net/5wpa8t4n/
What's a better way to accomplish this array-based approach using a single pass through the script, or at worst 2 passes, but not N-LOOP passes?

You don't need to know the position of the matching end of the loop when starting to interpret it. All you need to record is the position to jump back to when encountering the next end, but until then just continue interpreting token by token.
These positions, together with the respective counters, can be stored in a stack structure.
const script = `
DO
A
DO
B
LOOP 3
DO
C
DO
D
LOOP 5
E
LOOP 4
F
LOOP 2
`
const parse = (script) =>
script
.replace(/[\W_]+/g, " ")
.split(" ")
.filter(Boolean);
const interpret = (code) => {
let loops = []; // Active loops: iteration count and jump target
let ip = 0; // instruction pointer
let result = "";
while (ip < code.length) {
const instruction = code[ip];
switch (instruction) {
case "DO": {
++ip;
loops.push({count: 0, start: ip});
} break;
case "LOOP": {
const limit = Number(code[++ip]);
const {count, start} = loops.pop();
if (count < limit) {
loops.push({count: count+1, start});
ip = start; // jump back
} else {
++ip;
}
} break;
default: {
++ip;
result += instruction; // a print statement
} break;
}
}
return result;
};
console.log(interpret(parse(script)));
I've simplified the structure a bit to use do-while loops, so I'd never have to skip the loop body. In a true byte code, emitted by a parser, the jump targets (both back and forth) would be part of the instructions themselves, and only the count "variables" would need to be stored in the stack. The jump targets never change so you'd need to generate them only once in the parse function.

Related

How to restart a loop in Javascript whilst decrementing a global variable

I want the if statement (line 10) to detect when the next string in the array (line 1) is not matching the string in index0 (outputString0). And then to decrement the variable testLength (line 11) to slice the last character off the strings to allow them to be compared again in the next loop iteration to determine whether they are still matching. Then I want the loop to restart by setting i back to 0, which will increment to 1 once the loop is re-started (line 12).
But it seems to create an infinite loop. Is it because I am trying to modify the global variable testLength (line 11) from within the if statement?
const wordInput = ['fasts', 'fasta', 'fasts', 'fasts', 'fasts'];
let testLength = 5;
let outputString0 = wordInput[0].toString();
outputString0 = outputString0.slice(0, testLength);
for (let i = 1; i < 5; i++) {
output = wordInput[i].toString();
output = output.slice(0, testLength);
if ((this['outputString' + i] = output) !== outputString0) {
testLength = testLength - 1;
i = 0; continue;
}
else {
this['testString' + i] = output;
continue;
}
};
I attempted to move the global variable testLength to inside the for loop, but the console also returns an error. Not sure what to do next.

How can i solve this exercixe with String.prototype?

How can i create this to console log like this?
String.prototype.sheldonize = function () {
return `knock ${this}`
}
'Penny'.sheldonize(3)
I have this code at the moment, but I dont know how to repeat knock more times
Use the repeat method to establish a number of 'knocks' in the line and to establish how many times the line should repeat
String.prototype.sheldonize = function (repeats) {
const line = `${'knock '.repeat(repeats)}${this}, `.repeat(repeats)
return `${line.substring(0,line.length-2)}.`;
}
console.log('Penny'.sheldonize(3));
Using For Loop and repeat method
String.prototype.sheldonize = function(count) {
let ans = "";
for (let i = 0; i < count; i++) {
ans += "knock ";
}
ans = `${ans}${this}, `.repeat(count)
ans = ans.substring(0, ans.length - 2) + "."
return ans;
}
console.log('Penny'.sheldonize(3))
console.log('Penny'.sheldonize(2))
Create an array and on each iteration up to n - 1 add the string to it, finally joining it up and returning the string from the function.
// If you're adding to a prototype it's always best
// to double check to see if the method already exists
// no matter (in this case) how unlikely
if (!('sheldonize' in String.prototype)) {
String.prototype.sheldonize = function (n) {
// Create the array
const out = [];
// Create the string
const knock = 'knock '.repeat(n);
// Loop until `n - 1` has been reached
// pushing the string into the array
// on each iteration
for (let i = 0; i < n; i++) {
out.push(`${knock}${this}`);
}
// Finally return the joined array
return out.join(', ');
}
}
console.log('Penny'.sheldonize(3));
console.log('Penny'.sheldonize(2));
console.log('John'.sheldonize(4));
Additional documentation
repeat

Optimizing node.js solution for HackerRank QHEAP1

Hi I'm trying to familiarize myself a bit better with Heaps so wanted to try and implement a solution to HackerRanks>Practice>Data Structures>Heaps>QHEAP1 using primitives, however I'm getting a timeout error for two of the tests.
A quick summary: I need to be able to parse a standardized input and handle the following 3 types of queries:
Add an element to the heap.
Delete a specific element from the heap.
Print the minimum of all the elements in the heap.
I'm wondering where this could be optimized? From what I can tell my del() will be performed in O(n) since I need to search for the element provided.
// search for and delete specific element {x} from heap
function del(arr, x){
let i = 0;
let found = false;
let n = arr.length;
while(!found && i < n){
if(arr[i] == x) found = true;
i++;
}
if(found){
arr[i-1] = arr[n-1]; // take the last element and overwrite to delete
arr.length = n - 1; // shorten array
downHeap(arr, i); // perform downHeap opertaion from index deleted
}
}
// NOTE: customized for minHeap due to requirement to print minimum value
function downHeap(arr, t){
// use array as binary tree - next index looking down is double current index
// NOTE: i and t are 1 indexed for heap lookahead
let i = 2 * t;
if(i >= arr.length) return; // no more room
// checkes if right child is smallest - if so updates index to right child
if(i < arr.length - 1 && arr[i - 1] > arr[i]) i = i + 1;
// if lower element is smaller than current element, swap em
if(arr[i-1] < arr[t-1]){
swap(arr, i-1, t-1);
downHeap(arr,i); // downHeap again at the next level
}
}
// insert x into heap
function insert(arr, x){
const n = arr.length;
arr.length = n + 1; // increasing array size
arr[n] = x; // adding el to end of array
upHeap(arr, arr.length)
}
//NOTE: customized as minHeap due to requirement to print minimum value.
function upHeap(arr, t){
// using array as binary tree - looking up - parant is half of current index
const i = Math.floor(t/2);
// if we've hit zero gone too far - NOTE: i, and t are 1 indexed for heap reference
// also nothing to do if parent is smaller than current index
if(i == 0 || arr[i-1] <= arr[t-1]) return;
// child is smaller than parent swap and upHeap from parent
swap(arr, t-1, i-1)
upHeap(arr, i)
}
// swahp
function swap(arr, l, r){
const t = arr[l];
arr[l] = arr[r];
arr[r] = t;
}
PS. as a side question, I'm kind of switching between a 1 indexed for heap operations, and a 0 index for array operations (e.g. you'll notices a lot of i-1 statements inside the up and downHeap methods) - wondering if there's a smarter way of having done that?
Support Code:
function processData(input) {
//Enter your code here
const inputs = input.split('\n');
const n = inputs[0];
let arr = [];
for(let i = 1; i <= n; i++){
const query = inputs[i].split(' ');
const op = query[0];
if(op == "1"){
insert(arr, parseInt(query[1]))
} else if(op == "2"){
del(arr, parseInt(query[1]))
} else if(op == "3"){
console.log(arr[0])
} else {
console.log("Error reading op");
}
}
}
process.stdin.resume();
process.stdin.setEncoding("ascii");
_input = "";
process.stdin.on("data", function (input) {
_input += input;
});
process.stdin.on("end", function () {
processData(_input);
});
Example Input
22
1 286789035
1 255653921
1 274310529
1 494521015
3
2 255653921
2 286789035
3
1 236295092
1 254828111
2 254828111
1 465995753
1 85886315
1 7959587
1 20842598
2 7959587
3
1 -51159108
3
2 -51159108
3
1 789534713
The code is indeed confusing because (as you write) it sometimes uses 1-based indexes, while other times it uses them as 0-based.
For instance, in insert, the following line shows that you intend t and i to be a 1-based index, since you convert them on-the-fly to a 0-based index:
if(arr[i-1] < arr[t-1])
...but then in this line, you treat i as a 0-based index (arr.length would be an admissible value of i if it is 1-based):
if(i >= arr.length) return; // no more room
And the same mix-up happens here:
if(i < arr.length - 1 && arr[i - 1] > arr[i]) i = i + 1;
By consequence you will get wrong results.
It is confusing to work with 1-based indexes when JavaScript is expecting 0-based indexes everywhere indexes are used. I didn't feel the courage to further debug your code in that state. I would suggest to use 0-based indexes throughout your code, which means that the left child of a value at index t is at index t*2+1.
Some other remarks:
To find the index where a value occurs in the heap, you don't have to write an explicit loop. Just use the built-in indexOf method.
Recursion is nice, but the downHeap and upHeap functions will work more efficiently with an iterative method, because then -- instead of swapping values -- you can take a copy of the value to bubble up or down, and then only move (not swap) the conflicting values to finally insert the copied value in its right place. This will perform fewer assignments than swapping repeatedly.
To insert a value you can just use the push method instead of updating the length "manually".
Instead of Math.floor for the integer division by 2, you can use a shift operator.
So here is a correction of your code:
function del(arr, x) {
const i = arr.indexOf(x); // This will be faster
if (i >= 0) {
const value = arr.pop();
if (i < arr.length) { // Only assign back when it was not last
arr[i] = value;
downHeap(arr, i);
}
}
}
function downHeap(arr, t) {
const val = arr[t];
while (true) {
let i = t * 2 + 1;
if (i < arr.length - 1 && arr[i] > arr[i + 1]) i = i + 1;
if (i >= arr.length || arr[i] >= val) break;
arr[t] = arr[i]; // Don't swap to gain time
// No recursion to save stack space
t = i;
}
arr[t] = val;
}
function insert(arr, x) {
arr.push(x); // adding element to end of array
upHeap(arr, arr.length - 1);
}
function upHeap(arr, t) {
const val = arr[t];
while (true) {
let i = (t - 1) >> 1; // Shift operator may give some speed increase
if (i < 0 || arr[i] <= val) break;
arr[t] = arr[i]; // Don't swap to gain time
// No recursion to save stack space
t = i;
}
arr[t] = val;
}

How to iterate over an array of numbers to find the first number that occurs 3 times in the array

I have the following code
let range = [1,2,3];
let multiples = [1,2,3,4,5,6,2,4,6,3,6];
I want to find the first number in the multiples array that occurs range.lenght times (3);
I want to start with multiples[0] check how many times it occurs in multiples, if it occurs 3 times I want to return multiples[0], if it is less than 3 times, I want to check how many times multiples[1] occurs in the multiples array. If multiples[1] occurs 3 times I want to return multiples[1], else I move on to check multiples[2], etc. until I find a number that occurs 3 times. In the code above I should return 6.
I've looked at
How to count the number of certain element in an array?
and
Idiomatically find the number of occurrences a given value has in an array
and
get closest number out of array
among other research but have not figured it out yet.
I tried to simplify the question as much as possible. But if more info is needed it relates to this challenge on freeCodeCamp. Where I am at with my code is
function smallestCommons(arr) {
let sortArr = arr.sort((a, b) => a - b);
console.log(sortArr);
let range = [];
for (let i = sortArr[0]; i <= sortArr[1]; i++) {
range.push(i);
}
console.log("range = " + range);
let maxNum = range.reduce( (a, b) => a * b);
console.log("maxNum = " + maxNum);
let multiples = [];
for (let i = 0; i < maxNum; i++) {
let j = 0;
do {
multiples.push(j + range[i]);
j += range[i];
} while (j < maxNum);
//j = 0;
}
for (let i = 0; i < multiples.length; i++) {
let numberToFind = multiples[i];
/*stuck here hence my question, maybe I shouldn't even start with a for loop*/
//tried reduce, forEach, filter, while loop, do while loop
}
console.log("multiples = " + multiples);
}
console.log(smallestCommons([1,3]));
The logs are
1,3
range = 1,2,3
maxNum = 6
multiples = 1,2,3,4,5,6,2,4,6,3,6,NaN,NaN,NaN
What you can do is, first split your string with , and then using below function loop for check.
function countLength(arr, checkNumber) {
var count = 0;
for (var i = 0; i < arr.length; i++) {
if (arr[i] === checkNumber) {
count++;
}
}
return count;
}
countLength(list, NUMBER YOU WANT TO CHECK);
And if you want to check first number occur for 3 time then you need to make change in function and introduce .map or .filter in action to count number.
Example
const multiples = [1,2,3,4,5,6,2,4,6,3,6];
let occurance_arr=[];
const aCount = [...new Set(multiples)].map(x => {
if(multiples.filter(y=> y==x).length == 3) {
occurance_arr.push(x);
}
});
console.log(occurance_arr);
Above code will give you 6 in console, if you have multiple value then 0th element is the answer you are looking for which is first three time occurrence of item.
You can loop through your list keeping an object that maps each number to the number of times you've seen it. You can check the counts object as you loop, so if you see a number and the count is one less than your target, you can return it. If you make it through the loop without returning you didn't find what you're looking for — return something sensible :
let range = [1,2,3]
let multiples = [1,2,3,4,5,6,2,4,6,3,6]
function findFirstMult(arr, len){
let counts = {} // to keep track of how many times you've seen something
for (let n of arr){ // loop throught the array
if (!counts[n]) counts[n] = 0 // if it's then first time you've seen n, defined that key
if (counts[n] == len - 1) return n // found it
counts[n] +=1 // otherwise increase the count
}
return undefined
}
console.log(findFirstMult(multiples, range.length))
This will require only one loop through the array in the worse case and will return early if if finds something.

javascript while loop correctly iterating but for loop with same logic is not, on array with integer values and some null values in there

Iterating through a javascript array which has some data in, and some null or not defined values also, is giving funny behaviors with a for loop, but not with a while loop. It is not returning when it should and is stuck in an infinite loop
I have investigated the outputs extensively, the condition whether the number exists in the array is never evaluated to be true, only ever false, but it sometimes enters the if statement region as if it is true. It is seemingly arbitrary.
//function called within this code
function randomArrayOfIndexes() {
var randNumbArray = new Array(4);
var indexToAssign = Math.floor(Math.random() * Math.floor(4));
randNumbArray[0] = indexToAssign;
for (i = 1; i < randNumbArray.length; i++) {
indexToAssign = Math.floor(Math.random() * Math.floor(4));
while (arrayContains(randNumbArray, indexToAssign)) {
indexToAssign = Math.floor(Math.random() * Math.floor(4));
}
randNumbArray[i] = indexToAssign;
}
return randNumbArray;
}
//this works
function arrayContains(arrayin, numberIn) {
var i = arrayin.length;
while (i--) { //takes one from i so highest index is accurate on first iteration
if (arrayin[i] === numberIn) {
return true;
}
}
return false;
}
//this doesn't... not even backwards like the above iteration
function arrayIncludes(arrayin, numberIn) {
for (i = 0; i < arrayin.length; i++) {
if (arrayin[i] === numberIn) {
return true;
}
}
return false;
}
At first each function above is passed in an array with [int value, null, null, null], and a random number; when the function returns, the next null value is filled with the random number that doesn't exist in it already, so [int value, int value, null, null]... until all values are filled... the final array is filled with unique random numbers from 0 to 3, to provide an index for a piece of data in another array... to make sure that it is only used once in the program I am writing.
I would expect it to return true if the number passed in is already in there, another random number then generated outside of the broken function, and the process repeated until a unique random number is found. When it is found, the array being passed back in will be populated at the next available index, and the process repeated. This is not happening. It is getting stuck in an infinite loop, and never returning
you are just missing a var before i:
function arrayIncludes(arrayin, numberIn) {
for (var i = 0; i < arrayin.length; i++) {
// in ^ here
if (arrayin[i] === numberIn) {
return true;
}
}
return false;
}
You may also declare it before loop, like
var i;
for (i = 0; i < arrayin.length; i++) {
...
By the way, this way of generating random numbers without duplicates is very inefficient, I suggest something like having an array of 0-3 (in your current example) or 0-n and then just randomly taking items out of it. then you don't have to loop through the whole array each time you find a new number. every time you just find a random index between 0 and the length of remaining items.
Imagine that the array length is 1000, and the last item remaining is a number like 100, how many times you have to find a random number and loop through whole array till your random number is 100?
var n = 5;
var a = new Array(n);
for(var i=0;i<n;i++) a[i] = i;
var result = new Array(n);
var i = n;
while(i)
{
var index = Math.floor(Math.random() * i);
result[--i] = a[index];
a.splice(index,1);
}
document.getElementById('a').innerHTML = result;
<div id="a"></div>
You need to declare variables in you loops with for i=0. if you don't do this the variable is global and when you use the same loop variable in nested loops one can change the other.
You are using i in both loops so when you call the for loop with:
function arrayIncludes(arrayin, numberIn) {
for (i = 0; i < arrayin.length; i++) {
// etc
}
You set i back to 0 ad iterate it — this is the same i you are using in randomArrayOfIndexes so it interferes with that loop. This is a common cause of hard-to-find bugs and is hy you should always declare loop variables.
Here's the bug in it's simplest form. Notice that the out loop only runs once because i is incremented in the inner loop causing the outloop to exit early:
for (i = 0; i < 4; i++){
console.log("out loop number: ", i)
for (i = 0; i < 4; i++){
console.log("inner_loop: ", i)
}
}
If you declare the variables for for let i =, each loop gets its own version of i both loops run independently:
for (let i = 0; i < 4; i++){
console.log("out loop number: ", i)
for (let i = 0; i < 4; i++){
console.log("inner_loop: ", i)
}
}

Categories

Resources