对具有不确定数据的数组进行排序

Posted

技术标签:

【中文标题】对具有不确定数据的数组进行排序【英文标题】:Sorting arrays with uncertain data 【发布时间】:2016-04-04 19:17:22 【问题描述】:

我正在为浏览器游戏制作一个脚本,该脚本将生成一个随机动物供玩家使用 0-5 标记进行战斗。该动物身上的标记是随机生成的,并被输入一个自定义的 imagick 函数,该函数将按照它们在数组中出现的顺序添加它们。

虽然标记是随机决定的,但它们应该如何出现在动物身上有很多规则,例如“全身”区域的标记显示在“腹部”区域的标记之上。为了更好地解释,我将附上迄今为止测试人员的图像:

所以要分解这个随机生成的动物身上的 5 个标记,eyeshadow 标记属于眼睛区域,undertail 属于尾巴,streaks 属于全身,appaloosa 属于背部,@987654328 @属于腿。现在的订单只是在脚本循环访问数据库并随机选择标记时添加的,所以 okapi(腿上的条纹)位于最上面,因为它是数组中的最后一个,并且是添加的最后一个。但是按照顺序规则,数组中的最后一个应该是条纹(身体上的水平条纹),因为全身标记在顶部。

这是选择标记的代码,这是使用 Laravel 引擎完成的:

    // Determine number of markings
    $num = mt_rand(1,10);

    if ($num == 1) 
        $markingNum = 0;
     elseif ($num > 1 && $num < 4) 
        $markingNum = 1;
     elseif ($num > 4 && $num < 6) 
        $markingNum = 2;
     elseif ($num > 6 && $num < 8) 
        $markingNum = 3;
     elseif ($num > 8 && $num < 10) 
        $markingNum = 4;
     else 
        $markingNum = 5;
    

    // Calculate Marking type and color
    $markings = array();

    if ($markingNum > 0) 
        for ($m = 0 ; $m < $markingNum; $m++) 
            // Set color values (pulls from the "pallet" selected earlier in the code, which will determine the range of color that marking can be)
            if ($m == 1) 
                $pal = $pallet->marking1;
             elseif ($m == 2) 
                $pal = $pallet->marking2;
             elseif ($m == 3) 
                $pal = $pallet->marking3;
             elseif ($m == 4) 
                $pal = $pallet->marking4;
             else 
                $pal = $pallet->marking5;
            

            // Pull previous marking info
            if (count($markings) != 0) 
                    $previous = DataMarking::whereIn('name', array_keys($markings))->get();

                    // This pulls the regions of the current markings in the array so it won't select a region that already has a marking.
                    foreach ($previous as $p) 
                        $regions[$p->region] = $p->name;
                    

                    // Uncommon marking (10% chance)
                    $r = mt_rand(1, 10);

                    if ($r == 10) 
                        $marking = DataMarking::where('rarity', 1)
                                              ->where('public', 1)
                                              ->whereNotIn('name', array_keys($markings))
                                              ->whereNotIn('region', array_keys($regions))
                                              ->orderByRaw("RAND()")
                                              ->first();
                    // Common markings
                     else 
                        $marking = DataMarking::where('rarity', 0)
                                              ->where('public', 1)
                                              ->whereNotIn('name', array_keys($markings))
                                              ->whereNotIn('region', array_keys($regions))
                                              ->orderByRaw("RAND()")
                                              ->first();
                    

                    // Colors marking
                    if ($pal == 0) 
                        $markingColor = rand_color();
                     else 
                        $range = ColorRange::where('id', $pal)->firstOrFail();
                        $markingColor = "#" . mixRange(substr($range->start_hex, 1), substr($range->end_hex, 1));
                    

                    $markings[$marking->name] = $markingColor;
                 else 
                // Uncommon marking (10% chance)
                $r = mt_rand(1, 10);

                if ($r == 10) 
                    $marking = DataMarking::where('rarity', 1)
                                          ->where('public', 1)
                                          ->orderByRaw("RAND()")->first();
                // Common marking
                 else 
                    $marking = DataMarking::where('rarity', 0)
                                          ->where('public', 1)
                                          ->orderByRaw("RAND()")->first();
                

                // Colors marking
                if ($pal == 0) 
                    $markingColor = rand_color();
                 else 
                    $range = ColorRange::where('id', $pal)->firstOrFail();
                    $markingColor = "#" . mixRange(substr($range->start_hex, 1), substr($range->end_hex, 1));
                

                $markings[$marking->name] = $markingColor;
            
        
    

我认为我可以使用许多复杂的 if 语句来完成此任务,但它对我来说似乎不是一个优雅的解决方案。此外,还有一个例外:“渐变”,一个全身标记,即使它是一个全身标记,它也会出现在所有物体的下方。不过,到目前为止,它是唯一一个具有这种例外的标记。

我尝试过使用 php 提供的各种 sort 函数,但运气不佳。 uksort 似乎是最有希望的,但由于我们排序的值存在于数据库中而不是我们排序的数组中(imagick 函数必须输入标记 => 颜色数组格式),事实证明很难工作与。

Tl;dr:我需要根据数据库中存在的键值(标记区域)重新排序一个不确定数据量的数组。实现此目的最优雅的方法是什么?

【问题讨论】:

我喜欢有狗的图片,即使它看起来很奇怪。 哈哈!这是一条奇怪的狗,不是吗? 您的DataMarking 表中似乎需要一个z-index (int) 列。此属性充当深度。所以eyeshadowz-index例如可以是1,streaksz-index可以是5。z-index越高,标记越晚绘制,导致streaks被绘制在一切之上。检索到标记数组后,按z-index 升序排列。然后遍历它并绘制所有标记。 您似乎有很多不同的数据数组需要合并到一个数组中 如果您的代码有效,您应该考虑在CodeReview 上发布此问题;对于 *** 而言,这个问题的答案可能过于宽泛或冗长。 【参考方案1】:

以下是对您的代码的一些优化,内联有 cmets 来描述所做的工作。这显然还没有完成,因为 Marcin 在他的回答中指出了一些更好的东西。

 // Determine number of markings
    $num = mt_rand(1,10);

    // Removed redundent $num > X as the conditions were already meet that it was > X by the previous if statement
    if ($num == 1) 
        $markingNum = 0;
     else if ($num < 4) 
        $markingNum = 1;
     else if ($num < 6) 
        $markingNum = 2;
     else if ($num < 8) 
        $markingNum = 3;
     else if ($num < 10) 
        $markingNum = 4;
     else 
        $markingNum = 5;
    

    // Calculate Marking type and color
    $markings = array();

    if ($markingNum > 0) 
        for ($m = 1 ; $m <= $markingNum; $m++)  // incrimented to 1 and <= so we can dynamically select elements
            // Set color values (pulls from the "pallet" selected earlier in the code, which will determine the range of color that marking can be)
            $pal = $pallet->'marking' . $m; // Removed if/else and replaced with a dynamic variable


            // Uncommon marking (10% chance)
            $r = mt_rand(1, 10);

            // removed duplicate database selections for a simple $rarity variable that accomplishes the same task
            if ($r == 10) 
                $rarity = 1;
             else 
                $rarity = 0;
            

            $marking = DataMarking::where('rarity', $rarity)
                                  ->where('public', 1)
                                  ->whereNotIn('name', array_keys($markings))
                                  ->whereNotIn('region', $regions)
                                  ->orderByRaw("RAND()")
                                  ->first();

            // Colors marking
            if ($pal == 0) 
                $markingColor = rand_color();
             else 
                $range = ColorRange::where('id', $pal)->firstOrFail();
                $markingColor = "#" . mixRange(substr($range->start_hex, 1), substr($range->end_hex, 1));
            

            $markings[$marking->name] = $marking; // adds all of the marking data, this is where you could have a z-index in the database
            $markings[$marking->name] = $markingColor; // add your color to your marking data
            $regions[] = $marking->region;

        
    

【讨论】:

【参考方案2】:

我不会回答你的问题,但是看看你的代码有很大的改进空间。

考虑一下:

if ($m == 1)     
    $pal = $pallet->marking1;
 elseif ($m == 2) 
    $pal = $pallet->marking2;
 elseif ($m == 3) 
    $pal = $pallet->marking3;
 elseif ($m == 4) 
    $pal = $pallet->marking4;
 else 
    $pal = $pallet->marking5;

它可以更改为更简单的东西:

$pa1 = (in_array($m,range(1,4))) ? $pallet->marking$m : $pallet->marking5;

同样适用于:

if ($r == 10) 

    $marking = DataMarking::where('rarity', 1)
                          ->where('public', 1)
                          ->whereNotIn('name', array_keys($markings))
                          ->whereNotIn('region', array_keys($regions))
                          ->orderByRaw("RAND()")
                          ->first();
// Common markings
 else 
    $marking = DataMarking::where('rarity', 0)
                          ->where('public', 1)
                          ->whereNotIn('name', array_keys($markings))
                          ->whereNotIn('region', array_keys($regions))
                          ->orderByRaw("RAND()")
                          ->first();

如果可以改写为:

$marking = DataMarking::where('rarity', ($r == 10) ? 1 : 0)
                      ->where('public', 1)
                      ->whereNotIn('name', array_keys($markings))
                      ->whereNotIn('region', array_keys($regions))
                      ->orderByRaw("RAND()")
                      ->first();

当然,由于性能原因,上面的ORDER BY RAND() 可能不是最佳解决方案。

你应该真正关心你的代码和重复的数量,否则你很快就会迷失在你正在做的事情中

【讨论】:

非常感谢您的提示!

以上是关于对具有不确定数据的数组进行排序的主要内容,如果未能解决你的问题,请参考以下文章

如何按时间戳对数组进行排序?

按降序对对象数组进行排序[关闭]

冒泡排序算法

如何计算快速排序期间发生的数组比较次数?

数组列表排序[重复]

编写一个函数对对象数组进行排序(通过使用另一个对象来指定排序路径和顺序)