Я пытаюсь нормализовать массив элементов во временном диапазоне. Допустим, у вас есть 20 банковских транзакций, совершенных 1 января 2022 года.
transaction 1 - 2022/01/01
transaction 2 - 2022/01/01
...
transaction 20 - 2022/01/01
у нас нет других данных, кроме дня, когда они произошли, но мы все же хотим назначить им час дня, поэтому они заканчиваются так:
transaction 1 - 2022/01/01 00:00
transaction 2 - 2022/01/01 ??:??
...
transaction 20 - 2022/01/01 23:59
В Go у меня есть эта функция, которая пытается вычислить нормализацию времени суток для индекса в массиве элементов:
func normal(start, end time.Time, arraySize, index float64) time.Time {
delta := end.Sub(start)
minutes := delta.Minutes()
duration := minutes * ((index+1) / arraySize)
return start.Add(time.Duration(duration) * time.Minute)
}
Однако я получаю неожиданное вычисление 01.01.2022 05:59 для индекса 0 в массиве из 4 элементов во временном диапазоне от 01.01.2022 00:00 до 01.01.2022 23:59, вместо этого я ожидал увидеть 01.01.2022 00:00. Единственное, что отлично работает в этих условиях, это индекс 3.
Итак, что я делаю неправильно с моей нормализацией?
Вот функция, исправленная благодаря @icza
func timeIndex(min, max time.Time, entries, position float64) time.Time {
delta := max.Sub(min)
minutes := delta.Minutes()
if position < 0 {
position = 0
}
duration := (minutes * (position / (entries - 1)))
return min.Add(time.Duration(duration) * time.Minute)
}
Вот пример: допустим, наша дата начала и окончания 2022/01/01 00:00
- 2022/01/01 00:03
, также у нас есть 3 записи в нашем массиве банковских транзакций, и мы хотим получить нормализованное время для транзакции № 3 (2
в массиве):
result := timeIndex(time.Date(2022, time.January, 1, 0, 0, 0, 0, time.UTC), time.Date(2022, time.January, 1, 0, 3, 0, 0, time.UTC), 3, 2)
поскольку между временем начала и окончания (от 00:00
до 00:03
) всего 4 минуты, и вы хотите найти нормализованное время для последней записи (индекс 2
) в массиве (размер 3
), результат должен быть:
fmt.Printf("%t", result.Equal(time.Date(2022, time.January, 1, 0, 3, 0, 0, time.UTC))
// prints "true"
или последнюю минуту в диапазоне, который равен 00:03
.
Вот воспроизводимый пример: https://go.dev/play/p/EzwkqaNV1at
Используя index+1
, вы пропустите 00:00
. И если вы хотите включить начало и конец, количество сегментов (delta
периодов между отметками времени) равно arraySize - 1
.
@icza с этой настройкой в arraySize - 1
я смог исправить свой алгоритм, можете ли вы преобразовать свой комментарий в ответ?
Между n
точками находится n-1
отрезков. Это означает, что если вы хотите включить start
и end
в интерполяцию, количество периодов времени (будучи delta
) равно arraySize - 1
.
Кроме того, если вы добавите 1
к index
, у вас не может быть start
в результате (вы пропустите 00:00
).
Итак, правильный алгоритм таков:
func normal(start, end time.Time, arraySize, index float64) time.Time {
minutes := end.Sub(start).Minutes()
duration := minutes * (index / (arraySize - 1))
return start.Add(time.Duration(duration) * time.Minute)
}
Попробуйте на Go Playground.
Также обратите внимание, что если у вас много транзакций (в порядке количества минут в день, что составляет около тысячи), вы можете легко получить несколько транзакций с одной и той же меткой времени (один и тот же час и минута). Если вы хотите избежать этого, используйте меньшую точность, чем минуту, например. секунды или миллисекунды:
func normal(start, end time.Time, arraySize, index float64) time.Time {
sec := end.Sub(start).Seconds()
duration := sec * (index / (arraySize - 1))
return start.Add(time.Duration(duration) * time.Second)
}
Да, это приведет к временным меткам, где секунды также не обязательно равны нулю, но будут обеспечивать разные уникальные временные метки для более высоких номеров транзакций.
Если у вас есть транзакции, порядок величины которых близок к количеству секунд в сутках (что составляет 86400), то вы можете полностью отказаться от этой «единицы» и использовать саму time.Duration
(это количество наносекунд). Это гарантирует уникальность отметки времени даже для самого большого количества транзакций:
func normal(start, end time.Time, arraySize, index float64) time.Time {
delta := float64(end.Sub(start))
duration := delta * (index / (arraySize - 1))
return start.Add(time.Duration(duration))
}
Тестируя это с 1 миллионом транзакций, вот первые 15 временных частей (они откладываются только в своей второй части):
0 - 00:00:00.00000
1 - 00:00:00.08634
2 - 00:00:00.17268
3 - 00:00:00.25902
4 - 00:00:00.34536
5 - 00:00:00.43170
6 - 00:00:00.51804
7 - 00:00:00.60438
8 - 00:00:00.69072
9 - 00:00:00.77706
10 - 00:00:00.86340
11 - 00:00:00.94974
12 - 00:00:01.03608
13 - 00:00:01.12242
14 - 00:00:01.20876
15 - 00:00:01.29510
16 - 00:00:01.38144
17 - 00:00:01.46778
18 - 00:00:01.55412
19 - 00:00:01.64046
Попробуйте это на Go Playground.
Спасибо за ваш ответ! Я все еще пытаюсь обдумать математику, связанную с этой проблемой. Я использую точность в минутах, потому что я хочу вводить эти данные в другие приложения, и единственный способ сохранить порядок - использовать время дня, мой план состоит в том, чтобы сделать фрагменты между установленными отметками времени, когда поступают новые данные, и транзакциями, где рассчитываются между существующими записями.
Пожалуйста, предоставьте минимальный воспроизводимый пример . Однако то, что вы видите , похоже, то, что я ожидал увидеть, учитывая ваш алгоритм? (возможно, приведите пример того, что вы ожидаете получить на выходе и почему). Может это то, что вы хотите?