Рамда групп с определенной структурой c - PullRequest
0 голосов
/ 31 марта 2020

Я пытаюсь сгруппировать некоторые элементы с помощью Ramda и создать из них простое вложенное меню. Я получаю такую ​​структуру из бэкэнда:

const testArray = [
  {
    "id":6,
    "type":{
      "name":"Test1",
      "category":"Cat A"
    },
    "typeName":"Test1",
    "categoryName":"Cat A"
  },
  {
    "id":34,
    "type":{
      "name":"Test2",
      "category":"Cat A"
    },
    "typeName":"Test2",
    "categoryName":"Cat A"
  },
  {
    "id":662,
    "type":{
      "name":"Test6",
      "category":null
    },
    "typeName":"Test6",
    "categoryName":null
  },
  {
    "id":62,
    "type":{
      "name":"Test7",
      "category":"Cat A"
    },
    "typeName":"Test7",
    "categoryName":"Cat A"
  },
  {
    "id":1190,
    "type":{
      "name":"Test8",
      "category":null
    },
    "typeName":"Test8",
    "categoryName":null
  },
  {
    "id":"other",
    "type":{
      "name":"Others",
      "seen":true
    },
    "typeName":"Others"
  }
];

Когда я пытаюсь сгруппировать ее, используя:

const testRamda = R.groupBy(R.prop('categoryName'));

Я получаю в результате объект с группами: Cat A, null, undefined, потому что 'categoryName 'содержат имя, ноль или ничего, поэтому оно не определено.

Это то, чего я пытаюсь достичь. Структура отсортирована в алфавитном порядке с «Другие» всегда как последний вариант.

[
  {
    "name": "Cat A",
    "category": "Cat A",
    "children": [
      {
        "name": "Test1"
      },
      {
        "name": "Test2"
      },
      {
        "name": "Test7",
      },
    ]
  },
  {
    "name": "Test6",
    "category": null
  },
  {
    "name": "Test8",
    "category": null
  },
  {
    "name": "Others"
  }
]

Я буду признателен за любую помощь

Ответы [ 3 ]

2 голосов
/ 01 апреля 2020

Мне кажется, что вы хотите сделать три разные вещи. Вы хотите сгруппировать свои элементы по категориям. Вы хотите отсортировать категории так, чтобы те, у которых нет категории, были в самом конце, а те, у которых null, были прямо перед этим. И вы хотите преобразовать свои элементы, сгруппировав категории в единые объекты, и оставив остальные разделенными.

Это делает несколько странное преобразование. Но это не так уж удивительно, так как и ваша структура ввода, и ваша структура вывода несколько странны.

Вот один из способов, который почти выполняет то, что вы спрашиваете:

const compare = ([a], [b]) => 
  a == 'undefined' ? (b == 'undefined' ? '0' : 1) : b == 'undefined' ? -1
    : a == 'null' ? (b == 'null' ? 0 : 1) : b == 'null' ? -1
    : a < b ? -1 : a > b ? 1 : 0
    
const makeCat = ([key, nodes]) => 
  key == 'null' || key == 'undefined'
    ? nodes .map (node => node .type)
    : [{name: key, category: key, children: nodes .map (({type: {name}}) => ({name}))}]

const transform = pipe (
  groupBy (prop ('categoryName')),
  toPairs, 
  sort (compare),
  chain (makeCat)
) 

// changing this to demonstrate proper grouping.
// const testArray = [{id: 6, type: {name: "Test1", category: "Cat A"}, typeName: "Test1", categoryName: "Cat A"}, {id: 34, type: {name: "Test2", category: "Cat A"}, typeName: "Test2", categoryName: "Cat A"}, {id: 662, type: {name: "Test6", category: null}, typeName: "Test6", categoryName: null}, {id: 62, type: {name: "Test7", category: "Cat A"}, typeName: "Test7", categoryName: "Cat A"}, {id: 1190, type: {name: "Test8", category: null}, typeName: "Test8", categoryName: null}, {id: "other", type: {name: "Others", seen: true}, typeName: "Others"}];
const testArray = [{id: 662, type: {name: "Test6", category: null}, typeName: "Test6", categoryName: null}, {id: 6, type: {name: "Test1", category: "Cat A"}, typeName: "Test1", categoryName: "Cat A"}, {id: 34, type: {name: "Test2", category: "Cat A"}, typeName: "Test2", categoryName: "Cat A"}, {id: 62, type: {name: "Test7", category: "Cat A"}, typeName: "Test7", categoryName: "Cat A"}, {id: 1190, type: {name: "Test8", category: null}, typeName: "Test8", categoryName: null}, {id: "other", type: {name: "Others", seen: true}, typeName: "Others"}];

console .log (
  transform (testArray)
)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.27.0/ramda.js"></script>
<script> const {pipe, groupBy, prop, toPairs, sort, chain} = R       </script>
  • compare используется для сортировки. Он помещает undefined категории в конец и null категории непосредственно перед этим, сортируя остальные с естественной сортировкой. (Есть хороший аргумент, чтобы сделать более декларативную версию этого, но это отдельный вопрос, я думаю.)

  • makeCat занимает имя категории и список узлов из исходного списка, а также создает и массив выходных узлов. Он обрабатывает дела null / undefined отдельно от дел настоящей категории. Когда есть истинная категория, она создает массив, содержащий один элемент со свойствами name, category и children. Если категория null / undefined, мы просто извлекаем свойство type из каждого дочернего элемента и возвращаем их массив. Вот что вы можете изменить, если у вас не работает немного другой вывод.

  • transform сначала сворачивает все это вместе в конвейер группирование по элементам по их свойству categoryName, затем преобразование полученного объекта в массив из пар key - value, сортировка результата по compare, а затем вызов chain (аналогично Array.prototype.flatMap в в этом случае) с makeCat в результирующем массиве.

Единственное отличие в поведении, которое я отмечаю из вашего обновленного запроса на вывод, заключается в том, что он включает свойство seen в последнем узле. Это потому, что мы просто повторно используем элемент type. Если вам нужен какой-то другой процесс, вы можете просто заменить node => node .type на что-то более подходящее.

0 голосов
/ 03 апреля 2020

const {ascend, apply, applySpec, complement, compose, concat, descend, groupBy, has, head, last, map, partition, pick, pipe, prop, sortWith, toPairs, useWith} = R;
const testArray = [{"id":1192,"type":{"name":"Z1","category":null},"typeName":"Z1","categoryName":null},{"id":1191,"type":{"name":"A1","category":null},"typeName":"A1","categoryName":null},{"id":6,"type":{"name":"Test1","category":"Cat A"},"typeName":"Test1","categoryName":"Cat A"},{"id":34,"type":{"name":"Test2","category":"Cat A"},"typeName":"Test2","categoryName":"Cat A"},{"id":662,"type":{"name":"Test6","category":null},"typeName":"Test6","categoryName":null},{"id":62,"type":{"name":"Test7","category":"Cat A"},"typeName":"Test7","categoryName":"Cat A"},{"id":1190,"type":{"name":"Test8","category":null},"typeName":"Test8","categoryName":null},{"id":"other","type":{"name":"Others","seen":true},"typeName":"Others"}];

const withCategoryTransformation = pipe(
  groupBy(prop("categoryName")),
  toPairs,
  map(applySpec({
    name:     head,
    category: head,
    children: pipe(last, map(compose(pick(["name"]), prop("type")))),
  })),
);

const withoutCategoryTransformation = 
  map(compose(pick(["name", "category"]), prop("type")));

const sortCriteria = [
  descend(has("category")),             // presence of "category" property
  ascend(complement(prop("category"))), // truthiness of "category" value
  ascend(prop("name")),                 // "name" value
];

const toMenuStructure = pipe(
  partition(prop("categoryName")),
  apply(useWith(concat, [withCategoryTransformation, withoutCategoryTransformation])),
  sortWith(sortCriteria),
);

console.log(toMenuStructure(testArray));
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.0/ramda.min.js"></script>
  1. Я начну с того, что разделю коллекцию на две группы. Те, у кого есть название категории, и те, у кого нет названия категории. (На основе достоверности значения "categoryName".)

  2. Выполнение различных операций для коллекции с именем категории и без категории, объединяя результирующие значения.

    Следующие операции выполняются для группы с именем категории:

    1. Сначала сгруппируйте имена с именем категории по их свойству "categoryName".

    2. Преобразовать пары ключ / значение возвращаются в список.

    3. Преобразуйте каждый элемент, используя ключ группы (head) в качестве значения "name" и "category". Затем выберите свойство «name» значения «type» для каждого элемента (last) и используйте его как «children».

      // from
      [
        "Cat A",
        [
          {
            id: 34,
            type: {name: "Test1", category: "Cat A"},
            typeName: "Test1",
            categoryName: "Cat A"
          },
          /* ... */
        ]
      ]
      // to
      {name: "Cat A", category: "Cat A", children: [{name: "Test1"}, /* ... */]}
      

    Группа без категории выполняет только одна операция:

    1. Выберите свойства "name" и "category" значения "type" для каждого элемента. (Удаление свойства visible).
  3. Затем мы сортируем все объекты по следующему: наличие свойства "category", достоверность / ложность значения категории, наконец значение свойства "name".


Я решил отсортировать «Others» до конца, проверив наличие свойства «category». Если вы достаточно определенно отфильтровываете имя «Другие», вы можете

// replace
descend(has("category"))
// with
ascend(propEq("name", "Others"))

В зависимости от предпочтений вы также можете go для альтернативных версий withCategoryTransformation. Вот две альтернативы для последней операции в pipe:

// Non point-free, but uses a more native JavaScript style.
map(([category, items]) => ({
  name:     category,
  category: category,
  children: items.map(compose(pick(["name"]), prop("type"))),
}))

// A point-free version that executes separate operations for the category
// and items, then merges the results together.
map(apply(useWith(mergeLeft, [
  applySpec({ name: identity, category: identity }),
  applySpec({ children: map(compose(pick(["name"]), prop("type"))) }),
])))
0 голосов
/ 02 апреля 2020

Чтобы расширить ответ Скотта, мы собираемся сделать небольшой модуль Comparison. Но прежде чем мы увязнем в реализации, я хочу сначала установить sh ожидание того, как оно должно работать. Мы начнем с написания двух независимых сравнений -

const { empty, contramap, concat } =
  Comparison

const hasUndefinedCategory =
  contramap(empty, x => x.category === undefined)

const hasNullCategory =
  contramap(empty, x => x.category === null)

Далее мы покажем, как можно комбинировать сравнения -

// primary sort: hasUndefinedCategory
// secondary sort: hasNullCategory
arr.sort(concat(hasUndefinedCategory, hasNullCategory))

Comparison должен подчиняться моноидному функционалу l aws, поэтому мы можем ожидать объединить любое количество сравнений. N-сортировка работает следующим образом -

// primary sort: hasUndefinedCategory
// secondary sort: hasNullCategory
// tertiary sort: ...
arr.sort([ hasUndefinedCategory, hasNullCategory, ... ].reduce(concat, empty))

Что касается записей, которые имеют children, вы можете сортировать, используя sortByName -

const sortByName =
  contramap(empty, x => x.name)

arr.forEach(({ children = [] }) => children.sort(sortByName))

Наконец, реализуйте модуль Comparison -

const Comparison =
  { empty: (a, b) =>
      a < b ? -1
        : a > b ? 1
          : 0
  , contramap: (m, f) =>
      (a, b) => m(f(a), f(b))
  , concat: (m, n) =>
      (a, b) => Ordered.concat(m(a, b), n(a, b))
  }

Что зависит от реализации модуля Ordered -

const Ordered =
  { empty: 0
  , concat: (a, b) =>
      a === 0 ? b : a
  }

Теперь, когда все готово, вы можете go вернуться и поддерживать такие вещи, как reverse сортировка -

const Comparison =
  { // ...
  , reverse: (m) =>
      (a, b) => m(b, a)
  }

const { empty, contramap, concat, reverse } =
  Comparison

// N-sort
// first: hasUndefinedCategory in descending order
// second: hasNullCategory in ascending order (default)
// third: ...
const complexSort =
  [ reverse(hasUndefinedCategory)
  , hasNullCategory
  , ... 
  ].reduce(concat, empty)

arr.sort(complexSort)
...