Как рекурсивно идентифицировать ячейки определенного типа в сетке?

Я изучаю F# и создаю приложение для тральщика. Как часть этого, я пытаюсь иметь метод, который детонирует все соседние мины, если мина детонирует, рекурсивно. Итак, если у меня есть сетка вроде:

     |  0  |  1  |  2  |
------------------------
  0  | E-1 |  M  | E-2 |
  1  | E-2 | E-4 |  M  |
  2  |  M  | E-3 |  M  |

и я взорву мину в 0,1, она в свою очередь взорвет мину в 1,2 и та в свою очередь взорвет мину в 2,2. Мина в 2,0 не взорвется, потому что она не примыкает ни к одной из других.

На данный момент я фактически реализовал поле в виде списка:

module CellContents =
  type T = 
    | Empty of int
    | Mine
    | Crater

module Location =
  type T = { Row: int; Column: int }

module Cell =
  type T = 
    { Content : CellContents.T
      Location : Location.T }

module Field =
  type T = Cell.T list

У меня возникли проблемы с тем, как начать с ячейки 0,1 и получить список всех соседних мин. Итак, мне нужен список вроде (показывающий только координаты):

let minesToDetonate = [ {Row=0;Column=1};{Row=1;Column=2};{Row=2;Column=2} ]

У меня нет проблем с получением соседних мин для определенного места, а затем с определением мин из этой группы.

То, с чем у меня возникли проблемы, заключается в том, чтобы заставить это каким-то образом повторяться и идти до тех пор, пока рядом не будут найдены мины, что дает мне основной список мин, которые мне нужно взорвать.

Как только я получу основной список мин, я смогу взорвать их и построить обновленное поле, где эти мины станут кратерами.

Обновить Ответ @Kevin сработал, но мне было трудно его понять. На случай, если у других тоже возникнут затруднения, я добавляю функцию ниже с комментариями и парой изменений.

let detonateProximity (field:T) (start:Cell.T) =
    let rec inner cells m acc =
        match cells with
        | [] -> acc
        | x::xs -> 
            match x.Content with
            |Mine ->
              match proximity m.Location x.Location with
              // Continue but don't accumulate
              | Self -> inner xs m acc
              | Near -> 
                  // See if current cell has already been found
                  match acc |> List.tryFind (fun t -> t = x) with
                  // If it has, we're done. Pass out
                  // an empty list which ends recursion.
                  |Some _ -> [] 
                  // If it wasn't found (this parts hurts my brain)...
                  // calls function once for rest field and then
                  // using new starting point on whole field.
                  // Is this efficient at all?
                  |None -> List.concat [(inner xs m (x::acc));(inner field x (x::acc))]
              // Don't accumulate, continue with rest of mines.
              | Far -> inner xs m acc
            // Not a mine, keep going, don't accumulate
            |_ -> inner xs m acc

    // The set ensures no duplicates
    Set.ofList (inner field start [])

Функция proximity (не показана) просто завершает логику, определяющую, является ли тестируемая мина эталонной, близкой к ней или далекой от нее. Например. Self возвращается, если расстояние между текущей ячейкой и шахтой равно нулю {Row=0, Column=0}.


person Greg McGuffey    schedule 13.06.2015    source источник
comment
Почему вы не хотите использовать 2D-массив вместо местоположения и ячейки?   -  person FoggyFinder    schedule 13.06.2015
comment
Ну, первоначальная причина заключалась в том, чтобы усердно работать со списками, так как я предполагаю, что в реальной жизни я буду использовать это больше, чем массивы. Мне также интересно, насколько функционально будет работать решение 2d Array. Насколько я понимаю, мне придется либо постоянно перераспределять поле, если я оставлю его неизменным, либо мне придется изменить состояние массивов (т.е. изменить значения в массиве). Я был бы открыт для мыслей по этому поводу и / или разъяснений. Главное, что этот проект предназначен для изучения того, как использовать F#, поэтому я иногда принимаю решения, основываясь на том, что я узнаю, а не на эффективности.   -  person Greg McGuffey    schedule 16.06.2015


Ответы (2)


Это вернет набор всех взорванных ячеек, включая ту, которая запустила цепную реакцию.

module Location =
  type T = {Row: int; Column: int }

  let subtract l1 l2 =
      {Row=l1.Row - l2.Row;Column=l1.Column-l2.Colum

let detonate (field:Field.T) (start:Cell.T) =
  let rec inner cells m acc =
    match cells with
    | [] -> acc
    | x::xs -> match x.Content with
               |Mine ->match subtract m.Location x.Location with
                       |{Row = 0;Column = 0} -> inner xs m acc
                       |loc when abs (loc.Row) < 2 && abs (loc.Column) < 2 -> 
                          match acc |> List.tryFind (fun t -> t = x) with
                          |Some _ -> [] 
                          |None -> List.concat [(inner xs m (x::acc));(inner field x (x::acc))]
                       | _ -> inner xs m acc
               |_ -> inner xs m acc
  Set.ofList (inner field start [])

Если вам просто нужен список местоположений, как в вашем вопросе, его легко преобразовать следующим образом:

detonate board {Content=Mine;Location={Row=0;Column=1}}
  |> Set.map (fun t -> t.Location)
  |> Set.toList
person Kevin    schedule 13.06.2015
comment
Благодарность! Однако, поскольку я все еще новичок в функциональном программировании, и мой мозг вот-вот взорвется, пытаясь понять логику =:-o. Есть ли шанс, что вы могли бы добавить несколько комментариев, чтобы объяснить, что происходит? Я действительно не понимаю совпадения tryFind и почему вы обновляете аккумулятор, когда совпадение None. - person Greg McGuffey; 15.06.2015

Итак, я не знаю F#, поэтому напишу это на Python.

def getDetonationList ( startingWithThisCell ) :
    seen = set()
    current = set ( [startWithThisCell] )
    while current :
        # Get all the mine cells that are adjacent to every ones
        # we are currently exploring. Then remove the ones we're
        # currently exploring and the ones we've seen, just so
        # we don't duplicate effort later.
        nearby = allMinesAdjacentToCells(current) - seen - current

        # Mark that we've seen the current ones (add the current
        # ones to the overall seen ones
        seen.update ( current )

        # Now we start over, starting from the newly found mines
        current = nearby
        # Note that if this is empty, this list will end, which
        # means that we will have seen as many as we could have
        # seen.
    return seen
person iAdjunct    schedule 13.06.2015
comment
Хорошо, голосуйте против всего, что хотите, но Python также является отличным языком для написания псевдокода, который показывает вам путь к тому, КАК делать что-то, даже если у него нет кода для копирования и вставки. - person iAdjunct; 13.06.2015
comment
@FoggyFinder Потому что я не знаю F #, но вопрос о том, как рекурсивно находить близлежащие ячейки, не является языковым вопросом: это вопрос методологии (если вы не используете глупый язык). - person iAdjunct; 14.06.2015
comment
@iAdjunct, хотя вы правы в том, что это не обязательно языковой вопрос, это вопрос, связанный с парадигмой того, как это сделать функционально. Ваш пример сделан не функционально, а императивно. т.е. у вас есть переменная seen, которую вы обновляете в цикле. В функциональной парадигме не было бы цикла, потому что в идеале я не буду обновлять переменную, а буду вычислять значение. В этом и заключается смысл изучения F# (или любого функционального языка программирования): научиться думать о проблемах по-другому. - person Greg McGuffey; 15.06.2015
comment
Ах, я не знал, что F# — это функциональный язык. Я полагаю, это объясняет букву «F». - person iAdjunct; 15.06.2015