当if else构造可以在更好的时间内完成工作时,为什么要在函数中使用HashMap确定要返回哪个值(对于键)?


9

当我最近在一家大公司工作时,我注意到那里的程序员遵循这种编码风格:

假设我有一个函数,如果输入为A,则返回12;如果输入为B,则返回21;如果输入为C,则返回45。

所以我可以将函数签名写为:

int foo(String s){
    if(s.equals("A"))      return 12;
    else if(s.equals("B")) return 21;
    else if(s.equals("C")) return 45;
    else throw new RuntimeException("Invalid input to function foo");
}

但是在代码审查中,我被要求将功能更改为以下内容:

int foo(String s){
    HashMap<String, Integer> map = new HashMap<String, Integer>();
    map.put("A", 12);
    map.put("B", 21);
    map.put("C", 45);
    return map.get(s);
}

我不能说服自己为什么第二个代码比第一个更好。第二个代码肯定会花费更多的时间来运行。

使用第二个代码的唯一原因可能是它提供了更好的可读性。但是,如果该函数被多次调用,那么第二个函数会不会减慢该实用程序调用它的运行时间?

你怎么看待这件事?


4
对于三个值,Map似乎有点过大(switch似乎比更加合适if-else)。但是在某些时候,它变得有问题。使用Map的主要优点是可以从文件或表等中加载它。如果您要对地图的输入进行硬编码,那么在开关上我看不到很多价值。
JimmyJames

Answers:


16

关键是将哈希表的创建移到函数外,然后执行一次(或者比其他次数少一些)。

private static final Map<String, Integer> map;
static{
    Map<String, Integer> temp = new HashMap<String, Integer>();
    temp.put("A", 12);
    temp.put("B", 21);
    temp.put("C", 45);
    map = Collections.unmodifiableMap(temp);//make immutable
}

int foo(String s){
    if(!map.containsKey(s))
        throw new RuntimeException("Invalid input to function foo");

    return map.get(s);
}

但是自从java7以来,java一直能够在开关中具有(最终)字符串:

int foo(String s){
    switch(s){
    case "A":
        return 12;
    case "B": 
        return 21;
    case "C": 
        return 45;
    default: throw new RuntimeException("Invalid input to function foo");
}

1
我看不到这如何回答OP的问题,所以-1。但是+1表示建议切换。
user949300

它显示了如何正确实施编码样式以使其真正有意义并可能改善性能。对于3个选择,它仍然没有意义,但是原始代码可能更长。
Florian F

12

在第二个示例中,Map应当为私有静态成员,以避免冗余的初始化开销。

对于大量值,地图将表现更好。使用哈希表,可以在恒定时间内查找答案。多重if构造必须将输入与每种可能性进行比较,直到找到正确的答案。

换句话说,映射查找为O(1),而ifs为O(n),其中n是可能输入的数量。

映射创建为O(n),但仅在静态恒定状态下才执行一次。对于频繁执行的查找if,从长远来看,映射将胜过语句,而在程序启动时(或加载类,取决于语言)会花费更多时间。

话虽如此,地图并不总是适合这项工作的工具。如果有很多值,或者必须通过文本文件,用户输入或数据库(在这种情况下,映射充当缓存)可以配置这些值,则非常好。


是的,对于大量值,地图将表现更好。但是值的数量是固定的,是三个。
RemcoGerlich

地图的创建是O(N),只有其中的搜索是O(1)。
Pieter B

好点,我澄清了答案。

地图也需要自动拆箱,这会对性能产生很小的影响。
user949300

@ user949300是Java,是的,问题中的代码似乎是Java。但是,它没有用任何语言标记,并且该方法可跨多种语言(包括C#和C ++,都不需要装箱)使用。

3

软件有两种速度:写/读/调试代码所花费的时间;以及执行代码所需的时间。

如果您可以说服我(和您的代码审阅者)哈希表函数确实比if / then / else(重构为静态哈希图后)慢,并且可以说服我/审阅者调用它的次数足以使实际差异,然后继续将哈希图替换为if / else。

否则,哈希码代码非常可读;和(可能)没有错误;您只需查看即可快速确定。如果不认真研究,就不能对if / else说同样的话。当有数百种选择时,差异甚至更大。


3
好吧,当有人将其与交换机进行比较时,该异议就消失了……
Deduplicator

如果将if语句写在一行中,它也会崩溃。
gnasher729

2
通过将哈希图创建放在其他位置,您将变得更加难以确定实际发生的情况。您需要查看这些键和值以了解该功能的实际效果。
RemcoGerlich

2

我非常喜欢HashMap样式的答案。

有一个指标

有一个称为Cyclomatic Complexity的代码质量度量。此度量标准基本上计算通过代码的不同路径的数量(如何计算环复杂度)。

对于每一种可能的执行路径,一种方法变得越来越难以理解,并且难以正确测试。

归结为这样的事实,即“控制关键字”,例如:ifs,elses,whiles等...利用了可能是错误的布尔测试。重复使用“控制关键字”会产生易碎的代码。

额外的好处

同样,“基于地图的方法”鼓励开发人员将输入输出对视为可以提取,重用,在运行时进行操作,测试和验证的数据集。例如,下面我重写了“ foo”,这样我们就不会被永久锁定在“ A-> 12,B-> 21,C-> 45”中:

int foo(String s){
    HashMap<String, Integer> map = getCurrentMapping();
    return map.get(s);
}

rachet_freak在回答中提到了这种类型的重构,他主张提高速度和重用性,我主张运行时的灵活性(尽管根据情况使用不可变的集合可能会带来巨大的好处)


1
添加运行时灵活性要么是一个很好的前瞻性想法,要么是不必要的过度投入,这使得很难确定正在发生的事情。:-)。
user949300 '17

@JimmyJames该链接对我有用
Ivan

1
@ user949300关键是支持“ foo”方法的键/值数据是一个单独的概念,可能需要某种形式的清晰度。您要编写的用于隔离地图的代码量在很大程度上取决于地图包含的项目数量以及这些项目可能需要更改的频率。
伊凡(Ivan)

1
@ user949300我建议退出if-else链的部分原因是,我已经看到if-else链成组存在。有点像蟑螂,如果有一种基于if-else链的方法,那么在代码库的其他地方可能会有另一种方法具有类似/相同的if-else链。如果我们假设可能还有其他方法使用类似的查找/类似开关的逻辑结构,则提取地图会带来好处。
伊凡(Ivan)

我也曾经看到过重复的,几乎相同的if / else块散布在各处。好放手
乔恩·切斯特菲尔德

1

数据胜于代码。尤其是因为在代码中再添加一个分支太诱人,而在表中添加一行却很难出错。这个问题只是其中的一个小例子。您正在编写一个查询表。编写实现,并附带条件逻辑和文档,或者写出表然后在其中查找。

数据表始终比代码(模优化遍历)更好地表示某些数据。表的表达难度可能取决于语言-我不懂Java,但希望它可以比OP中的示例更简单地实现查找表。

这是python中的查找表。如果这被视为引发冲突,请考虑该问题未标记为Java,重构与语言无关,并且大多数人都不了解Java。

def foo(s):
    return {
               "A" : 12,
               "B" : 21,
               "C" : 45,
           }[s]

重组代码以减少运行时间的想法是有好处的,但是我宁愿使用编译器来提升通用设置而不是自己编译。


-1不是问题的答案。
Pieter B

凭什么?我本来会要求作者根据数据和代码应该分开的原则,用地图替换if else链。这是显而易见的原因,尽管所有map.put调用都是不幸的,但是第二个代码比第一个更好。
乔恩·切斯特菲尔德

2
@JonChesterfield这个答案基本上是“使用更好的语言”,很少有帮助。
walpen '17

@walpen公平点。Ivan通过Java进行了大致相同的观察,因此我显然不需要使用python。我会看看能否将其清理干净
乔恩·切斯特菲尔德

在一些较旧的Java代码中,我需要一些非常相似的地图,因此编写了一个小实用程序,可将2维数组的N维数组转换为地图。有点hacky,但效果很好。这个答案指出了Python / JS在如此简单而轻松地支持JSONy表示法方面的强大功能。
user949300
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.