Reputation: 39164
I'm trying to write a recursive function that return a List < int * int>, but I'm having some trouble with the syntax.
The function should return an empty list when the recursione ends, otherwise a tuple (int * int) merged with a List returned by a recursive call to itself:
let rec foobar () : List<int * int> =
if (recursionIsEnded) then
[]
else
foobar () :: (10, 10) // this is wrong
// (10,10) works, but I need to concatenate it with the elements returned by foobar recursive call
Could someone explain to me what I'm doing wrong?
EDIT:
I'll try to give more details . Actually my function is a bit more complicated. I'm iterating through a 2d array and build a list of tuples with the array index elements that satisfy a certain condition. Actually that's my code:
let rec GetSameColorNeighs(grid : Option<Ball> [,], row : int , col : int, color : Microsoft.Xna.Framework.Color) : List<int * int> =
if (row < 0 || col < 0 || row > MaxLineNumber - 1 || col > BallsPerLine - 1) then
[]
else
let ball = grid.[row,col]
match ball with
|Some(ball) ->
if (!ball.visited = false || not <| ball.color.Equals(color)) then
[row , col]
else
ball.visited := true
(row,col) ::GetSameColorNeighs(grid, row + 1, col + 1, color) :: GetSameColorNeighs(grid, row - 1, col - 1, color)
|None -> []
So here's 2 more question :):
How modify the following row in order to make it compile?
(row,col) ::GetSameColorNeighs(grid, row + 1, col + 1, color) :: GetSameColorNeighs(grid, row - 1, col - 1, color)
Is there any better way to do that?
I don't care about the list's elements order.
Upvotes: 3
Views: 4066
Reputation: 922
Although it's a simpler function, I hope it helps illustrate the point as your recurse through two lists to build tuples. This function takes two lists as input in the form ([],[]), and pairs each element together as a tuple in a new list.
let rec pairlists twolists =
match twolists with
| ([],[]) -> []
| (x::xs, y::ys) ->
(x,y)::pairlists(xs,ys)
;;
Given two lists of the same length (If they aren't the same length you'll get an error), each recursion will pair the first two elements into a tuple as (x,y). The syntax:
(x,y)::pairlists(xs,ys)
Is really just saying "Build a tuple with our current elements (x,y), then proceed building tuples with the rest of our lists (xs,ys).
Run:
pairlists ([1;2;3], 1;2;3])
And you'll get the output:
[(1,1);(2,2);(3;3)]
Upvotes: 0
Reputation: 243041
Daniel's solution looks good to me, but you don't need to use mutable state (ResizeArray
). Instead, you can write the loop
function as a sequence expression that generates results using yield
and makes recursive calls using yield!
:
let GetSameColorNeighs (grid:Option<Ball>[,], row, col, color:Color) =
let rec loop (row, col) = seq {
if not (row < 0 || col < 0 || row > MaxLineNumber - 1
|| col > BallsPerLine - 1) then
let ball = grid.[row,col]
match ball with
| Some(ball) ->
if (!ball.visited = false || not <| ball.color.Equals(color)) then
// Not sure what you want here - yield items using 'yield'?
// [row , col]
else
ball.visited := true
yield row, col // Add single item to results
yield! loop(row + 1, col + 1) // Add all generated to results
yield! loop(row - 1, col - 1) // -- || --
| None -> () }
loop(row, col) |> Seq.toList
Upvotes: 3
Reputation: 47904
Using an inner function with an accumulator is probably the easiest way to do it (as a bonus it's tail recursive).
let foobar() =
let rec foobar acc =
if (recursionIsEnded) then
List.rev acc
else
foobar ((10, 10)::acc)
foobar []
Based on your update, I'd prefer a mutable solution. Something like
let GetSameColorNeighs(grid : Option<Ball> [,], row : int , col : int, color : Microsoft.Xna.Framework.Color) : List<int * int> =
let results = ResizeArray()
let rec loop (row, col) =
if (row < 0 || col < 0 || row > MaxLineNumber - 1 || col > BallsPerLine - 1) then ()
else
let ball = grid.[row,col]
match ball with
|Some(ball) ->
if (!ball.visited = false || not <| ball.color.Equals(color)) then
[row , col] //doesn't compile, not sure what to do here
else
ball.visited := true
results.Add(row, col)
loop(row + 1, col + 1)
loop(row - 1, col - 1)
|None -> ()
loop(row, col)
results |> Seq.toList
Upvotes: 2
Reputation: 21742
Since foobar returns a list you either have to append the lists or concat and then reverse
(foobar())@(10,10)
however I'd usually do
it the other way around
(10,10)::(foobar()))
and then reverse when you return. the above have a problem with tail recursion so to tweek that you end up with something like:
let foobar () : List<int * int> =
let rec inner acc =
if (recursionIsEnded) then
list.Rev acc
else
inner ((10, 10)::acc)
inner []
reversing in the end should usually result in fewer operations than if you append a list to a list
Upvotes: 1