Related
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 6 months ago.
Improve this question
I am currently working on an algorithm. According to this algorithm there are folders and files. I intend to move a file with given id to a folder with given id. I'm having a hard time setting up this algorithmic structure. How can I set up this algorithm? For example;
Acceptance criteria
Imagine an array that contains folders. These folders can have files in it. move function moves a file to another folder and returns the new state of given list.
Example list
const list = [
{
id: '1',
name: 'Folder 1',
files: [
{ id: '2', name: 'File 1' },
{ id: '3', name: 'File 2' },
{ id: '4', name: 'File 3' },
{ id: '5', name: 'File 4' },
],
},
{
id: '6',
name: 'Folder 2',
files: [{ id: '7', name: 'File 5' }],
},
]
If I run move(list, '4', '6') then I expect file with id 4 moved to the folder which has id 6. Function should return the new state below;
const result = [
{
id: '1',
name: 'Folder 1',
files: [
{ id: '2', name: 'File 1' },
{ id: '3', name: 'File 2' },
{ id: '5', name: 'File 4' },
],
},
{
id: '6',
name: 'Folder 2',
files: [
{ id: '7', name: 'File 5' },
{ id: '4', name: 'File 3' },
],
},
];
I need to run the logic inside this function.
export default function move(list: List, source: string, destination: string): List {
return list;
}```
The data structure is not ideal for such operations, as it is not indexed. There is no other way than to search the whole tree structure for a given id. It would have been better if every id were mapped to the corresponding location in the tree.
But given that it is like it is, you would need a function to find the location of a given id. That function would then return the array in which that object resides, and at which index in that array. This allows the caller to use that information for several purposes, including removal.
Here is a possible implementation and a run of the example you have given:
function findItem(list, id) {
const i = list.findIndex(child => child.id === id);
if (i > -1) return [list, i];
for (const {files} of list) {
if (!files) continue;
const result = findItem(files, id);
if (result) return result;
}
}
function removeItem(list, id) {
const found = findItem(list, id);
return found && found[0].splice(found[1], 1)[0];
}
function move(list, sourceId, targetId) {
const found = findItem(list, targetId);
if (!found) return;
const removed = removeItem(list, sourceId);
if (!removed) return;
(found[0][found[1]].files ??= []).push(removed);
}
// Example tree as given in question
const list = [{id: '1',name: 'Folder 1',files: [{ id: '2', name: 'File 1' },{ id: '3', name: 'File 2' },{ id: '4', name: 'File 3' },{ id: '5', name: 'File 4' },],},{id: '6',name: 'Folder 2',files: [{ id: '7', name: 'File 5' }],},];
move(list, '4', '6');
console.log(list);
Remarks:
If the target of the move is an object without files property, that files property is created. If you don't want this to happen, but instead want to abort the operation, then replace this line:
if (!found) return;
With:
if (!found?.files) return;
Your function signature of move expects it to return the list, but as this function will mutate the given structure, this might not be really necessary -- it would be the same list that was passed as argument.
I have an infinite tree:
const Data = [
{
id: '1',
name: 'hello',
children: [
{
id: '2',
name: 'world',
children: [
{
id: '3',
name: 'world',
children: [],
},
{
id: '4',
name: 'world',
children: [],
},
],
},
{
id: '5',
name: 'world',
children: [],
},
],
},
];
What I want to do is get the id and name of the path that leads to "world" and push it in to an array.
For example: the first path would be:
[
{ id: '1', name: 'hello' },
{ id: '2', name: 'world' },
]
second:
[
{ id: '1', name: 'hello' },
{ id: '2', name: 'world' },
{ id: '3', name: 'world' },
]
And then push those arrays into another array.
So my result would look like this:
const result = [
[
{ id: '1', name: 'hello' },
{ id: '2', name: 'world' },
],
[
{ id: '1', name: 'hello' },
{ id: '2', name: 'world' },
{ id: '3', name: 'world' },
],
[
{ id: '1', name: 'hello' },
{ id: '2', name: 'world' },
{ id: '4', name: 'world' },
],
[
{ id: '1', name: 'hello' },
{ id: '5', name: 'world' },
],
];
I have a recursive function:
const findPath = (input="world", data, visitedStack, dataStack) => {
return data.map((node) => {
visitedStack.push({ id: node.id, name: node.name });
if (node.name.toLowerCase().includes(input.toLowerCase())) {
dataStack.push([...visitedStack]);
}
return findPath(
input,
node.children,
visitedStack,
dataStack
);
});
};
But this is adding on all the paths it has visited, so the last array that is pushed into dataStack will look like this:
[
{ id: '1', name: 'hello' },
{ id: '2', name: 'world' },
{ id: '3', name: 'world' },
{ id: '4', name: 'world' },
{ id: '5', name: 'world' },
]
Not sure how to fix this. Or is this an incorrect approach?
The problem is that your visitedStack keeps growing, as you are eventually pushing all nodes unto it. Be aware that all recursive executions of your function get the same visitedStack to work with. So pushing [...visitedStack] is not going to push a path, but all nodes that had been visited before, which after a while do not represent a path any more.
If we stick with your function, then just make sure you don't push on visited permanently, but create a copy of that stack with the extra node, which will remain in the deeper recursion, but will not contaminate the whole rest of the execution. This way that extra node will not be there in the other, sibling paths:
const findPath = (input="world", data, visitedStack, dataStack) => {
return data.map((node) => {
let newStack = visitedStack.concat({ id: node.id, name: node.name });
if (node.name.toLowerCase().includes(input.toLowerCase())) {
dataStack.push(newStack);
}
return findPath(
input,
node.children,
newStack,
dataStack
);
});
};
Call as:
let result = [];
findPath("world", data, [], result);
console.log(result);
Alternative
I would however also address the following:
It is a bit odd that findPath does not return the result, but that the caller needs to provide the array in which the resulting paths should be collected. So I would suggest a function that returns the new array, not requiring the caller to pass that array as argument.
It is not useful to have a default value for a parameter, when other parameters following it, do not have a default value. Because, that means you anyway have to provide values for those other parameters, including the one that could have had a default value.
The paths that are returned still contain multiple references to the same objects. You do copy the objects into new objects, but as that new object sits in visitedStack, it will be reused when pushed potentially several times for deeper paths. So I would suggest making the object copies at the very last moment -- when the path is pushing on the result array.
Instead of repeatedly converting the input to lower case, do this only once.
Here is how you could write it:
function findPath(data, input="world") {
const result = [];
input = input.toLowerCase();
function recur(data, visitedStack) {
for (const node of data) {
const newStack = visitedStack.concat(node);
if (node.name.toLowerCase().includes(input)) {
result.push(newStack.map(o => ({id: o.id, name:o.name})));
}
recur(node.children, newStack);
}
}
recur(data, []);
return result;
}
const data = [{id: '1',name: 'hello',children: [{id: '2',name: 'world',children: [{id: '3',name: 'world',children: [],},{id: '4',name: 'world',children: [],},],},{id: '5',name: 'world',children: [],},],},];
const result = findPath(data);
console.log(result);
I have an array of data containing multiple properties, that needs to be filtered out via select boxes. Just like an eCommerce site where you can select a category, then certain sizes, then prices, then colors, etc.etc. all the filters combined, give a result.
How can I filter down on multiple properties and eventually return data that only contains the selected values? http://jsfiddle.net/Jeffwise/z2y41k85/3/
var vm = new Vue({
el: '#root',
data: {
list: [{
name: 'asd',
category: 1
},
{
name: 'zxc',
category: 1
},
{
name: 'qwe',
category: 1
},
{
name: 'qqq',
category: 2
},
{
name: 'www',
category: 2
},
{
name: 'eee',
category: 2
},
{
name: 'rrr',
category: 2
},
{
name: 'ttt',
category: 2
},
{
name: 'ert',
category: 1
},
{
name: 'wer',
category: 2
},
{
name: 'sdf',
category: 1
},
{
name: 'dfg',
category: 2
},
{
name: 'xcv',
category: 1
}
],
categories: [{
id: 1,
name: 'cat 1'
},
{
id: 2,
name: 'cat 2'
}
],
keyword: 'e',
category: 1
},
computed: {
filteredByAll() {
return getByCategory(getByKeyword(this.list, this.keyword), this.category)
},
filteredByKeyword() {
return getByKeyword(this.list, this.keyword)
},
filteredByCategory() {
return getByCategory(this.list, this.category)
}
}
});
function getByKeyword(list, keyword) {
const search = keyword.trim()
if (!search.length) return list
return list.filter(item => item.name.indexOf(search) > -1)
}
function getByCategory(list, category) {
if (!category) return list
return list.filter(item => item.category === category)
}
I think what I am searching for is chaining multiple filtered functions together. But how do you do that? This js fiddle comes close, but how can you add more filters to this, so you can check for more functions/filters? I have a total of 10 filters that should 'work' together. I'm an absolute beginner and still learning.
Of course there are several ways to solve this problem.
Instead of chaining (e.g. data.withKey('cheap').withBrand('Levis') - which requires a new class with chainable methods on it) I would go with a recursive method that takes one filterObject. Every key: value pair is a filter:
methods: {
filter(data, filters) {
// filters should be an object of filterOptions, e.g. { name: 'blue', weight: 20 }
const filterProp = Object.keys(filters)[0]; // get first prop to filter for
// filter your data by this prop and the corresponding value
const filteredData = data.filter((entry) => {
return entry[filterProp] === filters[filterProp]
});
// get all the other filters through dynamic destructuring
const { [filterProp]: deletedFilter, ...otherFilters } = filters;
// if there are no other filters return filteredData,
// else filter filteredData again with the remaining filters
return Object.keys(otherFilters).length <= 0
? filteredData : this.filter(filteredData, otherFilters);
},
...
}
I have a data tree structure with children:
{ id: 1,
name: "Dog",
parent_id: null,
children: [
{
id: 2,
name: "Food",
parent_id: 1,
children: []
},
{
id: 3,
name: "Water",
parent_id: 1,
children: [
{
id: 4,
name: "Bowl",
parent_id: 3,
children: []
},
{
id: 5,
name: "Oxygen",
parent_id: 3,
children: []
},
{
id: 6,
name: "Hydrogen",
parent_id: 3,
children: []
}
]
}
]
}
Any child data object can have more children as shown in the above data. This represents a DOM structure that a user could select an item from to add a child to.
I have a known text title of the selected item from the DOM as well as the data the user wants to insert. I am having trouble finding a recursive algorithm that will allow me to add the new data to the correct level of the tree.
Here is a list of me thinking through the problem and trying to pseudo code it:
inputs:
tree (data from above)
parentTitle from clicked item in DOM
outputs:
tree with item inserted
steps:
determine highest used id to know what next unique id is
check current level of data for match with the title of parent
if matched then set id and parent_id in new data and push into children of parent
if no match then check if current level data have children
if current level has children needs to repeat steps 2+ for each until match is found
Here is my code:
function askUserForNewItem(e) {
const tree = getTree(); // returns above tree data structure
const name = prompt( 'Enter new item’s name:' ); // user input to match and insert as new item in tree
const clickedTitle = getClickedTitle(e); // returns string title of clicked on item from DOM - for example "Dog" or "Bowl"
const parent = determineParent(tree, clickedTitle);
const parent_id = parent[0].id;
// TODO - needs to set real unique id (highest unused id)
const newId = 101; // hard coded for now, needs to be dynamic
// TODO - needs to insert into correct level of children array in tree
return tree.children.push({ id: newId, name, emoji, children: [], parent_id: parent_id });
}
function determineParent(tree, clickedTitle) {
if(tree.children.length === 0) {
return false;
}
let treeLevel = tree;
let parent = [];
while(treeLevel.children.length !== 0) {
parent = treeLevel.children.filter(child => child.name === clickedTitle);
if(parent.length !== 0) {
break;
}
else {
// what to do here to make this recursive?
}
}
return parent;
}
So if a user typed "Cat" while clicking the add button for "Dog" then a new object
{
id: 7,
name: "Cat",
parent_id: 1,
children: []
}
Would be inserted into children of the first level "Dog" object in the data tree.
If you want a recursive solution, you should modify the determineParent method so it searches down the tree.
Not sure this is exactly what you are searching for, but i hope you get the general idea
function determineParent(curNode, clickedTitle) {
if(curNode.name===clickedTitle)
return curNode; // found the parent node with the correct clickedTitle
// not found yet, do a recusive search down the tree.
for(const node of curNode.children) {
return determineParent(node,clickedTitle);
}
return null; // not found.
}
the idea is to start at the topmost node (curNode) and first determine if it is the correct parent, if not then take the first children see if it matches and if not search down it's children and so on.
When dealing with recursion it may be necessary to handle the situation where you may experience circular references, consider a scenario where a node has a child that points to the nodes parent or grandparent, the recursive method will run forever (in real life it will run out of stack space and throw an exception).
One way it so include a safeguard counter that is decreased on each recursive call, and then bail out when it reaches zero.
function determineParent(curNode, clickedTitle, safeGuard) {
if(curNode.name===clickedTitle)
return curNode; // found the parent node with the correct clickedTitle
if(safeGuard===0)
return null; // bail out
// not found yet, do a recusive search down the tree.
for(const node of curNode.children) {
return determineParent(node,clickedTitle,--safeGuard);
}
return null; // not found.
}
and then call it like
this.determineParent(tree,"title",100);
to limit the number of searches to 100.
If it's ok to use Lodash+Deepdash, then:
let child = {
name: "Cat",
children: []
};
let maxUsedId=-1;
_.eachDeep([data],(val)=>{
if(val.id>maxUsedId){
maxUsedId = val.id;
}
if(val.name==parentName){
val.children.push(child);
child.parent_id = val.id;
}
},{tree:true});
child.id=maxUsedId+1;
Codepen for this
Not a big fan of reinventing the wheel, especially when it comes to algorithms. Here is how you could use object-scan to solve your problem. We use it for data processing since it is quite powerful once you wrap your head around it
// const objectScan = require('object-scan');
const insert = (tree, parentName, childName) => objectScan(['**.name'], {
abort: true,
rtn: 'bool',
filterFn: ({ value, parent }) => {
if (value === parentName) {
parent.children.push({
id: Math.max(...objectScan(['**.id'], { rtn: 'value' })(tree)) + 1,
name: childName,
parent_id: parent.id,
children: []
});
return true;
}
return false;
}
})(tree);
const data = { id: 1, name: 'Dog', parent_id: null, children: [{ id: 2, name: 'Food', parent_id: 1, children: [] }, { id: 3, name: 'Water', parent_id: 1, children: [{ id: 4, name: 'Bowl', parent_id: 3, children: [] }, { id: 5, name: 'Oxygen', parent_id: 3, children: [] }, { id: 6, name: 'Hydrogen', parent_id: 3, children: [] }] }] };
console.log(insert(data, 'Dog', 'Cat')); // true iff insert was successful
// => true
console.log(data);
// => { id: 1, name: 'Dog', parent_id: null, children: [ { id: 2, name: 'Food', parent_id: 1, children: [] }, { id: 3, name: 'Water', parent_id: 1, children: [ { id: 4, name: 'Bowl', parent_id: 3, children: [] }, { id: 5, name: 'Oxygen', parent_id: 3, children: [] }, { id: 6, name: 'Hydrogen', parent_id: 3, children: [] } ] }, { id: 7, name: 'Cat', parent_id: 1, children: [] } ] }
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.7.1"></script>
Disclaimer: I'm the author of object-scan
This actually might be a JavaScript question, but it is happening when I am using AngularJs.
Say I have an array like this:
var players = [
{
id: 1,
name: 'Player 1'
},
{
id: 2,
name: 'Player 2'
}
];
and then I have another array like this:
var teams = [
{
id: 1,
name: 'Team 1',
members: players
},
{
id: 2,
name: 'Team 2',
members: players
}
];
If I decide to add a new property called position to one of the teams:
teams[0].members[0].position = 1;
I don't want it to then update the second team members position.
I hope that makes sense.
Here is a codepen to illustrate my issue:
http://codepen.io/r3plica/pen/ZGZXjb?editors=101
Array in java-script are mutable so you need to make copy of player and assign it to teams member property.
Just change your code to :
var teams = [
{
id: 1,
name: 'Team 1',
members: angular.copy(players)
},
{
id: 2,
name: 'Team 2',
members: angular.copy(players)
}
];
for more information see : https://docs.angularjs.org/api/ng/function/angular.copy
The only way (if both teams have the same players) is to use a copy of the array for the second team. Now it's logical the second team gets updated, because both teams point to the same reference of players.
You can use angular.copy for that.
var copyofplayers = [];
angular.copy(players, copyofplayers);
also can use jQuery.extend() like
var teams = [
{
id: 1,
name: 'Team 1',
members: jQuery.extend({}, players)
},
{
id: 2,
name: 'Team 2',
members: jQuery.extend({}, players)
}
];