Related
I have 3 two dimensional arrays as given below which are series data to plot lines on a graph with the key being the timestamp.
const arr1 = [[1641013200000,1881],[1643691600000,38993],[1646110800000,41337],[1648785600000,78856],[1651377600000,117738],[1654056000000,119869],[1656648000000,157799],[1659326400000,196752],[1662004800000,199061],[1664596800000,237034],[1667275200000,239153],[1669870800000,269967]]
const arr2 = [[1641013200000,1302],[1643691600000,3347],[1646110800000,4754],[1648785600000,6948],[1651377600000,9725],[1654056000000,11314],[1656648000000,13787],[1659326400000,16666],[1662004800000,18370],[1664596800000,20876],[1667275200000,22384],[1669870800000,23560]]
const arr3 = [[1643691600000,67350],[1648785600000,134700],[1651377600000,202148],[1654056000000,202270],[1656648000000,269843],[1659326400000,337346],[1662004800000,337470],[1664596800000,404861],[1667275200000,404889],[1669870800000,472239]]
I want to plot another series line which gives the cumulative total of all three arrays values
(Note: if a timestamp is not present in either of the arrays, add the previous index value)
const totalArray = [
[1641013200000,3183],[1643691600000, 109690],[1646110800000, 113441],[1648785600000, 220504],
[1651377600000, 329611],[1654056000000, 333453],[1656648000000, 441429],[1659326400000, 550764],
[1662004800000, 554901],[1664596800000, 662771],[1667275200000, 666426],[1669870800000, 765766]
]
I have tried this, but some values are incorrect due to the timestamp not being present in either one
Approach:
const arr1 = [
[1641013200000, 1881],
[1643691600000, 38993],
[1646110800000, 41337],
[1648785600000, 78856],
[1651377600000, 117738],
[1654056000000, 119869],
[1656648000000, 157799],
[1659326400000, 196752],
[1662004800000, 199061],
[1664596800000, 237034],
[1667275200000, 239153],
[1669870800000, 269967]
];
const arr2 = [
[1641013200000, 1302],
[1643691600000, 3347],
[1646110800000, 4754],
[1648785600000, 6948],
[1651377600000, 9725],
[1654056000000, 11314],
[1656648000000, 13787],
[1659326400000, 16666],
[1662004800000, 18370],
[1664596800000, 20876],
[1667275200000, 22384],
[1669870800000, 23560]
];
const arr3 = [
[1643691600000, 67350],
[1648785600000, 134700],
[1651377600000, 202148],
[1654056000000, 202270],
[1656648000000, 269843],
[1659326400000, 337346],
[1662004800000, 337470],
[1664596800000, 404861],
[1667275200000, 404889],
[1669870800000, 472239]
];
const calculateTotal = () => {
var ret;
for (let a3 of arr3) {
var index = arr1.map(function(el) {
return el[0];
}).indexOf(a3[0]);
console.log(index);
if (index === -1) {
ret = arr1[index][0];
console.log(ret);
}
}
let unsortedArr = arr1.concat(arr2, arr3);
var sortedArray = unsortedArr.sort((a, b) => a[0] - b[0]);
var added = addArray(sortedArray);
console.log("Curent Output: " + JSON.stringify(added));
}
const addArray = (tuples) => {
var hash = {},
keys = [];
tuples.forEach(function(tuple) {
var key = tuple[0],
value = tuple[1];
if (hash[key] === undefined) {
keys.push(key);
hash[key] = value;
} else {
hash[key] += value;
}
});
return keys.map(function(key) {
return ([key, hash[key]]);
});
}
calculateTotal();
Is it possible to achieve this?
In your code there is this:
if (index === -1) {
ret = arr1[index][0];
But that assignment will fail as arr1[-1] is not defined.
Then when you do:
let unsortedArr = arr1.concat(arr2, arr3);
...you end up with an array that does not have the knowledge to use default values (from a previous index) when any of the three arrays has a "missing" time stamp.
I would suggest this approach:
Collect all the unique timestamps (from all arrays) into a Map, and associate arrays to each of those keys: these will be empty initially.
Populate those arrays with the timestamps from the original arrays
Get the sorted list of entries from that map
Fill the "gaps" by carrying forward values to a next array when the corresponding slot is undefined. At the same time sum up these values for the final output.
Here is an implementation:
function mergeArrays(...arrays) {
const map = new Map(arrays.flatMap(arr => arr.map(([stamp]) => [stamp, []])));
arrays.forEach((arr, i) => {
for (const [timeStamp, value] of arr) {
map.get(timeStamp)[i] = value;
}
});
const state = Array(arrays.length).fill(0);
return Array.from(map).sort(([a], [b]) => a - b).map(([timeStamp, arr], i) =>
[timeStamp, state.reduce((sum, prev, j) => sum + (state[j] = arr[j] ?? prev), 0)]
);
}
// Example run
const arr1 = [[1641013200000,1881],[1643691600000,38993],[1646110800000,41337],[1648785600000,78856],[1651377600000,117738],[1654056000000,119869],[1656648000000,157799],[1659326400000,196752],[1662004800000,199061],[1664596800000,237034],[1667275200000,239153],[1669870800000,269967]];
const arr2 = [[1641013200000,1302],[1643691600000,3347],[1646110800000,4754],[1648785600000,6948],[1651377600000,9725],[1654056000000,11314],[1656648000000,13787],[1659326400000,16666],[1662004800000,18370],[1664596800000,20876],[1667275200000,22384],[1669870800000,23560]];
const arr3 = [[1643691600000,67350],[1648785600000,134700],[1651377600000,202148],[1654056000000,202270],[1656648000000,269843],[1659326400000,337346],[1662004800000,337470],[1664596800000,404861],[1667275200000,404889],[1669870800000,472239]];
const result = mergeArrays(arr1, arr2, arr3);
console.log(result);
I am building a function that count Rank Order Ballots and returns the winner. The rules of this are that if a candidate has a clear majority then the candidate wins the election.
If not, we remove all reference to that candidate and whichever ballots the candidate go are assigned to whoever came second
So for example if we have this
const sample = { "A,B,C": 4, "B,C,A": 3, "C,B,A": 2};
Since C has the least number of votes and noone has a majority, all votes C won are then assigned to B, giving B the majority.
This is what I have written:
function removeLowestVotedCandidate(ballots) {
let lowestVotes = Object.entries(ballots).reduce((largestVal, comparison) =>comparison[1] < largestVal[1] ? comparison
:largestVal)[0]
.split('')[0]
//remove lowest voted candidate from object
const newRankedOrderBallots = JSON.parse(
JSON.stringify(ballots)
.replaceAll(`${lowestVotes}`, '')
.replaceAll(/(^[,\s]+)/g, '')
)
//remove leading commas
return Object.fromEntries(Object.entries(newRankedOrderBallots).map(([key, value]) => [key.replace(/^,+/,''), value]))
}
// console.log(removeLowestVotedCandidate(sample))
function getRankedChoiceWinner(ballots) {
let sum = 0
let winnerFound = false
let stretchWin = 0;
let sumByChar = {};
let winner = []
let updatedBallot = ballots
while(winnerFound === false) {
//count overall votes
for(let votes of Object.values(updatedBallot)){
sum +=votes
}
//calculate what is required for a clear majority
stretchWin = Math.round(sum/2)
//count votes assigned to each candidate
for(const[key, val] of Object.entries(updatedBallot)) {
const char = key[0];
sumByChar[char] = (sumByChar[char] ?? 0) + val;
}
console.log('sumByChar is currently', sumByChar)
//check if any candidate has a clear majority
winner = Object.entries(sumByChar)
.filter(([, val]) => val >= stretchWin)
.map(([keys]) => keys)
console.log('winner is currently', winner)
if (winner.length === 1) {
winnerFound = true
} else {
updatedBallot = removeLowestVotedCandidate(updatedBallot)
console.log('we are inside else', updatedBallot)
}
}
return winner
}
However, I seem to be getting the wrong answer, I am getting A as opposed to B. This is what is happening with my console.logs
sumByChar is currently { A: 4, B: 3, C: 2 }
winner is currently []
we are inside else { 'A,B,': 4, 'B,,A': 3, 'B,A': 2 }
sumByChar is currently { A: 8, B: 6, C: 4 }
winner is currently []
we are inside else { 'A,,': 4, A: 2 }
sumByChar is currently { A: 12, B: 9, C: 6 }
winner is currently [ 'A' ]
[ 'A' ]
It seems sumByChar is not reseting to zero and instead
There are 2 issues:
Your sumByChar is created outside the loop and mutated inside the loop. Every time there's a new iteration, you add additional values to it. Create the object inside the loop instead, so you get the sum only for the current ballots, not the cumulative sum for all iterations so far.
Your sum variable is also declared outside the loop, and you're adding to it inside the loop. Declare it inside the loop instead.
Also, the input structure is pretty badly designed for something like this. I'd highly recommend restructuring it to make removing candidates easier - using JSON.stringify and a regex just to remove something is extremely suspicious.
const sample = {
"A,B,C": 4,
"B,C,A": 3,
"C,B,A": 2
};
function removeLowestVotedCandidate(ballots) {
let lowestVotes = Object.entries(ballots).reduce((largestVal, comparison) => comparison[1] < largestVal[1] ? comparison :
largestVal)[0]
.split('')[0]
//remove lowest voted candidate from object
const newRankedOrderBallots = JSON.parse(
JSON.stringify(ballots)
.replaceAll(`${lowestVotes}`, '')
.replaceAll(/(^[,\s]+)/g, '')
)
//remove leading commas
return Object.fromEntries(Object.entries(newRankedOrderBallots).map(([key, value]) => [key.replace(/^,+/, ''), value]))
}
// console.log(removeLowestVotedCandidate(sample))
function getRankedChoiceWinner(ballots) {
let winnerFound = false
let stretchWin = 0;
let winner = []
let updatedBallot = ballots
while (winnerFound === false) {
let sumByChar = {};
//count overall votes
let sum = 0
for (let votes of Object.values(updatedBallot)) {
sum += votes
}
//calculate what is required for a clear majority
stretchWin = Math.round(sum / 2)
//count votes assigned to each candidate
for (const [key, val] of Object.entries(updatedBallot)) {
const char = key[0];
sumByChar[char] = (sumByChar[char] ?? 0) + val;
}
// console.log('sumByChar is currently', sumByChar)
//check if any candidate has a clear majority
winner = Object.entries(sumByChar)
.filter(([, val]) => val >= stretchWin)
.map(([keys]) => keys);
console.log('winner is currently', winner)
if (winner.length === 1) {
winnerFound = true
} else {
updatedBallot = removeLowestVotedCandidate(updatedBallot)
// console.log('we are inside else', updatedBallot)
}
}
return winner
}
console.log(getRankedChoiceWinner(sample));
Or, refactored to look halfway decent IMO:
const sample = {
"A,B,C": 4,
"B,C,A": 3,
"C,B,A": 2
};
const getRankedChoiceWinner = badBallots => checkOneBallot(restructureBallots(badBallots));
const restructureBallots = badBallots => Object.entries(badBallots)
.map(([candidatesStr, votes]) => [candidatesStr.split(','), votes]);
const checkOneBallot = (ballots) => {
const sumVotes = ballots.reduce((a, b) => a + b[1], 0);
const sumByCandidate = {};
for (const [candidates, voteCount] of ballots) {
const candidate = candidates[0];
sumByCandidate[candidate] = (sumByCandidate[candidate] ?? 0) + voteCount;
}
const winningEntry = Object.entries(sumByCandidate).find(([, val]) => val >= sumVotes / 2);
if (winningEntry) return winningEntry[0][0];
return removeLowestAndRetry(ballots, sumByCandidate);
};
const removeLowestAndRetry = (ballots, sumByCandidate) => {
const lowestVal = Math.min(...Object.values(sumByCandidate));
const lowestCandidateEntry = Object.entries(sumByCandidate).reduce(
(a, entry) => entry[1] < a[1] ? entry : a,
['', Infinity]
);
const lowestCandidate = lowestCandidateEntry[0];
for (const ballot of ballots) {
ballot[0] = ballot[0].filter(candidate => candidate !== lowestCandidate);
}
return checkOneBallot(ballots);
};
console.log(getRankedChoiceWinner(sample));
I would like to get find elements having same characters but in different order in an array. I made javascript below,is there any way to create Javascript function more basic? Can you give me an idea? Thank you in advance..
<p id="demo"></p>
<script>
const arr1 = ["tap", "pat", "apt", "cih", "hac", "ach"];
var sameChars = 0;
var subArr1 = [];
for(var i = 0; i < arr1.length; i++){
for(var j = i+1; j < arr1.length; j++){
if(!subArr1.includes(arr1[i]) && !subArr1.includes(sortAlphabets(arr1[i]))){
subArr1.push(arr1[i]);
sameChars++;
}
if(sortAlphabets(arr1[i]) == sortAlphabets(arr1[j])){
if(!subArr1.includes(arr1[j])){
subArr1.push(arr1[j]);
}
}
}
}
function sortAlphabets(text1) {
return text1.split('').sort().join('');
};
document.getElementById("demo").innerHTML = sameChars;
</script>
I would just use reduce. Loop over split the string, sort it, join it back. Use it as a key in an object with an array and push the items onto it.
const arr1 = ["tap", "pat", "apt", "cih", "hac", "ach"];
const results = arr1.reduce((obj, str) => {
const key = str.split('').sort().join('');
obj[key] = obj[key] || [];
obj[key].push(str);
return obj;
}, {});
console.log(Object.values(results));
You can get the max frequency value by building a map and getting the max value of the values.
const frequencyMap = (data, keyFn) =>
data.reduce(
(acc, val) =>
(key => acc.set(key, (acc.get(key) ?? 0) + 1))
(keyFn(val)),
new Map());
const groupMap = (data, keyFn) =>
data.reduce(
(acc, val) =>
(key => acc.set(key, [...(acc.get(key) ?? []), val]))
(keyFn(val)),
new Map());
const
data = ["tap", "pat", "apt", "cih", "hac", "ach"],
sorted = (text) => text.split('').sort().join(''),
freq = frequencyMap(data, sorted),
max = Math.max(...freq.values()),
groups = groupMap(data, sorted);
document.getElementById('demo').textContent = max;
console.log(Object.fromEntries(freq.entries()));
console.log(Object.fromEntries(groups.entries()));
.as-console-wrapper { top: 2em; max-height: 100% !important; }
<div id="demo"></div>
Maybe split the code into two functions - one to do the sorting and return a new array, and another to take that array and return an object with totals.
const arr = ['tap', 'pat', 'apt', 'cih', 'hac', 'ach'];
// `sorter` takes an array of strings
// splits each string into an array, sorts it
// and then returns the joined string
function sorter(arr) {
return arr.map(str => {
return [...str].sort().join('');
});
}
// `checker` declares an object and
// loops over the array that `sorter` returned
// creating keys from the strings if they don't already
// exist on the object, and then incrementing their value
function checker(arr) {
const obj = {};
for (const str of arr) {
// All this line says is if the key
// already exists, keep it, and add 1 to the value
// otherwise initialise it with 0, and then add 1
obj[str] = (obj[str] || 0) + 1;
}
return obj;
}
// Call `checker` with the array from `sorter`
console.log(checker(sorter(arr)));
<p id="demo"></p>
Additional documentation
map
Loops and iteration
Spread syntax
This question already has answers here:
Create an object based on file path string
(3 answers)
Closed 1 year ago.
I have an array of structured strings with have connection | as a format which self-divided into levels. I want to convert it into a structured object with multiple levels.
Input:
[
"clothes|tshirt|tshirt-for-kids",
"clothes|coat|raincoat",
"clothes|coat|leather-coat",
"clothes|tshirt|tshirt-for-men",
"clothes|tshirt|tshirt-for-men|luxury-tshirt",
]
Expected output:
{
clothes: {
tshirt: {
tshirt-for-kids: {},
tshirt-for-men: {
luxury-tshirt: {}
}
},
coat: {
raincoat: {}
leather-coat: {}
}
}
}
Very simple task - just enumerate the array and create the relevant object keys:
var myArray = [
"clothes|tshirt|tshirt-for-kids",
"clothes|coat|raincoat",
"clothes|coat|leather-coat",
"clothes|tshirt|tshirt-for-men",
"clothes|tshirt|tshirt-for-men|luxury-tshirt",
]
var result = {}, levels, current, temp;
while(myArray.length > 0)
{
levels = myArray.pop().split('|');
temp = result;
while(levels.length > 0)
{
current = levels.shift();
if(!(current in temp)) temp[current] = {};
temp = temp[current];
}
}
console.log(result);
You could try this:
const input = [
"clothes|tshirt|tshirt-for-kids",
"clothes|coat|raincoat",
"clothes|coat|leather-coat",
"clothes|tshirt|tshirt-for-men",
"clothes|tshirt|tshirt-for-men|luxury-tshirt",
];
function convertStrToObject(str, sep, obj) {
const sepIndex = str.indexOf(sep);
if (sepIndex == -1) {
obj[str] = obj[str] || {};
} else {
const key = str.substring(0, sepIndex);
obj[key] = obj[key] || {};
convertStrToObject(str.substring(sepIndex + 1), sep, obj[key]);
}
}
const all = {};
for (let i = 0; i < input.length; ++i) {
convertStrToObject(input[i], "|", all);
}
console.log(all);
Assuming you intend to collect properties, all having an empty object as leaf node.
// input
const input = [
"clothes|tshirt|tshirt-for-kids",
"clothes|coat|raincoat",
"clothes|coat|leather-coat",
"clothes|tshirt|tshirt-for-men",
"clothes|tshirt|tshirt-for-men|luxury-tshirt",
];
// Here, we collect the properties
const out = {};
// map the input array, splitting each line at |
input.map(i => i.split('|'))
.filter(a => a.length > 0) // lets not entertain empty lines in input
.forEach(a => { // process each array of property names
// start at outermost level
let p = out;
// iterate properties
for(const v of a){
// create a property if it is not already there
if(!p.hasOwnProperty(v)){
p[v] = {};
}
// move to the nested level
p = p[v];
}
});
// lets see what we have created
console.log(out);
A number of solutions have been suggested already, but I'm surprised none involves reduce() - which would seem the more idiomatic solution to me.
var array = [
"clothes|tshirt|tshirt-for-kids",
"clothes|coat|raincoat",
"clothes|coat|leather-coat",
"clothes|tshirt|tshirt-for-men",
"clothes|tshirt|tshirt-for-men|luxury-tshirt",
]
var object = array.reduce(function (object, element) {
var keys = element.split("|")
keys.reduce(function (nextNestedObject, key) {
if (!nextNestedObject[key]) nextNestedObject[key] = {}
return nextNestedObject[key]
}, object)
return object
}, {})
console.log(object)
One Liner With eval
Used eval to evaluate strings like the following:
'o["clothes"]??={}'
'o["clothes"]["tshirt"]??={}'
'o["clothes"]["tshirt"]["tshirt-for-kids"]??={}'
const
data = ["clothes|tshirt|tshirt-for-kids", "clothes|coat|raincoat", "clothes|coat|leather-coat", "clothes|tshirt|tshirt-for-men", "clothes|tshirt|tshirt-for-men|luxury-tshirt"],
arr = data.map((d) => d.split("|")),
res = arr.reduce((r, a) => (a.forEach((k, i) => eval(`r["${a.slice(0, i + 1).join('"]["')}"]??={}`)), r), {});
console.log(res)
One Liner Without eval
const
data = ["clothes|tshirt|tshirt-for-kids", "clothes|coat|raincoat", "clothes|coat|leather-coat","clothes|tshirt|tshirt-for-men", "clothes|tshirt|tshirt-for-men|luxury-tshirt"],
res = data.reduce((r, d) => (d.split("|").reduce((o, k) => (o[k] ??= {}, o[k]), r), r), {});
console.log(res)
Given the following 2 objects in javascript:
myFruit = {
'apple': 14,
'orange': 3,
'pear': 10
}
theirFruit = {
'banana': 10,
'grape': 30,
'apple': 2
}
What would be the most performant way to return an array of matching elements? The value for each of the keys does not matter.
Below is one example, but something tells me there is probably a better approach.
let matches = [];
let myKey;
Object.keys(myFruit).forEach((key, index) => {
myKey = key;
Object.keys(theirFruit).forEach((theirKey, index) => {
if(myKey === theirKey) {
matches.push(theirKey);
}
});
});
console.log(matches);
// will print: ['apple']
console.log(matches.length);
// will print: 1
Here is my solution.
const matches = Object.keys(myFruit).filter(key => key in theirFruit);
console.log(matches); // will output ['apple']
whether or not the 2 objects contain a matching key
If all keys are different, then a merged object will have as many keys as each object individually.
let haveAMatchingKey = Object.keys(Object.assign({}, myFruit, theirFruit)).length !=
Object.keys(myFruit).length + Object.keys(theirFruit)
After edit:
the most performant way to return an array of matching elements?
let myFruitSet = new Set(Object.keys(myFruit));
let theirFruitKeys = Object.keys(theirFruit);
let matchingKeys = theirFruitKeys.filter(fruit => myFruitSet.has(fruit))
Using HashMap Data Structure approach:
const findCommonFruits = () => {
const myFruit = {
'apple': 14,
'orange': 3,
'pear': 10
}
const theirFruit = {
'banana': 10,
'grape': 30,
'apple': 2
}
// #1 select lowest object keys
let lowestObj = null;
let biggestObj = null;
if (Object.keys(myFruit).length < Object.keys(theirFruit).length) {
lowestObj = myFruit;
biggestObj = theirFruit;
} else {
lowestObj = theirFruit;
biggestObj = myFruit;
}
// 2 Define an actual hashmap that will holds the fruit we have seen it
const haveYouSeenIt = {};
for (let fruit of Object.keys(lowestObj)) {
haveYouSeenIt[fruit] = fruit;
}
const res = [];
for (let fruit of Object.keys(haveYouSeenIt)) {
if (biggestObj[fruit] !== undefined) {
res.push(fruit);
}
}
return res;
}
console.log(findCommonFruits()); // ['apple']