【问题标题】:Optimizing if-else /switch-case with string options使用字符串选项优化 if-else /switch-case
【发布时间】:2012-08-02 00:13:43
【问题描述】:

会对这段代码进行哪些修改?在最后几行中,我应该使用更多的 if-else 结构,而不是“if-if-if”

if (action.equals("opt1"))
{
    //something
}
else
{
    if (action.equals("opt2"))
    {
        //something
    }
    else
    {
        if ((action.equals("opt3")) || (action.equals("opt4")))
        {
            //something
        }
        if (action.equals("opt5"))
        {
            //something
        }
        if (action.equals("opt6"))
        {
            //something
        }
    }
}

稍后编辑:这是 Java。我认为 switch-case 结构不适用于字符串。

后期编辑2:

开关使用字节,短, char 和 int 原始数据类型。它 也适用于枚举类型 (在类和继承中讨论) 和一些“包装”的特殊类 某些原始类型:字符, 字节、短整型和整数(已讨论 在简单数据对象中)。

【问题讨论】:

  • 选项必须是字符串吗?如果您可以将它们设为枚举(或在获取字符串时将其解析为枚举),那么您可以使用开关。
  • 当您提出此类问题时,请指定您想要“优化”的内容:例如可读性、性能等。简单地说“你如何优化这段代码?”没有任何意义。我之所以提到这一点,是因为我看到一些响应假设您指的是性能,而另一些则假设您指的是可读性——这实际上可能是相互排斥的优化。
  • 请将您的帖子标题更改为:“使用字符串选项优化 if-else /switch-case”。当前的标题毫无意义。在那之后我会投票赞成你的问题

标签: java


【解决方案1】:

即使你不使用 switch 语句,是的,使用 else if 以避免无用的比较:如果取第一个 if,你不希望在这里评估所有其他 if因为他们永远都是假的。此外,您不需要缩进每个 if 使最后一个块缩进以至于不滚动就看不到它,以下代码完全可读:

if (action.equals("opt1")) {
}
else if (action.equals("opt2")) {
}
else if (action.equals("opt3")) {
}
else {
}

【讨论】:

  • 由于Java 显然不能switch 处理字符串值,所以我会采用这个人的方法。 (特别是没有过度缩进)
  • ...虽然我仍然认为你的大括号在错误的行上,但那完全是另一个争论:)
  • 比以前好,但在最坏的情况下仍然 O(n)。
  • 你可以进一步按概率排序。
  • 甚至更好 - 如果有很多字符串,您可以计算所有变体的哈希值,对有问题的字符串进行哈希处理,然后首先比较哈希值,如果哈希值匹配,再比较字符串 - 这样会快得多。跨度>
【解决方案2】:

使用带有字符串作为键类型和委托* 作为值类型的字典。 - 使用字符串检索方法将花费 O(1+load)。

在类的构造函数中填充字典。

  • Java 不支持委托,因此作为一种变通方法,您可能需要定义一些内部类 - 每种情况一个并传递内部类的实例而不是方法作为值。

【讨论】:

  • A Map 在很多情况下会表现得更好。这是 O(1) 的映射与 O(N) 的 if/else。一切都取决于固定查找成本,这对于地图来说更大。
  • @Thomas:您的评论具有误导性。首先,并非所有地图都有 O(1) 查找成本;例如TreeMaps 不会,甚至 HashMaps 如果使用 daft 负载因子也不会。其次,对于具有默认(良好)负载因子的 HashMap,查找成本取决于字符串的长度、您之前是否对它进行哈希处理(字符串哈希码被缓存)以及字符串的哈希码是否冲突。在最佳情况假设下,HashMap 查找的摊销成本可能低于执行if (key.equals("value")) { 的成本。
  • @Stephen:你是对的。我想到了 HashMap。是的,只有没有哈希冲突的查找才会是 0(1)。但是我不认为一旦计算出散列值 并且 没有冲突,查找速度就取决于字符串长度。仅对于碰撞,必须调用 String.equals。这只会在它尝试实例相等之后。仅当您使用不同的字符串实例时才会发生这种情况。但总而言之,您是对的,尽管它可以以几乎 O(1) 的性能使用。
  • @Thomas。我不清楚。假设没有冲突,HashMap.get(str) 的摊销成本不依赖于str.length()。但是 if (str.equals("opt1") ... else if 序列的成本确实如此,假设 str 匹配字符串文字之一。
【解决方案3】:

假设您的语言支持切换字符串,请使用 switch 语句。

switch(action)
{
   case "opt6":
      //
      break;
   case "opt7":
      //
   ...
   ...
   ...
}

【讨论】:

  • 在 JDK 7 中将引入字符串开关:blogs.sun.com/darcy/entry/project_coin_final_five
  • 有人知道这是否会被编译成 Java 7 中的 tableswitch 字节码指令吗?所以它会对 if/else 产生性能差异。
  • @ThomasJung - 我想它将被编译为接近最佳代码,但该代码将取决于常量字符串的数量和大小。 Java 7 现已发布,您可以自行检查。但是除非这个开关(在 Java 7 中仍然是……)是性能瓶颈,否则你就是在浪费时间去研究它。让 JIT 编译器完成它的工作。
【解决方案4】:

在 Java 中有很多方法可以做到这一点,但这里有一个简洁的方法。

enum Option {
    opt1, opt2, opt3, opt4, opt5, opt6
}

...

switch (Option.valueOf(s)) {
case opt1:
    // do opt1
    break;
case opt2:
    // do opt2
    break;
case opt3: case opt4:
    // do opt3 or opt4
    break;
...
}

请注意,valueOf(String) 将抛出 IllegalArgumentException 如果参数 不是枚举成员之一的名称。在底层,valueOf 的实现使用静态哈希映射将其 String 参数映射到枚举值。

【讨论】:

    【解决方案5】:

    您可以使用开关。

    switch (action)
    {
     case "opt3":
     case "opt4":
     doSomething;
     break;
     case "opt5":
     doSomething;
     break;
     default:
     doSomeWork;
     break;
    }
    

    【讨论】:

      【解决方案6】:

      如果你指定语言会有所帮助...因为它看起来像 C++,你可以使用 switch。

      switch (action) {
         case "opt1":
            // something
            break;
         case "opt2":
            // something
            break;
         ...
      }
      

      如果你想使用 if 语句,我认为如果你使用不带花括号的“else if”,你可以提高可读性和性能,如下所示:

      if (action.equals("opt1")) {
          //something
      } else if (action.equals("opt2")) {
          //something
      } else if ((action.equals("opt3")) || (action.equals("opt4"))) {
          //something
      } else if (action.equals("opt5")) {
          //something
      } else if (action.equals("opt6")) {
          //something
      }
      

      我认为某些编译器可以比else { if 更好地优化else if。无论如何,我希望我能帮上忙!

      【讨论】:

      • 我认为编译器对else 和if 之间的大括号很在意。
      【解决方案7】:

      我只是将其清理为一系列 if/else 语句:

      if(action.equals("opt1"))
      {
          // something
      }
      else if (action.equals("opt2"))
      {
          // something
      }
      else if (action.equals("opt3"))
      {
          // something
      }
      etc...
      

      【讨论】:

        【解决方案8】:

        这取决于你的语言,但它看起来像 C,所以你可以试试 switch 语句:

        switch(action)
        {
        case "opt1":
            // something
            break;
        
        case "opt2":
            // something
            break;
        
        case "opt3":
        case "opt4":
            // something
            break;
        
        case "opt5":
            // something
            break;
        
        case "opt6":
            // something
            break;
        }
        

        但是,有时 switch 语句不能提供足够的清晰度或灵活性(正如 Victor 在下面指出的那样,它不适用于某些语言中的字符串)。大多数编程语言都会有一种表达“else if”的方式,而不是写作

        if (condition1)
        {
            ...
        }
        else
        {
            if (condition2)
            {
                ...
            }
            else
            {
                if (condition3)
                {
                    ...
                }
                else
                {
                    // This can get very indented very fast
                }
            }
        }
        

        ...有一堆缩进,你可以这样写:

        if (condition1)
        {
            ...
        }
        else if (condition2)
        {
            ...
        }
        else if (condition3)
        {
            ...
        }
        else
        {
            ...
        }
        

        在 C/C++ 中,我相信 C#,它是 else if。在 Python 中,它是 elif。

        【讨论】:

        • C 不支持打开字符串。您必须将 else-ifs 与 strcmp 或类似的东西一起使用。
        • 正确。请注意我回答的后半部分;-)
        【解决方案9】:

        建议使用 switch 语句的答案是可行的方法。 switch 语句比你现在拥有的混乱的if 和if...else 语句更容易阅读。

        简单的比较很快,//something 代码不会只针对一种情况执行,因此您可以跳过“优化”而选择“可维护性”。

        当然,这是假设 action.equals() 方法做了一些琐碎且廉价的事情,例如 ==。如果action.equals() 很贵,那你还有其他问题。

        【讨论】:

          【解决方案10】:

          这样的过程切换通常由多态更好地处理——而不是用字符串表示一个动作,而是用一个对象表示一个动作,该对象有一个你可以专门化的“某事”方法。如果您发现确实需要将字符串映射到选项,请使用Map<String,Option>。

          如果你想坚持程序化代码,而你真实代码中的选项真的都是“optX”:

          if ( action.startsWith("opt") && action.length() == 4 ) {
              switch ( action.charAt(3) ) {
                  case '1':  something; break;
                  case '2':  something; break;
                  case '3':  something; break;
                  ...
              }
          }
          

          这在解析器之类的东西中是可以的(分解字符串是问题域的一部分),并且应该很快,但不具有凝聚力(对象action 和行为之间的连接基于其表示的部分,而不是对象的任何内在内容)。

          【讨论】:

            【解决方案11】:

            实际上这取决于分支分析。如果你 99% 的决定都是“opt1”,那么这段代码已经很不错了。如果你 99% 的决定都是“opt6”,那么这段代码就很难看。

            如果您经常出现“opt6”而很少出现“opt1”,则将“opt6”放在第一个比较中,并根据执行数据流中字符串的频率对以下比较进行排序。

            如果您有很多选项并且所有选项都具有相同的频率,您可以对选项进行排序并将它们拆分为二叉树的形式,如下所示:

            if (action < "opt20")
            {
               if( action < "opt10" )
               {
                 if( action  ==  "opt4" ) {...}
                 else if( action  ==  "opt2" ) {...}
                 else if( action  ==  "opt1" ) {...}
                 else if( action  ==  "opt8" ) {...}
               }
            }
            else
            {
                if( action < "opt30 )
                {
            
                }
                else
                {
                  if( action  ==  "opt38" ) {...}
                  else if( action  ==  "opt32" ) {...}
                }
            }
            

            在此示例中,范围拆分将“opt38”和“opt4”所需的比较减少到 3。这样做结果是您在每个分支中获得 log2(n)+1 比较。这对于相同频率的选项是最好的。

            不要将二进制吐到最后,最后使用 4-10 "正常" else if 构造按选项的频率排序。二叉树的最后两层或三层并不需要太多的进步。

            总结

            这种比较至少有两个优化。

            1. 二元决策树
            2. 根据选项的频率排序

            二叉决策树由编译器用于大型 switch-case 构造。但是编译器对选项的频率一无所知。因此,如果一个或两个选项比其他选项更频繁,则根据频率进行排序可能对使用 switch-case 带来性能优势。在这种情况下,这是一种解决方法:

            if (action == "opt5") // Processing a frequent (99%) option first
            {
            }
            else // Processing less frequent options (>1%) second
            {
                switch( action )
                {
                case "opt1": ...
                case "opt2": ...
                }
            }
            

            警告

            在您完成分析并且确实有必要之前,不要优化您的代码。最好直接使用 switch-case 或 else-if,这样你的代码就会保持干净和可读。如果您优化了代码,请在代码中放置一些好的 cmets,以便每个人都能理解这种丑陋的代码和平。一年后,您将不知道分析数据,并且一些 cmets 将非常有用。

            【讨论】:

            • @zproxy - 错误 - 编译器不知道执行特定分支的频率。程序员通常对此很清楚。
            【解决方案12】:

            如果您发现本机 java 开关结构限制太多,请看一下 lambdaj Switcher,它允许通过将任何对象与一些 hamcrest 匹配器匹配来以声明方式打开任何对象。

            【讨论】:

              【解决方案13】:

              请注意,在 switch 语句中使用字符串是下一版 Java 中将添加的新功能之一。

              见Project Coin: Proposal for Strings in switch

              【讨论】:

                猜你喜欢
                • 2020-10-14
                • 1970-01-01
                • 2013-09-10
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2010-10-20
                相关资源
                最近更新 更多