과제 내용(각자 카드에 복사해 주세용!)
필수 과제 - 8월 18일까지 코드 리뷰
- 연습 문제 중 아래 문제들을 함수형/이터러블 프로그래밍을 응용하여 풀어주세요
- 해시 > 완주하지못한선수
- 스택/큐 > 기능개발
- 완전탐색 > 모의고사
추가 과제 - 코드 리뷰 선택
https://prudhvignv.github.io/pathFinderVisualizer/ 구현해보기 (~8/14)
학생 CheckList
리아
채점 기준
공통
- 함수형으로 잘 구현했는가.
- 올바른 결과가 나오는가.
- 예외 처리가 잘 되었는가.
- 만약 과제를 풀지 못했다면, 해답지를 보고 잘 이해했는가? (내 지식으로 만들었는가)
과제 답과 풀이
제가 풀어본 답과 풀이를 적어보았어요. 이건 제 방식일 뿐이에요. 이터러블 프로그래밍을 이용하여 이보다 더 간결하고 멋지게 문제를 푸시는 분도 분명히 계실꺼에요. 제가 보여드리고 싶은 기법과 아이디어를 전달하고자 해보았어요. 그럼 재밌게 봐주세요. :)
완주하지 못한 선수
const solution = (participant, completion) => failerName(count(participant), count(completion)); const failerName = (participant, completion) => go( participant, entries, find(([name, count]) => (completion[name] || 0) < count), head );
우선 저는 동명이인을 주목했어요. 참여자 배열
["mislav", "stanko", "mislav", "ana"]
와 완주자 배열 ["stanko", "ana", "mislav"]
와의 차이를 비교하기 쉬운 값으로 만들어서 시작하면 난이도가 훨씬 줄어들거라고 생각했어요.이터러블 프로그래밍(LISP)의 전략은 하나의 함수를 통과시킬때마다 점점 문제를 해결하기 쉬운 값들로 만들어가는 전략을 취해요.
그래서
count(["mislav", "stanko", "mislav", "ana"])
함수를 통과시켜 각각 다음과 같은 값으로 바꾸었어요. 그럼 failerName
이 받는 인자가 각각 participant 는 { mislav: 2, stanko: 1, ana: 1 }
, completion 은 { stanko: 1, ana: 1, mislav: 1 }
이 되어요.만약 애초에 문제가 처음부터 베열이 아닌 위와 같이 집계된 객체를 준다고 했다면 문제가 더 쉬운 문제가 되었겠지요? 저 역시 어려운 문제는 풀기 어렵기 때문에 일단 문제를 좀 더 쉽게 만들고 시작한거에요.
그럼 그 다음은
enriens
로 지연 평가되는 이터레이터를 만들었어요. 그럼 find
쪽에서 소비하는 만큼만 평가가 이루어지기 때문에 참여자가 아무리 많아도 효율성 테스트를 통과할 수 있게 됩니다.entries
를 통과하면 원래는 { mislav: 2, ... }
처럼 생겼던 값들이 [["mislav", 2], ...]
처럼 바뀌겠지요. 물론 앞서 말한 지연 평가 되는 형태이구요. 참여자 이름별로 하나씩 숫자를 비교해보면서 완주자의 카운트가 적은 이름을 찾을 때까지 find
를 하다가 처음 만나면 ["mislav", 2]
를 리턴하며 더이상 entries
의 소비도 멈추게 되어요.head(["mislav", 2])
는 "mislav"
가 되고 solution
은 끝나게 됩니다!기능 개발
const featureCount = periods => reduce(([counts, total_period], period) => total_period < period ? [inc(counts, counts.length), period] : [inc(counts, counts.length-1), total_period])([[], 0], periods); const solution = (progresses, speeds) => go( zip(progresses)(speeds), map(([progress, speed]) => Math.ceil((100 - progress) / speed)), featureCount, head );
zip(progresses)(speeds)
은 [[progresses[0], speeds[0]], [progresses[1], speeds[1]], ...]
와 같이 만들어요. 역시 마찬가지에요. 애초에 progress와 speed가 쌍이 맞춰서 들어왔다면 문제가 더 쉽겠죠. i 를 증가시키면서 for 를 돌면서 쌍을 직접 만들어 줄 필요가 없으니까요.그 다음 map 함수로 이미 쌍이 맞춰진 progress와 speed 들을 각각 문제에 나온대로 계산을 해요. 그럼 작업일들이 되겠죠. progresses, speeds 두 개 인자로부터 작업일만 있는 배열로 바뀌었으니 이후 문제는 더 쉬워지겠죠?
이제 이전까지 해결한 부분은 모두 잊고 문제의 시작이 작업일 배열이라고 생각하는거죠. 그럼 이전 로직에서 벗어날 수 있어요. 명령형으로 작성하면 이전 로직들이 계속 여기저기 코드에 따라다니는 형태가 될텐데, 함수형은 파이프라인으로 딱딱 문제를 끊어서 해결해나가니까, 문제를 점점 쉽게 만드는 매력이 있어요.
featureCount
는 작업일 배열을 받아 문제에 맞춰 적합하게 배포일을 만들어나가요. 이건 직접 콘솔을 찍어가며 어떻게 돌아가는지 테스트해보면 알 수 있을거에요.그런 다음 head 로 배포일 배열을 뽑고 끝.
모의고사
const students = [ { name: 1, pattern: [1, 2, 3, 4, 5] }, { name: 2, pattern: [2, 1, 2, 3, 2, 4, 2, 5] }, { name: 3, pattern: [3, 3, 1, 1, 2, 2, 4, 4, 5, 5] } ]; const last = arr => arr[arr.length-1]; function* repeat(a) { while (true) yield a; } const randomAnswers = (pattern, length) => go( pattern, repeat, flat, take(length) ); const scoring = answers => ({name, pattern}) => ({ name, score: go( randomAnswers(pattern, answers.length), zip(answers), countBy(([a, b]) => a === b ? 'o' : 'x'), counted => counted.o || 0 ) }); const solution = answers => go( students, map(scoring(answers)), groupBy(({score}) => score), Object.values, last, map(({name}) => name), _ => [..._] );
여기서는 조금 어려운 기법들을 넣어보았어요. scoring 함수는 answers를 받아 함수를 리턴하는 함수에요. 리턴된 함수는 map과 사용되죠. 이런 기법들에 익숙해지면 코드의 흐름을 더 잘 제어하고 응용할 수 있어요.
scoring 함수는 randomAnswers 가 핵심이고, 새로운 기법들로 만들어졌고, 나머지는 이전 문제들에 사용된 기법의 응용이에요.
randomAnswers에 대해서 주로 해설을 할께요.
randomAnswers의 pattern 인자는 students에 만들어둔 pattern 이에요. 그 패턴을 배열 통째로 무한히 반복하는 repeat 함수를 통과시켜서 [[1, 2, 3, 4, 5], [1, 2, 3, 4, 5], [1, 2, 3, 4, 5], ...] 처럼 무한히 반복되도록 만들었어요. 그런 다음 flat 함수를 통과시키면 [1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, ...] 로 펼쳐져서 무한히 반복되게 되어요.
이제 학생별로 문제의 갯수가 몇개이든지간에 무한히 답을 찍을 수 있게 되었어요. 그 상태에서 시험 문제의 갯수 length 만큼만 take로 가져오면 딱 필요한 만큼만 답을 찍게 되어요.
그렇게 찍어내는 답들과 정답인 answers를 zip 하면 역시 정답과 찍은 값이 쌍으로 나오겠지요?
그 다음은 그 두 값이 동일하면 'o' 아니면 'x'로 만들어서 count를 샌 다음 .o 로 카운트를 구하는거죠.
count 대신 filter로 정답인 배열을 필터링 한 후에 .length 를 가져와도 같은 값을 가져올 수 있지만 엄청나게 큰 배열이 만들어질 수 있기 때문에 countBy로 새는 것이 더 효율적이겠지요?
그 이후는 하나씩 콘솔에 찍어보면 어떤 의도를 가졌는지 알 수 있을거에요. 아 한 가지 자바스크립트의 특징을 이용한게 있는데 groupBy를 이용해서 객체의 키가 숫자인 경우 숫자가 클 수록 키가 뒤로 간다는 특징을 이용했어요.
제 강의를 들어주셔서 정말 감사드리고 수고 많으셨습니다. 남은 기간도 화이팅이에요!