I get a Javascript Object from my server, which depicts a filesystem. Now I want to get the path of all files in the system, e.g. the endpoints of the tree.
File str
working solution:
var json = {"path":"/pages/services/project", "is_dir":true, "children":[{"path":"/pages/services/project/headline","is_dir":false,"children":[]},{"path":"/pages/services/project/text","is_dir":false,"children":[]},
{"path":"/pages/services/project/test/","is_dir":true,"children":[{"path":"/pages/services/project/test/text","is_dir":false,"children":[]},
{"path":"/pages/services/project/test/picture","is_dir":false,"children":[]}]}]};
json.children.forEach(function (child) {
goToDeepestPoint(child);
});
function goToDeepestPoint(node) {
if (node.is_dir){
for(var i=0;i<node.children.length;i++){
goToDeepestPoint(node.children[i]);
}
}
else {
out(node.path);
}
}
function out()
{
var args = Array.prototype.slice.call(arguments, 0);
document.getElementById('output').innerHTML += args.join(" ") + "\n";
}
var json = {"path":"/pages/services/project", "is_dir":true, "children":[{"path":"/pages/services/project/headline","is_dir":false,"children":[]},{"path":"/pages/services/project/text","is_dir":false,"children":[]},
{"path":"/pages/services/project/test/","is_dir":true,"children":[{"path":"/pages/services/project/test/text","is_dir":false,"children":[]},
{"path":"/pages/services/project/test/picture","is_dir":false,"children":[]}]}]};
function getPaths(obj){
let foundPaths = [];
if(obj.children.length > 0){
obj.children.forEach(function (element){
let childPaths = getPaths(element);
foundPaths = foundPaths.concat(childPaths);
});
return foundPaths;
} else {
foundPaths.push(obj.path);
return foundPaths;
}
}
let paths = getPaths(json);
document.getElementById('output').innerHTML += paths.join("\n");
<pre id="output"></pre>
I was busy having a play with your original question and got this working:
goToDeepestPoint(json);
function goToDeepestPoint(node) {
if (node.is_dir)
node.children.forEach(function (child) {
goToDeepestPoint(child);
});
else
return out(node.path);
}
May not be appropriate in the light of your edit, but shame to waste it!
I'll share my answer because it's relevant to a thing I've been working on at the moment - it takes a more functional approach because that just happens to be what I've been studying
Persistent iterators
JavaScript's stateful iterators make me sad, so we can implement a persistent iterator interface using our own Yield
and Return
types. A Memo
type is used but that is merely an optimisation detail.
const Memo = (f, memo) => () =>
memo === undefined
? (memo = f (), memo)
: memo
const Yield = (value, next = Return) =>
({ done: false, value, next: Memo (next) })
const Return = value =>
({ done: true, value })
// example use
const ArrayIterator = (xs = []) =>
xs.length === 0
? Return ()
: Yield (xs [0], () => ArrayIterator (xs.slice (1)))
const it =
ArrayIterator ([1,2,3])
console.log (it.value) // 1
console.log (it.value) // 1
console.log (it.next () .value) // 2
console.log (it.next () .value) // 2
Now, if necessary to create an adapter to interoperate with JavaScript's native generators, we can create a Generator
type
Admittedly, this is not super interesting, but it does demonstrate the necessary functionality
const Generator = function* (it = Return ())
{
while (it.done === false)
(yield it.value, it = it.next ())
return it.value
}
Array.from (Generator (ArrayIterator ([1,2,3])))
// => [1,2,3]
Our persistent iterators open the door for more exciting things like this tho
const MappedIterator = (f = x => x, it = Return ()) =>
it.done
? Return ()
: Yield (f (it.value), () => MappedIterator (f, it.next ()))
const ConcatIterator = (x = Return (), y = Return) =>
x.done
? y ()
: Yield (x.value, () => ConcatIterator (x.next (), y))
const it =
MappedIterator (x => x * x, ArrayIterator ([1,2,3]))
Array.from (Generator (it)) // => [ 1, 4, 9 ]
Array.from (Generator (ConcatIterator (it, it))) // => [ 1, 4, 9, 1, 4, 9 ]
Pure expression
Our persistent iterators give us a neat way to express a potentially complex traversal of our data structure. Here's one way we could write your tree iterator as a pure expression
const FlatMappedIterator = (f, it = Return ()) =>
it.done
? Return ()
: ConcatIterator (f (it.value), () => FlatMappedIterator (f, it.next ()))
const MyTreeIterator = node =>
node === undefined
? Return ()
: node.is_dir
? FlatMappedIterator (MyTreeIterator, ArrayIterator (node.children))
: Yield (node.path)
Of course the answer is incomplete without a demonstration of working code
const Memo = (f, memo) => () =>
memo === undefined
? (memo = f (), memo)
: memo
const Yield = (value, next = Return) =>
({ done: false, value, next: Memo (next) })
const Return = value =>
({ done: true, value })
// -------------------------------------------------------------------
const ArrayIterator = (xs = []) =>
xs.length === 0
? Return ()
: Yield (xs [0], () => ArrayIterator (xs.slice (1)))
const ConcatIterator = (x = Return (), y = Return) =>
x.done
? y ()
: Yield (x.value, () => ConcatIterator (x.next (), y))
const FlatMappedIterator = (f, it = Return ()) =>
it.done
? Return ()
: ConcatIterator (f (it.value), () => FlatMappedIterator (f, it.next ()))
const Generator = function* (it = Return ())
{
while (it.done === false)
(yield it.value, it = it.next ())
return it.value
}
// -------------------------------------------------------------------
const MyTreeIterator = node =>
node === undefined
? Return ()
: node.is_dir
? FlatMappedIterator (MyTreeIterator, ArrayIterator (node.children))
: Yield (node.path)
const data =
{path:'/pages/services/project', is_dir:true, children:[
{path:'/pages/services/project/headline',is_dir:false,children:[]},
{path:'/pages/services/project/text',is_dir:false,children:[]},
{path:'/pages/services/project/test/',is_dir:true,children:[
{path:'/pages/services/project/test/text',is_dir:false,children:[]},
{path:'/pages/services/project/test/picture',is_dir:false,children:[]}
]}
]}
// -------------------------------------------------------------------
// example use of generator around our custom persistent iterator
for (const path of Generator (MyTreeIterator (data)))
{
const elem = document.createElement ('p')
elem.textContent = path
document.body.appendChild (elem)
}