I got a very interesting task to do but end up with a bad code. It mutates initial array I want to avoid.
Here is an array example:
[
{
ID: "some id",
NAME: "some name",
PROPERTIES:
[
{
TYPE: [{UF_NAME: "some type name"}]
},
{
OTHER_TYPE:
[
{UF_NAME: "some other type name"},
{UF_NAME: "some other type name"},
]
},
...
],
...
OFFERS:
[
{
ID: "some id",
NAME: "some name",
PROPERTIES:
[
{
SIZE: [{UF_NAME: "some type name"}]
},
{
COLOR:
[
{UF_NAME: "some color 1"},
{UF_NAME: "some color 2"},
]
},
...
],
},
{
ID: "some id",
NAME: "some name",
PROPERTIES:
[
]
},
...
]
},
...
]
Here is the result I am about to achieve without mutating initial array:
[
{
ID: "some id",
NAME: "some name",
PROPERTIES:
[
{
TYPE: [{UF_NAME: "some type name"}]
},
{
OTHER_TYPE:
[
{UF_NAME: "some other type name"},
{UF_NAME: "some other type name"},
]
},
...
],
...
OFFERS:
[
{
ID: "some id",
NAME: "some name",
PROPERTIES:
[
{
SIZE: [{UF_NAME: "some type name"}]
},
{
COLOR:
[
{UF_NAME: "some color 1"},
{UF_NAME: "some color 2"},
]
},
...
],
}
]
}
]
What filter should do:
Go through whole array searching values in object properties and offers properties
Return a new copy of initial array with filtered offers
Array structure:
Product 1
-- Offer 11
-- Offer 12
-- Offer 13
Product 2
-- Offer 21
-- Offer 22
-- Offer 23
Product 3
-- Offer 31
-- Offer 32
-- Offer 33
Filtered array structure:
Product 1
-- Offer 11
Product 2
-- Offer 23
Product 3
-- Offer 31
Here is my function:
function filter (array, filter) {
return array.filter(function iter(o) {
return Object.keys(o).some(function (k) {
if (typeof o[k] === 'string' && o[k].indexOf(filter) !== -1) {
return true
}
if (Array.isArray(o[k])) {
o[k] = o[k].filter(iter)
return o[k].length
}
})
})
}
You need to rewrite the code to avoid fiter with a nested filter and reassignment of properties.
This function reduce the array and checks the properties if a wanted property is found, a new property is added to a temporary object and later pushed to the result set.
Same goes for nested arrays, these are reduced as well, because of a possible nested structure.
function filter(array, filter) {
return array.reduce(function iter(r, o) {
var temp = {};
Object.keys(o).forEach(function (k) {
if (typeof o[k] === 'string' && o[k].indexOf(filter) !== -1) {
temp[k] = o[k];
} else if (Array.isArray(o[k])) {
var filtered = o[k].reduce(iter, []);
if (filtered.length) temp[k] = filtered;
}
});
if (Object.keys(temp).length) r.push(temp);
return r;
}, []);
}
Related
I am trying to create a nested object recursively. Here is the sample data:
"reactions": [
{
"name": "Ester amidation",
"target": "Data #1",
"sources": ["Data #2", "Data #3"],
},
{
"name": "Buchwald-Hartwig amination with amide",
"target": "Data #4",
"sources": ["Data #5", "Data #1"], // BECAUSE Data #1 is a target AND in sources for Data #4, we should nest it as a child of Data #4
}
]
Given the target, I was trying to have something that will output something like:
{
name: "My Route 1",
children: [
{
name: "Data #4",
children: [
{
name: "Data #5",
children: []
},
{
name: "Data #1",
children: [
{
name: "Data #2",
children: []
},
{
name: "Data #3",
children: []
}
]
},
],
},
],
};
I have tried something like this but I get confused when it comes to handling arrays:
function createNestedTree(objects, target = null) {
const children = objects.filter(o => o.target === target);
if (!children.length) {
return null;
}
return children.map(child => ({
...child,
children: createNestedTree(objects, child.id)
}));
}
Anyone have an idea of how to create an algorithm for this? Much appreciated!
You could first iterate the data to create the target objects (with empty children arrays), and reference them in a Map keyed by name. Then iterate the data to populate those children arrays, and at the same time mark children as not being a root. Return the remaining root(s).
Here is a possible implementation:
function makeTree(reactions) {
const map = new Map(reactions.flatMap(({target, sources}) =>
[target, ...sources].map(name => [name, { name, children: [] }])
));
const roots = new Map(map);
for (const {target, sources} of reactions) {
for (const source of sources) {
map.get(target).children.push(map.get(source));
roots.delete(source);
}
}
return [...roots.values()];
}
const reactions = [{"name": "Ester amidation","target": "Data #1","sources": ["Data #2", "Data #3"],},{"name": "Buchwald-Hartwig amination with amide","target": "Data #4","sources": ["Data #5", "Data #1"]}];
const result = makeTree(reactions);
console.log(result);
Restructuring array of objects to new array
Problem
There’s an array of objects that contains plain strings and might contain nested arrays as well. We want to create a new Array that will contain a node for each item in the array and separate nodes for each array item connected to its parent. Each parent node should have the following structure:
{
id: uuidv4(),
position: { x: 0, y: 0 },
data: { label: <item data goes here> }
}
Each array node with the following schema above, should also have a connection edge item added to the array with the following properties:
{
id: ‘e<array item Id>-<parentId>’,
source: <array item Id>,
target: <parentId>,
}
Example
We have the following array of objects for example:
[
{
"author": "John Doe",
"age": 26,
"books": [
{
"title": "Book 1"
},
{
"title": "Book 2",
"chapters": [
{
"title": "No Way Home",
"page": 256
}
]
}
]
}
]
The expected output is:
[
{
"id": "1",
"data": {
"label": {
"author": "John Doe",
"age": 26,
}
}
},
{
"id": "2",
"data": {
"label": "books" // key of array
}
},
{
"id": "3",
"data": {
"label": {
"title": "Book 1"
}
}
},
{
"id": "4",
"data": {
"label": {
"title": "Book 2"
}
}
},
{
"id": "5",
"data": {
"label": "chapters" // key of array
}
},
{
"id": "6",
"data": {
"label": {
"title": "No Way Home",
"page": 256
}
}
},
{
"id": "e2-1",
"source": "2",
"target": "1"
},
{
"id": "e3-2",
"source": "3",
"target": "2"
},
{
"id": "e4-2",
"source": "4",
"target": "2"
},
{
"id": "e5-4",
"source": "5",
"target": "4"
},
{
"id": "e6-5",
"source": "6",
"target": "5"
}
]
First of all, I would not be answering if there was not already a good answer. Please, on StackOverflow, always show your own attempts and explain where you got stuck. But since there is already an answer, I think this version might be a bit simpler.
Second, I'm assuming this output format is some sort of directed graph, that the first half is your list of vertices and the second half a list of edges. If so I don't know if your output format is constrained here. But if you had the option, I would think a better structure would be an object with vertices and edges properties, each containing an array. You might then not need the edges' ids. And the code could also be simplified.
This version first converts to an intermediate structure like this:
[
{id: "1", data: {label: {author: "John Doe", age: 26}}, children: [
{id: "2", data: {label: "books"}, children: [
{id: "3", data: {label: {title: "Book 1"}}, children: []},
{id: "4", data: {label: {title: "Book 2"}}, children: [
{id: "5", data: {label: "chapters"}, children: [
{id: "6", data: {label: {title: "No Way Home"}}, children: []}
]}
]}
]}
]}
]
Then we flatten that structure into the first section of the output and use it to calculate the relationships (edges?) between nested nodes to go in the second section.
The code looks like this:
const transform = (input) => {
const extract = (os, nextId = ((id) => () => String (++ id)) (0)) => os .map ((o) => ({
id: nextId(),
data: {label: Object .fromEntries (Object .entries (o) .filter (([k, v]) => !Array .isArray (v)))},
children: Object .entries (o) .filter (([k, v]) => Array .isArray (v)) .flatMap (([k, v]) => [
{id: nextId(), data: {label: k}, children: extract (v, nextId)},
])
}))
const relationships = (xs) =>
xs .flatMap (({id: target, children = []}) => [
... children .map (({id: source}) => ({id: `e${source}-${target}`, source, target})),
... relationships (children),
])
const flatten = (xs) =>
xs .flatMap (({children, ...rest}) => [rest, ... flatten (children)])
const res = extract (input)
return [...flatten (res), ... relationships (res)]
}
const input = [{author: "John Doe", age : 26, books: [{title: "Book 1"}, {title: "Book 2", chapters: [{title: "No Way Home", page: 256}]}]}]
console .log (transform (input))
.as-console-wrapper {max-height: 100% !important; top: 0}
We use three separate recursive functions. One does the recursive extract into that intermediate format. Along the way, it adds id nodes using a nextId stateful function (something I usually avoid, but seems to simplify things here.) Then flatten simply recursively lifts the children to sit alongside their parents. And relationships (again recursively) uses the ids of the parent- and child-nodes to add an edge node.
Using these three separate recursive calls is probably less efficient than some other solutions, but I think it leads to much cleaner code.
One has to choose a self recursive approach which in a generic way can process both, array-items and object-entries. Also, while the recursive process takes place, one not only has to create and collect the consecutively/serially numbered (the incremented id value) data nodes, but one in addition needs to keep track of every data node's parent reference in order to finally concatenate the list of edge items (as the OP calls it) to the list of data nodes.
function flattenStructureRecursively(source = [], result = [], tracker = {}) {
let {
parent = null, edgeItems = [],
getId = (id => (() => ++id))(0),
} = tracker;
const createEdgeItem = (id, pid) => ({
id: `e${ id }-${ pid }`,
source: id,
target: pid,
});
const putNodeData = node => {
result.push(node);
if (parent !== null) {
edgeItems.push(createEdgeItem(node.id, parent.id));
}
// every data node is a parent entity too.
parent = node;
};
if (Array.isArray(source)) {
result.push(
...source.flatMap(item =>
flattenStructureRecursively(item, [], {
getId, parent, edgeItems,
})
)
);
} else {
let {
dataNode,
childEntries,
} = Object
.entries(source)
.reduce(({ dataNode, childEntries }, [key, value]) => {
if (value && (Array.isArray(value) || (typeof value === 'object'))) {
// collect any object's iterable properties.
childEntries.push([key, value]);
} else {
// aggregate any object's non iterable
// properties at data node level.
(dataNode ??= {
id: getId(),
data: { label: {} }
}).data.label[key] = value;
}
return { dataNode, childEntries };
}, { dataNode: null, childEntries: [] });
if (dataNode !== null) {
putNodeData(dataNode);
}
childEntries
.forEach(([key, value]) => {
// every object's iterable property is supposed
// to be created as an own parent entity.
dataNode = {
id: getId(),
data: { label: key },
};
putNodeData(dataNode);
result.push(
...flattenStructureRecursively(value, [], {
getId, parent, edgeItems,
})
);
});
}
if (parent === null) {
// append all additionally collected edge items
// in the end of all the recursion.
result.push(...edgeItems);
}
return result;
}
console.log(
flattenStructureRecursively([{
author: "John Doe",
pseudonym: "J.D.",
books: [{
title: "Book 1",
}, {
title: "Book 2",
chapters: [{
title: "No Way Home",
page: 256,
}],
}],
age: 26,
}])
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
I'm trying to create an array from items inside objects, as well as items inside arrays inside objects in a vue app, by using foreach to loop over them. It works well when I only have one single item, but I can't figure out how to loop over an array inside the object and add all of those items to the array I'm creating.
What I have now
const examples = [
{
name: "Example 1",
type: ["Meat", "Water", "Dairy"],
showDetail: false
},
{
name: "Example 2",
type: Fruit,
showDetail: false
},
{
name: "Example 3",
type: Vegetable,
showDetail: false
}
]
new Vue({
data: {
examplesList: examples,
type: ''
},
methods: {
filterList: function() {
this.type = event.target.value;
}
},
computed: {
uniqueList: function() {
const types = [];
this.examplesList.forEach((example) => {
if (!types.includes(example.type)) {
types.push(example.type);
}
});
return types;
}
}
})
It works fine if I remove the object with the array inside of "type", and adds the Fruit and Vegetable items to the array. Any ideas?
Desired output:
["Meat", "Water", "Dairy", "Fruit", "Vegetable"]
Here is one possible solution. You'll need to translate the solution to vue, of course, but the problem here really doesn't have anything to do with vue specifically so I've shown a vanilla javascript solution just to keep things simple.
const examples = [
{
name: "Example 1",
type: ["Meat", "Water", "Dairy", "Fruit"],
showDetail: false
},
{
name: "Example 2",
type: "Fruit",
showDetail: false
},
{
name: "Example 3",
type: "Vegetable",
showDetail: false
}
];
const types = [];
examples.forEach((example) => {
const exampleTypes = Array.isArray(example.type)
? example.type
: [example.type];
for (let exampleType of exampleTypes) {
if (!types.includes(exampleType)) {
types.push(exampleType);
}
}
});
console.log(types);
Here's an abstract way of doing that using a Set. Sets guarantee unique values meaning there's no need to check if an item is present or not.
Using just an array will become increasingly expensive to check if an item was already added as it will have to scan the entire array for each includes, O(n) time complexity.
const examples = [{
name: "Example 1",
type: ["Meat", "Water", "Dairy"],
showDetail: false
},
{
name: "Example 2",
type: "Fruit",
showDetail: false
},
{
name: "Example 3",
type: "Vegetable",
showDetail: false
}
];
const typeSet = new Set();
let types;
examples.forEach((example) => {
if (Array.isArray(example.type)) {
example.type.forEach(type => {
typeSet.add(type);
});
} else {
typeSet.add(example.type);
}
});
types = [...typeSet];
console.log(types);
Here is one possible solution to achieve the desired result:
computed: {
uniqueList: function() {
return this.examplesList.reduce(
(acc, itm) => (
Array.isArray(itm.type)
? itm.type.filter(t => !acc.includes(t)).length > 0
? [
...acc,
...itm.type.filter(t => !acc.includes(t))
]
: acc
: acc.includes(itm.type)
? acc
: [...acc, itm.type]
), []
)
}
}
Explanation
reduce is used on the array this.examplesList
Each item itm is processed and acc is the accumulator/aggregator (initially set to an empty array [])
if itm.type is an Array, then
if any elements in itm.type array is not already present in acc array, include it (by using the ... spread operator)
otherwise (ie, itm.type is a string)
if it is not already in acc, then include it (again, using ... spread operator)
That's it !
Please comment if any further clarification/s or question/s.
Code snippet
const examples = [{
name: "Example 1",
type: ["Meat", "Water", "Dairy"],
showDetail: false
},
{
name: "Example 2",
type: "Fruit",
showDetail: false
},
{
name: "Example 3",
type: "Vegetable",
showDetail: false
}
];
const getUniqueTypes = (arr = examples) => (
arr.reduce(
(acc, itm) => (
Array.isArray(itm.type)
? itm.type.filter(t => !acc.includes(t)).length > 0
? [
...acc,
...itm.type.filter(t => !acc.includes(t))
]
: acc
: acc.includes(itm.type)
? acc
: [...acc, itm.type]
), []
)
);
console.log(getUniqueTypes());
Working Demo :
const examples = [{
name: "Example 1",
type: ["Meat", "Water", "Dairy"],
showDetail: false
},
{
name: "Example 2",
type: "Fruit",
showDetail: false
},
{
name: "Example 3",
type: "Vegetable",
showDetail: false
}];
let newArray = []
examples.forEach((item) => {
if (typeof(item.type) === 'object') {
item.type.forEach((elem) => {
newArray.push(elem)
})
} else {
newArray.push(item.type)
}
})
console.log(newArray)
I'm attempting to transform a blob of nested JSON objects and arrays that are returned from my GraphQL query. By passing my data to a map() function and using spread operators I am am able to flatten my data most of the way, but I can't figure out how to get one level deeper to finish the job.
Data source:
// data from GraphQL
var sampledata = {
data: {
allNodeResource: {
edges: [
{
node: {
title: "Sample node 1",
desc: {
description: "Sample description"
},
relationships: {
sin: [
{
name: "Gluttony"
},
{
name: "Greed"
}
]
}
}
},
{
node: {
title: "Sample node 2",
desc: {
description: "Another sample description"
},
relationships: {
sin: [
{
name: "Lust"
},
{
name: "Wrath"
},
{
name: "Envy"
}
]
}
}
}
]
}
}
};
Here is my initial attempt:
const flatten = arr =>
arr.map(({ node: { title, relationships: sin, desc } }) => ({
title,
...desc,
...sin,
}));
const transformedJSON = flatten(sampledata.data.allNodeResource.edges);
That gets me most of the way, and returns this (as you can see description is now flat, and sin is almost flat, but not quite!):
[
{
"title": "Sample node 1",
"description": "Sample description",
"sin": [
{
"name": "Gluttony"
},
{
"name": "Greed"
}
]
},
{
"title": "Sample node 2",
"description": "Another sample description",
"sin": [
{
"name": "Lust"
},
{
"name": "Wrath"
},
{
"name": "Envy"
}
]
}
]
My end goal is below, but for the life of me I can't figure out how to get here:
[
{
"node": {
"title": "Sample node 1",
"description": "Sample description",
"sin": [
"Gluttony",
"Greed"
]
}
},
{
"node": {
"title": "Sample node 2",
"description": "Another sample description",
"sin": [
"Lust",
"Wrath",
"Envy"
]
}
}
]
I feel like I'm a syntax error away from figuring this out, and have tried numerous combinations, such as trying to chain sin through another map() function, but I cannot seem to come up with the right technique (or maybe this is the wrong approach completely.)
Any tips on how to move this forward would be much appreciated...Tim
Codesandbox here: https://codesandbox.io/s/jovial-jennings-h6smp?fontsize=14
Tweaking your flatten function a bit, you can get the sin object to be flattened like this:
const flatten = arr =>
arr.map(({ node: { title, relationships, desc, ...rest } }) => ({
title,
sin: relationships.sin.map(sinObj => sinObj.name),
...desc,
...rest
}));
Forked CodeSandbox here: https://codesandbox.io/s/hardcore-glitter-f4xhn
You just need to pull out the name from the sin object. I also removed the destructuring of relationships into sin because that was causing you to end up with an object that would look like sin: { sin: {}}
const newJSON = transformedJSON.map(json => {
const changed = {...json};
changed.sin = changed.sin.reduce((result, sin) => {
return result.concat(sin.name);
}, []);
return changed;
});
I am doing an assessment right now and I am allowed to use what ever resources I want, just a preface. The prompt I was given was to use .filter to filter out all objects with out a specific key, here is the prompt...
Write a function called cookieLoversOnly that takes
in an array and filters out every object that
does NOT have the key favoriteCookie.
cookieLoversOnly should return the filtered array.
This is what I have so far...
function cookieLoversOnly(arr){
return arr.filter(e => arr[e]===favoriteCookie)
}
Here are some examples of arr.filter(e => !e.favouriteCookie)
let people = [
{
name: 'Mr Fooman',
job: 'Dog walker',
favouriteAnimal: 'Dog'
},
{
job: 'Barman',
favouriteFood: 'Cookies',
favouriteCookie: 'Double Choc Chip',
favouriteAnimal: 'Fox'
},
{
name: 'Miss Baz',
favouriteFood: 'Caesar Salad',
favouriteCookie: 'Raisin',
favouriteAnimal: 'Elephant'
}
];
let demons = [
{
name: "demon 1",
favouriteCookie: false
},
{
name: "demon 2",
favouriteCookie: true
},
{
name: "demon 3",
favouriteCookie: undefined
},
{
name: "demon 4",
favouriteCookie: null
}
];
function cookieLoversOnly(arr){
return arr.filter(e => e.favouriteCookie)
}
console.log("people:", cookieLoversOnly(people));
console.log("demons:", cookieLoversOnly(demons));
and therefore this answer is wrong, if you take the question literally.