실제로 손코딩 당시에는 sort 는 전혀 생각나지 않았고,
toCharArray 는 생각은 났으나, 실업무에는 안쓰는 메소드기에 철자가 틀리기 싷어 빼다보니 순수코딩의 라인이 길어지는 건 어쩔수가 없게 됩니다.
실제 넉넉한 마음으로 짜보니, 30 라인의 길이가 10라인까지 줄어들었습니다.
최종적으로 위의 줄인 로직을 기반으로 변경한 Stream API 처리로직은 다음과 같습니다.
넘어온 list 를 Stream에 담습니다. map 에 toCharArray를 담은 후 peek 로 ArrayList를 sort합니다.
다시 map으로 sort 된 값을 다시금 string으로 변환합니다.
distinct로 중복된 값을 제외한 후 count()를 샙니다.
만약 char로 정렬되지 않을 경우, count는 1이 아니게 됩니다.
// javascript 에서 리스트를 순회하는 법constlog=console.log;functionf(list,length){for(constaoflist){log(list);}}functionmain(){f([1,2,3,4,5],2)}main();
일반적으로 개발자가 작성하는 코드는 다음과 같습니다.
// 리스트에서 홀수를 length 만큼 뽑아서 제곱한 후 모두 더하기constlog=console.log;functionf(list,length){leti=0;letacc=0;for(constaoflist){if(a%2){acc=acc+a*a;if(++i==length)break;}}log(acc);}functionmain(){f([1,2,3,4,5],1);f([1,2,3,4,5],2);f([1,2,3,4,5],3);}
거의 전부를 표현했다고 볼 수 있습니다. 프로그램을 작성하는 모든 추상화되어있는 로직(기능)이 여기서 나올 수 있습니다.
if 를 사용해서 제어를 한다든지, 연산을 한다든지,
for문을 최적화 하기 위해, 시간복잡도를 좋게 하기 위해 break 를 사용한다던가,
외부에 영향을 주는 등의 log를 찍거나 하는 행위를 할 수 있습니다.
if 를 함수형 프로그래밍으로 변환
일단, 함수형 프로그래밍에서 if 같은 경우는 if를 한번만 사용할 경우 filter 라고 합니다.
위의 함수는 위의 if(a % 2) 를 특정조건일 경우 yield 를 할 수 있도록 하고, 일급함수로 받은 함수를 사용하여, 어떤 조건일 때 필터링을 할 것인지 위임하는 형태로 구성하게 됩니다.
// 리스트에서 홀수를 length 만큼 뽑아서 제곱한 후 모두 더하기constlog=console.log;function*filter(f,list){// 제네러이터 함수for(constaoflist){if(f(a))yielda;}}functionf(list,length){leti=0;letacc=0;for(constaoffilter(a=>a%2,list)){acc=acc+a*a;if(++i==length)break;}log(acc);}functionmain(){f([1,2,3,4,5],1);f([1,2,3,4,5],2);f([1,2,3,4,5],3);}main();
if문을 없애고, list 를 넣는 곳에 filter 연산에 대한 다양성을 대체할 수 있게 됩니다.
a * a 를 함수형 프로그래밍으로 변환
어떤 특정한 값이 다른 값으로 바꾸는 작업을 함수형 프로그래밍에서 map 이라는 함수를 통해 추상화되어있습니다.
function *map(f,list) {
for (const a of list) {
yield f(a);
}
}
으로 변환할 시에, 다음과 같이 변경할 수 있게 됩니다.
// 리스트에서 홀수를 length 만큼 뽑아서 제곱한 후 모두 더하기constlog=console.log;function*filter(f,list){// 제네러이터 함수for(constaoflist){if(f(a))yielda;}}function*map(f,list){for(constaoflist){yieldf(a);}}functionf(list,length){leti=0;letacc=0;for(constaofmap(a=>a*a,filter(a=>a%2,list))){acc=acc+a;if(++i==length)break;}log(acc);}functionmain(){f([1,2,3,4,5],1);f([1,2,3,4,5],2);f([1,2,3,4,5],3);}main();
++i 를 함수형 프로그래밍으로 변환
++i 는 1씩 증가하는 명령어 함수인데요. 명령어 코드는 실제 구체적으로 어떻게 할 것인지를 구술하는 명령어인데요.
실제 서술하는 서술형 함수입니다.
여담으로 순회가 가능한 값을 list 나 배열로 부르지않고 이터러블이라고 부릅니다. 좀더 추상화레벨에 높은 순회가 가능한 객체를 말합니다.
function take (length, iter) {
let res = [];
for (const a of iter) {
res.push(a);
if (res.length == length) return res;
}
return res;
}
break 문을 걸 수도 있지만, 함수형 프로그래밍은 계속 return 하는 형태가 좋습니다.
// 리스트에서 홀수를 length 만큼 뽑아서 제곱한 후 모두 더하기constlog=console.log;function*filter(f,list){// 제네러이터 함수for(constaoflist){if(f(a))yielda;}}function*map(f,list){for(constaoflist){yieldf(a);}}functiontake(length,iter){letres=[];for(constaofiter){res.push(a);if(res.length==length)returnres;}returnres;}functionf(list,length){leti=0;letacc=0;for(constaoftake(length,map(a=>a*a,filter(a=>a%2,list)))){acc=acc+a;}log(acc);}functionmain(){f([1,2,3,4,5],1);f([1,2,3,4,5],2);f([1,2,3,4,5],3);}main();
외부세상 일은 외부세상에서
함수형 프로그래밍에서 굉장히 중요한 것이 있습니다.
f([1,2,3,4,5],1);
외부에서 어떤 함수에게 메시지를 전달하여, 그 함수가 어떤 외부 세상에 영향을 끼치는 것보다, 최대한 인자와 리턴값으로 소통하고 외부세상에 영향을 끼치는 것은 외부세상에서 하도록 하는 식으로 권장합니다.
function f(list, length) {
let i = 0;
let acc = 0;
for (const a of take(length, map(a => a * a, filter(a => a % 2, list)))) {
acc = acc + a;
}
return acc;
}
function main() {
log(f([1,2,3,4,5],1));
log(f([1,2,3,4,5],2));
log(f([1,2,3,4,5],3));
}
reduce 같은 경우에 acc 를 생삭하고 f 와 iter 만 넘길 경우, 념겨진 파라미터(arguments)가 2개가 되므로,
functionreduce(f,acc,iter){if(arguments.length==2){iter=acc[Symbol.iterator]();acc=iter.next().value;// 첫번째에 있는 값을 꺼내서 넣겠다.}for(constaofiter){acc=f(acc,a);}returnacc;}
이렇게 만들면, reduce가 재귀함수처럼 사용할 수 있게 됩니다.
constgo=(a,...fs)=>reduce((a,f)=>f(a),a,fs);functionreduce(f,acc,iter){if(arguments.length==2){iter=acc[Symbol.iterator]();acc=iter.next().value;// 첫번째에 있는 값을 꺼내서 넣겠다.}for(constaofiter){acc=f(acc,a);}returnacc;}
처럼 사용가능합니다. 조금더 읽기 편하게 좌측부터 읽을 수 있도록 다음과같이 처리가 가능합니다.
filter 를 먼저 처리한 후 mapping 후, take 한 후에 reduce 를 처리해라 라고 말입니다.
해당 스크립트는 main 과 main2 로 분리하여 최종 처리하였습니다.
constlog=console.log;function*filter(f,list){// 제네러이터 함수for(constaoflist){if(f(a))yielda;}}function*map(f,list){for(constaoflist){yieldf(a);}}functiontake(length,iter){letres=[];for(constaofiter){res.push(a);if(res.length==length)returnres;}returnres;}constgo_old=(a,...fs)=>reduce((a,f)=>f(a),a,fs);// arguments 값을 변경할 수 있으면 다같이 무떵그려저 값을 전달할 수 있음.constgo=(...fs)=>reduce((a,f)=>f(a),fs);functionreduce(f,acc,iter){if(arguments.length==2){iter=acc[Symbol.iterator]();// acc 가 iter 임 (2번째값)acc=iter.next().value;// 첫번째에 있는 값을 꺼내서 넣겠다.}for(constaofiter){acc=f(acc,a);}returnacc;}constadd=(a,b)=>a+b;constf=(list,length)=>reduce((acc,a)=>acc+a,0,take(length,map(a=>a*a,filter(a=>a%2,list))));functionmain(){log(f([1,2,3,4,5],1));log(f([1,2,3,4,5],2));log(f([1,2,3,4,5],3));}main();constf2=(list,length)=>go(list,list=>filter(a=>a%2,list),list=>map(a=>a*a,list),list=>take(length,list),list=>reduce(add,list))functionmain2(){log(f2([1,2,3,4,5],1));log(f2([1,2,3,4,5],2));log(f2([1,2,3,4,5],3));}main2();
만들어진 위의 go 함수는 지연적으로 평가가 됩니다.
filter 와 map 은 *(제너레이터)로 구현이 되어있습니다.
제너레이터는 return 이 아니라 yield를 하도록 되어있는데,
yield 를 사용한다는 것은 지연적으로 평가하라는 말입니다.
var it = map(a => a+1, [1,2,3]);
it.next();
it.next 를 호출해야지만 처리가 되지 var it 으로는 아무런 실행이 되지 않습니다.
그렇기 때문에 처음 만들어진 시간복잡도가 동일하다는 것을 의미합니다.
const L = {};
filter 와 map 은 지연적으로 동작하니까, L 이라는 prefix를 달아줘서, 레이지한 함수라고 선언합닏나.
L.filter = curry(function *(f,list) { // 제네러이터 함수
for (const a of list) {
if(f(a)) yield a;
}
});
L.map = curry(function *(f,list) {
for (const a of list) {
yield f(a);
}
});
L.range = function *(stop) {
let i = -1;
while(++i < stop) yield i;
};
range 를 만들어서 range(Infinity) 로 무한대를 선언한 후에, 200개만 빼올 수 있도록 구현할 수 있습니다.
깃헙의 저장소Repository에 관한 정보를 기록하는 README.md는 깃헙을 사용하는 사람이라면 누구나 가장 먼저 접하게 되는 마크다운 문서입니다.
마크다운을 통해서 설치방법, 소스코드 설명, 이슈 등을 간단하게 기록하고 가독성을 높일 수 있다는 강점이 부각되면서 점점 여러 곳으로 퍼져가게 된되며, 현재 우리가 사용하고 있는 Atlassian 또한, 마크다운 문법을 간략하게는 지원하여, 해당 건에 대해 공유드립니다.
다만, 마크다운 문법의 경우 표준이 없으므로, github 의 마크다운 문법을 별도로 공부하시는 것이 좋으며, github, wiki, Atlassian 에서 비슷하지만 조금씩 다른 결과물, 혹은 지원하지 않는 것들이 있을 수 있습니다.
줄긋기
---
’-‘를 세번 연속으로 치면 문단선이 생깁니다.
마크다운 문법에서는 헤더나, 부제목을 만들기 위해 사용하는데, Atlassian 은 문단을 자르기 위한 용도로 사용됩니다.
(* 와 - 는 같은 값으로 인식합니다. *를 세번입력해도 상관없습니다.)
점표시
-
’-‘를 넣고 한칸띄우기(스페이스)를 누르면, 분단표시가 됩니다. 점표시로 변경하게 되며, 엔터를 키면 계속 목차가 이어지게 됩니다.
여기서 탭을 넣게 되면 dept 형태로 보여지게 됩니다.
’-‘ 하고 스페이스바를 눌렀을 경우
엔터를 치고 탭을 눌렀을 경우
(* 와 - 는 같은 값으로 인식합니다. *를 세번입력해도 상관없습니다.)
글자 크기 키우기
# This is a H1
## This is a h2
This is a H1
This is a H2
This is a H3
This is a H4
This is a H5
This is a H6
’#’을 누르고 한칸 띄우기를 하면 갯수에 따라 H1~H6 까지 자동으로 적용됩니다.
인용 문구 넣기
> 인용문구 넣기
인용문구 넣기
’>’ 를 넣고 한칸 뛰우기를 하면 됩니다. 마크다운 문법에서는 > 를 계속 넣으면서 여러 문용을 입력할 수 있는데, 여기는 단 한번만 넣을 수 있습니다.