Плавный перевод с C++ на F#

Эй люди, у меня есть следующий фрагмент кода из C++.

for (int i=0; i < nObstacles; i++)
{
  int x,y;
  bool bAlreadyExists;
  do {          
    x = rand() % nGridWidth;
    y = rand() % nGridHeight;                   
  } while (HasObstacle(x, y));
  SetObstacle(x, y, true);      
}

Я могу перевести его на F# прямо без проблем.

let R = new System.Random()
for i=0 to nObstacles do
        let mutable bGoodToGo = false;
        let mutable x =0;
        let mutable y = 0
        while not bGoodToGo do
            x <-R.Next(nWidth)
            y <-R.Next(nHeight)
            bGoodToGo <- IsEmptyAt x y
        board.[x,y]<-Obstacle;

конечно, это, вероятно, заставляет большинство из вас съеживаться, так как это не то, как F# должен был использоваться. Этот код имеет некоторые "некошерный" концепциями языка F#, такими как do-while циклы и изменяемые данные.

но то, что мне было бы интересно увидеть, - это "правильный" перевод F# с неизменяемыми данными и какой-то делать-в то время как эквивалентные.

2 ответов


вот моя попытка:

Seq.initInfinite (fun _ -> rnd.Next(width), rnd.Next(height))
|> Seq.filter (fun (x, y) -> IsEmptyAt x y)
|> Seq.distinct
|> Seq.take nObstacles
|> Seq.iter (fun (x, y) -> board.[x,y] <- Obstacle)

вы можете удалить Seq.фильтр, если плата пуста в начале. Как и в решении Tomas, он генерирует бесконечную последовательность позиций. Затем он удаляет плохие и дублированные позиции. Наконец, он обновляет доску с первыми элементами nObstacles.


в качестве первого шага вы можете посмотреть, как упростить while петли внутри for петли. Один из вариантов-использовать Seq.initInfinite для генерации последовательности, которая даст вам любое количество случайных координат X, Y. Тогда вы можете использовать Seq.find найти первый, который ссылается на пустое поле доски.

Я также изменил isEmpty взять кортеж (чтобы вы могли передать в качестве аргумента Seq.find используя частичное приложение функции), и я изменил некоторые имена, чтобы следовать более стандартным F# стиль (обычно вы не используете венгерскую нотацию именования):

let isEmpty (x, y) = board.[x,y] = -1

let rnd = new System.Random()
for i = 0 to obstacleCount do
  let x, y =
    // Generate infinite sequence of random X Y coordinates
    Seq.initInfinite (fun _ -> rnd.Next(width), rnd.Next(height))
    // Find first coordinate that refers to empty field
    |> Seq.find isEmpty
  // We still have mutation here
  board.[x,y] <- Obstacle

Я думаю, что это очень элегантное функциональное решение. Это может быть немного медленнее, чем императивное решение, но дело в том, что функциональный стиль упрощает запись и изменение реализации, как только вы ее изучите (вы всегда можете использовать императивный стиль в качестве оптимизации).

чтобы избежать всех изменяемых состояний, вам нужно сначала создать местоположения для препятствий, а затем инициализировать массив. Для например, вы можете рекурсивно добавлять новые координаты в набор, пока он не будет иметь требуемой длины. Затем вы можете создать массив, используя Array2D.init:

let rec generateObstacles obstacles =
  if Set.count obstacles = obstacleCount then obstacles
  else 
    // Try generating new coordinate and add it to the set
    // (if it is already included, this doesn't do anything)
    obstacles
    |> Set.add (rnd.Next(width), rnd.Next(height))
    |> generateObstacles

let obstacles = generateObstacles Set.empty
Array2D.init width height (fun x y -> 
  if obstacles.Contains(x, y) then Obstacle else Empty)

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