Related
I have a JavaScript array of objects which looks like
var myarr = [
{'xx':'2023-01-01,,1'},
{'ss':'2023-01-01,2,1.2'},
{'dd':'2023-01-01,4,'},
{'rr':'2023-01-01,,'},
{'ff':'2023-01-01,,'},
{'gg':'2023-01-01,,'}
];
The array is actually much bigger than that, but I have cut it down for testing purposes, some of my arrays are thousands of lines long
Each object contains a date and two comma-separated values, although I have some rows which contain 3 or 4 comma separate values
What I need to do, is if any blank comma-separated value is found on any row then get the previous comma separated value from that position to a maximum of 2 times going back, although I may need to change that to a bigger number in the future
So with my example, I would get the following output
var myarr = [
{'xx':'2023-01-01,,1.6'},
{'ss':'2023-01-01,2,1.2'},
{'dd':'2023-01-01,4,1.2'},
{'rr':'2023-01-01,4,1.2'},
{'ff':'2023-01-01,4,'},
{'gg':'2023-01-01,,'}
];
I have tried to solve this with
var myarr = [
{'xx':'2023-01-01,,1'},
{'ss':'2023-01-01,2,1.2'},
{'dd':'2023-01-01,4,'},
{'rr':'2023-01-01,,'},
{'ff':'2023-01-01,,'},
{'gg':'2023-01-01,,'}
];
var maxAttempts = 3;
for (var i = 0; i < myarr.length; i++) {
var obj = myarr[i];
var values = Object.values(obj)[0].split(",");
var date = values[0];
var value1 = values[1];
var value2 = values[2];
for (var j = 1; j <= maxAttempts; j++) {
if (!value1) {
value1 = (myarr[i-j] && Object.values(myarr[i-j])[0].split(",")[1]) || " ";
}
if (!value2) {
value2 = (myarr[i-j] && Object.values(myarr[i-j])[0].split(",")[2]) || " ";
}
if (value1 && value2) {
break;
}
}
console.log(date, value1, value2);
for (var k = 3; k < values.length; k++) {
var value = values[k];
console.log(value);
}
}
but it doesn't seem to provide the expected output.
Can someone help me with what might be wrong?
Maybe you can use something like this.
const myarr = [
{ "xx": "2023-01-01,,1" },
{ "ss": "2023-01-01,2,1.2" },
{ "dd": "2023-01-01,4," },
{ "rr": "2023-01-01,," },
{ "ff": "2023-01-01,," },
{ "gg": "2023-01-01,," }
]
function fillInBlanks(arr, maxLookBack) {
return arr.map((obj, index) => {
const key = Object.keys(obj)[0]
const value = Object.values(obj)[0]
.split(",")
.map((x, n) => {
if (x === "" && index > 0) {
for (let i = index - 1; i >= Math.max(0, index - maxLookBack); --i) {
const prev = Object.values(arr[i])[0].split(",")
if (prev[n] !== "") return prev[n]
}
} else return x
})
return Object.fromEntries([
[key, value.join(",")]
])
})
}
fillInBlanks(myarr, 2).forEach(x => console.log(x))
Here's my attempt. This will also work with any number of values per row.
const maxAttempts = 2;
myarr.reduce((modifiedAccumulation, currentObject, index) => {
const [key, csv] = Object.entries(currentObject)[0];
const splitCsv = csv.split(",");
const modifiedCsv = splitCsv
.reduce((fixedArray, currentElement, csvPos) => {
let numberToUse =
currentElement === ""
? myarr
.slice(Math.max(index - maxAttempts, 0), index)
.reduceRight((proposedNum, currentPastObj) => {
if (proposedNum !== "") return proposedNum;
let candidate =
Object.entries(currentPastObj)[0][1].split(",")[csvPos];
return candidate !== "" ? candidate : "";
}, "")
: currentElement;
return [...fixedArray, numberToUse];
}, [])
.join(",");
return [...modifiedAccumulation, { [key]: modifiedCsv }];
}, []);
This approach creates a 'window' array containing the last few entries, which is used to look up prior column values.
const myarr = [{"xx":"2023-01-01,,1"},{"ss":"2023-01-01,2,1.2"},{"dd":"2023-01-01,4,"},{"rr":"2023-01-01,,"},{"ff":"2023-01-01,,"},{"gg":"2023-01-01,,"}]
const windowSize = 2
const w = [], r =
myarr.map(e=>Object.entries(e).flatMap(([k,v])=>[k,...v.split(',')]))
.map(a=>(
w.unshift(a) > windowSize+1 && w.pop(),
a.map((_,i)=>w.find(x=>x[i])?.[i])
)).map(([k,...v])=>[k,v.join()]
).map(i=>Object.fromEntries([i]))
console.log(r)
I receive data => these data could be array of object or just a object.
I write some code, but maybe there is a way to make this code more sexy, clear, or shorter plus without any errors
Here is the code:
export const CalculateIt = (props) => {
const conversionValues = []
if (props) {
if (props.length > 0) {
for (let i = 0; i < props.length; i++) {
const clicks = props[i]?.insights?.[0].inline_link_clicks
const actionsNumber = props[i]?.insights?.[0]?.actions?.length || 0
let result = 0
if (clicks && actionsNumber) {
result = devideNumbers(clicks, actionsNumber, 8)
}
conversionValues.push(result)
}
return conversionValues
}
const clicks = props?.insights?.[0].inline_link_clicks
const actionsNumber = props?.insights?.[0]?.actions?.length || 0
let result = 0
if (clicks && actionsNumber) {
result = devideNumbers(clicks, actionsNumber)
}
return conversionValues.push(result)
}
}
As you can see there you can find some parts of the code that are similar like:
const clicks = props[i]?.insights?.[0].inline_link_clicks
and
const clicks = props?.insights?.[0].inline_link_clicks
Is it possible to write it more smart?
Best
Probably move the common code in a function:
function getResult(data) {
const clicks = data?.insights?.[0].inline_link_clicks
const actionsNumber = data?.insights?.[0]?.actions?.length || 0
let result = 0
if (clicks && actionsNumber) {
result = devideNumbers(clicks, actionsNumber, 8)
}
return result;
}
And use the helper function in your original function:
export const CalculateIt = (props) => {
const conversionValues = []
if (props) {
if (props.constructor === Array) {
props.forEach((item) => conversionValues.push(getResult(item)))
} else {
conversionValues.push(getResult(props));
}
return conversionValues;
}
}
In fact, you can force all data into a one-dimensional array and safe work with array of objects only.
Is this code sexy enough?
const obj = {id: 1, val: 1};
const arr = [{id: 1, val: 1},{id: 2, val: 2},{id: 3, val: 3}];
const normalize = (data) => [data].flat();
console.log(normalize(obj)[0]);
console.log(normalize(arr)[0]);
// -------------------
// and you can use this approach in code:
const getResult = (obj) => obj.val * 10;
const CalculateIt = (props) => [props].flat().map(getResult);
console.log(CalculateIt(obj));
console.log(CalculateIt(arr));
.as-console-wrapper{min-height: 100%!important; top: 0}
I am trying to get the smallest string out of every nested array in the following array object
let data = ["test string", ["abcd", "efj", ["hijklm", ["op"], "hijk", "hijklmn", ["op", "opq"]]]]
I have tried the code but it gives me stackoverflow error,Any help please
let data = ["test string", ["abcd", "efj", ["hijklm", ["op"], "hijk", "hijklmn", ["op", "opq"]]]]
let smallest = []
function getSmallest(data) {
data.forEach((ele, i) => {
if (typeof ele == "string") {
smallest.push(ele);
} else if (typeof ele == "object") {
// removing the array first
let _data = JSON.parse(JSON.stringify(data));
let only_array = _data.splice(i, 1);
getSmallest(only_array)
// now data contains only strings
//finding the smalles string from array
let small = _data.filter(v => typeof v === 'string')
.reduce((a, v) => a && a.length <= v.length ? a : v, '')
smallest.push(small);
}
});
}
getSmallest(data);
console.log(smallest)
Required result -Smallest in every array (nested one as well)
["test string", "efj", "hijk", "op", "op"]
You could take a recursive approach.
const
smallest = array => array
.reduce((r, value) => {
if (Array.isArray(value)) r.push(...smallest(value));
else if (!r[0].length || r[0][0].length > value.length) r[0][0] = value;
return r;
}, [[]])
.flat(),
data = ["test string", ["abcd", "efj", ["hijklm", ["op"], "hijk", "hijklmn", ["op", "opq"]]]],
result = smallest(data);
console.log(result);
You can use .reduce, here is an example:
const data = ["test string", ["abcd", "efj", ["hijklm", ["op"], "hijk", "hijklmn", ["op", "opq"]]]]
const sortingWeight = (v) => Array.isArray(v) ? Infinity : v.length
const reducer = (acc, cur, i, arr) => {
if (Array.isArray(cur)) {
acc = [...acc, ...cur.reduce(reducer, [])];
} else if (i === 0) {
const smallestAtThisLevel = arr.sort((a, b) => {
a = sortingWeight(a);
b = sortingWeight(b);
return a - b;
})[0];
if (!Array.isArray(smallestAtThisLevel)) {
acc.push(smallestAtThisLevel);
}
}
return acc;
}
const result = data.reduce(reducer, []);
console.log(result);
An solution with recursive function and reduce
let data = ["test string", ["abcd", "efj", ["hijklm", ["op"], "hijk", "hijklmn", ["op", "opq"]]]]
let result = [];
function getSmallestString(inp) {
let smallest = inp.reduce((a,v) => {
if(Array.isArray(v)) {
getSmallestString(v);
return a;
}
if(!a || v.length < a.length) return v;
return a;
}, null)
result.unshift(smallest);
}
getSmallestString(data);
console.log(result);
This version works by accumulating the results into an array (initially empty) that is passed down through the recursive layers:
// (sup-optimal) helper function to split the array by type
// if you want optimal, use the underscore version
const partition = (a, pred) => [ a.filter(pred), a.filter(e => !pred(e)) ];
// helper function for reduce
const shorter = (a, b) => (a.length < b.length) ? a : b;
function getSmallest(data, result = []) {
// split the array into strings and subarrays
const [strings, sub] = partition(data, e => typeof e === 'string');
// add the shortest string, if any
if (strings.length) {
result.push(strings.reduce(shorter));
}
// recurse through sub-arrays, if any
if (sub.length) {
sub.forEach(e => getSmallest(e, result));
}
return result;
}
let data = ["test string", ["abcd", "efj", ["hijklm", ["op"], "hijk", "hijklmn", ["op", "opq"]]]]
const shorter = (left, right) => left.length <= right.length ? left : right;
const smallest = data.flat(Infinity).reduce(shorter)
console.log(smallest);
Just vanilla javascript
let data = [
"test string",
["abcd", "efj", ["hijklm", ["op"], "hijk", "hijklmn", ["op", "opq"]]],
];
let k = 0;
let result = [];
function smallestStringSolve(arr) {
let temp_arr = [];
let smallest_string_len = Infinity;
let smallest_string = "";
for (let i = 0; i < arr.length; i++) {
if (typeof arr[i] == "string") {
if (arr[i].length < smallest_string_len) {
smallest_string_len = arr[i].length;
smallest_string = arr[i];
}
}else if (Array.isArray(arr[i])){
temp_arr.push(arr[i]);
}
}
if(smallest_string)
result[k++] = smallest_string;
if (temp_arr.length){
for(let i=0; i<temp_arr.length; i++)
smallestStringSolve(temp_arr[i]);
}
return;
}
smallestStringSolve(data);
console.log(result);
how can i turn this array to an object like below
let arr = ["key1:value1","key2,value2"]
to
{
"key1":"value1",
"key2":"value2"
}
Split by either colons or commas, then pass to Object.fromEntries:
const arr = ["key1:value1","key2,value2"];
const obj = Object.fromEntries(
arr.map(str => str.split(/[:,]/))
);
console.log(obj);
Try using reduce:
const arr = ["key1:value1","key2,value2"];
const object = arr.reduce((obj, item) => {
const colon = item.split(':');
if (colon.length === 2) {
obj[colon[0]] = colon[1];
return obj;
}
const comma = item.split(',');
if (comma.length === 2) {
obj[comma[0]] = comma[1];
return obj;
}
return obj;
}, {});
or forEach
const arr = ["key1:value1","key2,value2"];
const object = {};
arr.forEach(item => {
const colon = item.split(':');
if (colon.length === 2) {
obj[colon[0]] = colon[1];
}
const comma = item.split(',');
if (comma.length === 2) {
obj[comma[0]] = comma[1];
}
});
You can try with reduce()
let arr = ["key1:value1","key2,value2"];
var res = arr.reduce((a,c) => {
var k = c.split(/[:,]/)[0];
var v = c.split(/[:,]/)[1];
a[k] = v;
return a
},{});
console.log(res);
I have a question about walk down an object dynamically by an given array object.
Tried with some static code but this is not flexible in a situation where there are more or less levels
// value = 10
// field = ["data", "input", "level", "0"]
item[field[0]][field[1]][field[2]][field[3]] = value
I have no clue where to start with a function doing this with a for loop. Can anybody give me some advice to get started.
You could reduce the fields and take an object and it's properties. At the end assign the value with the last key.
const
setValue = (object, [...path], value) => {
var last = path.pop();
path.reduce((o, k) => o[k] = o[k] || {}, object)[last] = value;
},
object = {},
value = 10,
fields = ["data", "input", "level", "0"];
setValue(object, fields, value);
console.log(object);
There's a built-in method on Lodash that does just this - _.set.
_.set(item, field, value)
let item = {
data: {
input: {
level: [5]
}
}
};
const field = ["data", "input", "level", "0"];
const value = 10;
_.set(item, field, value);
console.log(item);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>
You can use a recursive function to navigate through your object :
var value = 10;
var field = ["data", "input", "level", "0"];
var obj =
{
data:
{
input:
{
level: [42]
}
}
};
function SetValue(field, obj, value, index = 0)
{
var memberName = field[index];
// not at last item ?
if (index < field.length - 1)
{
if (obj.hasOwnProperty(memberName))
{
SetValue(field, obj[memberName], value, index + 1);
}
}
else
{
obj[memberName] = value;
}
}
console.log("Before");
console.log(obj);
console.log("---------------------");
SetValue(field, obj, value);
console.log("After");
console.log(obj);
console.log("---------------------");
I already use a function in production that literally does what you want:
/**
* Replace an item in datasource with specified path with new value.
* Will _create_ an item if the path does not currently exist.
* #param {object} o Datasource
* #param {array} k Array of keys used to access item (usually getPath())
* #param {*} v New value of specified item
*/
const replaceItem = (o, k, v) => k.reduce((r, e, i, a) => {
if (!a[i + 1]) r[e] = v;
else return r[e]
}, o)
const dataSource = {
data: {
input: {
level: [1]
}
}
}
const path = ["data", "input", "level", "0"]
replaceItem(dataSource, path, 5)
console.log(dataSource)
another version, with a twist. It differentiates wether the object it creates should be an Object or an Array
const setValue = (object, path, value) => {
if (!object || !path || !path.length) return;
var key = path[0],
i = 1,
prev = key;
while (i < path.length) {
key = path[i++];
object = object[prev] || (object[prev] = +key === (key >>> 0) ? [] : {});
prev = key;
}
object[key] = value;
};
const object = {};
setValue(object, ["data", "input", "level", "0"], 10);
console.log(object);
You could keep a current variable which represents the current object you're on in your object and then set the value at the end once your loop is complete:
const item = {
"data": {
"input": {
"level": {
0: -1
}
}
}
}
let value = 10;
let field = ["data", "input", "level", "0"];
let current = item[field[0]];
for (let i = 1; i < field.length - 1; i++) {
current = current[field[i]];
}
current[field.pop()] = value;
console.log(item);
The above can also be achieved recursively (use nf === undefined instead of !nf if falsy field values exist):
const item = {
"data": {
"input": {
"level": {
0: -1
}
}
}
}
let value = 10;
let field = ["data", "input", "level", "0"];
const set_val = (val, obj, [f, nf, ...rest]) =>
!nf ? obj[f] = val : set_val(val, obj[f], [nf, ...rest]);
set_val(value, item, field);
console.log(item);
If you wish to build the item object from scratch, you could also do something like so:
const value = 10;
const field = ["data", "input", "level", "0"];
let item = {};
let curr = item;
for (let i = 0; i < field.length - 1; i++) {
curr[field[i]] = {};
curr = curr[field[i]];
}
curr[field.pop()] = value;
console.log(item);
This build can also be done recursively like so:
const value = 10;
const field = ["data", "input", "level", "0"];
let item = {};
let curr = item;
const set_val = (val, [f, nf, ...rest], item = {}) => {
if (!nf) {
item[f] = val;
return item;
} else {
item[f] = set_val(val, [nf, ...rest], item[f]);
return item;
}
}
console.log(set_val(value, field));