这是一个基本函数,它采用间隔长度 n 和一组数字(表示可用分钟数)。
通过调用getIntervals (30) (availableMinutes),我们将获得所有可用的时间段……
注意,下面的range 过程仅用于创建示例数据。您无需将其包含在您的程序中。
// getIntervals :: Number -> [Number] -> [{from: Number, to: Number}]
const getIntervals = n=> availability=> {
// intervals computed by reducing availability ...
let {slots} = availability.reduce(({slots, count, prev}, m)=> {
// initialize state with first minute
if (prev === undefined)
return {slots, count, prev: m}
// if current interval is empty, we must begin on an interval marker
else if (count === 0 && prev % n !== 0)
return {slots, count, prev: m}
// if current minute is non-sequential, restart search for next interval
else if (prev + 1 !== m)
return {slots, count: 0, prev: m}
// if current interval is complete, concat valid interval
else if (count === n - 1)
return {slots: [...slots, {from: m - n, to: prev}], count: 0, prev: m}
// otherwise, current minute is sequential, add to current interval
else
return {slots, count: count + 1, prev: m}
}, {slots: [], count: 0, prev: undefined})
// return `slots` value from reduce computation
return slots
}
// range :: Number -> Number -> [Number]
const range = min=> max=> {
let loop = (res, n) => n === max ? res : loop([...res, n], n + 1)
return loop([], min)
}
// create sample data
let availability = [
...range (55) (400), // [55, 56, 57, ..., 399]
...range (3111) (3333), // [3111, 3112, 3113 ,..., 3332]
...range (8888) (9000) // [8888, 8889, 8890, ..., 8999]
]
// get the intervals
console.log(getIntervals (30) (availability))
只要确保这些分钟标记是UTC timestamp 的分钟偏移量,一切都会变得简单。显示这些分钟标记只需将 X 分钟添加到设置为资源周开始的 UTC 时间戳。
我们将在这里创建一个名为 timestampAddMinutes 的小程序,它采用分钟数并根据资源的时间戳将其转换为 Date 对象 week
然后,我们创建一个 getIntervalDates 过程,将其应用于数组中每个区间的每个 from 和 to 值
// timestampAddMinutes :: Date -> Number -> Date
const timestampAddMinutes = t=> m=> {
let d = new Date(t.getTime())
d.setMinutes(t.getMinutes() + m)
return d
}
// getIntervalDates :: [{from: Minute, to: Minute}] -> [{from: Date, to: Date}]
const getIntervalDates = intervals => {
return intervals.map(({from,to}) => ({
from: timestampAddMinutes (week) (from),
to: timestampAddMinutes (week) (to)
}))
}
// sample timestamp for a resource
// Monday at midnight, timezone offset 0
let week = new Date("2016-08-08 00:00:00 +0000")
// interval result from previous code section
let intervals = [
{ from: 60, to: 89 }, { from: 90, to: 119 }, { from: 120, to: 149 },
{ from: 150, to: 179 }, { from: 180, to: 209 }, { from: 210, to: 239 },
{ from: 240, to: 269 }, { from: 270, to: 299 }, { from: 300, to: 329 },
{ from: 330, to: 359 }, { from: 360, to: 389 }, { from: 3120, to: 3149 },
{ from: 3150, to: 3179 }, { from: 3180, to: 3209 }, { from: 3210, to: 3239 },
{ from: 3240, to: 3269 }, { from: 3270, to: 3299 }, { from: 3300, to: 3329 },
{ from: 8910, to: 8939 }, { from: 8940, to: 8969 }
]
// convert intervals to dates
console.log(getIntervalDates(intervals))
您会看到from 和to 分别转换为Date 对象。还要注意我的浏览器(在 EDT 时区中)如何自动转换 UTC 时间戳以显示在我当前的时区中。上面的输出将以TZ 字符串格式显示它们,但它们是完全可用的 Date 对象。这意味着您可以拨打他们的任何Date method 以获取具体的详细信息,例如星期几、小时或分钟等。
由于我们的getIntervals 函数运行正常,您还会看到每个间隔为 30 分钟,并从 :00 或 :30 开始
输出
[ { from: Sun Aug 07 2016 21:00:00 GMT-0400 (EDT), to: Sun Aug 07 2016 21:29:00 GMT-0400 (EDT) },
{ from: Sun Aug 07 2016 21:30:00 GMT-0400 (EDT), to: Sun Aug 07 2016 21:59:00 GMT-0400 (EDT) },
{ from: Sun Aug 07 2016 22:00:00 GMT-0400 (EDT), to: Sun Aug 07 2016 22:29:00 GMT-0400 (EDT) },
{ from: Sun Aug 07 2016 22:30:00 GMT-0400 (EDT), to: Sun Aug 07 2016 22:59:00 GMT-0400 (EDT) },
{ from: Sun Aug 07 2016 23:00:00 GMT-0400 (EDT), to: Sun Aug 07 2016 23:29:00 GMT-0400 (EDT) },
{ from: Sun Aug 07 2016 23:30:00 GMT-0400 (EDT), to: Sun Aug 07 2016 23:59:00 GMT-0400 (EDT) },
{ from: Mon Aug 08 2016 00:00:00 GMT-0400 (EDT), to: Mon Aug 08 2016 00:29:00 GMT-0400 (EDT) },
{ from: Mon Aug 08 2016 00:30:00 GMT-0400 (EDT), to: Mon Aug 08 2016 00:59:00 GMT-0400 (EDT) },
{ from: Mon Aug 08 2016 01:00:00 GMT-0400 (EDT), to: Mon Aug 08 2016 01:29:00 GMT-0400 (EDT) },
{ from: Mon Aug 08 2016 01:30:00 GMT-0400 (EDT), to: Mon Aug 08 2016 01:59:00 GMT-0400 (EDT) },
{ from: Mon Aug 08 2016 02:00:00 GMT-0400 (EDT), to: Mon Aug 08 2016 02:29:00 GMT-0400 (EDT) },
{ from: Wed Aug 10 2016 00:00:00 GMT-0400 (EDT), to: Wed Aug 10 2016 00:29:00 GMT-0400 (EDT) },
{ from: Wed Aug 10 2016 00:30:00 GMT-0400 (EDT), to: Wed Aug 10 2016 00:59:00 GMT-0400 (EDT) },
{ from: Wed Aug 10 2016 01:00:00 GMT-0400 (EDT), to: Wed Aug 10 2016 01:29:00 GMT-0400 (EDT) },
{ from: Wed Aug 10 2016 01:30:00 GMT-0400 (EDT), to: Wed Aug 10 2016 01:59:00 GMT-0400 (EDT) },
{ from: Wed Aug 10 2016 02:00:00 GMT-0400 (EDT), to: Wed Aug 10 2016 02:29:00 GMT-0400 (EDT) },
{ from: Wed Aug 10 2016 02:30:00 GMT-0400 (EDT), to: Wed Aug 10 2016 02:59:00 GMT-0400 (EDT) },
{ from: Wed Aug 10 2016 03:00:00 GMT-0400 (EDT), to: Wed Aug 10 2016 03:29:00 GMT-0400 (EDT) },
{ from: Sun Aug 14 2016 00:30:00 GMT-0400 (EDT), to: Sun Aug 14 2016 00:59:00 GMT-0400 (EDT) },
{ from: Sun Aug 14 2016 01:00:00 GMT-0400 (EDT), to: Sun Aug 14 2016 01:29:00 GMT-0400 (EDT) } ]
单个参数的威力
只是为了展示getIntervals 的灵活性,如果您将资源的间隔长度更改为 75 分钟(而不是 30 分钟),它会是什么样子。
// using the same `availability` input data and a 75-minute interval length
let availableDates = getIntervalDates (getIntervals (75) (availability))
console.log(availableDates)
输出 - 每个间隔为 75 分钟,并从 75 分钟间隔标记开始
[ { from: Sun Aug 07 2016 21:15:00 GMT-0400 (EDT), to: Sun Aug 07 2016 22:29:00 GMT-0400 (EDT) },
{ from: Sun Aug 07 2016 22:30:00 GMT-0400 (EDT), to: Sun Aug 07 2016 23:44:00 GMT-0400 (EDT) },
{ from: Sun Aug 07 2016 23:45:00 GMT-0400 (EDT), to: Mon Aug 08 2016 00:59:00 GMT-0400 (EDT) },
{ from: Mon Aug 08 2016 01:00:00 GMT-0400 (EDT), to: Mon Aug 08 2016 02:14:00 GMT-0400 (EDT) },
{ from: Wed Aug 10 2016 00:30:00 GMT-0400 (EDT), to: Wed Aug 10 2016 01:44:00 GMT-0400 (EDT) },
{ from: Wed Aug 10 2016 01:45:00 GMT-0400 (EDT), to: Wed Aug 10 2016 02:59:00 GMT-0400 (EDT) } ]
替代实现
如果您必须在代码中添加 cmets,您可能做错了什么。因此,我在上述实现中严重依赖 cmets 来明确意图,从而打破了我自己的一条规则。
这个实现改为使用微小的程序,每个程序都有很明显的意图。此外,我使用了switch 语句,它允许我们在逻辑上对一些循环响应进行分组,而无需巨大的if 条件。
// getIntervals :: Number -> [Number] -> [{from: Number, to: Number}]
const getIntervals = n=> availability=> {
let emptyCount = x=> x === 0
let filledCount = x=> x + 1 === n
let invalidPrev = x=> x === undefined
let invalidMarker = x=> x % n !== 0
let nonsequential = (x,y)=> x + 1 !== y
let make = (from,to) => ({from,to})
return availability.reduce(({acc, count, prev}, m)=> {
switch (true) {
case invalidPrev(prev):
case emptyCount(count) && invalidMarker(prev):
case nonsequential(prev, m):
return {acc, count: 0, prev: m}
case filledCount(count):
return {acc: [...acc, make(m - n, prev)], count: 0, prev: m}
default:
return {acc, count: count + 1, prev: m}
}
}, {acc: [], count: 0, prev: undefined}).acc
}
// range :: Number -> Number -> [Number]
const range = min=> max=> {
let loop = (res, n) => n === max ? res : loop([...res, n], n + 1)
return loop([], min)
}
// create sample data
let availability = [
...range (55) (400), // [55, 56, 57, ..., 399]
...range (3111) (3333), // [3111, 3112, 3113 ,..., 3332]
...range (8888) (9000) // [8888, 8889, 8890, ..., 8999]
]
// get the intervals
console.log(getIntervals (30) (availability))
现在您可以看到循环仅以 3 种方式之一响应。
- 它会重置
count 并将prev 更新到当前分钟
- 它将一个新间隔(使用
make)附加到acc 并执行count 重置
- 或者,作为
default的情况,它增加count并更新prev
此外,现在程序已经被分解成小块,您可以看到其中一些可以轻松地在应用程序的其他区域中重用
// eq :: a -> a -> Bool
const eq = x=> y=> y === x
// isZero :: Number -> Bool
const isZero = eq (0)
// isUndefined :: a -> Bool
const isUndefined = eq (undefined)
// isSequential :: Number -> Number -> Bool
const isSequential = x=> eq (x + 1)
// isDivisibleBy :: Number -> Number -> Boolean
const isDivisibleBy = x=> y=> (isZero) (y % x)
// getIntervals :: Number -> [Number] -> [{from: Number, to: Number}]
const getIntervals = n=> availability=> {
let make = (from,to) => ({from,to})
return availability.reduce(({acc, count, prev}, m)=> {
switch (true) {
case isUndefined (prev):
case isZero (count) && ! isDivisibleBy (n) (prev):
case ! isSequential (prev) (m):
return {acc, count: 0, prev: m}
case isSequential (count) (n):
return {acc: [...acc, make(m - n, prev)], count: 0, prev: m}
default:
return {acc, count: count + 1, prev: m}
}
}, {acc: [], count: 0, prev: undefined}).acc
}
是的,它的工作原理是一样的。这里的想法是,如果您要不止一次地做同样的事情,为什么不定义一个过程并使用它呢?您必须做到这一点,但您可以看到通用的微型程序如何降低getIntervals 的整体复杂性。
现在,任何时候你想检查两个数字是否是连续的,你都有一个过程——isSequential。与isDivisibleBy 相同——您可以轻松检查y 是否可以被x 整除,而无需在应用中需要的任何地方复制y % x === 0。
而且,不管你信不信,即使我在这里给你的这些小程序也可以分解成更多部分
// comp :: (b -> c) -> (a -> b) -> (a -> c)
const comp = f=> g=> x=> f (g (x))
// comp2 :: (c -> d) -> (a -> b -> c) -> (a -> b -> d)
const comp2 = comp (comp) (comp)
// mod :: Number -> Number -> Number
const mod = x=> y=> y % x
// isDivisibleBy :: Number -> Number -> Boolean
const isDivisibleBy = comp2 (isZero) (mod)
底线是:不要对它发疯;知道何时足够抽象足以解决您的特定问题。只要知道那里有令人难以置信的技术可以将复杂性抽象掉。学习这些技术并知道何时应用它们。你会很高兴你做到了^_^