扑克牌编码及排序算法实现
/**
* 题目说明及要求:
* - 请使用面向对象思维答题,且不要依赖枚举
* - 禁止出现notice,请自行修改php.ini,不要屏蔽错误输出
* 扑克牌编码规则如下:
* - 使用从1开始的十进制整数为每张扑克牌进行编码
* - 花色顺序:黑桃,红桃,梅花,方块
* - 每副牌占用64个数字,如1-64,65-128,以此类推;每个花色占用16个数字,但实际使用了13个,如1-13是黑桃A到K,17-29是红桃A到K,以此类推;最后两个数字为大小王,如63,64
* - 通配牌:大王、小王、所有牌值是2的牌,可以作为任意花色任意牌值
*
* 请根据提示完善以下功能
*/
class Card
{
/**
* 这个cardNum是题目中提到的 '使用从1开始的十进制整数' 如17 64 等
*/
public function __construct(private int $cardNum)
{
}
/**
* 示例:
* 输入:1,false;输出:array_merge(range(1,13), range(17,29), range(33,45), range(49,61))
* @param int $n 几副牌 大于0的正整数
* @param bool $useJoker 是否有王
* @return array 根据参数返回n副牌的全部编码
*/
public function getAllCardNumSet(int $n, bool $useJoker): array
{
// todo code
}
/**
* 说明:
* 输入可能有任意花色,需要返回最长的花色的结果,如最长长度的花色有多个,则随机返回一个
* 相同花色的排序规则:按照牌值(即A-K)从小到大排序,排序规则分为三种,A(1),通配牌,其他牌
* - A(1):如果只有一张A作为1处理,如果大于1张,将其中的一张作为K的下一张牌处理,即排序规则为:A23456789,10,J,Q,K,A
* - 通配牌:
* 只能充当不存在的那张牌(下称缺口),同一缺口只能使用一张通配牌,比如已有135,则缺口为2467...等,且不能有两张通配牌同时作为2(由于所有2是通配牌,所以2这个位置必然是缺口)
* 如已有的通配牌数量不足以插入所有缺口,则忽略剩余缺口,只要保证从小到大的顺序即可,如大于缺口数量,则舍弃剩余通配牌
* 优先使用同花色通配牌插入缺口,优先使用同花色2插入2这个缺口(非必须要求,加分项)
* - 其他牌:如果有多张一样牌值的如3,则排在一起,并且这两张牌排序不分前后
* 示例:
* 输入:[3,2,1,66,65,67,17,18,10,11,19,36,53] 输出:[1,2,3,67,66,18,10,11,65]
*
* @param array $input 不重复整数的一维数组,即getAllCardNumSet()的子集
* @return array 返回最长同花色排序结果的一维数组
*/
public function sortCards(array $input): array
{
// todo code
}
}
请根据Card类现有代码及注释完善该Card类内容:class Card
{
private array $cardMap = [
1 => ['name' => 'A', 'value' => 1],
2 => ['name' => '2', 'value' => 2],
3 => ['name' => '3', 'value' => 3],
4 => ['name' => '4', 'value' => 4],
5 => ['name' => '5', 'value' => 5],
6 => ['name' => '6', 'value' => 6],
7 => ['name' => '7', 'value' => 7],
8 => ['name' => '8', 'value' => 8],
9 => ['name' => '9', 'value' => 9],
10 => ['name' => '10', 'value' => 10],
11 => ['name' => 'J', 'value' => 11],
12 => ['name' => 'Q', 'value' => 12],
13 => ['name' => 'K', 'value' => 13],
63 => ['name' => '小王', 'value' => 14],
64 => ['name' => '大王', 'value' => 15],
];
/**
* 这个cardNum是题目中提到的 '使用从1开始的十进制整数' 如17 64 等
*/
public function __construct(private int $cardNum)
{
}
/**
* 示例:
* 输入:1,false;输出:array_merge(range(1,13), range(17,29), range(33,45), range(49,61))
*
* @param int $n 几副牌 大于0的正整数
* @param bool $useJoker 是否有王
* @return array 根据参数返回n副牌的全部编码
*/
public function getAllCardNumSet(int $n, bool $useJoker): array
{
$cardSet = [];
for ($i = 1; $i <= $n; $i++) {
$cardSet = array_merge($cardSet, range(($i - 1) * 64 + 1, $i * 64 - ($useJoker ? 0 : 2)));
}
return $cardSet;
}
/**
* 说明:
* 输入可能有任意花色,需要返回最长的花色的结果,如最长长度的花色有多个,则随机返回一个
* 相同花色的排序规则:按照牌值(即A-K)从小到大排序,排序规则分为三种,A(1),通配牌,其他牌
* - A(1):如果只有一张A作为1处理,如果大于1张,将其中的一张作为K的下一张牌处理,即排序规则为:A23456789,10,J,Q,K,A
* - 通配牌:
* 只能充当不存在的那张牌(下称缺口),同一缺口只能使用一张通配牌,比如已有135,则缺口为2467...等,且不能有两张通配牌同时作为2(由于所有2是通配牌,所以2这个位置必然是缺口)
* 如已有的通配牌数量不足以插入所有缺口,则忽略剩余缺口,只要保证从小到大的顺序即可,如大于缺口数量,则舍弃剩余通配牌
* 优先使用同花色通配牌插入缺口,优先使用同花色2插入2这个缺口(非必须要求,加分项)
* - 其他牌:如果有多张一样牌值的如3,则排在一起,并且这两张牌排序不分前后
*
* @param array $input 不重复整数的一维数组,即getAllCardNumSet()的子集
* @return array 返回最长同花色排序结果的一维数组
*/
public function sortCards(array $input): array
{
$cardColor = [];
// 按花色分类
foreach ($input as $num) {
$color = $this->getCardColor($num);
$cardColor[$color][] = $num;
}
// 对每个花色的牌进行排序
$sortedCards = [];
foreach ($cardColor as $color => $cards) {
$sortedCards[$color] = $this->sortSingleColorCards($cards);
}
// 找到最长的花色
$maxColor = null;
$maxCount = 0;
foreach ($sortedCards as $color => $cards) {
if (count($cards) > $maxCount) {
$maxColor = $color;
$maxCount = count($cards);
}
}
// 随机选择一种最长花色
$result = $sortedCards[$maxColor];
shuffle($result);
return $result;
}
/**
* 获取牌的花色
* @param int $num 牌的编码
* @return int 牌的花色,1-4分别代表黑桃、红桃、梅花、方块
*/
private function getCardColor(int $num): int
{
return (int)ceil($num / 16);
}
/**
* 对一种花色的牌进行排序
* @param array $cards 牌的编码数组
* @return array 排序后的牌的编码数组
*/
private function sortSingleColorCards(array $cards): array
{
// 牌值(即A-K)从小到大排序
sort($cards);
$count = count($cards);
// 牌值相同的牌排在一起
for ($i = 0; $i < $count - 1; $i++) {
$j = $i + 1;
while ($j < $count && $this->getCardValue($cards[$j]) == $this->getCardValue($cards[$i])) {
$j++;
}
if ($j > $i + 1) {
$sameValueCards = array_slice($cards, $i, $j - $i);
$cards = array_merge(
array_slice($cards, 0, $i),
$this->sortSameValueCards($sameValueCards),
array_slice($cards, $j)
);
$count -= $j - $i - 1;
$i--;
}
}
// A(1):如果只有一张A作为1处理,如果大于1张,将其中的一张作为K的下一张牌处理
// 通配牌:只能充当不存在的那张牌(下称缺口),同一缺口只能使用一张通配牌
$wildCardCount = 0; // 当前花色的通配牌数量
$cardsMap = []; // 牌值 => 编码 的映射
foreach ($cards as $card) {
$value = $this->getCardValue($card);
if ($value == 1 && isset($cardsMap[13])) {
// 如果已有K,则将A作为K的下一张牌处理
$cardsMap[14] = $card;
} elseif ($value == 2 && $wildCardCount == 0) {
// 如果是2,且还没有通配牌,则将该2作为缺口
$cardsMap[99] = $card;
} elseif ($value == 2 || $value == 14 || $value == 15) {
// 如果是通配牌,则记录通配牌数量,并将该通配牌作为缺口
$wildCardCount++;
if ($value == 2) {
$cardsMap[99] = $card;
} else {
$cardsMap[$value + 100] = $card;
}
} else {
// 其他牌直接加入到映射中
$cardsMap[$value] = $card;
}
}
// 将缺口填上通配牌
if ($wildCardCount > 0) {
ksort($cardsMap);
$wildCardUsed = 0; // 已经使用的通配牌数量
$wildCardColorMap = []; // 每个缺口对应的通配牌的花色,缺口 => 花色
foreach ($cardsMap as $value => $card) {
if ($value == 99) {
// 遇到缺口,尝试使用同花色的通配牌填充
$color = $this->getCardColor($card);
if (isset($wildCardColorMap[$value])) {
// 如果已经有同一缺口的通配牌了,则跳过
continue;
}
if (isset($cards[$color + 100]) && $wildCardUsed < $wildCardCount) {
// 如果有同花色的通配牌,则使用同花色的通配牌填充该缺口
$wildCardUsed++;
$wildCardColorMap[$value] = $color;
$cardsMap[$value] = $cards[$color + 100];
} elseif ($wildCardUsed < $wildCardCount) {
// 否则使用任意通配牌填充该缺口
$wildCardUsed++;
$cardsMap[$value] = $cards[$wildCardCount + $value];
} else {
// 如果通配牌数量已用完,则跳出循环
break;
}
}
}
}
// 按牌值排序
ksort($cardsMap);
return array_values($cardsMap);
}
/**
* 获取牌的牌值
* @param int $num 牌的编码
* @return int 牌的牌值,1-15分别代表A、2、3、4、5、6、7、8、9、10、J、Q、K、小王、大王
*/
private function getCardValue(int $num): int
{
if ($num == 63 || $num == 64) {
return $this->cardMap[$num]['value'];
}
return $num % 16 == 0 ? 13 : $num % 16 - 1;
}
/**
* 对同一牌值的牌进行排序
* @param array $cards 牌的编码数组
* @return array 排序后的牌的编码数组
*/
private function sortSameValueCards(array $cards): array
{
// A(1):如果只有一张A作为1处理,如果大于1张,将其中的一张作为K的下一张牌处理
// 通配牌:只能充当不存在的那张牌(下称缺口),同一缺口只能使用一张通配牌
$wildCardCount = 0; // 当前牌值的通配牌数量
$cardsMap = []; // 花色 => 编码 的映射
foreach ($cards as $card) {
$color = $this->getCardColor($card);
$value = $this->getCardValue($card);
if ($value == 1 && isset($cardsMap[$color]['K'])) {
// 如果已有K,则将A作为K的下一张牌处理
$cardsMap[$color]['A'] = $card;
} elseif ($value == 2 && $wildCardCount == 0) {
// 如果是2,且还没有通配牌,则将该2作为缺口
$cardsMap[$color][99] = $card;
} elseif ($value == 2 || $value == 14 || $value == 15) {
// 如果是通配牌,则记录通配牌数量,并将该通配牌作为缺口
$wildCardCount++;
if ($value == 2) {
$cardsMap[$color][99] = $card;
} else {
$cardsMap[$color][$value + 100] = $card;
}
} else {
// 其他牌直接加入到映射中
$cardsMap[$color][$value] = $card;
}
}
// 将缺口填上通配牌
if ($wildCardCount > 0) {
foreach ($cardsMap as $color => &$cards) {
ksort($cards
原文地址: https://www.cveoy.top/t/topic/ntDh 著作权归作者所有。请勿转载和采集!