본문 바로가기

Web/JavaScript

함수형 자바스크립트 8. 그룹핑(group_by, count_by), 실무적인 예제

_group_by, _push

_push: Object[key] 에 들어있는 배열에 요소를 추가한다. 만약 배열이 비어있는 상태라면 빈 배열에 요소를 추가한다.

_group_by: 배열에서 객체들의 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

28

29

function _push( obj, key, val ) {

   (obj[key] = obj[key] || []).push(val);

   return obj;

}

var _group_by = _curryr(function(data, iter) {

   return _reduce(data, function(grouped, val) {

       return _push( grouped, iter(val), val );

   }, {});

});

_go(users,

   _group_by(_get('age')),

   console.log

);

_go(users,

   _group_by(function(user) {

       return user.age - user.age % 10;

   }),

   console.log

);

_go(users,

   _group_by(function(user) {

       return user.name[0];

   }),

   console.log

);

Colored by Color Scripter

cs


_count_by, _inc

_inc: object[key]의 카운트를 1만큼 증가시킨다. 만약 해당 요소가 비어있다면 1을 대입한다.

_count_by: 배열에서 객체들의 key를 기준으로 분류하여 요소들의 갯수를 구한다.


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

var _inc = function( count, key ) {

   count[key] ? count[key]++ : count[key] = 1;

   return count;

}

var _count_by = _curryr(function( data, iter ) {

   return _reduce(data, function(count, val) {

       return _inc( count, iter(val) );

   }, {});

});

console.log(

   _count_by(users, function(user) {

       return user.age - user.age % 10;

   })

);

_go(users,

   _count_by(function(user) {

       return user.name[0];

   }),

   console.log

);

Colored by Color Scripter

cs


자바 Stream API가 등장하기 전, 그룹화와 같은 기능을 구현하려면 코드가 꽤나 길었던 것으로 기억한다.

먼저, Map을 준비하고 반복자를 사용하여 반복문을 돌면서 key값으로 map에 요소들을 List로 연결시켜야 추가해야 했다.


1

2

3

4

5

6

7

8

9

10

11

12

Map<String, List<클래스>> map = new HashMap<>();

Iterator<클래스> iter = 리스트.iterator();

while ( iter.hasNext() ) {

   클래스 obj = iter.next();

   if( map.containsKey(키) ) {

       map.get(키).add(obj);    

   } else {

       List<클래스> list = new ArrayList<>();

       list.add = obj;

       map.put(키, obj);

   }

}

Colored by Color Scripter

cs

대충 이런 모양이였던 것 같다.

하지만, Stream API가 등장하면서 앞서 구현한 _group_by 와 같은 함수의 사용이 가능해졌다.



1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

//3 apple, 2 banana, others 1

List<String> items = Arrays.asList("apple", "apple", "banana",

                    "apple", "orange", "banana", "papaya");

Map<String, Long> result = items.stream().collect(

                               Collectors.groupingBy(

                                   Function.identity(), Collectors.counting()

                               )

                           );

System.out.println(result);

/*

{

   papaya=1, orange=1, banana=2, apple=3

}

*/

Colored by Color Scripter

cs


함수형 사고방식과 표현력을 공부하면 보다 코드가 정말 간결해진다.

코드를 작은 함수 단위로 분리하여 함수의 연속 실행을 통해서 프로그램을 완성해나가는 것이 함수형 프로그래밍이라고 한다.


다형성과 안정성이 높은 함수들의 조합으로 프로그램을 만들어 나아가기 때문에

작은 함수 단위의 테스트가 훨씬 쉬워지고, 해당 로직이 잘 돌아갈 것이라는 확신을 가지기 쉽다고 한다.

명시적 코드의 모습이 줄어들어 마치 소설을 읽는 듯한 느낌을 받을 수도 있다.


실무적인 예제

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

/* 실무적인 조합 예제 */

var _pairs = _map((val, key) => [key, val]);

console.log( _pairs(users[0]) );

console.clear();

// document.write 할 때에는 document가 this로 되어있어야 동작한다.

var f1 = _pipe(

   _count_by(function(user) { return user.age - user.age % 10; }),

   _map((count, key) => `<li>${key}대는 ${count}명 입니다.</li>`),

   list => '<ul>' + list.join('') + '</ul>',

   document.write.bind(document) // 무조건 document가 bind되어있도록 고정

);

f1(users);

_go(users, _reject(user => user.age < 20), f1);

_go(users, _filter(user => user.age < 20), f1);

cs


_pipe 함수를 통해 f1에 만들어지는 함수를 살펴보자.

세대별로 유저들을 분류하여 카운트를 계산하고,

세대와 카운트를 받아 <li>...</li> 문자열을 만들고,

다시 해당 문자열들을 배열로 받아 한줄로 만들어 <ul>...</ul> 문자열을 만들고,

HTML DOCUMENT에 <UL> element를 추가하는 함수를 만든다.


f1의 실행을 미루어두고, 거르기를 통해서 20대 이상을 제외하거나, 20대 이상만 포함하면서 파이프라인을 실행할 수도 있다.

실제로 경험한 SI 업무에서, 서버에서 데이터를 받아와 특정 데이터를 제거하거나 그룹화한다.

그룹화되거나 수집된 데이터를 이용해 표, 차트, 디테일을 그리는게 대부분의 업무였다.

마찬가지로 같은 자바스크립트로 구현했지만, 코드가 굉장히 복잡했었던 것으로 기억한다.


코드가 간결하고 읽기 좋다고 성능까지 취할 수 있는 것은 아니지만,

코드를 보는 사람이 눈살을 찌푸리지 않고 읽어 내려갈 수 있는 코드가 좋은 코드라고 생각한다.

그런면에서 위와 같이 글을 읽는 듯한 코드가 내가 지향하는 코딩 스타일인 것 같다.