문제 상황
두 글자씩 짝지어진 특수한 형태의 문자열이 있다고 가정해 보겠습니다.
const str = "AABBCCDDEE";
이 문자열을 기반으로 아래와 같은 중첩(nested) 구조의 객체를 만들어야 합니다.
const obj = {
code: "AA",
sub: {
code: "BB",
sub: {
code: "CC",
sub: {
code: "DD",
sub: {
code: "EE",
sub: {}
}
}
}
}
};여기서 주목할 점은, 문자열의 각 두 글자 쌍마다 새로운 sub 객체가 생성되며, 각 계층의 code 속성은 해당 위치의 두 글자 쌍을 그대로 담고 있다는 것입니다.
해결 접근 방식
이 문제는 반복(iterative) 방식으로 해결할 수 있습니다. while 루프를 사용해 문자열을 순회하면서 앞에서부터 두 글자씩 잘라내고, 잘라낸 값을 code 속성에 할당한 뒤 참조 변수를 새로 만든 sub 객체로 이동시키는 방식입니다.
핵심은 ref라는 임시 참조 변수입니다. 처음에는 결과 객체 res 자체를 가리키다가, 루프가 한 번 돌 때마다 새로 생성된 sub 객체를 가리키도록 갱신됩니다. 덕분에 재귀 함수 호출 없이도 깊이 중첩된 구조를 간단하게 만들 수 있습니다.
예제 코드
const str = "AABBCCDDEE";
const constructObject = str => {
const res = {};
let ref = res;
while(str){
const words = str.substring(0, 2);
str = str.substr(2, str.length);
ref.code = words;
ref.sub = {};
ref = ref.sub;
};
return res;
};
console.log(JSON.stringify(constructObject(str), undefined, 4));실행 결과
위 코드를 실행하면 콘솔에 다음과 같은 출력이 나타납니다.
{
"code": "AA",
"sub": {
"code": "BB",
"sub": {
"code": "CC",
"sub": {
"code": "DD",
"sub": {
"code": "EE",
"sub": {}
}
}
}
}
}코드 동작 원리와 개선 팁
substring(0, 2)는 문자열의 맨 앞 두 글자를 추출하고, substr(2, str.length)는 이미 처리된 부분을 제거한 나머지 문자열을 반환합니다. 루프는 문자열이 빈 값이 될 때까지 반복되므로, 마지막 계층에는 항상 빈 객체인 sub: {}가 남게 되는 구조입니다.
참고로 String.prototype.substr()은 현재 폐지 예정(deprecated) 상태이므로, 실무 코드에서는 slice() 메서드를 사용하는 것이 더 안전합니다. slice() 기반으로 리팩터링하면 다음과 같습니다.
const constructObject = str => {
const res = {};
let ref = res;
while(str.length){
ref.code = str.slice(0, 2);
str = str.slice(2);
ref.sub = {};
ref = ref.sub;
}
return res;
};