如何根据百分比在数组中分配值

How to distribute values across an array according to percentages

我正在根据构建制作 DBZ Xenoverse 2 统计分布指南,以便您在属性中获得一致的游戏玩法。我们的想法是,如果到 80 级,您总共有 332 stat/attribute 点可以分配,并且您希望在创建构建时保持一致的点分配,则需要从总数中收集属性的百分比收集点并根据每个级别的百分比分配它们。所以如果我想要一个看起来像这样的角色构建:

Health - 30
Ki - 42
Stamina - 42
Basic Attacks - 93
Strike Supers - 0 
Ki Blast Supers - 125
Total - 332

百分比看起来像:

Health - 9.03614457831325%
Ki - 12.65060240963855%
Stamina - 12.65060240963855%
Basic Attacks - 28.01204819277108%
Strike Supers - 0%
Ki Blast Supers -37.65060240963855%

所以在第 2 级(因为你没有获得第 1 级的统计数据)你得到两个统计点,你的属性看起来像这样:

Health - 0
Ki - 0
Stamina - 0
Basic Attacks - 1
Strike Supers - 0
Ki Blast Supers - 1

而在 20 级时,您的统计数据将如下所示:

Health - 5
Ki - 7
Stamina - 7
Basic Attacks - 16
Strike Supers - 0
Ki Blast Supers - 22
Total - 57

所以最终结果看起来像:

LVL 1
stat array

LVL 2
stat array

...

LVL 80
stat array

由于角色在每个级别接收可变数量的统计数据,我们必须有一个硬编码数组并根据它更改我们的分布,以及已经使用的和应该使用的。

<?php
class Stats {

    public function show_level_proportions( $build_stats ) {

        $build_stat_labels = [ 'max_health', 'max_ki', 'max_stamina', 'basic_attack', 'strike_supers', 'ki_blast_supers' ];
        $build = array_combine($build_stat_labels, $build_stats);

        $stat_percents = $this->calculate_stat_percents($build);
        $get_stats_per_lvl = $this->get_stats_per_lvl($stat_percents, $build);

        return $get_stats_per_lvl;
    }

    //Stats given from levels 1-20
    private $incremental_points = [
            0, //1
            2, //2
            3, //3
            3, //4
            3, //5
            3, //6
            3, //7
            3, //8
            3, //9
            3, //10
            3, //11
            3, //12
            3, //13
            3, //14
            3, //15
            3, //16
            3, //17
            3, //18
            3, //19
            4, //20 total: 57
        ];

    private function calculate_stat_percents( $build_stats ) {
        $stat_sum = array_sum( $build_stats );

        foreach ( $build_stats as $key => $value ) {
            $calculated_stat = $this->calc_percents($stat_sum, $value);
            $stat_percentages[$key] = $calculated_stat;
        }

        return $stat_percentages;
    }

    private function calc_percents($sum, $stat){
        $product = ( $stat / $sum );
        return round( $product, 8, PHP_ROUND_HALF_UP );
    }

/*================
* This is the problem area
* I can get the percentages for the inputted stats,
* but I don't even know how to start getting and
* distributing the percentages for each level. So
* right now, it's static, only works with the input,
* and doesn't incorporate the $incremental_stats.
================*/
    private function get_stats_per_lvl($percentages, $stats){
        $stats_total = array_sum($this->incremental_points);

        foreach( $percentages as $key => $value ){
            $lvl_twenty_stats[$key] = $stats_total * $value;
            $rounded_lvl_twenty_stats[$key] = round( $lvl_twenty_stats[$key], 0, PHP_ROUND_HALF_UP );
        }

        return $rounded_lvl_twenty_stats;
    }
}

$stat_tracker = new Stats();
print_r( $stat_tracker->show_level_proportions([5, 0, 5, 20, 7, 20]) );

好的,那么,为了回答这个问题,我将首先回顾一下理论,然后是 PHP 中的实际实现。


理论

因此,在任何给定时刻,您都有当前的属性值和理想的属性值。

对于这两个属性集合,您可以计算每个属性占总数的百分比。

因此,例如,给定一个属性集合,例如:

{
    "foo": 2,
    "baz": 1,
    "bar": 3,
    "foobar": 0
}

这里的总分是 6,所以计算出来的每分占总分的百分比是(伪JSON):

{
    "foo": 33.3%,
    "baz": 16.6%,
    "bar": 50.0%,
    "foobar": 0.0%
}

如果您为每个属性集合(所需的和当前的)计算此百分比列表,则您可以 减去 当前的每个所需的值以获得百分比偏移每个当前值都关闭了。

因此,负偏移百分比表示与该偏移关联的给定统计数据低于它应该在的位置,0 偏移意味着它完全均匀分布水平,正偏移百分比表示偏移 高于 应有的位置。

由于我们无法分配一个点的一部分,所以总会有添加点将其敲得太高,因此我们可以安全地忽略具有正百分比偏移的属性。

同样,也可以忽略其百分比偏移量恰好为 0 的属性,因为至少在当前时刻,该属性是正确分布的。

因此,我们只真正关心具有负偏移百分比的属性,因为这些是需要增加的属性。

总而言之,对于分配的每个点,我们需要计算当前属性的百分比偏移,将一个点分配给具有最低偏移百分比的属性,并重复这个过程,直到我们没有分数,记录我们在这个过程中分配的分数和分数。


例子

让我们使用与 OP 问题中相同的示例。理想的分布属性集合是(属性名称被截断):

{
    basic: 93,
    ss: 0, 
    kbs: 125,
    health: 30,
    ki: 42,
    stamina: 42
}

而当前是零的集合(因为还没有分配点):

{
    basic: 0,
    ss: 0, 
    kbs: 0,
    health: 0,
    ki: 0,
    stamina: 0
}

我们还有 2 分要分配。

点 1

对于第一个点,我们计算百分比偏移量,如下所示:

{
  basic: -0.28012048192771083,
  ss: 0,
  kbs: -0.37650602409638556,
  health: -0.09036144578313253,
  ki: -0.12650602409638553,
  stamina: -0.12650602409638553
}

如果我们忽略所有 zeroes/positive 值(如前所述),并按最低的负数对负数进行排序,我们将得到以下结果:

[
  { name: 'kbs', value: -0.37650602409638556 },
  { name: 'basic', value: -0.28012048192771083 },
  { name: 'ki', value: -0.12650602409638553 },
  { name: 'stamina', value: -0.12650602409638553 },
  { name: 'health', value: -0.09036144578313253 }
]

kbs(Ki Blast Supers)作为最低偏移量,因此,我们为其分配一个点,然后继续下一个点。

点 2

再次,让我们用增加的 KBS 值计算百分比偏移量。请注意,由于增加,KBS 现在 正值

{
  basic: -0.28012048192771083,
  ss: 0,
  kbs: 0.6234939759036144,
  health: -0.09036144578313253,
  ki: -0.12650602409638553,
  stamina: -0.12650602409638553
}

然后,再次按最低负值排序,抛出零值和正值。请注意,KBS 现在不符合条件,因为它与其他值相比略高。

[
  { name: 'basic', value: -0.28012048192771083 },
  { name: 'ki', value: -0.12650602409638553 },
  { name: 'stamina', value: -0.12650602409638553 },
  { name: 'health', value: -0.09036144578313253 }
]

所以现在 basic(基本攻击)的负值最低,因此我们将最终点分配给它。

因此,我们分配的总分如下:

{
  kbs: 1,
  basic: 1
}

根据 OP 的预期结果正确分配。

实施

至于 PHP 中的实现,我不会给出 100% 的工作示例,但基本思想是您需要一个名为 getPointsDistributionForTarget 的函数,它获取当前属性分数,目标属性得分分布,以及要分配的点数。

function getPointsDistributionForTarget(current, target, pointsToSpend) { ... }

在该函数中,您需要循环有积分可花费的次数,每次循环执行以下操作:

  1. 计算偏移百分比的排序数组
  2. 取最高(0个索引)值和:
  3. 增加关联的当前属性值并增加一,并且
  4. 请注意,您在 运行 每个属性的总计列表中将该属性增加了一个

希望这对您有所帮助!如果您有任何问题,请告诉我。我在 Node (Javascript) 中有一个有效的实现,我可以提供我用来测试这个理论并在示例中生成数字的信息,或者如果有帮助,我可以尝试将该示例转换为 PHP。