본문 바로가기
블로그 이미지

방문해 주셔서 감사합니다! 항상 행복하세요!

  
   - 문의사항은 메일 또는 댓글로 언제든 연락주세요.
   - "해줘","답 내놔" 같은 질문은 답변드리지 않습니다.
   - 메일주소 : lts06069@naver.com


Java(자바)/코딩 테스트 & 알고리즘

Java Map에서 Map데이터 다루기(자바 Map을 깊이탐색으로)

야근없는 행복한 삶을 위해 ~
by 마샤와 곰 2021. 6. 23.

자바(Java)를 사용하다보면 가장 아쉬운 기능이 Json형식의 데이터를 자유롭게 만들지 못하다는 점 입니다.

아래 json 데이터를 살펴 보겠습니다.

let 데이터1 = {
    "데이터2": {
        "데이터3": {
            "가져와서바꿀키" : "값"
        }
    }
}

 

위 json객체에서 "가져와서바꿀키"의 값을 확인하고 바꾸는 것은 매우 쉽습니다.

Javascript로는 전혀 어렵지 않는 기능 입니다.

뭐 기본 아니겠습니까? : )

 

이와 비슷하게 자바에서는 Map이라는 객체가 이러한 역할을 담당 합니다.

Map은 키(key)와 값(value)을 넣을 수 있는 컬렉션이라는 자료구조 입니다.

위 자바스크립트의 내용과 비슷하게 아래 Map객체로 데이터가 담아져 있다고 가정하여 봅니다.

{
    HashMap<Object, Object> first = new HashMap<>();
    HashMap<Object, Object> second = new HashMap<>();
    HashMap<Object, Object> third = new HashMap<>();
    HashMap<Object, Object> last = new HashMap<>();

    first.put("data", 1234);
    first.put("second", second);

    second.put("data", 5678);
    second.put("third", third);

    third.put("data", 91011);
    third.put("last", last);

    last.put("goal", new ArrayList<>());
}

 

firt라는 변수에 data, second 라는 키로 값이 담겨져 있습니다.

second키 값에는 Map객체가 담겨져 있으며, 해당 Map객체는 다시 third라는 변수로 Map객체가 담아져 있습니다.

third라는 변수에는 last라는 변수에 Map객체가 담겨져 있으며, last객체는 goal이라는 ArrayList를 가지고 있습니다.

* 말이 참 어렵네요..이렇게 복잡한 경우가 있을까요?

 

이를 간단하게 출력하여 보겠습니다.

map에 map에 map에 map이 담겨져 있습니다.

 

자 그러면 여기서 맨 마지막에 있는 Map객체의 goal이라는 값을 가져와서 출력한 뒤에 List형식의 데이터를 단순 문자열로 바꾸는 요구사항이 발생하였다고 가졍하여봅니다.

그러면 이제 어떻게 저 마지막 대상에게 접근 해야 되는 것 일까요..?

Map데이터를 바꾸려면 반복문(itor) 또는 스트림(stream)을 활용 해야만 하는데 감이 잡히지를 않습니다.

만약 단순하게 접근 한 다면 stream을 파동권(?) 형식으로 써 주면 될 것 입니다.

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;

public static void main(String args[]){
    HashMap<Object, Object> first = new HashMap<>();
    HashMap<Object, Object> second = new HashMap<>();
    HashMap<Object, Object> third = new HashMap<>();
    HashMap<Object, Object> last = new HashMap<>();

    first.put("data", 1234);
    first.put("second", second);

    second.put("data", 5678);
    second.put("third", third);

    third.put("data", 91011);
    third.put("last", last);

    last.put("goal", new ArrayList<>());

    first = first.entrySet().stream().map( entry ->{
        Map<Object, Object> second = (Map<Object, Object>)entry.getValue();
        second = second.entrySet().stream().map( entry222 ->{
            Map<Object, Object> third = (Map<Object, Object>)entry222.getValue();
            
            //이러한 방식으로, 이하 생략....
            
        }).collect(Collectors.toMap( k->k, v->v));
        return entry;
    }).collect(Collectors.toMap( k->k, v->v));
}

 

어떻게든 해결한다면 이러한 방법을 통해서 코드를 계속해서 붙여나가면 될 것 입니다.

또한 형변환(cast)을 하는 경우에 조건문을 통해서 Map객체인지 확인하는 코드도 추가되어야 할 것 입니다.

만약 Map객체의 데이터의 깊이(depth)가 늘어나면 코드가 점점 더 우측으로 길어지게 될 것 입니다.

과연 나중에 관리가 가능 할 까요..?

 

이를 해결하기 위해서는 깊이탐색 방법 알고리즘에서 자주 나오는 재귀형식의 호출을 적용하면 됩니다.

@SuppressWarnings("unchecked")
private static void iterator(Map<Object, Object> data, String key, Consumer<Entry<Object, Object>> consume) {
    data = data.entrySet().stream().map( entry -> {
        Object __key = entry.getKey();
        if(__key != null && !__key.toString().equals(key) && entry.getValue() instanceof Map) {  //map이면서 대상이 아닌경우
            iterator( (Map<Object, Object>)entry.getValue(), key, consume);  //map을 다시 검사!
        } else if(__key != null && __key.toString().equals(key)){  //map이면서 대상인 경우
            consume.accept(entry);
        }
        return entry;
    }).collect(Collectors.toMap( k->k, v->v));
}

 

코드가 다소 복잡해 보이긴 하나 어렵지 않습니다.

stream을 통해서 조사하는 데이터가 Map 객체이면서 key 값이 일치하면 컨슈머(Consumer)객체가 동작 하도록 하였습니다.

행위 자체를 함수형 인터페이스로 받게 하였으므로 값을 단순히 출력만 할지 아니면 바꿀지 선택을 자유롭게 하도록 하였습니다.

이를 실제로 동작시킨 최종 코드 입니다.

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Consumer;
import java.util.stream.Collectors;

public class Looper {

    public static void main(String[] args) {

        HashMap<Object, Object> first = new HashMap<>();
        HashMap<Object, Object> second = new HashMap<>();
        HashMap<Object, Object> third = new HashMap<>();
        HashMap<Object, Object> last = new HashMap<>();

        first.put("data", 1234);
        first.put("second", second);

        second.put("data", 5678);
        second.put("third", third);

        third.put("data", 91011);
        third.put("last", last);

        last.put("goal", new ArrayList<>());
        System.out.println(first);

        iterator(first, "goal", entry ->{
            System.out.println(entry.getValue());
            entry.setValue("abcd");
        });
        System.out.println("fin");
        System.out.println(first);
    }

    @SuppressWarnings("unchecked")
    private static void iterator(Map<Object, Object> data, String key, Consumer<Entry<Object, Object>> consume) {
        data = data.entrySet().stream().map( entry -> { //#1. entryset에서 스트림을통해 map을 동작합니다
            Object __key = entry.getKey();  //#2. 키 값을 가져옵니다
            if(__key != null && !__key.toString().equals(key) && entry.getValue() instanceof Map) { 
                iterator( (Map<Object, Object>)entry.getValue(), key, consume); //#3. 해당 값이 map객체면 iterator메소드를 다시 호출합니다
            } else if(__key != null && __key.toString().equals(key)){ 
                consume.accept(entry); //#4. 만약 찾고자하는 데이터가 맞으면 Consumer객체를 accept합니다
            }
            return entry;  //#5. map으로 변환된 객체를 리턴하여 적용 합니다
        }).collect(Collectors.toMap( k->k, v->v));
    }
}

 

원하는 데이터가 나올 때 까지 계속해서 들어가 탐색(Depth Search)을 하게 되어 있습니다.

조건문이 있기 때문에 형변환(cast) 오류와 널참조 오류는 발생하지 않으며 무한루프(loop)현상도 예방 할 수 있습니다.

실제로 동작하여 본 모습입니다.

빈 list값을 출력하였고, 이후에 abcd라는 문자를 넣어 주었습니다.

 

가장 하단부까지 모두 탐색을 하게 되므로 시간복잡도가 다소 있겠지만 Map객체의 구조가 몇백개의 깊이(depth)로 되어있지 않는 한 대부분의 케이스에 대해서 잘 작동 할 것 입니다.

이상으로 자바 Map에서 Map데이터 다루기에 대해 살펴 보았습니다.

좋은 의견 또는 틀린부분은 언제든 댓글, 메일로 알려주세요! 👻

반응형
* 위 에니메이션은 Html의 캔버스(canvas)기반으로 동작하는 기능 입니다. Html 캔버스 튜토리얼 도 한번 살펴보세요~ :)
* 직접 만든 Html 캔버스 애니메이션 도 한번 살펴보세요~ :)

댓글