Cookbook - ramda/ramda GitHub Wiki
Recipes
pickIndexes
: Pick values from a list by indexeslist
: Create alist
functioncssQuery
/setStyle
: Mess with the DOM- Apply a list of functions in a specific order into a list of values
dotPath
/propsDotPath
: Derivative ofR.props
for deep fields- Use ramda-fantasy Future to wrap AJAX
- Get n function calls as a list
defaults
: Set properties only if they don't existmethodNames
: Get an object's method namesobjFromKeys
: Make an object out of keys, with values derived from themobjFromListWith
: Returns an object from a given list where the key name for each value is the result of calling the specified function with the valuemapKeys
: Map keys of an objectrenameKeys
: Rename keys of an objectrenamekey
: Rename key of an objectrenameKeyWith
: Rename key of an object (rename key by a function)overlaps
: Problem: Do any of these strings appear in another list?objSize
: Get object sizefindById
: Get object by idconvert
: Convert object to arrayrangeStep
: Create an incrementing or decrementing range of numbers with a stepfilterWithKeys
: Filter an object using keys as well as valuesdiffObjs
: diffing objects (similar to Guava's Maps.Difference)- Convert a list of property-lists (with header) into a list of objects
mapPath
: Map over the value at the end of a pathapplyN
: Apply a given function N timessortByPredicate
: Sort a List by a given unary predicateflattenObj
: Flatten a nested object into dot-separated key / value pairssortByProps
: Sort a List by array of props (if first prop equivalent, sort by second, etc.)omitWhen
: Remove a subset of keys from an object whose associated values satisfy a given predicategroupByMultiple
: Group by multiplelinearScale
: Linear Scaling of arrayinspect
: Use a spec to get some parts of a data structurewhereAll
: Sort of like a recursive version of Ramda'swhere
zipLongest
: Zip lists to the longest list's lengthseparate
: Separate a list into n partsquantile
: Make an object from an array using a mapper functionlookup
: Look up the property corresponding to a string in a lookup objectunpivot
: Unpivot a tableunpivotRest
: Unpivot any other columns in a tablepivot
: Pivot a tablepivotWith
: Pivot a table with a function to resolve conflicts (multiple values for the same attribute)- SQL-style JOINs
size
: length for either arrays or objectsspreadPath
: Spreads object under property path onto provided objectspreadProp
: Spreads object under property onto provided objectflattenPath
: Flattens a property path so that its fields are spread out into the provided objectflattenProp
: Flattens a property so that its fields are spread out into the provided objectpaths
: Acts as multiple path: arrays of paths in, array of values out. Preserves ordermergeProps
: functional equivalent of merging object properties with object spread operatormergePaths
: Merge objects under corresponding pathsmergeProp
: Create a new object with the own properties of the object under thep
merged with the own properties of the providedsource
. If a key exists in both objects, the value from thesource
object will be usedmergePath
: Create a new object with the own properties of the object under thepath
merged with the own properties of the providedsource
. If a key exists in both objects, the value from thesource
object will be usedlensEq
: Returnstrue
if data structure focused by the given lens equals provided valuelensSatisfies
: Returnstrue
if data structure focused by the given lens satisfies the predicateviewOr
: Returns a "view" of the given data structure, determined by the given lens. The lens's focus determines which portion of the data structure is visible. Returns the defaultValue if "view" is null, undefined or NaN; otherwise the "view" is returned.overIf
: Appliesover
to lens only if lens value is notnull
orundefined
omitIndexes
: Returns a partial copy of an array omitting the indexes specified.assocPaths
: Set or override values at specific paths usingassocPath
(ceteris paribus).xPairs
: Create pairs from value and list of values.toggle
: Get the opposite value comparing against a given set of two values.camelizeKeys
: Transform recursively all keys within object.mergeTo
: Merge lists into one list based on the result of calling a String/Number/Boolean-returning function.applyEach
: Simultaneously apply a list of functionssteps
: Sequentially apply a list of functions
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']
list
function
Create a 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' ]));
R.props
for deep fields
Derivative of 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)
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:
- https://www.measurethat.net/Benchmarks/ShowResult/4127
- https://www.measurethat.net/Benchmarks/ShowResult/4128
- https://www.measurethat.net/Benchmarks/ShowResult/4133
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'
- Idea
- Included in ramda-extension
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'
// }
- Idea
- Included in ramda-extension
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]
]