Как нормализовать элементы массива во временном диапазоне?

Я пытаюсь нормализовать массив элементов во временном диапазоне. Допустим, у вас есть 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 21.11.2022 09:00

@icza с этой настройкой в ​​arraySize - 1 я смог исправить свой алгоритм, можете ли вы преобразовать свой комментарий в ответ?

shackra 22.11.2022 05:23
Создание API ввода вопросов на разных языках программирования (Python, PHP, Go и Node.js)
Создание API ввода вопросов на разных языках программирования (Python, PHP, Go и Node.js)
API ввода вопросов - это полезный инструмент для интеграции моделей машинного обучения, таких как ChatGPT, в приложения, требующие обработки...
2
3
55
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 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.

Спасибо за ваш ответ! Я все еще пытаюсь обдумать математику, связанную с этой проблемой. Я использую точность в минутах, потому что я хочу вводить эти данные в другие приложения, и единственный способ сохранить порядок - использовать время дня, мой план состоит в том, чтобы сделать фрагменты между установленными отметками времени, когда поступают новые данные, и транзакциями, где рассчитываются между существующими записями.

shackra 23.11.2022 18:26

Другие вопросы по теме