Split element into array with reduce() - javascript

I have some edges in a graph, which I get with:
const edges = getEdges();
Each edge has an array of labels, which I get with:
const edges = getEdges().map(function (edge, i) {
return (i + 1) + '-' + getLabels(edge).sort().join('-');
});
So if I have 2 edges, each with two labels, I get an array
[
'1-label1-label2',
'2-label1-label2'
]
But what I want is
[
'1-label1',
'2-label2',
'3-label1',
'4-label2'
]
So I guess I need to use Array.reduce().

Using Array#reduce method do something like this.
const edges = getEdges().reduce(function(arr, edge) {
return arr.concat(
getLabels(edge)
.sort()
.map(function(v, i) {
return (arr.length + i) + '-' + v;
})
);
}, []);
Using Array#push method instead of Array#concat method.
const edges = getEdges().reduce(function(arr, edge) {
[].push.apply(arr, getLabels(edge)
.sort()
.map(function(v, i) {
return (arr.length + i) + '-' + v;
})
);
return arr;
}, []);

It doesn't seem to me that either map or reduce is what you actually want, just a nested pair of forEach (although we could shoe-horn a reduce in if we wanted):
const edges = [];
getEdges().forEach(edge => {
getLabels(edge).sort().forEach(label => {
edges.push(`${(edges.length + 1)}-${label}`);
});
});
Live Example:
const getEdges = () =>
[
{labels: ['label2', 'label1']},
{labels: ['label1', 'label2']}
];
const getLabels = edge => edge.labels;
const edges = [];
getEdges().forEach(edge => {
getLabels(edge).sort().forEach(label => {
edges.push(`${(edges.length + 1)}-${label}`);
});
});
console.log(edges);

You can first map the result of getEdges to getLabels, and then flatten the resulting array with Array.prototype.concat and the spread syntax.
const getEdges = () => ['a', 'b'];
const getLabels = () => ['label1', 'label2'];
const edges = [].concat(...getEdges().map(getLabels))
.map((label, i) => `${i + 1}-label`);
console.log(edges);

You can use forEach as below instead of map or reduce.
Working snippet: (ES6)
const getEdges = () => [
'1-label1-label2',
'2-label1-label2'
];
const getLabels = (edge) => edge.split('-').sort();
let edges = [];
getEdges().forEach((edge) => {
getLabels(edge).forEach((label) => {
if(label.indexOf('label') > -1) {
edges.push(edges.length + 1 + '-' + label);
}
});
});
console.log(edges);
Working snippet: (ES5)
function getEdges() {
return [
'1-label1-label2',
'2-label1-label2'
];
}
function getLabels(edge) {
return edge.split('-').sort();
}
var edges = [];
getEdges().forEach(function (edge) {
getLabels(edge).forEach(function (label) {
if(label.indexOf('label') > -1) {
edges.push(edges.length + 1 + '-' + label);
}
});
});
console.log(edges);

You could use a reduce operation on the edges, which takes each edge and adds to a newly constructed array.
Using a forEach on the labels of each edge results in 1..n items, that will be added to this new array. Using the new array (result)'s current length can neatly solve the problem of naming the result item as desired (i.e. from 1 to N).
const res = getEdges().reduce(result, edge => {
const labels = getLabels(edge).sort();
labels.forEach(label => result.push(`${result.length + 1}-${label}`));
return result;
}, []);
NOTE: this solution uses reduce and ES6 features, which is according to mentioned tags.

Related

Divide S3 prefix to list of object in Javascript

Example, I have a path(prefix) like this: A/B/C/
I want to get bellow list:
[{name:"A",path:"A/"},
{name:"B",path:"A/B/",
{name:"C",path:"A/B/C/"
]
I can split the path to a array, then loop the array to build the new list of object.
But in my mind, I just know there should be a simple and smarter way to achieve this by using reducer, but just stuck here.
You're right that you could use a reducer. Something like this:
const str = "A/B/C/"
const arr = str.split("/").filter(Boolean).reduce((acc, name) => {
const path = [...acc.map(o => o.name), name].join("/") + "/"
return [...acc, { name, path }]
}, [])
console.log(arr)
You can solve this with map, but maybe not as cleanly as you were anticipating:
const result = 'A/B/C'
.split('/')
.filter(x => x)
.map((name, i, arr) => {
const prev = arr[i - 1];
return prev
? { name, path: `${prev.name}${name}/` }
: { name, path: `${name}/` };
});
My odd solution, but I think sdgluck's is the cleanest answer.
arr = "A/B/C/"
.split("/")
.filter(e => e)
.map((e, i, a) => {
a2 = a.filter((el, ix) => {
if (ix <= i) return el;
});
return {[e] : a2.join("/")};
});

Javascript map, reduce not working when implemented within object method

Based on the answer from this question I implemented the map reduce code within an object method.
this.displayValueGraph = async () => {
let scaleData = [];
this.positions.forEach(async (pos, i) => {
scaleData[i] = [];
let gdata = await pos.graphData;
gdata.option.forEach((d) => {
scaleData[i].push(d.map((x) => x * pos.size));
});
});
let out;
if (scaleData.length == 1) {
out = scaleData[0];
} else {
out = scaleData.reduce((a, b) => b.map((x, j) => x.map((v, k) => a[j][k] + v)));
}
};
The code by itself works fine. I have taken the input data (above scaleData) and run it through the map reduce function and the output is as expected. But if I include it as part of this method it does nothing. It doesn't throw any errors, it simply returns an empty array.
I have tried adding an empty array as an "initial value", but it doesn't help.
The root cause of the problem appears to have been the first forEach loop, where I included an await. I replaced the forEach with for in and it solved the problem.
this.displayValueGraph = async () => {
let scaleData = [];
for (const i in this.positions) {
const pos = this.positions[i];
scaleData[i] = [];
let gdata = await pos.graphData;
gdata.option.forEach((d) => {
scaleData[i].push(d.map((x) => x * pos.size));
});
}
let out;
if (scaleData.length == 1) {
out = scaleData[0];
} else {
out = scaleData.reduce((a, b) => b.map((x, j) => x.map((v, k) => a[j][k] + v)));
}
};

How to filter the matched element at top index in an array in JS

I have a list of array as follows:
var arr = ['Ashwin Rathod','Basker Babu','Cherry Spanish','Dravid Mukund'];
I have a search box and If I search for 'Mukund',then I should show all the results provided the string which contains 'Mukund' should come at top but in my case all the results are coming in alphabetical order(from backend).I did that using array.filter but I want to do it in any other best way.Can anyone please suggest me help.Thanks.
This is what I tried,
function filterResults() {
const matchedJobList = [],
unMatchedJobList = [];
arr.filter(function(job) {
if (job.toUpperCase().includes(mySearchedString.toUpperCase())) {
matchedJobList.push(job);
} else {
unmatchedJobList.push(job);
}
});
return [...matchedJobList, ...unmatchedJobList];
}
result:
['Dravid Mukund','Ashwin Rathod','Basker Babu','Cherry Spanish'];
I got the result as expected but hoping to get the best method to do it.
An alternative could be the function reduce + unshift in order to add the target strings at the first indexes of the array.
let arr = ['Ashwin Rathod','Basker Babu','Cherry Spanish','Dravid Mukund'],
target = 'Mukund',
result = arr.reduce((a, c) => {
if (c.toUpperCase().includes(target.toUpperCase())) a.unshift(c);
else a.push(c);
return a;
}, []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
If you don't mind the potential change of unmatched items order then you can go for sort function:
function filterResults() {
const sorderArray = [...arr];
sorderArray.sort(item => item.toUpperCase().indexOf(mySearchedString.toUpperCase()) !== -1 ? -1 : 1);
return sorderArray;
}
Sort function works directly on the array so I'm creating a copy of the arr to prevent it's mutation.
That is not filtering, that is sorting.
const arr = ['Ashwin Rathod', 'Basker Babu', 'Cherry Spanish', 'Dravid Mukund'];
const mySearchedString = 'Mukund';
function filterResults() {
const clonedResults = [...arr];
return clonedResults.sort((job1, job2) => {
const job1match = job1.toUpperCase().includes(mySearchedString.toUpperCase());
const job2match = job2.toUpperCase().includes(mySearchedString.toUpperCase());
if (job2match && !job1match) return 1;
return 0;
});
}
console.log(filterResults());
We can move founded element to the top:
let arr = ['Ashwin Rathod','Basker Babu','Cherry Spanish','Dravid Mukund'],
search = 'Mukund';
arr.forEach((element, ind) => {
if (element.indexOf(search))
arr.unshift(arr.splice(ind, 1)[0]);
});
console.log(arr);
Using splice()
const arr = ['Ashwin Rathod', 'Basker Babu', 'Cherry Spanish', 'Dravid Mukund']
const search = 'Mukund'
const index = arr.findIndex(e => e.includes(search))
console.log([...arr.splice(index, 1), ...arr]);
You should be using a sort for this, not a filter. Here is an example using a sort method.
const getMatchingLetters = (word, search) => {
return word.toLowerCase().split("").filter(l => {
return search.toLowerCase().includes(l);
})
}
const sortBySearch = (arr, search) => {
return arr.sort((a, b) => {
return getMatchingLetters(a, search).length - getMatchingLetters(b, search).length
}).reverse();
}
const myArray = ['Ashwin Rathod','Basker Babu','Cherry Spanish','Dravid Mukund'];
const searchTerm = "Mukund";
console.log(sortBySearch(myArray, searchTerm));

I need to calculate the sum of columns in an array with objects

I have a two-dimensional array, the cells are an object {id, amount}, you need to add a sum of columns, while using only methods. What I did -
let matrix = [
[{id:1, amount:11},{id:2, amount:22},{id:3, amount:33}],
[{id:4, amount:44},{id:5, amount:55},{id:6, amount:66}],
[{id:7, amount:77},{id:8, amount:88},{id:9, amount:99}],
[{id:10, amount:100},{id:11, amount:111},{id:12, amount:112}],
];
let c = matrix.reduce((acc, cur)=> {
return acc.map((item, index)=> {
return item + cur[index].amount;
})
});
console.log(c);
Why not using flatMap then reduce?
let matrix = [
[{id:1, amount:11},{id:2, amount:22},{id:3, amount:33}],
[{id:4, amount:44},{id:5, amount:55},{id:6, amount:66}],
[{id:7, amount:77},{id:8, amount:88},{id:9, amount:99}],
[{id:10, amount:100},{id:11, amount:111},{id:12, amount:112}],
];
let result = matrix.flatMap(it => it).reduce((acc, item) => acc + item.amount, 0);
console.log(result)
EDIT
After getting what you actually wanted to do, here is a complement (feel free not to read above).
const matrix = [
[{id:1, amount:11},{id:2, amount:22},{id:3, amount:33}],
[{id:4, amount:44},{id:5, amount:55},{id:6, amount:66}],
[{id:7, amount:77},{id:8, amount:88},{id:9, amount:99}],
[{id:10, amount:100},{id:11, amount:111},{id:12, amount:112}],
];
let result = matrix
.flatMap(it => it) //This flattens the items.
.reduce((acc, item, i) => (acc[i%matrix[0].length] += item.amount, acc), //This computes the sum based on the indices and the matrix width.
Array.apply(0, Array(matrix[0].length)).map(_ => 0)); //This inits the result array with zeros.
console.log(result)
You can create an array of length equal to width of array. Then use nested forEach to add the values to corresponding element.
let matrix = [
[{id:1, amount:11},{id:2, amount:22},{id:3, amount:33}],
[{id:4, amount:44},{id:5, amount:55},{id:6, amount:66}],
[{id:7, amount:77},{id:8, amount:88},{id:9, amount:99}],
[{id:10, amount:100},{id:11, amount:111},{id:12, amount:112}],
];
let res = Array(matrix[0].length).fill(0)
matrix.forEach(x => {
x.forEach((y,i) => {
res[i] += y.amount;
})
})
console.log(res)
So decided, if there are better solutions, I will be glad to see
getColSumMatrix = matrix =>
matrix.reduce((acc, cur) =>
acc.map((value, i) =>
typeof value == "object"
? value.amount + cur[i].amount
: value + cur[i].amount
)
);
const summ = matrix.reduce((acc, table) => {
return (
acc +
table.reduce((acc, { amount }) => {
return acc + amount;
}, 0)
);
}, 0);

List traversal with two adjacent elements in a Functional way

I'm trying to implement functional version of the below code
const adjacent = (list) => {
let results = [];
for (let idx = 0; idx < list.length - 1; idx++) {
const computedRes = someComplexFn(list[idx], list[idx + 1]);
results.push(computedRes );
}
return results;
}
i have come with the following version
const locations = [1,2,3,4,5];
const calcRatioFn = (x, y) => x+y;
const adjacentMap = (list, result=[]) => {
if(R.length(list) < 2) {
return result;
}
const f1 = R.head(list);
const f2 = R.tail(list);
result.push(calcRatioFn(f1 ,R.head(f2)));
return adjacentMap(R.tail(list), result);
}
const results = adjacentMap(locations);
console.log(results);
Are any any other simple solution to the above?
Can we avoid the default result value parameter and if condition check from the above function?
JSBin Link
http://jsbin.com/veyihepulu/1/edit?html,js,console
One approach would be to create a sliding window of adjacent elements using R.aperture. Then for a bit of extra sugar someComplexFn can be wrapped with R.apply to convert the binary function into one that accepts an array of two elements.
Your example would then look something like:
const adjacentMap = R.pipe(R.aperture(2), (R.map(R.apply(someComplexFn))))
Another approach would be to use converge on the array without the last element and the array without the first element.
let locations = [1,2,3,4,5];
const calcRatio = (x, y) => x+y;
// adjacentMap :: Array -> Array
const adjacentMap = R.converge(
R.zipWith(calcRatio),
[ R.init, R.tail]
);
// saveAdjacentMap :: Array -> Array
const saveAdjacentMap = R.cond([
[R.compose(R.lt(1), R.length), adjacentMap ],
[R.T, R.identity]
]);
console.log(saveAdjacentMap(locations));
Your JSBin uses Ramda 0.8.0. Things have changed in the current version 0.24.1.
The following code maybe what you require or can be adapted for your required solution.
const fn = (acc, c, i, a) => {
return !(a[i + 1])
? acc
: acc.concat(c + a[i + 1])
}
const _adjacentMap = (fn, list) => {
return list.reduce(fn, [])
}
const locations = [1,2,3,4,5]
const result = _adjacentMap(fn, locations)
console.log(result)
// => [ 3, 5, 7, 9 ]

Categories

Resources