developer tip

추악한 if 문 제거

copycodes 2020. 12. 2. 21:28
반응형

추악한 if 문 제거


이 못생긴 코드가 있습니다.

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 문을 어떻게 제거 할 수 있습니까?


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'의 값은 일반적으로 추가 할 수있는 것보다 대부분의 경우 낮은 범위 (<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;   

이러한 접근 방식은 어떻습니까?

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
}

NavigableMapAPI 사용 :

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();

OP 솔루션의 가장 분명한 문제는 분기이므로 다항 회귀를 제안합니다. 그러면 양식에 분기없는 멋진 표현이 생성됩니다.

size = round(k_0 + k_1 * v + k_2 * v^2 + ...)

물론 정확한 결과를 얻을 수는 없지만 약간의 편차를 견딜 수 있다면 매우 성능이 좋은 대안입니다. v<10다항식으로 모델링 할 수없는 값에 대한 원래 함수에 대한 '수정되지 않은 상태로두기'동작 때문에이 영역에 대해 0 차 유지 보간을 가정 할 자유를 얻었습니다.

다음 계수를 가진 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

, 아름답게 맞는 곡선을 얻을 수 있습니다.

대체 텍스트

보시다시피 0에서 200 *까지 전체 범위에서 1.73의 1- 노름 오류가 발생합니다!

*에 대한 결과 v∉[0,200]는 다를 수 있습니다.


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

원래 코드는 괜찮아 보이지만 여러 번 반환해도 괜찮다면 더 표 형식의 접근 방식을 선호 할 수 있습니다.

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의 답변 에서 다중 반환 여부 토론을 참조하십시오 .


여기에는 많은 답변과 제안이 있지만 솔직히 원래 방법보다 "더 예쁘거나" "더 우아"하다고 생각하지 않습니다.

확인해야 할 반복이 수십 또는 수백 번이면 for 루프로 이동하는 것을 쉽게 볼 수 있지만 솔직히 비교하면 몇 가지 비교를 수행하려면 if를 고수하고 계속 진행하십시오. 그렇게 못생긴 게 아니에요.


return (v-173) / -27;

여기 내 샷이 있습니다 ...

업데이트 : 수정되었습니다. 이전 솔루션이 정확한 값 (10,22,51 ...)에 대해 잘못된 답을 제공했습니다. if val <10의 경우 기본값은 6입니다.

   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;
    }

버전이 하나 더 있습니다. 나는이 기능이 성능 돼지가되지 않을 것이라고 100 % 확신 할 때 "성능"이라는 이름에 불필요한 복잡성을 추가하기 때문에 이것이 최선이라고 생각하지 않습니다 (누군가가 타이트한 루프에서 크기를 백만 번 계산하지 않는 한 ...).

그러나 나는 하드 코딩 된 바이너리 검색을 수행하는 것이 일종의 흥미 롭다고 생각 했기 때문에 그것을 제시합니다 . 매우 깊게 들어가는 요소가 충분하지 않기 때문에 이진법처럼 보이지는 않지만 원래 게시물에서와 같이 6 개가 아닌 3 개 이하의 테스트에서 결과를 반환 한다는 장점이 있습니다 . return 문은 이해 및 / 또는 수정에 도움이되는 크기별로 정렬되어 있습니다.

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;
   }
}

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

여기서는 7기본값 ( x <= 10)입니다.

편집 : 처음에는이 질문이 Java에 관한 것인지 몰랐습니다. 이 표현식은 Java에서는 유효하지 않지만 C / C ++에서는 유효합니다. 일부 사용자가 유용하다고 생각 했으므로 대답을 남겨 두겠습니다.


내 댓글 기능이 아직 켜져 있지 않습니다. 내 대답에 따라 아무도 "정당하게"라고 말하지 않기를 바랍니다.

추악한 코드를 예쁘게 만드는 것은 달성하려는 것으로 정의 할 수 있습니다.

  1. 가독성 (OK, 명백한 것을 말함-아마도 질문에 중복)
  2. 성능-최선을 다해 최적을 찾고, 최악의 경우 큰 낭비가 아닙니다.
  3. 실용주의-우아하거나 독특한 해결책이 필요하지 않은 평범한 문제를 감안할 때 대부분의 사람들이 일을하는 방식과 멀지 않습니다. 나중에이를 변경하는 것은 많은 기억이 필요하지 않은 자연스러운 노력이어야합니다.

IMO는 org.life.java가 제공 한 대답이 가장 예쁘고 읽기 쉬웠습니다. 나는 또한 읽기와 공연의 이유로 조건이 쓰여진 순서를 좋아했습니다.

이 주제에 대한 모든 주석을 살펴보면, 필자가 글을 쓰는 시점에 org.life.java만이 성능 문제를 제기 한 것으로 보입니다 (그리고 아마도 mfloryan도 무언가 "더 길어질 것"이라고 말함). 확실히 대부분의 상황에서, 그리고이 예제를 보면 당신이 그것을 작성하더라도 눈에 띄는 속도 저하를 겪지 않아야합니다.

그러나 조건을 중첩하고 조건을 최적으로 정렬하면 성능을 향상시킬 수 있습니다 [특히 이것이 루프 된 경우].

즉, 가능한 한 빨리 실행하려는 결정에 의해 발생하는 중첩 및 정렬 조건 (예제보다 더 복잡함)은 종종 읽기 어려운 코드와 변경하기 어려운 코드를 생성합니다. 나는 다시 # 3, 실용주의 ... 욕구의 균형을 참조합니다.


다음은 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 구문을 사용하여 매퍼를 생성 할 수있는 빌더가 포함되어 있습니다. 유용성을 위해 필수품이라고 말하고 싶습니다.
  2. 단일 항목과 컬렉션 매핑 방법을 모두 포함합니다.
  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;

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

실제로 크기가 변경 될 가능성이있는 경우 데이터베이스에서 수행하는 것이 좋은 대안 전략이 될 수 있습니다.

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

완전성을 위해 145 개의 요소로 배열 SIZES를 설정하여 답이 SIZES [v]로 직접 반환 될 수 있도록 제안하겠습니다. 모든 것을 쓰지 않은 것에 대해 용서하십시오. 물론 v가 범위 내에 있는지 확인해야합니다.

내가 그렇게했다고 생각할 수있는 유일한 이유는 어레이를 한 번 만들고 정말 빨라야하는 응용 프로그램에서 수천 번 사용하려는 경우 일 것입니다. 메모리와 속도 (한때는 문제가 아님), 그리고 설정 시간과 속도 간의 절충안의 예로 언급했습니다.


분명한 대답은 Groovy를 사용하는 것입니다.

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

하나의 라이너가 항상 더 좋습니다. v <= 10 인 정의되지 않은 케이스에 대해 7을 반환합니다.


누군가가 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;
    }

이것은 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;

It is interesting that there are plenty of beautiful answers for a simple "ugly" question. I like mfloryan's answer best, however I would push it further by removing the hard-coded array inside the method. Something like,

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

It now becomes more flexible and can process any given array in descending order and the method will find the index where the value 'v' belongs.

PS. I cannot comment yet on the answers.


If you really want the fastest big-O complexity time solution for this particular answer this one is constant lookup.

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

subsequently

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

What we are doing here is marking all the possible results of v within the range and where they fall, and then we only need to test for boundary conditions.

이것의 문제는 더 많은 메모리를 사용하고 물론 maxBoundary가 훨씬 크면 공간 비효율적이며 초기화하는 데 더 오랜 시간이 걸린다는 것입니다.

이것은 때때로 상황에 가장 적합한 솔루션 일 수 있습니다.


            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 문만 실행합니다.


또 다른 변형 ( 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;

참고 URL : https://stackoverflow.com/questions/3786358/get-rid-of-ugly-if-statements

반응형