8000 Cookbook · ramda/ramda Wiki · GitHub
[go: up one dir, main page]

Skip to content
  • zhanggy edited this page Jul 8, 2025 · 215 revisions

    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 10000 -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]]
        }
      }, 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]] },
        ],
      },
    }
    
    var inspector = inspect(spec)
    
    console.log(inspector(legacyData))
    // Output will be:
    // {
    //   "url1": "httpblahbiddyblah1",
    //   "favorite2": {
    //     "imageUrl": "httpblahbiddyblah2",
    //     "public": false
    //   },
    //   "location2": [234, 567]
    // }

    Original discussion: #2038

    < 10BC8 p>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]]
    console.log("R.zip(ys, zs):", R.zip(ys, zs))
    //=> [[1, "a"], [2, "b"]]
    
    // Using `zipLongest`
    console.log("zipLongest(xs, ys):", zipLongest(xs, ys))
    //=> [[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]]

    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']]

    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']], 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']], 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]]
    const xPairs = useWith(xprod, [of, identity]);

    Example:

    xPairs(1, [2, 3]) // [[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]
    ]

    mapIndexed

    Map an array with index.

    const mapIndexed = addIndex(map);
    
    const arr = ['a', 'b', 'c'];
    > mapIndexed((x, i) => `${i}: ${x}`)(arr);
    ["0: a", "1: b", "2: c"]

    Clone this wiki locally

  • 0