본문 바로가기
자바

Map을 활용한 이중 for문 줄이기

by 근즈리얼 2023. 3. 22.
728x90

업무를 하던 중 이중 for문을 쓰면 해결될 일이 생겼습니다...

 

하지만 이중 for문을 정말 쓰기 싫었고 개선할 수 있는 무언가 아쉬운 포인트들이 자꾸 눈에 보였습니다...

 

그래서 for문을 안쓰고 해결할 방법을 찾으려고 노력했고 노력한 끝에 Map을 사용해서 해결해보았습니다.

 

다만, 메모리나 속도 측면에서 아직 디테일한 체크는 못해봐서 오히려 손해인 로직일 수 있습니다. 이런 방법도 있구나 이렇게 봐주시면 좋을 것 같습니다!!

 

상황

- A서버에서 B서버에 데이터를 요청합니다.

- B서버에서는 데이터의 이름은 모르고 id값만 알고 있습니다.

- 따라서, B서버에서 받은 데이터의 id값을 기준으로 A서버에서 데이터의 이름을 넣어줍니다.

public void test(Long id){
	
    // 특정 id를 기준으로 A서버에서 데이터를 뽑아냄
    List<data> datas = dataRepository.findAllById(id);
    
    // 뽑아낸 데이터에서 id만 set형태로 뽑아냄
    Set<Long> ids = datas.stream().map(data -> data.getId()).collect(Collectors.toSet());

	// B서버와 통신
    List<bServerData> bServerDatas = bserverSenderService(ids);
    
    // B서버에서 받은 데이터에 A서버에서 이름 넣기 (forEach안에 forEach 존재)
    bServerDatas.stream().forEach(bDatas -> {
    	datas.forEach(aDatas -> {
        	if(aDatas.getId() == bDatas.getId()){
            	bDatas.updateName(aDatas.getName());
            }
        })
    })
}

위의 코드처럼 forEach안에 forEach가 또 존재하는 코드가 생겼습니다.

 

이번에는 제가 생각한 방식으로 코드를 개선해 보겠습니다!

public void test(Long id){
	
    // 특정 id를 기준으로 A서버에서 데이터를 뽑아냄
    List<data> datas = dataRepository.findAllById(id);
    
    // 뽑아낸 데이터에서 id만 set형태로 뽑아냄, map을 id를 key name을 value로 만들어보겠습니다.
    Map<Long, String> kIdVNameMap = new HashMap<>();
    Set<Long> ids = datas.stream().map(data -> {
    	kIdVNameMap.put(data.getId(), data.getName())
    	return data.getId()
    }).collect(Collectors.toSet());

	// B서버와 통신
    List<bServerData> bServerDatas = bserverSenderService(ids);
    
    // B서버에서 받은 데이터에 A서버에서 이름 넣기 (Map을 이용해서 이름 update)
    bServerDatas.stream().forEach(bDatas -> {
    	bDatas.updateName(kIdVNameMap.get(bDatas.getId()));
    })
}

위의 코드처럼 for문을 한번만 사용하고 내부에서 map을 이용해서 이름을 update하는 로직을 만들었습니다.

 

아직 성능에 대해서는 테스트 하지 못했기 때문에 이런 방법으로 이중 for문을 안 쓸수도 있구나 생각해주시면 감사하겠습니다!

 


업그레이드!!!!

추가된 내용입니다!! 이번에는 저의 이론 및 생각이 맞는지 체크를 해보겠습니다.

우선, 코드는 돌아가게만 만들었다는 점... 양해부탁드립니다.

 

테스트 시나리오~

  • TestBox에는 id와 name 모두 값을 채웁니다.
  • TestResponse에는 id값만 채웁니다.
  • map 혹은 이중 for문을 이용해서 TestResponse의 name을 채웁니다
  • 이제 로직을 실행시키기 전과 후에 시간을 측정하고 전과 후의 시간을 뺍니다.
  • 마지막으로 그 시간을 비교하면 됩니다.
// TestBox 코드
package maptest;

public class TestBox {

    private int id;

    private String name;

    public static TestBox of(int id, String name) {
        TestBox instance = new TestBox();

        instance.id = id;
        instance.name = name;

        return instance;
    }

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }
}

 

// TestResponse 코드
package maptest;

public class TestResponse {

    private int id;

    private String name;

    public static TestResponse of(int id, String name) {
        TestResponse instance = new TestResponse();

        instance.id = id;
        instance.name = name;

        return instance;
    }

    public void updateName(String name) {
        this.name = name;
    }

    public int getId() {
        return id;
    }
}

 

package maptest;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class TestLoginService {

// map 테스트
    public void mapTest(int count) {
        List<TestBox> testBoxes = new ArrayList<>();
        List<TestResponse> testResponses = new ArrayList<>();
        Map<Integer, String> map = new HashMap<>();
        createTestCase(testBoxes, testResponses, map, count);

        testResponses.forEach(testResponse -> {
            testResponse.updateName(map.get(testResponse.getId()));
        });

    }

// for 테스트
    public void forTest(int count) {
        List<TestBox> testBoxes = new ArrayList<>();
        List<TestResponse> testResponses = new ArrayList<>();
        Map<Integer, String> map = new HashMap<>();
        createTestCase(testBoxes, testResponses, map, count);

        for(TestResponse testResponse : testResponses) {
            for(TestBox testBox : testBoxes) {
                if(testResponse.getId() == testBox.getId()) {
                    testResponse.updateName(testBox.getName());
                }
            }
        }
    }

// 공통 로직
    private void createTestCase(List<TestBox> testBoxes, List<TestResponse> testResponses, Map<Integer, String> map, int count) {
        for (int i = 0; i < count; i++) {
            String name = "이름" + i;
            testBoxes.add(TestBox.of(i, name));
            map.put(i, name);
            testResponses.add(TestResponse.of(i, null));
        }
    }
}

 

import maptest.TestLoginService;

public class Main {
    public static void main(String[] args) {

        TestLoginService testLoginService = new TestLoginService();

        long start = System.currentTimeMillis();
	// 원하는 테스트에 맞춰 주석 처리~        
        testLoginService.mapTest(100);
        testLoginService.forTest(100);
        long end = System.currentTimeMillis();

        System.out.println(end - start);
    }
}

 

위의 일련의 코드들이 지나갔는데요~

이제는 테스트 결과를 보시겠습니다.

밑 i, 오른쪽 테스트 종류 map for
100 5 6
1000 8 15
10000 13 184
100000 36 22313

와우... 이게 시간이 지날수록 격차가 많이 커지는 것을 확인할 수 있었습니다...

역시 이중 for문의 시간 복잡도는 O(n^2)인 만큼 데이터가 많아질 수록 시간이 오래 걸리네요... ㅠㅠ

반면, map에서 데이터를 가져오는 시간 복잡도는 O(logN)인 만큼 데이터가 많을 때 확실히 효과적으로 계산이 가능합니다.

데이터가 정말 많은 경우에는 이중 for문은 최대한 피해야겠습니다!!

728x90

댓글