F# Immutable variable sized window data structure - data-structures

I have below a description of a data structure I need and I want to implement it using immutable data structures. I'm trying to determine... is there an existing data structure out there that will support what I'm trying to do here or do I need to create one--and if I need to create it, what would be a good place to start (building blocks)?
I have a steady stream of incoming values of a certain type. I want to add them to a persistent/immutable data structure to hold a history of them, and on each add, it will review the history and determine if one or more oldest items will be removed (for example, if the history is > a certain length or a value has a certain property).

Without knowing more about your requirements, I'd just say a vanilla Set<'a> does a more than adequate job. I'd prefer a 'Set' over a 'List' so you always have O(lg n) access to the largest and smallest items, allowing you to ordered your set by insert date/time for efficient access to the newest and oldest items.
Seems very easy to wrap up a set so that its Add/Remove methods invoke your callbacks:
type AwesomeSet(internalSet : Set<'a>, insertCallback : 'a -> unit, removeCallback : 'a -> unit) =
member this.Add(x) =
insertCallback(x)
AwesomeSet(internalSet.Add x, insertCallback, removeCallback)
member this.Remove(x) =
removeCallback(x)
AwesomeSet(internalSet.Remove x, insertCallback, removeCallback)
member this.Count = internalSet.Count
member this.Min = internalSet.MinimumElement
member this.Max = internalSet.MaximumElement

Thanks to Juliet's kind information, I have implemented what I need and I put it here in case anyone else might find it useful.
let rec removeLast (s : Set<'a>, num : int) : Set<'a> =
match num with
| 0 -> s
| _ -> removeLast(s.Remove(s.MinimumElement), num-1)
type History<'a when 'a : comparison>(underlying : Set<'a>, removal : History<'a> -> int) =
member this.Add(x) =
History(removeLast(underlying, removal(this)).Add x, removal)
member this.Count = underlying.Count
member this.Min = underlying.MinimumElement
member this.Max = underlying.MaximumElement
member this.Under = underlying
let maxHist = 2
let maxCountRemover (h : History<int>) =
if h.Count >= maxHist
then h.Count - maxHist + 1
else 0
let testHistory =
let s = History(Set.empty, r)
let s = s.Add(1);
printfn "%i: %i - %i" s.Count s.Min s.Max
let s = s.Add(2);
printfn "%i: %i - %i" s.Count s.Min s.Max
let s = s.Add(3);
printfn "%i: %i - %i" s.Count s.Min s.Max
let s = s.Add(4);
printfn "%i: %i - %i" s.Count s.Min s.Max
let s = s.Add(5);
printfn "%i: %i - %i" s.Count s.Min s.Max
printfn "%A" s.Under

Related

Getting a random number in a function in OCAML OR Telling compiler to evaluate function each time

I'm new to OCAML and was playing around with putting a marker on a random 5X5 square. I've written the example program below. "silly_method1" works but notice that it takes an argument. I don't really have argument to pass in for what I want. I'm just asking for a random number to create my robot on a particular square:
let create = {x = ( Random.int 4); y=3; face = North}
However, I get the same location each time. This makes sense to me... sort of. I'm assuming that the way I've set it up, "create" is basically a constant. It's evaluated once and that's it! I've fixed it below in silly_method2 but look how ugly it is!
let silly_method2 _ = (Random.int 10)
Every time I have to call it, I have to pass in an argument even though I'm not really using it.
What is the correct way to do this? There must be some way to have a function that takes no arguments and passes back a random number (or random tuple, etc.)
And possibly related... Is there a way to tell OCaml not to evaluate the function once and save the result but rather recalculate the answer each time?
Thank you for your patience with me!
Dave
let _ = Random.self_init()
let silly_method1 x = x + (Random.int 10)
let silly_method2 _ = (Random.int 10)
let report1 x = (print_newline(); print_string("report1 begin: "); print_int (silly_method1 x); print_string("report1 end"); print_newline(); )
let report2 y = (print_newline(); print_string("report2 begin: "); print_int(silly_method2 y ); print_string("report2 end"); print_newline(); )
let _ = report1 3
let _ = report1 3
let _ = report1 3
let _ = report2 3
let _ = report2 3
let _ = report2 3
The idiomatic way to define a function in OCaml that doesn't take an argument is to have the argument be (), which is a value (the only value) of type unit:
# let f () = Random.int 10;;
val f : unit -> int = <fun>
# f ();;
- : int = 5
# f ();;
- : int = 2
OCaml doesn't save function results for later re-use. If you want this behavior you have to ask for it explicitly using lazy.

How to wrap last/first element making building interpolation?

I've this code that iterate some samples and build a simple linear interpolation between the points:
foreach sample:
base = floor(index_pointer)
frac = index_pointer - base
out = in[base] * (1 - frac) + in[base + 1] * frac
index_pointer += speed
// restart
if(index_pointer >= sample_length)
{
index_pointer = 0
}
using "speed" equal to 1, the game is done. But if the index_pointer is different than 1 (i.e. got fractional part) I need to wrap last/first element keeping the translation consistent.
How would you do this? Double indexes?
Here's an example of values I have. Let say in array of 4 values: [8, 12, 16, 20].
It will be:
1.0*in[0] + 0.0*in[1]=8
0.28*in[0] + 0.72*in[1]=10.88
0.56*in[1] + 0.44*in[2]=13.76
0.84*in[2] + 0.14*in[3]=16.64
0.12*in[2] + 0.88*in[3]=19.52
0.4*in[3] + 0.6*in[4]=8 // wrong; here I need to wrapper
the last point is wrong. [4] will be 0 because I don't have [4], but the first part need to take care of 0.4 and the weight of first sample (I think?).
Just wrap around the indices:
out = in[base] * (1 - frac) + in[(base + 1) % N] * frac
, where % is the modulo operator and N is the number of input samples.
This procedure generates the following line for your sample data (the dashed lines are the interpolated sample points, the circles are the input values):
I think I understand the problem now (answer only applies if I really did...):
You sample values at a nominal speed sn. But actually your sampler samples at a real speed s, where s != sn. Now, you want to create a function which re-samples the series, sampled at speed s, so it yields a series as if it were sampled with speed sn by means of linear interpolation between 2 adjacent samples. Or, your sampler jitters (has variances in time when it actually samples, which is sn + Noise(sn)).
Here is my approach - a function named "re-sample". It takes the sample data and a list of desired re-sample-points.
For any re-sample point which would index outside the raw data, it returns the respective border value.
let resample (data : float array) times =
let N = Array.length data
let maxIndex = N-1
let weight (t : float) =
t - (floor t)
let interpolate x1 x2 w = x1 * (1.0 - w) + x2 * w
let interp t1 t2 w =
//printfn "t1 = %d t2 = %d w = %f" t1 t2 w
interpolate (data.[t1]) (data.[t2]) w
let inter t =
let t1 = int (floor t)
match t1 with
| x when x >= 0 && x < maxIndex ->
let t2 = t1 + 1
interp t1 t2 (weight t)
| x when x >= maxIndex -> data.[maxIndex]
| _ -> data.[0]
times
|> List.map (fun t -> t, inter t)
|> Array.ofList
let raw_data = [8; 12; 16; 20] |> List.map float |> Array.ofList
let resampled = resample raw_data [0.0..0.2..4.0]
And yields:
val resample : data:float array -> times:float list -> (float * float) []
val raw_data : float [] = [|8.0; 12.0; 16.0; 20.0|]
val resampled : (float * float) [] =
[|(0.0, 8.0); (0.2, 8.8); (0.4, 9.6); (0.6, 10.4); (0.8, 11.2); (1.0, 12.0);
(1.2, 12.8); (1.4, 13.6); (1.6, 14.4); (1.8, 15.2); (2.0, 16.0);
(2.2, 16.8); (2.4, 17.6); (2.6, 18.4); (2.8, 19.2); (3.0, 20.0);
(3.2, 20.0); (3.4, 20.0); (3.6, 20.0); (3.8, 20.0); (4.0, 20.0)|]
Now, I still fail to understand the "wrap around" part of your question. In the end, interpolation - in contrast to extrapolation is only defined for values in [0..N-1]. So it is up to you to decide if the function should produce a run time error or simply use the edge values (or 0) for time values out of bounds of your raw data array.
EDIT
As it turned out, it is about how to use a cyclic (ring) buffer for this as well.
Here, a version of the resample function, using a cyclic buffer. Along with some operations.
update adds a new sample value to the ring buffer
read reads the content a ring buffer element as if it were a normal array, indexed from [0..N-1].
initXXX functions which create the ring buffer in various forms.
length which returns the length or capacity of the ring buffer.
The ring buffer logics is factored into a module to keep it all clean.
module Cyclic =
let wrap n x = x % n // % is modulo operator, just like in C/C++
type Series = { A : float array; WritePosition : int }
let init (n : int) =
{ A = Array.init n (fun i -> 0.);
WritePosition = 0
}
let initFromArray a =
let n = Array.length a
{ A = Array.copy a;
WritePosition = 0
}
let initUseArray a =
let n = Array.length a
{ A = a;
WritePosition = 0
}
let update (sample : float ) (series : Series) =
let wrapper = wrap (Array.length series.A)
series.A.[series.WritePosition] <- sample
{ series with
WritePosition = wrapper (series.WritePosition + 1) }
let read i series =
let n = Array.length series.A
let wrapper = wrap (Array.length series.A)
series.A.[wrapper (series.WritePosition + i)]
let length (series : Series) = Array.length (series.A)
let resampleSeries (data : Cyclic.Series) times =
let N = Cyclic.length data
let maxIndex = N-1
let weight (t : float) =
t - (floor t)
let interpolate x1 x2 w = x1 * (1.0 - w) + x2 * w
let interp t1 t2 w =
interpolate (Cyclic.read t1 data) (Cyclic.read t2 data) w
let inter t =
let t1 = int (floor t)
match t1 with
| x when x >= 0 && x < maxIndex ->
let t2 = t1 + 1
interp t1 t2 (weight t)
| x when x >= maxIndex -> Cyclic.read maxIndex data
| _ -> Cyclic.read 0 data
times
|> List.map (fun t -> t, inter t)
|> Array.ofList
let input = raw_data
let rawSeries0 = Cyclic.initFromArray input
(resampleSeries rawSeries0 [0.0..0.2..4.0]) = resampled

Grid with obstacles coverage algorithm

I have to find an algorithm for a robot Agent to do the following (I'm sorry, I don't really know how to call it):
The robot is on a 10x10 grid with obstacles (each square is either a obstacle or traversable)
The robot has a bump sensor : it activates when the robot hits an obstacle.
On the grid there are carrots that are continously growing. There are fast-growing squares and slow growing squares.
Each step, the robot can : advance or turn 90° right or left or stay in place
The locations of the carrots and obstacles are not know before hand
The carrots continue growing while the robot is moving (even after harvest)
Carrots grow in most squares that are not obstacles
The robot does not know if the squares are fast or slow growing
In each square there can be between 0 and 20 carrots. At each time instance, there is a probability p = 0.01 (or p = 0.02 for fast-growing squares) for the amount of carrots of a square to increment
You can measure the amount of carrots you harvest.
The goal is to get the maximum amount of carrots in 2000 steps.
Would there be a lazy/easy way to do it?
So far, I am a bit lost, as it is not a maze-solving problem. Would it be a sort a flood-filling algorithm ? Is there anything simpler ?
I'm not necessarily searching to "solve" the problem, but rather for an easy approximation if possible
It is indeed a bit of work to find a robot implementation which has the perfect strategy, given that it does not know the location and the number of the food sources.
Any given strategy of a bot might not yield the maximum possible harvest in each run. So the question is rather, which strategy is most successful over a number of simulation runs.
To find a decent strategy for a given statistical distribution of square types (P(fastFood),P(slowFood),P(obstacle)), one might come up with the following idea:
Let Bot(npatch) be a bot which looks for npatch food spots. With the strategy to eat up what it finds in the first food patch before it searches the second and so on. When it visited npatch food sources (or found no more food patches), it returns to the first one found and re-harvests.
This class of bots (Bot(npatch)) can now compete against each other in a statistically relevant number of simulation runs. Best bot is winner of the competition.
This approach can be considered inspired by genetic algorithms, yet without mixing any genes but simply iterating all of them (1..npatch). Maybe someone has an idea how to turn this idea to a fully genetic algorithm. This could involve turning to a Bot(npatch,searchStrategy) and then, having multiple genes to apply a genetic algorithm.
Whenever the parameters of the simulation change, the competition has to be repeated, obviously as depending on the number of food patches in the world, it might or might not pay off to go find yet another food patch if some food patches are known already.
The code below is written in F# and is the simulator for that question (if I got all requirements right, that is...). Writing a new bot is as simple as writing a function, which is then passed to the simulator.
Consider this my easter egg for those of you who would like to try their own bots.
The 2 bots I wrote are called "marvinRobot" which does what Marvin would do and "lazyRobot" a bot which camps on the first food source it finds.
type Square =
| Empty
| Obstacle
| Food of float * (float -> float) // available * growth
| Unknown
let rnd = new System.Random()
let grow p a =
let r = rnd.NextDouble()
if r < p then a + 1.0
else a
let slowGrowth a = grow 0.01 a
let fastGrowth a = grow 0.02 a
let eatPerTick = 1.0
let maxFoodPerSquare = 20.0
let randomPick values =
let count = List.length values
let r = rnd.Next(0,count-1)
values.Item(r)
type World = Square[,]
let randomSquare pobstacle pfood =
let r = rnd.NextDouble()
match r with
| x1 when x1 < pobstacle -> Obstacle
| x2 when x2 < (pobstacle + pfood) && x2 >= pobstacle ->
Food(rnd.NextDouble() * maxFoodPerSquare, randomPick [slowGrowth; fastGrowth])
| _ -> Empty
let createRandomWorld n pobstacle pfood =
Array2D.init n n (fun col row -> randomSquare pobstacle pfood)
let createUnknownWorld n =
Array2D.create n n Unknown
type Position = { Column : int; Row : int }
type RoboState = { Memory : Square[,]; Pos : Position; Heading : Position }
type RoboAction =
| TurnRight
| TurnLeft
| MoveOne
| Eat
| Idle
type RoboActor = World -> RoboState -> RoboAction
let right heading : Position =
match heading with
| { Column = 0; Row = 1 } -> { Column = -1; Row = 0 }
| { Column = -1; Row = 0 } -> { Column = 0; Row = -1 }
| { Column = 0; Row = -1 } -> { Column = 1; Row = 0 }
| { Column = 1; Row = 0 } -> { Column = 0; Row = 1 }
| _ -> failwith "Invalid heading!"
let left heading : Position =
match heading with
| { Column = -1; Row = 0 } -> { Column = 0; Row = 1 }
| { Column = 0; Row = -1 } -> { Column = -1; Row = 0 }
| { Column = 1; Row = 0 } -> { Column = 0; Row = -1 }
| { Column = 0; Row = 1 } -> { Column = 1; Row = 0 }
| _ -> failwith "Invalid heading!"
let checkAccess n position =
let inRange v = v >= 0 && v < n
(inRange position.Column) && (inRange position.Row)
let tickWorld world =
world
|> Array2D.map
(fun sq ->
match sq with
| Empty -> Empty
| Obstacle -> Obstacle
| Food(a,r) -> Food(min (r a) maxFoodPerSquare, r)
| Unknown -> Unknown
)
let rec step robot world roboState i imax acc =
if i < imax then
let action = robot world roboState
match action with
| TurnRight ->
let rs1 = { roboState with Heading = right roboState.Heading }
let wrld1 = tickWorld world
step robot wrld1 rs1 (i+1) imax acc
| TurnLeft ->
let rs1 = { roboState with Heading = left roboState.Heading }
let wrld1 = tickWorld world
step robot wrld1 rs1 (i+1) imax acc
| MoveOne ->
let rs1 =
let c =
{ Column = roboState.Pos.Column + roboState.Heading.Column
Row = roboState.Pos.Row + roboState.Heading.Row
}
if checkAccess (Array2D.length1 world) c
then
match world.[c.Column,c.Row] with
| Obstacle ->
roboState.Memory.[c.Column,c.Row] <- Obstacle
roboState
| _ -> { roboState with Pos = c }
else
roboState
let wrld1 = tickWorld world
step robot wrld1 rs1 (i+1) imax acc
| Eat ->
let eat,acc1 =
match world.[roboState.Pos.Column,roboState.Pos.Row] with
| Empty -> Empty,acc
| Obstacle -> Obstacle,acc
| Food(a,r) ->
let eaten = if a >= eatPerTick then eatPerTick else 0.0
printfn "eating %f carrots" eaten
Food(a - eaten, r),eaten + acc
| Unknown -> Unknown,acc
world.[roboState.Pos.Column,roboState.Pos.Row] <- eat
let wrld1 = tickWorld world
step robot wrld1 roboState (i+1) imax acc1
| Idle ->
step robot (tickWorld world) roboState (i+1) imax acc
else
acc
let initRoboState n =
{ Memory = createUnknownWorld n;
Pos = { Column = 0; Row = 0;};
Heading = {Column = 1; Row = 0}
}
let simulate n pobstacle pfood imax robot =
let w0 = createRandomWorld n pobstacle pfood
let r0 = initRoboState n
printfn "World: %A" w0
printfn "Initial Robo State: %A" r0
let result = step robot w0 r0 0 imax 0.0
printfn "Final Robo State: %A" r0
result
// Not that Marvin would care, but the rule for this simulator is that the
// bot may only inspect the square in the world at the current position.
// This means, IT CANNOT SEE the neighboring squares.
// This means, that if there is a obstacle next to current square,
// it costs a simulation tick to find out, trying to bump against it.
// Any access to other squares in world is considered cheating!
// world is passed in spite of all said above to allow for alternate rules.
let marvinRobot world roboState =
Idle
// Tries to find a square with food, then stays there, eating when there is something to eat.
let lazyRobot (world : World) (roboState : RoboState) =
let search() =
let status action : RoboAction =
match action with
| TurnLeft -> printfn "%A TurnLeft at %A (heading: %A)" world.[roboState.Pos.Column,roboState.Pos.Row] roboState.Pos roboState.Heading
| TurnRight -> printfn "%ATurnRight at %A (heading: %A)" world.[roboState.Pos.Column,roboState.Pos.Row] roboState.Pos roboState.Heading
| MoveOne -> printfn "%A MoveOne at %A (heading: %A)" world.[roboState.Pos.Column,roboState.Pos.Row] roboState.Pos roboState.Heading
| Idle -> printfn "%A Idle at %A (heading: %A)" world.[roboState.Pos.Column,roboState.Pos.Row] roboState.Pos roboState.Heading
| Eat -> printfn "%A Eat at %A (heading: %A)" world.[roboState.Pos.Column,roboState.Pos.Row] roboState.Pos roboState.Heading
action
let neighbors =
[ roboState.Heading, MoveOne;
(roboState.Heading |> right),TurnRight;
(roboState.Heading |> left),TurnLeft;
(roboState.Heading |> right |> right),TurnRight
]
|> List.map (fun (p,a) -> (p.Column,p.Row),a)
|> List.map (fun ((c,r),a) -> (roboState.Pos.Column + c,roboState.Pos.Row + r),a)
|> List.filter (fun ((c,r),a) -> checkAccess (Array2D.length1 world){Position.Column = c; Row = r})
|> List.sortBy (fun ((c,r),a) -> match roboState.Memory.[c,r] with | Food(_,_) -> 0 | Unknown -> 1 | Empty -> 2 | Obstacle -> 3)
|> List.map (fun ((c,r),a) -> { Column = c; Row = r},a)
if neighbors.IsEmpty then failwith "It's a trap!" // can happen if bot is surrounded by obstacles, e.g. in a corner
else
let p,a = neighbors.Head
status a
roboState.Memory.[roboState.Pos.Column, roboState.Pos.Row] <-
world.[roboState.Pos.Column,roboState.Pos.Row]
match world.[roboState.Pos.Column,roboState.Pos.Row] with
| Food(a,_) ->
printfn "Found food at %A" roboState.Pos
Eat
| _ ->
search()
//simulate 10 0.1 0.05 2000 marvinRobot
simulate 10 0.1 0.1 2000 lazyRobot
Last not least a tip: if you simulate with 0.0 food patches, your bot should have visited all squares on the map. If it fails to do that, it is for sure not a good bot ;)

Generate two different randoms in F#

I have a F# list and I'm taking two elements of that list.
If the list has 10 elements in it :
let rnd = new Random()
let elem1 = list.Item(rnd.Next(0,9))
let elem2 = list.Item(rnd.Next(0,9))
There is a chance elem1 and elem2 are equal.
I have checked some workarounds and most of them work using a do while, but I don't want to implement a function that may never end in F#.
Is there a way to create a restriction in the random function?
First random : 0 <= x <= 9
Second random : 0 <= y <= 9 <> x
A simple solution:
let rnd = new Random()
let ndx1 = rnd.Next(9)
let ndx2 =
let x = rnd.Next(8)
if x < ndx1 then x else x + 1
let elem1, elem2 = list.[ndx1], list.[ndx2]
Another way, using maths and calling the random function once:
let r = Random().Next(9 * 8)
let x = 1 + r + r / 9
let elem1, elem2 = list.[x / 9], list.[x % 9]
which may be generalised to:
let getTwoElements lst =
let c = List.length lst
let x, y = Math.DivRem(Random().Next(c * (c-1)) * (c+1) / c + 1, c)
lst.[x], lst.[y]
A more declarative approach, taking into account your comment about points in the image:
let rnd = System.Random()
/// this will build you a list of 10 pairs of indices where a <> b.
let indices =
Seq.initInfinite (fun _ -> rnd.Next(0,10), rnd.Next(0,10))
|> Seq.filter (fun (a,b) -> a <> b)
|> Seq.take 10
|> List.ofSeq
/// map indices into actual points.
let elems =
let points = list |> Array.ofList
List.map (fun (a, b) -> points.[a], points.[b]) indices
As a side note, do not use random access on lists. They're not made for that and performance of that is poor. Convert them to an array first.
There are lots of way to achieve this. A simple one would be something like this:
open System
open System.Linq
let rnd = new Random()
let elem1 = list.Item(rnd.Next(0,9))
let elem2 = list.Where(fun x->x <> elem1).ElementAt(rnd.Next(0,8))

Return string and code optimisation in F#

How to modify below code to Return "string" so that returned output displayed on my MVC page and also would like to accept enteredChar from user.
Is there better way to do create this pyramid?
Current code:
let enteredChar = 'F' // As Interactive window doesn't support to Read Input
let mylist = ['A'..enteredChar]
let mylistlength = mylist |> List.length
let myfunc i x tlist1 =
(for j = 0 to mylistlength-i-2 do printf "%c" ' ')
let a1 = [for p in tlist1 do if p < x then yield p]
for p in a1 do printf "%c" p
printf "%c" x
let a2 = List.rev a1
for p in a2 do printf "%c" p
printfn "%s" " "
mylist |> List.iteri(fun i x -> myfunc i x mylist)
Output:
A
ABA
ABCBA
ABCDCBA
ABCDEDCBA
ABCDEFEDCBA
A few small optimizations could be:
Use StringBuilder instead of printf which is quite slow with long strings.
Use Array instead of List since Array works better with String.
Here is a version producing a pyramid string, which is kept closely to your code:
open System
open System.Text
let generateString c =
let sb = StringBuilder()
let generate i x arr =
String.replicate (Array.length arr-i-1) " " |> sb.Append |> ignore
let a1 = Array.filter (fun p -> p < x) arr
String(a1) |> sb.Append |> ignore
sb.Append x |> ignore
String(Array.rev a1) |> sb.Append |> ignore
sb.AppendLine " " |> ignore
let arr = [|'A'..c|]
arr |> Array.iteri(fun i x -> generate i x arr)
sb.ToString()
generateString 'F' |> printfn "%s"
As an alternative to Daniel's solution, you can achieve what you want with minimal changes to the code logic. Instead of using printf that writes the output to the console, you can use Printf.bprintf which writes the output to a specified StringBuilder. Then you can simply get the resulting string from the StringBuilder.
The modified function will look like this. I added parameter str and replaced printf with Printf.bprintf str (and printfn with bprintf together with additional \n char):
let myfunc i x tlist1 str =
(for j = 0 to mylistlength-i-2 do Printf.bprintf str "%c" ' ')
let a1 = [for p in tlist1 do if p < x then yield p]
for p in a1 do Printf.bprintf str "%c" p
Printf.bprintf str "%c" x
let a2 = List.rev a1
for p in a2 do Printf.bprintf str "%c" p
Printf.bprintf str "%s\n" " "
To call the function, you first create StringBuilder and then pass it to myfunc in every call. At the end, you can get the result using ToString method:
let str = StringBuilder()
mylist |> List.iteri(fun i x -> myfunc i x mylist str)
str.ToString()
I think Daniel's solution looks nicer, but this is the most direct way to tunr your printing code into a string-building code (and it can be done, pretty much, using Search & Replace).
If I understand your question (this likely belongs on Code Review) here's one way to rewrite your function:
let showPyramid (output: TextWriter) lastChar =
let chars = [|'A' .. lastChar|]
let getRowChars n =
let rec loop acc i =
[|
if i < n then let c = chars.[i] in yield c; yield! loop (c::acc) (i+1)
else yield! List.tail acc
|]
loop [] 0
let n = chars.Length
for r = 1 to n do
output.WriteLine("{0}{1}{0}", String(' ', n - r), String(getRowChars r))
Example
showPyramid Console.Out 'F'
or, to output to a string
use output = new StringWriter()
showPyramid output 'F'
let pyramid = output.ToString()
EDIT
After seeing Tomas' answer I realized I skipped over "return a string" in your question. I updated the code and added examples to show how you could do that.
let pyramid (ch:char) =
let ar = [| 'A'..ch |]
let len = ar.Length
Array.mapi
(fun i ch ->
let ar = ar.[0..i]
String.replicate (len - i - 1) " " + new string(ar) + new string((Array.rev ar).[1..]))
ar
|> String.concat "\n"
pyramid 'F' |> printfn "%s"
Here's another approach that seems to be a good demonstration of functional composition. I bet it's the shortest solution among the answers here. :)
let charsToString = Seq.map string >> String.concat String.Empty
let pyramid lastChar =
let src = '-'::['A'..lastChar] |> List.toArray
let len = Array.length src - 1
fun row col -> row-abs(col-len+1)+1 |> max 0 |> Array.get src // (1)
>> Seq.init (len*2-1) >> charsToString // (2)
|> Seq.init len // (3)
pyramid 'X' |> Seq.iter (printfn "%s")
First, we generate an unusual array of initial data. Its element [0] contains a space or whatever separator you want to have; I preferred dash (-) for debugging purposes.
The (1) line makes a function that calculates what character to be placed. The result of row-abs(col-len+1)+1 can be either positive (and there is a char to be placed) or zeronegative, and there should be a space. Note that there is no if statement: it is hidden within the max function;
The (2) line composes a function int -> string for generating an individual row;
The (3) line passes the function above as argument for sequence initializer.
The three lines can be written in a more verbose way:
let genCell row col = row-abs(col-len+1)+1 |> max 0 |> Array.get src
let genRow = genCell >> Seq.init (len*2-1) >> charsToString
Seq.init len genRow
Note genRow needs no formal argument due to functional composition: the argument is being bound into genCell, returning a function of a single argument, exactly what Seq.init needs.

Resources