Deep key structure based on recursion - javascript

I've been using lodash for a while now and I really love the _.set and _.get methods.
I'm trying to solve a problem to get the deep key paths whose final value is a string, but when I'm too dumb for it. Spend 3 hours on it and can't find a perfect solution:
const myObject = {
a: 'myObject.a',
b: {
ba: 'myObject.b.ba',
bb: ['myObject.b.bb[0]'],
},
c: [
{ ca: 'myObject.c[0].ca' },
],
};
So I have myObject (that's far more nested in real life) and I want to get paths to the values, but only the final one.
The method would look like getDeepPaths(myObject) and would return in this case: ['myObject.a', 'myObject.b.ba', 'myObject.b.bb[0]', 'myObject.c[0].ca' ]
Did anyone solve something like this before?

Recursion is actually not that hard. Here's how you could solve this problem:
const myObject = {
a: 'myObject.a',
b: {
ba: 'myObject.b.ba',
bb: ['myObject.b.bb[0]'],
},
c: [
{ ca: 'myObject.c[0].ca' },
],
};
var stringLeaves = function(path, obj) {
if (typeof obj === 'string') {
return [path]
}
return Object.keys(obj)
.filter(k => obj.hasOwnProperty(k))
.map(k => stringLeaves(path + '.' + k, obj[k]))
.reduce((a,x) => a.concat(x), []); // this line flattens the array
};
console.log(stringLeaves('myObject', myObject));
The work is done by the stringLeaves function. In this function:
if the obj passed in as a parameter is a string, then just return the current path.
otherwise we assume that the object is an array, or a generic object, in which case we iterate through its properties:
for each property, call stringLeaves recursively, by passing in the adjusted path (current path + the new property name) and the object/value that resides at that particular key.
The convention of the function is that it returns an array of all possible matches. This is why:
for scalar string values I return an array (to keep things consistent)
I have the .reduce((a,x) => a.concat(x), []); line: to transform an array of arrays into one array that consists of all the values present in the original arrays.
Note that the function cannot deduce that your object is called myObject, so I passed that name as an initial path.

I'll provide a more generic solution that doesn't use lodash or other external dependencies
const traverse = function* (node, path = [])
{
if (Object (node) === node)
for (const [ key, value ] of Object.entries (node))
yield* traverse (value, [ ...path, key ])
else
yield [ path, node ]
}
We can easily step thru our data using a for loop. Notice the generator yields a path-value pair for each value in the original object. All primitive values are included in the output, not just strings this time
// add a non-string value for demo
const myObject = {
...
d: 1
};
for (const [ path, value ] of traverse (myObject)) {
console.log ('path', path)
console.log ('value', value)
console.log ('---')
}
// path [ 'a' ]
// value myObject.a
// ---
// path [ 'b', 'ba' ]
// value myObject.b.ba
// ---
// path [ 'b', 'bb', '0' ]
// value myObject.b.bb[0]
// ---
// path [ 'c', '0', 'ca' ]
// value myObject.c[0].ca
// ---
// path [ 'd' ]
// value 1
// ---
If we wanted to, we can collect all of the pairs using Array.from
Array.from (traverse (myObject))
// [ [ [ 'a' ], 'myObject.a' ]
// , [ [ 'b', 'ba' ], 'myObject.b.ba' ]
// , [ [ 'b', 'bb', '0' ], 'myObject.b.bb[0]' ]
// , [ [ 'c', '0', 'ca' ], 'myObject.c[0].ca' ]
// , [ [ 'd' ], 1 ]
// ]
As you may have noticed, I keep path as an array rather than making it a .-separated string. There's no need to make it into a string just to split it apart again later.
const lookup = (obj, [ key, ...path ]) =>
obj && key
? lookup (obj [key], path)
: obj
for (const [ path, value ] of traverse (myObject)) {
console.log ('path', path)
console.log ('value', value)
console.log ('lookup', lookup (myObject, path))
console.log ('---')
}
// path [ 'a' ]
// value myObject.a
// lookup myObject.a
// ---
// path [ 'b', 'ba' ]
// value myObject.b.ba
// lookup myObject.b.ba
// ---
// path [ 'b', 'bb', '0' ]
// value myObject.b.bb[0]
// lookup myObject.b.bb[0]
// ---
// path [ 'c', '0', 'ca' ]
// value myObject.c[0].ca
// lookup myObject.c[0].ca
// ---
// path [ 'd' ]
// value 1
// lookup 1
// ---

Related

JavaScript: Find in array all values with substrings of array

I have two arrays; one with substrings and the other with objects.
I would like to obtain an array of objects where those objects contain any of the substrings in the substrings array.
So far I have tried to use filter and findIndex. Each approach works if a substring is identical. In this case, even indexOf was not working. I am not javascript guy, so probably I am doing something wrong.
Script
var strings = ['12', 'sv', 'eli', '23', '34'];
var data = [
{
a: 349531284734,
b: "sv123eippppppeli",
c: "aaabbbccc"
},
{
a: 1111123333312,
b: "ccccccccccccs2222",
c: "aaabbbccc"
},
{
a: 2222234,
b: "elllllllla",
c: false
},
];
// attempt 1
var results = data.filter(arr =>
Object.keys(arr).some(key => {
return String(arr[key]).toLowerCase().includes(strings) // or indexOf
})
);
// attempt 2 with only one data index
var obj = Object.values(data[0]);
var results = strings.some(s => obj.includes(s)) // or indexOf or findIndex;
Explanation
In this example with the given substrings, only data[0] is a match because it contains at least on of the substrings in the strings array.
How can I make this work without using a "for loop"?
Thanks
A simple solution that avoids the need for "for-loop" syntax would be to filter() each object of data by a predicate that checks if any value (of the current item being filtered) contains any one of the values of the strings array.
In code, this can be expressed as:
var strings = ['12', 'sv', 'eli', '23', '34'];
var data = [{
a: 349531284734,
b: "sv123eippppppeli",
c: "aaabbbccc"
},
{
a: 1111123333312,
b: "ccccccccccccs2222",
c: "aaabbbccc"
},
{
a: 2222234,
b: "elllllllla",
c: false
},
];
// Local helper retruns true if case-insenstive value in strings array
const stringHasValue = (value) => strings
.some(str => value.toLowerCase().includes(str.toLowerCase()))
// Filter each item in data array by existance of a value containing
// substring in strings array
var result = data.filter((item) => Object.values(item)
.filter(value => typeof value === 'string')
.some(stringHasValue, []));
console.log(result);

Programmatically modifying maps based on inner condition

I have a map like this for example
const Map = new Map().set('123', [ [ 'foo', 'bar' ] ]).set('456', [ [ 'baz', 'qux' ], [ 'quux', 'corge' ] ]);
/*
The structure of the Map looks like this:
Map {
'123' => [ [ 'foo', 'bar' ] ],
'456' => [ [ 'baz', 'qux' ], [ 'quux', 'corge' ] ]
}
*/
How would I go about deleting the array where the first nested element in the array === 'quux' so that it would return this?
Map {
'123' => [ [ 'foo', 'bar' ] ],
'456' => [ [ 'baz', 'qux' ] ]
}
I know how to remove the item by doing
Map.set('456', (Map.get('456')).filter(array => array[0] !== 'quux'));
But this is only because I know which key ('456') has the element with 'quux' in it. I'm not sure how I would programmatically sweep through the Map then find the corresponding key and then remove the item. The keys and values in the Map will dynamic (but the structure will be the same), whereas the element to search for will be static, i.e: 'quux', what I mean by this is that the contents in the Map could vary, and I am simply performing a search and remove.
You could iterate the map and if the wanted value is found, filter the array and assign the filtered array.
const map = new Map([['123', [['foo', 'bar']]], ['456', [['baz', 'qux'], ['quux', 'corge']]]]);
map.forEach((v, k, m) => {
if (v.some(a => a[0] === 'quux')) {
m.set(k, v.filter(a => a[0] !== 'quux'));
}
});
console.log([...map]);
You can loop over the values of the Map, use findIndex on each value v to see if it includes an array whose first element is quux, and splice that array out if so:
const map = new Map().set('123', [ [ 'foo', 'bar' ] ]).set('456', [ [ 'baz', 'qux' ], [ 'quux', 'corge' ] ]);
console.log("before", [...map]);
for (const v of map.values()) {
const index = v.findIndex((a) => a[0] === "quux");
if (index > -1) {
v.splice(index, 1);
}
}
console.log("after", [...map]);
Here’s the non-destructive alternative, which creates a new Map by taking the entries of the old one and mapping the values to filter out the arrays we don’t want:
const before = new Map().set('123', [ [ 'foo', 'bar' ] ]).set('456', [ [ 'baz', 'qux' ], [ 'quux', 'corge' ] ]);
console.log("before", [...before]);
const after = new Map([...before].map(([k, v]) => {
return [k, v.filter((a) => a[0] !== "quux")];
}))
console.log("after", [...after]);
NOTE: One difference between the two approaches is that the second one will remove all arrays that have quux as their first element, whereas the second one will remove only the first such array. They can, of course, both be altered to fit whichever of the two options you need.
You could do the key dynamically with a for of loop like this:
BTW open your devtools to checkout the new map since map cannot be properly displayed in the code snippet.
const Map = new Map().set('123', [
['foo', 'bar']
]).set('456', [
['baz', 'qux'],
['quux', 'corge']
]);
for (let el of Map) {
Map.set(el[0], (Map.get(el[0])).filter(array => array[0] !== 'quux'));
}
console.log(Map);
I hope this is what you wanted and otherwise you can comment and I will have a look at it ;).
Iterate over key-value pair of the map, the value will have the outer array from which we can filter out the inner array having the value we are looking for. We can get the index of the inner array from the forEach function, using which we can use the splice function to remove the inner array from the outer array.
const map = new Map().set('123', [ [ 'foo', 'bar' ] ]).set('456', [ [ 'baz', 'qux' ], [ 'quux', 'corge' ] ]);
map.forEach((v, k)=>
{
v.forEach((arr, idx)=> {
if(arr.includes('quux')){
v.splice(idx,1);
}
},)
});
console.log(map);
Not sure if it's better from the performance point to always use Array.prototype.filter or use Array.prototype.some before filtering the array.
This solution just filters all arrays without checking an apperance of 'quux' before.
const map = new Map().set('123', [ ['foo', 'bar' ] ]).set('456', [ [ 'baz', 'qux' ], [ 'quux', 'corge' ] ]);
map.forEach((val, key) => {
val = val.filter(arr => arr[0] !== 'quux');
map.set(key, val);
});
console.log(map);

Removing Duplicates in Arrays

I am trying to pass a function that removes duplicates from an array. It should handle strings, object, integers as well. In my code so far I am showing that it will handle strings but nothing else. How can Imake this function universalto handle numbers,handle arrays,handle objects, and mixed types?
let unique = (a) => a.filter((el, i ,self) => self.indexOf(el) ===i);
In this function I hav unique() filtering to make a new array which checks the element and index in the array to check if duplicate. Any help would be appreciated.
i think the first you should do is to sort the array ( input to the function ). Sorting it makes all the array element to be ordered properly. for example if you have in an array [ 1, 3, 4, 'a', 'c', 'a'], sorting this will result to [ 1 , 3 , 4, 'a', 'a' , 'c' ], the next thing is to filter the returned array.
const unique = a => {
if ( ! Array.isArray(a) )
throw new Error(`${a} is not an array`);
let val = a.sort().filter( (value, idx, array) =>
array[++idx] != value
)
return val;
}
let array = [ 1 , 5, 3, 2, "d", "q", "b" , "d" ];
unique(array); // [1, 2, 3, 5, "b", "d", "q"]
let obj = { foo: "bar" };
let arraySize = array.length;
array[arraySize] = obj;
array[arraySize++] = "foo";
array[arraySize++] = "baz";
array[arraySize++] = obj;
unique(array); // [1, 2, 3, 5, {…}, "b", "baz", "d", "foo", "hi", "q"]
it also works for all types, but if you pass in an array literal with arrays or objects as one of its element this code will fail
unique( [ "a", 1 , 3 , "a", 3 , 3, { foo: "baz" }, { foo: "baz" } ] ); // it will not remove the duplicate of { foo: "baz" } , because they both have a different memory address
and you should also note that this code does not return the array in the same order it was passed in , this is as a result of the sort array method
Try using sets without generics. You can write a function as
Set returnUnique(Object array[]) {
Set set=new HashSet();
for (Object obj:array) {
set.add(obj);
}
return set;
}

unexpected values being pushed onto array

I am having, what I believe to be, weird behaviour while trying to push onto an array. I see expected values when outputting the array. If I push onto the array and then output, I get repeated values. The code in question:
var test = "aab";
testA = test.split("");
permutations = [];
generatePermutations(testA, testA.length);
function generatePermutations(array, arrayLength) {
if (arrayLength === 1) {
console.log(array); // THIS OUTPUTS DIFFERENT PERMUTATIONS
permutations.push(array);
console.log(permutations); // VALUES IN ARRAY ARE ALL THE SAME
/*
permutations.push(array.join(""));
console.log(permutations);
SPLITTING THE STRING MAKES IT WORK FINE?!
*/
return;
}
for (var i = 0; i < arrayLength; i += 1) {
generatePermutations(array, arrayLength - 1);
if (arrayLength % 2 == 0) {
swapArrayElements(array, i, arrayLength - 1);
} else {
swapArrayElements(array, 0, arrayLength - 1);
}
}
}
function swapArrayElements(array, elementA, elementB) {
var temp = array[elementA];
array[elementA] = array[elementB];
array[elementB] = temp;
}
console.log(array) will output a permutation as expected. It will output all permutations as the function recurs:
[ 'a', 'a', 'b' ] [ 'a', 'a', 'b' ] [ 'b', 'a', 'a' ] [ 'a', 'b', 'a'
] [ 'a', 'b', 'a' ] [ 'b', 'a', 'a' ]
If I push the result onto another array, permutations.push(array), every element has the same value:
[ [ 'a', 'a', 'b' ], [ 'a', 'a', 'b' ], [ 'a', 'a', 'b' ], [
'a', 'a', 'b' ], [ 'a', 'a', 'b' ], [ 'a', 'a', 'b' ] ]
I get the expected result if I join the array whilst pushing it onto permutations: `permutations.push(array.join("")):
[ 'aab', 'aab', 'baa', 'aba', 'aba', 'baa' ]
what am I missing here? I cannot understand how array can contain a value that all of a sudden changes upon being pushed onto permutations.
For clarity, this is a freecodecamp task and I'm working towards finding non repeating permutations.
You are using the same array variable, which always references the same memory location. So whatever you change in that array, is changed independent of whether you access the array via array or via permutations, which has all its elements set to the same array reference.
You can solve this by adding copies of the arrays to the permutations array, like so:
permutations.push(array.slice(0));
Note that the array values you have consist of strings. If they were mutable objects which you also modified, then you would need to extend this solution further. But in your case the above is enough.

How do object references work internally in javascript

I am very new to javascript.
I have written the simple code:
var temp = {}
var arr = []
temp['a'] = ['a']
arr.push(temp)
console.log(arr);
As expected, it prints:
[ { a: [ 'a' ] } ]
But then, when I append the following line to the previous code:
temp['b'] = ['b']
arr.push(temp);
console.log(arr);
I would have expected it to print:
[ { a: [ 'a' ] }, { a: [ 'a' ], b: [ 'b' ] } ]
But it prints:
[ { a: [ 'a' ], b: [ 'b' ] }, { a: [ 'a' ], b: [ 'b' ] } ]
Entire code for unexpected result:
var temp = {}
var arr = []
temp['a'] = ['a']
arr.push(temp)
console.log(arr);
temp['b'] = ['b']
arr.push(temp);
console.log(arr);
Why did the first element of array got updated?
The following code gave me expected result:
var temp = {}
var arr = []
temp['a'] = ['a']
arr.push(temp)
console.log(arr);
temp = {};
temp['a'] = ['a']
temp['b'] = ['b']
arr.push(temp);
console.log(arr);
How does adding temp = {} helped here?
Objects in Javascript are passed by reference. That is, only one object is created and the symbol that represents that object can be used but it will refer to the same object always.
Lets take a deeper look:
If I'm understanding your example correct, this part
var temp = {}
var arr = []
temp['a'] = ['a']
arr.push(temp)
console.log(arr);
Creates a local variable temp to which you add ['a'] to. You then push that into arr.
So at this point, arr references the object temp and looks like this:
[ { a: [ 'a' ] } ]
When you do this:
temp['b'] = ['b']
arr.push(temp);
console.log(arr);
The temp symbol which points to the original object containing ['a'] is updated, and so the arr will also get updated, so arr contains this at that point:
[ { a: [ 'a' ], b: [ 'b' ] }, { a: [ 'a' ], b: [ 'b' ] } ]
Finally,
You then do this instead:
temp = {};
temp['a'] = ['a']
temp['b'] = ['b']
arr.push(temp);
console.log(arr);
This creates a separate global variable temp, onto which you add both
['a'] and ['b']. This is global because it does not have the var keyword in the declaration/initialization. This then gets pushed into the arr. However, since it's a global variable and not the original local variable, you see this instead:
[ { a: [ 'a' ] }, { a: [ 'a' ], b: [ 'b' ] } ]
In first case, arr[0] has temp's reference, arr[1] also has temp's reference. So, arr[0] and arr[1] have the same reference.
Hence updating the reference will update it everywhere where the reference is
being referred.
In second case however, when you do temp = {} you're just reassigning temp to a new reference, before pushing it. So, there's no relationship between the arr[0]'s reference, and hence updating temp now, only affects it.
The examples are not the same, it doesn't have to do with temp = {}.
In the first example you push temp twice, meaning arr has to references 2 temp.
After the first push you add another item to temp so within arr, if you had print it, you would have seen:
[ { a: [ 'a' ], b: [ 'b' ] } ]
So try this out on the console:
var temp = {}
var arr = []
temp['a'] = ['a']
arr.push(temp)
temp['b'] = ['b']
console.log(arr);
You'll see the result:
[ { a: [ 'a' ], b: [ 'b' ] } ]
Pushing another temp into arr is just going to result into two references into temp.
There are two data types in JavaScript - value types and reference types.
Value types are actually copied as they are sent between objects. This is because this is what you would expect for certain things like numbers and booleans.
Say I pass the number 1 to a function that stores it in an object A.
It would be surprising if I could then subsequently modify the value contained in A simply by modifying the value of the original number. Hence, pass by value. There are also optimizations that can be performed for value types.
Objects (i.e. everything other than number literals, boolean literals, null, undefined, and string literals*) are reference types in JavaScript and only their reference is passed around. This is largely for efficiency reasons. In your example, temp is an object. It is therefore passed by reference.
And so
temp['b'] = ['b']
Modifies the single existing instance of temp, thereby modifying the contents of arr, before you then also push temp into arr for a second time.
So you end up with an array containing two references to a single object temp, giving you the observed result.
* There is some complexity surrounding the string implementation that I am purposefully ignoring here.

Categories