Create a nested list from array - javascript

This is the 3rd question from Eloquent JavaScript chapter 4.
Basically it wants me to create a function that will make an array into a nested list from array.
E.g. arrayToList([1, 2, 3]) should return:
var list = {
value: 1,
rest: {
value: 2,
rest: {
value: 3,
rest: null
}
}
};
I wonder why my code leads to an infinite loop.
function arrayToList(arr) {
var list = {};
for (var i = 0; i < arr.length; i++) {
var a = arr[i];
function add(res) {
if (i == 0) {
res.value = a;
res.rest = "null";
}
else {
i -= 1;
add(res.rest);
}
}
add(list);
}
return list;
}
Thank you for taking a look!

You subtract 1 for i in the middle of the loop if it's not 0 - so it never finishes the for - you can use recursion for this!
function addToList(obj, arr, index) {
obj.value = arr[index];
if (index == (arr.length - 1)) {
obj.rest = null
} else {
obj.rest = {};
addToList(obj.rest, arr, index + 1)
}
}
var myObj = {};
var arr = [1,2,3];
addToList(myObj, arr, 0)
Demo: http://jsfiddle.net/freab275/

You are playing games with your loop counter and the recursion. Each time i is not 0 you subtract one, then the loop adds one. So with one step back and one step forward you never finish. Add some print statements to see what i is each time you reference it.

Related

WHY THE FUNCTION IS RETURNING NaN

When I try to run the function below, the line of code is returning Nan, when it's supposed to return a number.
var next = list[last]+list[before];
This is the full code:
function fibonacciGenerator(n) {
var list = [0, 1];
if (n === 0) {
return [];
} else if (n === 1) {
return [0]
} else {
while (n > 1) {
var last = list.length;
var before = last - 1;
var next = list[last] + list[before];
list.push(next);
n -= 1;
return list;
}
}
}
console.log(fibonacciGenerator(8));
Because you're trying to access list[last], which is list[list.length]. However, because a list of length 1 has a single item at index 0, you're getting undefined. So, use list.length - 1:
function fibonacciGenerator(n) {
var list = [0, 1];
if (n === 0) {
return [];
} else if (n === 1) {
return [0]
} else {
while (n > 1) {
var last = list.length - 1;
var before = last - 1;
var next = list[last] + list[before];
list.push(next);
n -= 1;
return list;
}
}
}
console.log(fibonacciGenerator(8));
(Also, your function has a small issue - return list runs as soon as the first loop iteration has completed. Move it outside of the loop:
function fibonacciGenerator(n) {
var list = [0, 1];
if (n === 0) {
return [];
} else if (n === 1) {
return [0]
} else {
while (n > 1) {
var last = list.length - 1;
var before = last - 1;
var next = list[last] + list[before];
list.push(next);
n -= 1;
}
return list;
}
}
console.log(fibonacciGenerator(8));
Basically, the way it works in JavaScript is that array[array.length] will always be undefined. It starts at index 0 - so array.length is always one more than the index of the last element:
let nextElementIndex = array.length; // The next element to be added to the array will have the index of nextElementIndex, or array.length AT THIS CURRENT MOMENT
array.push(1); // `1` has the index nextElementIndex
nextElementIndex == array.length; // False, because there's a new element in the array, so the array's length has updated to nextElementIndex + 1.
It is because your last = 2, which makes list[last] = list[2] while it only has 2 elements that is list[0] and list[1]. Therefore, you get undefined out of this one, and when undefined + list[before], it gets NaN.
You just using index out of array range, an array has index range from 0 to length - 1, so array[length] + ... will return NaN
But, Something i can change in your code:
Try to think about calculating fibo(n) from fibo(n-1), not just recalculate from beginning.
Something like this look better:
var fibo = function(n){
// some funky code to check fatal input
....
// init special values
if(n==0) return [];
if(n==1) return [1];
if(n==2) return [1, 1];
// get previous state
var arr = fibo(n-1);
var len = arr.length;
arr.push(arr[len-2] + arr[len-1]);
return arr;
}
You will hear your computer sound like a helicopter if you must use this function many times. So if you need re-use this, you need something for caching calculated values. Ex:
epic_history = {
1: 1,
2: 1,
3: 2,
4: 3,
5: 5,
.....
}

Longest decrease subsequence subarray in Javascript

I encountered this question:
Find longest decrease sequence in an array
So basically, if [3,1,4,3,2,1,2,3] is given, it will return [4,3,2,1] as the longest sequence within it. I was able to solve this question with O(n^2) time complexity but I feel like I can do O(n).
I know I am probably making a giant logic mistake but this is how I approach with 2 pointers method.
function findDecreaseSubArray(arr) {
let subList = [arr[0]];
// let maxLength= 0;
let left = 0;
for (let right = 1; right < arr.length; right++) {
// maxLength = (maxLength, windowEnd - windowStart + 1);
if (arr[left] > arr[right]) {
subList.push(arr[right]);
}else{
subList.shift();
left = right-1;
}
}
}
What I am trying to accomplish is moving left and right pointers if left element is larger than right, and if so, push both elements into sublist array.
My brain starts giving 404 error after this, so there are 2 subarrays that are decreasing, one of them is [3,1] and the other one is [4,3,2,1].
How could I track one subarray is larger than the other subarray? Or there is a better way to approach this solution?
Any hints, hands, code snippet would highly appreciated. (And sorry about my code snippet, I know it is shitty but I just wanted to display some of my thoughts on code)
You just have to iterate over the array once, but keep track of the start and length of the longest sequence as you progress through it:
var arr = [3,1,4,3,2,1,2,3];
function findDecreaseSubArray(arr) {
let startIndex = 0;
let length = 1;
let longestSequence = {
startIndex: 0,
length: 1
}
arr.forEach((element, index, arr) => {
if (index === 0) return;
if (element < arr[index -1]) {
length += 1;
} else {
length = 1;
startIndex = index;
}
if (length > longestSequence.length) {
longestSequence.length = length;
longestSequence.startIndex = startIndex;
}
})
return longestSequence;
}
console.log(findDecreaseSubArray(arr));
This approach supports VLAZ's comment and uses only the indices of the array.
function longestDecreasing(array) {
var longest,
start = 0,
i;
for (i = 0; i < array.length; i++) {
if (!i || array[i] < array[i - 1]) continue;
if (!longest || longest[1] - longest[0] < i - start) longest = [start, i];
start = i;
}
return array.slice(...longest && longest[1] - longest[0] > i - start
? longest
: [start, i]);
}
console.log(longestDecreasing([3, 1, 4, 3, 2, 1, 2, 3]))
It would probably be easier to iterate normally, from the first index to the end, while pushing sequential sequences to an array, which gets reset when a number greater than the last is found. When resetting, assign the resulting sequence array to a more permanent variable if it's longer than the array in that permanent variable:
const findDecreaseSubArray = (arr) => {
let longestSoFar = [];
let currentSequence = [];
const reset = (newItem) => {
if (currentSequence.length > longestSoFar.length) {
longestSoFar = currentSequence;
}
currentSequence = [newItem];
};
for (const item of arr) {
if (currentSequence.length && item > currentSequence[currentSequence.length - 1]) {
reset(item);
} else {
currentSequence.push(item);
}
}
reset();
return longestSoFar;
};
console.log(findDecreaseSubArray([3,1,4,3,2,1,2,3]));
Here is my code.
function getLongestDecreaseSequence(arr) {
if (
Object.prototype.toString.call(arr) !== "[object Array]" ||
arr.length == 0
) {
return arr;
}
let longestArr = [];
let tmpArr = [],
lastTmpIndex = -1;
arr.forEach((value, i) => {
if (arr[lastTmpIndex] < value) {
tmpArr = [];
}
// no matter what, I will put it in tmpArray
lastTmpIndex = i;
tmpArr.push(value);
if (longestArr.length < tmpArr.length) {
longestArr = tmpArr;
}
});
return longestArr;
}
console.log(getLongestDecreaseSequence([3, 1, 4, 3, 2, 1, 2, 3]));
This looks like a reducing job.
var a = [3,1,4,3,2,1,2,3],
r = a.reduce( function(r,e){
var l = r[r.length-1];
return l[l.length-1] > e ? ( l.push(e)
, r
)
: ( r.push([e])
, r
);
}
, [[]]
)
.reduce((p,c) => p.length > c.length ? p : c);
console.log(r);

Finding first duplicate in an array and returning the minimal index

So the question reads:
Given an array a that contains only numbers in the range from 1 to a.length, find the first duplicate number for which the second occurrence has the minimal index. In other words, if there are more than 1 duplicated numbers, return the number for which the second occurrence has a smaller index than the second occurrence of the other number does. If there are no such elements, return -1.
Write a solution with O(n) time complexity and O(1) additional space complexity.
I have a solution, but apparently it's not fast enough and stalls when there are over a thousand items in the array.
This is what I have:
function firstDuplicate(arr) {
let dictionary = {};
for(let i = 0, ii = arr.length; i < ii; i++) {
for(let z = i+1, zz = arr.length; z < zz; z++) {
if(arr[i] === arr[z]) {
if(dictionary.hasOwnProperty(arr[i])) {
if(dictionary[arr[i]] !== 0 && dictionary[arr[i]] > z) {
dictionary[i] = z;
}
} else {
dictionary[arr[i]] = z;
}
}
}
}
let answer = [];
for(key in dictionary) {
// [array number, position];
answer.push([key, dictionary[key]]);
};
if(answer.length > 0) {
return Number(answer.sort((a, b) => {
return a[1]-b[1];
})[0][0]);
}
return -1;
}
I think converting the object into an array and then sorting the array after the answers are complete slows down the whole function. Using built in JS methods like forEach, map and sort (like I did above), slows the code/function down even more. There is obviously a better and more accurate way to do this, so I'm asking for some JS masterminds to help me out on this.
you can keep adding numbers to a dictionary as keys with values as their index, and as soon as you find a repeating key in the dictionary return its value. This will be O(n) time complexity and O(n) space complexity.
function firstDuplicate(arr) {
var dictionary = {};
for(var i = 0; i < arr.length; i++) {
if(dictionary[arr[i]] !== undefined)
return arr[i];
else
dictionary[arr[i]] = i;
}
return -1;
}
console.log(firstDuplicate([2, 3, 3, 1, 5, 2]));
Since the numbers are between 1 to arr.length you can iterate on the array. For each arr[i] use arr[i] as index and make the element present and arr[arr[i]] negative, then the first arr[arr[i]] negative return arr[i]. This give O(1) space complexity and O(n) time complexity you can do this:
function firstDuplicate(arr) {
for(var i = 0; i < arr.length; i++) {
if(arr[Math.abs(arr[i])] < 0)
return Math.abs(arr[i]);
else
arr[Math.abs(arr[i])] = 0 - arr[Math.abs(arr[i])];
}
return -1;
}
console.log(firstDuplicate([2, 3, 3, 1, 5, 2]));
function firstDuplicate(arr) {
for(var i = 0; i < arr.length; i++) {
var num = Math.abs(arr[i]);
if(arr[num] < 0)
return num;
arr[num] = - arr[num];
}
return -1;
}
console.log(firstDuplicate([2,2,3,1,2]));
function firstDuplicate(arr) {
var numMap = {};
for (var i = 0; i < arr.length; i++) {
if (numMap[arr[i]]) {
return arr[i];
}
numMap[arr[i]] = true;
}
return -1;
}
Answer mentioned by #dij is great, but will fail for [2,2] or [2,3,3],
a little change
for input [2,2], i=0 we get a[ Math.abs[a[0] ] = a[2] = undefined
so we remove 1 from a[ Math.abs[a[0] -1 ] this will work now
function firstDuplicate(arr) {
for(var i = 0; i < arr.length; i++) {
if(arr[Math.abs(arr[i])-1] < 0)
return Math.abs(arr[i]);
else
arr[Math.abs(arr[i])-1] = 0 - arr[Math.abs(arr[i])-1];
}
return -1;
}
Please try the code below:
function getCountOccurence(A) {
let result = [];
A.forEach(elem => {
if (result.length > 0) {
let isOccure = false;
result.some((element) => {
if (element.element == elem) {
element.count++;
isOccure = true;
}
});
if (!isOccure) {
result.push({element: elem, count: 1});
}
} else {
result.push({element: elem, count: 1});
}
});
return result;
}
function getFirstRepeatingElement(A) {
let array = getCountOccurence(A);
array.some((element)=> {
if (element.count > 1) {
result = element.element;
return true;
}
});
return result;
}

How do you use a for loop inside of the callback function of the filter method?

This code returns the correct array but I am cheating inside of the arrayFilter function. By using matchItem[0] and matchItem[1] I am hard coding the use of only 2 filter items. If there are less than 2, the test will fail, and if there are more than 2, the extra items will not be filtered.
function destroyer(arr) {
// Remove all the values
var destroyerArgsAmount = arguments.length;
var matchArr = arguments[0];
var newArr = [];
var matchItems = [];
for(i = 1; i < destroyerArgsAmount; i++) {
matchItems.push(arr[i]);
}
function arrayFilter(val) {
if(val != matchItems[0] && val != matchItems[1]) {
return true;
}
}
arr = matchArr.filter(arrayFilter);
return arr;
}
destroyer([1, 2, 3, 1, 2, 3], 2, 3);
Why does a for loop not work in this situation?
for(i = 0; i < matchItems.length; i++) {
if(matchItems[i] != value) {
return value;
}
This loop does not work when placed into the arrayFilter function
Can someone explain to me why and then get me on the correct path to how this is done? I figure this will be an important thing to master since there are many methods employed this way like: filter, map, forEach and many more I don't know about yet.
I'm not exactly sure the results you're trying to achieve since you haven't explained it but, I'm assuming from your code your function is trying to filter all arguments passed after the first argument array. Like this:
function destroyer (arr) {
// You can use slice to grab all arguments after the first index in arguments
var args = Array.prototype.slice.call(arguments, 1);
return arr.filter(function (item) {
// filter anything not found in the arguments list
// indexOf will return -1 if not found
return args.indexOf(item) < 0;
});
}
What you seem to need is a way to check if a value is in an Array.
As mentioned by #adeneo, if val is a primitive value (string, number, etc) or an object that should be compared by reference, the solution is as simple as
matchItems.indexOf(val)
which returns a positive number (the index) if val is one of the items in matchItems and -1 otherwise.
If your use case is slightly more complicated, for example if matchItems contains objects and you want to see if an object with a particular property is in it, you can use Array.prototype.some:
var found = matchItems.some(function(item){
return item === val; // use the apropriate comparison here
});
Your full code would be:
function destroyer(arr) {
var matchItems = Array.prototype.slice.call(arguments, 1);
return arr.filter(function(a) {
return !matchItems.some(function(b){
return a === b;
});
});
}
destroyer([1, 2, 3, 1, 2, 3], 2, 3);
Use indexOf and return the result
function arrayFilter(val) {
return matchItems.indexOf(val) === -1;
}
I would think maybe you're looking for something more like this
function destroyer() {
var args = [].slice.call(arguments),
matchArr = args.shift();
return matchArr.filter(function(item) {
return args.every(function(item2) {
return item !== item2;
});
});
}
FIDDLE
I'm also doing the free code camp's challenges! This code passed the challenge:
function destroyer(arr) {
var arr = arguments[0];
for (var i = 0; i < arr.length; i++) {
for (var j = 1; j < arguments.length; j++) {
if (arr[i] === arguments[j]) {
arr.splice(i, 1);
i--;
}
}
}
return arr;
}
destroyer([1, 2, 3, 1, 2, 3], 2, 3);

Find if Array Contains 5 consecutive Numbers with Javascript

I have a sorted Array that contains numbers. I want to be able to check if this Array(or similar Array), contains 5 numbers in consecutive order.
NOTE: Array may contain duplicate and double digit numbers.
I am trying this, but failing epically.
var array = [1,3,5,7,7,8,9,10,11]
var current = null;
var cnt = 0;
for (var i = 0; i < array.length; i++) {
if (array[i] != current) {
if (cnt > 4) {
return true;
}
current = array[i];
cnt = 1;
} else {
cnt++;
}
}
if (cnt > 4) {
return true;
}
}
An iterative, straightforward approach would be:
var should_be_true = [1,3,5,7,7,8,9,10,11];
var should_be_false = [1,3,5,7,9,11,13,15,17];
var testArray = function(array) {
var conseq = 1;
for (var idx = 1; idx < array.length ; idx++) {
if (array[idx] == array[idx-1] + 1)
conseq++;
else
conseq = 1;
if (conseq == 5)
return true;
}
return false;
}
console.log(testArray(should_be_true)); //true
console.log(testArray(should_be_false)); //false
But for bonus fun, here's one variation on a functional approach, returning the position where the sequence starts, or -1 if no sufficiently long sequence exists:
should_be_true.map(function(curr,idx,arr) {
return (curr == arr[idx-1] +1) ? 1 : 0;
}).join('').search(/1{4}/);
A functional approach would be
function fiveInARow(array) {
// compare if one element is greater than or equal to the previous one
function compare(elt, i, arr) { return !i || elt >= arr[i-1]; });
// check if at a given position, every one of the last five comparisons is true
function check (_, i, greaters) {
return i >= 4 && greaters.slice(i-4, i) . every(Boolean);
}
return array . map(compare) . some(check);
}
The logic here is to first create an array of booleans using map, showing whether each element is greater than or equal to the previous. That yields an array such as [true, true, true, false, true].
The some part asks, for any element, is it the case that that element and every one of the preceding four elements are true? If so, it returns true.
Recursive solution
A recursive solution might be a bit easier to read.
function fiveInARow(array) {
return function _five(array, prev, n) {
if (n >= 5) return true;
if (!array.length) return false;
var next = array.shift();
return _five(array, next, next === prev ? n : next >= prev ? n+1 : 0);
}(array, -999999, 5);
}
var array = [1,3,5,7,7,8,9,10,11];
var cons=false;
var count=0;
for(i=0;i<array.length;i++){
if(array[i]+1==array[i+1]){
count++;
}
else{
count=0;
}
if(count==4){
cons=true;
}
}
if(cons){
//do what you want with it
}
DEMO
a more elegant way would be to define this whole thing in a function as below:
function checkCons(array){
var cons=false;
var count=0;
for(i=0;i<array.length;i++){
if(array[i]+1==array[i+1]){
count++;
}
else{
count=0;
}
if(count==4){
cons=true;
}
}
return cons;
}
and then using it like this:
var array = [1,3,5,7,7,8,9,10,11];
if(checkCons(array)){
//do what you want with it
}
DEMO
function fiveStraight() {
var array = [1, 3, 5, 7, 7, 8, 9, 10, 12];
var prev = array[0];
var numberOfStraight = 1;
for (var i = 1; i < array.length; i++) {
numberOfStraight = array[i] === prev + 1 ? numberOfStraight + 1 : 1;
prev = array[i];
if (numberOfStraight === 5) return true;
}
return false;
}
JSFIDDLE.
Here is what I found out to be the most straight forwards approach. Both descending and ascending values count as consecutive (if that's not your case, then fiddle with the Math.abs() call).
function consecutiveValuesCount(array) {
// sort the values if not pre-sorted
var sortedValues = array.sort();
// the result variable / how many consecutive numbers did we find?
// there's always atleast 1 consecutive digit ...
var count = 1;
for (var i = 0; i < sortedValues.length - 1; i++) {
// both descending and ascending orders count as we are using Math.abs()
if (Math.abs(sortedValues[i] - sortedValues[i+1]) == 1) {
++count;
}
}
return count;
}
//input
var array = [1,2,4,5,3];
// output
5
with this code you can find the highest number of consecutives for a given number or find the highest number of consecutives in general
var findMaxConsecutiveOnes = function (arr, number) {
//check for boundries
if(!number || !arr.length) return;
// maximum number of consectuives
let max = 0;
// count homy many consecutives before it ends (why it's 1 ? because a lonely number is still counted)
let counter = 1;
// you can ignore the next 2 variable if you want to use for loop instead of while
let length = arr.length;
let i = 1; // counting from index 1 because we are checking against index-1
while (i < length) {
if (arr[i] == arr[i - 1]) {
// boom, we have a consecutive, count it now
counter++;
} else {
// rest to 1
counter = 1;
}
// always update the max variable to the new max value
max = Math.max(counter, max);
//for the sake of iteration
i++;
}
return max== number;
};
console.log(findMaxConsecutiveOnes([5, 5, 5, 1, 1, 1, 1, 1]));
cnt will only increase once, when it hits the two 7s.
Put the incrementing line in the truthy condition, and the reset line in the else statement.
// Put into a function for testing.
function foo() {
var array = [1, 3, 5, 7, 7, 8, 9, 10, 11]
var current = null;
var cnt = 0;
for (var i = 0; i < array.length; i++) {
// Also need to make sure the next array item is a consecutive increase.
if (array[i] != current && array[i] === array[i-1] + 1) {
if (cnt > 4) {
return true;
}
current = array[i];
cnt++;
} else {
cnt = 1;
}
}
if (cnt > 4) {
return true;
} else {
return false;
}
};
// Call function.
alert(foo());

Categories