Cookbook
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.
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']
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]
// 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)
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' ]));
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 ]
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);
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
// 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']
// 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
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' } }
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 }
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' }
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 }
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 }
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
// 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 :: {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"}]
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]
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}
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 }
}
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"}
// ]
// :: [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 } } }
// 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)
// 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}, {}, {}]
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}
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}]
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
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.
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))
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
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
//
// 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
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']]
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}] ]
// 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' }
const lookup = R.flip(R.prop);
// usage:
// let cache = lookup({ a: 1, b: 2, c: 3 });
// cache('a')
// -> 1
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 }]
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 }]
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 }]
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 }]
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 }]
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);
const size = R.pipe(R.values, R.length);
// usage:
// size({ a: 1, b: 2, c: 3 }); // -> 3
// size(['a','b','c']); // -> 3
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: {} };
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 };
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 } } };
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 } };
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]
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 }
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 }
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 }
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 } }
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
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
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
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.
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']
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 } }
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 } }
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
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
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
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));
Poor man's pipeline operator.
const drive = R.reduce(R.applyTo);
> drive(2, [ R.multiply(2), R.add(2) ]);
6
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!']
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]
]