Skip to content

Commit

Permalink
Merge pull request #255 from du00cs/time/part-of-day
Browse files Browse the repository at this point in the history
[DUCK] PartOfDay未结束区间参考重做
  • Loading branch information
zhangsonglei authored Dec 26, 2024
2 parents 75e4c6c + 577f8d8 commit dda8328
Show file tree
Hide file tree
Showing 7 changed files with 80 additions and 73 deletions.
29 changes: 12 additions & 17 deletions .github/workflows/duckling.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,17 @@ on:
jobs:
build:

runs-on: ubuntu-latest
runs-on: ubuntu-20.04

steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Set up JDK 11
uses: actions/setup-java@v2
with:
java-version: '11'
distribution: 'adopt'
- name: Run tests
run: |
cd duckling-fork-chinese
sbt duckModel
sbt +test
sbt coverage
sbt coverageAggregate
bash <(curl -s https://codecov.io/bash) -r du00cs/MiNLP -t 'd2de025e-e5b7-4115-a98e-07e6fc3d7001'
- uses: actions/[email protected]
with:
fetch-depth: 0
- uses: olafurpg/setup-scala@v10
- run: |
cd duckling-fork-chinese
sbt duckModel
sbt +test
sbt coverage
sbt coverageAggregate
bash <(curl -s https://codecov.io/bash) -r du00cs/MiNLP -t 'd2de025e-e5b7-4115-a98e-07e6fc3d7001'
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ case class TimeData(timePred: TimePredicate,
|| hint != Hint.Recent && !options.timeOptions.alwaysInFuture)
val valueOpt =
try {
resolveTimeData(refTime, this, reverseTake, options)
resolveTimeData(refTime, this, reverseTake)
} catch {
case e: java.time.DateTimeException =>
logger.error(s"time resolve failed with DateTimeException [${e.getMessage}]")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,8 @@ object TimePredicateHelpers {
notImmediate: Boolean,
cyclicPred: TimePredicate,
basePred: TimePredicate): TimePredicate = {
def f(t: TimeObject, ctx: TimeContext, options: Options): Option[TimeObject] = {
val (past, future) = runPredicate(cyclicPred)(t, ctx, null)
def f(t: TimeObject, ctx: TimeContext): Option[TimeObject] = {
val (past, future) = runPredicate(cyclicPred)(t, ctx)
val rest = if (n >= 0) {
future match {
case ahead #:: _ if notImmediate && timeBefore(ahead, t) => future.drop(n + 1)
Expand All @@ -151,7 +151,7 @@ object TimePredicateHelpers {
def timeCycle(grain: Grain): CycleSeriesPredicate = timeCycle(grain, grain)

def timeCycle(grain: Grain, roundGrain: Grain, step: Int = 1): CycleSeriesPredicate = {
CycleSeriesPredicate((t: TimeObject, _: TimeContext, _: Options) => {
CycleSeriesPredicate((t: TimeObject, _: TimeContext) => {
timeSequence(grain, step, if (roundGrain != NoGrain) timeRound(t, roundGrain) else t)
}, step, grain)
}
Expand All @@ -160,10 +160,10 @@ object TimePredicateHelpers {
* Takes `n` cycles of `f`
*/
def takeN(literalN: Int, notImmediate: Boolean, cycleSP: CycleSeriesPredicate): TimePredicate = {
def series(t: TimeObject, context: TimeContext, options: Options) = {
def series(t: TimeObject, context: TimeContext) = {
val baseTime = context.refTime
// 确定起点
val (past, future) = runPredicate(cycleSP)(baseTime, context, options)
val (past, future) = runPredicate(cycleSP)(baseTime, context)
val fut = future match {
case ahead #:: rest if notImmediate && timeIntersect(ahead)(baseTime).nonEmpty => rest
case _ => future
Expand Down Expand Up @@ -200,8 +200,8 @@ object TimePredicateHelpers {
* 0 is the first element in the future
*/
def takeNth(n: Int, notImmediate: Boolean, f: TimePredicate): TimePredicate = {
val series = (t: TimeObject, context: TimeContext, options: Options) => {
val (past, future) = runPredicate(f)(context.refTime, context, options)
val series = (t: TimeObject, context: TimeContext) => {
val (past, future) = runPredicate(f)(context.refTime, context)
val rest = if (n >= 0) {
future match {
case Stream.Empty => Stream.Empty
Expand Down Expand Up @@ -232,7 +232,7 @@ object TimePredicateHelpers {
}

def solarTermPredicate(term: String): SeriesPredicate = {
val series: SeriesPredicateF = (t: TimeObject, context: TimeContext, options: Options) => {
val series: SeriesPredicateF = (t: TimeObject, context: TimeContext) => {
if (!containSolarTerm(t.start.year, term)) (Stream.empty, Stream.empty)
else {
def f(step: Int)(to: TimeObject): TimeObject = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@
package com.xiaomi.duckling.dimension

import com.github.heqiao2010.lunar.{LunarCalendar, LunarData}

import java.time.LocalTime

import com.xiaomi.duckling.Types.{conf, Options, ZoneCN}
import com.xiaomi.duckling.Types.{ZoneCN, conf}
import com.xiaomi.duckling.dimension.time.Types.{TimeContext, TimeObject, _}
import com.xiaomi.duckling.dimension.time.enums.AMPM._
import com.xiaomi.duckling.dimension.time.enums.Grain._
Expand All @@ -35,7 +36,7 @@ package object time {
/**
* Return a tuple of (past, future) elements
*/
type SeriesPredicateF = (TimeObject, TimeContext, Options) => PastFutureTime
type SeriesPredicateF = (TimeObject, TimeContext) => PastFutureTime

implicit class GrainWrapper(grain: Grain) {
def <(that: Grain): Boolean = grain.compareTo(that) < 0
Expand Down Expand Up @@ -81,12 +82,11 @@ package object time {

def resolveTimeData(refTime: TimeObject,
td: TimeData,
reverseTake: Boolean = false,
options: Options): Option[TimeObject] = {
reverseTake: Boolean = false): Option[TimeObject] = {

val tc = refTimeContext(refTime, reverseTake)

val (past, future) = runPredicate(td.timePred)(refTime, tc, options)
val (past, future) = runPredicate(td.timePred)(refTime, tc)

val reverse = if (reverseTake) {
future match {
Expand All @@ -113,17 +113,21 @@ package object time {
// 逻辑比较混乱,待收集到问题再处理
val happened =
td.timePred match {
// 下午3点问“下午”, 12号 04:30 问 12号凌晨,还需要停留在12号
case _: TimeIntervalsPredicate | IntersectTimePredicate(TimeIntervalsPredicate(_, _, _, _), _) =>
val beforeEndOfInterval = td.timePred match {
case TimeIntervalsPredicate(_, _, _, b) => b
case IntersectTimePredicate(TimeIntervalsPredicate(_, _, _, b), _) => b
}
val g = if (td.timeGrain >= Grain.Day) td.timeGrain else Grain.NoGrain
if (!beforeEndOfInterval) timeBefore(ahead, refTime, g)
else endBefore(ahead, refTime, g)
case _: TimeDatePredicate | _: IntersectTimePredicate =>
// 若参考时间是2013/2/12 04:30,在alwaysInFuture情况下
// 1. 过了一部分还需要再出的,12号 => 2/12,2月 => 2013/2
// 2. 问4点,需要给出 16:00
val g = if (td.timeGrain >= Grain.Day) td.timeGrain else Grain.NoGrain
if (options.timeOptions.beforeEndOfInterval) endBefore(ahead, refTime, g)
else timeBefore(ahead, refTime, g)
case TimeIntervalsPredicate(_, _, _, beforeEndOfInterval) =>
val g = if (td.timeGrain >= Grain.Day) td.timeGrain else Grain.NoGrain
if (!beforeEndOfInterval) timeBefore(ahead, refTime, g)
else endBefore(ahead, refTime, g)
timeBefore(ahead, refTime, g)
case _ => false
}
if (happened || td.notImmediate && timeIntersect(ahead)(refTime).nonEmpty) {
Expand All @@ -135,7 +139,7 @@ package object time {
}

val EmptySeries: PastFutureTime = (Stream.empty, Stream.empty)
val EmptySeriesPredicate: SeriesPredicateF = (_: TimeObject, _: TimeContext, _: Options) => EmptySeries
val EmptySeriesPredicate: SeriesPredicateF = (_: TimeObject, _: TimeContext) => EmptySeries

def runPredicate(tp: TimePredicate): SeriesPredicateF = {
tp match {
Expand All @@ -154,9 +158,9 @@ package object time {
year.map(runYearPredicate)
).flatten

def series(t: TimeObject, tc: TimeContext, options: Options): PastFutureTime = {
def series(t: TimeObject, tc: TimeContext): PastFutureTime = {
val pred = toCompose.reduceOption(runCompose).getOrElse(EmptySeriesPredicate)
val (past, future) = pred(t, tc, options)
val (past, future) = pred(t, tc)
(past, future)
}

Expand All @@ -173,7 +177,7 @@ package object time {
}
}

def runEndOfGrainPredicate(t: TimeObject, context: TimeContext, options: Options): PastFutureTime = {
def runEndOfGrainPredicate(t: TimeObject, context: TimeContext): PastFutureTime = {
val (start, grain) = t.grain match {
case Grain.Month =>
(t.start.plusMonths(1).plusDays(-1), Day)
Expand All @@ -187,10 +191,10 @@ package object time {
def runReplacePartPredicate(
td1: TimeData,
td2: TimeData
)(t: TimeObject, context: TimeContext, options: Options): PastFutureTime = {
)(t: TimeObject, context: TimeContext): PastFutureTime = {
(for {
t1 <- resolveTimeData(t, td1, options = options)
t2 <- resolveTimeData(t, td2, options = options)
t1 <- resolveTimeData(t, td1)
t2 <- resolveTimeData(t, td2)
} yield {
val to =
if (td2.timePred.maxGrain.nonEmpty && td1.timeGrain > td2.timePred.maxGrain.get) {
Expand Down Expand Up @@ -256,14 +260,14 @@ package object time {

@scala.annotation.tailrec
def runSequencePredicate(list: List[TimeData])(t: TimeObject,
context: TimeContext, options: Options): PastFutureTime = {
context: TimeContext): PastFutureTime = {
list match {
case Nil => (Stream.empty, Stream(context.refTime))
case td :: xs =>
resolveTimeData(t, td, options = options) match {
resolveTimeData(t, td) match {
case Some(refTime) =>
val tc = refTimeContext(refTime)
runSequencePredicate(xs)(refTime, tc, options)
runSequencePredicate(xs)(refTime, tc)
case None => EmptySeries
}
}
Expand All @@ -273,13 +277,13 @@ package object time {
runCompose(runPredicate(pred1), runPredicate(pred2))
}

def runSecondPredicate(n: Int)(t: TimeObject, context: TimeContext, options: Options): PastFutureTime = {
def runSecondPredicate(n: Int)(t: TimeObject, context: TimeContext): PastFutureTime = {
val s = t.start.second
val anchor = timePlus(timeRound(t, Second), Second, n - s % 60)
timeSequence(Minute, 1, anchor)
}

def runMinutePredicate(n: Int)(t: TimeObject, context: TimeContext, options: Options): PastFutureTime = {
def runMinutePredicate(n: Int)(t: TimeObject, context: TimeContext): PastFutureTime = {
val rounded = timeRound(t, Minute)
val m = t.start.minute
val anchor = timePlus(rounded, Minute, (n - m) % 60)
Expand All @@ -288,7 +292,7 @@ package object time {

def runHourPredicate(
ampm: Option[AMPM]
)(hour: (Boolean, Int))(t: TimeObject, context: TimeContext, options: Options): PastFutureTime = {
)(hour: (Boolean, Int))(t: TimeObject, context: TimeContext): PastFutureTime = {
val (is12H, n) = hour
val step = if (is12H && n <= 12 && ampm.isEmpty) 12 else 24
val nAdjust = ampm match {
Expand All @@ -309,13 +313,13 @@ package object time {
)
}

def runDayOfTheWeekPredicate(n: Int)(t: TimeObject, context: TimeContext, options: Options): PastFutureTime = {
def runDayOfTheWeekPredicate(n: Int)(t: TimeObject, context: TimeContext): PastFutureTime = {
val daysUntilNextWeek = Math.floorMod(n - t.start.dayOfWeek, 7)
val anchor = timePlus(timeRound(t, Day), Day, daysUntilNextWeek)
timeSequence(Day, 7, anchor)
}

def runDayOfTheMonthPredicate(n: Int)(t: TimeObject, context: TimeContext, options: Options): PastFutureTime = {
def runDayOfTheMonthPredicate(n: Int)(t: TimeObject, context: TimeContext): PastFutureTime = {

def enoughDays(t: TimeObject): Boolean = {
n <= t.start.date.lengthOfMonth
Expand All @@ -335,7 +339,7 @@ package object time {
(past, future)
}

def runMonthPredicate(calendar: Option[Calendar])(n: Int)(t: TimeObject, context: TimeContext, options: Options): PastFutureTime = {
def runMonthPredicate(calendar: Option[Calendar])(n: Int)(t: TimeObject, context: TimeContext): PastFutureTime = {
val y = timeRound(t, Year, calendar)
val rounded =
calendar match {
Expand All @@ -348,7 +352,7 @@ package object time {
timeSequence(Year, 1, anchor)
}

def runYearPredicate(n: Int)(t: TimeObject, context: TimeContext, options: Options): PastFutureTime = {
def runYearPredicate(n: Int)(t: TimeObject, context: TimeContext): PastFutureTime = {
val year = n
val tyear = t.start.year
val y = timePlus(timeRound(t, Year), Year, year - tyear)
Expand All @@ -363,14 +367,14 @@ package object time {
* Performs best when pred1 is smaller grain than pred2
*/
def runCompose(pred1: SeriesPredicateF, pred2: SeriesPredicateF): SeriesPredicateF = {
val series = (nowTime: TimeObject, context: TimeContext, options: Options) => {
val (past, future) = pred2(nowTime, context, options)
val series = (nowTime: TimeObject, context: TimeContext) => {
val (past, future) = pred2(nowTime, context)

def startsBefore(t1: TimeObject)(t: TimeObject): Boolean = timeStartsBeforeTheEndOf(t)(t1)

def computeSeries(tokens: Stream[TimeObject]): Stream[TimeObject] = {
tokens.take(safeMax).flatMap { time1 =>
val (past, future) = pred1(time1, fixedTimeContext(time1), options)
val (past, future) = pred1(time1, fixedTimeContext(time1))
val before = future.takeWhile(startsBefore(time1))
before.flatMap(timeIntersect(time1))
}
Expand All @@ -388,8 +392,8 @@ package object time {
pred2: TimePredicate,
beforeEndOfInterval: Boolean): SeriesPredicateF = {
// Pick the first interval *after* the given time segment
def f(thisSegment: TimeObject, ctx: TimeContext, options: Options): Option[TimeObject] = {
runPredicate(pred2)(thisSegment, ctx, options) match {
def f(thisSegment: TimeObject, ctx: TimeContext): Option[TimeObject] = {
runPredicate(pred2)(thisSegment, ctx) match {
case (_, firstFuture #:: tail) =>
// 避免9点-9点,左右一样(空区间)
val end = if (firstFuture != thisSegment || tail.headOption.isEmpty) firstFuture else tail.head
Expand All @@ -398,8 +402,8 @@ package object time {
}
}

def b(thisSegment: TimeObject, ctx: TimeContext, options: Options): Option[TimeObject] = {
runPredicate(pred1)(thisSegment, ctx, options) match {
def b(thisSegment: TimeObject, ctx: TimeContext): Option[TimeObject] = {
runPredicate(pred1)(thisSegment, ctx) match {
case (past, future) =>
val choosed = future.take(safeMax).find(t => timeStartsBeforeTheEndOf(t)(thisSegment))
.orElse(past.take(safeMax).find(t => timeStartsBeforeTheEndOf(t)(thisSegment)))
Expand All @@ -426,15 +430,15 @@ package object time {
* @return Series generator for values that come from `f`
*/
def timeSeqMap(dontReverse: Boolean,
f: (TimeObject, TimeContext, Options) => Option[TimeObject],
f: (TimeObject, TimeContext) => Option[TimeObject],
g: TimePredicate): SeriesPredicateF = {
def seriesF(nowTime: TimeObject, context: TimeContext, options: Options) = {
def seriesF(nowTime: TimeObject, context: TimeContext) = {
// computes a single interval from `f` based on each interval in the series
def applyF(series: Stream[TimeObject]) = {
series.take(safeMaxInterval).flatMap(f(_, context, options))
series.take(safeMaxInterval).flatMap(f(_, context))
}

val (firstPast, firstFuture) = runPredicate(g)(nowTime, context, options)
val (firstPast, firstFuture) = runPredicate(g)(nowTime, context)
val (past1, future1) = (applyF(firstPast), applyF(firstFuture))

// Separate what's before and after now from the past's series
Expand Down Expand Up @@ -467,7 +471,7 @@ package object time {
case _ => false
}

def runTimeOpenIntervalPredicate(it: IntervalDirection)(t: TimeObject, context: TimeContext, options: Options): PastFutureTime = {
def runTimeOpenIntervalPredicate(it: IntervalDirection)(t: TimeObject, context: TimeContext): PastFutureTime = {
(Stream(t.copy(direction = Some(it))), Stream.empty)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,20 @@ import com.xiaomi.duckling.dimension.time.helper.TimeObjectHelpers.{timeIntersec
import com.xiaomi.duckling.dimension.time.Types._
import com.xiaomi.duckling.dimension.time.enums.Grain
import com.xiaomi.duckling.ranking.Testing
import com.xiaomi.duckling.Types.{Options, ZoneCN}
import com.xiaomi.duckling.Types.ZoneCN
import com.xiaomi.duckling.UnitSpec

class TypesTest extends UnitSpec {

describe("TypesTest") {

def round1(refTime: TimeObject, td: TimeData, options: Options): Option[TimeObject] = {
def round1(refTime: TimeObject, td: TimeData): Option[TimeObject] = {
val tc = TimeContext(
refTime = refTime,
maxTime = timePlus(refTime, Grain.Year, 2000),
minTime = timePlus(refTime, Grain.Year, -2000)
)
val (past, future) = runPredicate(td.timePred)(refTime, tc, options)
val (past, future) = runPredicate(td.timePred)(refTime, tc)

val valueOpt = future match {
case Stream.Empty => past.headOption
Expand All @@ -53,14 +53,13 @@ class TypesTest extends UnitSpec {

it("sequence apply demo") {
val refTime = new TimeObject(Testing.testContext.referenceTime, Grain.Second)
val options = Options()
val td1 = cycleNth(Day, 1)

val r1 = round1(refTime, td1, options).get
val r1 = round1(refTime, td1).get
r1.start.dayOfMonth shouldBe 13

val td2 = cycleNth(Day, 2)
val r2 = round1(r1, td2, options).get
val r2 = round1(r1, td2).get
r2.start.dayOfMonth shouldBe 15
}

Expand Down
Loading

0 comments on commit dda8328

Please sign in to comment.