【问题标题】:Using if-condition or HashMap?使用 if 条件还是 HashMap?
【发布时间】:2014-01-19 18:34:19
【问题描述】:

我在一次采访中被问到这个问题,以改进提供的代码。提供的代码使用了很多 if 语句,因此我决定使用HashMap,因为检索会更快。不幸的是,我没有被选中担任该职位。我想知道是否有人知道比我改进代码更好的方法?

/* The following Java code is responsible for creating an HTML "SELECT" list of
   U.S. states, allowing a user to specify his or her state. This might be used,
   for instance, on a credit card transaction screen. 

   Please rewrite this code to be "better". Submit your replacement code, and 
   please also submit a few brief comments explaining why you think your code 
   is better than the sample.  (For brevity, this sample works for only 5 
   states. The real version would need to work for all 50 states. But it is 
   fine if your rewrite shows only the 5 states here.)
 */

/* Generates an HTML select list that can be used to select a specific U.S. 
   state.
 */

public class StateUtils {

  public static String createStateSelectList() {

    return
      "<select name=\"state\">\n"
    + "<option value=\"Alabama\">Alabama</option>\n"
    + "<option value=\"Alaska\">Alaska</option>\n"
    + "<option value=\"Arizona\">Arizona</option>\n"
    + "<option value=\"Arkansas\">Arkansas</option>\n"
    + "<option value=\"California\">California</option>\n"
    // more states here
    + "</select>\n"
    ;
  }


  /* Parses the state from an HTML form submission, converting it to the 
     two-letter abbreviation. We need to store the two-letter abbreviation 
     in our database.
   */

  public static String parseSelectedState(String s) {
    if (s.equals("Alabama"))     { return "AL"; }
    if (s.equals("Alaska"))      { return "AK"; }
    if (s.equals("Arizona"))     { return "AZ"; }
    if (s.equals("Arkansas"))    { return "AR"; }
    if (s.equals("California"))  { return "CA"; }
    // more states here
  }

  /* Displays the full name of the state specified by the two-letter code. */

  public static String displayStateFullName(String abbr) {
  {
    if (abbr.equals("AL")) { return "Alabama";    }
    if (abbr.equals("AK")) { return "Alaska";     }
    if (abbr.equals("AZ")) { return "Arizona";    }
    if (abbr.equals("AR")) { return "Arkansas";   }
    if (abbr.equals("CA")) { return "California"; }
    // more states here
  }
}

我的解决方案

/* Replacing the various "if" conditions with Hashmap<key, value> combination 
   will make the look-up in a constant time while using the if condition 
   look-up time will depend on the number of if conditions.
 */

import java.util.HashMap;

public class StateUtils {

  /* Generates an HTML select list that can be used to select a specific U.S. 
     state.
   */
  public static String createStateSelectList() {
    return "<select name=\"state\">\n"
    + "<option value=\"Alabama\">Alabama</option>\n"
    + "<option value=\"Alaska\">Alaska</option>\n"
    + "<option value=\"Arizona\">Arizona</option>\n"
    + "<option value=\"Arkansas\">Arkansas</option>\n"
    + "<option value=\"California\">California</option>\n"
    // more states here
    + "</select>\n";
  }

  /* Parses the state from an HTML form submission, converting it to the 
     two-letter abbreviation. We need to store the two-letter abbreviation 
     in our database.
   */

  public static String parseSelectedState(String s) {
    HashMap<String, String> map = new HashMap<String, String>();
    map.put("Alabama", "AL");
    map.put("Alaska", "AK");
    map.put("Arizona", "AZ");
    map.put("Arkansas", "AR");
    map.put("California", "CA");

    // more states here

    String abbr = map.get(s);
    return abbr;
  }

  /* Displays the full name of the state specified by the two-letter code. */

  public static String displayStateFullName(String abbr) {
    {

      HashMap<String, String> map2 = new HashMap<String, String>();
      map2.put("AL", "Alabama");
      map2.put("AK", "Alaska");
      map2.put("AZ", "Arizona");
      map2.put("AR", "Arkansas");
      map2.put("CA", "California");

      // more state abbreviations here here

      String full_name = map2.get(abbr);
      return full_name;
    }
  }
}

【问题讨论】:

  • 您为每次调用重新创建Map,因此您对“更快”的定义很有趣。如果状态是硬编码的,那么正确的解决方案是使用enum。最好从数据库“状态”表中读取这些信息。
  • @TedHopp OP 每次调用都会重新创建 Map...
  • @BoristheSpider - 我不确定enum 是“正确”的解决方案。要求是在全名和州缩写之间进行双向查找。这将需要至少两个enums。
  • @TedHopp 枚举提供了一个单例来存储数据,您将名称和缩写存储在 enum 中,并公开使用 Map 进行查找的方法。将所有内容隐藏在 interface 后面可能也是一个好主意。
  • 感谢@BoristheSpider 枚举确实有意义!

标签: java hashmap


【解决方案1】:

我认为您的代码有很多问题,尤其是为每个方法调用重新创建 Map

我会从接口开始。我们需要两件事; StateStateResolver。接口如下所示:

public interface State {

    String fullName();

    String shortName();
}

public interface StateResolver {

    State fromFullName(final String fullName);

    State fromShortName(final String shortName);

    Set<? extends State> getAllStates();
}

这允许在以后的阶段将实现换成更合理的东西,比如数据库。但让我们坚持使用示例中的硬编码状态。

我会将State 实现为enum,如下所示:

public enum StateData implements State {

    ALABAMA("Alabama", "AL"),
    ALASKA("Alaska", "AK"),
    ARIZONA("Arizona", "AZ"),
    ARKANSAS("Arkansas", "AR"),
    CALIFORNIA("Californiaa", "CA");

    private final String shortName;
    private final String fullName;

    private StateData(final String shortName, final String fullName) {
        this.shortName = shortName;
        this.fullName = fullName;
    }

    @Override
    public String fullName() {
        return fullName;
    }

    @Override
    public String shortName() {
        return shortName;
    }
}

但是,如上所述,这可以替换为从数据库加载的 bean。实现应该是不言自明的。

接下来到解析器,让我们针对我们的enum写一个:

public final class EnumStateResolver implements StateResolver {

    private final Set<? extends State> states;
    private final Map<String, State> shortNameSearch;
    private final Map<String, State> longNameSearch;

    {
        states = Collections.unmodifiableSet(EnumSet.allOf(StateData.class));
        shortNameSearch = new HashMap<>();
        longNameSearch = new HashMap<>();
        for (final State state : StateData.values()) {
            shortNameSearch.put(state.shortName(), state);
            longNameSearch.put(state.fullName(), state);
        }
    }

    @Override
    public State fromFullName(final String fullName) {
        final State s = longNameSearch.get(fullName);
        if (s == null) {
            throw new IllegalArgumentException("Invalid state full name " + fullName);
        }
        return s;
    }

    @Override
    public State fromShortName(final String shortName) {
        final State s = shortNameSearch.get(shortName);
        if (s == null) {
            throw new IllegalArgumentException("Invalid state short name " + shortName);
        }
        return s;
    }

    @Override
    public Set<? extends State> getAllStates() {
        return states;
    }

}

这也是不言自明的。变量位于实例级别。对 StateData 类的唯一依赖是在初始化程序块中。这显然需要为另一个State 实现重写,但这应该没什么大不了的。请注意,如果状态无效,此类将抛出 IllegalArgumentException - 这需要以某种方式在某个地方进行处理。目前尚不清楚这会发生在哪里,但需要考虑一些事情。

最后我们在类中实现所需的方法

public final class StateUtils {

    private static final StateResolver STATE_RESOLVER = new EnumStateResolver();
    private static final String OPTION_FORMAT = "<option value=\"%1$s\">%1$s</option>\n";

    public static String createStateSelectList() {
        final StringBuilder sb = new StringBuilder();
        sb.append("<select name=\"state\">\n");
        for (final State s : STATE_RESOLVER.getAllStates()) {
            sb.append(String.format(OPTION_FORMAT, s.fullName()));
        }
        sb.append("</select>\n");
        return sb.toString();
    }

    public static String parseSelectedState(final String s) {
        return STATE_RESOLVER.fromFullName(s).shortName();
    }

    public static String displayStateFullName(final String abbr) {
        return STATE_RESOLVER.fromShortName(abbr).fullName();
    }
}

请注意,我们只引用实用程序类顶部的实现,这使得更换实现快速而轻松。我们使用static final 引用来表示StateResolver 只创建一次。我还用基于动态循环的选择替换了硬编码的创建。我还使用格式化程序来构建选择。

应该注意的是,从来没有用 Java 构建 HTML 是一个好主意,任何这样做的人都应该对他们做一些难以形容的事情。

不用说,您应该对上述代码的每一行进行彻底的单元测试。

简而言之,您的答案并没有真正接近手头问题的适当、可扩展的企业解决方案。我的解决方案可能看起来有点矫枉过正,你可能是对的。但我认为这是正确的方法,因为抽象是可重用代码的关键。

【讨论】:

  • 没有冒犯,但我会认真担心雇用你 :) 然而你有我的支持。
  • @MarkoTopolnik 我也会;)。雇用在星期天为了好玩而进行编码的人是自找麻烦......
  • @MarkoTopolnik P.S.没有拍,但我可以问为什么吗?
  • 只是对你精心回答的一个玩笑,但如果我有一个真正的受访者想出这个,我承认我有点害怕雇用他---之后总而言之,他必须与人类同事相处:)
  • @MarkoTopolnik Ha。明白了。我从不完全确定编程面试 - 尝试在分配的时间内获得工作代码或尝试构建企业级解决方案;两者很少兼容。无论如何,可能不是这个论坛的讨论:p。
【解决方案2】:

为了避免手动维护 2 张地图并保持它们同步,我将创建第二张地图作为倒置的第一张地图。请参阅here 了解如何操作。

正如其他人所指出的,您只需要在方法调用之外创建一次地图。

** 只是为了好玩,在 Scala 中实现它的方式 **

val m = Map("AL" -> "Alabama", "AK" -> "Alaska")
m map { case (k, v) => (v, k) }
// gives: Map(Alabama -> AL, Alaska -> AK)

【讨论】:

  • 一个简单的解决方案只有一个方法addEntry 可以执行map1.put(code,name);map2.put(name,code);
  • 我没明白你的意思
  • 您可以使用适当的三行方法hashMap 进行非常接近的操作,这是我经常使用的方法:Map&lt;String,String&gt; m = hashMap("AL","Alabama", "AK","Alaska"); 至于反转,那是一个更难的方法:)
  • 关于其他 JVM 语言的话题,这里是 Clojure:(def m1 {"AL" "Alabama" "AK" "Alaska"}) (def m2 (into {} (map (comp vec reverse) m1)))
【解决方案3】:

似乎每个人都专注于解析,但创建也可以改进。获取所有州名,按字母顺序对它们进行排序,然后遍历该集合以创建每个 option。这样,用于解析的状态总是与用于顶峰的状态同步。如果添加新状态,只需将其添加到“主”枚举(或其他),两种方法都会反映更改。

【讨论】:

  • +1 “news Tate”真的让我坚持了一段时间 :) 用触摸屏回答,承认吧 :)
  • 花了我将近一分钟的时间输入“enum”全小写!
【解决方案4】:

您犯的唯一错误是每次都重建地图。如果您只构建了一次地图 - 也许在构造函数中我怀疑您会做得很好。

public class StateUtils {

    class State {

        final String name;
        final String abbreviation;

        public State(String name, String abbreviation) {
            this.name = name;
            this.abbreviation = abbreviation;
        }
    }
    final List<State> states = new ArrayList<State>();

    {
        states.add(new State("Alabama", "AL"));
        states.add(new State("Alaska", "AK"));
        states.add(new State("Arizona", "AZ"));
        states.add(new State("Arkansas", "AR"));
        states.add(new State("California", "CA"));
    }
    final Map<String, String> nameToAbbreviation = new HashMap<String, String>();

    {
        for (State s : states) {
            nameToAbbreviation.put(s.name, s.abbreviation);
        }
    }
    final Map<String, String> abbreviationToName = new HashMap<String, String>();

    {
        for (State s : states) {
            nameToAbbreviation.put(s.abbreviation, s.name);
        }
    }

    public String getStateAbbreviation(String s) {
        return nameToAbbreviation.get(s);
    }

    public String getStateName(String abbr) {
        return abbreviationToName.get(abbr);
    }
}

【讨论】:

  • 我几乎不会说“唯一”的错误。真正的解决方案是使用数据库,因此当德克萨斯脱离时,程序不必重新编译;)
  • @Kayaman 这是我们正在谈论的采访代码。您是否建议 OP 应该将其重写为基于 Spring 的声明式事务性企业应用程序?
  • @Kayaman 可能没有,因为这需要超过 15 分钟的时间和一个成熟的开发环境,包括依赖管理。
  • 事实上,我会因为过度设计而责备 Spring 家伙。 .properites 文件足以解决这个问题;这样就可以将其捆绑到 jar 中,从而简化运行环境。
  • 呵呵,有人会说,如果一家公司只想要能在 15 分钟内完成一个 Spring 应用程序的人,那么他们不值得加入。 ;)
【解决方案5】:

我不喜欢您的代码的一点是,每次调用该方法时您都会创建一个哈希图。映射应该只在类初始化时创建一次,并从方法中引用。

【讨论】:

    【解决方案6】:

    你做错了就是人们所说的 - 每次调用方法时你都在创建一个新的 HashMap - 一个静态字段宁可配置数据,并且只有在类被加载到我的 JVM 时才填充它。

    我宁愿在字符串上使用简单的开关——搜索并不比 HashMap 差(至少是渐近的),但你不使用额外的内存。虽然您需要两个长开关 - 更多代码。

    但比起 HashMap 解决方案,后面的解决方案对我来说是一样的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-05-15
      • 1970-01-01
      • 1970-01-01
      • 2023-03-28
      • 2022-01-24
      相关资源
      最近更新 更多