Javascript: перемешивание групп объектов в массиве - PullRequest
0 голосов
/ 12 апреля 2020

У меня есть массив объектов, где я отсортировал по ключу (group ниже) так, что все объекты с одинаковым значением для group смежны друг с другом в индексах data. Например:

var data = [{foo: "cat", group:"house"},
            {foo: "cat", group: "house"},
            {foo: "cat", group: "tree"},
            {foo: "dog", group: "tree"},
            {foo: "dog", group: "car"}];

Я пытаюсь перетасовать порядок объектов в массиве data, сохраняя порядок в пределах значений ключа group. Другими словами, я пытаюсь перетасовать группы объектов в data, а не отдельные объекты. Хотя я знаю, как перемешивать объекты в массиве, я не знаю, как перемешивать группы объектов в массиве.

Я думал, что может быть способ использовать тот факт, что значения группы изменяются только при смене группы.

Ответы [ 3 ]

1 голос
/ 13 апреля 2020

Просто создайте случайное свойство для сортировки на уровне группы и назначьте свойство каждому соответствующему объекту в массиве:

var data = [{foo: "cat", group: "house"},
            {foo: "cat", group: "house"},
            {foo: "cat", group: "tree"},
            {foo: "dog", group: "tree"},
            {foo: "dog", group: "car"}];

//get random sorting at the group level (via a hashtable)
let randomGroupSortKey = {}
data.forEach(d => randomGroupSortKey[d.group] = Math.random())
console.log("Group sort keys:", randomGroupSortKey)

//add the sortKey property to the individual array entries
let dataSortable = data.map(x => {
  return {
    ...x, 
    sortKey: randomGroupSortKey[x.group]
  }
})

dataSortable.sort((a, b) => a.sortKey - b.sortKey) //sort the groups!

console.log("Result:", dataSortable)
console.log("Result without sortKey:", dataSortable.map(({ sortKey, ...x }) => x))
1 голос
/ 13 апреля 2020

Вы можете сначала сгруппировать объекты по их свойству group, затем перетасовать группы, и, наконец, удалить их.

function groupBy(iterable, keyFn = obj => obj) {
  const groups = new Map();
  for (const item of iterable) {
    const key = keyFn(item);
    if (!groups.has(key)) groups.set(key, []);
    groups.get(key).push(item);
  }
  return groups;
}

function shuffle(array) {
  array = array.slice(0);
  for (let limit = array.length; limit > 0; --limit) {
    const index = Math.floor(Math.random() * limit);
    array.push(...array.splice(index, 1));
  }
  return array;
}

var data = [{foo:"cat",group:"house"},{foo:"cat",group:"house"},{foo:"cat",group:"tree"},{foo:"dog",group:"tree"},{foo:"dog",group:"car"}];

data = groupBy(data, obj => obj.group);
data = Array.from(data.values());
data = shuffle(data);
data = data.flat();

console.log(data);
1 голос
/ 12 апреля 2020

У вас есть забавный вопрос здесь. Я только что написал об этом недавно , поэтому перейдите по этой ссылке, если вам интересны идеи, представленные в этом ответе -

const randInt = (n = 0) =>
  Math.floor(Math.random() * n)

const { empty, contramap, concat } =
  Comparison

const sortByGroup =
  contramap(empty, x => x.group)

const sortByRand =
  contramap(empty, _ => randInt(3) - 1) // -1, 0, 1

Интуитивно, мы используем contramap(empty, ...), чтобы провести новое сравнение (сортировщик). concat - это то, что мы используем для объединения одного сравнения с другим -

// sort by .group then sort by rand
const mySorter =
  concat(sortByGroup, sortByRand) 

Наше сравнение подключается непосредственно к Array.prototype.sort -

const data =
  [ { name: "Alice", group: "staff" }
  , { name: "Monty", group: "client" }
  , { name: "Cooper", group: "client" }
  , { name: "Jason", group: "staff" }
  , { name: "Farrah", group: "staff" }
  , { name: "Celeste", group: "guest" }
  , { name: "Briana", group: "staff" }
  ]

console.log("first", data.sort(mySorter)) // shuffle once
console.log("second", data.sort(mySorter)) // shuffle again

В выводе мы видим элементы, сгруппированные по group и затем рандомизированный -

// first
[ { name: "Cooper", group: "client" }
, { name: "Monty", group: "client" }
, { name: "Celeste", group: "guest" }
, { name: "Alice", group: "staff" }
, { name: "Jason", group: "staff" }
, { name: "Farrah", group: "staff" }
, { name: "Briana", group: "staff" }
]

// second
[ { name: "Monty", group: "client" }
, { name: "Cooper", group: "client" }
, { name: "Celeste", group: "guest" }
, { name: "Farrah", group: "staff" }
, { name: "Alice", group: "staff" }
, { name: "Jason", group: "staff" }
, { name: "Briana", group: "staff" }
]

Наконец, мы реализуем 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))
  }

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

Разверните фрагмент ниже, чтобы проверить результаты в своем собственном браузере. Запустите программу несколько раз , чтобы увидеть, что результаты всегда упорядочены по group, а затем рандомизированы -

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

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

const randInt = (n = 0) =>
  Math.floor(Math.random() * n)

const { empty, contramap, concat } =
  Comparison

const sortByGroup =
  contramap(empty, x => x.group)

const sortByRand =
  contramap(empty, _ => randInt(3) - 1) // -1, 0, 1

const mySorter =
  concat(sortByGroup, sortByRand) // sort by .group then sort by rand

const data =
  [ { name: "Alice", group: "staff" }
  , { name: "Monty", group: "client" }
  , { name: "Cooper", group: "client" }
  , { name: "Jason", group: "staff" }
  , { name: "Farrah", group: "staff" }
  , { name: "Celeste", group: "guest" }
  , { name: "Briana", group: "staff" }
  ]
   
console.log(JSON.stringify(data.sort(mySorter))) // shuffle once
console.log(JSON.stringify(data.sort(mySorter))) // shuffle again

небольшое улучшение

Вместо жестко закодированных сортировщиков, таких как sortByGroup, мы можем сделать параметризованное сравнение, sortByProp -

const sortByProp = (prop = "") =>
  contramap(empty, (o = {}) => o[prop])

const sortByFullName =
  concat
    ( sortByProp("lastName")  // primary: sort by obj.lastName
    , sortByProp("firstName") // secondary: sort by obj.firstName
    )

data.sort(sortByFullName) // ...

почему модуль?

Преимущества определения отдельного Comparison модуля многочисленны но я не буду повторять их здесь. Модуль позволяет легко моделировать сложные сортировочные логи c -

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

const sortByAge =
  contramap(empty, x => x.age)

const data =
  [ { name: 'Alicia', age: 10 }
  , { name: 'Alice', age: 15 }
  , { name: 'Alice', age: 10 }
  , { name: 'Alice', age: 16 }
  ]

Сортировать по name, затем сортировать по age -

data.sort(concat(sortByName, sortByAge))
// [ { name: 'Alice', age: 10 }
// , { name: 'Alice', age: 15 }
// , { name: 'Alice', age: 16 }
// , { name: 'Alicia', age: 10 }
// ]

Сортировать по age затем сортируйте по name -

data.sort(concat(sortByAge, sortByName))
// [ { name: 'Alice', age: 10 }
// , { name: 'Alicia', age: 10 }
// , { name: 'Alice', age: 15 }
// , { name: 'Alice', age: 16 }
// ]

и без усилий reverse любого сортировщика. Здесь мы сортируем по name, затем выполняем обратную сортировку по age -

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

data.sort(concat(sortByName, reverse(sortByAge)))
// [ { name: 'Alice', age: 16 }
// , { name: 'Alice', age: 15 }
// , { name: 'Alice', age: 10 }
// , { name: 'Alicia', age: 10 }
// ]

принципам работы

Наш Comparison модуль является гибким, но надежным. Это позволяет нам писать наши сортировщики по формуле -

// this...
concat(reverse(sortByName), reverse(sortByAge))

// is the same as...
reverse(concat(sortByName, sortByAge))

И аналогично с concat выражениями -

// this...
concat(sortByYear, concat(sortByMonth, sortByDay))

// is the same as...
concat(concat(sortByYear, sortByMonth), sortByDay)

// is the same as...
nsort(sortByYear, sortByMonth, sortByDay)

multi-sort

Поскольку наши сравнения могут быть объединены для создания более сложных сравнений, мы можем эффективно сортировать по произвольному числу факторов. Например, для сортировки объектов даты требуется три сравнения: year, month и day. Благодаря функциональным принципам наши concat и empty выполняют всю тяжелую работу -

const Comparison =
  { // ...
  , nsort: (...m) =>
      m.reduce(Comparison.concat, Comparison.empty)
  }

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

const data =
  [ { year: 2020, month: 4, day: 5 }
  , { year: 2018, month: 1, day: 20 }
  , { year: 2019, month: 3, day: 14 }
  ]

const sortByDate =
  nsort
    ( contramap(empty, x => x.year)  // primary: sort by year
    , contramap(empty, x => x.month) // secondary: sort by month
    , contramap(empty, x => x.day)   // tertiary: sort by day
    )

Теперь мы можем сортировать по year, month, day -

data.sort(sortByDate)
// [ { year: 2019, month: 11, day: 14 }
// , { year: 2020, month: 4, day: 3 }
// , { year: 2020, month: 4, day: 5 }
// ]

И так же легко выполнить обратную сортировку по year, month, day -

data.sort(reverse(sortByDate))
// [ { year: 2020, month: 4, day: 5 }
// , { year: 2020, month: 4, day: 3 }
// , { year: 2019, month: 11, day: 14 }
// ]

Чтобы запустить примеры reverse и nsort, следуйте по оригинальный пост ?


сложная сортировка

Вы, конечно, ищете нюансированный сортировщик, но не беспокойтесь, наш модуль способен его обработать -

const { empty, contramap } =
  Comparison

const randParitionBy = (prop = "", m = new Map) =>
  contramap
    ( empty
    , ({ [prop]: value }) =>
        m.has(value)
          ? m.get(value)
          : ( m.set(value, Math.random())
            , m.get(value)
            )
    )

console.log(data)                               // presort...
console.log(data.sort(randParitionBy("group"))) // first...
console.log(data.sort(randParitionBy("group"))) // again...

Выход -

// pre-sort
[ {name:"Alice",group:"staff"}
, {name:"Monty",group:"client"}
, {name:"Cooper",group:"client"}
, {name:"Jason",group:"staff"}
, {name:"Farrah",group:"staff"}
, {name:"Celeste",group:"guest"}
, {name:"Briana",group:"staff"}
]

// first run (elements keep order, but sorted by groups, groups are sorted randomly)
[ {name:"Celeste",group:"guest"}
, {name:"Alice",group:"staff"}
, {name:"Jason",group:"staff"}
, {name:"Farrah",group:"staff"}
, {name:"Briana",group:"staff"}
, {name:"Monty",group:"client"}
, {name:"Cooper",group:"client"}
]

// second run (elements keep order and still sorted by groups, but groups are sorted differently)
[ {name:"Alice",group:"staff"}
, {name:"Jason",group:"staff"}
, {name:"Farrah",group:"staff"}
, {name:"Briana",group:"staff"}
, {name:"Monty",group:"client"}
, {name:"Cooper",group:"client"}
, {name:"Celeste",group:"guest"}
]

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

const data =
  [ { name: "Alice", group: "staff" }
  , { name: "Monty", group: "client" }
  , { name: "Cooper", group: "client" }
  , { name: "Jason", group: "staff" }
  , { name: "Farrah", group: "staff" }
  , { name: "Celeste", group: "guest" }
  , { name: "Briana", group: "staff" }
  ]

const randParitionBy = (prop = "", m = new Map) =>
  contramap
    ( empty
    , ({ [prop]: value }) =>
        m.has(value)
          ? m.get(value)
          : ( m.set(value, Math.random())
            , m.get(value)
            )
    )

console.log(JSON.stringify(data.sort(randParitionBy("group")))) // run multiple times!
...