Converter intervalo de tempo legível por humanos em componentes de data

16

Desafio

Escreva o programa mais curto que converte um intervalo de tempo legível por humanos em componentes de data do formulário:

{±YEARS|±MONTHS|±DAYS|±HOURS|±MINUTES|±SECONDS}

Casos de amostra

Cada caso de teste é de duas linhas, entrada seguida por saída:

1 year 2 months 3 seconds
{1|2|0|0|0|3}

-2 day 5 year 8months
{5|8|-2|0|0|0}

3day 9     years 4 seconds -5 minute 4 years 4 years -3seconds
{17|0|3|0|-5|1}

Regras

  • Você não pode usar strtotimeou qualquer função interna que faça todo o trabalho.
  • Vitórias com códigos mais curtos (bytes)
  • Você pode imprimir sua saída stdoutou um arquivo, o resultado também pode ser retornado por uma função, cabe a você
  • O token pode estar na forma singular ou plural.
  • Os componentes podem estar em uma ordem aleatória
  • Pode não haver espaço em branco entre o número e o token
  • O sinal é opcional quando o intervalo de tempo é positivo (entrada e saída)
  • Se um componente aparecer mais de uma vez, os valores devem ser adicionados
  • Cada componente tem seu próprio sinal
  • Os componentes devem ser manuseados separadamente (por exemplo, 80 minutespermanecem 80 na saída)
  • A entrada é garantida em minúsculas

Golfe feliz!

fpg1503
fonte
2
Gosto desse desafio, mas estou tendo dificuldades para encontrar algo que não seja longo e confuso em idiomas inadequados para o golfe de código. : /
Alex A.
O formato da saída é importante?
Titus
Sign is optional when the time interval is positiveIsso significa que a entrada pode conter +sinais?
Titus

Respostas:

3

CJam, 60 bytes

Depois de ficar preso nos anos 60 por um longo tempo, finalmente consegui compactar isso para 60 bytes. Bom o bastante! Enviá-lo!

Experimente online

Squished:

'{0a6*q[{_A,s'-+#)!{"ytdhic"#:I){]'0+iA/I_3$=@+t[}*}*}/'|*'}

Expandido e comentado:

'{              "Add '{' to output";
0a6*            "Initialize time to a list of 6 zeros";
q               "Read the input";
[               "Open an empty numeric character buffer";
{               "For each character in the input:";
  _               "Append the character to the numeric character buffer";
  A,s'-+#)!       "Check if the character is not part of a number";
  {               "If so:";
    "ytdhic"#:I     "Remove the character from the numeric character buffer and
                     convert it to the corresponding time unit index, or -1 if
                     not recognized
                     (Time units are recognized by a character in their name
                     that does not appear before the recognition character
                     in any other name)";
    ){              "Repeat (time unit index + 1) times:";
      ]'0+iA/         "Close the numeric character buffer and parse it as an
                       integer (empty buffer is parsed as 0)";
      I_3$=@+t        "Add the integer to the value of the indexed time unit";
      [               "Open an empty numeric character buffer";
    }*              "End repeat
                     (This is used like an if statement, taking advantage of
                     the fact that iterations after the first have no effect)";
  }*              "End if";
}/              "End for";
'|*             "Insert a '|' between each time unit value (implicitly added to
                 output)";
'}              "Add '}' to output";

Inicialmente, comecei a usar uma abordagem baseada em token, mas isso ficou muito firme em ... 61 bytes. Suspiro. Então mudei totalmente de ideia e mudei para essa abordagem baseada em caracteres, que é muito mais interessante de qualquer maneira.

Meu método de análise funciona adicionando caracteres numéricos válidos alcançados ( 0- 9e -) a um buffer e analisando o buffer como um número inteiro quando um determinado caractere de um dos nomes de unidades de tempo é atingido. Esses personagens são y, t, d, h, i, ec, que satisfazem as condições em que aparecem em um nome de unidade de tempo e não aparecem antes do caractere de reconhecimento em nenhum outro nome de unidade de tempo. Em outras palavras, quando um desses caracteres de reconhecimento da unidade de tempo é atingido, o buffer numérico será preenchido com o último número visto se isso realmente sinalizar uma unidade de tempo, ou o buffer numérico ficará vazio se isso aparecer, mas não deve ' sinal t, alguma outra unidade de tempo. Nos dois casos, o buffer numérico é analisado como um número inteiro, ou 0, se estiver vazio, e isso é adicionado ao valor da unidade de tempo correspondente. Assim, os caracteres de reconhecimento que aparecem em outras unidades de tempo após o caractere de reconhecimento não têm efeito.

Outros hacks malucos incluem:

  • Abusar loops para que caracteres numéricos sejam deixados na pilha (que atua como o buffer de caracteres numéricos) "de graça".
  • Repetir um bloco zero ou várias vezes em vez de condicionalmente porque o loop é mais compacto que uma instrução if e as iterações após a primeira não têm efeito.

Para qualquer pessoa curiosa sobre a minha solução baseada em token que ficou paralisada em 61 bytes, também a publicarei aqui. Eu nunca cheguei a expandir ou comentar, no entanto.

CJam, 61 bytes

'{0a6*q'm-'{,64/~m*{:X/XS**}/S%2/{~0="yodhis"#_3$=@i+t}/'|*'}
Runer112
fonte
+1 Isso definitivamente merece mais votos.
oopbase
2
@ Forlan07 Obrigado pelo apoio. :) Mas eu estava um pouco atrasado para responder, por isso não é inesperado. O processo de produção desta resposta foi satisfatório de qualquer maneira.
Runer112
10

Perl: 61 caracteres

Graças a @nutki.

s/-?\d+ *m?(.)/$$1+=$&/ge;$_="{y|o|d|h|i|s}";s/\w/${$&}+0/ge

Exemplo de execução:

bash-4.3$ perl -pe 's/-?\d+ *m?(.)/$$1+=$&/ge;$_="{y|o|d|h|i|s}";s/\w/${$&}+0/ge' <<< '1 year 2 months 3 seconds'
{1|2|0|0|0|3}

bash-4.3$ perl -pe 's/-?\d+ *m?(.)/$$1+=$&/ge;$_="{y|o|d|h|i|s}";s/\w/${$&}+0/ge' <<< '-2 day 5 year 8months'
{5|8|-2|0|0|0}

bash-4.3$ perl -pe 's/-?\d+ *m?(.)/$$1+=$&/ge;$_="{y|o|d|h|i|s}";s/\w/${$&}+0/ge' <<< '3day 9     years 4 seconds -5 minute 4 years 4 years -3seconds'
{17|0|3|0|-5|1}

Meus fracos esforços: 78 77 caracteres

s/([+-]?\d+) *(..)/$a{$2}+=$1/ge;$_="{ye|mo|da|ho|mi|se}";s/\w./$a{$&}||0/ge
homem a trabalhar
fonte
11
Algumas melhorias eu poderia encontrar:s/(-?\d+) *(..)/$$2+=$1/ge;$_="{ye|mo|da|ho|mi|se}";s/\w./${$&}+0/ge
nutki
11
Outros 4 caracteres:s/-?\d+ *(m.|.)/$$1+=$&/ge;$_="{y|mo|d|h|mi|s}";s/\w+/${$&}+0/ge
nutki
Uau. Ótimos truques, @nutki.
Manatwork
11
Também encontrado em outras soluções, (m.|.)-> m?(.)salva adicional 4.
nutki
Doh. Isso estava prestes a experimentar agora. Então funciona. :)
manatwork
5

Ruby, 119 106 86 85 84 bytes

Um byte economizado graças ao Sp3000.

->i{?{+"yodhis".chars.map{|w|s=0;i.scan(/-?\d+(?= *m?#{w})/){|n|s+=n.to_i};s}*?|+?}}

Esta é uma função sem nome, que recebe a entrada como uma sequência e retorna o resultado (também como uma sequência). Você pode testá-lo atribuindo-o a f, digamos, e chamando-o como

f["3day 9     years 4 seconds -5 minute 4 years 4 years -3seconds"]
Martin Ender
fonte
5

Python 2, 99 bytes

import re
f=lambda I:"{%s}"%"|".join(`sum(map(int,re.findall("(-?\d+) *m?"+t,I)))`for t in"yodhis")

Esta é uma função lambda que pega uma string e simplesmente usa um regex para extrair os números necessários.

Obrigado a Martin por apontar que \s*poderia ser <space>*. É fácil esquecer que as expressões regulares combinam espaços literalmente ...

Sp3000
fonte
4

JavaScript 100 105 112

Editar Adicionando strings de modelo (implementado pela primeira vez em dezembro de 2014, tão válido para este desafio) - no momento eu não os conhecia

Editar Eureka, finalmente entendi o significado de m?todas as outras respostas!

s=>s.replace(/(-?\d+) *m?(.)/g,(a,b,c)=>o['yodhis'.search(c)]-=-b,o=[0,0,0,0,0,0])&&`{${o.join`|`}}`

Teste

F=
s=>s.replace(/(-?\d+) *m?(.)/g,(a,b,c)=>o['yodhis'.search(c)]-=-b,o=[0,0,0,0,0,0])&&`{${o.join`|`}}`

;['1 year 2 months 3 seconds','-2 day 5 year 8months'
,'3day 9     years 4 seconds -5 minute 4 years 4 years -3seconds']
.forEach(i=>console.log(i,F(i)))

edc65
fonte
3

R, 197 bytes

Sei que essa não é uma entrada competitiva, eu só queria encontrar uma solução em R. Qualquer ajuda para encurtar isso é bem-vinda.

function(x){s="{";for(c in strsplit("yodhis","")[[1]])s=paste0(s,ifelse(c=="y","","|"),sum(as.numeric(gsub("[^0-9-]","",str_extract_all(x,perl(paste0("(-?\\d+) *m?",c)))[[1]]))));s=paste0(s,"}");s}

Como a resposta de Martin, essa é uma função sem nome. Para chamá-lo, atribua-o fe passe uma string.

Isso é bastante hediondo, então vamos dar uma olhada em uma versão sem golfe.

function(x) {
    s <- "{"
    for (c in strsplit("yodhis", "")[[1]]) {
        matches <- str_extract_all(x, perl(paste0("(-?\\d+) *m?", c)))[[1]]
        nums <- gsub("[^0-9-]", "", matches)
        y <- sum(as.numeric(nums))
        s <- paste0(s, ifelse(c == "y", "", "|"), y)
    }
    s <- paste0(s, "}")
    return(s)
}

Com base apenas na estrutura, é fácil ver o que está acontecendo, mesmo se você não estiver muito familiarizado com R., vou detalhar alguns dos aspectos de aparência mais estranha.

paste0() é assim que R combina strings sem separador.

A str_extract_all()função vem do stringrpacote de Hadley Wickham . O tratamento de R de expressões regulares no pacote base deixa muito a desejar, e é aí que stringrentra. Essa função retorna uma lista de correspondências de expressões regulares na string de entrada. Observe como o regex é cercado por uma função perl()- isso significa apenas que o regex é no estilo Perl, não no estilo R.

gsub()faz uma busca e substituição usando uma regex para cada elemento do vetor de entrada. Aqui, dizemos para substituir tudo que não é um número ou sinal de menos por uma string vazia.

E aí está. Mais explicações serão fornecidas com prazer, mediante solicitação.

Alex A.
fonte
Não acho que terceirizar a extração de strings para um pacote externo seja uma boa idéia. Não é uma brecha quando uma biblioteca externa suportada pela comunidade é usada? Mesmo se estiver tudo bem, por que você não incluiu library(stringr)na sua fonte?
Andreï Kostyrka 11/08/19
2

Cobra - 165

def f(s='')
    l=int[](6)
    for i in 6,for n in RegularExpressions.Regex.matches(s,'(-?\\d+) *m?['yodhis'[i]]'),l[i]+=int.parse('[n.groups[1]]')
    print'{[l.join('|')]}'
Furioso
fonte
2

C ++ 14, 234 229 bytes

Editar: reduza 5 bytes usando a declaração de estilo antigo em vez deauto.

Sei que o vencedor já foi escolhido e que essa seria a inscrição mais longa até agora, mas eu apenas tive que postar uma solução em C ++, porque aposto que ninguém esperava uma :)

Para ser honesto, estou muito feliz com o quão curto acabou por ser (por meio de medições em C ++, é claro), e tenho certeza de que não pode ficar mais curto que isso (com apenas uma observação, veja abaixo) . Também é uma ótima coleção de recursos novos para o C ++ 11/14.

Não há bibliotecas de terceiros aqui, apenas a biblioteca padrão é usada.

A solução está na forma de função lambda:

[](auto&s){sregex_iterator e;auto r="{"s;for(auto&t:{"y","mo","d","h","mi","s"}){int a=0;regex g("-?\\d+ *"s+t);decltype(e)i(begin(s),end(s),g);for_each(i,e,[&](auto&b){a+=stoi(b.str());});r+=to_string(a)+"|";}r.back()='}';s=r;};

Ungolfed:

[](auto&s)
{
    sregex_iterator e;
    auto r="{"s;
    for(auto&t:{"y","mo","d","h","mi","s"})
    {
        int a=0;
        regex g("-?\\d+\\s*"s+t);
        decltype(e)i(begin(s),end(s),g);
        for_each(i,e,[&](auto&b)
        {
            a+=stoi(b.str());
        });
        r+=to_string(a)+"|";
    }
    r.back()='}';
    s=r;
}

Por alguma razão, eu tive que escrever

regex g("-?\\d+\\s*"s+t);
decltype(e)i(begin(s),end(s),g);

em vez de apenas

decltype(e)i(begin(s),end(s),regex("-?\\d+\\s*"s+t));

porque o iterador retornaria apenas uma correspondência se eu passasse um objeto temporário. Isso não parece certo para mim, então me pergunto se há algum problema com a implementação de regex do GCC.

Arquivo de teste completo (compilado com o GCC 4.9.2 com -std=c++14):

#include <iostream>
#include <string>
#include <regex>

using namespace std;

int main()
{
    string arr[] = {"1 year 2 months 3 seconds",
                    "-2 day 5 year 8months",
                    "3day 9     years 4 seconds -5 minute 4 years 4 years -3seconds"};
    for_each(begin(arr), end(arr), [](auto&s){sregex_iterator e;auto r="{"s;for(auto&t:{"y","mo","d","h","mi","s"}){int a=0;auto g=regex("-?\\d+ *"s+t);decltype(e)i(begin(s),end(s),g);for_each(i,e,[&](auto&b){a+=stoi(b.str());});r+=to_string(a)+"|";}r.back()='}';s=r;});
    for(auto &s : arr) {cout << s << endl;}
}

Resultado:

{1|2|0|0|0|3}
{5|8|-2|0|0|0}
{17|0|3|0|-5|1}
Alexander Revo
fonte
0

PHP, 141 bytes

preg_match_all("#(.?\d+)\s*m?(.)#",$argv[1],$m);$r=[0,0,0,0,0,0];foreach($m[1]as$i=>$n)$r[strpos(yodhis,$m[2][$i])]+=$n;echo json_encode($r);

recebe entrada do primeiro argumento da linha de comando; usa [,]para saída em vez de {|}. Corra com -r.

demolir

preg_match_all("#(.?\d+)\s*m?(.)#",$argv[1],$m);    # find intervals.
# (The initial dot will match the sign, the space before the number or a first digit.)
$r=[0,0,0,0,0,0];                   # init result
foreach($m[1]as$i=>$n)              # loop through matches
    $r[strpos(yodhis,$m[2][$i])]+=$n;   # map token to result index, increase value
echo json_encode($r);               # print result: "[1,2,3,4,5,6]"
Titus
fonte