数独游戏之JavaScript解法

创作背景

数独(sudoku)是源自十八世纪末的瑞士,后在美国发展,并在日本发扬光大的数学智力拼图游戏,拼图是九宫格(即3格宽 x 3格高)的正方形,每一格又细分为一个九宫格,在每一个九宫格里,分别填上1至9的数字,让整个大的九宫格每一列、每一行的数字都不重复,下面是一个数独游戏。

235
19867
4652
247
197328
765
6485
51324
462

让我们开始吧

1.使用js解题

1.1 将数独表翻译成数组

这里我们首先要将表里的数据初始化到数组中哦!

const suzi = Array(81).fill(null)
suzi[0] =  2
suzi[4] =  3
suzi[6] =  5
suzi[10] = 1
suzi[12] = 9
suzi[14] = 8
suzi[15] = 6
suzi[16] = 7
suzi[18] = 4
suzi[20] = 6
suzi[23] = 5
suzi[25] = 2
suzi[28] = 2
suzi[31] = 4
suzi[35] = 7
suzi[36] = 1
suzi[38] = 9
suzi[39] = 7
suzi[41] = 3
suzi[42] = 2
suzi[44] = 8
suzi[45] = 7
suzi[50] = 6
suzi[52] = 5
suzi[55] = 6
suzi[57] = 4
suzi[60] = 8
suzi[62] = 5
suzi[64] = 5
suzi[65] = 1
suzi[66] = 3
suzi[68] = 2
suzi[70] = 4
suzi[74] = 4
suzi[76] = 6
suzi[80] = 2

2. 建立参考值

2.1得出每个小的九宫格可能的数字(所谓的参考值)

2.1.1 九宫格已经存在的有用值——单独的数字

function defaltValues(groupIndex: number, data: TSuzi[], distances: number[] ) {
  return distances.map(item => {
    const _index = groupIndex + item
    return data[_index];
  }).filter(value => value);

2.1.2 九宫格中未填的可能值——得到的一个字符串 

function referValue(value: number[], index: number, result: string[], config: { groupIndexes: number[], distances: number[], aReferValues: number[]}) {
  const { groupIndexes, distances, aReferValues } = config;
  const groupIndex = groupIndexes[index]
  const str = aReferValues.filter(item => !value.includes(item)).join('')
  return distances.reduce((res: string[], item) => {
    const index = groupIndex + item;
    res[index] = str
    return res
  }, result)
}
 2.2 最终的参考值
function initValues(data: TSuzi[], config: { distances: number[], aReferValues: number[], groupIndexes: number[] }) {
  const { distances, aReferValues, groupIndexes } = config
  const allValues = groupIndexes.map(groupIndex  => defaltValues(groupIndex, data, distances));
  const result: string[] = Array(81).fill(null)
  allValues.forEach((item, index) => {
    referValue(item as number[], index, result, {
      groupIndexes,
      distances,
      aReferValues
    });
  })
  return result
}

3. 列出比较方法

3.1 横向或纵向
function compareRowOrColumn(value: string, rowNums: number[]) {
  if (rowNums.length === 9) return value;
  return compare(value, rowNums)
}
3.2 每一个小九宫格
function compareCircle(centerIndex: number, data: TSuzi[], distances: number[]) {
  const nums = distances.map((dist) => { 
    return data[centerIndex + dist]
  });
  if (nums.every((item) => typeof item === 'number')) return;
  // 归类 统计字符串出现的次数 得到字符串长度与次数一致的字符串
  const obj: any = {}
  nums.forEach((item) => {
    if (obj[`${item}`]) {
      obj[`${item}`]++;
    } else { 
      obj[`${item}`] = 1
    }
  })
  let repeatNums: number[] = [], repeatString: string[] = [];
  for (let key in obj) {
    if (obj[key] > 1) {
      if (key.length === obj[key]) {
        repeatNums = repeatNums.concat(key.split('').map((item) => parseInt(item)));
        repeatString.push(key)
      }
    }
  }
  nums.forEach((item, index, arr) => {
    if (item && +item > 10) {
      const result = compare(item as string, arr as number[], {
        repeatNums,
        repeatString
      });
      if (typeof result === "number") {
        const _index = centerIndex + distances[index]
        data[_index] = result
        arr[index] = result;
      }
    }
  })
}
3.3 具体的比较方法
function compare(value: string, nums: number[], others?: {
  repeatNums: number[];
  repeatString: string[];
}) {
  if (others) { 
    nums = Array.from(new Set(nums.concat(others.repeatNums))) ;
  }
  if (others && others.repeatString.includes(value)) return value;
  const res = nums.reduce((_res, row) => {
    if (+row > 10) return _res;
    return _res.replace('' + row, '');
  }, value);
  return res.length > 1? `${res}` : +res
}

4. 得到横/纵向存在的数字

function rowsAndColumnsData(data: TSuzi[]) {
  const rows: number[][] = [], columns: number[][] = []
  data.forEach((item, index) => {
    const columnIndex = index % 9, rowIndex = Math.floor(index / 9);
    if (typeof item === "number") {
      if (columns[columnIndex]) {
        columns[columnIndex].push(item)
      } else {
        columns[columnIndex] = [item]
      }
      if (rows[rowIndex]) {
        rows[rowIndex].push(item)
      } else {
        rows[rowIndex] = [item]
      }
    }
  })
  return {
    rows,
    columns
  }
}

5. 填空空

function compareAndFlat(data: TSuzi[], rows: number[][], columns: number[][], initValues: string[], config: {
  groupIndexes: number[],
  distances: number[],
}) {
  const { groupIndexes, distances } = config;
  const _rows = [...rows], _columns = [...columns];
  const _data =  data.map((su, index) => {
    const _columnIndex = index % 9, _rowIndex = Math.floor(index / 9);
    const value = su;
    let _value: string | number = initValues[index];
    if (!value || +value > 10) {
      _value = compareRowOrColumn(value as string || _value , _rows[_rowIndex])
      if (typeof _value === 'string') {
        _value = compareRowOrColumn(_value, _columns[_columnIndex])
      }
      if (typeof _value === 'number') {
        _rows[_rowIndex].push(_value)
        _columns[_columnIndex].push(_value)
      }
      return _value
    }
    return su
  })
  groupIndexes.forEach(group => {
    compareCircle(group, _data, distances)
  })
  return _data
}

 最终执行

function sudoku(data: TSuzi[]) {
  const aReferValues = [1, 2, 3, 4, 5, 6, 7, 8, 9], groupIndexes = [10, 13, 16, 37, 40, 43, 64, 67, 70], distances = [-10, -9, -8, -1, 0, 1, 8, 9, 10];
  const initValue = initValues(data, { distances, groupIndexes, aReferValues });
  const { rows, columns } = rowsAndColumnsData(data);
  const _data = compareAndFlat(data, rows, columns, initValue, {
    groupIndexes,
    distances
  })
  if (_data.some((item) => typeof item !== 'number')) {
    return sudoku(_data)
  }
  return _data
}

结果成功得到 🎉🎉🎉🎉🎉

297634581
513928674
486175923
625849137
149753268
738216459
362497815
951382746
874561392

如果没有得到结果😔😔😔😔😔,请你检查一下你的数字是不是在正确的位置!!!

  • 6
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值