Php 为什么switch(true)的NPath复杂性比if()elseif()小?

Php 为什么switch(true)的NPath复杂性比if()elseif()小?,php,time-complexity,phpmd,Php,Time Complexity,Phpmd,我有一个函数,负责将文件名和mime类型转换为更“人性化”的内容(例如file.png,image/png到[image,png])。我发现有趣的是,一组if()elseif()语句比switch(true)语句具有更高的NPath复杂性 使用以下代码,PHP Mess Detector输出的NPath为4410: public function humanKind() { $typeRA = explode("/", strtolower($this->type)); $

我有一个函数,负责将文件名和mime类型转换为更“人性化”的内容(例如file.png,image/png到[image,png])。我发现有趣的是,一组
if()elseif()
语句比
switch(true)
语句具有更高的NPath复杂性

使用以下代码,PHP Mess Detector输出的NPath为4410:

public function humanKind()
{
    $typeRA = explode("/", strtolower($this->type));
    $fileRA = explode(".", $this->name);
    $fileType = strtoupper($fileRA[count($fileRA) - 1]);

    switch($typeRA[0]) {
        case "image":
            $humanType = "Image";
            break;
        case "video":
            $humanType = "Video";
            break;
        case "audio":
            $humanType = "Sound";
            break;
        case "font":
            $humanType = "Font";
            break;
        default:
            $humanType = "File";
    }

    switch ($this->type) {
        case "application/msword":
        case "application/pdf":
        case "applicaiton/wordperfect":
        case "text/plain":
        case "text/rtf":
        case "image/vnd.photoshop":
        case "image/psd":
        case "image/vnd.adobe.photoshop":
        case "image/x-photoshop":
        case "application/xml":
        case "application/x-mspublisher":
        case "text/html":
        case "application/xhtml+xml":
        case "text/richtext":
        case "application/rtf":
        case "application/x-iwork-pages-sffpages":
        case "application/vnd.apple.pages":
            $humanType = "Document";
            break;
        case "application/vnd.ms-excel":
        case "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":
        case "application/x-iwork-numbers-sffnumbers":
        case "application/vnd.apple.numbers":
            $humanType = "Spreadsheet";
            break;
        case "application/vnd.ms-powerpoint":
        case "application/vnd.openxmlformats-officedocument.presentationml.presentation":
        case "application/vnd.openxmlformats-officedocument.presentationml.slideshow":
        case "application/x-iwork-keynote-sffkey":
        case "application/vnd.apple.keynote":
            $humanType = "Slideshow";
            break;
        case "application/zip":
        case "application/x-zip-compressed":
        case "application/x-compressed":
        case "application/x-compress":
        case "application/x-rar-compressed":
        case "applicaiton/x-7z-compressed":
        case "application/x-ace-compressed":
            $humanType = "Archive";
            break;
        case "text/x-vcard":
        case "text/x-ms-contact":
            $humanType = "Contact";
            break;
        case "text/x-php":
        case "application/x-dosexec":
        case "application/x-xpinstall":
        case "application/x-opera-extension":
        case "application/x-chrome-extension":
        case "application/x-perl":
        case "application/x-shockwave-flash":
        case "application/java-archive":
            $humanType = "Program";
            break;
        case "application/vnd.ms-fontobject":
        case "application/font-woff":
        case "application/x-font-truetype":
        case "application/x-font-opentype":
        case "application/x-font-ttf":
        case "application/font-sfnt":
            $humanType = "Font";
            break;
    }

    // Special Cases
    if ($humanType == "Archive" && $fileType == "APK") { // Android App
        $humanType = "App";
    } elseif ($humanType == "Archive" && $fileType == "XPS") {
        $humanType = "Document";
    } elseif ($this->type == "application/xml" && $fileType == "CONTACT") {
        $humanType = "Contact";
    } elseif ($this->type == "application/octet-stream" && $fileType == "JNT") {
        $humanType = "Document";
    }

    if (strlen($fileType) > 4) {
        $fileType = "";
    }

    return array($humanType, $fileType);
如果我们将特殊情况替换为以下内容:

    // Special Cases
    switch(true) {
        case ($humanType == "Archive" && $fileType == "APK"): // Android App
            $humanType = "App";
            break;
        case ($humanType == "Archive" && $fileType == "XPS"):
            $humanType = "Document";
            break;
        case ($this->type == "application/xml" && $fileType == "CONTACT"):
            $humanType = "Contact";
            break;
        case ($this->type == "application/octet-stream" && $fileType == "JNT"):
            $humanType = "Document";
            break;
    }
PHP Mess Detector报告的NPath复杂性为1960


为什么会这样?是什么使switch(true)比我认为的几乎相同的控制结构更简单?

一般来说,switch可以比if/elseif更快,因为switch语句只对条件进行一次评估,然后对每种情况进行比较

据我所知,switch语句中的案例是内部索引的,因此您可能会因此获得更好的性能(尽管我找不到讨论这一点的原始文章,因此我无法证明这一点)

我还可以想象,switch语句的AST要比等价的if/elseif语句简单得多

编辑:

在基于C的语言(最有可能是其他语言)中,switch语句在长度超过4-5个案例时会实现为列表/哈希表。这意味着每个项目的访问时间变得相同。而在if/elseif块中,没有这样的优化

编译器更容易处理这些类型的switch语句,因为它可以对不同的条件进行更多的假设。因此,复杂性更低。任意情况的查找为O(1)。这再次链接到我之前的陈述,即交换机的AST很可能要简单得多

编辑#2:


在更多的CS术语中,编译器可以使用分支表(或跳转)来减少切换语句的cpu时间:

因为NPath复杂性度量完成这些语句所需的单元测试数量应该在两个“特殊情况”实现之间没有区别

但是计算中有一些不同。让我们逐步了解2个“特殊情况”实现,并手动计算NPath复杂性:

n带有
if。。其他..
此语句导致NPath复杂性9:如果..则
1分。。else
,if(expr)每个
一分,每个
&
操作员一分。(1+4+4=9)

n带有
开关的路径复杂性(true)
此语句导致NPath复杂性仅为4:对于
开关(true)
,它只包含0个点,因为它不包含
&&
|
运算符,并且每个
大小写
标签都包含1个点。(0+4=4)

n您的
函数的路径复杂性
为每个语句计算NPath值,然后将这些值相乘。没有“特殊情况”语句的函数的NPath复杂性为490。乘以
if。。否则,如果..
语句为9,则NPath复杂度为4410。如果将
开关(true)
语句的NPath值乘以4,则复杂性仅为1960。就这些


现在我们知道:NPath复杂度并不度量
switch
语句中
case
标签的表达式复杂度

简洁的问题。您可以通过快速切换按钮快速找到答案。@quickshift事实上,该选项更有可能成为候选人;PHPMD是一个包装在PHP Depend之上的接口,后者实际上包含NPathComplexity度量计算(可以找到)。如果您得到了这个问题的答案,请将其发布。@Jerska我打算这样做。我真的对它背后的科学很感兴趣(只要它不是一个bug)。只是一个简单的问题,你能试试
if($x=(/*first_-cond*/){xxx;}if($x=($x |/*second*/){…}
,因为我认为它更有可能像这样去糖化,而不是
if/else
(如果去糖化)(
$x | |
如果没有
中断;
在前面的案例陈述中)所有听起来都像是猜测我和@Evert在一起——听起来像是猜测,我真的很想知道原因(计算机科学)。这是错误的交换吗?@Navarr对响应进行了一点编辑。希望这会有所帮助。哈希表优化不适用于开关(true)。请记住,通常开关语句中包含逻辑,而案例中包含常量(因此可以在编译时生成哈希表或列表,从而加快运行速度)。对于switch true,情况并非如此,它将被转换为if/else if。您甚至无法获得只计算一次的优势(因为您计算一次的值是“true”).值得一提的是,我认为OP发现了一个bug。但我对NPath复杂性的了解还不足以确定。谢谢你,先生!我真的不知道这是一个计算中的bug(看起来是),还是它让它变得不那么复杂(因为我不是100%了解NPath复杂性),但你启发了我。
if ($humanType == "Archive" && $fileType == "APK") { // Android App
    $humanType = "App";
} 
elseif ($humanType == "Archive" && $fileType == "XPS") {
    $humanType = "Document";
} 
elseif ($this->type == "application/xml" && $fileType == "CONTACT") {
    $humanType = "Contact";
} 
elseif ($this->type == "application/octet-stream" && $fileType == "JNT") {
    $humanType = "Document";
}
switch(true) {
    case ($humanType == "Archive" && $fileType == "APK"): // Android App
        $humanType = "App";
        break;
    case ($humanType == "Archive" && $fileType == "XPS"):
        $humanType = "Document";
        break;
    case ($this->type == "application/xml" && $fileType == "CONTACT"):
        $humanType = "Contact";
        break;
    case ($this->type == "application/octet-stream" && $fileType == "JNT"):
        $humanType = "Document";
        break;
}