简体   繁体   中英

What is the best way to optimize a function with many if-else statements(multiple conditions)?

What is the best approach or design pattern to optimize the code below?(I've thought about using switch statement but switch statement cannot handle multiple conditions in a single case.)

Below is the code snippet. Each major is determined by a certain numerical range.

public String getMajor(String major) {
    crnCompare = Integer.parseInt(major);
    if ((crnCompare >= 90702 && crnCompare <= 90733) || (crnCompare >= 10004 && crnCompare <= 10037)) {
        this.major = "AC";
    } else if ((crnCompare >= 10087 && crnCompare <= 10108) || (crnCompare >= 10471 && crnCompare <= 10482) || (crnCompare >= 90024 && crnCompare <= 90071)) {
        this.major = "CS";
    } else if ((crnCompare >= 10109 && crnCompare <= 10158) || (crnCompare >= 90072 && crnCompare <= 90116)) {
        this.major = "EC";
    } else if ((crnCompare >= 90117 && crnCompare <= 90203) || (crnCompare >= 10075 && crnCompare <= 10213) || (crnCompare >= 10498 && crnCompare <= 10572)) {
        this.major = "EN";
    } else if ((crnCompare >= 10038 && crnCompare <= 10040) || (crnCompare >= 10214 && crnCompare <= 10255) || (crnCompare >= 10256 && crnCompare <= 10260) || (crnCompare >= 90017 && crnCompare <= 90203) || crnCompare == 11172) {
        this.major = "FI";
    } else if ((crnCompare >= 90670 && crnCompare <= 90790) || (crnCompare >= 11236 && crnCompare <= 11239)) {
        this.major = "FS";
    } else if ((crnCompare >= 90253 && crnCompare <= 90273) || (crnCompare >= 90734 && crnCompare <= 90769) || (crnCompare >= 90274 && crnCompare <= 90360) || (crnCompare >= 10261 && crnCompare <= 10393)) {
        this.major = "GB";
    } else if ((crnCompare >= 100394 && crnCompare <= 10429) || (crnCompare >= 90361 && crnCompare <= 90398)) {
        this.major = "GLS";
    } else if ((crnCompare >= 10430 && crnCompare <= 10451) || (crnCompare >= 90399 && crnCompare <= 90420)) {
        this.major = "HI";
    } else if ((crnCompare >= 10452 && crnCompare <= 10468) || (crnCompare >= 90422 && crnCompare <= 90436) || crnCompare == 11119) {
        this.major = "IDCC";
    } else if ((crnCompare >= 9437 && crnCompare <= 90438) || (crnCompare >= 10469 && crnCompare <= 10470)) {
        this.major = "IPM";
    } else if ((crnCompare == 90421) || (crnCompare >= 11280 && crnCompare <= 11426)) {
        this.major = "ID";
    } else if ((crnCompare >= 90439 && crnCompare <= 90448) || (crnCompare >= 90483 && crnCompare <= 90497)) {
        this.major = "LTF";
    } else if ((crnCompare >= 90504 && crnCompare <= 90535) || (crnCompare >= 10573 && crnCompare <= 10596) || crnCompare == 90785) {
        this.major = "MG";
    } else if ((crnCompare >= 90536 && crnCompare <= 90553) || (crnCompare >= 10598 && crnCompare <= 10616) || crnCompare == 10740) {
        this.major = "MK";
    } else if ((crnCompare >= 90449 && crnCompare <= 90503) || (crnCompare >= 10514 && crnCompare <= 10564) || (crnCompare == 11120) || (crnCompare == 10555) || (crnCompare == 11127)) {
        this.major = "MA";
    } else if ((crnCompare >= 10637 && crnCompare <= 10715) || (crnCompare == 11142) || (crnCompare == 10739) || (crnCompare >= 90575 && crnCompare <= 90622)) {
        this.major = "NAS";
    } else if (crnCompare >= 90554 && crnCompare <= 90574 || crnCompare == 10617 || crnCompare == 10636) {
        this.major = "ML";
    } else if ((crnCompare >= 90623 && crnCompare <= 10646) || (crnCompare >= 10671 && crnCompare <= 10696)) {
        this.major = "PI";
    } else if ((crnCompare == 90647 || crnCompare == 90649) || (crnCompare >= 10697 && crnCompare <= 10698) || crnCompare == 10756) {
        this.major = "PRS";
    } else if ((crnCompare >= 11341 && crnCompare <= 11420)) {
        this.major = "SL";
    } else if ((crnCompare >= 90650 && crnCompare <= 90668) || (crnCompare >= 10716 && crnCompare <= 10734)) {
        this.major = "SO";
    } else if ((crnCompare == 10735)) {
        this.major = "ST";
    }

    return this.major;
}

You might want to look into Guava's RangeMap classes (other similar implementations are available).

These allow you to express these conditions something like this:

RangeMap<Integer, String> rangeMap =
  ImmutableRangeMap.<Integer, String>builder()
    .put(Range.closed(90702, 90733), "AC")
    .put(Range.closed(10004, 10037), "AC")
    .put(Range.closed(10087, 10108), "EN")
    .put(Range.closed(10004, 10037), "AC")
    // ...
    .build();

Construct this once, and then query it like:

String major = rangeMap.get(crmCompare);

There are a couple of advantage of this:

  • It's a more compact syntax
  • The creation of the ranges is validated, both for min and max being in the correct order, and for not overlapping.

The disadvantage is the addition of Guava, if you're not already using it.

Standard table-driven approach:

public static class Range
{
    // getters omitted for conciseness
    int low;
    int high;
    String major;
    public Range(int low, int high, String major)
    {
        this.low  = low;
        this.high  = high;
        this.major = major;
    }
    public boolean contains(int v)
    {
        return (v >= low && v <= high);
    }
}

public static Range[] ranges = {
        new Range(10004,10037,"AC"),
        new Range(10087,10108,"AC"),
        // etc
        // Ideally this table is populated from a data file that can
        // be updated at runtime without recompiling the code.
};

public String getMajor(String m)
{
    int crnCompare = Integer.parseInt(m);
    // Search for the matching range
    for (Range r : ranges)
        if (r.contains(crnCompare)) return r.major;
    return null;
}

Encode all the conditions as a low-high pair and corresponding major code, and put all the conditions into an array. To determine the major search the array to find the matching condition.

Possible enhancements (left as an exercise) include

  1. loading the table from a file instead of hardcoding it
  2. keeping the table sorted so you can do a binary search instead of a linear search.

I thought a while about this; focusing on the aspect of: "how can you make sure that your implementation is correct?"

In that sense, I would have suggested a Range class, similar to the one proposed by Jim. Because such a class allows you to not only have a contains() method closely associated to the range data; but on top of that, you could add methods like:

  • boolean isOverlapping(Range other) ... which you can use to detect situations when you didn't pay attention and you defined ranges that, well, overlap!
  • int compareTo(Range other) ... to implement the Comparable interface. And now you could sort ranges. Which would allow you to do binary search when the list of ranges for a certain "category" gets too large.

But: I would not put that "major" name into the Range class; but the other way round:

public enum RangeBasedMajor {
  AC(Arrays.asList(new Range(90702, 90733), new Range(...)),
  EN(...

  private RangeBasedMajor(List<Range> ranges) ...

Key points here:

  • you have one place where those "majors" are created and mapped to their ranges
  • you can write some nice unit tests that fetch all constants from that enum, then grep their ranges, to ensure that no range is overlapping
  • and you are still free to push all that information into the special type of map Andy recommended to you in his solution ...

Depending on your context, this might be overkill. It really depends on how often you expect things to change in there; and for example: if it is required to access/deal with that "major" information within other parts of the program. When your requirement is only to map that number to a string (for example for printing something); then that RangeMap is fine.

But when you have other code that "deals" with "Majors" - then it might be worth looking into some of the suggestions I am making here.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM