[알고리즘]Array HOF 연습 문제

jaemin·2020년 10월 22일
0

알고리즘

목록 보기
6/7
post-thumbnail

Array HOF 연습

1. html 생성

아래 배열을 사용하여 html을 생성하는 함수를 작성하라.

const todos = [
  { id: 3, content: 'HTML', completed: false },
  { id: 2, content: 'CSS', completed: true },
  { id: 1, content: 'Javascript', completed: false }
];

function render() {
  let html = '';

  todos.forEach(todo => {

  });

  return html;
}

console.log(render());
<li id="3">
  <label><input type="checkbox">HTML</label>
</li>
<li id="2">
  <label><input type="checkbox" checked>CSS</label>
</li>
<li id="1">
  <label><input type="checkbox">Javascript</label>
</li>

위의 js코드를 기반으로 아래의 html 코드를 만들어 보아라.

풀이 과정 설계

<li>, <label>같은 텍스트는 동적으로 변하는 부분이 아니므로 템플릿 문자열 ``을 사용해서 표현할 수 있다.
변하지 않는 부분들만 적어보면 다음과 같다.

`<li id="">
  <label><input type="checkbox"></label>
</li>`

반복문 혹은 고차 함수를 사용하면 되므로 세 번 적어줄 필요는 없다.

그렇다면, html 코드에서 동적으로 변하는 부분은
1. <li>태그의 id 값
2. <labe>태그 사이의 텍스트들(HTML, CSS, JS)
3. 배열 todos에서 completed 프로퍼티 값이 true일 떄 checked 되는 부분이다.

1과 2에 해당하는 것들은 템플릿 리터럴에서 표현식을 삽입하는 방법인 ${ }을 사용할 수 있다. (템플릿 리터럴을 사용하지 않으면 ''과 + 로 연결해야 하므로 가독성이 떨어진다.) 표현식을 삽입해서 1.과 2.를 해결하자.

`<li id="${todo.id}">
  <label><input type="checkbox">${todo.content}</label>
</li>`

js 코드로 옮겨보자.

const todos = [
  { id: 3, content: 'HTML', completed: false },
  { id: 2, content: 'CSS', completed: true },
  { id: 1, content: 'Javascript', completed: false }
];

function render() {
  let html = '';

  todos.forEach(todo => {
    html += 
    `<li id="${todo.id}">
      <label><input type="checkbox">${todo.content}</label>
    </li>`
  });

  return html;
}

console.log(render());

디스트럭처링할당을 이용하면 다음과 같이 쓸 수도 있다.

todos.forEach(todo => {
    const {id, content, completed} = todo;
    html += 
    `<li id="${id}">
      <label><input type="checkbox">${content}</label>
    </li>`
  });

마지막으로 3.을 해결하려면 조건문을 사용해야 한다. completed 프로퍼티 값을 검사해서 true이면 checked를, 아니라면 빈 문자열이 나와야 한다. if문이 제일 먼저 떠오르지만 if문은 표현식이 아닌문이므로 템플릿 리터럴 내부에 사용할 수 없다. if문은 삼항 조건 연산자로 대체할 수 있다.

삽입하려는 삼항 조건식은 다음과 같다.

todo.completed === true ? 'checked' : ''

템플릿 리터럴로 집어넣자.

  todos.forEach(todo => {
    html += 
    `<li id="${todo.id}">
      <label><input type="checkbox" ${todo.completed === true ? 'checked' : ''}>${todo.content}</label>
    </li>`
  });

템플릿 리터럴 내부에서 평가돼야 하는 표현식은 ${}으로 감싸므로 위와 같다.

2. 특정 프로퍼티 값 추출

요소의 프로퍼티(id, content, completed)를 문자열 인수로 전달하면 todos의 각 요소 중, 해당 프로퍼티의 값만을 추출한 배열을 반환하는 함수를 작성하라.

단, for 문이나 Array#forEach는 사용하지 않도록 하자.

const todos = [
  { id: 3, content: 'HTML', completed: false },
  { id: 2, content: 'CSS', completed: true },
  { id: 1, content: 'Javascript', completed: false }
];

function getValues(key) {

}

console.log(getValues('id')); // [3, 2, 1]
console.log(getValues('content')); // ['HTML', 'CSS', 'Javascript']
console.log(getValues('completed')); // [false, true, false]

풀이 과정 설계

todos 배열의 요소의 특정 프로퍼티 키의 값을 뽑아내야 한다. todos 배열을 반복해서 돌아야 하는데 for문과 forEach문은 사용하지 않아야 한다. 고차 함수는 모두 내부적으로 반복문을 실행하므로 고차 함수로 대체할 수 있다. 대상 배열 todos의 요소 개수와 결과물 [3, 2, 1], ['HTML', 'CSS', 'Javascript']의 요소 개수가 같으므로 map()을 사용할 수 있을 것 같다.

풀이 과정

function getValues(key) {
  return todos.map(todo => todo[key]);
}

여기서 주의할 점은 todo['key']라고 접근하면 안된다는 것이다. todo['key']라고 접근한다면 todo 객체의 프로퍼티 키가 key인 것을 찾아 프로퍼티 값을 가져오겠다는 말이 된다. 반면 todo[key]는 대괄호 안의 key를 평가해서 나온 결과를 검사하겠다는 뜻이 된다. key를 평가한 결과란, 'id', 'content', 'completed'가 된다.

참고로 조건에서 forEach를 금지하지 않았다면 forEach로도 풀 수 있을까?
forEach를 사용해서 할 수도 있다.

function getValues(key) {
  let res = [];
  todos.forEach(todo => {res.push(todo[key])});
  return res;
}

res 라는 변수를 만들고 todos의 요소를 순회하면서 res에 push를 한 후 반환하면 된다. 가능한 방법이지만 forEach가 return 할 수 없어 새로운 변수를 만들어야 하는 번거로움이 있다.

답 :

const todos = [
  { id: 3, content: 'HTML', completed: false },
  { id: 2, content: 'CSS', completed: true },
  { id: 1, content: 'Javascript', completed: false }
];

function getValues(key) {
  return todos.map(todo => todo[key]);
}

console.log(getValues('id')); // [3, 2, 1]
console.log(getValues('content')); // ['HTML', 'CSS', 'Javascript']
console.log(getValues('completed')); // [false, true, false]

3. 프로퍼티 정렬

요소의 프로퍼티(id, content, completed)를 문자열 인수로 전달하면 todos의 요소를 정렬하는 함수를 작성하라.

단, todos는 변경되지 않도록 하자.

참고: Array.prototype.sort

const todos = [
  { id: 3, content: 'HTML', completed: false },
  { id: 2, content: 'CSS', completed: true },
  { id: 1, content: 'Javascript', completed: false }
];

function sortBy(key) {

}

console.log(sortBy('id'));
/*
[
  { id: 1, content: 'Javascript', completed: false },
  { id: 2, content: 'CSS', completed: true },
  { id: 3, content: 'HTML', completed: false }
]
*/
console.log(sortBy('content'));
/*
[
  { id: 2, content: 'CSS', completed: true },
  { id: 3, content: 'HTML', completed: false },
  { id: 1, content: 'Javascript', completed: false }
]
*/
console.log(sortBy('completed'));
/*
[
  { id: 3, content: 'HTML', completed: false },
  { id: 1, content: 'Javascript', completed: false },
  { id: 2, content: 'CSS', completed: true }
]
*/

풀이 과정 설계

함수 getValues에 전달되는 key에 따라서 정렬해야 한다. key에 들어오는 프로퍼티 키의 값이 숫자라면 숫자 정렬을, 문자라면 문자 정렬을 해야한다.
그런데, sort()는 유니코드를 기준으로 정렬하기 때문에 숫자는 정렬할 수 없다. 또, sort((a, b) => a - b)는 숫자를 정렬할 수 있지만 문자끼리는 뺄 수 없으므로 정렬할 수 없다. sort()에는 빼기만 할 수 있는 것이 아니라 부등호 비교도 할 수 있다. 부등호 비교를 이용하자.

풀이 과정

sort()는 내부적으로 결과값이 -1이면 정렬 순서를 그대로 유지하고 1이면 둘의 순서를 바꾼다. 0인 경우는 두 비교값이 같을 경우인데, 이때도 물론 정렬을 그대로 유지한다.
if문, 혹은 삼항 조건 연산자를 이용할 수 있다. if문은 식이 길어지고 바로 return할 수 없기 때문에 삼항 조건 연산자를 이용해 보자.

function sortBy(key) {
  return todos.sort((a, b) => a[key] < b[key] ? -1 : a[key] > b[key] ? 1 : 0);
}

답 :

const todos = [
  { id: 3, content: 'HTML', completed: false },
  { id: 2, content: 'CSS', completed: true },
  { id: 1, content: 'Javascript', completed: false }
];

function sortBy(key) {
  return todos.sort((a, b) => a[key] < b[key] ? -1 : (a[key] > b[key] ? 1 : 0))
}

console.log(sortBy('id'));
/*
[
  { id: 1, content: 'Javascript', completed: false },
  { id: 2, content: 'CSS', completed: true },
  { id: 3, content: 'HTML', completed: false }
]
*/
console.log(sortBy('content'));
/*
[
  { id: 2, content: 'CSS', completed: true },
  { id: 3, content: 'HTML', completed: false },
  { id: 1, content: 'Javascript', completed: false }
]
*/
console.log(sortBy('completed'));
/*
[
  { id: 3, content: 'HTML', completed: false },
  { id: 1, content: 'Javascript', completed: false },
  { id: 2, content: 'CSS', completed: true }
]
*/

4. 새로운 요소 추가

새로운 요소(예를 들어 { id: 4, content: 'Test', completed: false })를 인수로 전달하면 todos의 선두에 새로운 요소를 추가하는 함수를 작성하라. 단, Array#push는 사용하지 않도록 하자.

let todos = [
  { id: 3, content: 'HTML', completed: false },
  { id: 2, content: 'CSS', completed: true },
  { id: 1, content: 'Javascript', completed: false }
];

function addTodo(newTodo) {

}

addTodo({ id: 4, content: 'Test', completed: false });

console.log(todos);
/*
[
  { id: 4, content: 'Test', completed: false },
  { id: 3, content: 'HTML', completed: false },
  { id: 2, content: 'CSS', completed: true },
  { id: 1, content: 'Javascript', completed: false }
]
*/

풀이 과정 설계

선두에 요소를 추가하는 방법은 두 가지가 있다. Array.prototype.unshift와 스프레드 문법을 사용하는 방법이 있다.

둘의 차이는, unshift는 원본 배열을 변경하고 스프레드 문법은 원본 배열을 손상시키지 않는다.

이 문제는 todos를 직접 변경하는 것이므로 두 가지 모두 사용할 수 있다.

풀이 과정

  1. Unshift()
let todos = [
  { id: 3, content: 'HTML', completed: false },
  { id: 2, content: 'CSS', completed: true },
  { id: 1, content: 'Javascript', completed: false }
];

function addTodo(newTodo) {
  return todos.unshift(newTodo);
}

addTodo({ id: 4, content: 'Test', completed: false });

console.log(todos);
  1. 스프레드 문법
let todos = [
  { id: 3, content: 'HTML', completed: false },
  { id: 2, content: 'CSS', completed: true },
  { id: 1, content: 'Javascript', completed: false }
];

function addTodo(newTodo) {
  todos = [newTodo, ...todos];
  return todos
}

addTodo({ id: 4, content: 'Test', completed: false });

console.log(todos);

5. 특정 요소 삭제

todos에서 삭제할 요소의 id를 인수로 전달하면 해당 요소를 삭제하는 함수를 작성하라.

let todos = [
  { id: 3, content: 'HTML', completed: false },
  { id: 2, content: 'CSS', completed: true },
  { id: 1, content: 'Javascript', completed: false }
];

function removeTodo(id) {

}

removeTodo(2);

console.log(todos);
/*
[
  { id: 3, content: 'HTML', completed: false },
  { id: 1, content: 'Javascript', completed: false }
]
*/

풀이 과정 설계

todos 배열의 요소 개수는 3개, 결과 todos의 배열 요소 개수는 2개로 줄어들었다. 결과물의 요소 개수가 줄어들었다면 filter()를 생각해볼 필요가 있다.

filter()를 사용해서 요소의 id값이 인수값과 다르다면 새로운 배열에 담도록 하자. 문제 마지막에 console.log(todos)이므로 filter한 결과를 다시 todos에 할당해야 함을 잊지말자.

풀이 과정

function removeTodo(id) {
  todos = todos.filter(todo => todo.id !== id);
  return todos;
}

여기서 주의해야할 부분은 todo.id이다. todo[id]인지, todo['id'] 인지, todo.id인지 헷갈렸다.

todo.idtodo['id']는 같은 말이다. todo 객체의 "id"라는 프로퍼티키를 바라보겠다는 뜻이다.

반면 todo[id]는 평가된 id를 받겠다는 뜻이다. 문제에서 id에 2라는 인수를 줬는데 todo[id]라고 적게되면 todo['2']라는 말과 동치이다. 주의하도록 하자.

답 :

let todos = [
  { id: 3, content: 'HTML', completed: false },
  { id: 2, content: 'CSS', completed: true },
  { id: 1, content: 'Javascript', completed: false }
];

function removeTodo(id) {
  todos = todos.filter(todo => todo.id !== id);
  return todos;
}

removeTodo(2);

console.log(todos);

6. 특정 요소의 프로퍼티 값 반전

todos에서 대상 요소의 id를 인수로 전달하면 해당 요소의 completed 프로퍼티 값을 반전하는 함수를 작성하라.

hint) 기존 객체의 특정 프로퍼티를 변경/추가하여 새로운 객체를 생성하려면 Object.assign 또는 스프레드 문법을 사용한다.

let todos = [
  { id: 3, content: 'HTML', completed: false },
  { id: 2, content: 'CSS', completed: true },
  { id: 1, content: 'Javascript', completed: false }
];

function toggleCompletedById(id) {

}

toggleCompletedById(2);

console.log(todos);
/*
[
  { id: 3, content: 'HTML', completed: false },
  { id: 2, content: 'CSS', completed: false },
  { id: 1, content: 'Javascript', completed: false }
]
*/

풀이 과정 설계

todos 배열 요소 3개, 결과 todos 배열 요소 3개이므로 map()을 떠올릴 수 있다.

todos의 각 객체를 순회하면서 id값이 인수로 준 값과 일치하다면 completed의 값과 반대되는 값을 할당하면 된다.

풀이 과정

답 :

let todos = [
  { id: 3, content: 'HTML', completed: false },
  { id: 2, content: 'CSS', completed: true },
  { id: 1, content: 'Javascript', completed: false }
];

function toggleCompletedById(id) {
  todos.map(todo => todo.id === id ? todo.completed = !todo.completed : todo.completed);
}

toggleCompletedById(2);

console.log(todos);

삼항 조건 연산자에서 다음과 같이 빈 문자열이나 0을 넣어도 되지만 가독성이 떨어진다고 생각해 todo.completed를 넣었다.

todos.map(todo => todo.id === id ? todo.completed = !todo.completed : '');
todos.map(todo => todo.id === id ? todo.completed = !todo.completed : 0);

7. 모든 요소의 completed 프로퍼티 값을 true로 설정

todos의 모든 요소의 completed 프로퍼티 값을 true로 설정하는 함수를 작성하라.

hint) 기존 객체의 특정 프로퍼티를 변경/추가하여 새로운 객체를 생성하려면 Object.assign 또는 스프레드 문법을 사용한다.

let todos = [
  { id: 3, content: 'HTML', completed: false },
  { id: 2, content: 'CSS', completed: true },
  { id: 1, content: 'Javascript', completed: false }
];

function toggleCompletedAll() {

}

toggleCompletedAll();

console.log(todos);
/*
[
  { id: 3, content: 'HTML', completed: true },
  { id: 2, content: 'CSS', completed: true },
  { id: 1, content: 'Javascript', completed: true }
]
*/

풀이 과정 설계

이 문제는 위의 문제와 같은 문제이다.

위에서는 id가 인수로 받은 값과 같다면 completed를 반전하는 문제였다면, 이 문제는 completed가 false라면 completed를 반전시켜주면 된다.

풀이 과정

답 :

let todos = [
  { id: 3, content: 'HTML', completed: false },
  { id: 2, content: 'CSS', completed: true },
  { id: 1, content: 'Javascript', completed: false }
];

function toggleCompletedAll() {
  todos.map(todo => todo.completed === false ? todo.completed = true : todo.completed)
}

toggleCompletedAll();

console.log(todos);

8. completed 프로퍼티의 값이 true인 요소의 갯수 구하기

todos에서 완료(completed: true)한 할일의 갯수를 구하는 함수를 작성하라.

단, for 문, Array#forEach는 사용하지 않도록 하자.

let todos = [
  { id: 3, content: 'HTML', completed: false },
  { id: 2, content: 'CSS', completed: true },
  { id: 1, content: 'Javascript', completed: false }
];

function countCompletedTodos() {

}

console.log(countCompletedTodos()); // 1

풀이 과정 설계

이 문제는 두 가지로 생각할 수 있다.

첫 번째, todos의 배열 요소 개수가 3개, 결과의 개수는 배열이 아닌 하나의 수이다. 이때 reduce를 떠올릴 수 있다.

두 번째, filter로 completed 값이 true인 것을 뽑아낸 후, filter가 결과로 반환한 배열의 length를 구하면 된다.

두 가지 방법으로 풀어보자.

풀이 과정

  1. reduce() 사용
let todos = [
  { id: 3, content: 'HTML', completed: false },
  { id: 2, content: 'CSS', completed: true },
  { id: 1, content: 'Javascript', completed: false }
];

function countCompletedTodos() {
  return todos.reduce((acc, todo) => todo.completed === true ? ++acc : acc, 0);
}

console.log(countCompletedTodos()); 

초기값은 0으로 주면 acc는 0부터 시작한다. todo.completed가 true이면 acc에 1씩 더해준다.

  1. filter() 사용
let todos = [
  { id: 3, content: 'HTML', completed: false },
  { id: 2, content: 'CSS', completed: true },
  { id: 1, content: 'Javascript', completed: false }
];

function countCompletedTodos() {
  return todos.filter(todo => todo.completed === true).length;
}

console.log(countCompletedTodos()); // 1

두 가지 모두 사용할 수 있지만 reduce는 코드를 해석하기 어려우므로 다른 고차 함수를 사용하는 것이 더 좋다.

9. id 프로퍼티의 값 중에서 최대값 구하기

todos의 id 프로퍼티의 값 중에서 최대값을 구하는 함수를 작성하라.

단, for 문, Array#forEach는 사용하지 않도록 하자.

let todos = [
  { id: 3, content: 'HTML', completed: false },
  { id: 2, content: 'CSS', completed: true },
  { id: 1, content: 'Javascript', completed: false }
];

function getMaxId() {

}

console.log(getMaxId()); // 3

풀이 과정 설계

이 문제도 위와 같이 두 가지 방법이 있다.

reduce()를 사용하는 방법과 map()으로 id 프로퍼티 값만 추출해서 Math.max()로 최대값을 구할 수 있다.

풀이 과정

let todos = [
  { id: 3, content: 'HTML', completed: false },
  { id: 2, content: 'CSS', completed: true },
  { id: 1, content: 'Javascript', completed: false }
];

function getMaxId() {
  return Math.max(...todos.map(todo => todo.id));
}

console.log(getMaxId()); // 3

Math.max()는 배열이 값으로 들어오면 최대값을 계산하지 못하므로 스프레드 문법으로 풀어줘야 한다.

여기서 한단계 더 나아가서 만약 todos가 빈 배열일 경우, 0이나 빈 배열을 반환하는게 아니라 -Infinity를 반환한다. 이에 대한 방어 코드를 짜야한다.

function getMaxId() {
  return todos.length ? Math.max(...todos.map(todo => todo.id)) : 0;
}

todos.length가 truthy값이면 최대값 계산하고 falsy값이면 0을 반환한다. 0은 falsy값이므로 빈 배열일때 0이 나온다.

profile
프론트엔드 개발자가 되기 위해 공부 중입니다.

0개의 댓글