Related
I try to write a function (called: tally) using recursion (part of the exercise) to scan through an array of numbers and return an object with the numbers as key and the number of instances as value.
Example:
tally([2,3,4,5,5,5,5,5,5,5,6,7,,6,7,6,7,5,4,3,4,5,5,6])
//{2: 1, 3: 2, 4: 3, 5: 10, 6: 4, 7: 3}
I created the framework but i am not sure about the syntax to make it work:
function tally(arr) {
var obj = {}
if (/*check if object ('obj') has a key corresponding to the array element*/) {
//increase key's value by onee
} else {
//add key with value of 1
}
return obj
};
Any hints to complete the recursion function above? Please try to stick to my structure in your answers as much as possible since this is part of an exercise.
Here you are:
function tally(arr) {
if (arr.length == 0) {
return {}
}
var value = arr.pop()
var obj = tally(arr)
if (value in obj) {
obj[value] += 1
} else {
obj[value] = 1
}
return obj
};
EDIT:
It can also be done using slice() instead of pop():
function tally(arr) {
if (arr.length == 0) {
return {}
}
var value = arr[0]
var obj = tally(arr.slice(1))
if (value in obj) {
obj[value] += 1
} else {
obj[value] = 1
}
return obj
};
Using extra parameter for an index, i, the result, r -
const plus1 = (k = "", r = {}) =>
( k in r
? r[k] += 1
: r[k] = 1
, r
)
const tally = (a = [], i = 0, r = {}) =>
i >= a.length
? r
: tally
( a
, i + 1
, plus1(a[i], r)
)
console.log(tally([2,3,4,5,5,5,5,5,5,5,6,7,,6,7,6,7,5,4,3,4,5,5,6]))
Output
{
"2": 1,
"3": 2,
"4": 3,
"5": 10,
"6": 4,
"7": 3,
"undefined": 1
}
ok, so you are asked to do a recursion just for the sake of it.
This could be done (albeit is hacky) passing an extra parameter to tally. When you declare a function in vanilla js you can actually feed it extra stuff. So, in each recursion, pass obj as a second parameter:
EDIT
Thanks #Bergi, you're right. I'll edit the code
function tally(arr) {
let obj = arguments.length>1? arguments[1] : {};
if(arr.length===0) {
return obj;
}
let next_number=arr.pop();
obj[next_number]=obj[next_number]||0;
obj[next_number]++;
return tally(arr,obj);
};
let inputArr = [2,3,4,5,5,5,5,5,5,5,6,7,6,7,6,7,5,4,3,4,5,5,6],
outputObj=tally(inputArr);
console.log(outputObj);
console.log({outputEmpty:tally([])});
I am not sure how to guide you to an answer without giving it away entirely, but this is what I would recommend. (There are some problems such as you destroy arr in the process that you may want to consider)
function tally(arr, obj) {
// if the length is zero we've gone through every value
if(arr.length === 0)
return obj
// create obj if we didn't provide it
if(obj === undefined)
obj = {}
// pull the last value from arr
let val = arr.pop()
if (/*check if object ('obj') has a key corresponding to the array element*/) {
//increase key's value by onee
} else {
//add key with value of 1
}
// move onto the next value
return tally(arr,obj)
}
EDIT: took #Bergi's input
const arr = [
{"datetime":"2018/8/5","value":85,"type":"A"},
{"datetime":"2018/8/10","value":7,"type":"B"},
{"datetime":"2018/8/10","value":73,"type":"A"}
];
I have an array as you can see in the snippet. My issue is I need to check something per day:
For each day when A value > 60 or when B value > 6 then do something.
Else when A value <= 60 and when B value <= 6 then do something else.
And I don't know how to do this check with the current array structure as each step in the loop is a different day. I want to compare all values for one day at the same time.
Is it possible to transform the array to look like this? Then I will be able to compare day per day...
const arr = [
{"datetime":"2018/8/5","valueA":85,"valueB":undefined},
{"datetime":"2018/8/10","valueB":7,"valueA":73}
];
Thank you!
You can make a the date groups by reducing into an object. Then just set the appropriate value in that object. In the end your array will be in the Object.keys() of the grouped object.
[As you might surmise from the comments, the order of the final array is not guaranteed because object keys and values are not guaranteed. If your original data is ordered by date, you should say so in the question because there will be more efficient ways to do this if the order is guaranteed].
const arr = [{"datetime":"2018/8/5","value":85,"type":"A"},{"datetime":"2018/8/10","value":7,"type":"B"},{"datetime":"2018/8/10","value":73,"type":"A"}];
let groups = arr.reduce((obj, {datetime, value, type}) => {
if (!obj[datetime]) obj[datetime] = {datetime, valueA:undefined, valueB:undefined}
let currentKey = type == 'A' ? "valueA" : "valueB"
obj[datetime][currentKey] = value
return obj
},{})
let newArr = Object.values(groups)
console.log(newArr)
This will transform the array as OP asked for, and will respect the order.
const arr = [{"datetime":"2018/8/5","value":85,"type":"A"},
{"datetime":"2018/8/10","value":7,"type":"B"},
{"datetime":"2018/8/10","value":73,"type":"A"}];
var daysArr = []
arr.map(function(day){
var keyName = 'value'+day.type
var found = false
var dayObj = {}
for (var i=0; i < daysArr.length; i++) {
if (daysArr[i].datetime === day.datetime) {
daysArr[i][keyName] = day.value;
found = true
break
}
}
if (!found) {
dayObj = {"datetime":day.datetime,valueA:undefined,valueB:undefined}
dayObj[keyName] = day.value
daysArr.push(dayObj)
}
})
console.log(daysArr);
One solution could be using reduce(). Note that if a key is not defined will return undefined (this is exemplified on the second log to the console), so I consider redundant to define, for example "value-B": undefined, unless you want to assign to it another default value.
Warning: As discussed on the comments, you should note that the order of the final result, may not be preserved.
const arr = [
{"datetime":"2018/8/5","value":85,"type":"A"},
{"datetime":"2018/8/10","value":7,"type":"B"},
{"datetime":"2018/8/10","value":73,"type":"A"}
];
let res = arr.reduce((acc, {datetime, value, type: type}) =>
{
acc[datetime] = acc[datetime] || {};
Object.assign(acc[datetime], {datetime, [`value-${type}`]: value});
return acc;
}, {});
console.log(Object.values(res));
console.log(Object.values(res)[0]["value-B"]);
You could do this:
<html>
<head>
<meta charset="UTF-8"></meta>
<script type="text/javascript">
const arr = [{"datetime":"2018/8/5","value":85,"type":"A"},
{"datetime":"2018/8/10","value":7,"type":"B"},
{"datetime":"2018/8/10","value":73,"type":"A"}];
var new_arr = group_items(arr)
console.log(new_arr)
function group_items(arr)
{
var ret_arr = []
for(var x=0;x<arr.length;x++)
{
var cur_date = arr[x].datetime
var pos = lookup_date(cur_date, ret_arr)
var obj = {}
obj.datetime = cur_date
if(pos != false)
{
//add to existing item
if(arr[x].type == 'A')
{
ret_arr[pos].valueA = arr[x].value
}
else if(arr[x].type == 'B')
{
ret_arr[pos].valueB = arr[x].value
}
}
else{
if(arr[x].type == 'A')
{
obj.valueA = arr[x].value
}
else if(arr[x].type == 'B')
{
obj.valueB = arr[x].value
}
ret_arr.push(obj)
}
}
return ret_arr
}
function lookup_date(date, arr)
{
/*
returns the position in arr of date
*/
var retval = false
for(var x=0;x<arr.length;x++)
{
if(arr[x].datetime == date)
{
retval = x
break
}
}
return retval
}
</script>
</head>
<body>
</body>
If you don't need the final array to include the datetimes in the same order as the original, then you can just make an object that maps datetimes to the corresponding values and then use Object.values to get the final array. This approach does not guarantee order, since objects are unordered data structures:
const arr = [
{"datetime":"2018/8/5","value":85,"type":"A"},
{"datetime":"2018/8/10","value":7,"type":"B"},
{"datetime":"2018/8/10","value":73,"type":"A"}
];
const values_by_date = { };
arr.forEach( ({ datetime, type, value }) =>
values_by_date[ datetime ] = {
datetime/*, valueA: undefined, valueB: undefined*/,
...values_by_date[ datetime ], [`value${type}`]: value
}
);
const result = Object.values( values_by_date );
console.log( result );
If you need the final array to include the datetimes in the same order as the original array and the original array is already sorted by datetime, you can do it in a single pass like this:
const arr = [
{"datetime":"2018/8/5","value":85,"type":"A"},
{"datetime":"2018/8/10","value":7,"type":"B"},
{"datetime":"2018/8/10","value":73,"type":"A"}
];
const result = arr.reduce( ({ result, datetime: prev }, { datetime, type, value }) => {
if ( datetime !== prev )
result.push( { datetime/*, valueA: undefined, valueB: undefined*/ } );
Object.assign( result[ result.length - 1 ], { [`value${type}`]: value } );
return { result, datetime };
}, { result: [] } ).result;
console.log( result );
Note: In either snippet you can uncomment /*, valueA: undefined, valueB: undefined*/ if you want the resulting objects to include properties for the missing values.
I'm trying to filter an Object by an array of arrays, getting back an array of objects.
Like this:
let obj =
{
"a.1":1,
"a.2":2,
"b.1":3,
"b.2":4,
"c.1":5,
"c.2":6
}
let array =
[
["a.1","b.1"],
["a"],
["b","c.1"]
]
let expectedResult =
[
{
"a.1":1,
"b.1":3,
},
{
"a.1":1,
"a.2":2,
},
{
"b.1":3,
"b.2":4,
"c.1":5
},
]
// this is what I came up with
const filterObjectByArray = (obj, arr) =>
Object.keys(obj)
.filter(ch => {
for (var index = 0; index < arr.length; index++)
if (ch.startsWith(arr[index]))
return true;
})
.reduce((ret, key) =>{
ret[key] = obj[key]
return ret
},{})
let result = array.map(arr => filterObjectByArray(obj, arr))
//kind of deepEqual
console.log(JSON.stringify(expectedResult) == JSON.stringify(result))
Is there a easier or more convenient way to do that? I need to do this operation quite often and my object will be up to couple hundreds entries big, so I see a potential bottleneck here.
I would create a one type mapping of the "base" (the letter) to the "real" keys, and then use it to translate the letter to the real keys when create the object.
const obj = {
"a.1": 1,
"a.2": 2,
"b.1": 3,
"b.2": 4,
"c.1": 5,
"c.2": 6
};
const array = [
["a.1", "b.1"],
["a"],
["b", "c.1"]
];
const getBaseKey = (key) => key.match(/^[a-z]+/)[0]; // get the base of the key - the letter. If it's only one letter, you can use key[0]
/** create a one time map of keys by their base **/
const oobjKeysMap = Object.keys(obj).reduce((map, key) => {
const baseKey = getBaseKey(key);
const curr = map.get(baseKey) || [];
curr.push(key);
return map.set(baseKey, curr);
}, new Map());
const result = array.map((sub) => // iterate the array
[].concat(...sub.map((k) => k in obj ? k : oobjKeysMap.get(getBaseKey(k)))) // create the least of "real" keys
.reduce((ret, key) => { // create the object
ret[key] = obj[key];
return ret;
}, {})
);
console.log(result);
I have one object that I had to take apart into two arrays to handle properly.
It looked like this:
{
city:"stuttgart",
street:"randomstreet",
...
}
Since it needs to fit a certain directive I had to convert it to:
[
{key:"city", value:"stuttgart"}
{key:"street", value:"randomstreet"},
...
]
for this I first used
var mapFromObjectWithIndex = function (array) {
return $.map(array, function(value, index) {
return [value];
});
};
var mapFromObjectWithValue = function (array) {
return $.map(array, function(value, index) {
return [index];
});
});
to create two arrays, one containing the old key, the other one is holding the old value. Then I created another, two dimensional array map them into a single array doing this
var mapToArray = function (arrayValue, arrayIndex) {
var tableData = [];
for (var i = 0; i<arrayIndex.length; i++){
tableData[i] = {key:arrayIndex[i] , value:arrayValue[i]};
}
return tableData;
};
(maybe I have already messed up by here, can this be done any easier?)
Now, I use the array (tableData) to display the data in a form. The value fields can be edited. In the end, I want to convert the array (tableData) to its original. (see first object)
Please note, that the original object doesn't only contain strings as values, but can also contain objects as well.
I think conversion can be definitely easier:
var obj = {
city:"stuttgart",
street:"randomstreet",
};
var tableData = Object.keys(obj).map(k => {return {key: k, value: obj[k]}});
console.log(tableData);
var dataBack = {};
tableData.forEach(o => dataBack[o.key] = o.value);
console.log(dataBack);
What do you want to do with objects? Do you want to expand them as well? If yes you can do something like this (and it works with nested objects as well):
var obj = {
city:"stuttgart",
street:"randomstreet",
obj: {a: 'a', b: 'b'},
subObject: {aha: {z: 'z', y: 'y'}}
};
function trasformToTableData(obj) {
if (typeof obj !== 'object') return obj;
return Object.keys(obj).map(k => {return {key: k, value: trasformToTableData(obj[k])}});
}
var tableData = trasformToTableData(obj);
console.log(tableData);
function transformBack(obj) {
if (Array.isArray(obj)) {
var support ={};
for (let i = 0; i < obj.length; i++) {
support[obj[i].key] = transformBack(obj[i].value)
}
return support;
}
return obj;
}
var dataBack = {};
tableData.forEach(o => dataBack[o.key] = transformBack(o.value));
console.log(dataBack);
Let's have some fun and turn our object into iterable to do the job as follows;
var input = {city:"stuttgart", street:"randomstreet", number: "42"};
output = [];
input[Symbol.iterator] = function*(){
var ok = Object.keys(this),
i = 0;
while (i < ok.length) yield {key : ok[i], value: this[ok[i++]]};
};
output = [...input];
console.log(output);
This function will map your object to an array when you call objVar.mapToArray(), by using Object.keys() and .map()
Object.prototype.mapToArray = function() {
return Object.keys(this).map(function(v) {
return { key: v, value: this[v] };
}.bind(this));
}
I would do something like this:
var dataObj = {
city:"stuttgart",
street:"randomstreet",
};
function toKeyValue(obj) {
var arr = [];
for (var key in obj) {
if(obj.hasOwnProperty(key)) {
arr.push({'key': key, 'value': obj[key]});
}
}
return arr;
}
var arrayKeyValue = toKeyValue(dataObj);
console.log(arrayKeyValue);
I want to compare an unlimited amount of objects in an array, like
{ a: true, b: false, c: "foo"}
and
{ a: false, b: false, c: "foo"}
and get a result like
[b,c]
This can be done with just two objects like this:
function compare(first, second) {
var shared = []
$.each(first, function(index, value){
if(second[index] == value){
shared.push(index)
}
})
return shared
}
So my question is: How to write a (recursive) function that compares n objects and returns the properties they all have in common.
Thank you for reading this
That's pretty simple if written literally:
function allCommonProperties(objects) {
if (!objects.length) return [];
var first = objects[0];
return Object.keys(first).filter(function(p) {
return objects.every(function(o) {
return first[p] === o[p];
});
});
});
No need for recursion, no need for jQuery.
An alternative approach using .filter and .every. No libs.
Object.keys(first)
.filter(function(key){
return objects.every(function(obj){
return obj[key] === first[key];
});
});
For ES6 envs
Object.keys(first).filter(key => objects.every(obj => obj[key] === first[key]));
Where
objects - an array of all objects to compare
first = objects.shift() - the first element
This solution features Array.prototype.filter in combination with Object.keys.
var setA = { a: true, b: false, c: "foo" },
setB = { a: false, b: false, c: "foo" },
commonProperties = Object.keys(setA).filter(function (k) {
return setA[k] === setB[k];
});
document.write('<pre>' + JSON.stringify(commonProperties, 0, 4) + '</pre>');
I'd do it with .reduce(), going through the array and accumulating an object containing only properties that have been found (with matching values) in all the objects so far.
I'll give my example with an "extend" function; if you're working with jQuery you could use $.extend() or you can write a simple one. The idea is to do a shallow copy of properties from one object into another, which'll be necessary for the first object in the list.
var objects = [ ... ]; // the array of objects
var resultObject = objects.reduce(function(result, obj) {
if (result === null)
result = extend({}, obj);
else {
Object.keys(result).forEach(function(key) {
if (!(key in obj) || obj[key] !== result[key])
delete result[key];
});
}
return result;
}, null);
var resultProperties = Object.keys(resultObject);
Here's what extend() might look like:
function extend(obj) {
var args = arguments;
for (var i = 1; i < args.length; ++i)
Object.keys(args[i]).forEach(function(key) {
obj[key] = args[i][key];
});
return obj;
}
I think that work:
function compareObj(obj1,obj2){
var ar =[];//you need an array to put the diffirent property
for(key in obj1){//getting all proo
try{
if (obj1[key]!=obj2[key]) //if there is diffirence
{
ar[ar.length-1]=key;
//key is the name of property
}
}
catch(err){
console.log("Not the same type of objects");
return false;
}
}
return ar ;
}
This tries to iterate as least as possible. It mutates an array that holds the keys of the initial object every time a property is no longer found on the next object and will also stop iterating the rest of the array elements if there's no common properties left.
function filterByPropertyValue(arr) {
var acc = {},
initial, objectProperties;
if (arr.length > 0) {
initial = arr[1];
objectProperties = Object.keys(initial);
for (var i = 1; i < arr.length; i++) {
if (objectProperties.length > 0) {
objectProperties.forEach(function(prop, index) {
if (initial[prop] === arr[i][prop]) {
acc[prop] = initial[prop];
} else {
delete acc[prop];
delete objectProperties[index];
}
});
} else {
break;
}
}
}
return acc;
}
var arr = [{ a: true, b: false, c: "foo"}, { a: false, b: false, c: "foo"}, { a: true, b: false, c: "bar"}];
document.getElementById('results').innerHTML = JSON.stringify(filterByPropertyValue(arr), null, '\t');
<pre id="results"></pre>