Cookbook - ramda/ramda GitHub Wiki

Recipes

Feel free to contribute to Ramda Cookbook by adding a snippet you've found useful.

Pick values a from list by indexes

part of ramda-adjunct

// :: [Number] -> [a] -> [a]
var pickIndexes = R.useWith(R.ap, [R.map(R.nth), R.of]);
pickIndexes([0, 2], ['a', 'b', 'c']); // => ['a', 'c']

Source

Create a list function

part of ramda-adjunct

https://clojuredocs.org/clojure.core/list

//  list :: a... -> [a...]
var list = Array.of;
var list = R.unapply(R.identity);
list(1, 2, 3); // => [1, 2, 3]

Mess with the DOM

// Get all descendants that match selector
// Note: NodeList is array-like so you can run ramda list functions on it.
//  cssQuery :: String -> Node -> NodeList
var cssQuery = R.invoker(1, 'querySelectorAll');

// Mutate style properties on an element
//  setStyle :: String -> String -> Element -> Element
//  var setStyle = R.assoc('style');
var setStyle = R.curry(
  (style, tag) => R.forEachObjIndexed((value, key) => tag.style[key] = value, style)
);

// Make all paragraphs and anchors red
R.pipe(
  cssQuery('a, p'),
  R.forEach(setStyle({ color: 'red' })),
)(document)

Apply a list of functions in a specific order into a list of values

const {red, green, blue} = require('chalk');
const disco = R.pipe(R.zipWith(R.call, [ red, green, blue ]), R.join(' '));
console.log(disco([ 'foo', 'bar', 'xyz' ]));

Derivative of R.props for deep fields

var dotPath = R.useWith(R.path, [R.split('.')]);
var propsDotPath = R.useWith(R.ap, [R.map(dotPath), R.of]);
var obj = {
  a: { b: { c: 1 } },
  x: 2
};

propsDotPath(['a.b.c', 'x'], obj);
// => [ 1, 2 ]

Get n function calls as a list

R.map(R.call, R.repeat(Math.random, 5));

// Note that as of Ramda v0.2.3, you can just do this:
R.times(() => Math.random(), 5);

Set properties only if they don't exist

Useful for passing defaults, similar to lodash's _.defaults.

//  defaults :: Object -> Object -> Object
var defaults = R.flip(R.merge);

// process.env.SECRET overwrites deadbeef if it exists
defaults(process.env, {
  SECRET: 'deadbeef'
});

Available as R.mergeLeft

Get an object's method names

//  methodNames :: Object -> [String]
var methodNames = R.compose(R.keys, R.pickBy(R.is(Function)));

var obj = {
  foo: true,
  bar: function() {},
  baz: function() {},
};

methodNames(obj); // => ['bar', 'baz']

Make an object out of keys, with values derived from them

//    objFromKeys :: (String -> a) -> [String] -> {String: a}
const objFromKeys = R.curry((fn, keys) =>
  R.zipObj(keys, R.map(fn, keys)));
const files = ['diary.txt', 'shopping.txt'];
objFromKeys(fs.readFileSync, files);
// => { 'diary.txt': 'Dear diary...', ... }

part of ramda-adjunct as zipObjWith

Make an object out of a list, with keys derived from each element

const objFromListWith = R.curry((fn, list) => R.chain(R.zipObj, R.map(fn))(list))
objFromListWith(
  R.prop('id'),
  [{ id: 'foo', name: 'John' }, { id: 'bar', name: 'Jane' }]
)
// => { foo: { id: 'foo', name: 'John' }, bar: { id: 'bar', name: 'Jane' } }

Map keys of an object (rename keys by a function)

part of ramda-adjunct as renameKeysWith

//    mapKeys :: (String -> String) -> Object -> Object
const mapKeys = R.curry((fn, obj) =>
  R.fromPairs(R.map(R.adjust(0, fn), R.toPairs(obj))));
mapKeys(R.toUpper, { a: 1, b: 2, c: 3 }); // { A: 1, B: 2, C: 3 }

Rename keys of an object

part of ramda-adjunct

/**
 * Creates a new object with the own properties of the provided object, but the
 * keys renamed according to the keysMap object as `{oldKey: newKey}`.
 * When some key is not found in the keysMap, then it's passed as-is.
 *
 * Keep in mind that in the case of keys conflict is behaviour undefined and
 * the result may vary between various JS engines!
 *
 * @sig {a: b} -> {a: *} -> {b: *}
 */
const renameKeys = R.curry((keysMap, obj) =>
  R.reduce((acc, key) => R.assoc(keysMap[key] || key, obj[key], acc), {}, R.keys(obj))
);
const input = { firstName: 'Elisia', age: 22, type: 'human' }

renameKeys({ firstName: 'name', type: 'kind', foo: 'bar' })(input)
//=> { name: 'Elisia', age: 22, kind: 'human' }

Rename key of an object

const renameKey = curry((oldKey, newKey, obj) =>
  pipe(
    over(lens(prop(oldKey), assoc(newKey)), identity),
    dissoc(oldKey),
  )(obj));
// OR
const renameKey = curry((oldKey, newKey, obj) =>
  assoc(newKey, prop(oldKey, obj), dissoc(oldKey, obj)));

const obj = {
  name: 'Julia',
  age: 30,
}

renameKey('age', 'yearsOld', obj) // { "name": "Julia", "yearsOld": 30 }

Rename key of an object (rename key by a function)

part of ramda-adjunct

const renameKeyWith = curry((fn, key, obj) =>
  pipe(
    over(lens(prop(key), assoc(fn(key))), identity),
    dissoc(key),
  )(obj));

const obj = {
  name: 'Julia',
  age: 30,
}

renameKeyWith(concat('full'), 'name', obj) // { "fullname": "Julia", "age": 30 }

Problem: Do any of these strings appear in another list?

part of ramda-adjunct

//    overlaps :: [a] -> [a] -> Boolean
const overlaps = R.pipe(R.intersection, R.complement(R.isEmpty));
process.argv // ['node', 'script.js', '-v']
overlaps(['-v', '--verbose'], process.argv) // true

Get object by id

//    findById :: String -> Array -> Object
const findById = R.converge(
  R.find,
  [R.pipe(R.nthArg(0), R.propEq("id")), R.nthArg(1)]
);
// Or you can use `useWith`
const findById = R.useWith(
  R.find,
  [R.propEq('id'), R.identity],
);

Convert object to array

// convert :: {a} -> [{ word :: String, count :: a }]
const convert = R.compose(R.map(R.zipObj(['word', 'count'])), R.toPairs);

convert({I: 2, it: 4, that: 1});
// => [{"count": 2, "word": "I"}, {"count": 4, "word": "it"}, {"count": 1, "word": "that"}]

Create an incrementing or decrementing range of numbers with a step

const rangeStep = (start, step, stop) => R.map(
  n => start + step * n,
  R.range(0, (1 + (stop - start) / step) >>> 0)
);

rangeStep(1, 1, 4);   // [1, 2, 3, 4]
rangeStep(2, 2, 8);   // [2, 4, 6, 8]
rangeStep(0, 3, 10);  // [0, 3, 6, 9]
rangeStep(3, -2, -3); // [3, 1, -1, -3]

Filter an object using keys as well as values

const filterWithKeys = (pred, obj) => R.pipe(
  R.toPairs,
  R.filter(R.apply(pred)),
  R.fromPairs
)(obj);

filterWithKeys(
  (key, val) => key.length === val,
  {red: 3, blue: 5, green: 5, yellow: 2}
); //=> {red: 3, green: 5}

diffObjs - diffing objects (similar to Guava's Maps.Difference)

See http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/collect/Maps.html#difference%28java.util.Map,%20java.util.Map%29

var groupObjBy = curry(pipe(
  // Call groupBy with the object as pairs, passing only the value to the key function
  useWith(groupBy, [useWith(__, [last]), toPairs]),
  map(fromPairs)
))

var diffObjs = pipe(
  useWith(mergeWith(merge), [map(objOf("leftValue")), map(objOf("rightValue"))]),
  groupObjBy(cond([
    [
      both(has("leftValue"), has("rightValue")),
      pipe(values, ifElse(apply(equals), always("common"), always("difference")))
    ],
    [has("leftValue"), always("onlyOnLeft")],
    [has("rightValue"), always("onlyOnRight")],
  ])),
  evolve({
    common: map(prop("leftValue")),
    onlyOnLeft: map(prop("leftValue")),
    onlyOnRight: map(prop("rightValue"))
  })
);

diffObjs({a: 1, c: 5, d: 4 }, {a: 1, b: 2, d: 7});
/* =>
{
    "common": { "a": 1 },
    "difference": {
       "d": { "leftValue": 4, "rightValue": 7 }
    },
    "onlyOnLeft": { "c": 5 },
    "onlyOnRight": { "b": 2 }
}

Convert a list of property-lists (with header) into a list of objects

const tsv = [
  ['name',  'age', 'drink'],
  ['john',   23,   'wine'],
  ['maggie', 45,   'water']
];
compose(apply(lift(zipObj)), splitAt(1))(tsv); //=>
// [
//   {"age": 23, "drink": "wine", "name": "john"},
//   {"age": 45, "drink": "water", "name": "maggie"}
// ]

Map over the value at the end of a path

// :: [String] -> (a -> b) -> {k: a} -> {k: b}
const mapPath = R.curry((path, f, obj) =>
  R.assocPath(path, f(R.path(path, obj)), obj)
);

mapPath(['a', 'b', 'c'], R.inc, {a: {b: {c: 3}}}); //=>
// { a: { b: { c: 4 } } }

Apply a given function N times

// applyN :: (a -> a) -> Number -> (a -> a)
const applyN = compose(reduceRight(compose, identity), repeat);

applyN(x => x * x, 4)(2); //=> 65536 (2 -> 4 -> 16 -> 256 -> 65536)

const isOdd = n => n % 2 == 1;
const collatz = n => isOdd(n) ? (3 * n + 1) : (n / 2);

applyN(collatz, 5)(27); //=> 31 (27 -> 82 -> 41 -> 124 -> 62 -> 31)

Sort a List by a given unary predicate

// will put all true evaluations of the predicate first
// is as "stable" as sortBy
// the predicate here is free to instead just return a truthy value
const sortByPredicate = curry((pred, list) => sortBy(a => Boolean(pred(a)) ? 1 : 2, list))

const data = [
  {boolProp: true},
  {boolProp: false},
  {},
  {boolProp: true},
  {},
  {boolProp: true, a: 1}
]

const sortByBoolProp = sortByPredicate(obj => obj.boolProp === true)
sortByBoolProp(data)

// [{"boolProp": true}, {"boolProp": true}, {"a": 1, "boolProp": true}, {"boolProp": false}, {}, {}]

Flatten a nested object into dot-separated key / value pairs

const flattenObj = obj => {
  const go = obj_ => chain(([k, v]) => {
    if (type(v) === 'Object' || type(v) === 'Array') {
      return map(([k_, v_]) => [`${k}.${k_}`, v_], go(v))
    } else {
      return [k, v](/ramda/ramda/wiki/k,-v)
    }
  }, toPairs(obj_))

  return fromPairs(go(obj))
}

flattenObj({a:1, b:{c:3}, d:{e:{f:6}, g:[{h:8, i:9}, 0]}})

//=> {"a": 1, "b.c": 3, "d.e.f": 6, "d.g.0.h": 8, "d.g.0.i": 9, "d.g.1": 0}

Sort a List by array of props (if first prop equivalent, sort by second, etc.)

const firstTruthy = ([head, ...tail]) => R.reduce(R.either, head, tail);
const makeComparator = (propName) => R.comparator((a, b) => R.gt(R.prop(propName, a), R.prop(propName, b)));
const sortByProps = (props, list) => R.sort(firstTruthy(R.map(makeComparator, props)), list)

sortByProps(["a","b","c"], [{a:1,b:2,c:3}, {a:10,b:10,c:10}, {a:10,b:6,c:0}, {a:1, b:2, c:1}, {a:100}])

//=> [{a:100}, {a:10,b:10,c:10}, {a:10,b:6,c:0}, {a:1,b:2,c:3}, {a:1, b:2, c:1}]

Remove a subset of keys from an object whose associated values satisfy a given predicate

const omitWhen = curry((fn, ks, obj) =>
  merge(omit(ks, obj), reject(fn, pick(ks, obj))));

omitWhen(equals(2), ['a', 'c'], { a: 1, b: 1, c: 2, d: 2 });

//=> { a: 1, b: 1, d: 2 }

specialized version of this function part of ramda-adjunct as RA.omitBy

Group by multiple

const groupByMultiple = R.curry((fields, data) => {
  if (fields.length === 1) return R.groupBy(fields[0], data);
  let groupBy = R.groupBy(R.last(fields));
  R.times(() => {
    groupBy = R.mapObjIndexed(groupBy)
  }, fields.length - 1);

  return groupBy(groupByMultiple(R.init(fields), data))
});
const data = [
  {
    a: 1,
    b: 1,
    c: 1
  },
  {
    a: 1,
    b: 2,
    c: 2
  },
  {
    a: 1,
    b: 2,
    c: 1
  },
  {
    a: 2,
    b: 1,
    c: 1
  },
  {
    a: 2,
    b: 2,
    c: 2
  },
  {
    a: 2,
    b: 1,
    c: 2
  },
  {
    a: 2,
    b: 2,
    c: 2
  },
  {
    a: 1,
    b: 2,
    c: 2
  },
]

groupByMultiple([R.prop('a'), R.prop('b'), R.prop('c')], data)
//=> {"1": {"1": {"1": [{"a": 1, "b": 1, "c": 1}]}, "2": {"1": [{"a": 1, "b": 2, "c": 1}], "2": [{"a": 1, "b": 2, "c": 2}, {"a": 1, "b": 2, "c": 2}]}}, "2": {"1": {"1": [{"a": 2, "b": 1, "c": 1}], "2": [{"a": 2, "b": 1, "c": 2}]}, "2": {"2": [{"a": 2, "b": 2, "c": 2}, {"a": 2, "b": 2, "c": 2}]}}}

This allows to group a list of like objects by multiple keys.

Linear Scaling of array

To scale an array linearly from one range to another

const linearScale = (domainMin,domainMax) =>
                      (rangeMin,rangeMax) =>
                        (n) => 
                          rangeMin + (n - domainMin) * (rangeMax - rangeMin) / (domainMax - domainMin)

const initialScaleData = [0, 1000, 3000, 2000, 5000, 4000, 7000, 6000, 9000, 8000, 10000];
let linearScale = linearScale([0,1000]);
let converter = linearScale([0,100]);

console.log(R.map(converter(n),initialScaleData))

inspect: Use a spec to get some parts of a data structure

UPDATE (2017-08-14): A newer version of inspect is included in this npm package: https://www.npmjs.com/package/icylace-object-utils

//
// Retrieves values from a data structure according to a given spec which
// are then returned in an object keyed according to strings in the spec.
//
const inspect = R.curry((spec, obj) => {
  const props = {}
  function inspectProps(spec, obj) {
    R.forEachObjIndexed((v, k) => {
      const objValue = obj[k]
      if (typeof v === "string") {
        props[v] = objValue
      } else if (typeof objValue === "object") {
        inspectProps(v, objValue)
      }
    }, spec)
  }
  inspectProps(spec, obj)
  return props
})

// -----------------------------------------------------------------------------

var spec = {
  person: {
    favoritePhotos: [
      [{ imageUrl: "url1" }, "favorite2"],
      null,
      { locationCoordinates: [null, "location2"] },
    ]
  }
}

var legacyData = {
  person: {
    favoritePhotos: [
      [
        { imageUrl: "httpblahbiddyblah1", note: "1st favorite" },
        { imageUrl: "httpblahbiddyblah2", public: false },
      ],
      { obsoleteDataBlob: "AGb-A#A#+A+%A#DF-AC#" },
      { locationCoordinates: [123, 456], [234, 567](/ramda/ramda/wiki/123,-456],-[234,-567) },
    ],
  },
}

var inspector = inspect(spec)

console.log(inspector(legacyData))
// Output will be:
// {
//   "url1": "httpblahbiddyblah1",
//   "favorite2": {
//     "imageUrl": "httpblahbiddyblah2",
//     "public": false
//   },
//   "location2": [234, 567]
// }

Original discussion: #2038

CodePen demo: https://codepen.io/icylace/pen/RKRbxy?editors=1010

MeasureThat.net benchmarks: https://www.measurethat.net/Benchmarks/ShowResult/4149


whereAll: Sort of like a recursive version of Ramda's where

UPDATE (2017-08-14): A newer version of whereAll is included in this npm package: https://www.npmjs.com/package/icylace-object-utils

//
// An alternative to Ramda's `where` that has the following differences:
//     1. `whereAll` can take specs containing a nested structure.
//     2. `whereAll` specs can use shorthands for property detection:
//         `true` - check if the property is present in the test object.
//         `false` - check if the property is absent in the test object.
//         `null` - skip the existence check for the property.
//     3. `whereAll` specs can be shorter than similar `R.where` specs.
//     4. `whereAll` is much slower than `R.where` in most scenarios.
//
// When you need to do checks on nested structures and processor speed
// is not the bottleneck, `whereAll` is a nice alternative to `R.where`.
//
const whereAll = R.curry((spec, obj) => {
  if (typeof obj === "undefined") {
    if (spec === null || typeof spec === "boolean") {
      return !spec
    }
    return false
  } else if (spec === false) {
    return false
  }
  let output = true
  R.forEachObjIndexed((v, k) => {
    if (v === null || typeof v === "boolean" || R.keys(v).length) {
      if (!whereAll(v, obj[k])) {
        output = false
      }
    } else if (!v(obj[k])) {
      output = false
    }
  }, spec)
  return output
})

// -----------------------------------------------------------------------------

var data1 = {
  a: {
    h: [
      { i: 5 },
      [
        { j: 6, k: 7 },
        { j: 8, k: "nine" },
      ],
      10,
      { l: { m: { n: false } } },
    ],
  },
}

var data2 = {
  a: {
    h: [
      { i: 5 },
      [
        { j: 8, k: "nine" },
      ],
    ],
  },
}

// Using `R.where`
var detect1 = R.where({
  a: R.where({
    h: R.where([
      R.always(true),
      Array.isArray,
      R.complement(R.identical(undefined)),
      R.where({ l: R.where({ m: R.where({ n: R.complement(R.identical(undefined)) }) }) }),
    ]),
  }),
})
console.log(detect1(data1))    //=> true
console.log(detect1(data2))    //=> false

// Using `whereAll`
var detect2 = whereAll({
  a: {
    h: [
      R.always(true),
      Array.isArray,
      R.complement(R.identical(undefined)),
      { l: { m: { n: R.complement(R.identical(undefined)) } } },
    ],
  },
})
console.log(detect2(data1))    //=> true
console.log(detect2(data2))    //=> false

// Using `whereAll` (alternate)
var detect3 = whereAll({
  a: {
    h: [
      null,
      Array.isArray,
      true,
      { l: { m: { n: true } } },
    ],
  },
})
console.log(detect3(data1))    //=> true
console.log(detect3(data2))    //=> false

CodePen demo: https://codepen.io/icylace/pen/YNzJOq?editors=1010

MeasureThat.net benchmarks:


zipLongest: Zip lists to the longest list's length

//
// Zips all items from two lists using `undefined` for any missing items.
//
function zipLongest(xs, ys) {
  let l1 = xs
  let l2 = ys
  if (xs.length < ys.length) {
    l1 = R.concat(xs, R.repeat(undefined, ys.length - xs.length))
  } else if (ys.length < xs.length) {
    l2 = R.concat(ys, R.repeat(undefined, xs.length - ys.length))
  }
  return R.zip(l1, l2)
}

// -----------------------------------------------------------------------------

const xs = [1, 2, 3]
const ys = [1, 2, 3, 4, 5]
const zs = ["a", "b"]

// Using `R.zip`
console.log("R.zip(xs, ys):", R.zip(xs, ys))
//=> [1, 1], [2, 2], [3, 3](/ramda/ramda/wiki/1,-1],-[2,-2],-[3,-3)
console.log("R.zip(ys, zs):", R.zip(ys, zs))
//=> [1, "a"], [2, "b"](/ramda/ramda/wiki/1,-"a"],-[2,-"b")

// Using `zipLongest`
console.log("zipLongest(xs, ys):", zipLongest(xs, ys))
//=> [1, 1], [2, 2], [3, 3], [undefined, 4], [undefined, 5](/ramda/ramda/wiki/1,-1],-[2,-2],-[3,-3],-[undefined,-4],-[undefined,-5)
console.log("zipLongest(ys, zs):", zipLongest(ys, zs))
//=> [1, "a"], [2, "b"], [3, undefined], [4, undefined], [5, undefined](/ramda/ramda/wiki/1,-"a"],-[2,-"b"],-[3,-undefined],-[4,-undefined],-[5,-undefined)

CodePen demo: https://codepen.io/icylace/pen/RKbaLp?editors=1012

Separate a list into n parts

const separate = R.curry(function(n, list) {
  var len = list.length;
  var idxs = R.range(0, len);
  var f = (_v, idx) => Math.floor(idx * n / len);
  return R.values(R.addIndex(R.groupBy)(f, list));
});
// usage: separate(2, ['a','b','c','d','e'])
// -> ['a','b','c'],['d','e'](/ramda/ramda/wiki/'a','b','c'],['d','e')

Divide a list into n quantiles based on a comparator

const quantile = R.curry(function/*<T>*/(comparator /*: (v: T) => Ordinal*/, n /*: number*/, coll /*: T[] */) /*: T[][]*/ {
  let sorted = R.sortBy(comparator, coll);
  return separate(n, sorted);
});
// usage: quantile(R.prop('v'), 3, [{v:1},{v:2},{v:3},{v:7},{v:4},{v:2},{v:4},{v:1}])
// -> [ [{v:1},{v:1},{v:2}], [{v:2},{v:3},{v:4}], [{v:4},{v:7}] ]

Make an object from an array using a mapper function

// NB: this is functionally equivalent to objFromKeys
const arr2obj = R.curry((fn, arr) =>
  R.pipe(
    (list) => list.map(k => [k.toString(), fn(k)]),
    R.fromPairs
  )(arr)
);

// usage: arr2obj(R.reverse, ['abc', 'def'])
// -> { abc: 'cba', def: 'fed' }

Look up the property corresponding to a string in a lookup object

const lookup = R.flip(R.prop);
// usage:
// let cache = lookup({ a: 1, b: 2, c: 3 });
// cache('a')
// -> 1

Unpivot a table

const unpivot = R.curry((cols, attrCol, valCol, table) => R.chain((row) => R.pipe(
  R.pick(cols),
  R.filter(R.complement(R.isNil)),
  R.mapObjIndexed((v, k) => Object.assign({ [attrCol]: k, [valCol]: v }, R.omit(cols, row))),
  R.values,
)(row), table))
// usage: unpivot([ "attribute1", "attribute2", "attribute3" ], "attribute", "value", [{ key: "key1", attribute1: 1, attribute2: null, attribute3: 3 }])
// result:
// [{ attribute: attribute1, value: 1, key: key1 },
//  { attribute: attribute3, value: 3, key: key1 }]

Unpivot any other columns in a table

const unpivotRest = R.curry((cols, attrCol, valCol, table) => R.chain((row) => R.pipe(
  R.omit(cols),
  R.filter(R.complement(R.isNil)),
  R.mapObjIndexed((v, k) => Object.assign({ [attrCol]: k, [valCol]: v }, R.pick(cols, row))),
  R.values,
)(row), table))
// usage: unpivotRest([ "key" ], "attribute", "value", [{ key: "key1", attribute1: 1, attribute2: null, attribute3: 3 }])
// result:
// [{ attribute: attribute1, value: 1, key: key1 },
//  { attribute: attribute3, value: 3, key: key1 }]

Pivot a table with a function to resolve conflicts (multiple values for the same attribute)

const pivotWith = R.curry((fn, keyCol, valCol, table) => R.pipe(
  R.groupWith(R.eqBy(R.omit([keyCol, valCol]))),
  R.map((rowGroup) => R.reduce(
    R.mergeWith(fn),
    R.omit([keyCol, valCol], rowGroup[0]),
    rowGroup.map((row) => ({ [row[keyCol]]: row[valCol] }))
  )),
)(table))
// usage:
// pivotWith(R.min, "attribute", "value", [ 
//   { key: "key1", attribute: "attribute1" , value: 1 },  
//   { key: "key1", attribute: "attribute3" , value: 3 },  
//   { key: "key2" , attribute: "attribute1" , value: 2 },  
//   { key: "key2", attribute: "attribute1", value: 8 },  
//   { key: "key2", attribute: "attribute2", value: 4 },
// ])
// result:
// [{ key: key1, attribute1: 1, attribute3: 3 },
//  { key: key2, attribute1: 2, attribute2: 4 }]

Pivot a table

const pivot = pivotWith(R.nthArg(0))
// usage:
// pivot("attribute", "value", [  
//   { key: "key1", attribute: "attribute1", value: 1 },  
//   { key: "key1", attribute: "attribute3", value: 3 },  
//   { key: "key2", attribute: "attribute1", value: 2 },  
//   { key: "key2", attribute: "attribute2", value: 4 },
// ])
// result:
// [{ key: key1, attribute1: 1, attribute3: 3 },
//  { key: key2, attribute1: 2, attribute2: 4 }]

SQL-style JOINs

let people = [{ id: 1, name: 'me' }, { id: 3, name: 'you' }];
let transactions = [{ buyer: 1, seller: 10 }, { buyer: 2, seller: 5 }];

const joinRight = R.curry((mapper1, mapper2, t1, t2) => {
  let indexed = R.indexBy(mapper1, t1);
  return t2.map((t2row) => R.merge(t2row, indexed[mapper2(t2row)]));
});
// usage: joinRight(R.prop('id'), R.prop('buyer'), people, transactions)
// result:
// [{ buyer: 1, seller: 10, id: 1, name: 'me' },
//  { buyer: 2, seller: 5 }]

const joinLeft = R.curry((f1, f2, t1, t2) => joinRight(f2, f1, t2, t1));
// usage: joinLeft(R.prop('id'), R.prop('buyer'), people, transactions)
// result:
// [{ id: 1, name: me, buyer: 1, seller: 10 },
//  { id: 3, name: 'you' }]

const joinInner = R.curry((f1, f2, t1, t2) => {
  let indexed = R.indexBy(f1, t1);
  return R.chain((t2row) => {
    let corresponding = indexed[f2(t2row)];
    return corresponding ? [R.merge(t2row, corresponding)] : [];
  }, t2);
});
// usage: joinInner(R.prop('id'), R.prop('buyer'), people, transactions)
// result:
// [{ buyer: 1, seller: 10, id: 1, name: 'me' }]

const joinOuter = R.curry((f1, f2, t1, t2) => {
  let o1 = R.indexBy(f1, t1);
  let o2 = R.indexBy(f2, t2);
  return R.values(R.mergeWith(R.merge, o1, o2));
});
// usage: joinOuter(R.prop('id'), R.prop('buyer'), people, transactions)
// result:
// [{ id: 1, name: 'me', buyer: 1, seller: 10 },
//  { buyer: 2, seller: 5 },
//  { id: 3, name: 'you' }]

const nestedJoin = R.curry((f1, f2, newCol, t1, t2) => {
  let indexed = R.indexBy(f1, t1);
  return t2.map((t2row) => {
    let corresponding = indexed[f2(t2row)];
    return R.isNil(corresponding) ? t2row : R.assoc(newCol, corresponding, t2row);
  });
});
// usage: nestedJoin(R.prop('id'), R.prop('buyer'), 'buyerObj', people, transactions)
// result:
// [{ buyer: 1, seller: 10, buyerObj: { id: 1, name: 'me' } },
//  { buyer: 2, seller: 5 }]

mode: Return most common item from a list

Get a common value from array #2410

const occurences = reduce((acc, x) => ({
  ...acc,
  [x]: pipe(defaultTo(0), inc)(acc[x])
}), Object.create(null));

const largestPair = reduce(([k0, v0], [k1, v1]) => {
  const maxVal = max(v0, v1);
  const keyOfLargest = maxVal > v0 ? k1 : k0;
  return [keyOfLargest, maxVal];
}, [null, -Infinity]);

const mode = pipe(occurences, toPairs, largestPair, head);

size

const size = R.pipe(R.values, R.length);
// usage:
// size({ a: 1, b: 2, c: 3 }); // -> 3
// size(['a','b','c']); // -> 3

spreadPath

part of ramda-adjunct

const spreadPath = R.curryN(2, R.converge(R.merge, [R.dissocPath, R.pathOr({})]));
// usage:
// spreadPath(
//   ['b1', 'b2'],
//   { a: 1, b1: { b2: { c: 3, d: 4 } } }
// ); // => { a: 1, c: 3, d: 4, b1: {} };

spreadProp

part of ramda-adjunct

// RA === ramda-adjunct
const spreadProp = R.curry((prop, obj) => RA.spreadPath(R.of(prop), obj));
// usage:
// spreadProp('b', { a: 1, b: { c: 3, d: 4 } }); // => { a: 1, c: 3, d: 4 };

flattenPath

part of ramda-adjunct

const flattenPath = R.curry((path, obj) => R.merge(obj, R.pathOr({}, path, obj)));
// usage:
// flattenPath(
//   ['b1', 'b2'],
//   { a: 1, b1: { b2: { c: 3, d: 4 } } }
// ); // => { a: 1, c: 3, d: 4, b1: { b2: { c: 3, d: 4 } } };

flattenProp

part of ramda-adjunct

// RA = ramda-adjunct
const flattenPath = R.curry((prop, obj) => RA.flattenPath(R.of(prop), obj));
// usage:
// flattenProp(
//   'b',
//   { a: 1, b: { c: 3, d: 4 } }
// ); // => { a: 1, c: 3, d: 4, b: { c: 3, d: 4 } };

paths

part of ramda-adjunct

const paths = R.curry((paths, obj) => R.ap([R.path(R.__, obj)], paths));
// usage:
// const obj = {
//   a: { b: { c: 1 } },
//   x: 2,
// };
// paths(['a', 'b', 'c'], ['x'](/ramda/ramda/wiki/'a',-'b',-'c'],-['x'), obj); //=> [1, 2]

mergeProps

part of ramda-adjunct

const mergeProps = R.curryN(2, R.pipe(R.props, R.mergeAll));
// usage:
// const obj = {
//   foo: { fooInner: 1 },
//   bar: { barInner: 2 }
// };
// 
// const withSpread = { ...obj.foo, ...obj.bar }; //=> { fooInner: 1, barInner: 2 }
// const withFunc = mergeProps(['foo', 'bar'], obj); //=> { fooInner: 1, barInner: 2 }

mergePaths

part of ramda-adjunct

// RA === ramda-adjunct
const mergePaths = R.curryN(2, R.pipe(RA.paths, R.mergeAll))

// usage:
// const obj = {
//   foo: { fooInner: { fooInner2: 1 } },
//   bar: { barInner: 2 }
// };
// mergePaths(['foo', 'fooInner'], ['bar'](/ramda/ramda/wiki/'foo',-'fooInner'],-['bar'), obj); //=> { fooInner2: 1, barInner: 2 }

mergeProp

part of ramda-adjunct

// RA === ramda-adjunct
const mergeProp = R.curry((p, subj, obj) => RA.mergePath(R.of(p), subj, obj));

// usage:
// RA.mergeProp(
//   'outer',
//   { foo: 3, bar: 4 },
//   { outer: { foo: 2 } }
// ); //=> { outer: { foo: 3, bar: 4 }

mergePath

part of ramda-adjunct

// RA === ramda-adjunct
const mergePath = R.curry((path, source, obj) => R.over(R.lensPath(path), R.flip(R.merge)(source), obj));

// usage:
// RA.mergePath(
//   ['outer', 'inner'],
//   { foo: 3, bar: 4 },
//   { outer: { inner: { foo: 2 } } }
// ); //=> { outer: { inner: { foo: 3, bar: 4 } }

lensEq

part of ramda-adjunct

const lensEq = R.curryN(3, (lens, val, data) => R.pipe(R.view(lens), R.equals(val))(data));
const lensNotEq = R.complement(lensEq);
// usage:
// lensEq(R.lensIndex(0), 1, [0, 1, 2]); // => false
// lensEq(R.lensIndex(1), 1, [0, 1, 2]); // => true
// lensEq(R.lensPath(['a', 'b']), 'foo', { a: { b: 'foo' } }) // => true
// lensNotEq(R.lensIndex(0), 1, [0, 1, 2]); // => true
// lensNotEq(R.lensIndex(1), 1, [0, 1, 2]); // => false
// lensNotEq(R.lensPath(['a', 'b']), 'foo', { a: { b: 'foo' } }) // => false 

lensSatisfies

part of ramda-adjunct

const lensSatisfies = R.curryN(3,
  (predicate, lens, data) => R.pipe(R.view(lens), predicate, R.equals(true))(data)
);
const lensNotSatisfy = complement(lensSatisfies);
// usage:
// lensSatisfies(R.equals(true), R.lensIndex(0), [false, true, 1]); // => false
// lensSatisfies(R.equals(true), R.lensIndex(1), [false, true, 1]); // => true
// lensSatisfies(R.equals(true), R.lensIndex(2), [false, true, 1]); // => false
// lensSatisfies(R.identity, R.lensProp('x'), { x: 1 }); // => false 
// lensNotSatisfy(R.equals(true), R.lensIndex(0), [false, true, 1]); // => true
// lensNotSatisfy(R.equals(true), R.lensIndex(1), [false, true, 1]); // => false
// lensNotSatisfy(R.equals(true), R.lensIndex(2), [false, true, 1]); // => true
// lensNotSatisfy(R.identity, R.lensProp('x'), { x: 1 }); // => true

viewOr

part of ramda-adjunct

const viewOr = R.curryN(3,
  (defaultValue, lens, data) => R.defaultTo(defaultValue, R.view(lens, data))
);
// usage:
// viewOr('N/A', R.lensProp('x'), {}); // => 'N/A'
// viewOr('N/A', R.lensProp('x'), { x: 1 }); // => 1
// viewOr('some', R.lensProp('y'), { y: null }); // => 'some'
// viewOr('some', R.lensProp('y'), { y: false }); // => false 

overIf

Applies over to lens only if lens value is not null or undefined:

const overIf = curry((lens, fn) => unless(o(isNil, view(lens)), over(lens, fn)));
// usage:
// overIf(lensPath(['x', 'y']), toUpper)({x: {y: 'foo'}}); // => {"x": {"y": "FOO"}}
// overIf(lensPath(['x', 'y']), toUpper)({x: {z: 'foo'}}); // => {"x": {"z": "foo"}}

Also consider L-optional from partial.lenses library.

omitIndexes

part of ramda-adjunct

// helpers
const rejectIndexed = R.addIndex(R.reject);
const containsIndex = R.curry((indexes, val, index) => R.includes(index, indexes));

const omitIndexes = R.curry((indexes, list) => rejectIndexed(containsIndex(indexes), list));
// usage:
// omitIndexes([-1, 1, 3], ['a', 'b', 'c', 'd']); //=> ['a', 'c']

assocPaths

See assocPath

// assocPaths :: (([path], [*]) -> Object) -> Object
// path = [String | Int]
import { applyTo, assocPath, flip, pipe, reduce, zipWith } from 'ramda'

//point-free
const assocPaths = pipe(zipWith(assocPath), flip(reduce(applyTo)))

//usage:
const helper = assocPaths([ [ 'prop1', 'prop11' ], [ 'prop2', 'prop21', 'prop211' ], [ 'shouldChange' ], [ 'prop3', 'prop31' ]], [ 5, 6, 7, 9 ])
const out = helper({ prop1: { prop11: 20 }, shouldChange: 42, shouldNotChange: 24 })
console.log(out) 
// returns => { prop1: { prop11: 5 },​​​ shouldChange: 7,​​​​ shouldNotChange: 24,​​​​​ prop2: { prop21: { prop211: 6 } },​​​​​ prop3: { prop31: 9 } }​​​​​

evolveSpec

Extract only the fields specified in a model, applying functions therein, from a larger data structure.

const isFunction = val => Object.prototype.toString.call(val) === '[object Function]'; 

// evolveSpec :: Object -> Object -> Object 
const evolveSpec = R.curry((spec, source) => R.mapObjIndexed( (value, key) =>
  R.ifElse(isFunction,
    R.applyTo(R.prop(key, source)),
    R.flip(evolveSpec)(R.prop(key, source))
  )(value)
)(spec));

// usage:

const extract = evolveSpec({
  sumThese: R.sum,
  doubleThis: R.multiply(2),
  propThis: R.prop('key'),
  subItem: {
    halveThis: R.divide(R.__, 2),
  }
});

const input = {
  sumThese: [1, 2, 3],
  doubleThis: 21,
  propThis: { key: 'value' },
  subItem: { halveThis: 4, unwanted: 'omit this' },
  unwanted: 'omit this'
};

extract(input); // { sumThese: 6, doubleThis: 42, propThis: 'value', subItem: { halveThis: 2 } }

xPairs

Create pairs from value and list of values.

// xPairs :: a -> [b] -> [a, b](/ramda/ramda/wiki/a,-b)
const xPairs = useWith(xprod, [of, identity]);

Example:

xPairs(1, [2, 3]) // [1, 2], [1, 3](/ramda/ramda/wiki/1,-2],-[1,-3)

Included in ramda-extension

toggle

Get the opposite value comparing against a given set of two values.

const equalsAndAlways = useWith(unapply(identity), [equals, always]);

// toggle :: a -> b -> (* -> *)
const toggle = compose(
  cond,
  juxt([equalsAndAlways, flip(equalsAndAlways), always([T, identity])])
);

Example:

toggle('on', 'off')('on'); //  'off'
toggle('active', 'inactive')('inactive'); // 'active'
toggle(10, 100)(10); // 100
toggle('on', 'off')('other'); // 'other'

camelizeKeys

Transform recursively all keys within object.

// Must be written as arrow `x => camelizeKeys(x)` due to recursion
// for `toCamelCase` use your favourite implementation. One is included in ramda-extension 
const camelizeObj = compose(
	fromPairs,
	map(juxt([
		o(toCamelCase, head),
		o(camelizeKeys, last),
	])),
	toPairs
);
const camelizeArray = map(camelizeKeys);

// camelizeKeys :: a -> a
const camelizeKeys = cond([
	[isArray, camelizeArray],
	[isFunction, identity],
	[isNotNilObject, camelizeObj],
	[T, identity],
]);

Example:

 camelizeKeys({
     'co-obj': { co_string: 'foo' },
     'co-array': [0, null, { 'f-f': 'ff' }],
     'co-number': 1,
     'co-string': '1',
     'co-fn': head,
 });

 // {
 //     coArray: [
 //         0,
 //         null,
 //         {
 //             fF: 'ff'
 //         }
 //     ],
 //     coFn: {},
 //     coNumber: 1,
 //     coObj: {
 //         coString: 'foo'
 //     },
 //     coString: '1'
 // }

mergeTo

Merge lists into one list based on the result of calling a String/Number/Boolean-returning function

/**
 * (a → String/Number/Boolean) -> [a] -> [a] -> [a]
 * Returns the result of merging the given lists based on the result of calling a String/Number/Boolean-returning function( @see R.groupBy) on each element
 * 
 * If a a String-returning exists in both list, the object from the right list will be used.
 * 
 * @example 
 * const mergeListById = mergeTo(R.prop('id'));
 * const mergedList = mergeListById([{id:0, count: 99},{id:3, count: 103}], 
 *    [{id:0, count: 100},{id:1, count: 101},{id:2, count: 102}]); =>[{id:0, count: 100},{id:1, count: 101},{id:2, count: 102}, {id:3, count: 101}] 
 * 
 *  mergeTo(R.identity,[1,2,3], [1,5]); => [1, 2, 3, 5]
 * 
 * @param {*} fn Function :: a -> String/Number/Boolean
 * @param {*} l  An Array
 * @param {*} r  An Array
 * @returns {[]} An Array
 */
const mergeTo = R.curry((fn, l, r) =>R.pipe(
     R.useWith(R.merge,  [R.groupBy(fn), R.groupBy(fn)])(l),
     R.values,
     R.flatten
)(r));     

drive

Poor man's pipeline operator.

const drive = R.reduce(R.applyTo);

> drive(2, [ R.multiply(2), R.add(2) ]);
6

applyEach

Simultaneously apply a list of functions.

const applyEach = curry((fns, x) => map(applyTo(x), fns))

> applyEach([inc, identity, dec, always('Go!')], 2)
[3, 2, 1, 'Go!']

steps

Apply a list of functions in sequence.

const steps = curry((fns, x) => scan(applyTo, x, fns))

> steps([update(0, 1), update(1, 1), update(2, 1)], [0, 0, 0])
[
 [0, 0, 0], 
 [1, 0, 0], 
 [1, 1, 0], 
 [1, 1, 1]
]