How do i move through the values of keys in javascript? - javascript

How would i cycle move the key values in a js array,
for example:
{Name:"A", Task:"Task1"},
{Name:"B", Task:"Task2"},
{Name:"C", Task:"Task3"},
to
{Name:"A", Task:"Task3"},
{Name:"B", Task:"Task1"},
{Name:"C", Task:"Task2"},
to clarify further it should be a function that every time is run "shifts the task column" by one.
I have tried using methods such as:
ary.push(ary.shift());
however i don't know any way that i can specifically apply this to a specific key while not moving the others.

Map the array, and take the task from the previous item in the cycle using the modulo operation.
Unlike the % operator the result of using modulo with negative numbers would be a positive remainder. It's needed because in the first item (i is 0) i - 1 would be -1.
const modulo = (a, n) => ((a % n ) + n) % n
const fn = arr =>
arr.map((o, i) => ({
...o,
Task: arr[modulo((i - 1), arr.length)].Task
}))
const arr = [{"Name":"A","Task":"Task1"},{"Name":"B","Task":"Task2"},{"Name":"C","Task":"Task3"}]
const result = fn(arr)
console.log(result)

You are very close. You can decompose the array into two individual ones and shift one of them. See below function:
const arr = [
{ Name: "A", Task: "Task1" },
{ Name: "B", Task: "Task2" },
{ Name: "C", Task: "Task3" }
];
function cycle(arr) {
const names = arr.map(el => el.Name);
const tasks = arr.map(el => el.Task);
const el = tasks.pop();
tasks.unshift(el);
return names.map((letter, i) => {
return {
Name: letter,
Task: tasks[i]
};
});
}
console.log(cycle(arr));
Also, codepen for conveninence.

Approach using a Generator and your pop and shift concept
const data=[{Name:"A",Task:"Task1"},{Name:"B",Task:"Task2"},{Name:"C",Task:"Task3"}];
function* taskGen(data){
// create standalone array of the names that can be shifted
const tasks = data.map(({Task}) => Task);
while(true){
// move first to last position
tasks.push(tasks.shift());
// return new mapped array
yield data.map((o, i) => ({...o, Task: tasks[i] }))
}
}
const shifter = taskGen(data);
for (let i =0; i< 6; i++){
const next = shifter.next().value
console.log(JSON.stringify(next))
}
.as-console-wrapper {max-height: 100%!important;top:0;}

Related

Checking for "triplicates" strings in JavaScript array

trying to find the best way to check for triplicates values inside an array of strings.
I found many stackoverflow solutions for duplicates values which is not the case in here.
This is the farest i could get with solving this and I am not sure if it is the correct way:
const array = [
"peace",
"peace",
"Vrede",
"Patz",
"Salam",
"paz",
"Salam",
"Salam"
];
const findTriplicates = (param) => {
let counts = {};
for (let i = 0; i < param.length; i++) {
if (counts[param[i]]) {
counts[param[i]] += 1;
} else {
counts[param[i]] = 1;
}
}
for (let i in counts) {
if (counts[i] === 3) {
console.log(i + " exists " + counts[i] + " times.");
}
}
};
findTriplicates(array); // Salam exists 3 times.
please don't hesitate to fix my code or to post your solution.
thanks for your support in advance :)
Cheerz!
Your overall idea is good, using Hash Maps (js objects) is the best option for the task.
You can move your "=== 3" check to the first loop and have another object to save triplicates, it will be twice faster.
check this out
const findTriplicates = (param) => {
let values = [...new Set(param)];
let triples = [];
values.forEach(item=>{
let counter = 0;
param.forEach(s=>{
if(s===item) counter++;
})
if(3==counter) triples.push(item);
})
return triples;
};
There is no correct way to do things like this. You can always optimize or sacrifice performance for readability, but that is up to the developer.
I changed nothing about the functionality in findTriplicates, but the code is very different.
findTriplicates2 works a little different but is by no means superior
const array = [
"peace",
"peace",
"Vrede",
"Patz",
"Salam",
"paz",
"Salam",
"Salam"
];
const findTriplicates = (param) => {
let counts = param.reduce((acc, p) => {
acc[p] ? acc[p]++ : acc[p] = 1
return acc;
}, {})
Object.keys(counts).forEach((key) => counts[key] === 3 &&
console.log(`${key} exists 3 times.`)
);
};
findTriplicates(array); // Salam exists 3 times.
const findTriplicates2 = (param) => {
let triples = [...new Set(param)].reduce((acc, item) => {
let counter = param.reduce((acc2, s) => {
if (s === item) acc2++;
return acc2;
}, 0);
if (3 == counter) acc.push(item);
return acc;
}, [])
triples.forEach((triple) => console.log(`${triple} exists 3 times.`));
};
findTriplicates2(array); // Salam exists 3 times.
You create an object to keep count of how many times the string repeats and then iterate through each element in the array while updating the count in the object. Then you filter through that object for any values equal to 3.
const array = [
'peace',
'peace',
'Vrede',
'Patz',
'Salam',
'paz',
'Salam',
'Salam',
];
// Object to keep count of each word
const countObj = {};
// Iterate through the array to get the count for each word
array.forEach((element) => {
// Does word exist in the count object? if so -> add 1 to its count, else -> add word and count 1 to the object
countObj[element] ? (countObj[element] += 1) : (countObj[element] = 1);
});
// Filter out keys that appear exactly 3 times and print them them
const filteredArray = Object.keys(countObj).filter(
(key) => countObj[key] === 3
);
console.log(filteredArray); // Salam

How to make an average of nested array

I'm trying to make an average array of a bigger and dynamic array. Simpler looks like this:
const bigArr = [[[1,1,1], [2,2,2], [3,3,3]],[[3,3,3], [4,4,4], [7,7,7]]]
in the end, I'm expecting to get:
const averageArray = [[2,2,2], [3,3,3], [5,5,5]]
I know the best way is to triple loop over this array's, but I couldn't manage to get expected result.
averageArray[0][0] is an average of bigArr[0][0] and bigArr[1][0].
There are a few ways to do it (for loops, reduce, etc.) here I show an example with reduce:
const bigArr = [
[[1,1,1], [2,2,2], [3,3,3]],
[[3,3,3], [4,4,4], [7,7,7]],
//[[5,5,5], [6,6,6], [11,11,11]]
];
const averageArray = bigArr.reduce((aggArr, arr, i) => {
if (i == 0){
return arr.map( a => a );
}
else {
arr.forEach( (a, j) => {
a.forEach( (b, k) => {
aggArr[j][k] = ((aggArr[j][k] * i) + b) / (i + 1)
})
});
}
return aggArr;
}, []);
console.log(averageArray);
Output:
[[2,2,2], [3,3,3], [5,5,5]]
It would also work with a larger input like this:
const bigArr = [
[[1,1,1], [2,2,2], [3,3,3]],
[[3,3,3], [4,4,4], [7,7,7]],
[[5,5,5], [6,6,6], [11,11,11]]
];
We get this output:
[[3,3,3], [4,4,4], [7,7,7]]
One final example:
It would also work with a larger input with non identical sub-arrays like this (to illustrate how the averaging is occurring):
const bigArr = [
[[1,2,3], [1,2,3], [1,2,3]],
[[3,4,7], [3,4,7], [3,4,7]],
[[5,6,11], [5,6,11], [5,6,11]]
];
We get this output:
[[3,4,7], [3,4,7], [3,4,7]]

How to get "unfiltered" array items?

Let's say I have an array which I filter by calling myItems.filter(filterFunction1) and get some items from it.
Then I want to run another filtering function filterFunction2 against the remaining items which were not selected by filterFunction1.
Is that possible to get the remaining items that were left out after calling a filtering function?
You'd have to rerun the filter with an inverted predicate, which seems wasteful. You should reduce the items instead and bin them into one of two bins:
const result = arr.reduce((res, item) => {
res[predicate(item) ? 'a' : 'b'].push(item);
return res;
}, { a: [], b: [] });
predicate here is the callback you'd give to filter.
Unfortunately, there is no one-step solution based on filter. Still the solution is a simple one-liner:
Here's an example
const arr = [ 1,2,3,4,5,6,7,8 ];
const filtered = arr.filter(x=>!!(x%2))
const remaining = arr.filter(x=>!filtered.includes(x))
console.log(filtered, remaining);
You could map an array of flags and then filter by the flags values.
const cond = v => !(v % 2);
var array = [1, 2, 3, 4, 5],
flags = array.map(cond),
result1 = array.filter((_, i) => flags[i]),
result2 = array.filter((_, i) => !flags[i]);
console.log(result1);
console.log(result2);
You can achieve that using Array.reduce.
const myItems = [...];
const { result1, result2 } = myItems.reduce(
(result, item) => {
if (filterFunc1(item)) {
result.result1.push(item);
} else if (filterFunc2(item)) {
result.result2.push(item);
}
return result;
},
{ result1: [], result2: [] },
);
If you don't want to use reduce, you may want to iterate the array once and acquire the filtered and unfiltered items in a single shot, using a plain efficient for..of loop:
function filterAndDiscriminate(arr, filterCallback) {
const res = [[],[]];
for (var item of arr) {
res[~~filterCallback(item)].push(item);
}
return res;
}
const [unfiltered, filtered] = filterAndDiscriminate([1,2,3,4,5], i => i <= 3);
console.log(filtered, unfiltered);
There's a way more simple and readable way to do this:
const array1 = []
const array2 = []
itemsToFilter.forEach(item => item.condition === met ? array1.push(challenge) : array2.push(challenge))

Count the repetition of an element in an array using a function with one parameter

Good Day, I am trying to count how many times a particular element in an array appears. I tried but my code below counts only one of the array even if it appears more than once (this is not the problem). I want it to return the amount of time each element appears. For example
let arr = [1, 3, 2, 1];
this should return
{1:2} {3:1} {2:1}
My code returns 3 (as in it just doesn't count one twice)
How do i go about this?
Below is my code
function numberCount(number) {
let count = 0;
number.forEach(function (item, index) {
if (number.indexOf(item) == index) count++;
});
console.log(count);
}
While iterating over number (better to call it arr, it's an array, not a number), use an object to keep track of the number of times each number has occured so far. Then, iterate over the resulting object's entries to create the objects desired:
let arr = [1, 3, 2, 1];
function numberCount(arr) {
let count = 0;
const obj = arr.reduce((a, num) => {
a[num] = (a[num] || 0) + 1;
return a;
}, {});
return Object.entries(obj).map(([key, val]) => ({ [key]: val }));
}
console.log(numberCount(arr));
Numeric keys always come in numeric order in an object. If you want the objects in the output to come in insertion order (eg, the object with key 3 before the object with key 2), then use a Map instead of an object (map keys will be iterated over in insertion order):
let arr = [1, 3, 2, 1];
function numberCount(arr) {
let count = 0;
const map = arr.reduce((a, num) => (
a.set(num, (a.get(num) || 0) + 1)
), new Map());
return [...map.entries()]
.map(([key, val]) => ({ [key]: val }));
}
console.log(numberCount(arr));
You should filter out these numbers, then use the length:
let arr = [1, 3, 2, 1];
function itemCount(array) {
var sorted = array.sort()
var uniqueCount = sorted.filter((v, i, a) => a.indexOf(v) == i);
var count = [];
uniqueCount.forEach(item => {
var itemCount = sorted.filter(e => e == item).length;
count.push({[item]: itemCount});
});
return count;
}
console.log(itemCount(arr));
I would suggest not reinventing the wheel, and instead use lodash which already has this function. Using countBy() you will get an object you can then convert into your desired result. For example:
const arr = [1, 3, 2, 1]
const count = _.countBy(arr)
const result = Object.keys(count).map(k => ({ [k]: count[k] }))
console.log(result)
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.11/lodash.min.js"></script>

Reduce array list, to set of common paths

I am trying to reduce a list to a shorter list, of just common filesystem paths. Trying to find all the common grandparents, and put only those in the final list. Here is the goal: The goal is that we must eliminate all directories in the list for which there is a parent directory in the list.
A better way to phrase that might be:
The goal is that we must eliminate all paths in the list for which there is a parent directory of that path in the list.
Say I have this input and expected output:
const input = [
"/home/oleg/WebstormProjects/oresoftware/r2g",
"/home/oleg/WebstormProjects/oresoftware/sumanjs/suman",
"/home/oleg/WebstormProjects/oresoftware/sumanjs",
"/home/oleg/WebstormProjects/oresoftware/sumanjs/suman-types",
"/home/oleg/WebstormProjects/oresoftware/sumanjs/suman-watch"
];
const output = [
"/home/oleg/WebstormProjects/oresoftware/r2g",
"/home/oleg/WebstormProjects/oresoftware/sumanjs",
];
const getReducedList = function (input) {
return input
.sort((a, b) => (a.length - b.length))
.reduce((a, b) => {
// console.log('a:', a, 'b:', b);
const s = !a.some(v => {
return b.startsWith(v);
});
if (s) {
a.push(b);
}
return a;
}, []);
};
console.log(getReducedList(input));
that getReducedList function seems to work with our first test case, 5 is reduced to 2. But, if we add a second test case, here is where things get weird:
If I remove an item from the original list and change it to this list of 4:
const input = [
"/home/oleg/WebstormProjects/oresoftware/r2g",
"/home/oleg/WebstormProjects/oresoftware/sumanjs/suman",
"/home/oleg/WebstormProjects/oresoftware/sumanjs/suman-types",
"/home/oleg/WebstormProjects/oresoftware/sumanjs/suman-watch"
];
then I would expect to get this output (the same list of 4):
const output = [
"/home/oleg/WebstormProjects/oresoftware/r2g",
"/home/oleg/WebstormProjects/oresoftware/sumanjs/suman",
"/home/oleg/WebstormProjects/oresoftware/sumanjs/suman-types",
"/home/oleg/WebstormProjects/oresoftware/sumanjs/suman-watch"
];
the reason why I would expect/desire the same list of 4, is because no item in the list has a parent directory elsewhere in the list. But I actually get this output, a list of 2, which is incorrect:
const output = [
'/home/oleg/WebstormProjects/oresoftware/r2g',
'/home/oleg/WebstormProjects/oresoftware/sumanjs/suman'
];
does anyone know how I can fix this to get the expected result instead? An answer needs to satisfy both test cases.
To make it perfectly clear, if you add "/home/oleg" to the original list, then "/home/oleg" should be the only entry in the output.
I think you can do this with a very simple recursive function. You simply sort by length, then recursively pop the shortest, add it to the results, filter the array with that, recurse:
const input = [
"/home/oleg/WebstormProjects/oresoftware/r2g",
"/home/oleg/WebstormProjects/oresoftware/sumanjs/suman",
"/home/oleg/WebstormProjects/oresoftware/sumanjs",
"/home/oleg/WebstormProjects/oresoftware/sumanjs/suman-types",
"/home/oleg/WebstormProjects/oresoftware/sumanjs/suman-watch"
];
input.sort((a,b) => b.length - a.length)
function getPrefixes(list, res =[]) {
if (list.length < 1) return res
let next = list.pop()
res.push(next)
return getPrefixes(list.filter(u => !u.startsWith(next + '/')), res)
}
console.log(getPrefixes(input))
Looks like we only needed to change one line. Here is the original:
const getReducedList = function (input) {
return input
.sort((a, b) => (a.length - b.length))
.reduce((a, b) => {
const s = !a.some(v => {
return b.startsWith(v);
});
if (s) {
a.push(b);
}
return a;
}, []);
};
we need to change the one line to this instead:
return b.startsWith(v + '/');
You could simply use node's path module
const path = require('path');
const input = [
'/home/oleg/WebstormProjects/oresoftware/r2g',
'/home/oleg/WebstormProjects/oresoftware/sumanjs/suman',
'/home/oleg/WebstormProjects/oresoftware/sumanjs',
'/home/oleg/WebstormProjects/oresoftware/sumanjs/suman-types',
'/home/oleg/WebstormProjects/oresoftware/sumanjs/suman-watch'
];
const s = new Set();
const out = [];
input.forEach(e => {
let p = path.dirname(e);
if (! s.has(p)) {
s.add(p);
out.push(e);
}
});
console.log(out);
The forEach could obviously replaced by reduce if you want to...
If you can use sets from es6 then you can simply sort with respect to directory length and keep pushing to a set.
You could add a slash for look up and pop the last value if found in next element.
This proposal uses a sorted list by value, not by lenght of the string, because it needs a sorted list for compairing the next element.
function uniqueFolder(array) {
return array
.sort()
.reduce((a, b) => {
if (b.startsWith(a[a.length - 1] + '/')) {
a.pop();
}
a.push(b);
return a;
}, []);
}
function commonPath(array) {
return array
.sort()
.reduce((a, b) => {
if (!b.includes(a[a.length - 1])) {
a.push(b);
}
return a;
}, []);
}
const input = [ "/home/oleg/WebstormProjects/oresoftware/r2g", "/home/oleg/WebstormProjects/oresoftware/sumanjs/suman", "/home/oleg/WebstormProjects/oresoftware/sumanjs", "/home/oleg/WebstormProjects/oresoftware/sumanjs/suman-types", "/home/oleg/WebstormProjects/oresoftware/sumanjs/suman-watch"];
console.log(uniqueFolder(input));
console.log(commonPath(input));
.as-console-wrapper { max-height: 100% !important; top: 0; }
You need to avoid matches when the next value does not have its last forward slash following the previous value.
const input = [
"/home/oleg/WebstormProjects/oresoftware/r2g",
"/home/oleg/WebstormProjects/oresoftware/sumanjs/suman",
"/home/oleg/WebstormProjects/oresoftware/sumanjs",
"/home/oleg/WebstormProjects/oresoftware/sumanjs/suman-types",
"/home/oleg/WebstormProjects/oresoftware/sumanjs/suman-watch"
];
const getReducedList = (set => input => input.filter(b =>
!set.has(b.substr(0, b.lastIndexOf("/")))))(new Set(input));
console.log(getReducedList(input));
The logic is that it puts all paths in a set, and then checks for each path whether its parent is in the set. A path's parent is the path that has all characters up to, and excluding, the final /.
Removing the second entry from the input will not change the output. suman is treated the same way as suman-types and suman-watch.
Here is a more verbose, ES5 version (replacing the Set with a plain object):
const input = [
"/home/oleg/WebstormProjects/oresoftware/r2g",
"/home/oleg/WebstormProjects/oresoftware/sumanjs/suman",
"/home/oleg/WebstormProjects/oresoftware/sumanjs",
"/home/oleg/WebstormProjects/oresoftware/sumanjs/suman-types",
"/home/oleg/WebstormProjects/oresoftware/sumanjs/suman-watch"
];
function getReducedList(input) {
var hash = {};
input.forEach(function(path) {
hash[path] = true;
});
return input.filter(function (b) {
return !hash[b.substr(0, b.lastIndexOf("/"))];
}, []);
}
console.log(getReducedList(input));
It works without sort though has other drawbacks.
Edit: Had to add sort to work properly
const input = [
"/home/oleg/WebstormProjects/oresoftware/r2g",
"/home/oleg/WebstormProjects/oresoftware/sumanjs/suman",
"/home/oleg/WebstormProjects/oresoftware/sumanjs",
"/home/oleg/WebstormProjects/oresoftware/sumanjs/suman-types",
"/home/oleg/WebstormProjects/oresoftware/sumanjs/suman-watch"
];
const input2 = [
"/home/oleg/WebstormProjects/oresoftware",
"/home/oleg/WebstormProjects/oresoftware/r2g",
"/home/oleg/WebstormProjects/oresoftware/sumanjs/suman",
"/home/oleg/WebstormProjects/oresoftware/sumanjs/suman-types",
"/home/oleg/WebstormProjects/oresoftware/sumanjs/suman-watch"
];
function getParents(input) {
return input.sort((a, b) => a.length - b.length)
.reduce((acc, a) => {
const index = acc.findIndex(b => b.startsWith(a) || a.startsWith(b));
const slashDiff = index > 0 ? a.split('/').length === acc[index].split('/').length : false;
if (index === -1 || slashDiff) {
return [...acc, a];
}
acc.splice(index, 1, a.length < acc[index].length ? a : acc[index])
return acc;
}, []);
}
console.log(getParents(input));
console.log(getParents(input2));
const input = [
"/home/oleg/WebstormProjects/oresoftware/r2g",
"/home/oleg/WebstormProjects/oresoftware/sumanjs/suman",
"/home/oleg/WebstormProjects/oresoftware/sumanjs",
"/home/oleg/WebstormProjects/oresoftware/sumanjs/suman-types",
"/home/oleg/WebstormProjects/oresoftware/sumanjs/suman-watch",
"/home/oleg/WebstormProjects/oresoftware/sumanjs/suman-types-blabla",
];
let ouput = input.sort().reduce((a, c)=> {
let d = true;
for (let j =0; j< a.length; j++){
if(c.startsWith(a[j]+'/')) {
d=false;
break;}
}
if (d) a.push(c);
return a;
},[]);
console.log(ouput);

Categories