Dynamic Sorting Loop - javascript

I am trying to write a function which would loop through a list of positions in an array(Position Array) and for each position create an array in the Position Array. And then loop through another array with candidates with different positions and sort them into the arrays with the same positions within the Position Array.
This is what I have been able to do, I want to be able to make it dynamic, but do not know how to go about it;
Here's my code:
let positionArr = ["Chairman", "Secretary", "Organiser",
"Finacial Secretary" ];
let varPositions = [];
let candidates = [
{
name: "Kwesi",
position: "Finacial Secretary",
},
{
name: "Kofi",
position: "Chairman",
},
{
name: "Ama",
position: "Secretary",
},
{
name: "Kwame",
position: "Finacial Secretary",
},
{
name: "Juliet",
position: "Organiser",
},
{
name: "Jese",
position: "Chairman",
},
];
const sort = () => {
let position;
positionArr.forEach((element) => {
element = new Array();
varPositions.push(element);
});
candidates.forEach((elementPos) => {
position = elementPos.position;
positionArr.forEach((element) => {
if (element === position) {
if (position === positionArr[0]) {
varPositions[0].push(elementPos);
} else if (position === positionArr[1]) {
varPositions[1].push(elementPos);
} else if (position === positionArr[2]) {
varPositions[2].push(elementPos);
} else if (position === positionArr[3]) {
varPositions[3].push(elementPos);
}
}
});
});
console.log(varPositions);
};
sort();
Thanks in Advance :)

If all you're trying to do is sort your candidates based on the positions array, it is much much simpler than you have there. You simply sort by the index in the array
const result = candidates.sort((a,b) =>
positionArr.indexOf(a.position)
- positionArr.indexOf(b.position)
)
Live example:
let positionArr = ["Chairman", "Secretary", "Organiser",
"Finacial Secretary" ];
let candidates = [
{
name: "Kwesi",
position: "Finacial Secretary",
},
{
name: "Kofi",
position: "Chairman",
},
{
name: "Ama",
position: "Secretary",
},
{
name: "Kwame",
position: "Finacial Secretary",
},
{
name: "Juliet",
position: "Organiser",
},
{
name: "Jese",
position: "Chairman",
},
];
const result = candidates.sort((a,b) =>
positionArr.indexOf(a.position)
- positionArr.indexOf(b.position)
)
console.log(result);

Using a combination of map() and filter(), you could just do the following to achieve the same result:
const result = positions.map(
position => candidates.filter(candidate => candidate.position === position)
);
Complete snippet:
const positions = ["Chairman", "Secretary", "Organiser", "Finacial Secretary"];
const candidates = [{
name: "Kwesi",
position: "Finacial Secretary",
}, {
name: "Kofi",
position: "Chairman",
}, {
name: "Ama",
position: "Secretary",
}, {
name: "Kwame",
position: "Finacial Secretary",
}, {
name: "Juliet",
position: "Organiser",
}, {
name: "Jese",
position: "Chairman",
}];
const result = positions.map(
position => candidates.filter(candidate => candidate.position === position)
);
console.log(result);

You could take a grouping with given order of positions.
const
positions = ["Chairman", "Secretary", "Organiser", "Finacial Secretary"],
candidates = [{ name: "Kwesi", position: "Finacial Secretary" }, { name: "Kofi", position: "Chairman" }, { name: "Ama", position: "Secretary" }, { name: "Kwame", position: "Finacial Secretary" }, { name: "Juliet", position: "Organiser" }, { name: "Jese", position: "Chairman" }],
result = Object.values(candidates.reduce(
(r, o) => (r[o.position].push(o), r),
Object.fromEntries(positions.map(k => [k, []]))
));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Related

convert a flat array that has a "path" property to a nested array

I have a flat array like this example :
[
{
'name':'itemA',
'path':'foo/bar'
},
{
'name':'itemB',
'path':'bar/foo'
},
{
'name':'itemC',
'path':'foo'
},
{
'name':'itemD',
'path':'bar'
},
{
'name':'itemE',
'path':'foo/bar/wizz'
},
{
'name':'itemF',
'path':'bar/foo'
},
]
I want to build a tree based on the "path" property, so I could get this output :
[
{
'name':'itemD',
'path':'bar',
'items':[
{
'name':'itemD',
'path':'bar/foo'
},
{
'name':'itemF',
'path':'bar/foo'
}
]
},
{
'name':'itemC',
'path':'foo',
'items':[
{
'name':'itemA',
'path':'foo/bar',
'items':
[
{
'name':'itemE',
'path':'foo/bar/wizz'
}
]
},
]
}
]
How could I achieve that ?
I found out some examples like this one, but they are based on a parent ID and not a "path" like mine.
Thanks a lot !
You could find the level or add a new object for the level of the splitted path.
const
data = [{ name: 'itemA', path: 'foo/bar' }, { name: 'itemB', path: 'bar/foo' }, { name: 'itemC', path: 'foo' }, { name: 'itemD', path: 'bar' }, { name: 'itemE', path: 'foo/bar/wizz' }, { name: 'itemF', path: 'bar/foo' }],
tree = data.reduce((items, { name, path }) => {
path.split('/').reduce((o, _, i, p) => {
let path = p.slice(0, i + 1).join('/'),
temp = (o.items ??= []).find(q => q.path === path);
if (!temp) o.items.push(temp = { name, path });
return temp;
}, { items });
return items;
}, []);
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Transforming an array into an array of arrays

I am trying to do an opposite of flattening an array.
I have the following input JSON array of 4 elements:
[
{
"nestedObj": {
"id":12
}
},
{
"nestedObj": {
"id":555
}
},
{
"nestedObj": {
"id":555
}
},
{
"nestedObj" :{
"id":771
}
}
]
I want to transform it to an array of arrays, where each subarray has elements of the same nestedObj.id grouped up together.
I can assume the initial JSON is sorted by nestedObj.id.
In the above example, the id of nestedObj of 2nd and 3rd element are the same (555), so those elements would be grouped into one sub-array.
This would be the result, an array of only 3 sub-array elements:
[
[{
"nestedObj": {
"id":12
}
}],
[{
"nestedObj": {
"id":555
}
},
{
"nestedObj": {
"id":555
}
}],
[{
"nestedObj" :{
"id":771
}
}]
]
And this is the code that gets me what I want:
const data = [ /* ...the above input data... */ ];
let result = [];
let prevId = null;
for (let elem of data) {
let currId = elem.nestedObj.id;
if (currId === prevId) {
result[result.length - 1].push({...elem});
} else {
result.push([{...elem}]);
}
prevId = currId;
}
But as you can see... the code is very declarative. It's not very JavaScript-like, in a functional programming sense.
How can I re-write it using e.g. reduce or other 'modern JS' techniques?
Just group the objects.
let array = [{ nestedObj: { id: 12 } }, { nestedObj: { id: 555 } }, { nestedObj: { id: 555 } }, { nestedObj: { id: 771 } }],
result = Object.values(array.reduce((r, o) => {
(r[o.nestedObj.id] = r[o.nestedObj.id] || []).push(o);
return r;
}, {}));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can group by id using the function reduce, extract the grouped values using the function Object.values, and finally map the array to build the desired output.
This is assuming we have only one attribute called nestedObj
let arr = [{ nestedObj: { id: 12 } }, { nestedObj: { id: 555 } }, { nestedObj: { id: 555 } }, { nestedObj: { id: 771 } }],
result = Object.values(arr.reduce((a, {nestedObj: {id}}) => {
(a[id] || (a[id] = [])).push(id);
return a;
}, {})).map(r => r.map(id => ({nestedObj: {id}})));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Use a Map to group the items with same id then get the final values from the Map
const data = [{ nestedObj: { id: 12 } }, { nestedObj: { id: 555 } }, { nestedObj: { id: 555 } }, { nestedObj: { id: 771 } }]
const map = new Map;
data.forEach(o => {
const {nestedObj:{id}} = o;
map.has(id) ? map.get(id).push(o) : map.set(id,[o]);
});
console.log([...map.values()])

Implementing useMemo the right way

I am trying to understand the usage of useMemo. Since the object doesn't change, I thought I can improve performance by adding useMemo.
However as soon I add it here, I am being asked to add findFloorPosition. Once I did that my linter asks me to add useCallback to the findFloorPosition function.
Can you give me some advice what's the right way to implement useMemo here?
const location = useLocation();
const searchParams = parseQs(location.search);
const goto = searchParams?.goto;
const findFloorPosition = (elementId) => {
for (const floor of blueprint) {
for (const room of floor.rooms) {
const foundElement = room.elements.find(
(element) => element.id === elementId
);
if (foundElement) {
return floor.position;
}
}
}
};
const currentFloorPosition = useMemo(() => findFloorPosition(goto), [goto]);
Probably not relevant here, but here how the blueprint object looks like:
const blueprint = [
{
id: "4mD59WO",
name: "AUDITORIUM",
position: 1,
rooms: [
{
id: "zR8Qgpj",
name: "Audimax",
subtitle: null,
details: null,
position: 0,
elements: [
{
id: "1jLv04W",
position: 0,
type: "daily",
element: "listing_large",
properties: {
meetingId: null,
capacity: 6
}
},
{
id: "1jLv12W",
position: 1,
type: "daily",
element: "listing_large",
properties: {
meetingId: null,
capacity: 6
}
}
]
}
]
},
{
id: "3mDd9WO",
name: "FOYER",
position: 0,
rooms: [
{
id: "4R8Qgpj",
name: "Speakers Table",
subtitle: null,
details: null,
position: 0,
elements: [
{
id: "2jLv04W",
position: 0,
type: "daily",
element: "listing_large",
properties: {
meetingId: null,
capacity: 6
}
},
{
id: "2jLv12W",
position: 1,
type: "daily",
element: "listing_large",
properties: {
meetingId: null,
capacity: 6
}
}
]
}
]
}
];
Since functional components rely heavily on closures, its extremely important that when you memoize functions, you are using the correct and updated values from the closures.
The reason eslint warns you to add findFloorPosition to dependency is to make sure that nothing within findFloorPosition refers to old values
The above code can be implemented like
const findFloorPosition = useCallback((elementId) => {
for (const floor of blueprint) {
for (const room of floor.rooms) {
const foundElement = room.elements.find(
(element) => element.id === elementId
);
if (foundElement) {
return floor.position;
}
}
}
}, [blueprint]);
const currentFloorPosition = useMemo(() => findFloorPosition(goto), [goto, findFloorPosition]);
Notice that you should memoize a value only if the dependency array values (the value of goto) not frequently change.
The warning occurs because the linter (eslint) only evaluates the semantics, it doesn't know that findFloorPosition is just a helper function.
So basically you need to inline the helper function like:
const currentFloorPosition = useMemo(() => {
for (const floor of blueprint) {
for (const room of floor.rooms) {
const foundElement = room.elements.find(
(element) => element.id === goto
);
if (foundElement) {
return floor.position;
}
}
}
return null;
}, [goto]);
// Or
const currentFloorPosition = useMemo(() => {
const findFloorPosition = (elementId) => {
for (const floor of blueprint) {
for (const room of floor.rooms) {
const foundElement = room.elements.find(
(element) => element.id === elementId
);
if (foundElement) {
return floor.position;
}
}
}
};
return findFloorPosition(goto);
}, [goto]);

Group Multiple Objects Based on Type Using lodash

I want to group unordered-list-item and ordered-list-item.
Below is the original structure:
{
data: {
areas: [{
sections: [{
rjf: [
{
type: "unordered-list-item",
text: "Item 1",
},
{
type: "unordered-list-item",
text: "Item 2",
},
{
type: "paragraph",
text: "This is text",
}]
}]
}]
}
}
Now I want to group all list items into a new object.
So the expected structure is:
{
data: {
areas: [{
sections: [{
rjf: [
{
type: "list",
items: [{
type: "unordered-list-item",
text: "Item 1",
},
{
type: "unordered-list-item",
text: "Item 2",
}]
},
{
type: "paragraph",
text: "This is text",
}]
}]
}]
}
}
So I wanted to move all the unordered-list-item and ordered-list-item to items array and the following object type like paragraph shouldn't be impacted.
I created a solution in TypeScript, but the code was too long:
const areas = data.areas;
const listItemTypes = ['unordered-list-item', 'ordered-list-item'];
return areas.map(area => {
return area.sections.map(section => {
let lastHeadingIndex = -1;
return section.rjf.reduce((acc, current, index) => {
if (!current.type || !listItemTypes.includes(current.type)) {
lastHeadingIndex = acc.length;
return [...acc, current];
}
let listObject = acc.find((el, i) => i > lastHeadingIndex && i < index && el.type === 'list');
if (!listObject) {
listObject = {
type: 'list',
items: [current]
};
return [...acc, listObject];
}
listObject.items = [...listObject.items, current];
return acc;
}, []);
});
});
How can I achieve the same functionality using lodash?
****UPDATE****
I tried with lodash, but dosen't seems to work.
var content = {
data: {
areas: [{
sections: [{
rjf: [{
type: "unordered-list-item",
text: "Item 1",
},
{
type: "unordered-list-item",
text: "Item 2",
},
{
type: "paragraph",
text: "This is text",
}
]
}]
}]
}
};
var result = content.data.areas.map((area => {
return area.sections.map(section => {
section.rfj = _.groupBy(section.rfj, 'type');
});
}));
document.body.innerHTML = '<pre>' + JSON.stringify(result, null, ' ') + '</pre>';
<script src="https://cdn.jsdelivr.net/lodash/4.17.2/lodash.min.js"></script>
return data.areas.map((area => {
return area.sections.map(section => {
return section.rfj = _.groupBy(section.rfj, 'type')
})
})
You may need some more little tweaking on the result if you need a more specific structure, but this should do most of the work
You could simplify your code to something like this.
Use nested map on areas and sections.
You are returrning the arrays from both map. Return objects with sections and rjf property instead (Assuming both structures are objects with just one property)
When looping through rjf, create 2 arrays: items and others
Group each object to these 2 arrays based on whether types array includes the current object's type.
Create an array with one list object ({ type: "list", items }) and remaining objects from others array
const types = ['unordered-list-item', 'ordered-list-item'],
input = {data:{areas:[{sections:[{rjf:[{type:"unordered-list-item",text:"Item 1",},{type:"unordered-list-item",text:"Item 2",},{type:"paragraph",text:"This is text",}]}]}]}}
const areas = input.data.areas.map(a => {
return {
sections: a.sections.map(s => {
const items = [], others = [];
s.rjf.forEach(o =>
types.includes(o.type) ? items.push(o) : others.push(o)
)
return { rjf: [{ type: "list", items }, ...others] }
})
}
})
console.log({ data: { areas } })
.as-console-wrapper { max-height: 100% !important; top: 0; }
You could use another approach by copying each object leven and map all arrays and group the last level by using a function for grouping.
This approach uses am iter function which gets all functions for each level and the starting object.
At the end, it returns a new object with the given structure and the grouped items.
function copy(source, fn) {
return Object.assign({}, ...Object
.entries(source)
.map(([k, v]) => ({ [k]: fn(v) }))
);
}
function map(array, fn) {
return array.map(fn);
}
function group(array) {
var items;
return array.reduce((r, o) => {
if (listItemTypes.includes(o.type)) {
if (!items) {
items = [];
r.push({ type: 'list', items });
}
items.push(o);
} else {
items = undefined;
r.push(o);
}
return r;
}, []);
}
function iter([fn, ...fns], object) {
return fn ? fn(object, iter.bind(null, fns)) : object;
}
var object = { data: { areas: [{ sections: [{ rjf: [{ type: "unordered-list-item", text: "Item 1" }, { type: "unordered-list-item", text: "Item 2" }, { type: "paragraph", text: "This is text" }] }] }] } },
listItemTypes = ['unordered-list-item', 'ordered-list-item'],
result = iter([copy, copy, map, copy, map, copy, group], object);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

multi level groupby with function on a property

This is similar to multi level groupby ramda js, but with a twist that is giving me trouble. In addition to a two level grouping, I'd like the inner group by to be on a processed version of the property value.
Consider data like this:
const data = [
{ top: 'top1',
name: 'junk-key-a-101' },
{ top: 'top1',
name: 'junk-key-b-102' },
{ top: 'top2',
name: 'junk-key-c-103' },
{ top: 'top2',
name: 'junk-key-c-104' } ];
I can pull out the key, process it and make it unique like so:
const getZoneFromName = n => join('-', slice(1, 3, split('-', n)));
uniq(map(getZoneFromName, pluck('name', data)));
which will get me a nice list:
["key-a", "key-b", "key-c"]
I can group the list at two levels fine:
const groupByTopThenZone = pipe(
groupBy(prop("top")),
map(groupBy(prop("name")))
);
groupByTopThenZone(data);
But I cannot figure out how to combine them to get the following output:
{
top1: {
"key-a": [
{
name: "junk-key-a-101",
top: "top1"
}
],
"key-b": [
{
name: "junk-key-b-102",
top: "top1"
}
]
},
top2: {
"key-c": [
{
name: "junk-key-c-103",
top: "top2"
},
{
name: "junk-key-c-104",
top: "top2"
}
]
}
}
I'm feeling a bit silly that I can't get this. Any ideas? Here is a place to play with it.
You were very close. Just combining those functions with compose/pipe does the trick.
(Note here also a simplified version of getZoneFromName.)
const {pipe, groupBy, map, prop, slice} = R
//const getZoneFromName = n => join('-', slice(1, 3, split('-', n)));
const getZoneFromName = slice(5, -4)
const groupByTopThenZone = pipe(
groupBy(prop("top")),
map(groupBy(pipe(prop("name"), getZoneFromName)))
)
const data = [{"name": "junk-key-a-101", "top": "top1"}, {"name": "junk-key-b-102", "top": "top1"}, {"name": "junk-key-c-103", "top": "top2"}, {"name": "junk-key-c-104", "top": "top2"}]
console.log(groupByTopThenZone(data))
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
Of course with this function simplified that much, it's probably easier to inline it:
const groupByTopThenZone = pipe(
groupBy(prop("top")),
map(groupBy(pipe(prop("name"), slice(5, -4)))
)
The main thing to remember is that groupBy is not necessarily tied with prop. We can group on the result of any String/Number/Symbol-generating function.
This is not using ramda, but vanilla JS.
const data = [
{ top: 'top1',
name: 'junk-key-a-101' },
{ top: 'top1',
name: 'junk-key-b-102' },
{ top: 'top2',
name: 'junk-key-c-103' },
{ top: 'top2',
name: 'junk-key-c-104' } ];
const res = data.reduce((acc, val, ind, arr) => {
const top = val.top;
// if the top does not exist in the obj, create it
if (!acc[top]) {
acc[top] = {};
}
// get the key through split. you could also use a regex here
const keyFragments = val.name.split('-');
const key = [keyFragments[1], keyFragments[2]].join('-');
// if the key obj prop does not exist yet, create the array
if (!acc[top][key]) {
acc[top][key] = [];
}
// push the value
acc[top][key].push({ name: val.name, top: val.top });
return acc;
}, {});
console.log(res);
Another way would be to construct each final object and merge them all:
You can transform this object:
{
"top": "top1",
"name": "junk-key-a-101"
}
Into this one:
{
"top1": {
"key-a": [
{
"name": "junk-key-a-101",
"top": "top1"
}
]
}
}
With these functions:
const key = slice(5, -4);
const obj = ({top, name}) => ({
[top]: {
[key(name)]: [
{top, name}
]
}
});
So now you can iterate on your data, transform each object and merge them together:
const groupByTopTenZone = reduce(useWith(mergeDeepWith(concat), [identity, obj]), {});
Full example:
const {slice, useWith, identity, reduce, mergeDeepWith, concat} = R;
const data = [
{ top: 'top1',
name: 'junk-key-a-101' },
{ top: 'top1',
name: 'junk-key-b-102' },
{ top: 'top2',
name: 'junk-key-c-103' },
{ top: 'top2',
name: 'junk-key-c-104' }
];
const key = slice(5, -4);
const obj = ({top, name}) => ({
[top]: {
[key(name)]: [
{top, name}
]
}
});
const groupByTopTenZone = reduce(useWith(mergeDeepWith(concat), [identity, obj]), {});
console.log(
groupByTopTenZone(data)
)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>

Categories

Resources