JavaScript: Indexing an object by column values - javascript

I have a table array which looks like this:
tablearray =
[
{'column1': 1, 'column2': 1, 'column3': 1, 'column4': 2},
{'column1': 1, 'column2': 2, 'column3': 3, 'column4': 4},
{'column1': 2, 'column2': 0, 'column3': 4, 'column4': 6}
]
I'm trying to make a function which takes the table array and an array of column names and makes a new object indexed by the column values. So
newObject = indexByColumnValues(tablearray, ['column1', 'column2']);
should result in an object like
newObject =
{
1:
{
1: {'column1': 1, 'column2': 1, 'column3': 1, 'column4': 2},
2: {'column1': 1, 'column2': 2, 'column3': 3, 'column4': 4}
}
2:
{
0: {'column1': 2, 'column2': 0, 'column3': 4, 'column4': 6}
}
}
So
newObject[1][1]['column3'] = 1
newObject[1][2]['column4'] = 4
etc...
If the number of columns in the column name array (['column1', 'column2'] above) is known, the solution is not hard. But if I allow for any number of column names in this array, it becomes more difficult as there is indefinite recursion
newObject[tablearray[columnNameArray[0]][tablearray[columnNameArray[1]][tablearray[columnNameArray[2]]...
Here is one attempt. I tried to use a pointer to point to the dimensional depth of the newObject array. First, pointer = newObject. Then pointer = newObject[...[0]]. Then point = newObject[...[0]][...[1]]. And so on. This builds the object properly but then I do not have a way to assign a value to newObject[...[0]]...[...[k]].
function indexByColumnValues(object, columnNameArray)
{
var newObject = {};
for(i in object)
{
var index=[];
for(j in columnNameArray)
{
index.push(object[i][columnNameArray[j]]);
}
var pointer = newObject;
for(j in index)
{
if(pointer[index[j]] == undefined)
{
pointer[index[j]] = {};
}
pointer = pointer[index[j]];
}
//now pointer points to newObject[index[0]][index[1]]...[index[k]]
//but I need to set newObject[...] above to be object[i]. How?
//pointer = object[i]; //won't work
}
return newObject;
}
Any help or hints would be great here. Thanks.

You mention recursion, but you don't use it in your code. This is a classic situation where recursion is the right tool. Here's one implementation:
function indexByColumnValues(table, cols) {
// get the column we're indexing
var col = cols[0],
index = {},
x, val;
// find all values
for (x=0; x<table.length; x++) {
val = table[x][col];
// add to index if necessary
if (!index[val]) index[val] = [];
// push this row
index[val].push(table[x]);
}
// recurse if necessary
if (cols.length > 1) {
for (x in index) {
if (index.hasOwnProperty(x)) {
// pass the filtered table and the next column
index[x] = indexByColumnValues(
index[x],
cols.slice(1)
);
}
}
}
return index;
}
Note that, as #jfriend00 notes, you want the "leaf" of your index to be an array of matching rows, not a single object - it's just coincidence that in your example you only have one matching row for your given data and set of columns. Usage:
indexByColumnValues(tablearray, ['column1','column2']);​
Output:
{
"1":{
"1":[
{"column1":1,"column2":1,"column3":1,"column4":2}
],
"2":[
{"column1":1,"column2":2,"column3":3,"column4":4}
]
},
"2":{
"0":[
{"column1":2,"column2":0,"column3":4,"column4":6}
]
}
}
JsFiddle: http://jsfiddle.net/RRcRM/3/

Related

JavaScript - Complexity for the below Executions (forEach vs forEach-indexOf)

Problem Statement:- arr1 is an ordered array with values 3,4 and 5.In the list shown below all the objects whose property "c" matches with that of the elements of the array should be updated with index +1 of the found index at arr1.
I have two different ways of achieving the same, which one has a better time complexity and which one should be chosen?
var arr1 = [3,4,5];
var list = [{a:1,b:2,c:3},{a:1,b:2,c:3},{a:2,b:3,c:5},{a:3,b:4,c:4}];
output: [{a:1,b:2,c:1},{a:1,b:2,c:1},{a:2,b:3,c:3},{a:3,b:4,c:3}];
1: Find the index and then update the index:
list.forEach((obj) => {
var i = arr1.indexOf(obj.c);
if(i > -1) {
obj.c = i +1;
}
});
Using two forEach loops.
arr1.forEach((id,index) => {
list.forEach((obj) => {
if(obj.c === id){
obj.c = index+1;
}
})
});
Which is the better way to write and why?
You could take a Map with value and index in advance, with a single loop.
And another loop for all elements of list, where c gets an update if necessary.
An instance of Map is a data structure to keep a key value relation with a fast access O(1) to the value.
var array = [3, 4, 5],
list = [{ a: 1, b: 2, c: 3 }, { a: 1, b: 2, c: 3 }, { a: 2, b: 3, c: 5 }, { a: 3, b: 4, c: 4 }],
values = new Map;
array.forEach(Map.prototype.set, values);
list.forEach(o => {
if (values.has(o.c)) o.c = values.get(o.c) + 1;
});
console.log(list);

Search and replace value of object property from one array with value of object property from second array?

I have two arrays of objects say 1- variants and 2- inventoryLevels. Objects in both arrays share a property which is the id. So I want to search for each variant if it's id is matched with any inventoryLevel I want to change its property named shopify_inventory_quantity with matched inventoryLevel's property available ? My words are little but confusing but take a look at code below basically it's doing properly whats needed I just want to know can it be optimized right now it's nested for loop. So any help to make it efficient would be appreciated ?
for (let i = 0; i < variants.length; i++) {
for (let j = 0; j < inventorylevels.length; j++) {
if (variants[i].id === inventorylevels[j].variant_id) {
variants[i].shopify_inventory_quantity = inventorylevels[j].available;
}
}
}
I understand you have a solution in O(n²). Assuming your ids are unique, you can reduce the time complexity to O(n) (basically what #Alireza commented):
var variants = [
{id: 0, shopify_inventory_quantity: 0},
{id: 1, shopify_inventory_quantity: 0},
{id: 2, shopify_inventory_quantity: 0}
];
var inventoryLevels = [
{id: 0, available: 10},
{id: 1, available: 2},
{id: 2, available: 3}
];
// O(n) + O(n) = O(n)
function getAvailableVariants(v, i) {
// O(n)
var inventoryLevels = i.reduce(function(inventoryLevels, inventoryLevel) {
inventoryLevels[inventoryLevel.id] = inventoryLevel;
return inventoryLevels;
}, {});
// O(n)
return v.map(variant => Object.assign(variant, {shopify_inventory_quantity: inventoryLevels[variant.id].available}));
}
var results = document.createElement('pre');
results.textContent = JSON.stringify(getAvailableVariants(variants, inventoryLevels), null, '\t');
document.body.appendChild(results);

Get 1 element from each javascript array concat then loop into one array

I have 4 arrays, I'd like to combine them into 1. I can do that, but I'd like to take one element from each array, push it to my new array, then get the next 4 and so on. This is what I got:
var a = [ "foo", "bar", "baz", "bam", "bun", "fun" ];
var b = [ 1, 2, 3, 4, 5, 6];
var c=["a","b","c","d","e","f"];
var d=[7,8,9,10,11,12]
var neat=[];
neat= a.concat(b, c,d);
//neat=["foo","bar","baz","bam","bun","fun",1,2,3,4,5,6,"a","b","c","d","e","f",7,8,9,10,11, 12]
The result I want would be something like this:
//neat=["foo",1,"a",7,"bar",2,"b",8...]
I'm not sure if a loop will work or if I need to use another function
Assuming each source array is the same length:
a.forEach((e, i) => {
neat.push(e, b[i], c[i], d[i]);
};
Please try the below code :
var a = [ "foo", "bar", "baz", "bam", "bun", "fun" ];
var b = [ 1, 2, 3, 4, 5, 6];
var c=["a","b","c","d","e","f"];
var d=[7,8,9,10,11,12]
var neat=[];
//neat= a.concat(b, c,d);
//neat=["foo","bar","baz","b
for (var i = 0; i < a.length ; i++)
{
neat.push(a[i], b[i], c[i], d[i]);
}
console.log(neat);
While Justins answer is correct, however if the lengths of the array are not the same every time, you could do
var maxItems = Math.max(a.length,b.length,c.length,d.length);
var neat = [];
for(var i = 0; i < maxItems; i++){
if(a[i] != undefined){
neat.push(a[i]);
}
if(b[i] != undefined){
neat.push(b[i]);
}
if(c[i] != undefined){
neat.push(c[i]);
}
if(d[i] != undefined){
neat.push(d[i]);
}
}
Math.max would find the biggest number of entries from between the 4 arrays, then a simple for loop on that number and check if the value is undefinedbefore pushing it to neat array.
See JSFiddle
Because the length of the all arrays are equal. So we can easily do that using loop.
var a = [ "foo", "bar", "baz", "bam", "bun", "fun" ];
var b = [ 1, 2, 3, 4, 5, 6];
var c=["a","b","c","d","e","f"];
var d=[7,8,9,10,11,12]
var neat=[], i;
for(i=0;i<a.length;i++){
neat.push(a[i]);
neat.push(b[i]);
neat.push(c[i]);
neat.push(d[i]);
}
console.log(neat);

How can I change the index order of an array?

I have a button that has a function called clickNext(). Whenever that button is clicked, it increments the index position (scope.selected) on an array called 'arr1'.
<button type="button" class="right-btn col-xs-6" role="menuitem" ng-click="clickNext()">Next</button>
.
function clickNext()
{
scope.selected = (scope.selected + 1) % length;
}
arr1 = [
{apple: 1 , tango},
{banana: 3, kappa},
{orange:5, alpha},
{apple: 8 , beta},
{grape: 10 , sigma}
]
Problem
I have an identical array to arr1 called 'arr2'. What I'm trying to do is have the clickNext() increment to the next index position based on the arr2 array instead of the arr1 array.
Right now, the clickNext function still increments in the order of the arr1 array. For example, if I were to click the button, it would start on orange:5 then move to apple 8, etc.
arr2 = [
{orange:5, alpha},
{apple: 8 , beta},
{banana: 3, kappa},
{grape: 10 , sigma},
{apple: 1 , tango}
]
What I have tried
My though process to accomplish this is to use the findIndex() function and match the arr2 item to the arr1 item. That doesn't work, but maybe I'm structuring it wrong?
clickNext(){
var oldIndex = filteredToXs(scope.selected);
scope.selected = oldIndex + 1;}
function filteredToXs( filteredIndex ) {
var firstArr = scope.arr1[ filteredIndex ];
var xsIndex = scope.arr2.findIndex( function(item) {
return item.trackingNumber === firstArr.trackingNumber;
} );
if( xsIndex >= 0 ) return xsIndex;
if( xsIndex === -1 ) return 0; // Default value
}
I hope I understood your question correctly. Please read my comments in the code sections as well.
I had to modify your source so I was able to create a fiddle for you.
HTML: I changed the click event and removed a css class that's not available
<button type="button" role="menuitem" onclick="clickNext();">Next</button>
Sampe Arrays:
They were containing invalid objects: I changed alpha, beta, tango, .. to a property. You can also define them as values.. this shouldn't matter:
var arr1 = [
{ apple: 1, tango: '' },
{ banana: 3, kappa: '' },
{ orange: 5, alpha: '' },
{ apple: 8, beta: '' },
{ grape: 10, sigma: '' }];
var arr2 = [
{ orange: 5, alpha: '' },
{ apple: 8, beta: '' },
{ banana: 3, kappa: '' },
{ grape: 10, sigma: '' },
{ apple: 1, tango: '' }];
Code:
var idx = 0; //save current index of array 2
function clickNext() {
idx++;
//I'm comparing the array objects using a string compare- this only works because you said 'I have an identical array'
//this may cause issues if you're objects are cross-referenced
var find = function(array, obj) { //lookup value from arr2 in arr1
for (var i=0, l=array.length;i<l;i++)
if (JSON.stringify(array[i]) == JSON.stringify(obj)) //adjust this line to your needs
return array[i];
}
var result = find(arr1, arr2[idx])
if (!result)
throw new Error('not found- your arrays are not identical or you run out of index');
console.log(result);
}
fiddle: https://jsfiddle.net/k50y8pp5/

How to indent a two dimensional array?

I'm trying to create a JSON file out of this array named table
table is a two dimensional array, and its second level contains:
[name, id, parent]
and I'd like to transform them into a JSON, but I don't know if I'm in the right direction or if there's a better way to do it. Can you help me?
Thanks in advance.
My Code:
var table = [
["name1", 1, 2],
["name2", 2, 3],
["name3", 3, 0],
["name4", 4, 1],
["name5", 5, 3]
];
function deepcheck(dad) {
for (var i = 0; i < table.length; i++) {
if (table[i][2] === dad) {
console.log('{' + table[i][1] + '}');
var h = table[i][1];
deepcheck(h);
}
}
}
for (var i = 0; i < table.length; i++) {
if (table[i][2] === 0) {
console.log('[{');
console.log(table[i][0] + ',' + table[i][1] + '[');
var t = table[i][1];
deepcheck(t);
}
}
Maybe this fits your need.
For a JSON string just use JSON.stringify(obj).
This solution heavily features the Array.prototype.reduce() method.
function getChildren(parent) {
// Array.reduce is a method which returns a value. the callback can have up to
// 4 parameters, a start value `r`, if defined, otherwise the first element of the
// array, the array element (maybe it starts with the second) `a`, the index (not
// defined here) and the object itself (not defined here).
// to make a structure i need to iterate over the given data `table` and look
// for a given parent. if found then i have to look for their children and iterate
// over the `table` again, until no children is found.
return table.reduce(function (r, a) {
// test if the parent is found
if (a[2] === parent) {
// if so, generate a new object with the elements of `cols` as properties
// and the values of the actual array `a`
// like { name: "name3", id: 3, parent: 0 }
var row = cols.reduce(function (rr, b, i) {
rr[b] = a[i];
return rr;
}, {});
// create a new property `children`and assign children with the actual id
// as parentId
row['children'] = getChildren(a[1]);
// push row to the result
r.push(row);
}
// return the result
return r;
// start value for r is an empty array
}, []);
}
var table = [
["name1", 1, 2],
["name2", 2, 3],
["name3", 3, 0],
["name4", 4, 1],
["name5", 5, 3]
],
cols = ['name', 'id', 'parent'],
obj = getChildren(0);
document.write('<pre>' + JSON.stringify(obj, null, 4) + '</pre>');

Categories

Resources