Sort array of arrays with documents using lodash - javascript

I've an array with arrays of documents in it, can some help me on how to sort those arrays with multiple documents inside the array. The user can chose of the type to be searched on. If they can choose 'A' sort all the array items by value associated with type 'A', similarly for the other types. What is the best way to do this. Lodash solution would be highly beneficial. Those values can be integers/strings/guids/datetime fields.
Type is always a string, in any case I don't think it does really matter.
[
[{type:A, value:10},{type:B, value:20},{type:C, value:15},{type:D, value:20}],
[{type:A, value:5},{type:B, value:10},{type:C, value:35},{type:D, value:40}],
[{type:A, value:30},{type:B, value:30},{type:C, value:25},{type:D, value:30}],
[{type:A, value:20},{type:B, value:50},{type:C, value:55},{type:D, value:10}]
]
Desired out put if sorted by type 'A'
[[{type:A, value:5},{type:B, value:10},{type:C, value:35},{type:D, value:40}],
[{type:A, value:10},{type:B, value:20},{type:C, value:15},{type:D, value:20}],
[{type:A, value:20},{type:B, value:50},{type:C, value:55},{type:D, value:10}],
[{type:A, value:30},{type:B, value:30},{type:C, value:25},{type:D, value:30}]]

Here's a lodash solution that uses sortBy for sorting and find to get the corresponding values with a specific type.
var result = _.sortBy(data, function(item) {
return _.find(item, { type: 'A' }).value;
});
var data = [
[{
type: 'A',
value: 10
}, {
type: 'B',
value: 20
}, {
type: 'C',
value: 15
}, {
type: 'D',
value: 20
}],
[{
type: 'A',
value: 5
}, {
type: 'B',
value: 10
}, {
type: 'C',
value: 35
}, {
type: 'D',
value: 40
}],
[{
type: 'A',
value: 30
}, {
type: 'B',
value: 30
}, {
type: 'C',
value: 25
}, {
type: 'D',
value: 30
}],
[{
type: 'A',
value: 20
}, {
type: 'B',
value: 50
}, {
type: 'C',
value: 55
}, {
type: 'D',
value: 10
}]
];
var result = _.sortBy(data, function(item) {
return _.find(item, { type: 'A' }).value;
});
console.log(result);
body > div { min-height: 100%; top: 0; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>

In plain Javascript, you could look for the given type and sort by the corresponding value.
function sort(data, type) {
function getValue(array) {
var value = -Number.MAX_VALUE;
array.some(function (o) {
if (o.type === type) {
value = o.value;
return true;
}
});
return value;
}
data.sort(function (a, b) {
var aa = getValue(a),
bb = getValue(b);
return isFinite(aa) && isFinite(bb) ? aa - bb : aa.localeCompare(bb);
});
}
var data = [[{ type: 'A', value: 10 }, { type: 'B', value: 20 }, { type: 'C', value: 15 }, { type: 'D', value: 'b' }], [{ type: 'A', value: 5 }, { type: 'B', value: 10 }, { type: 'C', value: 35 }, { type: 'D', value: 'a' }], [{ type: 'A', value: 30 }, { type: 'B', value: 30 }, { type: 'C', value: 25 }, { type: 'D', value: 'd' }], [{ type: 'A', value: 20 }, { type: 'B', value: 50 }, { type: 'C', value: 55 }, { type: 'D', value: 'c' }]];
sort(data, 'A');
console.log(data);
sort(data, 'D');
console.log(data);
.as-console-wrapper { max-height: 100% !important; top: 0; }

You can dynamically create a relationship for sorting using closures.
function sortBy(key){
return function(a,b){
return a[key] - b[key];
}
}
var index = {'A':0, 'B': 1, … };
var userPickedType = 'A';
yourarray.sort(sortBy(index[userPickedType]));

Related

How to sort objects of an array by same order of objects of another array where need to compare multiple properties? [duplicate]

This question already has answers here:
Javascript - sort array based on another array
(26 answers)
Closed 1 year ago.
Updated requirement on 04/06/2021:
I have two array of objects, arrX and arrY. Need to sort the objects of 'arrY' same as the order of 'arrX'. What is the shortest or best way?
Note: Objects which has type other than types of arrX should got to the bottom ie. "type: 'X'" here.
const arrX = [
{type: 'C', category: 'CAT2'},
{type: 'A', category: 'CAT1},
{type: 'B', category: 'CAT3'},
]
const arrY = [
{type: 'B', category: 'CAT3'},
{type: 'A', category: 'CAT1'},
{type: 'C', category: 'CAT2'},
{type: 'B', category: 'CAT3'},
{type: 'A', category: 'CAT1'},
{type: 'X', category: 'CAT4'},
{type: 'B', category: 'CAT2'},
{type: 'X', category: 'CAT4'},
{type: 'X', category: 'CAT5'},
{type: 'A', category: 'CAT1'},
{type: 'C', category: 'CAT2'},
]
Should Be sorted as:
const arrX = [
{type: 'C', category: 'CAT2'},
{type: 'C', category: 'CAT2'},
{type: 'A', category: 'CAT1'},
{type: 'A', category: 'CAT1'},
{type: 'A', category: 'CAT1'},
{type: 'B', category: 'CAT3'},
{type: 'B', category: 'CAT3'},
{type: 'X', category: 'CAT4'},
{type: 'B', category: 'CAT2'},
{type: 'X', category: 'CAT4'},
{type: 'X', category: 'CAT5'},
]
You could take an object for the order with a default value for unknown types.
const
arrX = [{ type: 'C' }, { type: 'A' }, { type: 'B' }],
arrY = [{ type: 'B' }, { type: 'A' }, { type: 'C' }, { type: 'B' }, { type: 'A' }, { type: 'X' }, { type: 'B' }, { type: 'X' }, { type: 'X' }, { type: 'A' }, { type: 'C' }],
order = Object.fromEntries(arrX.map(({ type }, i) => [type, i + 1]));
order.default = Number.MAX_VALUE;
arrY.sort((a, b) => (order[a.type] || order.default) - (order[b.type] || order.default));
console.log(arrY);
.as-console-wrapper { max-height: 100% !important; top: 0; }
I would add something that would be comparable in arrX. In the following, I have added an index to arrY to be more distinguishable.
const addIndex = (list) => list.map((item, index) => ({ ...item, index }));
const targetIndex = (targets, element) => {
const found = targets.find((item) => item.type === element.type);
return found ? found.index : null;
};
const sortByType = (target, source) => {
const targetWithId = addIndex(target);
const sourceWithId = addIndex(source);
sourceWithId.sort((a, b) => {
const indexA = targetIndex(targetWithId, a);
const indexB = targetIndex(targetWithId, b);
if (indexA === indexB) {
return 0;
}
if (indexA === null) {
return 1;
}
if (indexB === null) {
return -1;
}
return indexA - indexB;
});
return sourceWithId;
};
result = sortByType(arrX, arrY);
which would yield:
[
{ type: 'C', index: 2 },
{ type: 'C', index: 10 },
{ type: 'A', index: 1 },
{ type: 'A', index: 4 },
{ type: 'A', index: 9 },
{ type: 'B', index: 0 },
{ type: 'B', index: 3 },
{ type: 'B', index: 6 },
{ type: 'X', index: 5 },
{ type: 'X', index: 7 },
{ type: 'X', index: 8 }
]

Group consecutive similar array of elements

Here is my simple array of objects
const array = [
{ name: 'a', val: '1234' },
{ name: 'b', val: '5678' },
{ name: 'c', val: '91011' },
{ name: 'c', val: '123536' },
{ name: 'e', val: '5248478' },
{ name: 'c', val: '5455' },
{ name: 'a', val: '548566' },
{ name: 'a', val: '54555' }
]
I need to group consecutive name elements and push the corresponding val. So the expected output should be
const array = [
{ name: 'a', vals: '1234' },
{ name: 'b', vals: '5678' },
{ name: 'c', vals: ['91011', '123536'] },
{ name: 'e', vals: '5248478' },
{ name: 'c', vals: '5455' },
{ name: 'a', vals: ['548566', '54555'] }
]
I tried it But could not get over it. Please help
const output = []
const result = array.reduce((a, c) => {
if (a.name === c.name) {
output.push(a);
}
}, []);
You were actually quite close:
const output = [];
array.reduce((a, c) => {
if (a.name === c.name) { // current element equals previous element, lets merge
a.values.push(c.val);
} else output.push(a = { name: c.name, values: [c.val] ); // otherwise add new entry
return a; // the current element is the next previous
} , {}); // start with an empty a, so that c always gets pushed
Note that it makes little sense to store numbers as string though.
You can reduce the array like this. Compare the current name with previous item's name. If they are not the same, add a new item to the accumulator. If they are the same, then use concat the merge val with the last item in accumulator. concat is used because vals could either be a string or an array.
const array = [
{ name: 'a', val: '1234' },
{ name: 'b', val: '5678' },
{ name: 'c', val: '91011' },
{ name: 'c', val: '123536' },
{ name: 'e', val: '5248478' },
{ name: 'c', val: '5455' },
{ name: 'a', val: '548566' },
{ name: 'a', val: '54555' }
]
const merged = array.reduce((acc, { name, val }, i, arr) => {
// check if name is same as the previous name
if (arr[i - 1] && arr[i - 1].name === name) {
const prev = acc[acc.length - 1]; // last item in the accumulator
prev.vals = [].concat(prev.vals, val)
} else
acc.push({ name, vals: val })
return acc
}, [])
console.log(merged)

Unflatten array to Tree without parent id but with level

I'm a bit stuck with something implying recursion. I am receiving data
from an API. It looks like this:
const input = [
{ id: 'a', level: 0 },
{ id: 'b', level: 1 },
{ id: 'c', level: 1 },
{ id: 'd', level: 2 },
{ id: 'e', level: 1 },
{ id: 'f', level: 0 },
];
and I need something like
const out = [
{ id: 'a', nodes: [
{ id: 'b', nodes: [] },
{ id: 'c', nodes: [
{ id: 'd', nodes: [] },
] },
{ id: 'e', nodes: [] },
] },
{ id: 'f', nodes: [] },
];
How would you achieve that in an elegant way such as out = f(input) ?
I feel we can do a recursive nest method through a reduce but I did not manage to get it right :)
Thanks in advance!
You could use a helper array for the levels with the latest array/nodes property from the object.
const
input = [{ id: 'a', level: 0 }, { id: 'b', level: 1 }, { id: 'c', level: 1 }, { id: 'd', level: 2 }, { id: 'e', level: 1 }, { id: 'f', level: 0 }],
result = [],
levels = [result];
input.forEach(({ id, level }) =>
levels[level].push({ id, nodes: levels[level + 1] = [] })
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You may try out like,
function makeObject(id){
return { id: id, nodes:[] };
}
function addObjectToNodes(array, id, node){
array.map(a => {
if(a.id === id)
a.nodes.push(node);
});
}
const nodes = [];
nodes.push(makeObject('a'));
nodes.push(makeObject('f'));
addObjectToNodes(nodes, 'a', makeObject('b'));
addObjectToNodes(nodes, 'a', makeObject('c'));
addObjectToNodes(nodes, 'a', makeObject('d'));
addObjectToNodes(nodes, 'a', makeObject('e'));
console.log(nodes);

transform array with map and reduce

I have several arrays like that I'm getting from a web service:
const a = [ { label: 'A', value: 100 }, { label: 'B', value: 200 } ];
const b = [ { label: 'A', value: 50 }, { label: 'B', value: 300 } ];
const c = [ { label: 'A', value: 20 }, { label: 'B', value: 130 } ];
const d = [ { label: 'A', value: 10 }, { label: 'B', value: 25 } ];
and I would like to have something like that (in a React state):
[
{ label: 'A', a: 100, b: 50, c: 20, d: 10 },
{ label: 'B', a: 200, b: 300, c: 130, d: 25 }
]
using modern JavaScript, I guess with map and reduce
EDIT:
I was not clear at all at first. I want to update my state when I get new data.
if my current state is:
[
{ label: 'A', a: 100, b: 50, c: 20, d: 10 },
{ label: 'B', a: 200, b: 300, c: 130, d: 25 }
]
and I'm getting
{ title: "a", values: [ { label: 'A', value: 12 }, { label: 'B', value: 13 } ] };
I want to update my state to
[
{ label: 'A', a: 12, b: 50, c: 20, d: 10 },
{ label: 'B', a: 13, b: 300, c: 130, d: 25 }
]
my best attempts was:
myFunction().then(data => {
const {chartData} = this.state;
chartData[data.title] = Object.keys(data.values).map(key => ({
label: chartData[data.title] || data.values[key].label,
[data.title]: data.values[key].value,
...chartData[data.title]
});
this.setState({chartData});
})
You could iterate the given state array, look for an item with the wanted label and update the properties.
function update(state, { title, values }) {
return values.reduce((r, { label, value }) => {
var temp = r.find(o => o.label === label);
if (!temp) r.push(temp = { label });
Object.assign(temp, { [title]: value });
return r;
}, state);
}
var state = [{ label: 'A', a: 100, b: 50, c: 20, d: 10 }, { label: 'B', a: 200, b: 300, c: 130, d: 25 }],
data = { title: "a", values: [{ label: 'A', value: 12 }, { label: 'B', value: 13 }] };
update(state, data);
console.log(state);
.as-console-wrapper { max-height: 100% !important; top: 0; }
const glue = (label, ...vars) => ([].concat(vars).filter(i => i.label === label).reduce((agg, i) => ({ ...agg, ...i }), {}));
where
glue('A', [a,b,c,d]);
glue('B', [a,b,c,d]);
// .. and so on
`
Create an object with all the arrays using Shorthand property names (The key names are required to create the properties in the output. You could add more arrays to this object). Then reduce the entries returned by Object.entries()
const a = [ { label: 'A', value: 100 }, { label: 'B', value: 200 } ];
const b = [ { label: 'A', value: 50 }, { label: 'B', value: 300 } ];
const c = [ { label: 'A', value: 20 }, { label: 'B', value: 130 } ];
const d = [ { label: 'A', value: 10 }, { label: 'B', value: 25 } ];
const input = { a, b, c, d };
const merged = Object.entries(input).reduce((r, [key, arr]) => {
arr.forEach(({label, value}) => {
r[label] = r[label] || { label };
r[label][key] = value;
})
return r;
}, {})
console.log(Object.values(merged))
Here idea is
Combine all the values in one array
Use a array to keep alphabets
Loop over combined array and for each element in combined array add vales to labels and alphabet based as per index of combined array
const a = [{ label: 'A', value: 100 }, { label: 'B', value: 200 }];
const b = [{ label: 'A', value: 50 }, { label: 'B', value: 300 }];
const c = [{ label: 'A', value: 20 }, { label: 'B', value: 130 }];
const d = [{ label: 'A', value: 10 }, { label: 'B', value: 25 }];
let alpha = [...'abcdefghijklmnopqrstuvwxyz']
let combine = [a,b,c,d]
let op = combine.reduce((op,inp,i) => {
inp.forEach(({label,value}) => {
op[label] = op[label] || {label}
op[label][alpha[i]] = value
})
return op
},{})
console.log(Object.values(op))

How to find avg in crossfilter group in most efficient way

I want to find the average in the data by grouping similar things in the data.
For example I have data in an object like this:
var data = [
{ key: 'A', value: 1 },
{ key: 'A', value: 6 },
{ key: 'B', value: 8 },
{ key: 'B', value: 9 },
{ key: 'A', value: 7 },
{ key: 'A', value: 6 },
{ key: 'C', value: 8 },
{ key: 'A', value: 9 }
];
How do I find average key-wise without recalculating the average at each step (because I don't know the count at once)?
If I want it in one iteration what is the best way?
You could use a hash table for the result object and for additional information, like sum and count.
In every loop update the result set with new values and the average.
var data = [{ key: 'A', value: 1 }, { key: 'A', value: 6 }, { key: 'B', value: 8 }, { key: 'B', value: 9 }, { key: 'A', value: 7 }, { key: 'A', value: 6 }, { key: 'C', value: 8 }, { key: 'A', value: 9 }],
hash = Object.create(null),
result = [];
data.forEach(function (o) {
if (!hash[o.key]) {
hash[o.key] = { _: { key: o.key, avg: 0 }, sum: 0, count: 0 };
result.push(hash[o.key]._);
}
hash[o.key].sum += o.value;
hash[o.key].count++;
hash[o.key]._.avg = hash[o.key].sum / hash[o.key].count;
});
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Categories

Resources