"Write a function arrayToList that builds up a list structure like"
let LL = { data: 1, next: { data: 2, next: { data: 3, next: null }}};
I understand the typical solution to this problem, where the list must be built from the inside out:
function arrToLList(arr) {
let LList = null;
for (let i = arr.length - 1; i >= 0; i--) {
LList = { data: arr[i], next: LList };
}
return LList;
}
But my initial solution was to brute force it with a typical for loop.
function arrayToLList(arr) {
let d = "data";
let n = "next";
let LList = nextNode();
for (let i = 0; i < arr.length; i++) {
LList[d] = arr[i];
d = "next." + d;
LList[n] = nextNode();
n = "next." + n;
}
function nextNode() {
return {
data: null,
next: null
};
}
return LList;
}
What you want to achieve is possible, but you need to customize the functionality of how getting a property works when you use bracket notation. As you mentioned, using dot notation with bracket notation won't work, you need a way to define this logic yourself. ES6 introduced Proxies which allows you to specify a set method trap for your object. Whenever you set a value on the object, the set method will be called. Using this idea, you can split the dot-notation string by . and traverse the path it returns to get your nested object. Once you have retrieved the nested object, you can set its value.
See example below:
function arrayToLList(arr) {
let d = "data";
let n = "next";
let LList = nextNode();
for (let i = 0; i < arr.length; i++) {
LList[d] = arr[i];
d = "next." + d;
if(i < arr.length-1) // don't add null object to last node
LList[n] = nextNode();
n = "next." + n;
}
function nextNode() {
const obj = {
data: null,
next: null
};
return new Proxy(obj, {
set: function(obj, key, val) {
const path = key.split('.');
const last = path.pop();
for(const prop of path)
obj = obj[prop];
obj[last] = val;
return true;
}
});
}
return LList;
}
console.log(arrayToLList([1, 2, 3]));
However, you don't need to use a proxy. A more straightforward way of doing this would be by creating a method such as setValueByPath(val, obj, strPath) which performs the logic in the proxy for you. Then, instead of setting your object using bracket notation, you simply call the setValueByPath(obj, strPath):
function setValudByPath(val, obj, strPath) { // pefroms same logic from proxy, just using reduce instead
const path = strPath.split('.');
const last = path.pop();
path.reduce((nested, p) => nested[p], obj)[last] = val;
}
function arrayToLList(arr) {
let d = "data";
let n = "next";
let LList = {data: null, next: null};
for (let i = 0; i < arr.length; i++) {
setValudByPath(arr[i], LList, d); // same as LList[d] = arr[i];
d = "next." + d;
if(i < arr.length-1) // don't add null object to last node
setValudByPath({data: null, next: null}, LList, n); // same as: LList[n] = nextNode();
n = "next." + n;
}
return LList;
}
console.log(arrayToLList([1, 2, 3]));
As you are trying to access an objects parameters using strings.
You can't use dot notation with string.
e.g.
let data = {name:'test'};
console.log("data.name");
this is what you're attempting and it will return data.name and not the value test.
you can do the following though: data['name'] so with nested object you can do the following:
LList['next']['next']...['next']['data']
to get the n'th data element.
Related
I'm trying to create a JS object dynamically providing a key and a value. The key is in dot notation, so if a string like car.model.color is provided the generated object would be:
{
car: {
model: {
color: value;
}
}
}
The problem has a trivial solution if the key provided is a simple property, but i'm struggling to make it work for composed keys.
My code:
function (key, value) {
var object = {};
var arr = key.split('.');
for(var i = 0; i < arr.length; i++) {
object = object[arr[i]] = {};
}
object[arr[arr.length-1]] = value;
return object;
}
your slightly modified code
function f(key, value) {
var result = object = {};
var arr = key.split('.');
for(var i = 0; i < arr.length-1; i++) {
object = object[arr[i]] = {};
}
object[arr[arr.length-1]] = value;
return result;
}
In the loop you should set all of the props but the last one.
Next set the final property and all set.
If you're using lodash you could use _.set(object, path, value)
const obj = {}
_.set(obj, "car.model.color", "my value")
console.log(obj)
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.15/lodash.min.js"></script>
Use namespace pattern, like the one Addy Osmani shows: http://addyosmani.com/blog/essential-js-namespacing/
Here's the code, pasted for convenience, all credit goes to Addy:
// top-level namespace being assigned an object literal
var myApp = myApp || {};
// a convenience function for parsing string namespaces and
// automatically generating nested namespaces
function extend( ns, ns_string ) {
var parts = ns_string.split('.'),
parent = ns,
pl, i;
if (parts[0] == "myApp") {
parts = parts.slice(1);
}
pl = parts.length;
for (i = 0; i < pl; i++) {
//create a property if it doesnt exist
if (typeof parent[parts[i]] == 'undefined') {
parent[parts[i]] = {};
}
parent = parent[parts[i]];
}
return parent;
}
// sample usage:
// extend myApp with a deeply nested namespace
var mod = extend(myApp, 'myApp.modules.module2');
function strToObj(str, val) {
var i, obj = {}, strarr = str.split(".");
var x = obj;
for(i=0;i<strarr.length-1;i++) {
x = x[strarr[i]] = {};
}
x[strarr[i]] = val;
return obj;
}
usage: console.log(strToObj("car.model.color","value"));
I would use a recursive method.
var createObject = function(key, value) {
var obj = {};
var parts = key.split('.');
if(parts.length == 1) {
obj[parts[0]] = value;
} else if(parts.length > 1) {
// concat all but the first part of the key
var remainingParts = parts.slice(1,parts.length).join('.');
obj[parts[0]] = createObject(remainingParts, value);
}
return obj;
};
var simple = createObject('simple', 'value1');
var complex = createObject('more.complex.test', 'value2');
console.log(simple);
console.log(complex);
(check the console for the output)
Here's a recursive approach to the problem:
const strToObj = (parts, val) => {
if (!Array.isArray(parts)) {
parts = parts.split(".");
}
if (!parts.length) {
return val;
}
return {
[parts.shift()]: strToObj(parts, val)
};
}
I'm trying to add a series of values to a nested object, having some trouble with the loop in the following code. Any help would be really appreciated.
let settings = {};
function write(id, values) {
if(!settings[id]) settings[id] = {};
for(var x = 0; x < Object.keys(values).length; x ++) {
settings[id][values[x]] = values[values[x]];
}
}
//example
write('example', {'prop1': 5, 'prop2': 10});
You're attempting to index the object values with x, which is a number. To loop through the keys of your object you can use a for...in loop:
function write(id, values) {
if(!settings[id]) settings[id] = {};
for(const key in values) {
settings[id][key] = values[key];
}
}
Another approach would be to use object destructuring:
function write(id, values) {
settings[id] = { ...(settings[id] || {}), ...values };
}
values is an object. Accessing values[x] will return undefined.
You have to access it with the correct keys in that object as below.
let settings = {};
function write(id, values) {
if (!settings[id]) settings[id] = {};
const keys = Object.keys(values);
for (var x = 0; x < keys.length; x++) {
settings[id][keys[x]] = values[keys[x]];
}
console.log(settings)
}
//example
write('example', { 'prop1': 5, 'prop2': 10 });
try to keep the Object.keys(values) return in another variable and use it to assign value in setting like this
function write(id, values) {
if(!settings[id]) settings[id] = {};
const key = Object.keys(values)
for(var x = 0; x < key.length; x ++) {
settings[id][key[x]] = values[key[x]];
}
}
trialObject : {
'color': ['red','blue'],
'size': ['s','m'],
'material': ['cotton']
}
// RECURSION FUNCTION TO MAKE COMBINATIONS
makeObjectVariants(selected){
let key = Object.keys(selected)
if(Object.keys(selected).length === 1){
return selected[key[0]];
} else {
var result = [];
var currentArray = selected[key[0]]
delete selected[key[0]]
var restObjects = this.makeObjectVariants(selected) // call function again
for(var i = 0; i < restObjects.length; i++){
for (var j = 0; j < currentArray.length; j++) {
result.push([restObjects[i] +','+ currentArray[j]]);
}
}
return result; // resultant array
}
}
// OUTPUT
0:["cotton,s,red"]
1:["cotton,s,blue"]
2:["cotton,m,red"]
3:["cotton,m,blue"]
// EXPECTED OUTPUT
[{'material':cotton,'size':s,'color':red},...]
I want the output to contain key value pairs so that the array elements can be recognized which group they fall into.
I am facing problem in adding keys to the elements generated because m unable to keep track of the object keys
If you can use ES6 (default parameters, spread operator, arrow function, ...), the following code do the job:
var trialObject = {
color: ['red','blue'],
size: ['s','m'],
material: ['cotton']
};
var result = buildCombinations(trialObject);
console.log(result);
function buildCombinations(trialObject , keys = Object.keys(trialObject ), keyIndex = 0 , subObj = {}, res = []) {
trialObject[keys[keyIndex]].forEach(element => {
subObj[keys[keyIndex]] = element;
keys[keyIndex + 1] ? buildCombinations(trialObject , keys, keyIndex + 1, subObj, res) : res.push({...subObj});
});
return res;
}
I need to know if one or more duplicates exist in a list. Is there a way to do this without travelling through the list more than once?
Thanks guys for the suggestions. I ended up using this because it was the simplest to implement:
var names = [];
var namesLen = names.length;
for (i=0; i<namesLen; i++) {
for (x=0; x<namesLen; x++) {
if (names[i] === names[x] && (i !== x)) {alert('dupe')}
}
}
Well the usual way to do that would be to put each item in a hashmap dictionary and you could check if it was already inserted. If your list is of objects they you would have to create your own hash function on the object as you would know what makes each one unique. Check out the answer to this question.
JavaScript Hashmap Equivalent
This method uses an object as a lookup table to keep track of how many and which dups were found. It then returns an object with each dup and the dup count.
function findDups(list) {
var uniques = {}, val;
var dups = {};
for (var i = 0, len = list.length; i < len; i++) {
val = list[i];
if (val in uniques) {
uniques[val]++;
dups[val] = uniques[val];
} else {
uniques[val] = 1;
}
}
return(dups);
}
var data = [1,2,3,4,5,2,3,2,6,8,9,9];
findDups(data); // returns {2: 3, 3: 2, 9: 2}
var data2 = [1,2,3,4,5,6,7,8,9];
findDups(data2); // returns {}
var data3 = [1,1,1,1,1,2,3,4];
findDups(data3); // returns {1: 5}
Since we now have ES6 available with the built-in Map object, here's a version of findDups() that uses the Map object:
function findDups(list) {
const uniques = new Set(); // set of items found
const dups = new Map(); // count of items that have dups
for (let val of list) {
if (uniques.has(val)) {
let cnt = dups.get(val) || 1;
dups.set(val, ++cnt);
} else {
uniques.add(val);
}
}
return dups;
}
var data = [1,2,3,4,5,2,3,2,6,8,9,9];
log(findDups(data)); // returns {2 => 3, 3 => 2, 9 => 2}
var data2 = [1,2,3,4,5,6,7,8,9];
log(findDups(data2)); // returns empty map
var data3 = [1,1,1,1,1,2,3,4];
log(findDups(data3)); // returns {1 => 5}
// display resulting Map object (only used for debugging display in snippet)
function log(map) {
let output = [];
for (let [key, value] of map) {
output.push(key + " => " + value);
}
let div = document.createElement("div");
div.innerHTML = "{" + output.join(", ") + "}";
document.body.appendChild(div);
}
If your strings are in an array (A) you can use A.some-
it will return true and quit as soon as it finds a duplicate,
or return false if it has checked them all without any duplicates.
has_duplicates= A.some(function(itm){
return A.indexOf(itm)===A.lastIndexOf(itm);
});
If your list was just words or phrases, you could put them into an associative array.
var list=new Array("foo", "bar", "foobar", "foo", "bar");
var newlist= new Array();
for(i in list){
if(newlist[list[i]])
newlist[list[i]]++;
else
newlist[list[i]]=1;
}
Your final array should look like this:
"foo"=>2, "bar"=>2, "foobar"=>1
Not an expert on the old JS so here goes
I have
store1.baseParams.competition = null;
store2.baseParams.competition = null;
store3.baseParams.competition = null;
What I want to do is
for (i=1; 1<=3; 1++) {
store + i +.baseParams.competition = null;
}
Hope that makes sense what I want to do - is it possible
Basically make a variable / object by adding to it
Cheers
One way to accomplish this is via eval() - (usually a Very Bad Idea)
for (var i=1; i<=3; i++) {
eval("store" + i + ".baseParams.competition = null;");
}
Another, more complex but relatively efficient way would be to create a function which gives you the ability to mutate arbitrarily deep object hierarchies dynamically at run-time. Here's one such function:
/*
Usage:
Nested objects:
nested_object_setter(object, ['property', 'propertyOfPreviousProperty'], someValue);
Top-level objects:
nested_object_setter(object, 'property', someValue);
*/
function dynamic_property_setter_base(obj, property, value, strict) {
var shouldPerformMutation = !strict || (strict && obj.hasOwnProperty(property));
if(shouldPerformMutation) {
obj[property] = value;
}
return value;
}
function dynamic_property_setter(obj, property, value) {
return dynamic_property_setter_base(obj, property, value, false);
}
function nested_object_setter(obj, keys, value) {
var isArray = function(o) {
return Object.prototype.toString.call(o) === '[object Array]';
};
//Support nested keys.
if(isArray(keys)) {
if(keys.length === 1) {
return nested_object_setter(obj, keys[0], value);
}
var o = obj[keys[0]];
for(var i = 1, j = keys.length - 1; i < j; i++)
o = o[keys[i]];
return dynamic_property_setter(o, keys[keys.length - 1], value);
}
if(keys != null &&
Object.prototype.toString.call(keys) === '[object String]' &&
keys.length > 0) {
return dynamic_property_setter(obj, keys, value);
}
return null;
}
Your code would look like this:
for(var i = 1; i <= 3; i++)
nested_object_setter(this, ['store' + i, 'baseParams', 'competition'], null);
Here's another example, running in the JS console:
> var x = {'y': {'a1': 'b'}};
> var i = 1;
> nested_object_setter(this, ['x','y','a' + i], "this is \"a\"");
> x.y.a1
"this is "a""
Another way to do it, IMHO this is the simplest but least extensible way:
this['store' + i].baseParams.competition = null;
That won't work. You can make an object though, storing the 'store'+i as a property.
var storage = {},i=0;
while(++i<4) {
storage['store' + i] = { baseParams: { competition:null } };
}
Console.log(String(storage.store1.baseParams.competition)); //=> 'null'
In a browser, you can also use the window namespace to declare your variables (avoiding the use of eval):
var i=0;
while(++i<4) {
window['store' + i] = { baseParams: { competition:null } };
}
Console.log(String(store1.baseParams.competition)); //=> 'null'
for (i=1; i<=3; i++) {
this["store" + i + ".baseParams.competition"] = null;
}
Just another form of assigning variables in JS.