检查平面数组中的相邻元素

Checking neighbour elements in flat array

问题描述:
我想遍历一个数组(现在展平 2D --> 1D 数组)并继续检查它最近的邻居。由此我想确定它们是否是 dead/alive('X' 或 '.')并根据我的规则(简化的康威规则)更改它们的状态。

我的网格看起来像这样例如:

...............
...........X...
.X.......X...X.
...............
....X..........
....X..........
...............
.....XX........
..........X....
...............
Cells alive: 9

但我将此数组展平为一维数组以对其进行迭代。所以基本上它变成了这样的东西:....X...X....X. 等等。 把它写在纸上后,我认为有几个案例可以检查这个 "grid":

但是用一些 if 和 case 语句来检查它似乎完全愚蠢。如果我可以使用真正的二维数组,我想我可以创建偏移量 (-1, 1)、(0, 1)... 等数组。但我想不出如何用我的代码处理这个问题。 任何tips/examples等等我都会很高兴。

到目前为止我的代码:

cellsAlive=0

#STDIN variables
geneFile=
nRows= 
nColumns=
let "cells = $nRows * $nColumns" 
declare -i tick_rate # instead maybe use watch or sleep

readarray -t geneArr < $geneFile # -t removes a trailing newline from each line read.
elementsCounts=${#geneArr[@]}

echo -e "--- Coordinates ---"
for (( i = 0; i < $elementsCounts; i++ )); do
    echo "${geneArr[$i]}" #| cut -d' ' -f2 $geneFile | head -2
done

echo -e "\n--- Grid ---"
#file must end with a newline
[[ $geneFile && -f $geneFile && -r $geneFile ]] || { printf >&2 'arg must be readable file.\n'; exit; }

array=()
for ((i=0; i<nRows*nColumns; ++i)); do
    array+=( '.' )
done
printf "\n"

while read -r x y; do 
    [[ $x && $y ]] || continue
    [[ $x = +([[:digit:]]) && $y = +([[:digit:]]) ]] || continue
    ((x=10#$x,y=10#$y)) #10 digit base
    (( x<nRows && y<nColumns )) || continue
    array[x+y*nRows]='X' 
    if [[ ${array[x+y*nRows]} == 'X' ]]; then
        let "cellsAlive += 1"
    fi
done < "$geneFile"

# print to stdout and to file
for((i=0;i<nColumns;++i)); do
    printf '%s' "${array[@]:i*nRows:nRows}" $'\n'
done | tee currentState 

arrayCopy=("${array[@]}")  
printf "Cells alive: %d" $cellsAlive ; printf "\n"

# printf "\n"

for (( i = 0; i < ${#arrayCopy[@]}; i++ )); do 
    #neighboursCount=0
    case $i in

        "0") if [[ ${arrayCopy[$(($i - 1))]} == 'X' ]] || [[ ${arrayCopy[$(($i + $nColumns))]} == 'X' ]] || [[ ${arrayCopy[$(($i + $nColumns + 1))]} == 'X' ]] ; then #TopLeft
            echo "That is just ridiculous way to check it..."
        fi ;;
        "$(($nColumns - 1))") printf "${arrayCopy[$i]}" ;; #TopRight
        "$(($nColumns*$nRows-1))") printf "${arrayCopy[$i]}" ;; #BottomRight
        "$(($nColumns*$nRows-$nColumns))") printf "${arrayCopy[$i]}" ;; #BottomLeft
        *) ;; #Middle elements with 8 neighbours
    esac
done
printf "\n"

在此先感谢您的帮助。

示例geneFile.txt(在末尾添加空格):

1 2
4 5
6 7
13 2
5 7
4 4
9 2
11 1
10 8

好的。开始了。因为我发现在 bash 中实现这个问题很有趣,所以我刚刚编写了康威生命游戏的实现。

回答您的问题的主要部分可能是:如果矩阵是线性化的,如何访问矩阵中某个位置的邻居?

因此您可以通过

访问扁平矩阵中的元素
(row*fieldwidth)+columnoffset. 

然后可以通过调整 rowcolumnoffset 来访问每个邻居,方法是 +/-10 处的行和列偏移量开始。

查看 getnextstate 函数以查看特殊情况。

所以这里是实现。 您可以提供一个仅包含 CELLALIVEMARKERCELLDEADMARKER 和空格的文件作为输入。如果扁平矩阵的长度不适合 FIELD 的 width/height 参数,它只是用随机值填充。

#!/bin/bash

# system values
BASENAME="/usr/bin/basename"
ECHO="/bin/echo"
SLEEP="/bin/sleep"
TPUT="/usr/bin/tput"
GREP="/bin/grep"
WC="/usr/bin/wc"
CAT="/bin/cat"

if [ "${#}" != "4" -a "${#}" != "5" ]; then
  ${ECHO} "USAGE:    ./$(${BASENAME} [=11=]) FIELDWIDTH FIELDHEIGHT RULESALIVE RULESDEAD [LINSTARTMATRIX]"
  ${ECHO} "EXAMPLES: ./$(${BASENAME} [=11=]) 50         50          \"2 3\"    \"3\""
  ${ECHO} "          ./$(${BASENAME} [=11=]) 50         50          \"2 3\"    \"3\""    init.mtx
  exit
fi

# field values
FWIDTH=
FHEIGHT=
# number of living neighbours for a living cell to stay alive in the next generation
RULESALIVE=($(${ECHO} ))
# number of living neighbours for a dead cell to become alive in the next generation
RULESDEAD=($(${ECHO} ))
CELLALIVEMARKER="o"
CELLDEADMARKER="."
FIELD=() # flatted matrix representation
# if there are just marker values or spaces in the file it is a valid one
${CAT}  | ${GREP} -oq '[^\'${CELLALIVEMARKER}'\'${CELLDEADMARKER}'\ ]'
isvalid="${?}"
if [ "" != "" ] && [ "${isvalid}" == "1" ]; then
  FIELD=($(${CAT} ))
  # fill up with randoms if the length won't fit the dimension parameters
  if [ "${#FIELD[@]}" != "$((${FWIDTH}*${FHEIGHT}))" ]; then
    ${ECHO} "I: Padding matrix with random values."
    # fill up field with randoms if its too short
    for((i=${#FIELD[@]}; i<${FWIDTH}*${FHEIGHT}; i=$((${i}+1)))); do
      cell="${CELLALIVEMARKER}"
      alive=$((${RANDOM}%2))
      if [ "x${alive}" == "x1" ]; then
        cell="${CELLDEADMARKER}"
      fi
      FIELD[${#FIELD[@]}]="${cell}"
    done
  fi
else
  # fill random field
  for((i=0; i<${FWIDTH}*${FHEIGHT}; i=$((${i}+1)))); do
    cell="${CELLALIVEMARKER}"
    alive=$((${RANDOM}%2))
    if [ "x${alive}" == "x1" ]; then
      cell="${CELLDEADMARKER}"
    fi
    FIELD[${#FIELD[@]}]="${cell}"
  done
fi

# evaluate rules and get the next state for the cell
getnextstate() {
  local i="" # row
  local j="" # col
  local neighbours=""

  # left upper
  if [ "${i}" -eq "0" -a "${j}" -eq "0" ]; then
    neighbours="${FIELD[$(((${i}*${FWIDTH})+(${j}+1)))]} ${FIELD[$((((${i}+1)*${FWIDTH})+${j}))]} ${FIELD[$((((${i}+1)*${FWIDTH})+(${j}+1)))]}"
  # right upper
  elif [ "${i}" -eq "0" -a "${j}" -eq "$((${FWIDTH}-1))" ]; then
    neighbours="${FIELD[$(((${i}*${FWIDTH})+(${j}-1)))]} ${FIELD[$((((${i}+1)*${FWIDTH})+(${j}-1)))]} ${FIELD[$((((${i}+1)*${FWIDTH})+${j}))]}"
  # left bottom
  elif [ "${i}" -eq "$((${FHEIGHT}-1))" -a "${j}" -eq "0" ]; then
    neighbours="~${FIELD[$((((${i}-1)*${FWIDTH})+${j}))]} ${FIELD[$((((${i}-1)*${FWIDTH})+(${j}+1)))]} ${FIELD[$(((${i}*${FWIDTH})+(${j}+1)))]}"
  # right bottom
  elif [ "${i}" -eq "$((${FHEIGHT}-1))" -a "${j}" -eq "$((${FWIDTH}-1))" ]; then
  neighbours="?${FIELD[$((((${i}-1)*${FWIDTH})+(${j}-1)))]} ${FIELD[$((((${i}-1)*${FWIDTH})+${j}))]} ${FIELD[$(((${i}*${FWIDTH})+(${j}-1)))]}"
  # upper
  elif [ "${i}" -eq "0" -a "${j}" -gt "0" ]; then
    neighbours="-${FIELD[$(((${i}*${FWIDTH})+(${j}-1)))]} ${FIELD[$(((${i}*${FWIDTH})+(${j}+1)))]} ${FIELD[$((((${i}+1)*${FWIDTH})+(${j}-1)))]} ${FIELD[$((((${i}+1)*${FWIDTH})+${j}))]} ${FIELD[$((((${i}+1)*${FWIDTH})+(${j}+1)))]}"
  # bottom
  elif [ "${i}" -eq "$((${FHEIGHT}-1))" -a "${j}" -gt "0" ]; then
    neighbours="=${FIELD[$((((${i}-1)*${FWIDTH})+(${j}-1)))]} ${FIELD[$((((${i}-1)*${FWIDTH})+${j}))]} ${FIELD[$((((${i}-1)*${FWIDTH})+(${j}+1)))]} ${FIELD[$(((${i}*${FWIDTH})+(${j}-1)))]} ${FIELD[$(((${i}*${FWIDTH})+(${j}+1)))]}"
  # right
  elif [ "${i}" -gt "0" -a "${j}" -eq "0" ]; then
    neighbours="#${FIELD[$((((${i}-1)*${FWIDTH})+${j}))]} ${FIELD[$((((${i}-1)*${FWIDTH})+(${j}+1)))]} ${FIELD[$(((${i}*${FWIDTH})+(${j}+1)))]} ${FIELD[$((((${i}+1)*${FWIDTH})+${j}))]} ${FIELD[$((((${i}+1)*${FWIDTH})+(${j}+1)))]}"
  # left
  elif [ "${i}" -gt "0" -a "${j}" -eq "$((${FWIDTH}-1))" ]; then
    neighbours="_${FIELD[$((((${i}-1)*${FWIDTH})+(${j}-1)))]} ${FIELD[$((((${i}-1)*${FWIDTH})+${j}))]} ${FIELD[$(((${i}*${FWIDTH})+(${j}-1)))]} ${FIELD[$((((${i}+1)*${FWIDTH})+(${j}-1)))]} ${FIELD[$((((${i}+1)*${FWIDTH})+${j}))]}"
  # center
  else
    neighbours="@${FIELD[$((((${i}-1)*${FWIDTH})+(${j}-1)))]} ${FIELD[$((((${i}-1)*${FWIDTH})+${j}))]} ${FIELD[$((((${i}-1)*${FWIDTH})+(${j}+1)))]} ${FIELD[$(((${i}*${FWIDTH})+(${j}-1)))]} ${FIELD[$(((${i}*${FWIDTH})+(${j}+1)))]} ${FIELD[$((((${i}+1)*${FWIDTH})+(${j}-1)))]} ${FIELD[$((((${i}+1)*${FWIDTH})+${j}))]} ${FIELD[$((((${i}+1)*${FWIDTH})+(${j}+1)))]}"
  fi

  # count neighbours alive
  ncnt=$(${ECHO} ${neighbours} | ${GREP} -o ${CELLALIVEMARKER} | ${WC} -l)
  # evaluate rules
  local next=""
  if [ "${FIELD[$(((${i}*${FWIDTH})+${j}))]}" == "${CELLALIVEMARKER}" ] && [[ "$(${ECHO} ${RULESALIVE[@]})" =~ ${ncnt} ]]; then
    next="${CELLALIVEMARKER}"
  elif [ "${FIELD[$(((${i}*${FWIDTH})+${j}))]}" == "${CELLDEADMARKER}" ] && [[ "$(${ECHO} ${RULESDEAD[@]})" =~ ${ncnt} ]]; then
    next="${CELLALIVEMARKER}"
  else
    next="${CELLDEADMARKER}"
  fi
  ${ECHO} ${next}
}

firstrun=1
while [ true ]; do
  # print lines
  FIELD_UPDATE=()

  for((i=0; i<${FHEIGHT}; i=$((${i}+1)))); do
    line=""
    # calculate lines
    for((j=0; j<${FWIDTH}; j=$((${j}+1)))); do
      if [ "${firstrun}" == "1" ]; then
        line="${line}${FIELD[$(((${i}*${FWIDTH})+${j}))]} "
      # start calculation just after the first print
      elif [ "${firstrun}" == "0" ]; then
        line="${line}$(getnextstate ${i} ${j}) "
      fi
    done
    FIELD_UPDATE=($(${ECHO} ${FIELD_UPDATE[@]}) $(${ECHO} ${line}))
    ${ECHO} ${line}
  done
  FIELD=(${FIELD_UPDATE[@]})
  ${SLEEP} 2
  # refresh lines in the field
  for((i=0; i<${FHEIGHT}; i=$((${i}+1)))); do
    # refresh lines
    ${TPUT} cuu1
    ${TPUT} el
  done
  firstrun=0
done

因此提供包含以下矩阵的文件init.mtx

. o . . . . . . . .
. . o . . . . . . .
o o o . . . . . . .
. . . . . . . . . .
. . . . . . . . . . 
. . . . . . . . . . 
. . . . . . . . . . 
. . . . . . . . . . 
. . . . . . . . . . 
. . . . . . . . . .

您可以创建一个简单的滑翔机(从左上角到右下角)

. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . . 
. . . . . . . . . . 
. . . . . . . . . . 
. . . . . . . . . . 
. . . . . . . . o o 
. . . . . . . . o o

通过运行这个脚本使用Conway的默认规则如下:

./gameoflife 10 10 "2 3" "3" init.mtx

希望这对您有所帮助。 顺便说一句,在 bash 中实现它很有趣 :)