Skip to main content

2021년 7월

2021-07-06#

합병 정렬(merge sort)#


합병 정렬(merge sort) 알고리즘 개념 요약#

  • '존 폰 노이만(John von Neumann)'이라는 사람이 제안한 방법.
  • 일반적인 방법으로 구현했을 때 이 정렬은 안정 정렬 에 속하며, 분할 정복 알고리즘의 하나 이다.
    • 분할 정복(divide and conquer) 방법
      • 문제를 작은 2개의 문제로 분리하고 각각을 해결한 다음, 결과를 모아서 원래의 문제를 해결하는 전략이다.
      • 분할 정복 방법은 대개 순환 호출을 이용하여 구현한다.
  • 과정 설명
    1. 리스트의 길이가 0 또는 1이면 이미 정렬된 것으로 본다. 그렇지 않은 경우에는
    2. 정렬되지 않은 리스트를 절반으로 잘라 비슷한 크기의 두 부분 리스트로 나눈다.
    3. 각 부분 리스트를 재귀적으로 합병 정렬을 이용해 정렬한다.
    4. 두 부분 리스트를 다시 하나의 정렬된 리스트로 합병한다.

합병 정렬(merge sort) 알고리즘의 구체적인 개념#

  • 하나의 리스트를 두 개의 균등한 크기로 분할하고 분할된 부분 리스트를 정렬한 다음, 두 개의 정렬된 부분 리스트를 합하여 전체가 정렬된 리스트가 되게 하는 방법이다.
  • 합병 정렬은 다음 단계들로 이루어진다.
    • 분할(Divide): 입력 배열을 같은 크기의 2개의 부분 배열로 분할한다.
    • 정복(Conquer): 부분 배열을 정렬한다. 부분 배열의 크기가 충분히 작지 않으면 순환 호출을 이용하여 다시 분할 정복 방법을 적용한다.
    • 결합(Combine): 정렬된 부분 배열들을 하나의 배열에 합병한다.
  • 합병 정렬의 과정
    • 추가적인 리스트가 필요하다.
    • 각 부분 배열을 정렬할 때도 합병 정렬을 순환적으로 호출하여 적용한다.
    • 합병 정렬에서 실제로 정렬이 이루어지는 시점은 2개의 리스트를 합병(merge)하는 단계이다. aas.png

합병 정렬(merge sort) 알고리즘의 예제#

  • 배열에 27, 10, 12, 20, 25, 13, 15, 22이 저장되어 있다고 가정하고 자료를 오름차순으로 정렬해 보자.
  • 2개의 정렬된 리스트를 합병(merge)하는 과정
    1. 2개의 리스트의 값들을 처음부터 하나씩 비교하여 두 개의 리스트의 값 중에서 더 작은 값을 새로운 리스트(sorted)로 옮긴다.
    2. 둘 중에서 하나가 끝날 때까지 이 과정을 되풀이한다.
    3. 만약 둘 중에서 하나의 리스트가 먼저 끝나게 되면 나머지 리스트의 값들을 전부 새로운 리스트(sorted)로 복사한다.
    4. 새로운 리스트(sorted)를 원래의 리스트(list)로 옮긴다.

2211.png

구현 코드#

function mergeSort(list, left, right) {
if (left < right) {
let mid = Math.floor((left + right) / 2);
mergeSort(list, left, mid);
mergeSort(list, mid + 1, right);
merge(list, left, mid, right);
}
}
function merge(list, left, mid, right) {
let result = [];
let i = left;
let j = mid + 1;
let index = 0;
while (i <= mid && j <= right) {
if (list[i] <= list[j]) {
result[index] = list[i];
index++;
i++;
} else {
result[index] = list[j];
index++;
j++;
}
}
while (i <= mid) {
result[index] = list[i];
index++;
i++;
}
while (j <= right) {
result[index] = list[j];
index++;
j++;
}
index--;
while (index >= 0) {
list[left + index] = result[index];
index--;
}
}

합병 정렬(merge sort) 알고리즘의 특징#

  • 장점
    • 안정적인 정렬 알고리즘
    • 자료구조를 리스트로 구성하면 링크 인덱스만 변경되므로 데이터 이동은 현저히 감소
    • 데이터의 분포에 영향을 덜 받으므로 입력된 자료들에 영향을 받지 않고 정렬되는 시간이 동일
  • 단점
    • 병합하는 과정에서 입력 자료들의 크기(n)만큼의 메모리가 추가적으로 필요
    • 만약, 자료구조를 배열로 구성할 경우 입력 자료의 크기가 크게 되면 이동횟수가 많아 낭비

합병 정렬(merge sort)의 시간복잡도#

시간 복잡도를 계산한다면

  • 분할 단계
    • 비교 연선과 이동 연산이 수행되지 않는다.
  • 합병 단계
    • 비교 횟수
    • 순환 호출의 깊이(합병 단계의 수)
      • 레코드의 개수 n이 2의 거듭제곱이라고 가정(n=2^k)했을 때, n=2^3의 경우, 2^3 -> 2^2 -> 2^1 -> 2^0 순으로 줄어들어 순환 호출의 깊이가 3임을 알 수 있다. 이것을 일반화하면 n=2^k의 경우, k(k=log₂n)임을 알 수 있다.
      • k=log2n
    • 각 합병 단계의 비교 연산
      • 크기 1인 부분 배열 2개를 합병하는 데는 최대 2번의 비교 연산이 필요하고, 부분 배열의 쌍이 4개이므로 24=8번의 비교 연산이 필요하다. 다음 단계에서는 크기 2인 부분 배열 2개를 합병하는 데 최대 4번의 비교 연산이 필요하고, 부분 배열의 쌍이 2개이므로 42=8번의 비교 연산이 필요하다. 마지막 단계에서는 크기 4인 부분 배열 2개를 합병하는 데는 최대 8번의 비교 연산이 필요하고, 부분 배열의 쌍이 1개이므로 8*1=8번의 비교 연산이 필요하다. 이것을 일반화하면 하나의 합병 단계에서는 최대 n번의 비교 연산을 수행함을 알 수 있다.
      • 최대 n번
    • 순환 호출의 싶이 만큼의 합병 단계 * 각 합병 단계의 비교 연산 = nlog2n
  • 이동 횟수
    • 순환 호출의 깊이(합병 단계의 수)
      • k=log2n
    • 각 합병 단계의 이동 연산
      • 임시 배열에 복사했다가 다시 가져와야 되므로 이동 연산은 총 부분 배열에 들어 있는 요소의 개수가 n인 경우, 레코드의 이동이 2n번 발생한다.
    • 순환 호출의 싶이 만큼의 합병 단계 * 각 합병 단계의 이동연산 = 2nlog2n
  • T(n) = nlog₂n(비교) + 2nlog₂n(이동) = 3nlog₂n = O(nlog₂n)

정렬 알고리즘 시간 복잡도 비교#

image.png

출처:https://gmlwjd9405.github.io/2018/05/08/algorithm-merge-sort.html

2021-07-13#

퀵 정렬(quick sort)#


퀵 정렬(quick sort) 알고리즘의 개념 요약#

  • ‘찰스 앤터니 리처드 호어(Charles Antony Richard Hoare)’가 개발한 정렬 알고리즘
  • 퀵 정렬은 불안정 정렬에 속하며, 다른 원소와의 비교만으로 정렬을 수행하는 비교 정렬에 속한다.
  • 분할 정복 알고리즘의 하나로, 평균적으로 매우 빠른 수행 속도를 자랑하는 정렬 방법
    • 합병 정렬(merge sort)과 달리 퀵 정렬은 리스트를 비균등하게 분할한다.
  • 분할 정복(divide and conquer) 방법
    • 문제를 작은 2개의 문제로 분리하고 각각을 해결한 다음, 결과를 모아서 원래의 문제를 해결하는 전략이다.
    • 분할 정복 방법은 대개 순환 호출을 이용하여 구현한다.
  • 과정 설명
    1. 리스트 안에 있는 한 요소를 선택한다. 이렇게 고른 원소를 피벗(pivot)이라고 한다.
    2. 피벗을 기준으로 피벗보다 작은 요소들은 모두 피벗의 왼쪽으로 옮겨지고 피벗보다 큰 요소들은 모두 피벗의 오른쪽으로 옮겨진다. (피벗을 중심으로 왼쪽: 피벗보다 작은 요소들, 오른쪽: 피벗보다 큰 요소들)
    3. 피벗을 제외한 왼쪽 리스트와 오른쪽 리스트를 다시 정렬한다.
      • 분할된 부분 리스트에 대하여 순환 호출 을 이용하여 정렬을 반복한다.
      • 부분 리스트에서도 다시 피벗을 정하고 피벗을 기준으로 2개의 부분 리스트로 나누는 과정을 반복한다.
    4. 부분 리스트들이 더 이상 분할이 불가능할 때까지 반복한다. - 리스트의 크기가 0이나 1이 될 때까지 반복한다. d1.png

퀵 정렬(quick sort) 알고리즘의 예제#

  • 배열에 5,3,8,4,9,1,6,2,7 이 저장되어 있다고 가정하고 자료를 오름차순으로 정렬해 보자.
  • 퀵 정렬에서 피벗을 기준으로 두개의 리스트로 나누는 과정(partition 함수의 내용)

1.png 2.png

  • 피벗 값을 입력 리스트의 첫 번째 데이터로 하자. (다른 임의의 값이어도 상관없다.)
  • 2개의 인덱스 변수(low, high)를 이용해서 리스트를 두개의 부분 리스트로 나눈다.
  • 1회전: 피벗이 5인 경우,
    1. low는 왼쪽에서 오른쪽으로 탐색해가다가 피벗보다 큰 데이터(8)을 찾으면 멈춘다.
    2. high는 오른쪽에서 왼쪽으로 탐색해가다가 피벗보다 작은 데이터(2)를 찾으면 멈춘다.
    3. low와 high가 가리키는 두 데이터를 서로 교환한다.
    4. 이 탐색-교환 과정은 low와 high가 엇갈릴 때까지 반복한다.
  • 2회전: 피벗(1회전의 왼쪽 부분리스트의 첫 번째 데이터)이 1인 경우,
    • 위와 동일한 방법으로 반복한다.
  • 3회전: 피벗(1회전의 오른쪽 부분리스트의 첫 번째 데이터)이 9인 경우,
    • 위와 동일한 방법으로 반복한다.

퀵 정렬(quick sort) JavaScript 코드#

function partition(list, start, end) {
let pivot = list[start];
let low = start;
let high = end + 1;
let temp;
do {
/* list[low]가 피벗보다 작으면 계속 low를 증가 */
do {
low++; // low는 start+1 에서 시작
} while (low <= end && list[low] < pivot);
/* list[high]가 피벗보다 크면 계속 high를 감소 */
do {
high--; //high는 end 에서 시작
} while (high >= start && list[high] > pivot);
// 만약 low와 high가 교차하지 않았으면 list[low]를 list[high] 교환
if (low < high) {
temp = list[low];
list[low] = list[high];
list[high] = temp;
}
} while (low < high);
temp = list[high];
list[high] = list[start];
list[start] = temp;
return high;
}
function quickSort(list, start, end) {
if (start < end) {
let index = partition(list, start, end);
quickSort(list, start, index - 1);
quickSort(list, index + 1, end);
}
return;
}

퀵 정렬(quick sort) 알고리즘의 특징#

  • 장점

    1. 속도가 빠르다.

      • 시간 복잡도가 O(nlog2n)를 가지는 다른 정렬 알고리즘과 비교했을 때도 가장 빠르다.
    2. 추가 메모리 공간을 필요로 하지 않는다.

      • 퀵 정렬은O(log n)만큼의 메모리를 필요로 한다.
  • 단점

    1. 정렬된 리스트에 대해서는 퀵 정렬의 불균형 분할에 의해 오히려 수행시간이 더 많이 걸린다.
  • 퀵 정렬의 불균형 분할을 방지하기 위하여 피벗을 선택할 때 더욱 리스트를 균등하게 분할할 수 있는 데이터를 선택한다.

    • ex) 리스트 내의 몇 개의 데이터 중에서 크기순으로 중간 값(medium)을 피벗으로 선택한다.

퀵 정렬(quick sort)의 시간복잡도#

  • 평균T(n) = O(nlog2n)
  • 시간 복잡도가 O(nlog2n)를 가지는 다른 정렬 알고리즘과 비교했을 때도 가장 빠르다.
  • 퀵 정렬이 불필요한 데이터의 이동을 줄이고 먼거리의 데이터를 교환할 뿐만 아니라, 한 번 결정된 피벗들이 추후 연산에서 제외되는 특성때문이다.

정렬 알고리즘 시간복잡도 비교#

image.png

출처:https://gmlwjd9405.github.io/2018/05/10/algorithm-quick-sort.html

2021-07-20#

힙 정렬(heap sort)#


힙 정렬(heap sort) 알고리즘의 개념 요약#

  • 최대 힙(max heap) 트리나 최소 힙(min heap) 트리를 구성해 정렬을 하는 방법
  • 내림차순 정렬을 위해서는 최대 힙을 구성하고 오름차순 정렬을 위해서는 최소 힙을 구성하면 된다.
  • 과정 설명
    1. 정렬해야 할 n개의 요소들로 최대 힙(완전 이진 트리 형태)을 만든다.
    2. 그 다음으로 한 번에 하나씩 요소(루트 노드의 값)를 힙에서 꺼내서 배열의 뒤부터 저장하면 된다.
    3. 삭제되는 요소들(최대값부터 삭제)은 값이 감소되는 순서로 정렬되게 된다.

자료구조 힙(heap)#

  • 완전 이진 트리의 일종으로 우선순위 큐를 위하여 만들어진 자료구조이다.
  • 여러 개의 값들 중에서 최대값이나 최솟값을 빠르게 찾아내도록 만들어진 자료구조이다.
  • 힙은 일종의 반정렬 상태(느슨한 정렬 상태) 를 유지한다.
    • 큰 값이 상위 레벨에 있고 작은 값이 하위 레벨에 있다는 정도
    • 간단히 말하면 부모 노드의 키 값이 작식 노드의 키 값보다 항상 큰(작은) 이진 트리를 말한다.
  • 힙 트리에서는 중복된 값을 허용한다. (이진 탐색트리에서는 중복된 값을 허용하지 않는다.)

힙(heap)의 종류#

  • 최대 힙(max heap)
    • 부모 노드의 키 값이 자식 노드의 키 값보다 크거나 같은 완전 이진 트리
    • key(부모 노드) >= key(자식 노드)
  • 최소 힙(min heap)
    • 부모 노드의 키 값이 자식 노드의 키 값보다 작거나 같은 완전 이진 트리
    • key(부모 노드) <= key(자식 노드) image.png

힙(heap)의 구현#

  • 힙을 저장하는 표준적인 자료구조는 배열 이다.
  • 특정 위치의 노드 번호는 새로운 노드가 추가되어도 변하지 않는다.
    • 예를 들어 루트 노드의 오른쪽 노드의 번호는 항상 3이다.
  • 힙에서의 부모 노드와 자식 노드의 관계
    • 왼쪽 자식의 인덱스 = (부모의 인덱스)*2
    • 오른쪽 자식의 인덱스 = (부모의 인덱스)*2 + 1
    • 부모의 인덱스 = (자식의 인덱스) / 2 image.png

힙(heap)의 삽입#

  1. 힙에 새로운 요소가 들어오면, 일단 새로운 노드를 힙의 마지막 노드에 이어서 삽입한다.
  2. 새로운 노드를 부모 노드들과 교환해서 힙의 성질을 만족시킨다.
  • 아래의 최대 힙(max heap)에 새로운 요소 8를 삽입해보자. image.png
/* 현재 요소의 개수가 heap_size인 힙 h에 item을 삽입한다. */
// 최대 힙(max heap) 삽입 함수
void insert_max_heap(HeapType *h, element item){
int i;
i = ++(h->heap_size); // 힙 크기를 하나 증가
/* 트리를 거슬러 올라가면서 부모 노드와 비교하는 과정 */
// i가 루트 노트(index: 1)이 아니고, 삽입할 item의 값이 i의 부모 노드(index: i/2)보다 크면
while((i != 1) && (item.key > h->heap[i/2].key)){
// i번째 노드와 부모 노드를 교환환다.
h->heap[i] = h->heap[i/2];
// 한 레벨 위로 올라단다.
i /= 2;
}
h->heap[i] = item; // 새로운 노드를 삽입
}

출처: https://gmlwjd9405.github.io/2018/05/10/algorithm-heap-sort.html

힙(heap)의 삭제#

  1. 최대 힙에서 최대값은 루트 노드이므로 루트 노드가 삭제된다.
  • 최대 힙(max heap)에서 삭제 연산은 최대값을 가진 요소를 삭제하는 것이다.
  1. 삭제된 루트 노드에는 힙의 마지막 노드를 가져온다.
  2. 힙을 재구성한다.
  • 아래의 최대 힙(max heap)에서 최대값을 삭제해보자. image.png
// 최대 힙(max heap) 삭제 함수
element delete_max_heap(HeapType *h){
int parent, child;
element item, temp;
item = h->heap[1]; // 루트 노드 값을 반환하기 위해 item에 할당
temp = h->heap[(h->heap_size)--]; // 마지막 노드를 temp에 할당하고 힙 크기를 하나 감소
parent = 1;
child = 2;
while(child <= h->heap_size){
// 현재 노드의 자식 노드 중 더 큰 자식 노드를 찾는다. (루트 노드의 왼쪽 자식 노드(index: 2)부터 비교 시작)
if( (child < h->heap_size) && ((h->heap[child].key) < h->heap[child+1].key) ){
child++;
}
// 더 큰 자식 노드보다 마지막 노드가 크면, while문 중지
if( temp.key >= h->heap[child].key ){
break;
}
// 더 큰 자식 노드보다 마지막 노드가 작으면, 부모 노드와 더 큰 자식 노드를 교환
h->heap[parent] = h->heap[child];
// 한 단계 아래로 이동
parent = child;
child *= 2;
}
// 마지막 노드를 재구성한 위치에 삽입
h->heap[parent] = temp;
// 최댓값(루트 노드 값)을 반환
return item;
}

출처: https://gmlwjd9405.github.io/2018/05/10/algorithm-heap-sort.html

힙 정렬(heap sort) 알고리즘의 c언어 코드#

// 우선순위 큐인 힙을 이용한 정렬
void heap_sort(element a[], int n){
int i;
HeapType h;
init(&h);
for(i=0; i<n; i++){
insert_max_heap(&h, a[i]);
}
for(i=(n-1); i>=0; i--){
a[i] = delete_max_heap(&h);
}
}

출처: https://gmlwjd9405.github.io/2018/05/10/algorithm-heap-sort.html

힙 정렬(heap sort) 알고리즘의 특징#

  • 장점
    1. 시간 복잡도가 좋은편
    2. 힙 정렬이 가장 유용한 경우는 전체 자료를 정렬하는 것이 아니라 가장 큰 값 몇개만 필요할 때 이다.

힙 정렬(heap sort)의 시간복잡도#

  • 힙 트리의 전체 높이가 거의 log2n(완전 이진 트리이므로)이므로 하나의 요소를 힙에 삽입하거나 삭제할 때 힙을 재정비하는 시간이 log2n만큼 소요된다.
  • 요소의 개수가n개 이므로 전체적으로 O(nlog2n)의 시간이 걸린다.
  • T(n) = O(nlog2n)

정렬 알고리즘 시간복잡도 비교#

1.png

출처:https://gmlwjd9405.github.io/2018/05/10/algorithm-heap-sort.html

2021-07-27#

함수 생성 방식#


함수 선언(function declaration)#

  • function 키워드, 함수 이름, 괄호로 둘러싼 매개변수를 차례로 써주면 함수를 선언할 수 있습니다. 위 함수에는 매개변수가 없는데, 만약 매개변수가 여러 개 있다면 각 매개변수를 콤마로 구분해 줍니다. 이어서 함수를 구성하는 코드의 모임인 '함수 본문(body)'을 중괄호로 감싸 붙여줍시다.
  • 함수 선언문이라고 부르기도 한다.
function functionName(parameters) {
// 함수 본문
}
function sayHi() {
console.log('Hello!');
}

함수 표현식(Function Expression)#

  • 함수를 생성하고 변수에 값을 할당하는 것처럼 하는 방식
// 함수 선언문
let sayHi = function () {
console.log('Hello!');
};

함수 표현식 vs 함수 선언문#

  1. 문법
  • 함수 선언문: 함수는 주요 코드 흐름 중간에 독자적인 구문 형태로 존재합니다.
// 함수 선언문
function sum(a, b) {
return a + b;
}
  • 함수 표현식: 함수는 표현식이나 구문 구성(syntax construct) 내부에 생성된다.
// 함수 표현식
let sum = function (a, b) {
return a + b;
};
  1. 함수 생성 시점
  • 함수 표현식: 실제 실행 흐름이 해당 함수에 도달했을 때 함수를 생성합니다. 따라서 실행 흐름이 함수에 도달했을 때부터 함수를 사용할 수 있습니다.
sayHi('John'); // error!
let sayHi = function (name) {
alert(`Hello, ${name}`);
};
  • 함수 선언문: 함수 선언문이 정의되기 전에도 호출할 수 있습니다.
sayHi('John'); // Hello, John
function sayHi(name) {
alert(`Hello, ${name}`);
}
  1. 스코프
  • 함수 선언문: 엄격 모드에서 함수 선언문이 코드 블록 내에 위치하면 해당 함수는 블록 내 어디서든 접근할 수 있지만 블록 밖에서는 함수에 접근하지 못한다.
'use strict';
let age = 16; // 16을 저장했다 가정합시다.
if (age < 18) {
welcome(); // (실행)
function welcome() {
alert('안녕!'); // 함수 선언문은 함수가 선언된 블록 내 어디에서든 유효합니다
}
welcome(); // (실행)
} else {
function welcome() {
alert('안녕하세요!');
}
}
// 여기는 중괄호 밖이기 때문에
// 중괄호 안에서 선언한 함수 선언문은 호출할 수 없습니다.
welcome(); // Error: welcome is not defined
  • 함수 표현식: 코드 블록 밖에서도 함수에 접근할 수 있다.
'use strict';
let age = prompt('나이를 알려주세요.', 18);
let welcome;
if (age < 18) {
welcome = function () {
alert('안녕!');
};
} else {
welcome = function () {
alert('안녕하세요!');
};
}
welcome(); // 제대로 동작합니다.

화살표 함수(arrow function)#

  • 함수 표현식을 보다 단순하고 간결하게 만드는 문법
let func = (arg1, arg2, ...argN) => expression;

이렇게 코드를 작성하면 인자 arg1..argN를 받는 함수 func이 만들어집니다. 함수 func는 화살표(=>) 우측의 표현식(expression)을 평가하고, 평가 결과를 반환합니다.

아래 함수의 축약 버전이라고 할 수 있죠.

let func = function (arg1, arg2, ...argN) {
return expression;
};
  • 구체적인 예시
// 함수 선언문
function sum(a, b) {
return a + b;
}
// 함수 표현식
let sum = function (a, b) {
return a + b;
};
// 화살표 함수
let sum = (a, b) => a + b;
  • 인수가 하나밖에 없다면 인수를 감싸는 괄호를 생략할 수 있다.
let double = function (n) {
return n * 2;
};
// let double = n => x * 2;
  • 인수가 하나도 없을 경우
let sayHi = () => alert('안녕하세요!');
sayHi();
  • 본문이 여러 줄인 화살표 함수
let sum = (a, b) => {
// 중괄호는 본문 여러 줄로 구성되어 있음을 알려줍니다.
let result = a + b;
return result; // 중괄호를 사용했다면, return 지시자로 결괏값을 반환해 주어야 합니다.
};
alert(sum(1, 2)); // 3

기명 함수 표현식(Named Function Expression, NFE)#

  • 이름이 있는 함수 표현식을 나타내는 용어
  • 이름을 사용해서 함수 표현식 내부에서 자기 자신을 참조할 수 있다.
  • 기명 함수 표현식 외부에선 그 이름을 사용할 수 없다.
let sayHi = function func(who) {
alert(`Hello, ${who}`);
};
let sayHi = function func(who) {
if (who) {
alert(`Hello, ${who}`);
} else {
func('Guest'); // func를 사용해서 자신을 호출합니다.
}
};
sayHi(); // Hello, Guest
// 하지만 아래와 같이 func를 호출하는 건 불가능합니다.
func(); // Error, func is not defined (기명 함수 표현식 밖에서는 그 이름에 접근할 수 없습니다.)
  • 기명 함수를 사용하지 않고 내부에서 호출할 경우 외부 코드에 의해 변경될 수 있다는 문제가 생긴다.
let sayHi = function (who) {
if (who) {
alert(`Hello, ${who}`);
} else {
sayHi('Guest'); // TypeError: sayHi is not a function
}
};
let welcome = sayHi;
sayHi = null;
welcome(); // 중첩 sayHi 호출은 더 이상 불가능합니다!
let sayHi = function func(who) {
if (who) {
alert(`Hello, ${who}`);
} else {
func('Guest'); // 원하는 값이 제대로 출력됩니다.
}
};
let welcome = sayHi;
sayHi = null;
welcome(); // Hello, Guest (중첩 호출이 제대로 동작함)

new Function 문법#

  • new Function 문법을 사용하면 함수를 만들 수 있다.
let func = new Function([arg1, arg2, ...argN], functionBody);

새로 만들어지는 함수는 인수 arg1...argN과 함수 본문 functionBody로 구성된다.

let sum = new Function('a', 'b', 'return a + b');
console.log(sum(1, 2)); // 3
  • 인수가 없고 본문만 있는 함수
let sayHi = new Function('console.log("Hello")');
sayHi(); // "Hello"

보너스#

function sayHi() {
console.log('Hello');
}
let func = sayHi;
let func2 = sayHi();
console.log(typeof sayHi);
console.log(typeof func);
console.log(typeof func2);
sayHi = 1;
console.log(typeof sayHi);

https://ko.javascript.info/