Cookbook

GingerPlusPlus edited this page Aug 5, 2018 · 189 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.compose(R.values, R.pickAll);
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 = 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));

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'
});

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...', ... }

Make an object out of a list, with keys derived form 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(fn, 0), 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' }

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

//    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
objSize(); // 0
objSize(undefined); // 0
objSize(null); // 0
objSize(false); // 0
objSize(true); // 1
objSize(''); // 0
objSize('foo'); // 3
objSize(0); // 1
objSize(13); // 2
objSize(101); // 3
objSize(0.01); // 4
objSize({}); // 0
objSize({foo:undefined, bar:undefined}); // 2
objSize([]); // 0
objSize([undefined, undefined]); // 2

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)]
);

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 }

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

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

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.contains(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(unnaply(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((x) => camelizeKeys(x), last),
	])),
	toPairs
);
const camelizeArray = map((x) => camelizeKeys(x));

// 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));     
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.