배열, 객체의 call by value, call by reference에 대해 공부하다가 깊은 복사를 단순하게라도 구현해보고 싶은 마음이 생겼다.

 

1. 객체 깊은 복사 코드

function objectDeep(origin){
        const target = {};
        Object.keys(origin).forEach((k)=>{
            if(Array.isArray(origin[k])){
                target[k] = arrayDeep(origin[k]);
            }else if(typeof origin[k] === 'object'){
                target[k] = objectDeep(origin[k]);
            }else{
                target[k] = origin[k];
            }
        });
        return target;
    }

2. 배열 깊은 복사 코드

    function arrayDeep(origin){
        const target = [];
        origin.forEach((v)=>{
            if(Array.isArray(v)){
                target.push(arrayDeep(v));
            }else if(typeof v === 'object'){
                target.push(objectDeep(v));
            }else{
                target.push(v);
            }
        });
        return target;
    }

 

두 함수는 각각 객체 깊은 복사함수(1번), 배열 깊은 복사함수(2번)이다.

두 함수의 생김새나 하는 역할은 매우 유사한 것을 확인할 수 있다.

 

objectDeep을 위주로 설명해보자면 

1. 재귀함수 개념을 사용하였다.

2. 해당 키 또는 인덱스에 있는 값이 배열이면 arrayDeep을 호출한다.

3. 해당 키 또는 인덱스에 있는 값이 객체이면 objectDeep을 호출한다.

4. 2번, 3번에 해당하지 않으면 원시값으로 판단하고 그대로 대입하여 복사한다.

 

↓ 3번은 객체안의 객체값이 해당한다고 생각하면 된다.

const obj = {
 inner: {
  text: '객체안의 객체',
 },
}

 

원시값이면 복사할 배열(target)에 바로 값을 대입하지만 객체이거나 배열이라면 그대로 값을 대입해버리면 참조값만 대입되어 얉은 복사가 되므로 주의해야 한다.

 

3. 테스트

더미데이터

const array = [1,2,3,4,5,[6,7,8,[9,10,11]],12,13,14, {a:1,b:2}];
const target = arrayDeep(array); //array의 데이터를 깊은 복사한 배열

테스트 케이스

array.push(15);
target[9].a=55;
target[5][0] = 77777;
target[5][3][0] = 99999;

결과

테스트1. 원본 데이터 변경

array.push(15)

원본인 array의 끝에 15라는 값을 추가했지만 복사본인 target에는 추가되지 않은 것을 확인할 수 있다.

 

테스트2. 복사본 데이터 변경

target[9].a=55;

복사본(target)의 9번 인덱스에 위치한 객체의 a를 55로 바꾸었지만 원본(array)의 9번 인덱스에 위치한 객체는 변경되지 않은 것을 확인할 수 있다.

 

테스트3. 복사본 데이터 변경2

target[5][0] = 77777;

설명이 필요없다. 테스트2번의 경우와 똑같다.

 

아래도 마찬가지

target[5][3][0] = 99999;

이렇게 원본 또는 복사본에 있는 원시값이 아닌 객체들을 변경해도 서로 메모리가 공유되어 있지 않으니 각각 적용이 되는 것을 볼 수 있었다.

개발을 하다보면 배열에 있는 값을 랜덤으로 섞어서 다른 배열을 만들어야할 때가 있다.

 

 

그때그때 짜기 보다는 그냥 하나 만들어두고 복붙하는게 좋을거같아서 끄적여본다 ㅎㅎ.

    const origin = [1,2,3,4,5,6,7,8,9];
    const shuffle = shuffleFunc(origin);

    function shuffleFunc(origin){
        const shuffle = [];    
        const dummy = origin.slice();
        
        while(dummy.length > 0){
            const index = Math.floor(Math.random() * dummy.length); // 0 <= x < length
            shuffle.push(dummy[index]);
            dummy.splice(index,1); //버려준다.
        }
        return shuffle;
    }

 

[설명]

1. 변수 설명

const shuffle 배열은 무작위하게 섞인 항목들이 들어갈 배열이 된다.
const dummy 배열은 해당 함수가 splice를 사용하여 구현되기 때문에 원본배열을 손상시키지 않기 위해서 
원본배열의 참조값을 가지고 있는 배열일 뿐이다.

 

2. 주요 로직

[ 무작위 값 선택 ] 

주의할 점은 배열의 범위를 넘어서는 index를 선택하면 안된다.

Math.random() 은 무작위로 0 <= x < 1의 값을 반환한다.

우리는 dummy 배열의 인덱스를 무작위로 추출해야하므로 dummy배열의 length를 활용한다.

 

Math.random() * dummy.length 의 반환값은 아래와 같을 것이다.

0 <= x < dummy.length 즉, 위의 예시에서는 0 <= x < 9 (0~8까지)

해당 식의 반환값은 현재 dummy 길이를 초과하지 않는 index값이 반환될 것이고, shuffle에 해당 index의 값을 넣어주기만 하면된다.

 

[ 후처리 ]

index하나를 무작위로 선택하여 결과 배열(shuffle)에 넣었으니 선택된 index는 다시 선택되지 않도록 처리해줘야한다.

 

그것을 splice로 처리하고 있다.

 

splice(i, N)  하면 i번째 부터 N개까지 추출하여 배열로 반환하는 메소드이며, 해당 메소드는 대상이 되는 원본배열을 수정해버리기 때문에 주의해야한다. (slice와 다름 주의!!) 

 

결과적으로 splice(index, 1)을 하게되면 해당 index의 항목이 빠지게 되는 효과를 볼 수 있다.

 

[ 정리 ]

const index = Math.floor(Math.random() * dummy.length); ===> 4가 나왔다고 가정

 

 

shuffle.push(dummy[index]);

 


           

dummy.splice(index,1);

 

 

 

 

 

+ Recent posts