본문 바로가기

Web/JavaScript

함수형 자바스크립트 10. 실전코드조각과 효율 높이기

함수형 자바스크립트 9일차

함수형 프로그래밍에서 실전에 쓰일만한 예제와 효율성을 높이는 방법에 대한 내용이다.


보통 사이트에서 보일법한 게시판에 대한 예제를 살펴봤다.

users라는 사용자 데이터가 있고, 각각의 user들이 작성한 posts 게시글과, comments 댓글의 목록이 존재한다.


이러한 데이트를 고차함수와 보조함수를 이용해서 문제를 해결해 나아간다.

문제를 해결 한 뒤에는 반복을 줄여서 효율성을 높이는 방법에 대한 예제를 공부했다.


효율성

코드 내용은 추후에 적도록 하고, 우선 효율성을 높이는 방법에 대한 내용이다.

map, find 등의 고차함수를 이용해 데이터를 걸러내거나, 수집할 때,

users를 순회하고 다시 내부에서 posts를 순회하고, 다시 comments를 순회하곤 한다.

특정인의 게시글을 거를 때 사용자를 순회하면서 user_id가 포함된 게시글을 거르고,

댓글을 수집할 때는 모든 댓글을 순회하면서 post_id가 포함되는지 검사한다.



1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

function find_user_by_id(user_id) {

   return _.find(users, function(user) {

       return user.id == user_id;

   });

}

function find_comments_by_post_id(post_id) {

   return _.find(comments, function(comment) {

       return comment.post_id == post_id;

   });

}

var comments2 = _.go(

   comments,

   _.map(function(comment) {

       return _.extend({

           user: find_user_by_id(comment.user_id)

       }, comment);

   }),

   _.group_by('post_id'));

var posts2 = _.map(posts, function(post) {

       return _.extend({

           comments: find_comments_by_post_id(post.id),

           user: find_user_by_id(post.user_id)

       }, post);

   });

Colored by Color Scripter

cs

users를 순회하면서 user_id로 사용자를 찾는 함수와 comments를 순회하면서 post_id로 댓글목록을 찾는 함수를 만들었다.

다음으로, 댓글에는 댓글을 작성한 사용자의 정보를 넣고, 게시글에는 게시글을 작성한 사용자의 정보와 해당 게시글에 작성된 댓글들의 정보를 넣으려고 한다.


13번째 줄을 먼저 살펴보면, 전체 댓글에 사용자 정보를 추가해야 하므로 comments의 개수만큼 반복한다. (loop 1)

loop 1의 내부에서, 반복되는 요소인 댓글에 속해있는 user_id를 이용해 사용자 정보를 조회해야 하므로 다시 users의 개수만큼 반복한다.(loop 2)

loop 1에서 반복되는 요소인 comment와 loop 2에서 검색되는 user를 합치기 위하여 _.extend를 사용한다.

여기서 반복은 comments의 개수 * users의 개수가 된다.


22번째 줄을 살펴보면, 전체 개시글에 사용자 정보와 댓글 목록 정보를 추가해야 하므로 posts의 개수만큼 반복한다. (loop 1)

loop 1의 내부에서, 반복되는 요소인 게시글에 속해있는 user_id와 post_id를 이용해 사용자 정보와 댓글 목록을 조회해야 하므로 다시 users의 개수(loop 2), comments의 개수(loop 3)만큼 반복한다.

loop 1에서 반복되는 요소인 post, loop 2에서 검색되는 user, loop 3에서 검색되는 comment를 합치기 위하여 _.extend를 사용한다.

여기서 반복은 post의 개수 * ( users의 개수 + comments의 개수)가 된다.


이러한 반복은 각각의 요소 전체를 돌면서 거르거나 수집해야 하기 때문에, 효율이 떨어진다.


이런 경우 key 첨자를 이용하면 한 번에 찾을 수 있다는 것을 이용하여 인덱싱을 하면서 반복을 미리 해두면 성능을 높일 수 있다.



1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

/* index_by 해당하는 배열을 새로운 형태로 바꾸는 함수, 어떠한 키를 가지고 인덱싱을 해놓을 것이냐 */

/* 원래는 순회하면서 찾아야 하는 것을 key값을 안다면 한번에 찾을 수 있다. users[101] */

/* user_id로 인덱싱을 한 users */

var users2 = _.index_by(users, 'id');

console.log(users2);

/* 마찬가지로 반복을 미리 해두면 효율이 높아짐 */

/* post_id로 그룹핑을 한 comments - group_by */

var comments2 = _.go(

   comments,

   _.map(function(comment) {

       return _.extend({

           user: users2[comment.user_id]

       }, comment);

   }),

   _.group_by('post_id'));

console.log( comments2 );

var posts2 = _.go(

   posts,

   _.map(function(post) {

       return _.extend({

           comments: comments2[post.id] || [],

           user: users2[post.user_id] || []

       }, post);

   })

);

Colored by Color Scripter

cs

users를 id로 인덱싱을 하면  사용자를 검색할 때, 반복을 이용하지 않고 users[id]와 같이 배열첨자를 이용하여 접근할 수 있다.

이를 이용해 댓글 객체에 사용자 정보를 추가할 때, comments만큼 반복하지만, user를 검색할 때는 id를 첨자로 사용하므로 반복은 comments의 개수만큼으로 끝난다.

여기서 만들어진 객체는 추후 검색을 편하게 하기 위해 post_id로 인덱싱을 한다. (group_by)

마찬가지로 게시글 객체에 사용자 정보와 댓글 목록을 추가할 때, posts만큼 반복하지만, user와 comment를 검색할 때는 user_id와 post_id를 첨자로 사용하므로 반복은 post의 개수만큼으로 끝난다.


문제점

위와 같이 인덱싱을 이용하면 반복 횟수가 줄어 성능이 증가한다.

이제 댓글에는 사용자 정보가, 게시글에는 사용자 정보와 댓글 목록에 해당하는 정보가 들어있게 되었다.

이런 상황에서 사용자 정보에 자신이 작성한 게시글 정보를 추가하고 싶다.


1

2

3

4

_.each(users2, function(user) {

   user.posts = _.filter(posts2, function(post) {

       return post.user_id == user.id;

   });

Colored by Color Scripter

cs

자바에서 하던 방식처럼 기존의 객체를 변경하는 방식이다.

users2의 개수와 posts2의 개수만큼 반복하면서 사용자 각각의 게시글들을 찾아 posts라는 속성에 저장한다.

users2를 변경한 뒤, 확인을 해보면 posts 정보가 잘 들어있다.

해당 posts를 조회해보면 comments와 user 정보 또한 잘 들어있다.

하지만 다시 user를 조회하면 같은 사용자가 조회되고, 다시 같은 posts가 등장한다.

이런식으로 기존의 데이터를 변경하는 방식은 이러한 위험성을 내포한다.


나는 홈페이지에서 게시글을 클릭하여 해당 게시글의 댓글과 작성자 정보를 조회할 때,

외래키를 이용하여 DB에서 가져왔기 때문에, 위와 같은 재귀문제에 대해서는 생각해보지 못했다.

DB에서 조회된 데이터는 자바를 거쳐 JSON으로 브라우저에서 전송되어 화면에 출력된다.

그러나 만약 위의 예제와 같이 데이터를 조작하여 재귀적인 문제를 품게되면, 해당 객체는 JSON 으로 변환할 수 없게 된다. 때문에, 함수형 프로그래밍에서는 기존 객체를 수정하지 않고 새로운 객체를 만들어 반환하는 방식으로 프로그래밍하라고 한다.


문제점 해결과 실전 예제

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

var users3 = _.map(users2, function(user) {

   return _.extend({

       posts: posts3[user.id] || []

   }, user);

});

console.log(users3);

/* 5.1. 특정인의 posts의 모든 comments 거르기 */

/* flatten - 중첩 배열을 풀어내는 함수 */

/* deep_pluck plcuk를 하면서 flatten 까지 되는 함수*/

console.clear();

var user = users3[0];

_.go(user.posts,

   _.pluck('comments'),

   _.flatten,

   console.log);

console.log(_.deep_pluck(user, 'posts.comments'));

/* 5.2. 특정인의 posts에 comments를 단 친구의 이름들 뽑기 */

_.go(user.posts,

   _.pluck('comments'),

   _.flatten,

   _.pluck('user'),

   _.pluck('name'),

   _.uniq,

   console.log);

_.go(user, _.deep_pluck('posts.comments.user.name'), _.uniq, console.log);

/* 5.3. 특정인의 posts에 comments를 단 친구의 카운트 정보 */

_.go(user.posts,

   _.pluck('comments'),

   _.flatten,

   _.pluck('user'),

   _.pluck('name'),

   _.count_by,

   console.log);

_.go(user, _.deep_pluck('posts.comments.user.name'), _.count_by, console.log);

/* 5.3. 특정인이 comments를 단 posts 거르기 */

console.log(

   _.filter(posts2, function(post) {

       return _.find(post.comments, function(comment) {

           return comment.user_id == 105;

       });

   })

);

Colored by Color Scripter

cs


users3이라는 새로운 객체를 만들어 반환하면 users2를 수정하는 것과 같이 재귀적인 문제가 해결된다.

다음으로 11번 라인부터는 실전 예제와 관련된 코드이다.

  1. 특정인의 posts의 모든 comments를 조회하기

    1. 특정 user의 posts를 가져온다.

    2. 해당 posts의 comments를 수집하며 중첩 배열을 풀어낸다.

  2. 특정인의 posts에 comments를 단 친구의 이름을 수집하기

    1. 특정 user의 posts를 가져온다.

    2. 해당 posts의 comments를 수집한다.

    3. 해당 comments의 user를 수집한다.

    4. 해당 user의 name을 수집하며 중복을 제거한다.

  3. 특정인의 posts에 comments를 단 친구의 카운트 정보

    1. 특정 user의 posts를 가져온다.

    2. 해당 posts의 comments를 수집한다.

    3. 해당 comments의 user를 수집한다.

    4. 해당 user의 name을 수집하며 카운트를 계산한다.

  4. 특정인이 comments를 단 posts 조회하기

    1. posts2의 개수만큼 반복하여 post의 요소를 가져온다.

    2. 해당 post의 comments만큼 반복하면서 comment의 요소를 가져온다.

    3. 해당 comment의 특정 사용자의 식별자인 user_id 속성을 검사하여 검색한다.