У вас есть забавный вопрос здесь. Я только что написал об этом недавно , поэтому перейдите по этой ссылке, если вам интересны идеи, представленные в этом ответе -
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!