摆脱丑陋的if语句

时间:2010-09-24 10:59:46

标签: java coding-style

我有这个丑陋的代码:

if ( v > 10 ) size = 6;
if ( v > 22 ) size = 5;
if ( v > 51 ) size = 4;
if ( v > 68 ) size = 3;
if ( v > 117 ) size = 2;
if ( v > 145 ) size = 1;
return size;

如何摆脱多个if语句?

25 个答案:

答案 0 :(得分:160)

这种方法怎么样:

int getSize(int v) {
    int[] thresholds = {145, 117, 68, 51, 22, 10};

    for (int i = 0; i < thresholds.length; i++) {
        if (v > thresholds[i]) return i+1;
    }
    return 1;
}

功能:(在Scala中演示)

def getSize(v: Int): Int = {
  val thresholds = Vector(145, 117, 68, 51, 22, 10)
  thresholds.zipWithIndex.find(v > _._1).map(_._2).getOrElse(0) + 1
}

答案 1 :(得分:88)

使用NavigableMap API:

NavigableMap<Integer, Integer> s = new TreeMap<Integer, Integer>();
s.put(10, 6);
s.put(22, 5);
s.put(51, 4);
s.put(68, 3);
s.put(117, 2);
s.put(145, 1);

return s.lowerEntry(v).getValue();

答案 2 :(得分:81)

OP解决方案最明显的问题是分支,所以我建议进行多项式回归。这将在表单

上产生一个很好的无分支表达式
size = round(k_0 + k_1 * v + k_2 * v^2 + ...)

你当然不会得到确切的结果,但如果你能容忍一些偏差,那么这是一个非常有效的选择。由于对于v<10无法用多项式建模的值的原始函数的“保留未修改”行为,我冒昧地假设该区域的零阶保持插值。

对于具有以下系数的45度多项式,

-9.1504e-91 1.1986e-87 -5.8366e-85 1.1130e-82 -2.8724e-81 3.3401e-78 -3.3185e-75  9.4624e-73 -1.1591e-70 4.1474e-69 3.7433e-67 2.2460e-65 -6.2386e-62 2.9843e-59 -7.7533e-57 7.7714e-55 1.1791e-52 -2.2370e-50 -4.7642e-48 3.3892e-46 3.8656e-43 -6.0030e-41 9.4243e-41 -1.9050e-36 8.3042e-34 -6.2687e-32 -1.6659e-29 3.0013e-27 1.5633e-25 -8.7156e-23  6.3913e-21 1.0435e-18 -3.0354e-16 3.8195e-14 -3.1282e-12 1.8382e-10 -8.0482e-09 2.6660e-07 -6.6944e-06 1.2605e-04 -1.7321e-03 1.6538e-02 -1.0173e-01 8.3042e-34 -6.2687e-32 -1.6659e-29 3.0013e-27 1.5633e-25 -8.7156e-23 6.3913e-21 1.0435e-18 -3.0354e-16 3.8195e-14 -3.1282e-12 1.8382e-10 -8.0482e-09 2.6660e-07 -6.6944e-06 1.2605e-04 -1.7321e-03 1.6538e-02 -1.0173e-01 3.6100e-01 -6.2117e-01 6.3657e+00

,你得到一条漂亮的曲线:

alt text

正如您所看到的,在从0到200 *的整个范围内,您只得到1.73的1-norm误差!

* v∉[0,200]的结果可能会有所不同。

答案 3 :(得分:76)

if ( v > 145 ) size = 1;
else if ( v > 117 ) size = 2;
else if ( v > 68 ) size = 3;
else if ( v > 51 ) size = 4;
else if ( v > 22 ) size = 5;
else if ( v > 10 ) size = 6;

return size;     

这对你的情况更好。

您可以选择尽可能选择Switch Case

Update:   如果您已经分析了“v”的值,那么在大多数情况下,“v”的值通常都在较低的范围内(<10),而不是添加此值。

if(v < 10)           size = SOME_DEFAULT_VALUE;
else if ( v > 145 )  size = 1;
else if ( v > 117 )  size = 2;
else if ( v > 68 )   size = 3;
else if ( v > 51 )   size = 4;
else if ( v > 22 )   size = 5;
else if ( v > 10 )   size = 6;   

further : 您还可以根据您的分析更改条件序列。如果您知道大多数值小于10,然后在第二位置大多数值介于68-117之间,则可以相应地更改条件序列。

编辑:

if(v < 10)           return SOME_DEFAULT_VALUE;
else if ( v > 145 )  return 1;
else if ( v > 117 )  return 2;
else if ( v > 68 )   return 3;
else if ( v > 51 )   return 4;
else if ( v > 22 )   return 5;
else if ( v > 10 )   return 6;   

答案 4 :(得分:51)

return v > 145 ? 1 
     : v > 117 ? 2 
     : v > 68 ? 3 
     : v > 51 ? 4 
     : v > 22 ? 5 
     : v > 10 ? 6 
     : "put inital size value here";

答案 5 :(得分:23)

原始代码对我来说似乎不错,但如果你不介意多次退货,你可能更喜欢更表格的方法:

if ( v > 145 ) return 1;
if ( v > 117 ) return 2;
if ( v >  68 ) return 3;
if ( v >  51 ) return 4;
if ( v >  22 ) return 5;
if ( v >  10 ) return 6;
return ...;     // The <= 10 case isn't handled in the original code snippet. 

请参阅org.life.java's answer中的多次回复讨论。

答案 6 :(得分:17)

这里有很多答案和建议,但老实说,我认为它们中的任何一个都不比原始方法“漂亮”或“更优雅”。

如果你有几十次或几百次迭代检查,那么我可以很容易地看到去一些for循环但老实说,对于你的少数比较,坚持使用if并继续前进。这不是那么难看。

答案 7 :(得分:14)

return (v-173) / -27;

答案 8 :(得分:12)

这是我拍摄的......

更新:已修复。先前的解决方案给出了精确值的错误答案(10,22,51 ...)。对于if val&lt;这个,默认值为6。 10

   static int Foo(int val)
    {
                          //6, 5, 4, 3, 2 ,1
        int[] v = new int[]{10,22,51,68,117,145};
        int pos = Arrays.binarySearch(v, val-1);
        if ( pos < 0) pos = ~pos;
        if ( pos > 0) pos --;
        return 6-pos;
    }

答案 9 :(得分:11)

我还有一个版本。我真的不认为它是最好的,因为当我100%确定这个函数永远不会是一个性能损失时,它会增加“性能”名称的不必要的复杂性(除非有人在一个紧密的循环中计算大小一百万次...)。

但我之所以提出它只是因为我认为执行硬编码的二进制搜索有点像。它看起来不是二元 - 因为没有足够的元素去深入,但它确实具有以下优点:它返回的结果不超过3次测试,而不是6次原帖。返回语句也按大小排序,这有助于理解和/或修改。

if (v > 68) {
   if (v > 145) {
      return 1
   } else if (v > 117) {
      return 2;
   } else {
      return 3;
   }
} else {
   if (v > 51) {
      return 4;
   } else if (v > 22) {
      return 5;
   } else {
      return 6;
   }
}

答案 10 :(得分:7)

7 - (x>10 + x>22 + x>51 + x>68 + x>117 + x>145)

其中7是默认值(x <= 10)。

编辑:最初我没有意识到这个问题是关于Java的。此表达式在Java中无效,但在C / C ++中有效。我会留下答案,因为有些用户觉得它很有帮助。

答案 11 :(得分:5)

这是一个面向对象的解决方案,一个名为Mapper<S,T>的类,它映射来自任何实现与任何目标类型相当的类型的值。

<强>语法:

Mapper<String, Integer> mapper = Mapper.from("a","b","c").to(1,2,3);

// Map a single value
System.out.println(mapper.map("beef")); // 2

// Map a Collection of values
System.out.println(mapper.mapAll(
    Arrays.asList("apples","beef","lobster"))); // [1, 2, 3]

<强>代码:

public class Mapper<S extends Comparable<S>, T> {

    private final S[] source;
    private final T[] target;

    // Builder to enable from... to... syntax and
    // to make Mapper immutable
    public static class Builder<S2 extends Comparable<S2>> {
        private final S2[] data;
        private Builder(final S2[] data){
            this.data = data;
        }
        public <T2> Mapper<S2, T2> to(final T2... target){
            return new Mapper<S2, T2>(this.data, target);
        }
    }


    private Mapper(final S[] source, final T[] target){
        final S[] copy = Arrays.copyOf(source, source.length);
        Arrays.sort(copy);
        this.source = copy;
        this.target = Arrays.copyOf(target, target.length);
    }

    // Factory method to get builder
    public static <U extends Comparable<U>, V> Builder<U> from(final U... items){
        return new Builder<U>(items);
    }

    // Map a collection of items
    public Collection<T> mapAll(final Collection<? extends S> input){
        final Collection<T> output = new ArrayList<T>(input.size());
        for(final S s : input){
            output.add(this.map(s));
        }
        return output;
    }

    // map a single item
    public T map(final S input){
        final int sourceOffset = Arrays.binarySearch(this.source, input);
        return this.target[
            Math.min(
                this.target.length-1,
                sourceOffset < 0 ? Math.abs(sourceOffset)-2:sourceOffset
            )
        ];
    }
}

编辑:最后用更高效(更短)的版本替换了map()方法。我知道:对于大型阵列,搜索分区的版本仍然会更快,但很抱歉:我太懒了。

如果您认为这太过臃肿,请考虑一下:

  1. 它包含一个构建器,允许您使用varargs语法创建Mapper。我会说这是可用性的必备条件
  2. 它包含单个项目和集合映射方法
  3. 它是不可变的,因此线程安全
  4. 当然,所有这些功能都可以轻松删除,但代码不太完整,可用性较低或稳定性较差。

答案 12 :(得分:5)

我的评论能力尚未开启,希望没有人会根据我的回答“正确地”说出来......

将丑陋的代码弄清楚可以/应该被定义为试图实现:

  1. 可读性(好的,说明显而易见的 - 可能是多余的问题)
  2. 表现 - 充其量只追求最佳,最糟糕的是它并不是一个很大的消耗
  3. 实用主义 - 大多数人做事的方式并不遥远,因为一个普通的问题不需要优雅或独特的解决方案,以后改变它应该是一种自然的努力,而不需要太多的回忆。 / LI>

    IMO org.life.java给出的答案是最漂亮的,非常容易阅读。出于阅读和表现的原因,我也喜欢写条件的顺序。

    查看关于这个主题的所有评论,在我写作的时候,似乎只有org.life.java提出了性能问题(也许mfloryan,也说明某些事情会“更长”)。当然在大多数情况下,并且考虑到这个例子,你写它时不应该有明显的减速。

    但是,通过嵌套条件并优化排序条件可以提高性能[值得,特别是如果这是循环的]。

    所有这一切,通过确定尽可能快地执行所带来的嵌套和排序条件(比您的示例更复杂)通常会产生不太可读的代码,并且代码更难以改变。我再次提到#3,实用主义......平衡需求。

答案 13 :(得分:4)

这是否存在潜在的数学规则?如果是这样,你应该使用它:但只有它来自问题域,而不仅仅是恰好符合案例的一些公式。

答案 14 :(得分:3)

您可以在ARM代码中重写它。它只有7个周期的最坏情况和一个细长的164个字节。希望有所帮助。 (注意:这是未经测试的)

; On entry
;   r0 - undefined
;   r1 - value to test
;   lr - return address
; On exit
;   r0 - new value or preserved
;   r1 - corrupted
;
wtf
        SUBS    r1, r1, #10
        MOVLE   pc, lr
        CMP     r1, #135
        MOVGT   r0, #1
        ADRLE   r0, lut
        LDRLEB  r0, [r0, r1]
        MOV     pc, lr
;
; Look-up-table
lut
        DCB     0   ; padding
        DCB     6   ; r1 = 11 on entry
        DCB     6
        DCB     6
        DCB     6
        DCB     6
        DCB     6
        DCB     6
        DCB     6
        DCB     6
        DCB     6
        DCB     6
        DCB     6
        DCB     5   ; r1 = 23 on entry
        DCB     5
        ...
        ALIGN

答案 15 :(得分:3)

int[] arr = new int[] {145, 117, 68, 51, 22, 10};
for(int index = 0; index < arr.length; index++)
{
  if(v > arr[index]) return 1 + index; 
}

return defaultValue;

答案 16 :(得分:2)

实际上,如果尺寸可能会发生变化,那么在数据库中执行此操作可能是一个很好的替代策略:

CREATE TABLE VSize (
   LowerBound int NOT NULL CONSTRAINT PK_VSize PRIMARY KEY CLUSTERED,
   Size int NOT NULL
)
INSERT VSize VALUES (10, 6)
INSERT VSize VALUES (22, 5)
INSERT VSize VALUES (51, 4)
INSERT VSize VALUES (68, 3)
INSERT VSize VALUES (117, 2)
INSERT VSize VALUES (145, 1)

存储过程或函数:

CREATE PROCEDURE VSizeLookup
   @V int,
   @Size int OUT
AS
SELECT TOP 1 @Size = Size
FROM VSize
WHERE @V > LowerBound
ORDER BY LowerBound

答案 17 :(得分:1)

为了完整起见,我建议您可以设置一个包含145个元素的数组SIZES,这样答案就可以直接返回为SIZES [v]。 请原谅我没有写出整件事。当然,你必须确保v在范围内。

我能想到这样做的唯一原因就是你要创建一次数组并在一个必须非常快的应用程序中使用它数千次。我把它作为内存和速度之间的权衡(不是以前的问题),以及设置时间和速度之间的权衡。

答案 18 :(得分:1)

显而易见的答案是使用Groovy:

def size = { v -> [145,117,68,51,22,10].inject(1) { s, t -> v > t ? s : s + 1 } }

一个衬垫总是更好。对于未定义的情况返回7,其中v <= 10。

答案 19 :(得分:1)

为什么有人没有提出switch语句。它比其他梯子好得多。

public int getSize(int input)
    {
        int size = 0;
        switch(input)
        {
        case 10:
            size = 6;
            break;

        case 22:
            size = 5;
            break;


        case 51:
            size = 4;
            break;

        case 68:
            size = 3;
            break;

        case 117:
            size = 2;
            break;

        case 145:
            size = 1;
            break;
        }

        return size;
    }

答案 20 :(得分:0)

如果你真的想要这个特定答案的最快的大O复杂时间解决方案,那么这个就是不断查找。

final int minBoundary = 10;
final int maxBoundary = 145;
final int maxSize = 6;
Vector<Integer> index = new Vector<Integer>(maxBoundary);
    // run through once and set the values in your index

随后

if( v > minBoundary )
{
   size = (v > maxBoundary ) ? maxSize : index[v];
}

我们在这里做的是在范围内标记v的所有可能结果以及它们落在何处,然后我们只需要测试边界条件。

这个问题是它使用了更多的内存,当然如果maxBoundary更大,那么它的空间效率会非常低(并且需要更长的时间来初始化)。

这有时可能是解决问题的最佳方案。

答案 21 :(得分:0)

有趣的是,对于一个简单的“丑陋”问题,有很多美丽的答案。我最喜欢mfloryan的答案,但是我会通过删除方法中的硬编码数组来进一步推动它。像,

int getIndex(int v, int[] descArray) {
    for(int i = 0; i < descArray.length; i++)
        if(v > descArray[i]) return i + 1;
    return 0;
}

它现在变得更加灵活,可以按降序处理任何给定的数组,并且该方法将找到值'v'所属的索引。

PS。我还不能评论答案。

答案 22 :(得分:0)

这是我的代码示例,使用SortedSet。您初始化边界一次。

SortedSet<Integer> boundaries = new SortedSet<Integer>;

boundaries.add(10);

boundaries.add(22);

boundaries.add(51);

boundaries.add(68);

boundaries.add(117);

boundaries.add(145);

然后以这种方式使用它来获得v(和初始化大小)的多个值

SortedSet<Integer> subset =  boundaries.tailSet(v);
if( subset.size() != boundaries.size() )
  size = subset.size() + 1;

答案 23 :(得分:-1)

            if (v <= 10)
                return size;
            else {
                size = 1;

                if (v > 145)
                    return size;
                else if (v > 117)
                    return ++size;
                else if (v > 68)
                    return (size+2);
                else if (v > 51)
                    return (size+3);
                else if (v > 22)
                    return (size+4);
                else if (v > 10)
                    return (size+5);
            }

这将仅执行必要的if语句。

答案 24 :(得分:-1)

另一种变体(不如George的答案明显

  //int v = 9;
  int[] arr = {145, 117, 68, 51, 22, 10};
  int size = 7; for(;7 - size < arr.length && v - arr[size - 2] > 0; size--) {};
  return size;