红音列表实现引发的思考

来自姬鸿昌的知识库
Jihongchang讨论 | 贡献2022年11月12日 (六) 11:21的版本 →‎考虑所有用户总权重值的离散程度
(差异) ←上一版本 | 最后版本 (差异) | 下一版本→ (差异)
跳到导航 跳到搜索

需求

是:

要为当前登录用户显示一个列表,这个列表里是过去72小时内所有登录过的用户;

这些用户要按一定的规则排序,规则是:

比如当前登录用户是女性,那么男性用户要排在女性用户前面(1、0);

同样是男性用户,有房的要排在没房的前面(1、0);

同样有房的,有车的要排在没车的前面(1、0);

受教育程度(博士、硕士、学士、高中)……(4、3、2、1);

婚姻状况(未婚、离异、丧偶)(3、2、1)

工作(央企、国企、公务员、……)(4、3、2、1)

年龄(28~35、35~40、23~28、……)(4、3、2、1);

身材((kg~kg)苗条、(kg~kg)匀称、(kg~kg)偏瘦、(kg~kg)偏胖、……)(4、3、2、1)

GPS定位计算出相对距离(km数、m数)

…… 要求实现一套权重机制


权重设计根据需求细分考虑两种情况:

一种是绝对优于:属性A 属性B 属性C……,这些属性的权重不在一个数量级,A>2B,B>2C,C>2D,属性优先级存在绝对高低,本需求是这种,这种就要看存储的属性(字段)多少,总权重根据字段多少倍增,来考虑怎么存;

一种不能存在绝对优于,各属性权重在同一个数量级:,这些属性的权重在一个数量级相对好存;

考虑权重因子中最大值和最小值的差距:

比如:

是否有房作为一个因子

和GPS定位相距多少米

是否有房 其他字段 GPS相距多少米
0 …… 100
0 …… 200
1 …… 100
1 …… 200



考虑所有用户总权重值的离散程度

简单的实现

是:

public class RankRecord {

    /**
     * 性别
     */
    private int gender;

    /**
     * 是否有房
     */
    private int house;

    /**
     * 是否有车
     */
    private int car;

    /**
     * 身高
     */
    private int height;

    /**
     * 体重
     */
    private int weight;

    /**
     * 权重
     */
    private int weights;

    public RankRecord(int gender, int house, int car, int height, int weight) {
        this.gender = gender;
        this.house = house;
        this.car = car;
        this.height = height;
        this.weight = weight;
        initWeights();
    }

    /**
     * 初始化权重
     * 整型应用位运算计算权重要把权重最大的属性放在前面,权重小的属性放在后面
     */
    private void initWeights() {
        int weights = gender;
        weights = (weights << 1) + house;
        weights = (weights << 1) + car;
        weights = (weights << 1) + height;
        weights = (weights << 1) + weights;
        this.weights = weights;
    }

}

但是 int 只有4个字节,最大值是 231,即便不考虑单个字段多值的情况(不止有0和1,还有像GPS定位计算出的相距公理、米数)作为权重因子,

应用左移扩大2倍很可能放不下所有的权重字段。

那么 weight 就声明成 long,可 long 最大也就是 263,还有比 long 能存储更大的值以便应用所有、甚至更多的权重因数吗?

float、double、BigDecimal、BigInteger?那么它们是怎么存的呢?每个字段存放进去又是怎么一个情况呢?


浮点数的实现

需要注意的是:不能应用位运算在浮点数上。

package rank;

public class RankRecord {

    /**
     * 性别
     */
    private int gender;

    /**
     * 是否有房
     */
    private int house;

    /**
     * 是否有车
     */
    private int car;

    /**
     * 身高
     */
    private int height;

    /**
     * 体重
     */
    private int weight;

    /**
     * 权重
     */
    private double weights;

    public RankRecord(int gender, int house, int car, int height, int weight) {
        this.gender = gender;
        this.house = house;
        this.car = car;
        this.height = height;
        this.weight = weight;
        initWeights();
    }

    /**
     * 初始化权重
     */
    private void initWeights() {
        double weights = 0;
        weights += gender;
        weights = weights * 2 + house;
        weights = weights * 2 + car;
        weights = weights * 2 + height;
        weights = weights * 2 + weights;
        this.weights = weights;
    }

}

这样应该能放下很多权重因子了,对于浮点数来说更多的是精度的问题,如果精度存储达不到了,计算和比较就会出现错误。


那么浮点数的极限在哪里?

这一节点可以参考一下:Java中的浮点型

float

比如float的精度:

public class FloatPrecision {
    public static void main(String[] args) {
        float a = 12345.12346f;
        System.out.println(a);

        float b = 123456789f;
        System.out.println(b);
    }
}
12345.123
1.23456792E8

这么看 float 应用科学计数法存储二进制之后也就只能达到 7、8 位有效数/尾数 的样子,应用在字段上做权重的话就是:

public class Test4 {

    public static void main(String[] args) {

        float f1 = 12345678f;
        System.out.println(f1);

        float f2 = 123456789f;
        System.out.println(f2);

        System.out.println(Math.log(f1)/Math.log(2));
        
    }

}
1.2345678E7
1.23456792E8
23.557502732800643

就是大概20个字段左右,这还是字段权重是绝对的、是2倍乘的关系。


那么 double 呢?

public class DoublePrecision {

    public static void main(String[] args) {
        double a = 123456789.01234567890123d;
        System.out.println(a);

        double b = 1234567890123456789d;
        System.out.println(b);
    }

}
1.2345678901234567E8
1.23456789012345677E18

double 也是存个17、18位的十进制有效数的样子,做权重的话就是:

public class Test5 {
    public static void main(String[] args) {
        double d = 12345678901234567d;
        System.out.println(d);
        System.out.println(Math.log(d)/Math.log(2));
    }
}
1.2345678901234568E16
53.45485569210364

50个字段左右,少于50个字段,属性不多的话应该是够用了。



这么看整型和浮点型似乎是差不多的

整型是最大值、最小值的限制;

浮点数是尾数精度的限制;

但其实在二进制位的角度上考虑:

一个字段的权重状态如果只有1和0的一个bit,那整型和浮点型能存储下的各个属性满足或不满足的状态信息的属性个数是差不多的,占用存储空间越大的类型、能存放下的描述对象的属性细节(整型-大小值、浮点型-精确程度)也就越多。



最终解决方案还是要 BigInteger、BigDecimal

字段要是比较多、字段权重因数比较高的话,还是要用 BigInteger、BigDecimal