Как использовать Ramda для решения часового формата? - PullRequest
1 голос
/ 29 апреля 2020

Я новичок в функциональном программировании, и после изучения основ функционального программирования и Ramda я решил испачкать руки, заменив некоторые функции, написанные ранее, с помощью императивного программирования. Я решил переписать функцию, чтобы выполнить простую задачу

Преобразование 13:22 to 1:22 PM

Вот код, который я написал после долгих размышлений

const formatHr = (R.cond([
  [R.lt(R.__,13), R.identity],
  [R.gte(R.__,13), R.subtract(R.__,12)],
]));

const updateHr = x => {
  const hr = parseInt(R.head(x));
  const newHr = formatHr(hr);
  const newX = R.update(0, newHr, x);
  const meridiem = R.ifElse(R.lt(R.__,12),x => R.identity('AM'), x => R.identity('PM'))(hr);
  return `${R.join(':',newX)} ${meridiem}`
}

const getHour = R.pipe(R.split(':'), updateHr);
const hr = getHour('13:22'); // 1:22 PM

Это только кажется слишком сложный для чего-то, что может быть легко написано на vanilla javascript с синтаксисом ES6. Может кто-нибудь показать мне, как это сделано эффективно?

Ответы [ 3 ]

2 голосов
/ 30 апреля 2020

Я бы использовал R.applySpe c после разделения, потому что каждая функция отображения имеет доступ ко всем исходным значениям.

Я бы не использовал Ramda для функций отображения (normalizeHour и amPm), так как шаблоны деструктурирования и литералы были бы проще в использовании и читабельны.

После отформатируйте значения, получите их с помощью R.props (R.values ​​также будет работать, но это не так явно) и объедините значения.

const { pipe, split, applySpec, props, join } = R 

const normalizeHour = ([m]) => m > 12 ? m - 12 : m
const amPm = ([h, m]) => `${m} ${h >= 12 && h < 24 ? 'PM' : 'AM'}`

const formatHour = pipe(
  split(':'),
  applySpec({
    h: normalizeHour,
    m: amPm,
  }),
  props(['h', 'm']),
  join(' ')
);

console.log(
  ['7:04', '11:59', '12:01', '13:22', '24:53'].map(formatHour)
)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.27.0/ramda.js"></script>
2 голосов
/ 30 апреля 2020

полночь 00:00 и 24:00

В 24-часовой системе обозначений день начинается в полночь, 00:00 и в последнюю минуту день начинается в 23:59. Там, где это удобно, обозначение 24:00 также может использоваться для обозначения полуночи в конце заданной даты [5], то есть 24:00 одного дня совпадает с 00:00 следующего дня.

Обозначение 24:00 в основном служит для обозначения точного конца дня во временном интервале. Типичное использование дает часы работы, заканчивающиеся в полночь (например, «00: 00–24: 00», «07: 00–24: 00»). Аналогичным образом, некоторые расписания автобусов и поездов показывают 00:00 как время отправления и 24:00 как время прибытия. Юридические контракты часто заключаются с даты начала в 00:00 до даты окончания в 24:00.

В то время как 24-часовая запись однозначно различает полночь в начале (00:00) и конце (24: 00) любой указанной даты, среди пользователей 12-часового обозначения нет общепринятого различия. Руководства по стилю и правила военного общения в некоторых странах, говорящих на английском языке, не поощряют использование 24:00 даже в 24-часовой записи и рекомендуют вместо этого сообщать время около полуночи как 23:59 или 00:01. [6] Иногда использования 00:00 также избегают. [6] В отличие от этого, в руководстве по переписке для ВМС США и Корпуса морской пехоты США ранее указывалось от 0001 до 2400. [7] Руководство было обновлено в июне 2015 года для использования от 0000 до 2359

раз после 24:00

Обозначения времени суток после 24: 00 (например, 24: 01 или 25: 00 вместо 00: 01 или 01: 00 ) обычно не используются и не охватывается соответствующими стандартами. Тем не менее, они иногда использовались в некоторых особых контекстах в Великобритании, Франции, Испании, Канаде, Японии, Южной Корее, Гонконге и Китае, где рабочие часы выходят за полночь, например, для производства телевизионных программ и планирования.

(цитата: https://en.wikipedia.org/wiki/24-hour_clock)


наша программа

Другие ответы здесь обсуждают некоторые из мы делаем скидку при выборе express нашей программы разными способами. В этом ответе я специально хочу сосредоточиться на реализации надежного форматера, который учитывает изменения, изложенные выше. Мы напишем format24, который принимает 24-часовую строку времени и функцию форматирования, и ожидаем, что она будет работать следующим образом -

format24("00:00", to12) //=> "12:00 AM"
format24("11:59", to12) //=> "11:59 AM"
format24("12:01", to12) //=> "12:01 PM"
format24("13:22", to12) //=> "1:22 PM"
format24("24:00", to12) //=> "12:00 AM"
format24("25:01", to12) //=> "1:01 AM"

Функция форматирования to12 в этом примере принимает 0-23 hour и 0-59 minute -

const to12 = (h = 0, m = 0) =>
  `${formatHour(h % 12)}:${formatMinute(m)} ${formatMeridian(h)}`

const formatHour = (h = 0) =>
  h === 0 ? "12" : String(h)

const formatMinute = (m = 0) =>
  m < 10 ? `0${m}` : String(m)

const formatMeridian = (h = 0) =>
  h < 12 ? "AM" : "PM"

Наконец, мы реализуем format24 -

const format24 = (time = "00:00", formatter = String) =>
{ const [ _, h = 0, m = 0 ] =
    time.match(/^(\d?\d):(\d\d)/) || []

  const minutes =
    Number(h) * 60 + Number(m)

  return formatter
    ( Math.floor(minutes / 60) % 24 // 0-23
    , minutes % 60                  // 0-59
    )
}

, чтобы убедиться, что он работает правильно

const times =
  [ "00:00", "00:01", "01:23", "11:59", "12:00", "12:01", "13:22", "23:59", "24:00", "24:01", "25:00", "27:45" ]

times.forEach(t => console.log(t, "->", format24(t, to12)))

Вход -> Выход

00:00 -> 12:00 AM
00:01 -> 12:01 AM
01:23 -> 1:23 AM
11:59 -> 11:59 AM
12:00 -> 12:00 PM
12:01 -> 12:01 PM
13:22 -> 1:22 PM
23:59 -> 11:59 PM
24:00 -> 12:00 AM
24:01 -> 12:01 AM
25:00 -> 1:00 AM
27:45 -> 3:45 AM

код демонстрации

Разверните фрагмент ниже, чтобы проверить результаты format24 в вашем собственном браузере -

const format24 = (time = "00:00", formatter = String) =>
{ const [ _, h = 0, m = 0 ] =
    time.match(/^(\d?\d):(\d\d)/) || []

  const minutes =
    Number(h) * 60 + Number(m)

  return formatter
    ( Math.floor(minutes / 60) % 24
    , minutes % 60
    )
}

const to12 = (h = 0, m = 0) =>
  `${formatHour(h % 12)}:${formatMinute(m)} ${formatMeridian(h)}`

const formatHour = (h = 0) =>
  h === 0 ? "12" : String(h)

const formatMinute = (m = 0) =>
  m < 10 ? `0${m}` : String(m)

const formatMeridian = (h = 0) =>
  h < 12 ? "AM" : "PM"

const times =
  [ "00:00", "00:01", "01:23", "11:59", "12:00", "12:01", "13:22", "23:59", "24:00", "24:01", "25:00", "27:45" ]

times.forEach(t => console.log(t, "->", format24(t, to12)))

недопустимые времена

Если время полностью искажено, мы, вероятно, должны throw сделать ошибку, чтобы вызывающий абонент знает, что время ввода не может быть надежно преобразовано -

const format24 = (time = "00:00", formatter = String) =>
{ const [ match, h = 0, m = 0 ] =
    time.match(/^(\d\d):(\d\d)$/) || []

  if (match === undefined)
    throw Error("invalid 24-hour time")

  // ...
}

format24("foo", to12)     // Error: invalid 24-hour time
format24("1:23", to12)    // Error: invalid 24-hour time
format24("123:456", to12) // Error: invalid 24-hour time
2 голосов
/ 29 апреля 2020

Хотя мы определенно могли бы улучшить это с помощью инструментов Ramda, кажется, есть очень мало причин для этого. Я думаю, что ваша цель немного ошибочна. Рамда (отказ от ответственности: я один из его авторов) разработан, чтобы предложить вам инструменты, которые помогут вам кодировать определенным образом. Это не новый диалект JS, в который вы должны конвертировать весь свой код.

Я бы написал эту функцию примерно так:

const formatHour = (time) => {
  const [h, m] = time .split (':')
  return `${h > 12 ? h - 12 : h}:${m} ${h >= 12 ? 'PM' : 'AM'}`
}


console .log (formatHour ('13:22'))

Основные моменты, которые я проверяю, когда хочу убедиться, что пишу функциональный код, заключаются в том, что то, что я пишу, не связано с мутацией данных (обычно это включает в себя не переназначение переменные, хотя когда-нибудь я это сделаю по соображениям производительности) и что у меня есть только чистые функции - те, которые всегда возвращают один и тот же вывод для одного и того же ввода и не имеют побочных эффектов. Эта функция проходит оба теста. Мы ничего не мутируем и присваиваем h и m только один раз, без переназначения. Функция полагается только на свои аргументы для вычисления непротиворечивого результата, не затрагивая ничего снаружи.

Есть некоторые другие функции, которые я предпочитаю при написании функционального кода, но они менее важны и, возможно, более противоречивы. Мне не нравится иметь много промежуточных переменных, и если они используются только один раз, я часто добавляю их в строку. Поэтому я предпочитаю версию выше этой:

const formatHour = (time) => {
  const [hour, minute] = time .split (':')
  const newHour = hour > 12 ? hour - 12 : hour
  const meridian = hour >= 12 ? 'PM' : 'AM'
  return `${newHour}:${minute} ${meridian}`
}

Но это незначительное предпочтение, и я часто выбираю другой путь, если мой код начинает чувствовать себя нечитаемым без вспомогательных локальных переменных. Другой, однако, я стараюсь делать как можно чаще: я предпочитаю работать с чистыми выражениями, а не с утверждениями. Я буду использовать условные операторы (троичные) над if -общениями. Я выберу тела с одним выражением для функций со стрелками над { - } -ограниченными блоками.

Это побудило бы меня написать эту функцию следующим образом:

const formatHour = (time, [h, m] = time .split (':')) => 
  `${h > 12 ? h - 12 : h}:${m} ${h >= 12 ? 'PM' : 'AM'}`;

И часто Я делаю такие вещи. Но используемые по умолчанию параметры могут быть головной болью. Используя более ранние версии функции, я мог написать

['7:04', '11:59', '12:01', '13:22'] .map (formatHour)
//=> ["7:04 AM", "11:59 AM", "12:01 PM", "1:22 PM"]

Но в этой последней версии это не сработает. Array.prototype.map предоставляет два дополнительных параметра помимо текущего элемента: его индекс и весь массив. Так что в этой последней версии, если мы передадим его в map, параметры [h, m] не будут правильно сформированы, поскольку в этом месте есть целочисленный индекс, а не массив. Мы получим какую-то ошибку разрушения. Мы можем исправить это, добавив несколько неиспользуемых параметров, например:

const formatHour = (time, _, __, [h, m] = time .split (':')) => 
  `${h > 12 ? h - 12 : h}:${m} ${h >= 12 ? 'PM' : 'AM'}`;

['7:04', '11:59', '12:01', '13:22'] .map (formatHour)
//=> ["7:04 AM", "11:59 AM", "12:01 PM", "1:22 PM"]

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


Рамда не предлагает никаких улучшений, которые я вижу.

Но, если Я должен был попытаться сделать это с функциями Рамды, я мог бы сделать это немного иначе, чем вы.

Прежде всего, есть несколько более простых альтернатив Рамде cond, когда вы только в одном случае ifElse. Но даже это может быть излишним, когда одна из ваших ветвей просто возвращает данные без изменений. Затем вы можете использовать when (или его аналог, unless) для преобразования данных при выполнении условия и в противном случае оставить его в покое.

Во-вторых , Я бы, вероятно, использовал функцию evolve, чтобы позволить мне изменить одну часть моей структуры данных (массив со свойствами hour и minute), но оставить другие части в покое.

Наконец, поскольку мне приходится использовать час в двух местах и ​​объединять их результаты в одно, я бы использовал converge или, что еще лучше, если это возможно, lift, чтобы справиться с этим.

const formatHour = pipe (
  split (':'),
  lift (([h, m], meridian) => `${h}:${m} ${meridian}`) (
    evolve ([when (gt (__, 12), subtract (__, 12))]),
    ([h, m]) => h >= 12 ? 'PM' : 'AM'
  )
);

console .log (
  ['7:04', '11:59', '12:01', '13:22'] .map (formatHour)
)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.27.0/ramda.js"></script>
<script> const {pipe, split, lift, evolve, when, gt, __, subtract} = R   </script>

Обратите внимание, что я не пытаюсь сделать это полностью бессмысленным. Я мог бы пойти этим путем, заменив

    ([h, m]) => h >= 12 ? 'PM' : 'AM'

на

    ifElse(pipe(head, gte(__, 12)), always('PM'), always('AM'))

, и я уверен, что мы могли бы сделать что-то еще более ужасное для

    ([h, m], meridian) => `${h}:${m} ${meridian}`

Но я не надену не вижу в этом смысла. Мы уменьшаем читаемость здесь, и было бы еще хуже, если бы мы попробовали другой.

И в этом все дело. Рамда это инструмент. Используйте его, когда он улучшает что-то важное в вашем коде: удобочитаемость, удобство обслуживания, производительность или что-то еще ощутимое. Но не используйте его просто потому, что он доступен и уже включен в ваш проект. Цель никогда не должна звучать так: « Как мне сделать эту работу, используя Рамду? », если вы не работаете над изучением Рамды. Цели должны включать простоту, ремонтопригодность, производительность и т. Д. c. Если Рамда поможет вам достичь этих целей, отлично. Пропустите это, если это не так.

Запоздалая мысль

Я только что понял, что ни один из вышеперечисленных не обрабатывает полночь правильно. Предположительно, вы хотите, чтобы "00:35" стал "12:35 AM", и это заняло бы незначительную настройку всех версий выше. Первым станет

const formatHour = (time) => {
  const [h, m] = time .split (':')
  return `${h > 12 ? h - 12 : h == 0 ? 12 : h}:${m} ${h >= 12 ? 'PM' : 'AM'}`
}

И аналогичные изменения необходимо будет применить к другим.

...