How to understand this fragment of code in javascript react - javascript

I found the implementation of the function in js in the Internet, This function recursively filters an array of objects, each object may have property "children" which is array of objects and that objects also may have children and so on. The function works correctly but I didn't understand it a bit.
This is my function:
getFilteredArray (array, key, searchString) {
const res = array.filter(function iter(o) {
if (o[key].toLowerCase().includes(searchString.toLowerCase())) {
return true;
}
if(o.children){
return (o.children = o.children.filter(iter)).length;
}
});
this.setState({
filteredArray: res
});
}
I don't understand in this code:
if(o.children){
return (o.children = o.children.filter(iter)).length;
}
Can we simplify this expression (o.children = o.children.filter(iter)).length; ?
Why we return the length of array not the array itself?
And function "iter" accepts one argument that is object. Why we just write o.children.filter(iter) without any arguments passed to the "iter" here? according to recursion tutorials, there are arguments always passed, if the function requires them. But here we don't pass, this is strangely.

Here's a re-write that strives for clarity and simplifies the logic a bit to remove distractions:
const recursivelyFilter = (arr, key, searchString) => {
return arr.filter(function iter(obj) {
if (obj[key].includes(searchString)) {
return true;
}
if (obj.children) {
obj.children = obj.children.filter(child => iter(child));
return obj.children.length > 0;
}
return false;
});
};
Array#filter is the meat of this code. filter accepts a callback which should return a boolean to determine whether an element will be included in the result array. It doesn't work in-place.
The base cases (terminating conditions for the recursion) are:
If the current object (a node in the tree) has a key key matching searchTerm, return true.
If the current node doesn't match searchTerm and has no children, return false. In the original code, returning undefined defaults to falsey.
The recursive case is:
If the current node has children, recursively filter them using the boolean result of the iter function. If at least one descendant of the current node passes the filter condition, include the current node in its parent's children array, otherwise remove it. The code treats the length of the new child array as a boolean to achieve this.
return (o.children = o.children.filter(iter)).length; first performs an assignment to o.children, necessary because o.children.filter returns a fresh copy of the array. After the assignment is finished, the expression resolves to the new o.children and its length property is returned. The length is then treated as truthy/falsey according to the recursive case rule described above. This amounts to:
obj.children = obj.children.filter(child => iter(child));
return obj.children.length > 0;
If we returned the array itself, everything would be treated as true because an empty array, [], evaluates to true. [].length, on the other hand, evaluates to false, which is the desired outcome.
As for o.children.filter(iter), Array#filter accepts a callback as its first parameter, which can be a function variable such as iter. Another option is creating an anonymous function directly in the argument list; this is usually how it's done. The above version adds an arrow wrapper, but it's an obviously unnecessary extra layer of indirection since the lone parameter is just passed through the wrapper. We could also use the function keyword here; whatever the case, the goal is the same, which is that we pass a function into filter to call on each element.
By the way, the function assumes that key is set on all the nodes of the nested objects in array and that obj[key].includes is defined. Clearly, the author had a very specific data structure and purpose in mind and wasn't interested in prematurely generalizing.
Here's test code illustrating its operation. Playing around with it should help your understanding.
const recursivelyFilter = (arr, key, searchString) => {
return arr.filter(function iter(obj) {
if (obj[key].includes(searchString)) {
return true;
}
if (obj.children) {
obj.children = obj.children.filter(child => iter(child));
return obj.children.length > 0;
}
return false;
});
};
const arr = [
{
foo: "bar",
children: [
{
foo: "baz",
children: [
{foo: "quux"},
{foo: "quuz"},
]
}
]
},
{
foo: "corge",
children: [
{foo: "quux"}
]
},
{
foo: "grault",
children: [{foo: "bar"}]
}
];
console.log(recursivelyFilter(arr, "foo", "quux"));

Perhaps some code changes will help you understand what is going on.
function iter(o){
if (o[key].toLowerCase().includes(searchString.toLowerCase())) {
return true;
}
if(o.children){
o.children = o.children.filter(iter);
return o.children.length;
}
}
getObject (array, key, searchString) {
const res = array.filter(iter);
this.setState({
filteredArray: res
});
}
The iter function is executed by array.filter for each element in the array, if it returns true the element is added to the result, otherwise it is excluded.
In this scenario, if the item itself isn't a direct match, but a child item is we want to keep it. The function handles that by filtering the o.children array using the same criteria. The filtered version of the array is re-assigned to o.children.
The length of the filtered array is then returned as the true/false value that the previous array.filter is looking for. If the array is empty, the length is zero, which is false so the item is excluded. Otherwise a non-zero value is returned, which is true, so the item is kept.

class A {
static getFilteredArray(array, key, searchString) {
const query = searchString.toLowerCase()
const res = array.filter(function searchText(item) {
const text = item[key].toLowerCase()
if (text.includes(query)) {
return true
}
if (item.children) { // if object has children, do same filtering for children
item.children = item.children.filter(searchText)
return item.children.length
// same as below, but shorter
// item.children = item.children.filter(function (child) {
// return searchText(child)
// })
}
})
return res
}
}
const input = [{
name: 'John',
children: [{
name: 'Alice'
}]
}]
const output1 = A.getFilteredArray(input, 'name', 'Alic')
const output2 = A.getFilteredArray(input, 'name', 'XY')
console.log('Alic => ', output1)
console.log('XY =>', output2)

The return is not for getObject. It is for the .filter() callback.
The answer is therefore simple: filter expects its callback to return a true/false value depending on weather or not you want to keep or remove the object form the resulting array. Therefore returning the length is enough since 0 is falsy and all other numbers are truthy.

The answer from ggorlen admirably explains how this function works.
But this function and ggorlen's simplification of it do something I believe a filter should never do: they mutate the initial data structure. If you examine this value before and after the call in ggorlen's example, you will notice that it changes from 2 to 1:
arr[0].children[0].children.length
And this issue is present in the original code too. As far as I can see, there is no simple way to fix this with an implementation based on Array.prototype.filter. So another implementation makes some sense. Here's what I have come up with, demonstrated with ggorlen's test case:
const getFilteredArray = (arr, test) => arr .reduce
( ( acc
, {children = undefined, ...rest}
, _ // index, ignored
, __ // array, ignored
, kids = children && getFilteredArray (children, test)
) => test (rest) || (kids && kids .length)
? acc .concat ({
... rest,
...(kids && kids .length ? {children: kids} : {})
})
: acc
, []
)
const arr = [
{foo: "bar", children: [{foo: "baz", children: [{foo: "quux"}, {foo: "quuz"}]}]},
{foo: "corge", children: [{foo: "quux"}]},
{foo: "grault", children: [{foo: "bar"}]}
];
const test= (item) => item.foo == 'quux'
console .log (
getFilteredArray (arr, test)
)
Note that I made it a bit more generic than requested, testing with an arbitrary predicate rather than testing that key property matches the searchString value. This makes the code simpler and the breakdown in logic clearer. But if you want that exact API, you can make just a minor change.
const getFilteredArray = (arr, key, searchString) => arr .reduce
( ( acc
, {children = undefined, ...rest}
, _ // index, ignored
, __ // array, ignored
, kids = children && getFilteredArray (children, key, searchString)
) => rest[key] === searchString || (kids && kids .length)
? acc .concat ({
... rest,
...(kids && kids .length ? {children: kids} : {})
})
: acc
, []
)
One thing that might be missing is that the predicate runs against a value that does not include the children. If you wanted to be able include them, it's not much more work. We'd have to pass item in place of {children = undefined, ...rest} and destructure them inside the body of the function. That would require changing the body from a single expression to a { - } one.
If I wasn't trying to closely match someone else's API, I would also change the signature to
const getFilteredArray = (test) => (arr) => arr .reduce ( /* ... */ )
This version would allow us to partially apply the predicate and get a function that we can run against different inputs.

Related

Oh man groupBy() and arrayToObject() are breaking me

So I have been working on some extra credit for my classes. I am very new to programming and have already sought help for this same assignment, I started rolling through and now am absolutely lost.
I need to define the two functions groupBy() and arrayToObect() as asked in the below test.
I'm not necessarily looking for the answer but if someone could help point me in the right direction that would be awesome.
What I have deduced is as follows:
I need to be using the spread operator ...
I need to create a newObj = {}
a. and somehow push the element derived from the array into the obj
I need to take the individual values of the array and assign them as keys, with the variances as the properties of the key.
Bracket notation
I have been racking my brain for hours on this now and could really use some guidance!
describe('groupBy', function () {
const input = [4.2, 6.1, 6.3]
const result = groupBy(input, (el) => Math.floor(el))
it('returns an object', function () {
expect(result).to.be.an('object')
})
it('group array items together based on the callback return value', function () {
expect(result).to.be.eql({
4: [4.2],
6: [6.1, 6.3],
})
})
})
describe('arrayToObject', function () {
const input = ['cat', 'dog', 'bird']
const result = arrayToObject(input, (word) => word + 's')
it('returns an object', function () {
expect(result).to.be.an('object')
})
it('object has original array elements as keys and the result of the callback as values', function () {
expect(result).to.be.eql({
cat: 'cats',
dog: 'dogs',
bird: 'birds',
})
})
})
})
groupBy
Write a function called groupBy which takes an array and a callback. The function should return an object. Each return value of the callback should be a key of the object and the values should be the input element with which the callback was called.
arrayToObject
Write a function called arrayToObject which takes an array and a callback. The function should return an object. Each element of the input array should be a key of the returned object and the output from the callback with an element passed in as the corresponding value.
These questions have been answered a million times on stackoverflow. Essentially what you want to be doing here is using the common js array functions map, filter, reduce, flatten, ..., and think about how your problem can be expressed in terms of those.
A lot of real world code is transforming data like this, so it's good to be comfortable doing it.
Also realize that spread syntax copies the entire object which can be pretty inefficient. JavaScript doesn't have persistent data structures! It's usually better to just mutate — as long as your code is what "owns" the object.
const groupBy = (elts, keyfn) =>
elts.reduce((m, elt) => {
const key = keyfn(elt);
m[key] = m[key] || [];
m[key].push(elt);
return m;
}, {});
const arrayToObject = (elts, fn) =>
elts.reduce(
(obj, elt) => Object.assign(obj, { [elt]: fn(elt) }),
{},
);
I figured it out using a for loop!!
function groupBy(arr, callback) {
const newObj = {}
for (let i = 0; i < arr.length; i++) {
if (callback(arr[i])) {
const key = callback(arr[i])
newObj[key] = newObj[key] || []
newObj[key].push(arr[i])
}
}
return newObj
}
function arrayToObject(arr, callback) {
const obj = {}
for (let i = 0; i < arr.length; i++) {
if (callback(arr[i])) {
const key = callback(arr[i])
obj[arr[i]] = obj[key] || callback(arr[i])
}
}
return obj
}

How to access original collection and bind to context while iterating over an array?

In this exercise, we're asked to write a function that replicates the behaviour of _.each (UnderscoreJS) and that passes a series of unit tests.
// COLLECTIONS
// _.each(collection, iteratee, [context])
// Iterates over a collection of elements (i.e. array or object),
// yielding each in turn to an iteratee function, that is called with three arguments:(element, index|key, collection), and bound to the context if one is passed.
// Returns the collection for chaining.
These are the unit tests it should pass (and its current status as per the code block below):
:x: - should iterate over an array
√ - should iterate over an object
√ - should ignore object prototype
:x: - should access the original collection
:x: - should bind to context if one is passed
√ - should return the collection
While I already tried several different methods(for loop, for...of), none has worked so far in successfully iterating over an array.
I also tried to implement two if statements - for the case the collection is an Array or an Object using either the Array.isArray() method, but also the Object.prototype.toString.call() to evaluate if the matches either '[object Object]' or '[object Array]'.
I wrote different variations using if-else statements, switch case and simple if statements. This is where I currently stand. I imagine that I might not be correctly referring to the context, so any hints or remarks would be highly appreciated.
Edit and taking in account #Bergi's comment:
"It should be iteratee.call(context, elem, index, collection) instead of iteratee.call(context, index, elem, collection); otherwise this looks fine"
It turns out this was actually correct, and the problem was due to a mutation in the mocks data from a previous exercise. Thank you both for your input and time spent on this. Cheers!
_.each = function (collection, iteratee, context) {
iteratee = iteratee.bind(context);
if (Array.isArray(collection)) {
for (let [index, elem] of collection.entries()) {
iteratee.call(context, elem, index, collection);
}
}
if (!Array.isArray(collection)) {
for ( let el in collection) {
if (collection.hasOwnProperty(el)) {
iteratee.call(context, collection[el], el, collection);
}
}
}
return collection;
};
"... We're not supposed to use the native forEach :) What puzzles me is that even with fewer redundancies, it doesn't pass the test of iterating over an array." – licvdom
Edit ... and taking into account Bergi's comment ...
"Object.entries does not produce numbers for array indices. Don't use it on arrays." – Bergi
One can choose a generic approach which enables the iteration of both objects (key and value) and list like structures (index and item) like Arrays, HTMLCollections, NodeLists or even Strings.
A list like structure can be successfully converted into an array. The custom each functionality for such an array gets implemented by a simple for statement.
Any other data structure will be passed to Object.entries. The return value then can be iterated with a for...of statement where the currently processed entry can be destructured into [key, value].
function isString(value) {
return (/\[object\s+String\]/)
.test(
Object.prototype.toString.call(value)
);
}
const _ = {
each(possiblyIterable, iteratee, target) {
if (typeof iteratee !== 'function') {
throw new ReferenceError('2nd argument needs to be a function type');
}
// returns an array for any array or any
// list like structures like `String`s
// `NodeList`s or `HTMLCollection`s.
const arr = Array.from(possiblyIterable ?? []);
const isListLike = (arr.length >= 1);
// assure a context or the null value.
target = target ?? null;
if (isListLike) {
// requirement: "should access the original collection"
// - tricky due to implemented list like string iteration,
// but solvable.
const list = isString(possiblyIterable) ? arr : possiblyIterable;
for (let idx = 0; idx < arr.length; ++idx) {
iteratee.call(target, arr[idx], idx, list);
}
} else {
const entries = Object.entries(possiblyIterable ?? {})
for (let [key, value] of entries) {
iteratee.call(target, value, key, possiblyIterable);
}
}
return possiblyIterable;
},
};
const object = { x: 'XX', y: 'YY' };
const array = [5, 3, 1];
const nodeList = document.body.querySelectorAll('em');
const string = 'test';
console.log('+++ object test ...function expression and bound target +++');
_.each(object, function (value, key, obj) {
const target = this;
console.log({ target, value, key, obj });
}, { foo: 'FOO' });
console.log('\n+++ array test ...function expression and bound target +++');
_.each(array, function (item, idx, list) {
const target = this;
console.log({ target, item, idx, list });
}, { foo: 'FOO' });
console.log('\n+++ nodeList test ...non bindable arrow function +++');
_.each(nodeList, (item, idx, list) =>
console.log({ item, idx, list })
);
console.log('\n+++ string test ...non bindable arrow function +++');
_.each(string, (item, idx, list) =>
console.log({ item, idx, list })
);
console.log('\n+++ return value test +++');
console.log(
'(_.each(object, (x) => x) === object) ?..',
(_.each(object, (x) => x) === object)
);
console.log(
'(_.each(array, (x) => x) === array) ?..',
(_.each(array, (x) => x) === array)
);
console.log(
'(_.each(nodeList, (x) => x) === nodeList) ?..',
(_.each(nodeList, (x) => x) === nodeList)
);
console.log(
'(_.each(string, (x) => x) === string) ?..',
(_.each(string, (x) => x) === string)
);
console.log('\n+++ 2nd argument function type test +++');
_.each(object, null);
.as-console-wrapper { min-height: 100%!important; top: 0; }
<em>Node</em><em>List</em><em>Test</em>

How to find Key value from an object based on the value passed

Thanks for your time.
I have the following object in JavaScript:
{
"key1":"value1,value2,value3",
"key2":"value4,value5,value6"
}
Now I want to parse for a specific value and if the value exists, I want to return the key associated with it. Suppose that I am passing 'value3', it should return key1; if passing value5, it should return me key2 and so on.
What is the best way to implement it using Javascript, keeping in mind the execution time. I have tried using sting manipulation functions like indexOf, substr; but not most effective I believe.
TIA.
Here is a slightly different approach that will generate a map where the key is actually the value of your original values object.
The generated map will be a sort of fast lookup. Just to make things clear this solution is efficient as long as you need to do a lot of lookups. For a single, unique lookup this solution is the less efficient, since building the hashmap requires much more time than just looking up for a single value.
However, once the map is ready, acquiring values through keys will be incredibly fast so, if you need to later acquire multiple values, this solution will be more suitable for the use case.
This can be accomplished using Object.entries and Object.values. Further explanations are available in the code below.
The code below (despite not required) will also take care of avoiding indexOf with limit cases like searching 'value9' over 'value9999' which, on a regular string, would actually work with indexOf.
const values = {
"key1":"value1,value2,value3",
"key2":"value4,value5,value6",
"key3":"value444,value129839,value125390", // <-- additional test case.
"key4": "value9" // <-- additional test case.
};
const map = Object.entries(values).reduce((acc, [key, value]) => {
// If the value is invalid, return the accumulator.
if (!value) return acc;
// Otherwise, split by comma and update the accumulator, then return it.
return value.split(',').forEach(value => acc[value] = key), acc;
}, {});
// Once the map is ready, you can easily check if a value is somehow linked to a key:
console.log(map["value444"]); // <-- key 3
console.log(map["value9"]); // <-- key 4
console.log(map["Hello!"]); // undefined
To me, the fastest and most concise way of doing that would be the combination of Array.prototype.find() and String.prototype.includes() thrown against source object entries:
const src={"key1":"value1,value2,value3","key2":"value4,value5,value6"};
const getAkey = (obj, val) => (Object.entries(obj).find(entry => entry[1].split(',').includes(val)) || ['no match'])[0];
console.log(getAkey(src, 'value1'));
console.log(getAkey(src, 'value19'));
p.s. while filter(), or reduce(), or forEach() will run through the entire array, find() stops right at the moment it finds the match, so, if performance matters, I'd stick to the latter
Lodash has a function for this called findKey which takes the object and a function to determine truthiness:
obj = { 'key1': 'value1, value2, value3', 'key2': 'value4,value5,value6' }
_.findKey(obj, val => val.includes('value3'))
# 'key1'
_.findKey(obj, val => val.includes('value5'))
# 'key2'
Based on your search, you can use indexOf after looping through your object.
Here's an old school method:
var obj = {
"key1":"value1,value2,value3",
"key2":"value4,value5,value6"
}
function search (str) {
for (var key in obj) {
var values = obj[key].split(',');
if (values.indexOf(str) !== -1) return key
}
return null;
}
console.log(search('value1'))
console.log(search('value6'))
Or you can use Object.keys() with filter() method and get the index 0 of the returned array.
var obj = {
"key1":"value1,value2,value3",
"key2":"value4,value5,value6"
}
function search (str) {
return Object.keys(obj).filter((key) => {
const values = obj[key].split(',');
if (values.indexOf(str) !== -1) {
return key
}
})[0]
}
console.log(search('value1'))
console.log(search('value6'))
You can try iterating over each value in your object and then splitting the value on each comma, then checking if the value is in the returned array like so:
const myObj = {"key1":"value1,value2,value3","key2":"value4,value5,value6"}
function findKeyByValue(obj, value) {
for (var key in myObj) {
const valuesArray = myObj[key].split(',')
if (valuesArray.includes(value)) {
return key
}
}
}
const key = findKeyByValue(myObj, 'value5') // returns 'key2'
console.log(key)
EDIT: Changed loop for efficiency, and extracted code to function
This should do it. Just uses Object.entries and filters to find the entries that contain the value you're looking for. (Can find more than one object that has the desired value too)
var obj = {
"key1": "value1,value2,value3",
"key2": "value4,value5,value6"
};
var find = 'value2';
var key = Object.entries(obj).filter(([k, v]) => v.split(',').includes(find))[0][0];
console.log(key);
Might want to check the return value of Object.entries(obj).filter((o) => o[1].split(',').includes(find)) before trying to access it, in case it doesn't return anything. Like so:
var obj = {
"key1": "value1,value2,value3",
"key2": "value4,value5,value6"
};
function findKey(objectToSearch, valueToFind) {
var res = Object.entries(objectToSearch).filter(([key, value]) => value.split(',').includes(valueToFind));
if(res.length > 0 && res[0].length > 0) {
return res[0][0];
}
return false;
}
console.log(findKey(obj, 'value5'));
includes can be used to check whether a value is present in an array. Object.keys can be used for iteration and checking for the match.
function findKey(json, searchQuery) {
for (var key of Object.keys(json)) if (json[key].split(',').includes(searchQuery)) return key;
}
const json = {
"key1": "value1,value2,value3",
"key2": "value4,value5,value6"
}
console.log(findKey(json, 'value5'))
Use Object.entries with Array.prototype.filter to get what the desired key.
const data = {
"key1": "value1,value2,value3",
"key2": "value4,value5,value6"
};
const searchStr = 'value3';
const foundProp = Object.entries(data).filter(x => x[1].indexOf(searchStr) !== -1);
let foundKey = '';
if (foundProp && foundProp.length) {
foundKey = foundProp[0][0];
}
console.log(foundKey);

Return false if one element of array is not empty without use Iterators - Typescript

I have a method where I receive an string array and I want to return false if one of array elements is false.
myMethod(attrs: Array<String>) {
for (const element of attrs) {
if (!element) {
return false;
}
}
return true;
}
Is it possible to simplify this code?
Javascript Array has a method that checks if every item satisfies a predicate: every.
Example:
const arr = [1, 2, null];
const every = arr.every(item => item !== null); // Check if every item is not-null. This will be false
It can be shortened by casting an item to a boolean:
const every2 = arr.every(item => Boolean(item)); // This will give false for 0, null, undefined, etc.
And even shorter by just passing a Boolean constructor to a callback, which will pass items to it's constructor
const every2 = arr.every(Boolean);
Also check methods find and some.
Keep in mind this does use iterator, it's just that JS built-in functions do it under the hood.
Try This:
var attrs =[true,true,false];
var result = !attrs.some( elm => (elm === false) );
console.log(result);

es6 Array Reduce function and string split usage

I'm trying to use the array reduce function to return a 2D array of objects. The input is a comma separated values. The first row of the string is used as the title row. I'm analyzing the solution and I don't understand the notation. Specifically I don't understand the "=> ((obj[title] =
values[index]), obj), {})" portion in the code below. I'm looking to have someone explain it to me. For me it seems like we're initializing obj to be a an object. After that I'm lost.
const CSV_to_JSON = (data, delimiter = ',') => {
const titles = data.slice(0, data.indexOf('\n')).split(delimiter);
return data
.slice(data.indexOf('\n') + 1)
.split('\n')
.map(v => {
const values = v.split(delimiter);
return titles.reduce(
(obj, title, index) => ((obj[title] = values[index]), obj)
, {});
});
};
console.log(CSV_to_JSON('col1,col2\na,b\nc,d')); // [{'col1': 'a', 'col2': 'b'}, {'col1': 'c', 'col2': 'd'}];
console.log(CSV_to_JSON('col1;col2\na;b\nc;d', ';')); // [{'col1': a', 'col2': 'b'}, {'col1': 'c', 'col2': 'd'}]
It's an (ab)use of the comma operator, which takes a list of comma-separated expressions, evaluates the first expression(s), discards them, and then the whole (...) resolves to the value of the final expression. It's usually something only to be done in automatic minification IMO, because the syntax looks confusing.
The .reduce there
return titles.reduce((obj, title, index) => ((obj[title] =
values[index]), obj), {});
is equivalent to
return titles.reduce((obj, title, index) => {
obj[title] = values[index];
return obj;
}, {});
which makes a lot more sense - it turns an array of titles (eg ['foo', 'bar']) and an array of values (eg ['fooVal', 'barVal']), and uses .reduce to transform those into a single object, { foo: 'fooVal', bar: 'barVal' }.
The first argument to the .reduce callback is the accumulator's initial value (the second argument to .reduce), or the value that was returned on the last iteration - the code above passes {} as the initial value, assigns a property to the object, and returns the object on every iteration. .reduce is the usually the most appropriate method to use to turn an array into an object, but if you're more familiar with forEach, the code is equivalent to
const obj = {};
titles.forEach((title, index) => {
obj[title] = values[index];
});
return obj;
While the comma operator can be useful when code-golfing, it's probably not something that should be used when trying to write good, readable code.

Categories