Related
So as the question states, I'm attempting to write a function for which the input can be either an Array or an Object (as well as a callback to test them against).
The desired return value is an Array / Object of the elements (value/key pairs in the case of the Object) that passed the test - but the original Array / Object should be updated to remove those elements.
For example - if you passed in an "isEven" callback function that determines if numbers are even, then the expected results would be:
let a = [1, 2, 3, 4]
reject(a)
//Output:
[2, 4]
console.log(a);
//expected output
[1, 3]
So far I've been trying to write an conditional scenario based on Array.isArray(input), to use one set of code for handling arrays, another set for handling objects. However, it's been not working properly for objects, and I'm curious if there'd be one way to write it that would work for both cases? If not what might be the best approach here?
My rough attempt to this point, if the code is helpful:
function reject(collection, callback) {
let newArr;
if (!collection.constructor == Array) {
newArr = {};
for (let [key, value] of Object.entries(collection)) {
if (!callback(value)) {
newArr[key] = value;
}
}
} else {
newArr = [];
for (let el of collection) {
if (!callback(el)){
newArr.push(el);
}
}
}
return newArr;
}
You can use Array.isArray to check if an object is an array. To remove elements from the array, you can iterate backwards and use Array#splice.
function reject(o, f) {
if (Array.isArray(o)) {
let res = [];
for (let i = o.length - 1; i >= 0; i--)
if (f(o[i])) res.push(o.splice(i, 1)[0]);
return res.reverse();
}
let res = {};
for (const [k, v] of Object.entries(o))
if (f(v)) {
res[k] = v;
delete o[k];
}
return res;
}
let a = [1, 2, 3, 4];
console.log(reject(a, x => x % 2 === 0), a);
let obj = {a : 1, b : 2, c : 3, d : 4};
console.log(reject(obj, x => x > 2), obj);
I tried to do a recursive function with clousure, but dosent work.
I tried to add each single number from array to finally return [1,2,3,4,5,6,7,8] but just return me [1,2,5] something weird is I put a console.log inside of else it's print me all numbers.
Could you explain me why happens this
var array = [1, 2, [3, 4], 5, [6, [7, 8]]]; // --> 1,2,3,4,5,6,7,8
function recursiveArrayPrint(array) {
let resultado = []
return function iterator() {
for (let i = 0; array.length > i; i++) {
if (Array.isArray(array[i])) {
recursiveArrayPrint(array[i])()
} else {
console.log(array[i])
resultado.push(array[i])
}
}
return resultado
}
}
const test = recursiveArrayPrint(array);
console.log('test: ',test())
the bug in your program
resultado is declared inside of recursiveArrayPrint
inside iterator you call recursiveArrayPrint, which creates a new resultado each time
only the outermost resultado is returned
function recursiveArrayPrint(array) {
let resultado = [] // #1
return function iterator() {
for (let i = 0; array.length > i; i++) {
if (Array.isArray(array[i])) {
recursiveArrayPrint(array[i])() // #2
} else {
console.log(array[i])
resultado.push(array[i])
}
}
return resultado // #3
}
}
how to fix
function recursiveArrayPrint(array) {
// no need for resultado
function* iterator() { // use generator
for (let i = 0; array.length > i; i++) {
if (Array.isArray(array[i])) {
yield *recursiveArrayPrint(array[i]) // yield
} else {
yield array[i] // yield, not console.log
// no need for resultado
}
}
// no need for resultado
}
return iterator()
}
const arr = [1, 2, [3, 4], 5, [6, [7, 8]]]
for (const x of recursiveArrayPrint(arr))
console.log(x)
1
2
3
4
5
6
7
8
we can do better
recursiveArrayPrint is not a good name. We've disentangled the console.log effect from the iteration and the caller is free to decide what to do with the output. We can just call it recursiveArray instead
Inner function iterator is a bit useless now. No need for this abstraction anymore
for loop with i is prone to off-by-one errors. We can write this in a clearer way using for..of loop
function* recursiveArray(array)
{ for (const x of array)
if (Array.isArray(x))
yield *recursiveArray(x)
else
yield x
}
const arr = [1, 2, [3, 4], 5, [6, [7, 8]]]
for (const x of recursiveArray(arr))
console.log(x)
If you are hoping to get the flattened array as a return value, JavaScript provides Array.from which turns any iterable into an array -
console.log(Array.from(recursiveArray(arr)))
[1,2,3,4,5,6,7,8]
not just for arrays
We can rename recursiveArray to traverse and it can operate as a generic function
Instead of assuming the first input is an array, we check isArray first and only then run the for..of loop. Otherwise simply yield the input
function* traverse(t)
{ if (Array.isArray(t))
for (const x of t)
yield *traverse(x)
else
yield t
}
const arr = [1, 2, [3, 4], 5, [6, [7, 8]]]
for (const x of traverse(arr))
console.log(x)
1
2
3
4
5
6
7
8
Writing robust functions will help you in the long run. traverse could easily be expanded to step through Objects now or other iterables. Now when non-array input is given, instead of an error, we get a sensible result -
for (const x of traverse(123))
console.log(x)
123
So what the recursiveArrayPrint function does, is that it creates a variable and returns a function that has closure over this variable. I think you get this part. The part that might confuse you, is that when you encounter an array, you call the recursiveArrayPrint function again, which will only create a new variable and return a new function that has closure over this variable, but this is in an entirely different scope.
It might make more sense to look at it this way.
var array = [1, 2, [3, 4], 5, [6, [7, 8]]]; // --> 1,2,3,4,5,6,7,8
function recursiveArrayPrint(array) {
let resultado = []
return function iterator() {
for (let i = 0; array.length > i; i++) {
if (Array.isArray(array[i])) {
// This is where a new closure is created, but with reference to another variable
let resultado1 = []
return function iterator1() {
for (let i = 0; array.length > i; i++) {
if (Array.isArray(array[i])) {
// This is where a new closure is created, but with reference to another variable
}
return resultado1
}
}
} else {
console.log(array[i])
resultado.push(array[i])
}
}
return resultado
}
}
const test = recursiveArrayPrint(array);
console.log('test: ',test())
I think what you intend to do is something more like this.
var array = [1, 2, [3, 4], 5, [6, [7, 8]]]; // --> 1,2,3,4,5,6,7,8
function recursiveArrayPrint() {
let resultado = []
return function iterator(array) {
for (let i = 0; array.length > i; i++) {
if (Array.isArray(array[i])) {
iterator(array[i])
} else {
console.log(array[i])
resultado.push(array[i])
}
}
return resultado
}
}
const test = recursiveArrayPrint();
console.log('test: ',test(array))
I think the main problem in your code is not just a difficulty in mastering the different elements of the JavaScript language or simply knowing how to play with the scope of variables for example.
No, it's just algorithmic, and that's arguably the hardest thing to do. We all fell into the trap of doing complicated things by trying in vain to make them simple.
It takes time to experience, to practice incessantly to accustom our brain to handling concepts in order to know how to put them together without fail.
Recursive solution:
const arr1 = [1, 2, [3, 4], 5, [6, [7, 8]]];
function recursiveflat(arr)
{
let res = []
for (let el of arr)
if (Array.isArray(el)) res.push(...recursiveflat(el) )
else res.push(el)
return res
}
console.log( JSON.stringify( recursiveflat( arr1 )))
.as-console-wrapper {max-height: 100%!important;top:0}
The idea of a closure as a recursive function seems incompatible to me, the only thing that can be done is to have a recursive function in a closure.
const arr1 = [1, 2, [3, 4], 5, [6, [7, 8]]]
, arr2 = ['a','b',[['c','d'],'e','f'],'g',['h',['i',['j'],'k'],'l'],'m']
function recursiveflatClosure(arr_in)
{
let resFLat = [] // global array is more efficient in this case
recursiveflat(arr_in) // builder
return ()=>resFLat // closure answer
function recursiveflat(arr)
{
for (let el of arr )
if (Array.isArray(el)) recursiveflat(el)
else resFLat.push(el)
}
}
let test1 = recursiveflatClosure( arr1 )
, test2 = recursiveflatClosure( arr2 )
console.log('test 1:', JSON.stringify( test1() ))
console.log('test 2:', JSON.stringify( test2() ))
.as-console-wrapper {max-height: 100%!important;top:0}
This is a somewhat strange requirement. It seems that the desired API is for a function that accepts a nested array and returns a function that returns the flattened version of it. It's unusual as generally we would not have any need for that intermediate function.
But we can update your code to get this to work. The biggest problem is that your internal recursive iterator function takes no argument. When you make a recursive call to it, there is no place to store the data you want to process. So let's make this accept an array argument:
function iterator(array) {
Now we note that this internal function is supposed to be recursive, but you're trying to call the outer function from inside. Let's call this instead:
if (Array.isArray(array[i])) {
iterator(array[i])
} else {
And now, we can't return that iterator function directly. We need to add a level of indirection, one that passes our original array into it.
function iterator(array) {
// ...
}
return function() {return iterator (array)}
Together, these make for what seems to be the smallest change to make your function work correctly:
const array = [1, 2, [3, 4], 5, [6, [7, 8]]]; // --> 1,2,3,4,5,6,7,8
function recursiveArrayPrint(array) {
let resultado = []
function iterator(array) {
for (let i = 0; array.length > i; i++) {
if (Array.isArray(array[i])) {
iterator(array[i])
} else {
console.log(array[i])
resultado.push(array[i])
}
}
return resultado
}
return function() {return iterator (array)}
}
const test = recursiveArrayPrint(array);
console.log('test: ',test())
.as-console-wrapper {max-height: 100% !important; top: 0}
However
This does not sound like a good idea. If you want a flattened version of an array, other answers and comments here suggest better ways, including better recursive solutions.
Moreover, recursion is a technique strongly associated with functional programming, and a fundamental tenet of functional programming is to avoid data mutation. But this solution is built around mutating your temporary array, resultado. I would look to the other answers for better ways of doing this.
A simple and clean flattening approach based on a reduce task and a recursively used reducer function ... no need of a closure either ...
function collectItemFlattened(list, item) {
return list.concat(
Array.isArray(item)
? item.reduce(collectItemFlattened, [])
: item
);
}
console.log(
[1, 2, [3, 4], 5, [6, [7, 8]]].reduce(collectItemFlattened, [])
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
The following code should take a given array and filter out all arguments that follow. For example the below code should return: [1, 1].
function destroyer(arr) {
var args= [];
args.push(arguments[0]);
var realArgs = args[0];
var filteredArr=[];
function removeIt (val){
return val != ;
}
filteredArr= realArgs.filter(removeIt);
return filteredArr;
}
destroyer([1, 2, 3, 1, 2, 3], 2, 3);
I can't figure out the filter function. Do I need to use Boolean somehow?
If you want to access the arguments of the function you should use arguments not args. The filtering part is not straightforward because javascript still doesn't have a builtin function we can use, so we have to implement it by ourselves (that's the includes function).
function destroyer(arr) {
var arr = arguments[0];
var toFilter = [];
for (var i = 0; i < arguments.length; i++)
toFilter.push(arguments[i]);
function removeIt (arr, numsToFilter){
var array = arr.slice(); // make sure to copy the array in order not to modify the original
for (var i = 0; i < array.length; i++) {
if (includes(numsToFilter, array[i])) {
delete array[i];
}
}
return array;
}
function includes(arr, k) {
for(var i=0; i < arr.length; i++){
if( arr[i] === k){
return true;
}
}
return false;
}
return removeIt(arr, toFilter);
}
destroyer([1, 2, 3, 1, 2, 3], 2, 3);
Running the code-example
You can call Array#slice on the arguments object to convert it to an array. We'll slice from the index 1 (skip the arr). Then you can Array#filter the arr array. On each iteration check if the item is in the toRemove array by using Array#indexOf. Items that are in toRemove will be filtered out. Note - Array#filter returns a new array.
function destroyer(arr) {
// convert all the arguments but arr into array
var toRemove = [].slice.call(arguments, 1);
// filter the original array
return arr.filter(function removeIt(val){
// keep all vals that are not in the toRemove array
return toRemove.indexOf(val) === -1;
});
}
var result = destroyer([1, 2, 3, 1, 2, 3], 2, 3);
console.log(result);
I've been trying to create a generic partition function that returns an array of arrays. the function should be made under the following guidelines:
Arguments:
An array
A function
Objectives:
Call <function> for each element in <array> passing it the arguments:
element, key, <array>
Return an array that is made up of 2 sub arrays:
0. An array that contains all the values for which <function> returned something truthy
1. An array that contains all the values for which <function> returned something falsy
Here is what I have so far. I get the return of two. I feel like maybe I just have to do the filter function on two separate occasions, but I'm not sure how to put it together. Thoughts and suggestions are highly appreciated.
_.partition = function (collection, test){
var allValues = [];
var matches = [];
var misMatches = [];
_.filter(collection.value, function(value, key, collection){
if (test(value[key], key, collection) === "string"){
matches.push(value[key]);
}else{
misMatches.push(value[key]);
}
});
return allValues.push(matches, misMatches);
}
Here is a version which uses reduce:
function partition(arr, filter) {
return arr.reduce(
(r, e, i, a) => {
r[filter(e, i, a) ? 0 : 1].push(e);
return r;
}, [[], []]);
}
Here's an alternative version which uses Array#filter to find the matches, and builds an array of non-matches as it goes along:
function partition(arr, filter) {
var fail = [];
var pass = arr.filter((e, i, a) => {
if (filter(e, i, a)) return true;
fail.push(e);
});
return [pass, fail];
}
You're correct about calling the filter method on separate occasions. One filter call would obtain the truthy values; the other would obtain the falsy values:
_.partition = function(collection, testFunc) {
var matches = collection.filter(function(elem) {
return test(elem) === 'string';
});
var misMatches = collection.filter(function(elem) {
return test(elem) !== 'string';
});
return [matches, misMatches];
}
You are close, but there are a couple issues I see:
You are returning the result of allValues.push which is not allValues itself, but rather the new length of the array.
You are using _.filter to iterate over array elements and sort them into two arrays. This is strange, since it's not the intended use of _.filter.
If you want a quick and readable solution using _.filter, this will work:
_.mixin({
partition: function(collection, test) {
return [
_.filter(collection, test), // items which satisfy condition
_.filter(collection, _.negate(test)) // items which don't
];
}
});
A more efficient solution which makes only one pass over the collection is below (this is almost what you already have):
_.mixin({
partition: function(collection, test) {
var matches = [], misMatches = [], value;
// can replace this loop with _.each
for (var i = 0, len = collection.length; i < len; ++i) {
value = collection[i];
// push the value into the appropriate array
if (test(value, i, collection)) {
matches.push(value);
} else {
misMatches.push(value);
}
}
return [matches, misMatches];
}
});
Usage examples (and Plunker):
function isOdd(x) {
return x % 2;
}
// _.mixin allows you to do either one of these
_.partition([1, 2, 3, 4, 5, 6], isOdd); // result: [[1, 3, 5], [2, 4, 6]]
_([1, 2, 3, 4, 5, 6]).partition(isOdd); // result: [[1, 3, 5], [2, 4, 6]]
// this is a use case you brought up in the comments
_.partition([1, "a", 2, "b", 3, "c"], _.isString); // result: [["a", "b", "c"], [1, 2, 3]]
This is generally known as partition ing in functional languages. You suply an array (xs) and a predicate function (p) to a reduceing function with initial value [[],[]].
var partition = (xs,p) => xs.reduce( (r,e) => ( p(e) ? r[0].push(e)
: r[1].push(e)
, r
)
, [[],[]]
);
Such that;
> partition([1,2,3,4,5,6,7,8,9,0], x => x < 5)
> [[1, 2, 3, 4, 0],[5, 6, 7, 8, 9]]
I can not figure out why my code does not flatten out the nested arrays as indicated. I'd greatly appreciate some help here. I used a recursion to get to the actual value of the nested array. I tried to debug my code, and it seems to replace my results array every time the recursion takes place.
//Helper methods
function toType(obj){
return ({}).toString.call(obj).match(/\s([a-zA-Z]+)/)[1].toLowerCase()
}
function each(collection, callback){
if (Array.isArray(collection)){
for (var i = 0; i < collection.length; i++){
callback(collection[i], i, collection)
}
} else {
for (var i in collection){
callback(collection[i], i, collection)
}
}
}
//Flatten function
function flatten(array, isShallow=false, callback){
var results = [];
each(array, function(item){
if (!!isShallow && toType(item) === 'array'){
each (item, function(value){
results.push(value);
})
} else if (toType(item) !== 'array'){
results.push(item);
} else {
return flatten(item)
}
})
return results;
}
flatten([1, [2], [3, [[4]]]]);
// ---> [1]
Your problem appears to be with this line:
return flatten(item)
Returning here is a problem because the loop will end and the current entries in results will be ignored, as well as the remaining items. The line should be changed to instead append the results of
flatten(item)
to results array via push.
I recommend using a library for this sort of thing. http://underscorejs.org/#flatten is a great one!
Please see the refactored code below.
The major change is that instead of creating new copies of results, we are passing it to subsequent calls to flatten as a reference.
Please see the added comments
//Helper methods
function toType(obj){
return ({}).toString.call(obj).match(/\s([a-zA-Z]+)/)[1].toLowerCase()
}
function each(collection, callback){
if (Array.isArray(collection)){
for (var i = 0; i < collection.length; i++){
callback(collection[i], i, collection)
}
} else if(typeof collection === 'object'){
//type number was failing here
for (var i in collection){
callback(collection[i], i, collection)
}
}
else {
//default for primitive types
callback(collection, 0, collection);
}
}
//Flatten function
//Removed isShallow, how do we know if its shallow or not?
//Added results as arg, so we only manipulate the reference to results
//And to not create multiple scopes of var results;
function flatten(array, results, callback){
results = results || [];
each(array, function(item){
//removed 3rd if clause not needed.
//Only need to know if item is an object or array
if (toType(item) === 'array' || toType(item) === 'object'){
each (item, function(value){
flatten(value,results);
})
} else {
results.push(item);
}
})
return results;
}
var array1 = [1,[2,[3,4]]];
var array2 = [5,[6,[7,[8, {a:9,b:[10,11,12]}]]]];
var obj = {a:array1, b:array2};
console.log(flatten(array1)); // [ 1, 2, 3, 4 ]
console.log(flatten(array2)); // [ 5, 6, 7, 8, 9, 10, 11, 12 ]
console.log(flatten(obj)); // [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ]
You can do something like that:
function flatten(array, i) {
i = i || 0;
if(i >= array.length)
return array;
if(Array.isArray(array[i])) {
return flatten(array.slice(0,i)
.concat(array[i], array.slice(i+1)), i);
}
return flatten(array, i+1);
}
Example:
var weirdArray = [[],1,2,3,[4,5,6,[7,8,9,[10,11,[12,[[[[[13],[[[[14]]]]]]]]]]]]]
flatten(weirdArray);
//returns ==> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
The best way to flatten you array in javascript is to use splice function of Array. Please follow the following code. It works for nesting as well.
function flattenArray(arr){
for(var i=0;i<arr.length;i++){
if(arr[i] instanceof Array){
Array.prototype.splice.apply(arr,[i,1].concat(arr[i]))
}
}
return arr;
}
Use underscore.js's flatten function (http://underscorejs.org/#flatten). Underscore.js is a 3rd party library with 80 some-odd functions to make your life as a javascript programmer easier. Don't reinvent the wheel.
var _ = require('underscore');
_.flatten([1, [2], [3, [[4]]]]);
=> [1, 2, 3, 4];