[JS] 자바스크립트 함수형 프로그래밍(인프런) 정리 (STEP 50)

컬렉션 중심 프로그래밍

image

STEP 50

자바스크립트로 알아보는 함수형 프로그래밍 - 인프런 강의 강의를 보고 정리한 내용입니다.

image

컬렉션 중심 프로그래밍

컬렉션 중심 프로그래밍은 돌림직한 데이터들을 다루는 것이다. 아래 4가지 유형이 있으며 각 유형에서 bold 표시된 대표함수(map, filter, find, reduce)들로 나머지 특화 함수들을 만들 수 있다.

  • 수집하기 - map, values, pluck
  • 거르기 - filter, reject, compact, without
  • 찾아내기 - find, find_index, some, every
  • 접기(집계, 축약) - reduce, min_by, max_by, group_by, count_by

들어가기전, 준비되어있어야하는 코드

자바스크립트 함수형 프로그래밍 유인동 강의 깃허브 소스코드, STEP 49 참고

// curry
function _curry(fn) {
  return function (a, b) {
    return arguments.length === 2
      ? fn(a, b)
      : function (b) {
          return fn(a, b);
        };
  };
}

// curryr
function _curryr(fn) {
  return function (a, b) {
    return arguments.length === 2
      ? fn(a, b)
      : function (b) {
          return fn(b, a);
        };
  };
}

// _get
var _get = _curryr(function (obj, key) {
  return obj == null ? undefined : obj[key];
});

function _filter(list, predi) {
  const new_list = [];
  _each(list, function (val) {
    if (predi(val)) {
      new_list.push(val);
    }
  });
  return new_list;
}

function _map(list, mapper) {
  const new_list = [];
  _each(list, function (val) {
    new_list.push(mapper(val));
  });
  return new_list;
}

function _is_object(obj) {
  return typeof obj === "object" && !!obj;
}
function _keys(obj) {
  return _is_object(obj) ? Object.keys(obj) : [];
}

var _length = _get("length");
function _each(list, iter) {
  var keys = _keys(list);
  for (let i = 0, len = keys.length; i < len; i++) {
    iter(list[keys[i]]);
  }
  return list;
}

var _map = _curryr(_map),
  _filter = _curryr(_filter),
  _each = _curryr(_each);

var slice = Array.prototype.slice;

function _rest(list, num) {
  return slice.call(list, num || 1);
}

function _reduce(list, iter, memo) {
  if (arguments.length === 2) {
    memo = list[0];
    list = _rest(list);
  }

  _each(list, function (val) {
    memo = iter(memo, val);
  });

  return memo;
}

function _pipe() {
  var fns = arguments;

  return function (arg) {
    return _reduce(
      fns,
      function (arg, fn) {
        return fn(arg);
      },
      arg
    );
  };
}

function _go(arg) {
  var fns = _rest(arguments);
  return _pipe.apply(null, fns)(arg);
}

1. 수집하기 - map, values, pluck

저번 문서에서 만들었던 _map을 사용해 values, pluck을 만들어보자.

STEP 49에서 썼던 users 배열을 사용할 것이다.

const users = [
  { id: 1, name: "ID", age: 36 },
  { id: 2, name: "BJ", age: 32 },
  { id: 3, name: "JM", age: 32 },
  { id: 4, name: "PJ", age: 27 },
  { id: 5, name: "HA", age: 25 },
  { id: 6, name: "JE", age: 26 },
  { id: 7, name: "JI", age: 31 },
  { id: 8, name: "MP", age: 23 },
];

1-1) values

받은 value을 value으로 리턴하는 함수. 배열에 사용할 때는 인풋과 아웃풋이 같기 때문에 크게 의미가 없지만 key:value 형태의 객체에 사용할 때 유의미해진다. _values는 object의 keyvaluevalue만 뽑아준다.(Object.values와 동일한 기능을 하는 함수)

// 1-1) values
// function _values(data) {
//   return _map(data, _identity);
// }

// _curryr을 사용한 _map을 생각해보면, 위 _values 함수를 아래와 같이 간단하게 쓸 수도 있다.
var _values = _map(_identity);

function _identity(val) {
  return val;
}
// 💡 _identity 함수가 _values보다 아래있는데도 동작하는 이유는 호이스팅 때문(함수 선언문의 경우 함수 전체가 호이스팅됨)

console.log(users[0]); // {id: 1, name: "ID", age: 36}
console.log(_keys(users[0])); // ["id", "name", "age"] // key만 꺼낸 배열
console.log(_values(users[0])); // [1, "ID", 36] // value만 꺼낸 배열 ⭐

_identity 함수는 받은 값을 그대로 리턴하는 함수이다. 이 예제에서는 _values안의 _map의 인자인 mapper에 _identity를 넣어 유용하게 사용했다.

1-2) pluck

다음과 같은 결과를 내는 함수. 돌림직한 데이터를 돌면서 주어진 key에 해당하는 값들을 배열로 리턴하는게 목적이다. 예를 들어 다음과 같이 동작하게 하는게 목적인 함수이다.

_pluck(users, "age"); // [33, 22, 11, ...] // age가 담긴 배열이 리턴됨
// 1-2) pluck
// function _pluck(data, key) {
//   return _map(data, function (obj) {
//     return obj[key];
//   });
// }

// 만들었던 _get 적용
function _pluck(data, key) {
  return _map(data, _get(key));
}

console.log(_pluck(users, "age")); // [36, 32, 32, 27, 25, 26, 31, 23]

2. 거르기 - filter, reject, compact, without

filter는 예를 들어 true로 평가된 값들을 꺼내는(거르는) 작업을 한다.

2-1) reject

reject는 filter와 반대로 동작한다. true로 평가된 값들을 제외시키는 함수이다.

filter를 이용해서 users 중에서 나이가 30세 이상인 사람만 꺼내는 코드와 30세 미만인 사람만 꺼내는 코드를 작성해보자.

console.log(
  _filter(users, function (user) {
    return user.age > 30;
  })
);

_reject는 다음과 같이 _filter와 동일한 인자를 줘도 반대로 동작하도록 구현해야한다. _reject 함수를 작성해보자. _filter를 이용하면 된다.

// 2-1) reject

// _negate 적용하기 전 _reject
// function _reject(data, predi) {
//   return _filter(data, function (val) {
//     return !predi(val);
//   });
// }

// _negate는 함수를 리턴한다.
function _negate(func) {
  return function (val) {
    return !func(val);
  };
}

var _reject = _curryr(function _reject(data, predi) {
  return _filter(data, _negate(predi));
});

console.log(
  _reject(users, function (user) {
    return user.age > 30;
  })
); // 위의 _filter 코드와 인자를 똑같이 작성했을 때, reject는 user.age > 30 평가식이 true가 나오는 값들을 제외시켜야 한다.
// users중 age가 30 이하인 user들만 걸러진다.

2-2) compact

compact 함수는 truthy한 값만 남기는 함수이다. 예를 들어 다음과 같은 결과를 내는 함수이다.

console.log(_compact([1, 2, 0, false, null, {}])); // [1,2,{}]
// 2-2) compact

var _compact = _filter(_identity);
// 이렇게하면 _filter의 인자인 predi에 _identity가 전해지게 된다. _filter 함수의 if (predi(val)) 부분에서 predi(val)가 1, 2, 0, false 등 각각으로 대치되며 실행될 것이다.(그럼 true인지 false인지 판단될 것임)

console.log(_compact([1, 2, 0, false, null, {}])); // [1,2,{}]

3. 찾아내기 - find, find_index, some, every

3-1) find 만들기

find는 찾고자하는 값을 만나면 그 값을 리턴하는 함수이다. filter는 조건에 맞는 값들을 모두 뽑아 리턴하지만 find는 조건에 맞는 값을 처음 만나면 그 값 하나를 리턴한다.

// 3-1) find 만들기
// _find함수는 _each 함수의 코드를 조금만 변형하면 쉽게 만들 수 있다.
function _find(list, predi) {
  var keys = _keys(list);
  for (let i = 0, len = keys.length; i < len; i++) {
    var val = list[keys[i]];
    if (predi(val)) return val;
  }
  // 조건에 맞는 값을 찾지 못하면 undefined를 리턴한다. (자바스크립트의 일반 함수나 메서드에서 리턴문을 작성하지 않을 경우 undefined를 리턴한다.)
}

// users를 돌다가 30세 미만인 사람을 처음으로 만날 때 그 한 명만을 리턴한다.
console.log(
  _find(users, function (user) {
    return user.age < 30;
  })
); // {id: 4, name: "PJ", age: 27}

console.log(
  _find(users, function (user) {
    return user.id == 20;
  })
); // undefined

3-2) find_index

find_index는 찾고자 하는 값을 만나면 그 값의 인덱스를 리턴하는 함수이다.

// 3-2) find_index
function _find_index(list, predi) {
  var keys = _keys(list);
  for (let i = 0, len = keys.length; i < len; i++) {
    if (predi(list[keys[i]])) return i;
  }
  return -1; // 찾지 못했을 경우 -1을 리턴하도록 작성
}

console.log(
  _find_index(users, function (user) {
    return user.name == "HA";
  })
); // 4

_get, _go 여러가지를 적용해 _find, _find_index 사용하기

console.log(
  _get(
    _find(users, function (user) {
      return user.id == 1;
    }),
    "name"
  )
); // "ID"

var _find = _curryr(function _find(list, predi) {
  var keys = _keys(list);
  for (let i = 0, len = keys.length; i < len; i++) {
    var val = list[keys[i]];
    if (predi(val)) return val;
  }
});

var _find_index = _curryr(function _find_index(list, predi) {
  var keys = _keys(list);
  for (let i = 0, len = keys.length; i < len; i++) {
    if (predi(list[keys[i]])) return i;
  }
  return -1; // 찾지 못했을 경우 -1을 리턴하도록 작성
});

// 이렇게 작성하려면 위처럼 _find나 _find_index를 curryr을 사용해 만들어놓아야한다.
_go(
  users,
  _find(function (user) {
    return user.id == 1;
  }),
  _get("name"),
  console.log
); // "ID"

_go(
  users,
  _find_index(function (user) {
    return user.id == 1;
  }),
  console.log
); // 0

3-3) some

some함수는 조건에 맞는 값이 하나라도 있으면 true를 리턴하는 함수이다.

_some([1, 2, 5, 10, 20], function (val) {
  return val > 10;
}); // true

_some([1, 2, 5, 10, 20], function (val) {
  return val > 20;
}); // false

_find_index를 이용해 _some을 구현해보자.

// 3-3) some

function _some(data, predi) {
  return _find_index(data, predi || _identity) != -1;
}

console.log(
  _some([1, 2, 5, 10, 20], function (val) {
    return val > 10;
  })
); // true

console.log(
  _some([1, 2, 5, 10, 20], function (val) {
    return val > 20;
  })
); // false

// users중에 20세 미만인 사람이 한명이라도 있냐는 뜻
console.log(
  _some(users, function (user) {
    return user.age < 20;
  })
); // false

3-4) every

every는 모든 값이 조건을 만족해야 true를 리턴하는 함수이다.

_every([1, 2, 5, 10, 20], function (val) {
  return val > 10;
}); // false

_every([1, 2, 5, 10, 20], function (val) {
  return val > 0;
}); // true

_every를 구현해보자. false가 하나도 안 나와야 true임을 이용해 코드를 작성해보자.

// 3-4) every

function _every(data, predi) {
  return _find_index(data, _negate(predi || _identity)) == -1;
}
// _negate를 사용하여 every에 predi로 넘겨준 값이 false가 되는 값의 인덱스를 찾게될 것이다. 그리고 이 값이 -1이 나온다면 false가 되는 값이 하나도 없다는 뜻이므로 true를 리턴하게 될 것이다.

console.log(
  _every([1, 2, 5, 10, 20], function (val) {
    return val > 10;
  })
); // false

console.log(
  _every([1, 2, 5, 10, 20], function (val) {
    return val > 0;
  })
); // true

some, every는 predi가 생략되어도 동작해야한다.

predi가 없으면 _identity를 predi로 사용하도록 _some, _every 코드를 고쳐주면된다. (들어온 값을 그대로 리턴하는 _identity 함수가 값 자체의 true, false를 리턴하는데 유용하게 쓰이고 있음을 알 수 있다)

console.log(_some([1, 2, 0, 10])); // true
console.log(_some([null, false, 0])); // false

console.log(_every([null, false, 1])); // false
console.log(_every([1, 2, 10])); // true

💡 이렇게 고차함수(some, every)와 보조함수(고차함수에 전달되는)를 이용하면 보조함수에 우리가 원하는 로직을 넣을 수 있다는 장점이 있다. (이러한 이유로 indexOf()보다 _find_index가 확장성이 높다.)

4. 접기 - reduce, min_by, max_by, group_by, count_by

배열이나 iterable한 데이터를 돌면서 접혀진 값을 만들기 위해 사용한다. 집계를 하기도 하고 merge된 값을 구하거나 전혀 다른 값을 만들기 위해 사용된다.

reduce는 순차적인 for문을 대체하는 식으로 사용하는게 아니라, 순수함수로서 축약해나가는 함수로 이해해야 한다.

4-1) min, max, min_by, max_by

배열의 요소가 앞에서부터 순서대로 들어온다고 생각하고 프로그래밍하면 안된다. reduce를 사용하니 순서와 상관없이 한가지 로직을 작성하는 것으로 생각해야한다. a,b가 들어왔을 때 무슨일을 할건지에 집중하라는 것이다.


min, max

// min
function _min(data) {
  return _reduce(data, function (a, b) {
    return a < b ? a : b;
  });
}

console.log(_min([1, 2, 4, 10, 5, -4])); // -4

// max
function _max(data) {
  return _reduce(data, function (a, b) {
    return a > b ? a : b;
  });
}

console.log(_max([1, 2, 4, 10, 5, -4])); // 10

min_by, max_by

min_by, max_by는 min, max코드에서 어떤 조건을 통해 비교를 할 것인지에 대한 로직이 추가된 것이다.

보조함수를 인자로 받기 때문에 데이터의 값이 어떤 것이든 둘을 비교할 때 추가적인 작업을 넣을 수 있다. 이 때문에 min, max보다 다형성이 높다.

원본 데이터는 변경하지 않고 로직이 진행되는 것도 장점이다.

// min_by
function _min_by(data, iter) {
  return _reduce(data, function (a, b) {
    return iter(a) < iter(b) ? a : b;
  });
}

// 데이터를 절댓값으로 모두 바꿔 비교하려면?
console.log(_min_by([1, 2, 4, 10, 5, -4], Math.abs)); // 1

// max_by

function _max_by(data, iter) {
  return _reduce(data, function (a, b) {
    return iter(a) > iter(b) ? a : b;
  });
}

console.log(_max_by([1, 2, 4, 10, 5, -4, -11], Math.abs)); // -11

// min_by, max_by는 단순 배열이 아닌 실무적인 데이터에도 적용이 가능하다.
console.log(
  _max_by(users, function (user) {
    return user.age;
  })
); // users 중 가장 나이가 많은 user를 리턴한다.

min_by, max_by에 커링 적용

/* min_by, max_by에 curry 적용*/
var _min_by = _curryr(function _min_by(data, iter) {
  return _reduce(data, function (a, b) {
    return iter(a) < iter(b) ? a : b;
  });
});

var _max_by = _curryr(function _max_by(data, iter) {
  return _reduce(data, function (a, b) {
    return iter(a) > iter(b) ? a : b;
  });
});

// 이렇게 커링을 적용하면 아래와 같이 작성하는 것도 가능해진다.

// users 중 30대인 사람중에 제일 어린 사람 구하기
_go(
  users,
  _filter((user) => user.age >= 30),
  _min_by((user) => user.age),
  console.log
); // {id: 7, name: "JI", age: 31}

// 위 코드는 이 코드와 같다.(_get 사용)
_go(
  users,
  _filter((user) => user.age >= 30),
  _min_by(_get("age")),
  console.log
); // {id: 7, name: "JI", age: 31}

// 30대 제외하고 가장 나이 많은 사람
_go(
  users,
  _reject((user) => user.age >= 30),
  _max_by(_get("age")),
  console.log
); // {id: 4, name: "PJ", age: 27}

_go(
  users,
  _reject((user) => user.age >= 30),
  _max_by(_get("age")),
  _get("name"),
  console.log
); // "PJ"

4-2) group_by, push

group_by, push

인자로 준 조건에 맞게 그룹핑(grouping)을 하는 함수이다. group_by는 조건에 맞춰 새로운 값을 만들어나가는 것이므로 reduce로 만들면 된다.

// users에 group_by를 적용한다고 하면 아래와 같은 결과를 내야한다.

// 예를 들어 나이로 그룹을 짓는다고 하면 다음과 같은 결과가 나와야한다. (변수의 형태는 객체!)
// var users2 = {
//   36: [{ id: 1, name: 'ID', age: 36 }],
//   32: [{ id: 2, name: 'BJ', age: 32 }, { id: 3, name: 'JM', age: 32 },]
// }

// //push 적용 전
// var _group_by = _curryr(function (data, iter) {
//   return _reduce(
//     data,
//     function (grouped, val) {
//       var key = iter(val);
//       (grouped[key] = grouped[key] || []).push(val);
//       return grouped;
//     },
//     {}
//   );
// });

// push
function _push(obj, key, val) {
  (obj[key] = obj[key] || []).push(val);
  return obj;
}

// push 적용 후
var _group_by = _curryr(function (data, iter) {
  return _reduce(
    data,
    function (grouped, val) {
      return _push(grouped, iter(val), val);
    },
    {}
  );
});

// users를 나이로 그룹핑
console.log(
  _group_by(users, function (user) {
    return user.age;
  })
);
  • _reduce에는 data, iter, memo(초기값)을 준다. _group_by의 결과가 객체여야하므로 초기값에는 빈 객체({})가 들어간다.
  • _group_by를 호출할 때 인자로 준 iter 함수의 결과는 우리가 만들 객체의 key로 사용된다.
  • grouped[key]가 있으면 그 그대로의 배열을, 없으면 빈배열로 세팅되도록하고 이 배열에 값을 추가하면서 reduce 코드가 진행된다.
// _go로 더 간단히 출력하는 방법
_go(
  users,
  _group_by(function (user) {
    return user.age;
  }),
  console.log
);

// 위 코드를 _get으로 더 간단하게
_go(users, _group_by(_get("age")), console.log);

// users를 10대, 20대 등으로 그룹핑
_go(
  users,
  _group_by(function (user) {
    return user.age - (user.age % 10);
  }),
  console.log
);

// name의 첫번째 문자가 같은사람별로 그룹핑
_go(
  users,
  _group_by(function (user) {
    return user.name[0];
  }),
  console.log
);

// 위 코드는 _head, _pipe를 이용해 다음과 같이 작성할 수도 있다.
var _head = function (list) {
  return list[0];
};
_go(users, _group_by(_pipe(_get("name"), _head)), console.log); // pipe로 users들의 name을 먼저 뽑고... _head 적용한 것

4-3) count_by, inc

count_by

같은 키를 가진 데이터가 몇개있는지를 구하기 위한 함수이다.

// 4-3) count_by, inc

// inc 적용전
// var _count_by = _curryr(function (data, iter) {
//   return _reduce(
//     data,
//     function (count, val) {
//       var key = iter(val);
//       count[key] ? count[key]++ : (count[key] = 1);
//       return count;
//     },
//     {}
//   );
// });

var _inc = function (count, key) {
  count[key] ? count[key]++ : (count[key] = 1);
  return count;
};

// inc 적용 후
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;
  })
);

// 10대, 20대, 30대 별로 몇명인지 구해보자.
console.log(
  _count_by(users, function (user) {
    return user.age - (user.age % 10);
  })
);

추가

_each, _map 고치기

// _each
function _each(list, iter) {
  var keys = _keys(list);
  for (let i = 0, len = keys.length; i < len; i++) {
    iter(list[keys[i]], keys[i]);
  }
  return list;
}
// iter에 index값도 넘겨줌

//_map

function _map(list, mapper) {
  const new_list = [];
  _each(list, function (val, key) {
    new_list.push(mapper(val, key));
  });
  return new_list;
}
// map에도 _each에 key를 넘겨주도록 바꿈

아래 코드 처럼 key, value 모두를 _map으로 출력할 수 있다.

_map([1, 2, 3], console.log);
// 1 "0"
// 2 "1"
// 3 "2"

_map(user[0], console.log);
// 1 "id"
// ID "name"
// 36 "age"

console.log(
  _map(users[0], function (val, key) {
    return [key, val];
  })
);
// [["id", 10], ["name", "ID"], ["age", 36]]

// 위 코드는 pairs를 만들면 아래와 같이 간단히 작성할 수 있다.
// var _pairs = _map(function (val, key) {
//   return [key, val];
// });

var _pairs = _map((val, key) => [key, val]); // 화살표 함수로 변경

console.log(_pairs(users[0])); // [["id", 10], ["name", "ID"], ["age", 36]]
// _go(users,
//   _count_by(function (user) {
//     return user.age - user.age%10;
//   }),
//   _map((count, key) => `<li>${key}대는 ${count}명 입니다.</li>`),
//   list => '<ul>' + list.join('') + '</ul>',
//   document.write
// ); // 에러남. document.write()는 this로 document가 바인딩되어있어야 동작하기 때문

_go(
  users,
  _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)
);

// 10대는 제외하고 싶다면?
_go(
  users,
  _reject(function (user) {
    return user.age < 20;
  }),
  _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)
);

image

지금까지 완성한 모든 함수들

// curry
function _curry(fn) {
  return function (a, b) {
    return arguments.length === 2
      ? fn(a, b)
      : function (b) {
          return fn(a, b);
        };
  };
}

// curryr
function _curryr(fn) {
  return function (a, b) {
    return arguments.length === 2
      ? fn(a, b)
      : function (b) {
          return fn(b, a);
        };
  };
}

// _get
var _get = _curryr(function (obj, key) {
  return obj == null ? undefined : obj[key];
});

function _filter(list, predi) {
  const new_list = [];
  _each(list, function (val) {
    if (predi(val)) {
      new_list.push(val);
    }
  });
  return new_list;
}

function _map(list, mapper) {
  const new_list = [];
  _each(list, function (val, key) {
    new_list.push(mapper(val, key));
  });
  return new_list;
}

function _is_object(obj) {
  return typeof obj === "object" && !!obj;
}
function _keys(obj) {
  return _is_object(obj) ? Object.keys(obj) : [];
}

var _length = _get("length");
function _each(list, iter) {
  var keys = _keys(list);
  for (let i = 0, len = keys.length; i < len; i++) {
    iter(list[keys[i]], keys[i]);
  }
  return list;
}

var _map = _curryr(_map),
  _filter = _curryr(_filter),
  _each = _curryr(_each);

var slice = Array.prototype.slice;

function _rest(list, num) {
  return slice.call(list, num || 1);
}

function _reduce(list, iter, memo) {
  if (arguments.length === 2) {
    memo = list[0];
    list = _rest(list);
  }

  _each(list, function (val) {
    memo = iter(memo, val);
  });

  return memo;
}

function _pipe() {
  var fns = arguments;

  return function (arg) {
    return _reduce(
      fns,
      function (arg, fn) {
        return fn(arg);
      },
      arg
    );
  };
}

function _go(arg) {
  var fns = _rest(arguments);
  return _pipe.apply(null, fns)(arg);
}

var _values = _map(_identity);

function _identity(val) {
  return val;
}

function _pluck(data, key) {
  return _map(data, _get(key));
}

function _negate(func) {
  return function (val) {
    return !func(val);
  };
}

var _reject = _curryr(function _reject(data, predi) {
  return _filter(data, _negate(predi));
});

var _compact = _filter(_identity);

var _find = _curryr(function _find(list, predi) {
  var keys = _keys(list);
  for (let i = 0, len = keys.length; i < len; i++) {
    var val = list[keys[i]];
    if (predi(val)) return val;
  }
});

var _find_index = _curryr(function _find_index(list, predi) {
  var keys = _keys(list);
  for (let i = 0, len = keys.length; i < len; i++) {
    if (predi(list[keys[i]])) return i;
  }
  return -1; // 찾지 못했을 경우 -1을 리턴하도록 작성
});

function _some(data, predi) {
  return _find_index(data, predi || _identity) != -1;
}

function _every(data, predi) {
  return _find_index(data, _negate(predi || _identity)) == -1;
}

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);
    },
    {}
  );
});

var _head = function (list) {
  return list[0];
};

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));
    },
    {}
  );
});

Comment

팀원들 결과물

References