How to make tree mapping tail-recursive?Tail recursive functions for BinaryTreeWhat is tail recursion?What Is Tail Call Optimization?What's a good way to rewrite this non-tail-recursive function?What are the options for storing hierarchical data in a relational database?Scala: Tree Insert Tail Recursion With Complex StructureHow to append to Rose Trees in ScalaSplitting a BinTree with tail recursion in HaskellTail recursive functions for BinaryTreeTransverse a tree like object in a Tail recursive way in scalaNon-binary tree recursion
There is only s̶i̶x̶t̶y one place he can be
Applicability of Single Responsibility Principle
Is there any reason not to eat food that's been dropped on the surface of the moon?
Cynical novel that describes an America ruled by the media, arms manufacturers, and ethnic figureheads
Personal Teleportation as a Weapon
Implement the Thanos sorting algorithm
Where in the Bible does the greeting ("Dominus Vobiscum") used at Mass come from?
Trouble understanding overseas colleagues
Transcription Beats per minute
How could Frankenstein get the parts for his _second_ creature?
Is it correct to write "is not focus on"?
Best way to store options for panels
Opposite of a diet
I'm in charge of equipment buying but no one's ever happy with what I choose. How to fix this?
What is the oldest known work of fiction?
What to do with wrong results in talks?
Hostile work environment after whistle-blowing on coworker and our boss. What do I do?
Go Pregnant or Go Home
How does it work when somebody invests in my business?
How was Earth single-handedly capable of creating 3 of the 4 gods of chaos?
Can a monster with multiattack use this ability if they are missing a limb?
Can I Retrieve Email Addresses from BCC?
Everything Bob says is false. How does he get people to trust him?
How do I rename a LINUX host without needing to reboot for the rename to take effect?
How to make tree mapping tail-recursive?
Tail recursive functions for BinaryTreeWhat is tail recursion?What Is Tail Call Optimization?What's a good way to rewrite this non-tail-recursive function?What are the options for storing hierarchical data in a relational database?Scala: Tree Insert Tail Recursion With Complex StructureHow to append to Rose Trees in ScalaSplitting a BinTree with tail recursion in HaskellTail recursive functions for BinaryTreeTransverse a tree like object in a Tail recursive way in scalaNon-binary tree recursion
Suppose I have a tree data structure like this:
trait Node val name: String
case class BranchNode(name: String, children: List[Node]) extends Node
case class LeafNode(name: String) extends Node
Suppose also I've got a function to map over leaves:
def mapLeaves(root: Node, f: LeafNode => LeafNode): Node = root match
case ln: LeafNode => f(ln)
case bn: BranchNode => BranchNode(bn.name, bn.children.map(ch => mapLeaves(ch, f)))
Now I am trying to make this function tail-recursive but having a hard time to figure out how to do it. I've read this answer but still don't know to make that binary tree solution work for a multiway tree.
How would you rewrite mapLeaves to make it tail-recursive?
scala recursion functional-programming tree tail-recursion
add a comment |
Suppose I have a tree data structure like this:
trait Node val name: String
case class BranchNode(name: String, children: List[Node]) extends Node
case class LeafNode(name: String) extends Node
Suppose also I've got a function to map over leaves:
def mapLeaves(root: Node, f: LeafNode => LeafNode): Node = root match
case ln: LeafNode => f(ln)
case bn: BranchNode => BranchNode(bn.name, bn.children.map(ch => mapLeaves(ch, f)))
Now I am trying to make this function tail-recursive but having a hard time to figure out how to do it. I've read this answer but still don't know to make that binary tree solution work for a multiway tree.
How would you rewrite mapLeaves to make it tail-recursive?
scala recursion functional-programming tree tail-recursion
1
I think you need some intermediate format, as you see in your mentioned 'answer' - there is a List (which is also the result as a difference to your problem)
– pme
Mar 7 at 13:08
Thanks for the suggestion. I concur I need some "todo list" (as in the mentioned answer) but I cannot figure out how to do it.
– Michael
Mar 7 at 13:41
Can you use cats or scalaz?
– Krzysztof Atłasik
Mar 7 at 14:51
2
I would prefer not to (in this particular case).
– Michael
Mar 7 at 14:53
add a comment |
Suppose I have a tree data structure like this:
trait Node val name: String
case class BranchNode(name: String, children: List[Node]) extends Node
case class LeafNode(name: String) extends Node
Suppose also I've got a function to map over leaves:
def mapLeaves(root: Node, f: LeafNode => LeafNode): Node = root match
case ln: LeafNode => f(ln)
case bn: BranchNode => BranchNode(bn.name, bn.children.map(ch => mapLeaves(ch, f)))
Now I am trying to make this function tail-recursive but having a hard time to figure out how to do it. I've read this answer but still don't know to make that binary tree solution work for a multiway tree.
How would you rewrite mapLeaves to make it tail-recursive?
scala recursion functional-programming tree tail-recursion
Suppose I have a tree data structure like this:
trait Node val name: String
case class BranchNode(name: String, children: List[Node]) extends Node
case class LeafNode(name: String) extends Node
Suppose also I've got a function to map over leaves:
def mapLeaves(root: Node, f: LeafNode => LeafNode): Node = root match
case ln: LeafNode => f(ln)
case bn: BranchNode => BranchNode(bn.name, bn.children.map(ch => mapLeaves(ch, f)))
Now I am trying to make this function tail-recursive but having a hard time to figure out how to do it. I've read this answer but still don't know to make that binary tree solution work for a multiway tree.
How would you rewrite mapLeaves to make it tail-recursive?
scala recursion functional-programming tree tail-recursion
scala recursion functional-programming tree tail-recursion
edited Mar 21 at 11:44
Michael
asked Mar 7 at 11:35
MichaelMichael
15.8k49133256
15.8k49133256
1
I think you need some intermediate format, as you see in your mentioned 'answer' - there is a List (which is also the result as a difference to your problem)
– pme
Mar 7 at 13:08
Thanks for the suggestion. I concur I need some "todo list" (as in the mentioned answer) but I cannot figure out how to do it.
– Michael
Mar 7 at 13:41
Can you use cats or scalaz?
– Krzysztof Atłasik
Mar 7 at 14:51
2
I would prefer not to (in this particular case).
– Michael
Mar 7 at 14:53
add a comment |
1
I think you need some intermediate format, as you see in your mentioned 'answer' - there is a List (which is also the result as a difference to your problem)
– pme
Mar 7 at 13:08
Thanks for the suggestion. I concur I need some "todo list" (as in the mentioned answer) but I cannot figure out how to do it.
– Michael
Mar 7 at 13:41
Can you use cats or scalaz?
– Krzysztof Atłasik
Mar 7 at 14:51
2
I would prefer not to (in this particular case).
– Michael
Mar 7 at 14:53
1
1
I think you need some intermediate format, as you see in your mentioned 'answer' - there is a List (which is also the result as a difference to your problem)
– pme
Mar 7 at 13:08
I think you need some intermediate format, as you see in your mentioned 'answer' - there is a List (which is also the result as a difference to your problem)
– pme
Mar 7 at 13:08
Thanks for the suggestion. I concur I need some "todo list" (as in the mentioned answer) but I cannot figure out how to do it.
– Michael
Mar 7 at 13:41
Thanks for the suggestion. I concur I need some "todo list" (as in the mentioned answer) but I cannot figure out how to do it.
– Michael
Mar 7 at 13:41
Can you use cats or scalaz?
– Krzysztof Atłasik
Mar 7 at 14:51
Can you use cats or scalaz?
– Krzysztof Atłasik
Mar 7 at 14:51
2
2
I would prefer not to (in this particular case).
– Michael
Mar 7 at 14:53
I would prefer not to (in this particular case).
– Michael
Mar 7 at 14:53
add a comment |
2 Answers
2
active
oldest
votes
"Call stack" and "recursion" are merely popular design patterns that later got incorporated into most programming languages (and thus became mostly "invisible"). There is nothing that prevents you from reimplementing both with heap data structures. So, here is "the obvious" 1960's TAOCP retro-style solution:
trait Node val name: String
case class BranchNode(name: String, children: List[Node]) extends Node
case class LeafNode(name: String) extends Node
def mapLeaves(root: Node, f: LeafNode => LeafNode): Node =
case class Frame(name: String, mapped: List[Node], todos: List[Node])
@annotation.tailrec
def step(stack: List[Frame]): Node = stack match
// "return / pop a stack-frame"
case Frame(name, done, Nil) :: tail =>
val ret = BranchNode(name, done.reverse)
tail match
case Nil => ret
case Frame(tn, td, tt) :: more =>
step(Frame(tn, ret :: td, tt) :: more)
case Frame(name, done, x :: xs) :: tail => x match
// "recursion base"
case l @ LeafNode(_) => step(Frame(name, f(l) :: done, xs) :: tail)
// "recursive call"
case BranchNode(n, cs) => step(Frame(n, Nil, cs) :: Frame(name, done, xs) :: tail)
case Nil => throw new Error("shouldn't happen")
root match
case l @ LeafNode(_) => f(l)
case b @ BranchNode(n, cs) => step(List(Frame(n, Nil, cs)))
The tail-recursive step function takes a reified stack with "stack frames". A "stack frame" stores the name of the branch node that is currently being processed, a list of child nodes that have already been processed, and the list of the remaining nodes that still must be processed later. This roughly corresponds to an actual stack frame of your recursive mapLeaves function.
With this data structure,
- returning from recursive calls corresponds to deconstructing a
Frameobject, and either returning the final result, or at least making thestackone frame shorter. - recursive calls correspond to a step that prepends a
Frameto thestack - base case (invoking
fon leaves) does not create or remove any frames
Once one understands how the usually invisible stack frames are represented explicitly, the translation is straightforward and mostly mechanical.
Example:
val example = BranchNode("x", List(
BranchNode("y", List(
LeafNode("a"),
LeafNode("b")
)),
BranchNode("z", List(
LeafNode("c"),
BranchNode("v", List(
LeafNode("d"),
LeafNode("e")
))
))
))
println(mapLeaves(example, case LeafNode(n) => LeafNode(n.toUpperCase) ))
Output (indented):
BranchNode(x,List(
BranchNode(y,List(
LeafNode(A),
LeafNode(B)
)),
BranchNode(z, List(
LeafNode(C),
BranchNode(v,List(
LeafNode(D),
LeafNode(E)
))
))
))
Thanks a lot for so detailed answer. I understand how to implement call stack explicitly. Now I am just wondering how to simplify this solution.
– Michael
Mar 8 at 12:27
add a comment |
It might be easier to implement it using a technique called trampoline.
If you use it, you'd be able to use two functions calling itself doing mutual recursion (with tailrec, you are limited to one function). Similarly to tailrec this recursion will be transformed to plain loop.
Trampolines are implemented in scala standard library in scala.util.control.TailCalls.
import scala.util.control.TailCalls.TailRec, done, tailcall
def mapLeaves(root: Node, f: LeafNode => LeafNode): Node =
//two inner functions doing mutual recursion
//iterates recursively over children of node
def iterate(nodes: List[Node]): TailRec[List[Node]] =
nodes match
case x :: xs => tailcall(deepMap(x)) //it calls with mutual recursion deepMap which maps over children of node
.flatMap(node => iterate(xs).map(node :: _)) //you can flat map over TailRec
case Nil => done(Nil)
//recursively visits all branches
def deepMap(node: Node): TailRec[Node] =
node match
case ln: LeafNode => done(f(ln))
case bn: BranchNode => tailcall(iterate(bn.children))
.map(BranchNode(bn.name, _)) //calls mutually iterate
deepMap(root).result //unwrap result to plain node
Instead of TailCalls you could also use Eval from Cats or Trampoline from scalaz.
With that implementation function worked without problems:
def build(counter: Int): Node =
if (counter > 0)
BranchNode("branch", List(build(counter-1)))
else
LeafNode("leaf")
val root = build(4000)
mapLeaves(root, x => x.copy(name = x.name.reverse)) // no problems
When I ran that example with your implementation it caused java.lang.StackOverflowError as expected.
1
Thanks for introducing trampolines. I need to learn about them.
– Michael
Mar 8 at 8:38
add a comment |
Your Answer
StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");
StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "1"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);
else
createEditor();
);
function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader:
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
,
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);
);
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f55042834%2fhow-to-make-tree-mapping-tail-recursive%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
"Call stack" and "recursion" are merely popular design patterns that later got incorporated into most programming languages (and thus became mostly "invisible"). There is nothing that prevents you from reimplementing both with heap data structures. So, here is "the obvious" 1960's TAOCP retro-style solution:
trait Node val name: String
case class BranchNode(name: String, children: List[Node]) extends Node
case class LeafNode(name: String) extends Node
def mapLeaves(root: Node, f: LeafNode => LeafNode): Node =
case class Frame(name: String, mapped: List[Node], todos: List[Node])
@annotation.tailrec
def step(stack: List[Frame]): Node = stack match
// "return / pop a stack-frame"
case Frame(name, done, Nil) :: tail =>
val ret = BranchNode(name, done.reverse)
tail match
case Nil => ret
case Frame(tn, td, tt) :: more =>
step(Frame(tn, ret :: td, tt) :: more)
case Frame(name, done, x :: xs) :: tail => x match
// "recursion base"
case l @ LeafNode(_) => step(Frame(name, f(l) :: done, xs) :: tail)
// "recursive call"
case BranchNode(n, cs) => step(Frame(n, Nil, cs) :: Frame(name, done, xs) :: tail)
case Nil => throw new Error("shouldn't happen")
root match
case l @ LeafNode(_) => f(l)
case b @ BranchNode(n, cs) => step(List(Frame(n, Nil, cs)))
The tail-recursive step function takes a reified stack with "stack frames". A "stack frame" stores the name of the branch node that is currently being processed, a list of child nodes that have already been processed, and the list of the remaining nodes that still must be processed later. This roughly corresponds to an actual stack frame of your recursive mapLeaves function.
With this data structure,
- returning from recursive calls corresponds to deconstructing a
Frameobject, and either returning the final result, or at least making thestackone frame shorter. - recursive calls correspond to a step that prepends a
Frameto thestack - base case (invoking
fon leaves) does not create or remove any frames
Once one understands how the usually invisible stack frames are represented explicitly, the translation is straightforward and mostly mechanical.
Example:
val example = BranchNode("x", List(
BranchNode("y", List(
LeafNode("a"),
LeafNode("b")
)),
BranchNode("z", List(
LeafNode("c"),
BranchNode("v", List(
LeafNode("d"),
LeafNode("e")
))
))
))
println(mapLeaves(example, case LeafNode(n) => LeafNode(n.toUpperCase) ))
Output (indented):
BranchNode(x,List(
BranchNode(y,List(
LeafNode(A),
LeafNode(B)
)),
BranchNode(z, List(
LeafNode(C),
BranchNode(v,List(
LeafNode(D),
LeafNode(E)
))
))
))
Thanks a lot for so detailed answer. I understand how to implement call stack explicitly. Now I am just wondering how to simplify this solution.
– Michael
Mar 8 at 12:27
add a comment |
"Call stack" and "recursion" are merely popular design patterns that later got incorporated into most programming languages (and thus became mostly "invisible"). There is nothing that prevents you from reimplementing both with heap data structures. So, here is "the obvious" 1960's TAOCP retro-style solution:
trait Node val name: String
case class BranchNode(name: String, children: List[Node]) extends Node
case class LeafNode(name: String) extends Node
def mapLeaves(root: Node, f: LeafNode => LeafNode): Node =
case class Frame(name: String, mapped: List[Node], todos: List[Node])
@annotation.tailrec
def step(stack: List[Frame]): Node = stack match
// "return / pop a stack-frame"
case Frame(name, done, Nil) :: tail =>
val ret = BranchNode(name, done.reverse)
tail match
case Nil => ret
case Frame(tn, td, tt) :: more =>
step(Frame(tn, ret :: td, tt) :: more)
case Frame(name, done, x :: xs) :: tail => x match
// "recursion base"
case l @ LeafNode(_) => step(Frame(name, f(l) :: done, xs) :: tail)
// "recursive call"
case BranchNode(n, cs) => step(Frame(n, Nil, cs) :: Frame(name, done, xs) :: tail)
case Nil => throw new Error("shouldn't happen")
root match
case l @ LeafNode(_) => f(l)
case b @ BranchNode(n, cs) => step(List(Frame(n, Nil, cs)))
The tail-recursive step function takes a reified stack with "stack frames". A "stack frame" stores the name of the branch node that is currently being processed, a list of child nodes that have already been processed, and the list of the remaining nodes that still must be processed later. This roughly corresponds to an actual stack frame of your recursive mapLeaves function.
With this data structure,
- returning from recursive calls corresponds to deconstructing a
Frameobject, and either returning the final result, or at least making thestackone frame shorter. - recursive calls correspond to a step that prepends a
Frameto thestack - base case (invoking
fon leaves) does not create or remove any frames
Once one understands how the usually invisible stack frames are represented explicitly, the translation is straightforward and mostly mechanical.
Example:
val example = BranchNode("x", List(
BranchNode("y", List(
LeafNode("a"),
LeafNode("b")
)),
BranchNode("z", List(
LeafNode("c"),
BranchNode("v", List(
LeafNode("d"),
LeafNode("e")
))
))
))
println(mapLeaves(example, case LeafNode(n) => LeafNode(n.toUpperCase) ))
Output (indented):
BranchNode(x,List(
BranchNode(y,List(
LeafNode(A),
LeafNode(B)
)),
BranchNode(z, List(
LeafNode(C),
BranchNode(v,List(
LeafNode(D),
LeafNode(E)
))
))
))
Thanks a lot for so detailed answer. I understand how to implement call stack explicitly. Now I am just wondering how to simplify this solution.
– Michael
Mar 8 at 12:27
add a comment |
"Call stack" and "recursion" are merely popular design patterns that later got incorporated into most programming languages (and thus became mostly "invisible"). There is nothing that prevents you from reimplementing both with heap data structures. So, here is "the obvious" 1960's TAOCP retro-style solution:
trait Node val name: String
case class BranchNode(name: String, children: List[Node]) extends Node
case class LeafNode(name: String) extends Node
def mapLeaves(root: Node, f: LeafNode => LeafNode): Node =
case class Frame(name: String, mapped: List[Node], todos: List[Node])
@annotation.tailrec
def step(stack: List[Frame]): Node = stack match
// "return / pop a stack-frame"
case Frame(name, done, Nil) :: tail =>
val ret = BranchNode(name, done.reverse)
tail match
case Nil => ret
case Frame(tn, td, tt) :: more =>
step(Frame(tn, ret :: td, tt) :: more)
case Frame(name, done, x :: xs) :: tail => x match
// "recursion base"
case l @ LeafNode(_) => step(Frame(name, f(l) :: done, xs) :: tail)
// "recursive call"
case BranchNode(n, cs) => step(Frame(n, Nil, cs) :: Frame(name, done, xs) :: tail)
case Nil => throw new Error("shouldn't happen")
root match
case l @ LeafNode(_) => f(l)
case b @ BranchNode(n, cs) => step(List(Frame(n, Nil, cs)))
The tail-recursive step function takes a reified stack with "stack frames". A "stack frame" stores the name of the branch node that is currently being processed, a list of child nodes that have already been processed, and the list of the remaining nodes that still must be processed later. This roughly corresponds to an actual stack frame of your recursive mapLeaves function.
With this data structure,
- returning from recursive calls corresponds to deconstructing a
Frameobject, and either returning the final result, or at least making thestackone frame shorter. - recursive calls correspond to a step that prepends a
Frameto thestack - base case (invoking
fon leaves) does not create or remove any frames
Once one understands how the usually invisible stack frames are represented explicitly, the translation is straightforward and mostly mechanical.
Example:
val example = BranchNode("x", List(
BranchNode("y", List(
LeafNode("a"),
LeafNode("b")
)),
BranchNode("z", List(
LeafNode("c"),
BranchNode("v", List(
LeafNode("d"),
LeafNode("e")
))
))
))
println(mapLeaves(example, case LeafNode(n) => LeafNode(n.toUpperCase) ))
Output (indented):
BranchNode(x,List(
BranchNode(y,List(
LeafNode(A),
LeafNode(B)
)),
BranchNode(z, List(
LeafNode(C),
BranchNode(v,List(
LeafNode(D),
LeafNode(E)
))
))
))
"Call stack" and "recursion" are merely popular design patterns that later got incorporated into most programming languages (and thus became mostly "invisible"). There is nothing that prevents you from reimplementing both with heap data structures. So, here is "the obvious" 1960's TAOCP retro-style solution:
trait Node val name: String
case class BranchNode(name: String, children: List[Node]) extends Node
case class LeafNode(name: String) extends Node
def mapLeaves(root: Node, f: LeafNode => LeafNode): Node =
case class Frame(name: String, mapped: List[Node], todos: List[Node])
@annotation.tailrec
def step(stack: List[Frame]): Node = stack match
// "return / pop a stack-frame"
case Frame(name, done, Nil) :: tail =>
val ret = BranchNode(name, done.reverse)
tail match
case Nil => ret
case Frame(tn, td, tt) :: more =>
step(Frame(tn, ret :: td, tt) :: more)
case Frame(name, done, x :: xs) :: tail => x match
// "recursion base"
case l @ LeafNode(_) => step(Frame(name, f(l) :: done, xs) :: tail)
// "recursive call"
case BranchNode(n, cs) => step(Frame(n, Nil, cs) :: Frame(name, done, xs) :: tail)
case Nil => throw new Error("shouldn't happen")
root match
case l @ LeafNode(_) => f(l)
case b @ BranchNode(n, cs) => step(List(Frame(n, Nil, cs)))
The tail-recursive step function takes a reified stack with "stack frames". A "stack frame" stores the name of the branch node that is currently being processed, a list of child nodes that have already been processed, and the list of the remaining nodes that still must be processed later. This roughly corresponds to an actual stack frame of your recursive mapLeaves function.
With this data structure,
- returning from recursive calls corresponds to deconstructing a
Frameobject, and either returning the final result, or at least making thestackone frame shorter. - recursive calls correspond to a step that prepends a
Frameto thestack - base case (invoking
fon leaves) does not create or remove any frames
Once one understands how the usually invisible stack frames are represented explicitly, the translation is straightforward and mostly mechanical.
Example:
val example = BranchNode("x", List(
BranchNode("y", List(
LeafNode("a"),
LeafNode("b")
)),
BranchNode("z", List(
LeafNode("c"),
BranchNode("v", List(
LeafNode("d"),
LeafNode("e")
))
))
))
println(mapLeaves(example, case LeafNode(n) => LeafNode(n.toUpperCase) ))
Output (indented):
BranchNode(x,List(
BranchNode(y,List(
LeafNode(A),
LeafNode(B)
)),
BranchNode(z, List(
LeafNode(C),
BranchNode(v,List(
LeafNode(D),
LeafNode(E)
))
))
))
edited Mar 7 at 16:10
answered Mar 7 at 15:26
Andrey TyukinAndrey Tyukin
30k42351
30k42351
Thanks a lot for so detailed answer. I understand how to implement call stack explicitly. Now I am just wondering how to simplify this solution.
– Michael
Mar 8 at 12:27
add a comment |
Thanks a lot for so detailed answer. I understand how to implement call stack explicitly. Now I am just wondering how to simplify this solution.
– Michael
Mar 8 at 12:27
Thanks a lot for so detailed answer. I understand how to implement call stack explicitly. Now I am just wondering how to simplify this solution.
– Michael
Mar 8 at 12:27
Thanks a lot for so detailed answer. I understand how to implement call stack explicitly. Now I am just wondering how to simplify this solution.
– Michael
Mar 8 at 12:27
add a comment |
It might be easier to implement it using a technique called trampoline.
If you use it, you'd be able to use two functions calling itself doing mutual recursion (with tailrec, you are limited to one function). Similarly to tailrec this recursion will be transformed to plain loop.
Trampolines are implemented in scala standard library in scala.util.control.TailCalls.
import scala.util.control.TailCalls.TailRec, done, tailcall
def mapLeaves(root: Node, f: LeafNode => LeafNode): Node =
//two inner functions doing mutual recursion
//iterates recursively over children of node
def iterate(nodes: List[Node]): TailRec[List[Node]] =
nodes match
case x :: xs => tailcall(deepMap(x)) //it calls with mutual recursion deepMap which maps over children of node
.flatMap(node => iterate(xs).map(node :: _)) //you can flat map over TailRec
case Nil => done(Nil)
//recursively visits all branches
def deepMap(node: Node): TailRec[Node] =
node match
case ln: LeafNode => done(f(ln))
case bn: BranchNode => tailcall(iterate(bn.children))
.map(BranchNode(bn.name, _)) //calls mutually iterate
deepMap(root).result //unwrap result to plain node
Instead of TailCalls you could also use Eval from Cats or Trampoline from scalaz.
With that implementation function worked without problems:
def build(counter: Int): Node =
if (counter > 0)
BranchNode("branch", List(build(counter-1)))
else
LeafNode("leaf")
val root = build(4000)
mapLeaves(root, x => x.copy(name = x.name.reverse)) // no problems
When I ran that example with your implementation it caused java.lang.StackOverflowError as expected.
1
Thanks for introducing trampolines. I need to learn about them.
– Michael
Mar 8 at 8:38
add a comment |
It might be easier to implement it using a technique called trampoline.
If you use it, you'd be able to use two functions calling itself doing mutual recursion (with tailrec, you are limited to one function). Similarly to tailrec this recursion will be transformed to plain loop.
Trampolines are implemented in scala standard library in scala.util.control.TailCalls.
import scala.util.control.TailCalls.TailRec, done, tailcall
def mapLeaves(root: Node, f: LeafNode => LeafNode): Node =
//two inner functions doing mutual recursion
//iterates recursively over children of node
def iterate(nodes: List[Node]): TailRec[List[Node]] =
nodes match
case x :: xs => tailcall(deepMap(x)) //it calls with mutual recursion deepMap which maps over children of node
.flatMap(node => iterate(xs).map(node :: _)) //you can flat map over TailRec
case Nil => done(Nil)
//recursively visits all branches
def deepMap(node: Node): TailRec[Node] =
node match
case ln: LeafNode => done(f(ln))
case bn: BranchNode => tailcall(iterate(bn.children))
.map(BranchNode(bn.name, _)) //calls mutually iterate
deepMap(root).result //unwrap result to plain node
Instead of TailCalls you could also use Eval from Cats or Trampoline from scalaz.
With that implementation function worked without problems:
def build(counter: Int): Node =
if (counter > 0)
BranchNode("branch", List(build(counter-1)))
else
LeafNode("leaf")
val root = build(4000)
mapLeaves(root, x => x.copy(name = x.name.reverse)) // no problems
When I ran that example with your implementation it caused java.lang.StackOverflowError as expected.
1
Thanks for introducing trampolines. I need to learn about them.
– Michael
Mar 8 at 8:38
add a comment |
It might be easier to implement it using a technique called trampoline.
If you use it, you'd be able to use two functions calling itself doing mutual recursion (with tailrec, you are limited to one function). Similarly to tailrec this recursion will be transformed to plain loop.
Trampolines are implemented in scala standard library in scala.util.control.TailCalls.
import scala.util.control.TailCalls.TailRec, done, tailcall
def mapLeaves(root: Node, f: LeafNode => LeafNode): Node =
//two inner functions doing mutual recursion
//iterates recursively over children of node
def iterate(nodes: List[Node]): TailRec[List[Node]] =
nodes match
case x :: xs => tailcall(deepMap(x)) //it calls with mutual recursion deepMap which maps over children of node
.flatMap(node => iterate(xs).map(node :: _)) //you can flat map over TailRec
case Nil => done(Nil)
//recursively visits all branches
def deepMap(node: Node): TailRec[Node] =
node match
case ln: LeafNode => done(f(ln))
case bn: BranchNode => tailcall(iterate(bn.children))
.map(BranchNode(bn.name, _)) //calls mutually iterate
deepMap(root).result //unwrap result to plain node
Instead of TailCalls you could also use Eval from Cats or Trampoline from scalaz.
With that implementation function worked without problems:
def build(counter: Int): Node =
if (counter > 0)
BranchNode("branch", List(build(counter-1)))
else
LeafNode("leaf")
val root = build(4000)
mapLeaves(root, x => x.copy(name = x.name.reverse)) // no problems
When I ran that example with your implementation it caused java.lang.StackOverflowError as expected.
It might be easier to implement it using a technique called trampoline.
If you use it, you'd be able to use two functions calling itself doing mutual recursion (with tailrec, you are limited to one function). Similarly to tailrec this recursion will be transformed to plain loop.
Trampolines are implemented in scala standard library in scala.util.control.TailCalls.
import scala.util.control.TailCalls.TailRec, done, tailcall
def mapLeaves(root: Node, f: LeafNode => LeafNode): Node =
//two inner functions doing mutual recursion
//iterates recursively over children of node
def iterate(nodes: List[Node]): TailRec[List[Node]] =
nodes match
case x :: xs => tailcall(deepMap(x)) //it calls with mutual recursion deepMap which maps over children of node
.flatMap(node => iterate(xs).map(node :: _)) //you can flat map over TailRec
case Nil => done(Nil)
//recursively visits all branches
def deepMap(node: Node): TailRec[Node] =
node match
case ln: LeafNode => done(f(ln))
case bn: BranchNode => tailcall(iterate(bn.children))
.map(BranchNode(bn.name, _)) //calls mutually iterate
deepMap(root).result //unwrap result to plain node
Instead of TailCalls you could also use Eval from Cats or Trampoline from scalaz.
With that implementation function worked without problems:
def build(counter: Int): Node =
if (counter > 0)
BranchNode("branch", List(build(counter-1)))
else
LeafNode("leaf")
val root = build(4000)
mapLeaves(root, x => x.copy(name = x.name.reverse)) // no problems
When I ran that example with your implementation it caused java.lang.StackOverflowError as expected.
edited Mar 8 at 14:42
answered Mar 7 at 15:41
Krzysztof AtłasikKrzysztof Atłasik
4,79711234
4,79711234
1
Thanks for introducing trampolines. I need to learn about them.
– Michael
Mar 8 at 8:38
add a comment |
1
Thanks for introducing trampolines. I need to learn about them.
– Michael
Mar 8 at 8:38
1
1
Thanks for introducing trampolines. I need to learn about them.
– Michael
Mar 8 at 8:38
Thanks for introducing trampolines. I need to learn about them.
– Michael
Mar 8 at 8:38
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f55042834%2fhow-to-make-tree-mapping-tail-recursive%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
1
I think you need some intermediate format, as you see in your mentioned 'answer' - there is a List (which is also the result as a difference to your problem)
– pme
Mar 7 at 13:08
Thanks for the suggestion. I concur I need some "todo list" (as in the mentioned answer) but I cannot figure out how to do it.
– Michael
Mar 7 at 13:41
Can you use cats or scalaz?
– Krzysztof Atłasik
Mar 7 at 14:51
2
I would prefer not to (in this particular case).
– Michael
Mar 7 at 14:53