2
\$\begingroup\$

Story:

I build a kind of labyrinth. This labyrinth is divided in seven steps. You start with the first, then second, and so on to the seventh. You cannot go back to a previous step. Each step contains 3 to 5 rooms. Every room of a step is connected to at least one room of the next step. As well, every room of a step is connected to at least one room of the previous one. No hallway (connection between rooms) can cross another hallway. No room can have more than 3 source-rooms, neither more than 3 destination-rooms.

Implementation:

My point is to create a matrix (two-dim array) which depict the connection of a step to its successor. Rows represent the rooms of the source step, and columns the destination step. As I want to practice it, F# is required.

Decisions:

The matrix will only contains 3 values:
- M[i,j] = 1 means that source-room #i is connected to destination-room #j
- M[i,j] = 0 means that source-room can not be connected to destination-room
- M[i,j] = -1 means that source-room can be connected (but is not) to destination-room

Constraints:

  1. Because hallways cannot be crossed, first source-room is imperatively connected to the first destination-room ; same for last rooms.
  2. Because rooms can not have more than three destination-room, the first source-room can only be connected to the three first destination-rooms, and the last destination-room can only be connected to the three last source-rooms.

Code:

module MapConnections =
 let IsUncertain array row col =
 Array2D.get array row col = -1
 let IsDisconnected array row col =
 Array2D.get array row col = 0
 let IsConnected array row col =
 Array2D.get array row col = 1
 let ListUncertain array =
 let mutable list : (int*int) list = list.Empty
 for i in 0 .. Array2D.length1 array - 1 do
 for j in 0 .. Array2D.length2 array - 1 do
 if IsUncertain array i j then
 list <- list @ [(i,j)]
 list
 let ListDisconnected array =
 let mutable list : (int*int) list = list.Empty
 for i in 0 .. Array2D.length1 array - 1 do
 for j in 0 .. Array2D.length2 array - 1 do
 if IsDisconnected array i j then
 list <- list @ [(i,j)]
 list
 let ListConnected array =
 let mutable list : (int*int) list = list.Empty
 for i in 0 .. Array2D.length1 array - 1 do
 for j in 0 .. Array2D.length2 array - 1 do
 if IsConnected array i j then
 list <- list @ [(i,j)]
 list
 let CountUncertain array =
 ListUncertain array |> Seq.length<int*int>
 let CountDisconnection array =
 ListDisconnected array |> Seq.length<int*int>
 let CountConnection array =
 ListConnected array |> Seq.length<int*int>
 let Disconnect array row col =
 if IsUncertain array row col then
 Array2D.set array row col 0
 IsDisconnected array row col
 // Check no-cross rule
 let Connect array row col =
 if IsUncertain array row col then
 Array2D.set array row col 1
 // Disconnect all 'top-right' connections
 for i in 0 .. row - 1 do
 for j in col + 1 .. Array2D.length2 array - 1 do
 Disconnect array i j |> ignore
 // Disconnect all 'bottom-left' connections
 for i in row + 1 .. Array2D.length1 array - 1 do
 for j in 0 .. col - 1 do
 Disconnect array i j |> ignore
 IsConnected array row col
 // Check no-more-than-three rule
 let Create rows cols =
 let matrix = Array2D.create rows cols -1
 Connect matrix 0 0 |> ignore
 Connect matrix (rows - 1) (cols - 1) |> ignore
 for i in 3 .. rows - 1 do
 Disconnect matrix i 0 |> ignore
 for i in 0 .. rows - 4 do
 Disconnect matrix i (cols - 1) |> ignore
 for j in 3 .. cols - 1 do
 Disconnect matrix 0 j |> ignore
 for j in 0 .. cols - 4 do
 Disconnect matrix (rows - 1) j |> ignore
 matrix

What I want to be sure:

I want to follow the F# conventions. For example, if lists are non-mutable, I presume that a mutable list is something to avoid?

asked Nov 12, 2018 at 17:52
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

I'm not sure, I understand what the purpose of your code is, so here are some general thoughts:


You have 3 identical functions except for a predicate:

let ListUncertain array =
 let mutable list : (int*int) list = list.Empty
 for i in 0 .. Array2D.length1 array - 1 do
 for j in 0 .. Array2D.length2 array - 1 do
 if IsUncertain array i j then
 list <- list @ [(i,j)]
 list

let ListDisconnected array =

let ListConnected array =

So a generalization is appropriate:

let toFilteredList predicate array =
 let mutable list : (int*int) list = list.Empty
 for i in 0 .. Array2D.length1 array - 1 do
 for j in 0 .. Array2D.length2 array - 1 do
 if predicate array i j then
 list <- list @ [(i,j)]
 list

which then could be called from each specialized function like:

let ListUncertain array = array |> toFilteredList IsUncertain

etc.


Building a list by repeated concatenations of single item lists is rather expensive and in general in functional programming mutable objects should be avoided:

list <- list @ [(i,j)]

Instead it may be better to use higher order functions like:

let toSeq<'a> = Seq.cast<'a>
let filterBy cellType array =
 array 
 |> Array2D.mapi (fun r c x -> (r, c, x)) 
 |> toSeq<int*int*int>
 |> Seq.where (fun (r, c, x) -> cellType = x) 
 |> Seq.map (fun (r, c, _) -> (r, c))

where cellType is -1, 0 or 1 and then call it as:

let ListUncertain array = array |> filterBy -1

As an alternative you can "yield" a sequence like:

let filterBy predicate array =
 seq {for i in 0 .. Array2D.length1 array - 1 do
 for j in 0 .. Array2D.length2 array - 1 do
 if predicate array i j then
 yield (i,j)}

which is maybe a better solution?


Overall I think I would use a discriminated union type instead of -1, 0, 1 as cell types:

type CellType =
| Uncertain
| Disconnected
| Connected
let getAt array row col = Array2D.get array row col
let setAt array row col value = Array2D.set array row col value
let IsUncertain array row col = Uncertain = getAt array row col
let IsDisconnected array row col = Disconnected = getAt array row col
let IsConnected array row col = Connected = getAt array row col
let filterBy predicate array =
 seq {for i in 0 .. Array2D.length1 array - 1 do
 for j in 0 .. Array2D.length2 array - 1 do
 if predicate array i j then
 yield (i,j)}
let ListUncertain array = array |> filterBy IsUncertain
let ListDisconnected array = array |> filterBy IsDisconnected
let ListConnected array = array |> filterBy IsConnected
let CountUncertain array = ListUncertain array |> Seq.length
let CountDisconnection array = ListDisconnected array |> Seq.length
let CountConnection array = ListConnected array |> Seq.length
let Disconnect array row col =
 match getAt array row col with
 | Uncertain -> 
 setAt array row col Disconnected
 true
 | Connected -> false
 | Disconnected -> true
// Check no-cross rule
let Connect array row col =
 if IsUncertain array row col then
 setAt array row col Connected
 // Disconnect all 'top-right' connections
 for i in 0 .. row - 1 do
 for j in col + 1 .. Array2D.length2 array - 1 do
 Disconnect array i j |> ignore
 // Disconnect all 'bottom-left' connections
 for i in row + 1 .. Array2D.length1 array - 1 do
 for j in 0 .. col - 1 do
 Disconnect array i j |> ignore
 IsConnected array row col
// Check no-more-than-three rule
let Create rows cols =
 let matrix = Array2D.create rows cols Uncertain
 Connect matrix 0 0 |> ignore
 Connect matrix (rows - 1) (cols - 1) |> ignore
 for i in 3 .. rows - 1 do
 Disconnect matrix i 0 |> ignore
 for i in 0 .. rows - 4 do
 Disconnect matrix i (cols - 1) |> ignore
 for j in 3 .. cols - 1 do
 Disconnect matrix 0 j |> ignore
 for j in 0 .. cols - 4 do
 Disconnect matrix (rows - 1) j |> ignore
 matrix

The most significant improvement is, that there are no "magic numbers" left in the code - they are replaced with descriptive union types.

There may be other - smarter - ways to do it all, but this is what I can contribute with.

answered Jan 31, 2019 at 15:41
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.