数独的特点
1,数独分成三种不同的单元:行、列和宫
2,数独一共有9行、9列和9个宫,每个单元都有9个格子
3,各个单元需要填入1-9,且单元中格子的数字不能重复
代码逻辑
这里先拆分解释每一块代码的逻辑,最后我会将完整的代码贴出来。
1.为数组的单元编写类
根据以上的特点,需要为每个单元创造一个类,类中需要包含一个字典(用来判断是否已经放入了某个数)以及一个数组(记录数字填入的位置)
public class ShuDuGroup
{
//字典,用来判断单元里面是否已经放入了某个数字。Key是数字,Value是数字放入的位置
private Dictionary<int, int> membersDic = new Dictionary<int, int>();
//数组,用来记录数字填入的位置
private int[] membersArr = new int[9];
/// <summary>
/// 这个单元是否已经填过某个数字
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public bool Contains(int member)
{
return membersDic.ContainsKey(member);
}
/// <summary>
/// 计数,这个单元已填入多少个数字
/// </summary>
/// <returns></returns>
public int GetCount()
{
return membersDic.Count;
}
/// <summary>
/// 检查这个单元的指定格子是否已经填入数字
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public bool AnyNumberFillInIndex(int index)
{
return membersArr[index] != 0;
}
/// <summary>
/// 获取这个单元中该数字填入的位置
/// </summary>
/// <param name="member"></param>
/// <returns></returns>
public int GetMemberIndex(int member)
{
if (membersDic.ContainsKey(member))
{
return membersDic[member];
}
return -1;
}
/// <summary>
/// 填入数字
/// </summary>
/// <param name="indexInZoom"></param>
/// <param name="number"></param>
public void AddMember(int indexInZoom, int number)
{
//只能加入1-9
if (number != 1&&
number != 2&&
number != 3&&
number != 4&&
number != 5&&
number != 6&&
number != 7&&
number != 8&&
number !=9) return;
if (membersDic.ContainsKey(number))
{
GameLogger.Log("the number was existed ");
}
else
{
membersDic.Add(number, indexInZoom);
membersArr[indexInZoom] = number;
}
}
/// <summary>
/// 移除数字
/// </summary>
/// <param name="number"></param>
public void RemoveMember(int number)
{
if (membersDic.ContainsKey(number))
{
int index = membersDic[number];
membersDic.Remove(number);
membersArr[index] = 0;
}
}
/// <summary>
/// 清空
/// </summary>
public void ClearAll()
{
membersDic.Clear();
for (int i = 0; i < 9; i++)
{
membersArr[i] = 0;
}
}
}
}
2.生成数独终盘
生成数独,我用到的算法思路是回溯法。从数字1开始尝试填写入每个宫,如果能够填入,那么继续下一个宫,如果所有宫都填完,那么继续数字2,依次类推;如果某个数字在某个宫中无法填写,那么退回上一个宫重新填写。为了减少回溯和递归的次数,我考虑了两点:
1)每个数字在每个宫的相对位置是唯一的
对于每个数字,要确保它们在每行每列上不重复,它们在每个宫中的位置一定是不一样的。如下图,数字1在第一个宫填在了左上角的格子,那么在其余的8个宫中,数字1都不能填在左上角。
按这个规律,对于每个数字,它们能填在宫里的位置我们标为0,1,2,3,4,5,6,7,8(待填写序列),每一次填写,我们都从待填写序列中取出一个位置尝试填写,如果填写失败,那么再将这个位置编号再放回到待填写序列的末尾,当然因为我们的数独是需要随机生成的,我们需要在一开始就打乱待填写序列的顺序。
2)规定宫的填写顺序
规定了程序按下图的顺序填写每一个宫,这样的顺序可以让每个数字更早地对行、列产生约束,减少回溯次数。
考虑完上诉两点,然后进入生成数独的代码。
核心递归方法
用FillNumber填写第0个宫,递归填写下一个宫,若下一个宫的填写返回了false,那么将当前宫的这个位置记录到不得填写的位置的字典中,入队列,并重新取出一个新的位置填写,重新递归下一个宫,以此类推
/// <summary>
/// 填入数字,用于递归 回溯算法
/// </summary>
/// <param name="zoomOrderIndex"></param>
/// <param name="indexes"></param>
/// <param name="number"></param>
/// <returns></returns>
private bool FillNumber(int zoomOrderIndex, Queue<int> indexes, int number)
{
//如果这个数字已经将所有宫都填写完,那么直接返回true
if (zoomOrderIndex >= ZoomOrder.Length) return true;
int cycleCount = 0;//记录循环次数,防止死循环
//如果没有可填写位置,代表无解,返回false退回上一步
if (indexes.Count < 1)
{
return false;
}
//从待填写位置的序列中取出第一个位置
int indexInZoom = indexes.Dequeue();
//计算它的行和列
int zoomRow = ZoomOrder[zoomOrderIndex] / 3;//在这个宫的行号
int zoomColumn = ZoomOrder[zoomOrderIndex] % 3;//在这个宫的列号
int r = zoomRow * 3 + indexInZoom / 3;//在整个数独中的行号
int c = zoomColumn * 3 + indexInZoom % 3;//在整个数独中的列号
//记录该数字在每个宫中有哪些位置不可填
//比如说第1个宫我们填在了左上角的位置,运行到下一个宫的时候,发现无解
//那么说明我们在第1个宫不能填左上角的位置,将这个位置记录到字典中,防止算法又重新填这个位置,陷入死循环
Dictionary<int, int> indexesNotAllow = Zoom1NotAllow;
switch (ZoomOrder[zoomOrderIndex])
{
case 1:
indexesNotAllow = Zoom1NotAllow;
break;
case 7:
indexesNotAllow = Zoom7NotAllow;
break;
case 3:
indexesNotAllow = Zoom3NotAllow;
break;
case 5:
indexesNotAllow = Zoom5NotAllow;
break;
case 0:
indexesNotAllow = Zoom0NotAllow;
break;
case 2:
indexesNotAllow = Zoom2NotAllow;
break;
case 6:
indexesNotAllow = Zoom6NotAllow;
break;
case 8:
indexesNotAllow = Zoom8NotAllow;
break;
}
//若取出的位置不能填写,那么放回队列,重新取出下一个位置,循环
while (Zooms[ZoomOrder[zoomOrderIndex]].AnyNumberFillInIndex(indexInZoom) || Rows[r].Contains(number) || Columns[c].Contains(number) || indexesNotAllow.ContainsKey(indexInZoom))
{
//将这个位置记录到不允许填写的字典中
if (!indexesNotAllow.ContainsKey(indexInZoom)) indexesNotAllow.Add(indexInZoom, 0);
cycleCount++;//记录循环次数
if (cycleCount > indexes.Count)//如果整个队列都循环过,代表无解,该回溯到上一个宫
{
//放回对列
indexes.Enqueue(indexInZoom);
//清空字典
indexesNotAllow.Clear();
//返回false回溯上一个宫
return false;
}
//放回队列并取出下一个位置
indexes.Enqueue(indexInZoom);
indexInZoom = indexes.Dequeue();
//计算它的行列号
zoomRow = ZoomOrder[zoomOrderIndex] / 3;
zoomColumn = ZoomOrder[zoomOrderIndex] % 3;
r = zoomRow * 3 + indexInZoom / 3;
c = zoomColumn * 3 + indexInZoom % 3;
}
//重置循环次数
cycleCount = 0;
//更新行、列和宫的信息
Zooms[ZoomOrder[zoomOrderIndex]].AddMember(indexInZoom, number);
Rows[r].AddMember(c, number);
Columns[c].AddMember(r, number);
//将数字填入位置,这里的GetCellIndex方法是用宫编号、在宫中的行列号计算出这个格子在整个数独中的下标号
ShuDuMap[GetCellIndex(ZoomOrder[zoomOrderIndex], indexInZoom / 3, indexInZoom % 3)] = number;
//进入递归,填写下一个宫
bool nextOK = FillNumber(zoomOrderIndex + 1, indexes, number);
//检查下一个宫是否能填写,返回true,代表下一个宫也没问题,那么返回true
//如果返回false,代表下一个宫无解,那么这个宫不能填这个位置
if (nextOK)
{
indexesNotAllow.Clear();
return true;
}
else
{
//将这个位置加到不能填写的位置的字典中
if (!indexesNotAllow.ContainsKey(indexInZoom)) indexesNotAllow.Add(indexInZoom, 0);
//删除它在对应行、列、宫中的信息
Zooms[ZoomOrder[zoomOrderIndex]].RemoveMember(number);
Rows[r].RemoveMember(number);
Columns[c].RemoveMember(number);
//清除数独这个格子填写的数字
ShuDuMap[GetCellIndex(ZoomOrder[zoomOrderIndex], indexInZoom / 3, indexInZoom % 3)] = 0;
//将这个位置重新入队列
indexes.Enqueue(indexInZoom);
//重新填写这个宫,也是递归
bool refillOK = FillNumber(zoomOrderIndex, indexes, number);
indexesNotAllow.Clear();//只要返回了上一步,都需要清空字典。
return refillOK;
}
}
生成数独方法
先将数字1-9随机地填入最中心的宫中,然后按数字1到数字9的顺序,依次填入其余8个宫(按上面规定的顺序),同一个数字宫与宫之间的冲突会在FillNumber方法中回溯解决,如果FillNumber最终还是返回false,那么代表这个数字无法填入所有宫,需要回溯上一个数字的填写,这里我不想再弄一个特别复杂的逻辑了,所以如果需要回溯到上一个数字,那么直接将整个数独推翻重来。
/// <summary>
/// 生成数独,结果写入ShuDuMap
/// </summary>
//[Server]
public void GenerateMap()
{
isGenerated = false;
//初始化
for (int i = 0; i < 9; i++)
{
Zooms[i] = new ShuDuGroup();
Rows[i] = new ShuDuGroup();
Columns[i] = new ShuDuGroup();
}
//这里是打乱数字0-8的顺序
int[] pickItms = RandomTools.RandomFromGroup(9, 9);
//先将数字1-9随机地填写入最中心的宫
//这里因为pickItms是随机过的,直接按pickItms的顺序填写达到随机目的
for (int i = 0; i < pickItms.Length; i++)
{
int number = pickItms[i] + 1;
Zooms[4].AddMember(i, number);
int r = 3 + i / 3;
int c = 3 + i % 3;
Rows[r].AddMember(i, number);
Columns[c].AddMember(i, number);
ShuDuMap[GetCellIndex(4, r - 3, c - 3)] = number;
}
//按数字1到数字9的顺序填入数独
for (int numberOperate = 1; numberOperate <= 9; numberOperate++)
{
//打乱0-8的顺序,这里的0-8代表每个数字在宫里可以填的相对位置,
//从左到右从上到下,0是左上角,8是右下角
int[] indexRandom = RandomTools.RandomFromGroup(9, 9);
//剔除已经在中心宫填过的位置
int indexInMiddleZoom = Zooms[4].GetMemberIndex(numberOperate);
//将剩下的位置入队列
Queue<int> indexNotUsed = new Queue<int>();
for (int i = 0; i < indexRandom.Length; i++)
{
if (indexRandom[i] != indexInMiddleZoom)
{
indexNotUsed.Enqueue(indexRandom[i]);
}
}
//调用递归方法FillNumer
bool ok = FillNumber(0, indexNotUsed, numberOperate);
//清空这些字典
Zoom1NotAllow.Clear();
Zoom7NotAllow.Clear();
Zoom3NotAllow.Clear();
Zoom5NotAllow.Clear();
Zoom0NotAllow.Clear();
Zoom2NotAllow.Clear();
Zoom6NotAllow.Clear();
Zoom8NotAllow.Clear();
//在FillNumber中的回溯代表的是宫无法填写,退回上一个宫
//在这里是整个方法返回false,代表某个数字无解,需要退回上一个数字
//为了偷懒不再写一个十分复杂的逻辑,这里如果需要退回上一个数字,那么就整个数独推翻重来
if (!ok)
{
GenerateMap();
return;
}
}
//输出结果
string stringDebug = "";
for (int i = 0; i < 9; i++)
{
for (int j = 0; j < 9; j++)
{
stringDebug = stringDebug + ShuDuMap[i * 9 + j] + " ";
}
stringDebug = stringDebug + "\n";
}
GameLogger.Log(stringDebug);
isGenerated = true;//标记位
}
3.完整代码
using System.Collections.Generic;
using UnityEngine;
namespace Logic
{
/// <summary>
/// 数独终盘生成算法
/// </summary>
public class ShuDuAnswerGenerator
{
//单例
private static ShuDuAnswerGenerator s_instance;
public static ShuDuAnswerGenerator instance
{
get
{
if (null == s_instance)
s_instance = new ShuDuAnswerGenerator();
return s_instance;
}
}
private bool isGenerated = false; //标识位,用来标注数独是否已生成
private int[] ShuDuMap = new int[81];//数独的结果
private ShuDuGroup[] Zooms = new ShuDuGroup[9];//九个宫
private ShuDuGroup[] Rows = new ShuDuGroup[9];//九行
private ShuDuGroup[] Columns = new ShuDuGroup[9];//九列
private int[] ZoomOrder = new int[] { 1, 7, 3, 5, 0, 2, 6, 8 };//填写Zoom的顺序
//避免死循环,字典用来记录这个宫不允许填入某些位置 key会放入indexInZoom
Dictionary<int, int> Zoom1NotAllow = new Dictionary<int, int>();
Dictionary<int, int> Zoom7NotAllow = new Dictionary<int, int>();
Dictionary<int, int> Zoom3NotAllow = new Dictionary<int, int>();
Dictionary<int, int> Zoom5NotAllow = new Dictionary<int, int>();
Dictionary<int, int> Zoom0NotAllow = new Dictionary<int, int>();
Dictionary<int, int> Zoom2NotAllow = new Dictionary<int, int>();
Dictionary<int, int> Zoom6NotAllow = new Dictionary<int, int>();
Dictionary<int, int> Zoom8NotAllow = new Dictionary<int, int>();
/// <summary>
/// 查询数独这个位置的数
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public int GetNumberOfIndex(int index)
{
if (!isGenerated) GenerateMap();
return ShuDuMap[index];
}
/// <summary>
/// 生成数独,结果写入ShuDuMap
/// </summary>
//[Server]
public void GenerateMap()
{
isGenerated = false;
//初始化
for (int i = 0; i < 9; i++)
{
Zooms[i] = new ShuDuGroup();
Rows[i] = new ShuDuGroup();
Columns[i] = new ShuDuGroup();
}
//这里是打乱数字0-8的顺序
int[] pickItms = RandomTools.RandomFromGroup(9, 9);
//先将数字1-9随机地填写入最中心的宫
//这里因为pickItms是随机过的,直接按pickItms的顺序填写达到随机目的
for (int i = 0; i < pickItms.Length; i++)
{
int number = pickItms[i] + 1;
Zooms[4].AddMember(i, number);
int r = 3 + i / 3;
int c = 3 + i % 3;
Rows[r].AddMember(i, number);
Columns[c].AddMember(i, number);
ShuDuMap[GetCellIndex(4, r - 3, c - 3)] = number;
}
//按数字1到数字9的顺序填入数独
for (int numberOperate = 1; numberOperate <= 9; numberOperate++)
{
//打乱0-8的顺序,这里的0-8代表每个数字在宫里可以填的相对位置,
//从左到右从上到下,0是左上角,8是右下角
int[] indexRandom = RandomTools.RandomFromGroup(9, 9);
//剔除已经在中心宫填过的位置
int indexInMiddleZoom = Zooms[4].GetMemberIndex(numberOperate);
//将剩下的位置入队列
Queue<int> indexNotUsed = new Queue<int>();
for (int i = 0; i < indexRandom.Length; i++)
{
if (indexRandom[i] != indexInMiddleZoom)
{
indexNotUsed.Enqueue(indexRandom[i]);
}
}
//调用递归方法FillNumer
bool ok = FillNumber(0, indexNotUsed, numberOperate);
//清空这些字典
Zoom1NotAllow.Clear();
Zoom7NotAllow.Clear();
Zoom3NotAllow.Clear();
Zoom5NotAllow.Clear();
Zoom0NotAllow.Clear();
Zoom2NotAllow.Clear();
Zoom6NotAllow.Clear();
Zoom8NotAllow.Clear();
//在FillNumber中的回溯代表的是宫无法填写,退回上一个宫
//在这里是整个方法返回false,代表某个数字无解,需要退回上一个数字
//为了偷懒不再写一个十分复杂的逻辑,这里如果需要退回上一个数字,那么就整个数独推翻重来
if (!ok)
{
GenerateMap();
return;
}
}
//输出结果
string stringDebug = "";
for (int i = 0; i < 9; i++)
{
for (int j = 0; j < 9; j++)
{
stringDebug = stringDebug + ShuDuMap[i * 9 + j] + " ";
}
stringDebug = stringDebug + "\n";
}
GameLogger.Log(stringDebug);
isGenerated = true;//标记位
}
/// <summary>
/// 填入数字,用于递归 回溯算法
/// </summary>
/// <param name="zoomOrderIndex"></param>
/// <param name="indexes"></param>
/// <param name="number"></param>
/// <returns></returns>
private bool FillNumber(int zoomOrderIndex, Queue<int> indexes, int number)
{
//如果这个数字已经将所有宫都填写完,那么直接返回true
if (zoomOrderIndex >= ZoomOrder.Length) return true;
int cycleCount = 0;//记录循环次数,防止死循环
//如果没有可填写位置,代表无解,返回false退回上一步
if (indexes.Count < 1)
{
return false;
}
//从待填写位置的序列中取出第一个位置
int indexInZoom = indexes.Dequeue();
//计算它的行和列
int zoomRow = ZoomOrder[zoomOrderIndex] / 3;//在这个宫的行号
int zoomColumn = ZoomOrder[zoomOrderIndex] % 3;//在这个宫的列号
int r = zoomRow * 3 + indexInZoom / 3;//在整个数独中的行号
int c = zoomColumn * 3 + indexInZoom % 3;//在整个数独中的列号
//记录该数字在每个宫中有哪些位置不可填
//比如说第1个宫我们填在了左上角的位置,运行到下一个宫的时候,发现无解
//那么说明我们在第1个宫不能填左上角的位置,将这个位置记录到字典中,防止算法又重新填这个位置,陷入死循环
Dictionary<int, int> indexesNotAllow = Zoom1NotAllow;
switch (ZoomOrder[zoomOrderIndex])
{
case 1:
indexesNotAllow = Zoom1NotAllow;
break;
case 7:
indexesNotAllow = Zoom7NotAllow;
break;
case 3:
indexesNotAllow = Zoom3NotAllow;
break;
case 5:
indexesNotAllow = Zoom5NotAllow;
break;
case 0:
indexesNotAllow = Zoom0NotAllow;
break;
case 2:
indexesNotAllow = Zoom2NotAllow;
break;
case 6:
indexesNotAllow = Zoom6NotAllow;
break;
case 8:
indexesNotAllow = Zoom8NotAllow;
break;
}
//若取出的位置不能填写,那么放回队列,重新取出下一个位置,循环
while (Zooms[ZoomOrder[zoomOrderIndex]].AnyNumberFillInIndex(indexInZoom) || Rows[r].Contains(number) || Columns[c].Contains(number) || indexesNotAllow.ContainsKey(indexInZoom))
{
//将这个位置记录到不允许填写的字典中
if (!indexesNotAllow.ContainsKey(indexInZoom)) indexesNotAllow.Add(indexInZoom, 0);
cycleCount++;//记录循环次数
if (cycleCount > indexes.Count)//如果整个队列都循环过,代表无解,该回溯到上一个宫
{
//放回对列
indexes.Enqueue(indexInZoom);
//清空字典
indexesNotAllow.Clear();
//返回false回溯上一个宫
return false;
}
//放回队列并取出下一个位置
indexes.Enqueue(indexInZoom);
indexInZoom = indexes.Dequeue();
//计算它的行列号
zoomRow = ZoomOrder[zoomOrderIndex] / 3;
zoomColumn = ZoomOrder[zoomOrderIndex] % 3;
r = zoomRow * 3 + indexInZoom / 3;
c = zoomColumn * 3 + indexInZoom % 3;
}
//重置循环次数
cycleCount = 0;
//更新行、列和宫的信息
Zooms[ZoomOrder[zoomOrderIndex]].AddMember(indexInZoom, number);
Rows[r].AddMember(c, number);
Columns[c].AddMember(r, number);
//将数字填入位置,这里的GetCellIndex方法是用宫编号、在宫中的行列号计算出这个格子在整个数独中的下标号
ShuDuMap[GetCellIndex(ZoomOrder[zoomOrderIndex], indexInZoom / 3, indexInZoom % 3)] = number;
//进入递归,填写下一个宫
bool nextOK = FillNumber(zoomOrderIndex + 1, indexes, number);
//检查下一个宫是否能填写,返回true,代表下一个宫也没问题,那么返回true
//如果返回false,代表下一个宫无解,那么这个宫不能填这个位置
if (nextOK)
{
indexesNotAllow.Clear();
return true;
}
else
{
//将这个位置加到不能填写的位置的字典中
if (!indexesNotAllow.ContainsKey(indexInZoom)) indexesNotAllow.Add(indexInZoom, 0);
//删除它在对应行、列、宫中的信息
Zooms[ZoomOrder[zoomOrderIndex]].RemoveMember(number);
Rows[r].RemoveMember(number);
Columns[c].RemoveMember(number);
//清除数独这个格子填写的数字
ShuDuMap[GetCellIndex(ZoomOrder[zoomOrderIndex], indexInZoom / 3, indexInZoom % 3)] = 0;
//将这个位置重新入队列
indexes.Enqueue(indexInZoom);
//重新填写这个宫,也是递归
bool refillOK = FillNumber(zoomOrderIndex, indexes, number);
indexesNotAllow.Clear();//只要返回了上一步,都需要清空字典。
return refillOK;
}
}
/// <summary>
/// 用它所在的区域编号和在当前区域的行列号计算出在整个数独的编号,输入及输出的下标都从0开始
/// </summary>
/// <returns></returns>
private int GetCellIndex(int zoomIndex, int rowInZoom, int columnInZoom)
{
int globalIndex;
int zoomRow = zoomIndex / 3;//这个区域排在9个区域的第几行
int zoomColumn = zoomIndex % 3;//这个区域排在9个区域的第几列
globalIndex = (rowInZoom + zoomRow * 3) * 9 + (columnInZoom + zoomColumn * 3);
return globalIndex;
}
}
/// <summary>
/// 一行、一列或是一个9宫格区域
/// </summary>
public class ShuDuGroup
{
//字典,用来判断单元里面是否已经放入了某个数字
private Dictionary<int, int> membersDic = new Dictionary<int, int>();
//数组,用来记录数字填入的位置
private int[] membersArr = new int[9];
/// <summary>
/// 这个单元是否已经填过某个数字
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public bool Contains(int member)
{
return membersDic.ContainsKey(member);
}
/// <summary>
/// 计数,这个单元已填入多少个数字
/// </summary>
/// <returns></returns>
public int GetCount()
{
return membersDic.Count;
}
/// <summary>
/// 检查这个单元的指定格子是否已经填入数字
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public bool AnyNumberFillInIndex(int index)
{
return membersArr[index] != 0;
}
/// <summary>
/// 获取这个单元中该数字填入的位置
/// </summary>
/// <param name="member"></param>
/// <returns></returns>
public int GetMemberIndex(int member)
{
if (membersDic.ContainsKey(member))
{
return membersDic[member];
}
return -1;
}
/// <summary>
/// 填入数字
/// </summary>
/// <param name="indexInZoom"></param>
/// <param name="number"></param>
public void AddMember(int indexInZoom, int number)
{
//只能加入1-9
if (number != 1&&
number != 2&&
number != 3&&
number != 4&&
number != 5&&
number != 6&&
number != 7&&
number != 8&&
number !=9) return;
if (membersDic.ContainsKey(number))
{
GameLogger.Log("the number was existed ");
}
else
{
membersDic.Add(number, indexInZoom);
membersArr[indexInZoom] = number;
}
}
/// <summary>
/// 移除数字
/// </summary>
/// <param name="number"></param>
public void RemoveMember(int number)
{
if (membersDic.ContainsKey(number))
{
int index = membersDic[number];
membersDic.Remove(number);
membersArr[index] = 0;
}
}
/// <summary>
/// 清空
/// </summary>
public void ClearAll()
{
membersDic.Clear();
for (int i = 0; i < 9; i++)
{
membersArr[i] = 0;
}
}
}
}
public class RandomTools
{
/// <summary>
/// 从totalAmount中抽取pickItemCount个不重复项目,返回下标值
/// </summary>
/// <param name="totalAmount"></param>
/// <param name="pickItemCount"></param>
/// <returns></returns>
public static int[] RandomFromGroup(int totalAmount, int pickItemCount)
{
int[] totalArr = new int[totalAmount];
for (int i = 0; i < totalAmount; i++)
{
totalArr[i] = i;//令每个元素等于下标
}
int[] pickIndex = new int[pickItemCount];//抽取出来的序号的数组
for (int j = 0; j < pickItemCount; j++)//抽取j次
{
//从未抽取的部分随机抽取一项
int ranIndex = Mathf.FloorToInt(Random.value * (float)(totalAmount - j));
if (ranIndex == (totalAmount - j)) ranIndex = totalAmount - j - 1;
pickIndex[j] = totalArr[ranIndex];//将一项放到结果数组里面
//被抽出来的项目,和数组末尾交换,因为我们不会再读取到末尾,所以只需要把当前项目改掉就行
totalArr[ranIndex] = totalArr[totalAmount - j - 1];
}
return pickIndex;
}
}