Chop of n elements from the head of an array - javascript

I am learning Javascript at Freecodecamp. This challenge is to chop of n elements from the head of an array. My code works, except when slicing at 1.
I have been at this for a while, unfortunately I do not have my head around for loops sufficiently to find my own solution. I feel its best I know why this went wrong, rather than discover an alternative method. Many thanks in advance.
function slasher(arr, howMany) {
// it doesn't always pay to be first
var newArr = [];
for (i=0; i < arr.length; i++) {
if (arr[i] <= howMany) {
return arr.slice(howMany);
}
else {
return arr;
}
}
}
slasher(["burgers", "fries", "shake"], 1);
expected output
slasher([1, 2, 3], 2) should return [3].
slasher([1, 2, 3], 0) should return [1, 2, 3].
slasher([1, 2, 3], 9) should return [].
slasher([1, 2, 3], 4) should return [].
slasher(["burgers", "fries", "shake"], 1) should return ["fries", "shake"].
slasher([1, 2, "chicken", 3, "potatoes", "cheese", 4], 5) should return
["cheese", 4].

For the benefit of future readers, one possible approach to this problem could be like this:
function sliceFrom(sequence, n) {
let result = [];
for (let value of sequence)
if (--n < 0)
result.push(value);
if (typeof sequence === 'string')
return result.join('');
return result;
}
console.log(sliceFrom('abcdefg', 3));
console.log(sliceFrom([11,22,33,44,55], 2));
console.log(sliceFrom([11,22,33,44,55], 100));
Note that, because of using the of loop, this "slicer" works with arbitrary sequences (or rather "iterable" values), including those without .length property (e.g. Map or Set objects).
From the practical standpoint, there are built-in methods Array.slice and String.slice that do exactly that when invoked with one positive argument. However, our home-made function is better than slice when dealing with "astral" characters, e.g. emojis:
function sliceFrom(sequence, n) {
let result = [];
for (let value of sequence)
if (--n < 0)
result.push(value);
if (typeof sequence === 'string')
return result.join('');
return result;
}
faces = "πŸ˜‚πŸ˜„πŸ˜"
console.log(faces.slice(1)) // not really
console.log(sliceFrom(faces, 1)) // looks fine

Check for the length of that array:
function slasher(arr, howMany) {
return arr.slice(howMany);
}
console.log(slasher([1, 2, 3], 2)); // should return [3].
console.log(slasher([1, 2, 3], 0)); // should return [1, 2, 3].
console.log(slasher([1, 2, 3], 9)); // should return [].
console.log(slasher([1, 2, 3], 4)); // should return [].
console.log(slasher(["burgers", "fries", "shake"], 1)); // should return ["fries", "shake"].
console.log(slasher([1, 2, "chicken", 3, "potatoes", "cheese", 4], 5));// should return ["cheese", 4].
.as-console-wrapper { max-height: 100% !important; top: 0; }

Related

Strange output with Array.filter() method

In the code below I expected the result of destroyer([1, 2, 3, 1, 2, 3], 2, 3); would be [1,1].
But the result pops out to be [1,2,3,1,2,3].
Where am I going wrong?
function destroyer(arr) {
function hlp(x) {
for(var i=1; i<arguments.length ; i++) {
if(x == arguments[i]) return false;
}
return true;
}
return arr.filter(hlp);
}
console.log(destroyer([1, 2, 3, 1, 2, 3], 2, 3));
You can use ES6 rest parameter syntax and check if array includes any of those parameters using include()
function destroyer(arr, ...rest) {
function hlp(e) {
return !rest.includes(e)
}
return arr.filter(hlp);
}
console.log(destroyer([1, 2, 3, 1, 2, 3], 2, 3));
If you want to use for loop and arguments your code should look like this.
function destroyer(arr) {
var arg = arguments
function hlp(x) {
for (var i = 1; i < arg.length; i++) {
if (x == arg[i]) return false;
}
return true;
}
return arg[0].filter(hlp);
}
console.log(destroyer([1, 2, 3, 1, 2, 3], 2, 3));
You've defined destroyer to only accept one argument, while you're passing in more than one. arguments is undefined, and iterating in a function passed to Array.filter() isn't a good idea. Array.filter() checks every element for you. Not that it would work in your code, but if it would, it would be better to just return x == arguments[i] instead of checking whether it's true or false. If you return x == arguments[i] you're actually returning a boolean. An example of that is where I in the code below return exclude.indexOf(i) == -1;. Try the following approach instead:
function destroyer(arr, exclude) {
return arr.filter(function(i) {
return exclude.indexOf(i) == -1;
});
}
console.log(destroyer([1, 2, 3, 1, 2, 3], [2, 3]));
Solved in two ways:
1st:
function destroyer(arr) {
function hlp(x) {
for(var i=1; i<destroyer.arguments.length ; i++) {
if(x == destroyer.arguments[i]) return false;
}
return true;
}
return arr.filter(hlp);
}
console.log(destroyer([1, 2, 3, 1, 2, 3], 2, 3));
2nd
Look to the solution posted by #Nenad Vracar

Bonfire Seek and Destroy- Freecodecamp Challenge

You will be provided with an initial array (the first argument in the destroyer function), followed by one or more arguments. Remove all elements from the initial array that are of the same value as these arguments.
I've these instructions:
destroyer([1, 2, 3, 1, 2, 3], 2, 3) should return [1, 1].
destroyer([1, 2, 3, 5, 1, 2, 3], 2, 3) should return [1, 5, 1].
destroyer([3, 5, 1, 2, 2], 2, 3, 5) should return [1].
destroyer([2, 3, 2, 3], 2, 3) should return [].
destroyer(["tree", "hamburger", 53], "tree", 53) should return ["hamburger"].
I've found code:
function destroyer(arr) {
var args = Array.prototype.slice.call(arguments);
args.splice(0,1);
return arr.filter(function(element) {
return args.indexOf(element) === -1;
});
}
My questions:
Can you explain this code in English, please?
Can you give a shortcut code for above challenge? please.
I've done this challenge using filter function, though t is recommended to use also 'indexOf' in order to compare value in array by the value to filter with.
````
function destroyer(arr) {
// Remove all the values
var temp = [];
for (var i = 1; i < arguments.length; i++) {
temp.push(arguments[i]);
arr = arguments[0].filter(function(value) {
return ( value !== temp[i - 1]) ;
});
}
return arr;
}
destroyer([1, 2, 3, 1, 2, 3], 2, 3);
````
function destroyer(arr) {
var args = arr.slice.call(arguments);
args.splice(0,1);
return arr.filter(function(element) {
return args.indexOf(element) === -1;
});
}
line 1 declares the overall function used for the challenge
line 2 converts the arguments of arr into a array variable named args, although this method doesn't work when optimizing in certain javascript platforms (optimization is basically what you're trying to do with your second question)
line 3 removes zero (0) and one (1) from args
lines 4 and 5 return the final result (true/false) of filtering arr for falsy and truthy by seeing whether or not an element of arg is present in arr, returning -1 if not and the element's location in arr if true
this is actually relatively optimized; there is much more more-optimized code but oftentimes it is only viable on certain javascript platforms (mozilla javascript, etc.)

Filtering out specific values from an array

destroyer([1, 2, 3, 1, 2, 3], 2, 3) should return [1, 1], but it returns [1, 2, 3, 1, 2, 3]. What is wrong with this code?
function destroyer(arr) {
// Remove all the values
var arg_num = arguments.length;
var new_arr = arguments[0].filter(function(a){
for (var i = 1; i < arg_num; i++){
if (a === arguments[i]) a = false;
}
return a;
});
return new_arr;
}
destroyer([1, 2, 3, 1, 2, 3], 2, 3);
argument is used to get the list of arguments of current function. While performing filter you used argument, it gives argument of filter method. it differ from the destroyer method argument.
So store the destroyer method argument in one variable.
Try this code.
function destroyer(arr) {
// Remove all the values
var args = arguments;
var arg_num = args.length;
var flag;
var new_arr = arguments[0].filter(function (a) {
flag = false;
for (var i = 1; i < arg_num; i++) {
if (a === args[i]) {
flag = true;
break;
};
}
if (flag)
return false;
else
return true;
});
return new_arr;
}
console.log(destroyer([0, 2, 3, 0, 2, 3], 0, 3));
Hope this will help you.
You can instead use difference function from lodash library. It's a well tested and widely used utility library.
var _ = require('lodash');
var result = _.difference([1, 2, 3, 1, 2, 3], [2, 3])); // returns [1, 1]
The problem is that the arguments keyword is usually bound to the current function, which in this case is the anonymous function used in filter.
ES6 allows to fix this easily by introducing arrow functions, which don't bind arguments.
(function destroyer(arr) {
var arg_num = arguments.length;
return arguments[0].filter(a => {
for (var i = 1; i < arg_num; i++){
if (a === arguments[i]) return false;
}
return true;
});
})([1, 2, 3, 1, 2, 3], 2, 3); // [1, 1]
However, note this function has cost n m where n is the number of elements in the array and m is the number of additional arguments. But could be better.
For example, if the additional arguments will always be numbers, you can sort them, and then use dichotomic searches. This will cost n lg(m).
Another approach would be using a hash table. It will still cost n m on the worst case, but only n on average.
If you don't want to implement that manually, you can use a ES6 set. Its specific cost will be implementation dependent, but is required to be sublinear on average.
(function destroyer(arr, ...args) {
var s = new Set(args);
return arr.filter(a => !s.has(a));
})([1, 2, 3, 1, 2, 3], 2, 3); // [1, 1]

Flattening an array recursively (withoout looping) javascript

I'm practicing recursion and am trying to flatten an array without looping (recursion only). As a first step I did the iterative approach and it worked, but am stuck on the pure recursion version:
function flattenRecursive(arr) {
if (arr.length === 1) {
return arr;
}
return Array.isArray(arr) ? arr = arr.concat(flattenRecursive(arr)) : flattenRecursive(arr.slice(1))
}
console.log(flattenRecursive([
[2, 7],
[8, 3],
[1, 4], 7
])) //should return [2,7,8,3,1,4,7] but isn't - maximum call stack error
//working version (thanks #Dave!):
function flattenRecursive(arr) {
if (arr.length === 1) {
return arr;
}
return arr[0].concat(Array.isArray(arr) ? flattenRecursive(arr.slice(1)) : arr);
}
console.log(flattenRecursive([
[2, 7],
[8, 3],
[1, 4], 7
]))
//returns [ 2, 7, 8, 3, 1, 4, 7 ]
Here's a working version that's slightly less verbose.
//using reduce
function flattenRecursive(arr) {
return arr.reduce(function(result, a){
return result.concat(Array.isArray(a) ? flattenRecursive(a) : a);
}, []);
}
//without reduce
function flattenRecursive2(arr) {
if (arr.length === 0)
return arr;
var head = arr.shift();
if (Array.isArray(head))
return flattenRecursive2(head).concat(flattenRecursive2(arr));
else
return [head].concat(flattenRecursive2(arr));
}
var testArray = [1,[2, 3],[[4, 5, [6, 7]], [8, 9]], 10];
console.log(flattenRecursive(testArray));
console.log(flattenRecursive2(testArray));
<script src="http://gh-canon.github.io/stack-snippet-console/console.min.js"></script>
Sorry, I do not have the reputation to comment. Here are my 2 cents.
1) be careful about your initial condition. Here, it seems that if your input is Μ€arr = [[1,2]], your function returns [[1,2]], while you would like it to return [1,2].
2) in the core of the recursion, you must be sure than you recursively call your function with a smaller argument. Here, you should concat the first element of your array with the flattened rest of the array. The functionslice` may be handy for that.
3) It would be also be possible to use a reduce-like function.

Elegant way to find contiguous subarray within an array in JavaScript?

I wanted to write a function to find a contiguous subarray within a given array from a given starting index and return the index of the subarray within the array if it's found, and -1 if it's not found. This is similar to String.indexOf, but for arrays and subarrays instead of strings and substrings.
This is my working code:
var find_csa = function (arr, subarr, from_index) {
if (typeof from_index === 'undefined') {
from_index = 0;
}
var i, found, j;
for (i = from_index; i < 1 + (arr.length - subarr.length); ++i) {
found = true;
for (j = 0; j < subarr.length; ++j) {
if (arr[i + j] !== subarr[j]) {
found = false;
break;
}
}
if (found) return i;
}
return -1;
};
And these are my tests and their expected values:
console.log(find_csa([1, 2, 3, 4, 5], [2, 3, 4]) === 1);
console.log(find_csa([1, 2, 3, 4, 5], [5]) === 4);
console.log(find_csa([1, 2, 3, 4, 5], [1, 3]) === -1);
console.log(find_csa([1, 2, 3, 4, 5], [42]) === -1);
console.log(find_csa([1, 2, 3, 4, 5], []) === 0);
console.log(find_csa([3, 4, 3, 4, 3, 4], [3, 4, 3], 1) === 2);
console.log(find_csa([6, 6, 6, 7], [6, 6, 7]) === 1);
console.log(find_csa([12, 9, 16, 42, 7, 866, 3], [16, 42, 7, 866]) === 2);
My code passes the tests, but as you can see, it uses a boolean value found in the inner loop which is just my messy, ad-hoc way of continuing an outer loop from a nested loop. is there a cleaner way of writing it? I looked into Array.prototype.findIndex but it is an experimental technology at the moment so I can't use it. I want a method that works in most browsers. I know there is a "polyfill" code snippet written on the Mozilla page, but that is even longer than my current code and it will be slower due to the function calls, so I'd rather avoid it.
My primary goal for this function is performance (the subarrays will be very small, so I believe that using Boyer-Moore string search algorithm or tries is a tad overkill), and then my secondary goal is elegance of my implementation. With those two goals in mind, I would like to know if there is a better way of writing this code, or if there are any JavaScript features or functions that I'm missing that could help me avoid the found boolean.
JSFiddle if it helps anyone: http://jsfiddle.net/qc4zxq2p/
Are there any JavaScript features or functions that I'm missing that could help me avoid the found boolean
Yes, you can use a label on your outer loop:
function find_csa(arr, subarr, from_index) {
var i = from_index >>> 0,
sl = subarr.length,
l = arr.length + 1 - sl;
loop: for (; i<l; i++) {
for (var j=0; j<sl; j++)
if (arr[i+j] !== subarr[j])
continue loop;
return i;
}
return -1;
}
This is the same as yours, just prettified a bit (at least to my aesthetics):
var find_csa = function (arr, subarr, from_index) {
from_index = from_index || 0;
var i, found, j;
var last_check_index = arr.length - subarr.length;
var subarr_length = subarr.length;
position_loop:
for (i = from_index; i <= last_check_index; ++i) {
for (j = 0; j < subarr_length; ++j) {
if (arr[i + j] !== subarr[j]) {
continue position_loop;
}
}
return i;
}
return -1;
};
The inner loop can be reduced to a single line using the array method every:
if(subarr.every(function(e, j) { return (e === arr[i + j]); })
return i;
or (ES6 proposal):
if(subarr.every( (e, j) => (e === arr[i + j]) ))
return i;
But this may be just a curiosity or educational example, unless you don't care about performance.
Inside your loop, you can eliminate the found variable and avoid continue like this:
for (j = 0; j < subarr.length; ++j) {
if (arr[i + j] !== subarr[j]) break;
}
/*
* the above loop breaks in two cases:
* normally: j === subarr.length
* prematurely: array items did not match
* we are interested in kowing if loop terminated normally
*/
if (j === subarr.length) return i;
Having said that, here is my solution using Array.join and String.indexOf. This is only good for array of Numbers:
function find_csa(arr, subarr, from_index) {
from_index |= 0;
if (subarr.length === 0) {
return from_index;
}
var haystack = "," + arr.slice(from_index).join(",") + ",",
needle = "," + subarr.join(",") + ",",
pos = haystack.indexOf(needle);
if (pos > 0) {
pos = haystack.substring(1, pos).split(",").length + from_index;
}
return pos;
}
console.log("All tests should return true");
console.log(find_csa([1, 2, 3, 4, 5], [1, 2, 3]) === 0);
console.log(find_csa([1, 2, 3, 4, 5], [2, 3, 4]) === 1);
console.log(find_csa([1, 2, 3, 4, 5], [5]) === 4);
console.log(find_csa([1, 2, 3, 4, 5], [6]) === -1);
console.log(find_csa([1, 2, 3, 4, 5], [1, 3]) === -1);
console.log(find_csa([6, 6, 6, 7], [6, 6, 7]) === 1);
console.log(find_csa([1, 2, 3, 4, 5], []) === 0);
console.log(find_csa([3, 4, 3, 4, 3, 4], [3, 4, 3], 1) === 2);
console.log(find_csa([1, 2, 3, 4, 5], [], 1) === 1);
Reading initial discussion based on zerkms proposition, I was interested to try a solution using JSON.stringify, despite the unfavorable opinions.
Then I finally got a solution, which passes all tests properly.
Probably not the faster method, but surely the shortest one:
var find_csa = function (arr, subarr, from_index) {
var start=from_index|0,
needle=JSON.stringify(subarr),
matches=JSON.stringify(arr.slice(start)).
match(new RegExp('^\\[(.*?),?'+
needle.substr(1,needle.length-2).replace(/([\[\]])/g,'\\$1')
));
return !!matches?(matches[1].length?matches[1].split(',').length:0)+start:-1;
}
The above code accepts arrays of arrays, as suggested by Shashank, but fails to process items containing commas.
So I developped another solution which also accepts commas (thanks to Steven Levithan for the elegant tip about while(str!=(str=str.replace(regexp,replacement)));).
But it is only for fun, since:
the code is not so short, now... Sigh!
it probably consumes a lot of CPU time
it doesn't properly work with empty items (they are ignored)
I suspect (and didn't dig deeper :-) it might fail with complex objects as items
Anyway, here is it:
var find_csa = function (arr, subarr, from_index) {
var start=from_index|0,
commas=new RegExp('(?:(\')([^,\']+),([^\']+)\'|(")([^,"]+),([^"]+))"'),
strip_commas='$1$2$3$1$4$5$6$4',
haystack=JSON.stringify(arr.slice(start)),
needle=JSON.stringify(subarr).replace(/^\[(.*)\]$/,'$1');
while(haystack!=(haystack=haystack.replace(commas,strip_commas)));
while(needle!=(needle=needle.replace(commas,strip_commas)));
matches=haystack.match(new RegExp('^\\[(.*?),?'+needle.replace(/([\[\]])/g,'\\$1')));
return !!matches?(matches[1].length?matches[1].split(',').length:0)+start:-1;
}
i fixed this question with this code:
getCount(arr)
{
const chunked = [];
for(let i=0; i<arr.length; i++) chunked[i] = [];
let sub = 0;
for (let i = 1; i < arr.length; i++) {
if (arr[i]>arr[i-1]) {
chunked[sub].push(...[arr[i-1],arr[i]]);
} else {
sub++
}
}
const chunked2 = [...chunked.filter(k=>k.length).map(k => [...new Set(k)])];
for (let i = 0; i < chunked2.length; i++) {
if (chunked2[i+1])
if( chunked2[i][chunked2[i].length - 1] > chunked2[i+1][0]) {
chunked2[i+1].shift();
}
}
return [].concat.apply([], chunked2).lenght;
}

Categories