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
세 가지 값의 경우 맵이 과도 switch하게 보입니다 (보다 적합합니다 if-else). 그러나 어느 시점에서는 문제가됩니다. 맵을 사용하는 주된 장점은 파일이나 테이블 등에서로드 할 수 있다는 것입니다. 입력을 맵에 하드 코딩하는 경우 스위치를 통해 많은 가치를 볼 수 없습니다.
JimmyJames

답변:


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 이후로 java7은 스위치에 (최종) 문자열을 가질 수있었습니다.

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경우, 프로그램이 시작될 때 (또는 언어에 따라 클래스가로드 됨) 약간의 시간 이 걸리면서 맵이 장기적으로 명령문 보다 성능이 우수합니다 .

즉,지도가 항상 이 작업에 적합한 도구 는 아닙니다 . 많은 값이 있거나 텍스트 파일, 사용자 입력 또는 데이터베이스 (이 경우 맵이 캐시 역할을 함)를 통해 값을 구성 할 수 있어야합니다.


그렇습니다. 많은 양의 값의 경우 맵 성능이 향상됩니다. 그러나 값의 양은 고정되어 있으며 3입니다.
RemcoGerlich

맵 생성은 O (N)이며, 검색 만 O (1)입니다.
Pieter B

좋은 지적, 나는 대답을 명확하게했다.

또한지도에는 자동 언 박싱이 필요하므로 성능에 약간의 영향을 미칩니다.
user949300

Java의 경우 @ user949300, 예, 질문의 코드는 Java 인 것으로 보입니다. 그러나 어떤 언어로도 태그가 지정되지 않았으며이 접근 방식은 여러 언어 (C # 및 C ++ 포함, 복싱 필요 없음)에서 작동합니다.

3

소프트웨어에는 두 가지 속도가 있습니다. 코드를 작성 / 읽기 / 디버그하는 데 걸리는 시간; 코드를 실행하는 데 걸리는 시간

해시 맵 함수가 if / then / else (정적 해시 맵을 만들기 위해 리팩토링 한 후)보다 실제로 느리다는 것을 저 (및 코드 검토 자)에게 설득 할 수 있다면 실제 / 제작자가 실제 시간을 만들기에 충분한 시간을 호출했다고 설득 할 수 있습니다 차이가 나면 해시 맵을 if / else로 바꾸십시오.

그렇지 않으면 해시 맵 코드를 눈에 잘 띄게 읽을 수 있습니다. 그리고 (아마도) 버그가없는 것; 당신은 그것을 보면서 그냥 빨리 결정할 수 있습니다. 실제로 공부하지 않고 if / else에 대해 똑같은 것을 말할 수는 없습니다. 수백 가지 옵션이있을 때 그 차이는 훨씬 더 과장됩니다.


3
글쎄, 대신이 스위치와 비교할 때이 반대는
Deduplicator

if 문을 한 줄에 쓰면 또한 무너집니다.
gnasher729

2
해시 맵 생성을 다른 곳에두면 실제 상황을 파악하기가 더 어려워집니다. 이 키와 값을보고 기능의 실제 효과가 무엇인지 알아야합니다.
RemcoGerlich

2

나는 HashMap 스타일 답변을 매우 선호합니다.

이것에 대한 지표가 있습니다

Cyclomatic Complexity 라는 코드 품질 메트릭이 있습니다 . 이 메트릭은 기본적으로 코드를 통한 여러 경로 수 를 계산합니다 (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

@JimmyJames 링크가 나를 위해 작동합니다 : 그것은 : leepoint.net/principles_and_practices/complexity/…
Ivan

1
@ user949300 요점은 "foo"메소드를 뒷받침하는 키 / 값 데이터는 어떤 형태의 명확성 을 얻을 수 있는 별도의 개념이라는 것입니다 . 지도를 분리하기 위해 작성하려는 코드의 양은지도에 포함 된 항목 수와 변경해야하는 빈도에 따라 크게 달라집니다.
Ivan

1
@ user949300 if-else 체인을 제거하는 것이 좋습니다 이유 중 하나는 if-else 체인이 그룹에 존재하기 때문입니다. 바퀴벌레처럼, if-else 체인을 기반으로 한 방법이 있다면 코드베이스에 유사하거나 동일한 if-else 체인을 가진 다른 방법이있을 수 있습니다. 유사한 추출 / 스위치 형 논리 구조를 사용하는 다른 방법이있을 수 있다고 가정하는 경우 맵 추출은 배당금을 지불합니다.
Ivan

나도 중복 / 거의 모든 경우에 / / 다른 블록이 흩어져있는 것을 보았습니다. 잘 정리
Jon Chesterfield

1

데이터는 코드보다 낫습니다. 코드에 또 다른 브랜치를 추가하는 것이 너무 유혹 적이기 때문에 테이블에 행을 추가하는 것은 잘못되기가 어렵습니다. 이 질문은 이것의 작은 예입니다. 조회 테이블을 작성 중입니다. 구현을 작성하거나 조건부 논리 및 문서를 작성하거나 테이블을 작성한 다음 찾아보십시오.

데이터 테이블은 코드보다 모듈로 최적화가 통과하는 것보다 항상 일부 데이터를 더 잘 표현합니다. 표를 표현하는 것이 얼마나 어려운지는 언어에 따라 다를 수 있습니다. Java를 모르지만 OP의 예보다 더 간단하게 조회 테이블을 구현할 수 있기를 바랍니다.

이것은 파이썬의 룩업 테이블입니다. 이것이 충돌을 일으키는 것으로 간주되는 경우 질문에 java 태그가 지정되어 있지 않으며 리팩토링은 언어에 구애받지 않으며 대부분의 사람들이 Java를 모른다는 것을 고려하십시오.

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

런타임을 줄이기 위해 코드를 재구성한다는 아이디어는 장점이 있지만, 일반적인 설정을 직접 수행하는 컴파일러를 사용하는 것이 좋습니다.


-1 질문에 대한 답변이 아닙니다.
Pieter B

어떤 의미에서? 필자는 데이터와 코드가 분리되어야한다는 이유로 if else 체인을 맵으로 바꾸라고 요청했을 것입니다. 모든 map.put 호출이 불행하지만, 이것이 두 번째 코드가 첫 번째 코드보다 나은 이유입니다.
존 체스터 필드

2
@JonChesterfield이 답변은 기본적으로 "더 나은 언어 사용"으로, 거의 도움이되지 않습니다.
walpen

@walpen 페어 포인트. Ivan은 Java를 통해 대략 동일한 결과를 얻었으므로 파이썬으로 떨어 뜨릴 필요가 없었습니다. 내가 조금 정리할 수 있는지 보자
Jon Chesterfield

일부 오래된 Java 코드에서는 매우 유사한 것에 대한 몇 가지 맵이 필요했기 때문에 2 차원 배열의 N 차원 배열을 맵으로 변환하는 작은 유틸리티를 작성했습니다. 약간 해 키지 만 잘 작동했습니다. 이 답변은 JSONy 표기법을 간단하고 쉽게 지원하는 데 Python / JS의 힘을 지적합니다.
user949300 2018 년
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.