Oblicz różnicę między dwoma dniami.

11

Kolejny problem z manipulowaniem datami: P

Zadanie

Napisz program lub funkcję, która oblicza różnicę między dwiema datami podanymi przez użytkownika.

Wejście wyjście

Podobnie jak poprzedni , dane wejściowe to dwa YYYYMMDDs, oddzielone spacją , przecinkiem ,lub znakiem minus -.

Przykład wartości wejściowych:

20100101-20010911
20110620-20121223
19000101 20101010
33330101,19960229
00010101 99991231

Dane wyjściowe to liczba całkowita, która stanowi różnicę między dwiema datami w dniach.

Na przykład: 20110101-20100101wydajność 365i 33320229 17000101wydajność 596124.

Możesz przetestować wyniki tutaj co tutaj . (Zobacz komentarze rintaun poniżej.) Jeśli dwie daty są takie same, program powinien powrócić 0, jeśli data jest prawidłowa (patrz punktacja ).

Ograniczenie

Oczywiście nie wolno używać żadnych funkcji / klas / ..., które są związane ze znacznikiem czasu lub datą, i należy używać kalendarza gregoriańskiego .

Wynik

Jeśli twój kod nie zachowuje ograniczenia, to score = -∞.

Domyślnie bonusjest to 1.

  • Jeśli Twój kod działa niezależnie od kolejności wejść (na przykład, 20100101,20110101zwrotów 365lub -365) bonus+=1.
  • Jeśli Twój kod może obsługiwać roku 0 , bonus+=0.5.
  • Jeśli kod rozpozna nieprawidłowy miesiąc (od 1 do 12) / datę (od 1 do 31), polub 20109901lub 34720132, i wydrukuje E(i zakończy działanie programu lub zwróci coś w rodzaju 0) bonus+=1,.
  • Niezależnie od powyższej zasady, jeśli kod rozpozna nieprawidłowe daty, takie jak 20100230, 20100229lub 20111131, i drukuje E(i kończy działanie programu lub zwraca coś takiego 0) bonus+=1,.
  • Niezależnie od powyższych dwóch zasad, jeśli kod rozpozna nieprawidłowy ciąg wejściowy, taki jak 20100101|20100202lub 2010010120100202, i drukuje E(i kończy działanie programu lub zwraca coś takiego 0) bonus+=1,.

score = floor(-4.2*code.length/bonus). Wygrywa kod z najwyższym wynikiem. Jeśli dwa najlepsze kody mają taki sam wynik, wygrywają kody z najwyższym bonusem. Jeśli dwa najlepsze kody mają zarówno ten sam wynik, jak i bonus, wygrywają kody z najwyższą liczbą głosów.

(Due: Gdy jest więcej niż 5 kodów, które mają więcej niż (lub równe) +1głosów).

JiminP
źródło
Czy według trzeciego bonusu 20030229 jest uważany za nieprawidłowy termin?
rintaun
@rintaun Tak. W przeciwieństwie do tego jest nieważny 20040229. : P
JiminP
1
Czy WolframAlpha faktycznie zwraca poprawny wynik? Otrzymuję od niego sprzeczne odpowiedzi i timeanddate.com . Mój program, który moim zdaniem działa poprawnie (przynajmniej w tym przypadku: P), zgadza się z tym ostatnim.
rintaun
@rintaun Myślę, że Wolfram | Alpha się mylił, ponieważ 365*4 + 2 + 2= 1464. Dzięki za informację!
JiminP
1
Należy zauważyć, że nawet w przypadku timeanddate.com występują pewne problemy: akceptuje tylko lata 1-3999 i automatycznie dostosowuje się do 11-dniowej rozbieżności między kalendarzami juliańskim i gregoriańskim dla dat przed 3 września 1752 r. (więc od 17520903 do 17520914 nie są prawidłowymi datami). Pamiętaj o tym podczas testowania wyników.
rintaun

Odpowiedzi:

3

Perl 5,14, wynik = -162

-163 -181 -196 -214 -167 -213 -234
  • code.length = 211: 208 znaków źródłowych + 3 do uruchamiania perla z -popcją
  • bonus = 5,5: domyślnie, zamówienie, rok 0, nigdy nieważny miesiąc / dzień, nieprawidłowa data, całkowicie niepoprawne dane wejściowe

Kod

$_=eval(join'-',map{($y,$m,$d)=/(....)(..)(..)/;die"E\n"if!($m*$d)||$m>12||$d>30+($m&1^$m>7)-($m==2)*(2-!($y=~s/00$//r%4));$y-=($m<3)-400;$d+int(($m+9)%12*30.6+.4)+int(365.2425*$y)}/^(\d{8})[ ,-](\d{8})$/)//E

Oblicza zmodyfikowaną liczbę dni juliańskich dla każdej daty (ignorując korekty związane z epoką, aby zapisać długość kodu) i odejmuje te dwie wartości. (zob. „Dzień Juliana” z Wikipedii ).

  • wymaga perl 5.14+ dla /ropcji zamiany
  • obliczenie długości miesiąca, aby uzyskać premię za nieważną datę: 30+($m&1^$m>7)część podaje długość dowolnego miesiąca oprócz lutego; reszta dostosowuje się do lutego w roku zwykłym lub przestępnym

Założenia

  • „użyj kalendarza gregoriańskiego” oznacza proleptyczny kalendarz gregoriański dla dat poprzedzających jakiekolwiek przejście Juliana na gregoriański, którego używamy. Oznacza to, że nie należy odejmować 11 dni dla przedziałów, które przekraczają, na przykład przejście brytyjskie z 3 września 1752 na 14 września 1752.
  • „obsłużyć rok 0” oznacza, na przykład, 00000101-00010101powinno dać 366, ponieważ 0 jest całkowitą wielokrotnością 400, a zatem rok 0 jest rokiem przestępnym.
DCharness
źródło
Po dokonanych zmianach wydaje się, że Twój program akceptuje teraz nieprawidłowe miesiące i dni, np . 20111300-20119999Zwroty 2717.
migimaru
@migimaru: Rzeczywiście zoptymalizowałem poprawność. Cerować. Zmienię i może do tego wrócę.
DCharness
2

PHP, wynik: -539.1

  • 706 znaków
  • Wszystkie przedmioty bonusowe; premia = 5,5

Kod

<?php $a='(\d{4})(0[0-9]|1[0-2])([0-2][0-9]|3[01])';@$p=preg_match;if(!$p('/^(\d{8})[- ,](\d{8})$/',fgets(STDIN),$z))@die(E);unset($z[0]);sort($z);foreach($z AS$x){if(!$p('/(\d{4})(0[0-9]|1[0-2])(0[1-9]|[12][0-9]|3[01])/',$x,$w))die(E);$n[]=$w;}$m=array(31,28,31,30,31,30,31,31,30,31,30,31);$r=0;$b=$n[0][1];$c=$n[0][2];$d=$n[0][3];$e=$n[1][1];$f=$n[1][2];$g=$n[1][3];@$t=str_pad;if((($b.$e==229)&&(!(!($b%4)+!($b%100)-!($b%400))))||($c>12))die(E);for($z=$b.$c.$d;;$s=$d,$r++){if($z==$e.$f.$g)break;if($z>$e.$f.$g)@die(E);if(@$s==$d)$d++;if((($c!=2)&&($d>$m[$c-1]))||(($c==2)&&($d>($m[$c-1]+!($b%4)-!($b%100)+!($b%400))))){$c++;$d=1;}if($c>12){$b++;$c=1;}$z=$b.$t($c,2,0,0).$t($d,2,0,0);}echo($r>0)?--$r:0;

Nie golfił

<?php
$a='(\d{4})(0[0-9]|1[0-2])([0-2][0-9]|3[01])';
@$p=preg_match;
if(!$p('/^(\d{8})[- ,](\d{8})$/',fgets(STDIN),$z)) @die(E);
unset($z[0]);
sort($z);
foreach($z AS $x)
{
        if (!$p('/(\d{4})(0[0-9]|1[0-2])(0[1-9]|[12][0-9]|3[01])/',$x,$w)) die(E);
        $n[]=$w;
}
$m=array(31,28,31,30,31,30,31,31,30,31,30,31);
$r=0;
$b=$n[0][1];
$c=$n[0][2];
$d=$n[0][3];
$e=$n[1][1];
$f=$n[1][2];
$g=$n[1][3];
@$t=str_pad;
if ((($b.$e==229)&&(!(!($b%4)+!($b%100)-!($b%400))))||($c>12)) die(E);
for ($z=$b.$c.$d;;$s=$d,$r++)
{
        if ($z==$e.$f.$g)break;
        if ($z>$e.$f.$g)@die(E);
        if (@$s==$d)$d++;
        if ((($c!=2)&&($d>$m[$c-1]))||(($c==2)&&($d>($m[$c-1]+!($b%4)-!($b%100)+!($b%400)))))
        {
                $c++;
                $d=1;
        }
        if ($c>12)
        {
                $b++;
                $c=1;
        }
        $z=$b.$t($c,2,0,0).$t($d,2,0,0);
}
echo($r>0)?--$r:0;

Uwaga

Oblicza liczbę dni, iterując każdą prawidłową datę między dwoma podanymi. Jest dość wolny na większych zakresach. Jestem pewien, że nie jest to najlepszy sposób na rozwiązanie tego problemu, ale niecierpliwiłem się i właśnie z tym skończyłem. :)

Wiem też, że „niepoznany” kod wciąż nie jest zbyt czytelny, ale jego całkowite przepisanie wymagałoby zbyt wiele wysiłku.

rintaun
źródło
2

Ruby 1.9, Wynik: -175 -186 -191 -199

  • Długość kodu: 229 243 250 260 znaków
  • Premia: 5,5 (domyślnie, zamówienie, rok 0, nieprawidłowy miesiąc / dzień, nieprawidłowa data, nieprawidłowe dane wejściowe)

Kod akceptuje wprowadzanie przez standardowe wejście.

h=->n{n/4-n/100+n/400+1}
u,v=gets.split(/[ ,-]/).map{|s|s=~/^\d{8}$/?(d,e,f=[s[0,4],s[4,2],s[6,2]].map &:to_i;x=[0,y=31,28+h[d]-z=h[d-1]]+[y,30,y,30,y]*2
(!x[e]||e*f<1||f>x[e])?0:d*365+z+eval(x[0,e]*?+)+f):0}
puts (v*u>0)?u-v :?E

Uwagi:

  • h zwraca liczbę lat przestępnych do tego roku (w tym rok 0 dla premii).
  • Wyrażenie regularne obsługuje nieprawidłowy bonus wejściowy.
  • (!x[e]||e*f<1||f>x[e])Stan obsługuje nieprawidłowe miesiąc / dzień / data premie.
  • Wynik jest wyświetlany jako pierwsza data minus druga data, więc jeśli druga data jest późniejsza, zostanie wyświetlona jako liczba ujemna.
  • Nie dostosowuje się do zmiany między kalendarzami Julian i Gregorian, więc 33320229 17000101powoduje 596134.
migimaru
źródło
Dzięki za sprawdzanie błędów w moim rozwiązaniu i zachęcanie mnie do ciągłego doskonalenia. Szczególnie podoba mi się tutaj twoje obliczenie długości lutego.
DCharness
@DCharness Dzięki za popchnięcie mnie również. Uświadomiłem sobie, że w moim pierwotnym zgłoszeniu było dużo miejsca na ulepszenia.
migimaru
1

Python, wynik: -478

  • znaki: 455
  • bonus: odwrotne daty, nieprawidłowy dzień / miesiąc, nieprawidłowy termin

rozwiązanie:

import re
a=re.split('[-, ]',raw_input())
def c(x):return x[0]
def f(x,y=3):return(1if x%400==0 or x%100!=0and x%4==0 else 0)if y>2 else 0
t=[31,28,31,30,31,30,31,31,30,31,30,31]
[q,w,e],[i,o,p]=sorted([map(int,[a[x][:4],a[x][4:6],a[x][6:]])for x in[0,1]],key=c)
print sum(map(f,range(q,i)))+(i-q)*365+p+sum(t[:o-1])-e-sum(t[:w-1])+f(i,o)-f(q,w)if 0<w<13and 0<e<32and 0<o<13and 0<p<32and(e<=t[w-1]or(f(q)and e==29))and(p<=t[o-1]or(f(i)and p==29))else 'E'

Nie mam wersji „bez golfa”, bo tak to napisałem. Nie przetestowałem go poprawnie, więc jeśli znajdziesz błąd - skomentuj.

edit: mam nadzieję, że naprawiłem błąd wskazany w komentarzach i dodano rozpakowanie w postaci [a, b], [c, d] = [[1,2], [3,4]

rplnt
źródło
Niestety, ale kiedy testowałem z powłoką Python 2.7, nieprawidłowe dane wejściowe, takie jak „20000001,20010101”, nie są drukowane E. (FYI, 0>-1>12, 0>6>12, 0>13>12powraca False.)
JiminP
Dzięki. Jestem całkiem nowy w Pythonie. Pisząc ten skrypt dowiedziałem się, że Python dokonuje tego x<y<zporównania lub istnieje x if y else z. Próbowałem to naprawić.
rplnt
@rpInt: do gry w golfa jest też ten, [x,z][y]który jest krótszy niż x if y else z, choć nie zawsze działa, ponieważ w przeciwieństwie do wyrażenia if nie jest leniwy.
Lie Ryan,
1

PHP, wynik: -516

znaki: 685 676

premia: 5,5

<? $z='/((\d{1,4})(\d\d)(\d\d))[- ,]((\d{1,4})(\d\d)(\d\d))/';if(!preg_match($z,$argv[1],$m))die('E');$s=1;if($m[1]>$m[5]){if(!preg_match($z,"$m[5] $m[1]",$m))die('E');$s=-1;}$b=array(31,28,31,30,31,30,31,31,30,31,30,31);list($x,$v,$c,$d,$e,$w,$f,$g,$h)=$m;if($d>12||1>$d||$g>12||1>$g||1>$e||1>$h||($e>$b[$d-1]&&!($d==2&&$e<30&&$c%4==0))||($h>$b[$g-1]&&!($g==2&&$h<30&&$f%4==0)))die('E');$z='array_slice';$y='array_sum';$x=$d!=$g||$e>$h;$r=$x?$b[$d-1]+$h-$e:$h-$e;$d+=$x;if($d>12){$c++;$d=1;}$r+=$d>$g?$y($z($b,$d-1,13-$d))+$y($z($b,0,$g-1)):($d!=$g?$y($z($b,$d-1,$g-$d)):0);$r+=($f-$c-($d>$g))*365;for($i=$c;$i<=$f;$i++)if($i%4==0&&$i.'0229'>$v&&$i.'0229'<$w)$r++;echo $s*$r;
Alfwed
źródło
Kod PHP potrzebuje <?na początku do uruchomienia, w przeciwnym razie po prostu wypisuje kod.
Gareth