Как да идентифицирам рекурсивно клетки от определен тип в мрежата?

Уча 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