JavaScript에서 배열을 복제하는 가장 빠른 방법-슬라이스 대 'for'루프


634

JavaScript에서 배열을 복제하려면 다음 중 사용하는 것이 더 빠릅니까?

슬라이스 방법

var dup_array = original_array.slice();

For 고리

for(var i = 0, len = original_array.length; i < len; ++i)
   dup_array[i] = original_array[i];

나는 두 가지 방법으로 만 할 알고 얕은 복사 : original_array 개체에 대한 참조를 포함하는 경우, 객체는 복제되지 않지만 참조 만이 복사됩니다, 따라서 두 배열은 같은 객체에 대한 참조를해야합니다. 그러나 이것은이 질문의 요점이 아닙니다.

나는 단지 속도에 대해서만 묻고있다.


3
jsben.ch/#/wQ9RU <= 어레이를 복제하는 가장 일반적인 방법에 대한 벤치 마크
EscapeNetscape

답변:


776

배열을 복제하는 방법 은 적어도 5 가지 (!) 있습니다.

  • 고리
  • 일부분
  • Array.from ()
  • 연결하다
  • 스프레드 연산자 (FASTEST)

huuuge BENCHMARKS 스레드 가 있으며 다음 정보를 제공합니다.

  • 대한 깜박임 브라우저 slice(), 가장 빠른 방법입니다 concat()조금 느린이며, while loop2.4 배는 느립니다.

  • 다른 브라우저의 while loop경우 가장 빠른 방법입니다. 해당 브라우저에는 slice및에 대한 내부 최적화가 없기 때문 concat입니다.

2016 년 7 월에도 마찬가지입니다.

다음은 브라우저의 콘솔에 복사하여 붙여넣고 여러 번 실행하여 그림을 볼 수있는 간단한 스크립트입니다. 그들은 밀리 초를 출력하며 낮을수록 좋습니다.

while 루프

n = 1000*1000;
start = + new Date();
a = Array(n); 
b = Array(n); 
i = a.length;
while(i--) b[i] = a[i];
console.log(new Date() - start);

일부분

n = 1000*1000;
start = + new Date();
a = Array(n); 
b = a.slice();
console.log(new Date() - start);

이러한 메소드는 Array 객체 자체를 복제하지만 배열 내용은 참조로 복사되며 딥 복제되지는 않습니다.

origAr == clonedArr //returns false
origAr[0] == clonedArr[0] //returns true

48
@ cept0 감정 없음, 벤치마킹 jsperf.com/new-array-vs-splice-vs-slice/31
Dan

2
@ 댄 그래서 무엇? 테스트 결과 : 매일 밤 Firefox 30이 Chrome보다 ~ 230 % 더 빠릅니다. V8의 소스 코드를 확인하면 splice놀랄 것입니다. (...)
mate64

4
슬프게도 짧은 배열의 경우 그 대답은 크게 다릅니다 . 예를 들어, 각 리스너를 호출하기 전에 리스너의 배열을 복제합니다. 이러한 배열은 대개 작으며 대개 1 개의 요소입니다.
gman

6
이 방법을 놓쳤다 :A.map(function(e){return e;});
wcochran

13
깜박임 브라우저 에 대해 쓰고 있습니다. 주로 HTML 렌더링에 영향을 미쳐 중요하지 않은 레이아웃 엔진 만 깜박 이지 않습니까? V8, Spidermonkey 및 친구들에 대해 이야기하고 싶다고 생각했습니다. 나를 혼란스럽게 한 것. 내가 틀렸다면 나를 깨우 치십시오.
Neonit

241

기술적 slice 으로 가장 빠른 방법입니다. 그러나0 시작 색인 을 추가하면 더 빠릅니다 .

myArray.slice(0);

보다 빠르다

myArray.slice();

http://jsperf.com/cloning-arrays/3


그리고 myArray.slice(0,myArray.length-1);보다 빠르다 myArray.slice(0);?
jave.web

1
@ jave.web you; 방금 배열의 마지막 요소를 삭제했습니다. 전체 사본은 array.slice (0) 또는 array.slice (0, array.length)입니다.
Marek Marczak

137

es6 방법은 어떻습니까?

arr2 = [...arr1];

23
babel로 변환하는 경우 :[].concat(_slice.call(arguments))
CHAN

1
어디에서 오는지 확실하지 않습니다 arguments... 귀하의 babel 출력이 몇 가지 다른 기능을 혼란스럽게 생각합니다. 더 가능성이 높습니다 arr2 = [].concat(arr1).
Sterling Archer

3
@SterlingArcher arr2 = [].conact(arr1)와는 다릅니다 arr2 = [...arr1]. [...arr1]구문은 hole을로 변환합니다 undefined. 예를 들면 다음과 같습니다 arr1 = Array(1); arr2 = [...arr1]; arr3 = [].concat(arr1); 0 in arr2 !== 0 in arr3.
tsh

1
위의 Dan 대답에 대해 내 브라우저 (Chrome 59.0.3071.115)에서 이것을 테스트했습니다. .slice ()보다 10 배 이상 느 렸습니다. n = 1000*1000; start = + new Date(); a = Array(n); b = [...a]; console.log(new Date() - start); // 168
Harry Stevens

1
여전히 다음과 같은 것을 복제하지 않습니다 [{a: 'a', b: {c: 'c'}}]. 경우 c의 값은 "중복"배열에 변경 그냥 참조 사본이 아닌 클론이기 때문에, 그것은 원래의 배열로 변경됩니다.
신경 전달 물질

44

배열 또는 객체를 딥 복제하는 가장 쉬운 방법 :

var dup_array = JSON.parse(JSON.stringify(original_array))

56
초보자를위한 중요 사항 : JSON에 따라 다르므로 제한 사항도 상속합니다. 무엇보다도 배열은 undefined또는을 포함 할 수 없습니다 function. 그 과정에서 둘 다 null당신 을 위해 변환됩니다 JSON.stringify. (['cool','array']).slice()변경 과 같은 다른 전략 은 물론 배열 내에서 개체를 딥 복제하지 않습니다. 따라서 절충안이 있습니다.
세스 할러데이

27
성능이 매우 좋지 않으며 DOM, 날짜, 정규 표현식, 함수 ... 또는 프로토 타입 객체와 같은 특수 객체에서는 작동하지 않습니다. 순환 참조를 지원하지 않습니다. 딥 클론에는 JSON을 사용해서는 안됩니다.
Yukulélé

17
최악의 방법! 일부 문제의 경우 다른 모든 문제가 해결되지 않는 경우에만 사용하십시오. 속도가 느리고 리소스가 많이 사용되며 주석에 언급 된 모든 JSON 제한이 있습니다. 25 개의 투표권을 얻는 방법을 상상할 수 없습니다.
루카스 리에 시스

2
프리미티브로 배열을 복사하고 속성은 추가 프리미티브 / 배열이있는 배열입니다. 이를 위해 괜찮습니다.
Drenai

4
위의 Dan 대답에 대해 내 브라우저 (Chrome 59.0.3071.115)에서 이것을 테스트했습니다. .slice ()보다 거의 20 배 느 렸습니다. n = 1000*1000; start = + new Date(); a = Array(n); var b = JSON.parse(JSON.stringify(a)) console.log(new Date() - start); // 221
Harry Stevens

29
var cloned_array = [].concat(target_array);

3
이것이 무엇을하는지 설명하십시오.
Jed Fox

8
이 코드 스 니펫은 질문에 대답 할 수 있지만 방법과 이유를 설명하는 컨텍스트를 제공하지는 않습니다. 답을 설명하기 위해 한두 문장을 추가하십시오.
brandonscript

32
나는 이런 종류의 의견을 싫어한다. 그것이하는 일이 분명하다!
EscapeNetscape

6
간단한 질문에 대한 간단한 답변, 읽을만한 큰 이야기가 없습니다. 나는 대답 이런 종류의 일처럼
아킴

15
"속도에 대해서만 묻습니다"-이 답변은 속도에 대한 정보를 제공하지 않습니다. 이것이 주요 질문입니다. brandonscript는 좋은 지적이 있습니다. 이 답변을 고려하려면 추가 정보가 필요합니다. 그러나 더 간단한 질문이라면 훌륭한 답변이 될 것입니다.
TamusJRoyce

26

빠른 데모를 만들었습니다 : http://jsbin.com/agugo3/edit

Internet Explorer 8의 결과는 156, 782 및 750이며이 slice경우 훨씬 빠릅니다.


이 작업을 매우 빠르게 수행해야하는 경우 가비지 수집기의 추가 비용을 잊지 마십시오. 슬라이스를 사용하여 셀룰러 오토마타의 각 셀에 대해 각 이웃 배열을 복사하고 있었고 이전 배열을 재사용하고 값을 복사하는 것보다 훨씬 느 렸습니다. Chrome은 총 시간의 약 40 %가 가비지 수집에 소비되었음을 나타냅니다.
drake7707

21

a.map(e => e)이 작업의 또 다른 대안입니다. 현재로서는 Firefox에서는 .map()매우 빠르지 만 (거의 .slice(0)) Chrome에서는 그렇지 않습니다.

반면에 배열이 다차원 인 경우 배열은 객체이고 객체는 참조 유형이므로 슬라이스 또는 연결 방법 중 어느 것도 치료법이 될 수 없습니다. 따라서 배열을 복제하는 적절한 방법 중 하나 Array.prototype.clone()는 다음과 같습니다.

Array.prototype.clone = function(){
  return this.map(e => Array.isArray(e) ? e.clone() : e);
};

var arr = [ 1, 2, 3, 4, [ 1, 2, [ 1, 2, 3 ], 4 , 5], 6 ],
    brr = arr.clone();
brr[4][2][1] = "two";
console.log(JSON.stringify(arr));
console.log(JSON.stringify(brr));


나쁘지는 않지만 불행히도 배열에 Object가있는 경우 작동하지 않습니다 : \ JSON.parse (JSON.stringify (myArray))이 경우 더 잘 작동합니다.
GBMan

17

🏁 어레이를 복제하는 가장 빠른 방법

어레이를 복제하는 데 걸리는 시간을 테스트하기 위해 매우 일반적인 유틸리티 기능을 만들었습니다. 100 % 신뢰할 수는 없지만 기존 어레이를 복제하는 데 걸리는 시간에 대한 정보를 얻을 수 있습니다.

function clone(fn) {
    const arr = [...Array(1000000)];
    console.time('timer');
    fn(arr);
    console.timeEnd('timer');
}

그리고 다른 접근법을 테스트했습니다.

1)   5.79ms -> clone(arr => Object.values(arr));
2)   7.23ms -> clone(arr => [].concat(arr));
3)   9.13ms -> clone(arr => arr.slice());
4)  24.04ms -> clone(arr => { const a = []; for (let val of arr) { a.push(val); } return a; });
5)  30.02ms -> clone(arr => [...arr]);
6)  39.72ms -> clone(arr => JSON.parse(JSON.stringify(arr)));
7)  99.80ms -> clone(arr => arr.map(i => i));
8) 259.29ms -> clone(arr => Object.assign([], arr));
9) Maximum call stack size exceeded -> clone(arr => Array.of(...arr));

업데이트 :
참고 : 그중에서도 배열을 딥 복제하는 유일한 방법은을 사용하는 것 JSON.parse(JSON.stringify(arr))입니다.

즉, 배열에 반환 할 함수가 포함되어 있으면 위의 내용을 사용하지 마십시오 null.
이 업데이트에 대해 @GilEpshtain에게 감사합니다 .


2
귀하의 답변을 벤치마킹하려고 시도한 결과 매우 다른 결과를 얻었습니다. jsben.ch/o5nLG
mesqueeb

@mesqueeb, 물론 기계에 따라 테스트가 변경 될 수 있습니다. 그러나 테스트 결과로 답변을 업데이트하십시오. 잘 했어!
Lior Elrom

나는 당신의 대답을 많이 좋아하지만 테스트를 시도하고 그것이 arr => arr.slice()가장 빠릅니다.
Gil Epshtain

1
@LiorElrom, 메소드를 직렬화 할 수 없기 때문에 업데이트가 올바르지 않습니다. 예를 들어 JSON.parse(JSON.stringify([function(){}]))출력됩니다[null]
길 Epshtain

1
좋은 벤치 마크. 나는이 브라우저 내 Mac에서이 테스트 한 크롬 버전 81.0.4044.113 사파리 버전 13.1 (15609.1.20.111.8) 빠른 확산 작업은 다음 [...arr]4.653076171875ms크롬과 8.565ms사파리입니다. 크롬에서 두 번째로 빠른 슬라이스 기능이다 arr.slice()6.162109375ms사파리에서 두 번째 [].concat(arr)13.018ms.
edufinn

7

링크를 살펴보십시오 . 속도가 아니라 편안함입니다. 보시 다시피 기본 유형 에서만 slice (0) 을 사용할 수 있습니다 .

참조의 복사본이 아닌 배열의 독립 복사본을 만들려면 배열 슬라이스 방법을 사용할 수 있습니다.

예:

참조의 복사본이 아닌 배열의 독립 복사본을 만들려면 배열 슬라이스 방법을 사용할 수 있습니다.

var oldArray = ["mip", "map", "mop"];
var newArray = oldArray.slice();

객체를 복사하거나 복제하려면

function cloneObject(source) {
    for (i in source) {
        if (typeof source[i] == 'source') {
            this[i] = new cloneObject(source[i]);
        }
        else{
            this[i] = source[i];
  }
    }
}

var obj1= {bla:'blabla',foo:'foofoo',etc:'etc'};
var obj2= new cloneObject(obj1);

출처 : 링크


1
기본 유형의 코멘트는 적용 for뿐만 아니라 해당 루프.
user113716

4
객체 배열을 복사하는 경우 새 배열이 객체를 복제하는 대신 동일한 객체를 참조 할 것으로 기대합니다.
lincolnk

7

@Dan으로 "이 대답은 빠르게 구식이된다 사용. 말했다 벤치 마크 실제 상황을 확인하기 위해"자체에 대한 대답이 없었습니다 jsperf에서 하나 개의 특정 응답이 : 상태 :

var i = a.length;
while(i--) { b[i] = a[i]; }

578,129 ops / sec ( a.concat()60 %)의 런너 업으로 960,589 ops / sec를 기록했습니다.

이것은 최신 Firefox (40) 64 비트입니다.


@aleclarson은 새롭고보다 안정적인 벤치 마크를 만들었습니다.


1
jsperf를 실제로 연결해야합니다. 'while loop'테스트를 제외하고 모든 테스트 케이스에 새 배열이 생성되므로 생각중인 것이 깨졌습니다.
aleclarson

1
더 정확한 새 jsperf를 만들었습니다 : jsperf.com/clone-array-3
aleclarson

60 % 무엇? 60 % 더 빠릅니까?
Peter Mortensen

1
@PeterMortensen : 587192는 960589의 ~ 60 % (61.1 ...)입니다.
serv-inc

7

Spread운영자 와 ECMAScript 2015 방법 :

기본 예 :

var copyOfOldArray = [...oldArray]
var twoArraysBecomeOne = [...firstArray, ..seccondArray]

브라우저 콘솔에서 시도하십시오 :

var oldArray = [1, 2, 3]
var copyOfOldArray = [...oldArray]
console.log(oldArray)
console.log(copyOfOldArray)

var firstArray = [5, 6, 7]
var seccondArray = ["a", "b", "c"]
var twoArraysBecomOne = [...firstArray, ...seccondArray]
console.log(twoArraysBecomOne);

참고 문헌


아마도 스프레드가 빠른 유일한 것은 입력하는 것입니다. 다른 방법보다 성능이 떨어집니다.
XT_Nova

3
논증에 대한 링크를 제공하십시오.
Marian07

6

브라우저에 따라 다릅니다. 블로그 게시물 Array.prototype.slice vs manual array creation 을 보면 각각의 성능에 대한 대략적인 지침이 있습니다.

여기에 이미지 설명을 입력하십시오

결과 :

여기에 이미지 설명을 입력하십시오


1
arguments적절한 배열이 아니며 컬렉션에서 call강제 slice로 실행하는 데 사용하고 있습니다. 결과가 잘못 될 수 있습니다.
lincolnk

Yeh는 내 게시물에서 이러한 통계가 브로 더가 향상됨에 따라 지금은 변경 될 것이라고 언급했지만 일반적인 아이디어를 제공합니다.
kyndigs

2
@diugalde 나는 그림으로 코드를 게시하는 것이 허용되는 유일한 상황은 코드가 잠재적으로 위험하고 복사 붙여 넣기를해서는 안되는 경우라고 생각합니다. 그러나이 경우에는 꽤 어리 석습니다.
Florian Wendelborn 2016 년

6

훨씬 더 깨끗한 해결책이 있습니다.

var srcArray = [1, 2, 3];
var clonedArray = srcArray.length === 1 ? [srcArray[0]] : Array.apply(this, srcArray);

Array생성자가 정확히 하나의 인수로 호출 될 때 생성자가 다르게 동작 하므로 길이 검사가 필요합니다 .


2
그러나 가장 빠릅니까?
Chris Wesseling

14
splice()아마도 보다 의미 론적 입니다. 하지만 실제로는, 적용 하고 모든하지만 직관적이다.
Michael Piefel

쇼에 가장 느린 성능 크롬 - jsperf.com/new-array-vs-splice-vs-slice/113
chrismarx

3
Array.of길이를 사용 하고 무시할 수 있습니다 .Array.of.apply(Array, array)
Oriol

6

.slice ()는 2 차원 배열에서는 작동하지 않습니다. 다음과 같은 기능이 필요합니다.

function copy(array) {
  return array.map(function(arr) {
    return arr.slice();
  });
}

3
자바 스크립트에는 2 차원 배열이 없습니다. 배열을 포함하는 배열이 있습니다. 당신이하려는 것은 질문에 필요하지 않은 깊은 사본 입니다.
Aloso

5

배열의 길이에 따라 다릅니다. 배열 길이가 <= 1,000,000 인 경우 sliceconcat메소드는 거의 같은 시간이 걸립니다. 그러나 더 넓은 범위를 제공하면 concat방법이 승리합니다.

예를 들어 다음 코드를 사용해보십시오.

var original_array = [];
for(var i = 0; i < 10000000; i ++) {
    original_array.push( Math.floor(Math.random() * 1000000 + 1));
}

function a1() {
    var dup = [];
    var start = Date.now();
    dup = original_array.slice();
    var end = Date.now();
    console.log('slice method takes ' + (end - start) + ' ms');
}

function a2() {
    var dup = [];
    var start = Date.now();
    dup = original_array.concat([]);
    var end = Date.now();
    console.log('concat method takes ' + (end - start) + ' ms');
}

function a3() {
    var dup = [];
    var start = Date.now();
    for(var i = 0; i < original_array.length; i ++) {
        dup.push(original_array[i]);
    }
    var end = Date.now();
    console.log('for loop with push method takes ' + (end - start) + ' ms');
}

function a4() {
    var dup = [];
    var start = Date.now();
    for(var i = 0; i < original_array.length; i ++) {
        dup[i] = original_array[i];
    }
    var end = Date.now();
    console.log('for loop with = method takes ' + (end - start) + ' ms');
}

function a5() {
    var dup = new Array(original_array.length)
    var start = Date.now();
    for(var i = 0; i < original_array.length; i ++) {
        dup.push(original_array[i]);
    }
    var end = Date.now();
    console.log('for loop with = method and array constructor takes ' + (end - start) + ' ms');
}

a1();
a2();
a3();
a4();
a5();

original_array의 길이를 1,000,000으로 설정하면 slice메서드와 concat메서드가 거의 같은 시간 (임의의 숫자에 따라 3 ~ 4ms)이 걸립니다.

original_array의 길이를 10,000,000으로 설정하면 slice60ms를 초과하고 concat20ms를 초과합니다.


dup.push에서 잘못 사용되었습니다 a5. 대신 dup[i] = 사용해야합니다
4esn0k


2
        const arr = ['1', '2', '3'];

         // Old way
        const cloneArr = arr.slice();

        // ES6 way
        const cloneArrES6 = [...arr];

// But problem with 3rd approach is that if you are using muti-dimensional 
 // array, then only first level is copied

        const nums = [
              [1, 2], 
              [10],
         ];

        const cloneNums = [...nums];

// Let's change the first item in the first nested item in our cloned array.

        cloneNums[0][0] = '8';

        console.log(cloneNums);
           // [ [ '8', 2 ], [ 10 ], [ 300 ] ]

        // NOOooo, the original is also affected
        console.log(nums);
          // [ [ '8', 2 ], [ 10 ], [ 300 ] ]

따라서 이러한 시나리오가 발생하지 않도록하려면

        const arr = ['1', '2', '3'];

        const cloneArr = Array.from(arr);

cloneNums[0][0]예제의 변경 사항이 변경 사항을 어떻게 전파 했는지에 대해 지적하는 것은 타당합니다. nums[0][0]그러나 nums[0][0]실제로는 cloneNums스프레드 연산자 에 의해 참조가 복사되는 객체 이기 때문 입니다. 즉,이 동작은 값 (int, string 등 리터럴)으로 복사하는 코드에는 영향을 미치지 않습니다.
Aditya MP

1

벤치 마크 시간!

function log(data) {
  document.getElementById("log").textContent += data + "\n";
}

benchmark = (() => {
  time_function = function(ms, f, num) {
    var z = 0;
    var t = new Date().getTime();
    for (z = 0;
      ((new Date().getTime() - t) < ms); z++)
      f(num);
    return (z)
  }

  function clone1(arr) {
    return arr.slice(0);
  }

  function clone2(arr) {
    return [...arr]
  }

  function clone3(arr) {
    return [].concat(arr);
  }

  Array.prototype.clone = function() {
    return this.map(e => Array.isArray(e) ? e.clone() : e);
  };

  function clone4(arr) {
    return arr.clone();
  }


  function benchmark() {
    function compare(a, b) {
      if (a[1] > b[1]) {
        return -1;
      }
      if (a[1] < b[1]) {
        return 1;
      }
      return 0;
    }

    funcs = [clone1, clone2, clone3, clone4];
    results = [];
    funcs.forEach((ff) => {
      console.log("Benchmarking: " + ff.name);
      var s = time_function(2500, ff, Array(1024));
      results.push([ff, s]);
      console.log("Score: " + s);

    })
    return results.sort(compare);
  }
  return benchmark;
})()
log("Starting benchmark...\n");
res = benchmark();

console.log("Winner: " + res[0][0].name + " !!!");
count = 1;
res.forEach((r) => {
  log((count++) + ". " + r[0].name + " score: " + Math.floor(10000 * r[1] / res[0][1]) / 100 + ((count == 2) ? "% *winner*" : "% speed of winner.") + " (" + Math.round(r[1] * 100) / 100 + ")");
});
log("\nWinner code:\n");
log(res[0][0].toString());
<textarea rows="50" cols="80" style="font-size: 16; resize:none; border: none;" id="log"></textarea>

버튼을 클릭 한 후 10 초 동안 벤치 마크가 실행됩니다.

내 결과 :

크롬 (V8 엔진) :

1. clone1 score: 100% *winner* (4110764)
2. clone3 score: 74.32% speed of winner. (3055225)
3. clone2 score: 30.75% speed of winner. (1264182)
4. clone4 score: 21.96% speed of winner. (902929)

Firefox (SpiderMonkey Engine) :

1. clone1 score: 100% *winner* (8448353)
2. clone3 score: 16.44% speed of winner. (1389241)
3. clone4 score: 5.69% speed of winner. (481162)
4. clone2 score: 2.27% speed of winner. (192433)

우승자 코드 :

function clone1(arr) {
    return arr.slice(0);
}

승자 엔진 :

스파이더 몽키 (Mozilla / Firefox)


1

JavaScript에서 배열을 순서대로 복제하는 빠른 방법 :

#1: array1copy = [...array1];

#2: array1copy = array1.slice(0);

#3: array1copy = array1.slice();

배열 객체에 직렬화 할 수없는 JSON 콘텐츠 (함수, Number.POSITIVE_INFINITY 등)가 더 나은 경우

array1copy = JSON.parse(JSON.stringify(array1))


0

이 코드를 따를 수 있습니다. 불변의 방법 배열 복제. 이것은 복제를 배열하는 완벽한 방법입니다


const array = [1, 2, 3, 4]

const newArray = [...array]
newArray.push(6)
console.log(array)
console.log(newArray)

0

ES6에서는 간단히 스프레드 구문을 활용할 수 있습니다 .

예:

let arr = ['a', 'b', 'c'];
let arr2 = [...arr];

스프레드 연산자는 완전히 새로운 배열을 생성하므로 하나를 수정해도 다른 배열에는 영향을 미치지 않습니다.

예:

arr2.push('d') // becomes ['a', 'b', 'c', 'd']
console.log(arr) // while arr retains its values ['a', 'b', 'c']
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.