如何用php更好的实现 0777这种二进制的集合标记

@Ta 2022-08-29发布,2022-08-29修改 3982点击

望大佬们提供更加好的解决办法,有人会问这个使用场景,比如 数据库字段你存一个用户的爱好集合,你使用json ["xx","xx"] 还是希望用一个int 来存集合?

目前我使用两种方案。
第一种是转换为 二进制字符串,然后从右往左 按照位是否是==1 来标记集合.
第二种使用 按位与& 来实现,是否 爱好和 用户int 命中,如果两者的任意位 命中,那么 a&b 一定>0 也就是 !=false 也就是 !==0b0 



image.png(13.14 KB)


目前我认为 的 比较佳的解决方案,

image.png(12.97 KB)

代码
<?php
$map=[0=>"唱歌a",1=>"跳舞",2=>"洗澡",7=>'睡觉a',8=>'吃饭'];

$decNum=129; // 010000001 (len=9,max_index=8) ; result=["唱歌","吃饭"]

$bin= decbin($decNum);

$mapKeys=array_keys($map);

$maxIndex=$mapKeys;
ksort($maxIndex);

$maxIndex=array_pop($maxIndex)+1;

echo '转2进制(缩写):'.$bin."</br>";

echo '转2进制(补全最大位):'.sprintf("%0".$maxIndex."s",$bin)."</br>";

function strMethod($dec,$map){
    //取最大位数+1
    $mapKeys=array_keys($map);
    $maxIndex=$mapKeys;
    ksort($maxIndex);
    $str= decbin($dec);
    $result=[];
    //吧 0b1000 反转为 0b0001 ,因为 mapkey的第0=> 是指 从右往左的 下标
    $str=strrev($str);
    for($i=0;$i<=strlen($str);$i++){
        if(\in_array($i,$mapKeys) && intval($str[$i])===0b1){
            $result[]=$map[$i];
        }
    }
    return  $result;
}


function bitOperation($dec,$map){

    $mapKeys=array_keys($map);
    $maxIndex=$mapKeys;
    ksort($maxIndex);
    $maxIndex=array_pop($maxIndex)+0b1;


    /**
     *
     * array_filter结果是反转k=>v; { ["唱歌"]=> int(0) ["跳舞"]=> int(1) ["洗澡"]=> int(2) ["睡觉"]=> int(7) ["吃饭"]=> int(8) }
     *
     * array_filter(array,function($MapItemValue){
        //return true 就是不过滤,false为过滤
        //假设第1伦是 $MapItemValue=0  ; //(2== 0b10)
     * 唱歌 0b0+0b1=0b1=1
        0b        1
        0b010000001
     * result : &按位与  两个数二进制部分 相同索引的都是1才能 !=false (0b0),所以这个是1 >0
     * 跳舞 0b01+0b01= 0b10=2
        0b       10
        0b010000001
     * })
     */

   return  array_filter(array_flip($map),function($MapItemValue)use ($dec,$maxIndex){

//       echo '本次 $MapItemValue='.sprintf("%0".$maxIndex."s",decbin(1<<$MapItemValue));
//       echo ';dec='.sprintf("%0".$maxIndex."s",decbin($dec));
//       echo ';&结果'. (decbin(1<<$MapItemValue)&$dec).';';
       //这里犯错  (1<<$MapItemValue)&$dec!==0b00000; //这里会优先 $dec!=0b0001 然后在&
//       echo ((1<<$MapItemValue)&$dec)!==0b00000;
//       echo "<br>";
       return ((1<<$MapItemValue)&$dec)!==0b00000;
    });

}

$result1=strMethod($decNum,$map);
var_dump($result1);

echo "<br>";

$result2=bitOperation($decNum,$map);
var_dump(array_keys($result2));
回复列表(17|隐藏机器人聊天)
  • @Ta / 2022-08-29 / /
    @老虎会游泳 快来讨论
  • @Ta / 2022-08-29 / /

    对于虎绿林的权限,我选择预定义常量 + 整数 + 位操作,因为权限的个数是固定的。

    注意别用MySQL的bit类型,那是自找麻烦(写SQL时有类型转换问题),使用int UNSIGNED类型就可以避免,如果超过32位就用bigint UNSIGNED

    --
    -- 表的结构 `hu60_user`
    --
    
    CREATE TABLE `hu60_user` (
      `uid` int(11) NOT NULL,
      `name` varchar(16) CHARACTER SET utf8mb4 NOT NULL,
      -- ……
      `permission` int(10) UNSIGNED NOT NULL DEFAULT 0,
      -- ……
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    

    https://gitee.com/hu60t/hu60wap6/blob/master/src/class/userinfo.php

    https://github.com/hu60t/hu60wap6/blob/master/src/class/userinfo.php

    <?php
    /**
     * 虎绿林WAP6 读取用户信息
     */
    class userinfo implements ArrayAccess
    {
        // 权限列表开始
        // 权限有两种,一种是管理员具有的管理权限,一种是用户被管理员设置的负面状态。
        // 两者的作用完全不同,但是存储在一起,请注意区分。
    
        /*** 管理员权限: 帖子编辑权限 */
        const PERMISSION_EDIT_TOPIC = 1;
    
        /*** 用户负面状态: 用户被禁止使用div和span标签
         * 
         * 该状态未开放给版主,只有站长通过SQL才能设置该状态。
        */
        const DEBUFF_UBB_DISABLE_STYLE = 2;
    
        /*** 用户负面状态: 用户被禁言 */
        const DEBUFF_BLOCK_POST = 4;
    
        /*** 用户负面状态: 用户被禁止@他人 */
        const DEBUFF_BLOCK_ATINFO = 8;
    
        /*** 管理员权限: 设置禁言的权限 */
        const PERMISSION_SET_BLOCK_POST = 16;
    
        /*** 管理员权限: 帖子加精权限 */
        const PERMISSION_SET_ESSENCE_TOPIC = 32;
    
    	/*** 用户负面状态: 用户发言需要审核 */
    	const DEBUFF_POST_NEED_REVIEW = 64;
    
    	/*** 管理员权限: 审核用户发言的权限 */
    	const PERMISSION_REVIEW_POST = 128;
    
        // 权限列表结束
    
        // ……
    
        public function hasPermission($permission) {
            if (NULL === self::$data['permission'][$this->uid]) {
                $db = self::conn(true);
                $sql = 'SELECT `permission` FROM `'.DB_A.'user` WHERE uid = ?';
                $rs = $db->prepare($sql);
    
                if (!$rs || !$rs->execute([$this->uid])) {
                    throw new UserException('数据库异常,无法读取权限信息!', 10500);
                }
    
                $data = $rs->fetch(db::num);
                self::$data['permission'][$this->uid] = $data[0];
            }
    
            return (bool) ($permission & self::$data['permission'][$this->uid]);
        }
    
    	public function addPermission($permission) {
    		unset(self::$data['permission'][$this->uid]);
    
    		$db = self::conn();
    		$sql = 'UPDATE `'.DB_A.'user` SET `permission` = `permission` | ? WHERE uid = ?';
    		$rs = $db->prepare($sql);
    
    		if (!$rs || !$rs->execute([(int)$permission, $this->uid])) {
    			throw new UserException('数据库异常,无法设置权限信息!', 11500);
    		}
    	}
    
    	
    	public function removePermission($permission) {
    		unset(self::$data['permission'][$this->uid]);		
    
    		$db = self::conn();
    		$sql = 'UPDATE `'.DB_A.'user` SET `permission` = `permission` & ~ ? WHERE uid = ?';
    		$rs = $db->prepare($sql);
    
    		if (!$rs || !$rs->execute([(int)$permission, $this->uid])) {
    			throw new UserException('数据库异常,无法设置权限信息!', 11500);
    		}
    	}
    
        // ……
    }
    
  • @Ta / 2022-08-29 / /

    对于用户的爱好集合,我选择存成单独的表。

    CREATE 爱好列表(
        爱好id int,
        爱好名称 varchar(255)
    );
    CREATE 用户的爱好(
        uid int,
        爱好id int
    );
    

    这样才方便对爱好列表进行动态更新。

  • @Ta / 2022-08-29 / /
    @老虎会游泳
    ```
    $sql = 'UPDATE `'.DB_A.'user` SET `permission` = `permission` & ~ ? WHERE uid = ?';
    ```

    字段还可以这样玩吗?这个   `permission` = `permission` & ~ ?
  • @Ta / 2022-08-29 / /

    @胡椒舰长,MySQL位运算符:

    • 与:a & b
    • 或:a | b
    • 非(取反):~ a
    • 异或:a ^ b
    • 左移:a << b
    • 右移:a >> b

    a & ~ b表示“a与非b”,先反转b,然后再与上a,这样就从a中去掉了b。

    注意:a和b应该同为int,或者同为bit。如果一个为bit一个为int,结果不正确。所以我在上面建议只使用int,因为int的表达更简单。

  • @Ta / 2022-08-29 / /
    @老虎会游泳, 使用markdown语法一定要 每次手动加 这个吗,我在hu60一直用的ubb,觉得还是markdown方便,ubb 对手机输入不友好
    <!-- markdown -->
    
  • @Ta / 2022-08-29 / /
    @老虎会游泳
    <!-- markdown -->
    >  注意:a和b应该同为int,或者同为bit。如果一个为bit一个为int,结果不正确。所以我在上面建议只使用int,因为int的表达更简单。

    我的理解 bit 是  代码 `0b0010` ,而 int 默认是10进制 `2` ,这两个表达式的区别吗?我一直认为 bit和int 好像区别不大,反而是string则是 array[ bit,bit,bit]
  • @Ta / 2022-08-29 / /

    @胡椒舰长,你可以选择自动加

    https://hu60.cn/q.php/bbs.topic.83135.html

    标记必须出现在顶部,否则没有效果。

    只为了解它的人开启它,以免产生*号消失的困扰。

  • @Ta / 2022-08-29 / /

    @胡椒舰长,PHP没有原生bitset类型,只能以int发送和接收参数。在字段为bit但预处理参数为int时,结果不正确,我不知道为什么。因为PHP没有bitset,所以解决该问题的最简单办法显然是把类型统一到int。

  • @Ta / 2022-08-29 / /
    @老虎会游泳,可以增加一个“仅 Markdown”,无表情无小尾巴的
  • @Ta / 2022-08-29 / /

    @胡椒舰长,php中的0b0010依然是int类型,因为它有固定的宽度(32位或64位)。而bitset(MySQL中的bit类型)具有自定义宽度。

  • @Ta / 2022-08-29 / /
    @tasy5kg,加好了
  • @Ta / 2022-08-30 / /

    @胡椒舰长,我认为若干方案及其适合场景:

    方案 不限制爱好总数 快速查找所有爱好xx的人 节省数据库空间
    bigint位运算 (64)
    json字段
    单独成表 ❌[^1]

    [^1]: MySQL数据库InnoDB引擎CompactDynamic格式下,按照老虎在 3楼 的用户的爱好表结构(假设uid爱好id都是主键),较差情况下(全部是随机插入),每存某个人的一个爱好,平均会占用 52 字节。

  • @Ta / 2022-08-30 / /
    @无名啊,int储存方式,也可以查询啊, 比如我要查用户爱好,用生成器,生成数字,然后查询,bigint 最大是8字节,也就是64位,可以存64种类型,但是数据库查询int 永远最快
  • @Ta / 2022-08-30 / /

    @胡椒舰长,可以查询“某人是否爱好xx”,且此时比单独成表

    但若你要“查找所有爱好xx的人”的话,就要扫全表了

  • @Ta / 2022-08-30 / /
    @无名啊,集合确实要全表扫
  • @Ta / 2022-08-30 / /

    @胡椒舰长,所以就看你需求啦,

    • 不用省空间,或要“快速查找所有爱好xx的人”,就单独成表
    • 要省空间,且“爱好总数”一定不会超过 8/16/24/32/64,就xxxINT UNSIGNED字段
    • 否则就json字段

    这是我的理解

添加新回复
回复需要登录