smitach
smitach

Reputation: 11

How to sort object with ramda js

I want to sort the following object,

const form = {
A_ALL: {A1_ALL: { A1_ONE: 'Y', A2_TWO: 'Y', A3_THREE: 'Y'}, A2: {A2_FOUR: 'Y', A2_FIVE: 'N', A2_SIX: 'Y'}, A3: 'Y', A4: 'N'},
B_ALL: {B1_ALL: { B1_ONE: 'Y', B1_TWO: 'Y', B1_THREE: 'Y'}, B2: {B2_FOUR: 'Y', B2_FIVE: 'Y', B2_SIX: 'Y'}, B3: 'Y', B4: 'Y'},
C_ALL: {XX: 'Y', YY:'Y'},
D: 'Y',
E: 'N'
}

I am implementing a form with nested check boxes who's value can be 'Y' or 'N'. I want to capture only the 'Y' check boxes, and if all of the child check boxes are 'Y', I want to discard them and get only parent checkbox. Above is the object which has all the check box values. I want the object to be sorted to:

{
A_ALL: {A1_ALL: 'Y', A2: {A2_FOUR: 'Y', A2_SIX: 'Y'}, A3: 'Y'},
B_ALL : 'Y',
C: 'Y',
D: 'Y'
}

My code so far looks like this:

const FORM = ({ A1_ALL: { a: 'Y', b: 'Y', c: 'Y' }, B1_ALL: { a: 'Y', b: 'Y', c: 'N' }, C1_ALL: { a: 'Y', b: 'Y', c: 'Y' }, }) 
const eY = equals('Y') 
const isSelected = compose(all(eY), values) 
const groupValue = when(isSelected, always('Y')); 
const formValue = map(groupValue) formValue(FORM) Result is {A1_ALL: "Y", C1_ALL: "Y", B1_ALL: {a: "Y", b: "Y", c: "N"}}

Upvotes: 0

Views: 2080

Answers (3)

user1726343
user1726343

Reputation:

Here is a slightly different approach that (afaict) produces the same result as Scott Christopher's answer:

// Ramda doesn't support folding objects, so we have to define this here
// :: Monoid m -> (a -> m) -> StrMap a -> m
const foldMap = M => f => o =>
  R.keys(o).reduce((p, c) => M.append(p)(f(o[c])), M.empty);

// :: Monoid Boolean
const And = { empty: true, append: x => y => x && y };

// Each layer of the form can be thought of as a string map, where
// a key is mapped to one of the strings "Y" or "N", or to a value
// of some given type `a`
// :: type ValueOr a = "Y" | "N" | a
// :: type FormLayer a = StrMap (ValueOr a)

// We can think of a form as an infinite nesting of form layers
// :: type Fix f = f (Fix f)
// :: type Form = Fix FormLayer
//              = FormLayer (FormLayer (FormLayer (FormLayer ...)))

// Recursion schemes can help us promote a function for operating
// on one layer of the structure to a function that operates on an
// infinite nesting of layers
// :: Functor f -> (f a -> a) -> Fix f -> a
const cata = F => alg => {
  const rec = x => alg(F.map(rec)(x));
  return rec;
};

// It's useful to factor repeated case analysis into a pattern
const ValOr = {
  Y: "Y",
  N: "N",
  Other: x => x,
  match: ({ Y, N, Other }) => v => v === "Y" ? Y : v === "N" ? N : Other(v),
  map: f => ValOr.match({ Y, N, Other: f })
};
const { Y, N, Other } = ValOr;

// :: Functor FormLayer
const FormLayer = { map: f => R.map(ValOr.map(f)) };

// :: ValueOr _ -> Boolean
const isY = ValOr.match({ Y: true, N: false, Other: _ => false });
// :: FormLayer _ -> Boolean
const allYs = foldMap(And)(isY);
// :: Form -> ValueOr Form
const squashYs = cata(FormLayer)(o => allYs(o) ? Y : Other(o));

// :: ValueOr _ -> Boolean
const isntN = ValOr.match({ Y: true, N: false, Other: _ => true });
// :: Form -> Form
const filterNs = cata(FormLayer)(R.filter(isntN));

// :: Form -> ValueOr Form
const f = R.pipe(
  // Squash Y-only objects recursively
  squashYs,
  // If the top level result is still a form, filter out the "N"s recursively
  ValOr.match({ Y, N, Other: filterNs })
);

// :: Form
const form = {
  A_ALL: {
    A1_ALL: { A1_ONE: "Y", A2_TWO: "Y", A3_THREE: "Y" },
    A2: { A2_FOUR: "Y", A2_FIVE: "N", A2_SIX: "Y" },
    A3: "Y",
    A4: "N"
  },
  B_ALL: {
    B1_ALL: { B1_ONE: "Y", B1_TWO: "Y", B1_THREE: "Y" },
    B2: { B2_FOUR: "Y", B2_FIVE: "Y", B2_SIX: "Y" },
    B3: "Y",
    B4: "Y"
  },
  C_ALL: { XX: "Y", YY: "Y" },
  D: "Y",
  E: "N"
};

// :: ValueOr Form
const result = f(form);

console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.min.js"></script>

Upvotes: 0

Scott Christopher
Scott Christopher

Reputation: 6516

You're close with your attempt, you just need to recursively call map for each object that it finds in the values of the object.

const form = { A_ALL: {A1_ALL: { A1_ONE: 'Y', A2_TWO: 'Y', A3_THREE: 'Y'}, A2: {A2_FOUR: 'Y', A2_FIVE: 'N', A2_SIX: 'Y'}, A3: 'Y', A4: 'N'}, B_ALL: {B1_ALL: { B1_ONE: 'Y', B1_TWO: 'Y', B1_THREE: 'Y'}, B2: {B2_FOUR: 'Y', B2_FIVE: 'Y', B2_SIX: 'Y'}, B3: 'Y', B4: 'Y'}, C_ALL: {XX: 'Y', YY:'Y'}, D: 'Y', E: 'N' }

const eY = R.equals('Y') 
const isSelected = R.compose(R.all(eY), R.values) 
const groupValue = R.when(isSelected, R.always('Y'))

const fn = objOrString => R.pipe(
  R.unless(R.is(String), R.map(fn)),
  R.unless(R.is(String), groupValue),
  R.unless(R.is(String), R.filter(R.either(R.is(Object), R.equals('Y'))))
)(objOrString)

console.log(fn(form))
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>

Upvotes: 1

Scott Sauyet
Scott Sauyet

Reputation: 50797

I have a partial solution, and no time at the moment to take the next step. It essentially filters out any values that are not 'Y's. You should then be able to add another step to slim this down by recursively replacing values which are objects containing only 'Y' values with just 'Y'.

const form = {
      A_ALL: {A1_ALL: { A1_ONE: 'Y', A2_TWO: 'Y', A3_THREE: 'Y'}, A2: {A2_FOUR: 'Y', A2_FIVE: 'N', A2_SIX: 'Y'}, A3: 'Y', A4: 'N'},
      B_ALL: {B1_ALL: { B1_ONE: 'Y', B1_TWO: 'Y', B1_THREE: 'Y'}, B2: {B2_FOUR: 'Y', B2_FIVE: 'Y', B2_SIX: 'Y'}, B3: 'Y', B4: 'Y'},
      C_ALL: {XX: 'Y', YY:'Y'},
      D: 'Y',
      E: 'N'
}

const isObject = s => Object.prototype.toString.call(s) == '[object Object]'
const isArray = s => Object.prototype.toString.call(s) == '[object Array]'

const collect = pairs => pairs.reduce(
  (a, [k, v]) => ({...a, [k]: isArray(v) ? collect(v) : v}),
  {}
)

const spread = val => obj => Object.entries(obj)
  .filter(([k, v]) => v == val || isObject(v))
  .map(([k, v]) => isObject(v) ? [k, spread(val)(v)] : [k, v])

const findMatchingKeys = (val, obj) => collect(spread(val)(obj))

console.log(findMatchingKeys('Y', form))

This solution doesn't use Ramda. I'm one of the founders of Ramda, and a big fan of the library, but I don't see Ramda offering much help here, only a few minor clean-ups.

Upvotes: 0

Related Questions