Search code examples
listrecursionmultidimensional-arraysmlnjml

2d matrix sml inserting to list - simple code


I have an sml-nj project in which I want to work with a 'list of lists' structure, which has "Squares". I'm trying to insert values to the list of lists recursively, but I still haven't understood how to insert elements to a 2d list. Note - I CAN'T USE 'REF', ONLY http://smlfamily.org/Basis/list.html#SIG:LIST.app:VAL these functions.

datatype SquareContent = Mine | Digit of int | Blank;
datatype Square = Revealed of SquareContent | Concealed of SquareContent;
fun createMineSweeperGrid (n:int)
:(Square list   list)= 
let
fun createMines (rowCounter:int, colCounter:int
, retGame:Square list list):(Square list list) = 
if rowCounter=n then   
retGame         (* finished all rows, should be n lists of size n*)
else
if colCounter=n then   (*finished current row, move on*)
createMines (rowCounter+1, 0, mines,retGame)
else   

let
val squareToInsert = Concealed(Mine)  (* I wish to insert 'squareToInsert'         
to retGame[rowCounter][colCounter], it's done dynamically, but I don't know 
how to do that *)
in
createMines (rowCounter, colCounter+1, retGame) 
end

in
createMines (0,0,[])
end

I could insert any kind of Square, it's decided dynamically and here I gave example only of concealed Mine so you can help me.. HELP..?


Solution

  • The essential thing to recognize is that in Standard ML, you don't mutate existing structures; rather, you create new ones. (Standard ML does support mutable structures, via ref and its friends, but it's not something to do lightly, and I see that you've already — rightly — ruled it out.)

    In general, therefore, inserting something into the middle of a linked list is pretty expensive: it requires "unwinding" the list to the point where you want to insert, then inserting the value, and lastly building a copy of everything you'd unwound. For example, here's a function that would insert a value x at index i of a list:

    fun insert (x, 0, L) = x :: L
      | insert (x, i, h :: t) = h :: insert (x, i - 1, t)
      | insert (_, _, nil) = raise Subscript
    

    Fortunately, your function is written so as to not have to insert anything into the middle of an already-built linked list; rather, if I understand correctly what it's trying to do, it always puts the new square at the beginning of the first list. So:

    let
      val squareToInsert = Concealed(Mine)
      val topRow :: rest = retGame
    in
      createMines (rowCounter, colCounter+1, (squareToInsert::topRow)::rest) 
    end
    

    Note that you'll also need to fix another bug, which is that you never actually create new rows: you have a comment about "finished current row, move on", but then it just proceeds exactly the same as if it were still in the same row (just resetting the numbers as if it had moved to a new row). To fix this, use [] :: retGame when you want to add a new row at the top, and use [[]] instead of [] for the initial board (so that it starts out with an empty row).