Read a date in xkcd notation

49

5

In his xkcd about the ISO 8601 standard date format Randall snuck in a rather curious alternative notation:

enter image description here

The large numbers are all the digits that appear in the current date in their usual order, and the small numbers are 1-based indices of the occurrences of that digit. So the above example represents 2013-02-27.

Let's define an ASCII representation for such a date. The first line contains the indices 1 to 4. The second line contains the "large" digits. The third line contains the indices 5 to 8. If there are multiple indices in a single slot, they are listed next to each other from smallest to largest. If there are at most m indices in a single slot (i.e. on the same digit, and in the same row), then each column should have be m+1 characters wide and left-aligned:

2  3  1  4
0  1  2  3  7
5     67    8

See also the companion challenge for the opposite conversion.

The Challenge

Given a date in xkcd-notation, output the corresponding ISO 8601 date (YYYY-MM-DD).

You may write a program or function, taking input via STDIN (or closest alternative), command-line argument or function argument and outputting the result via STDOUT (or closest alternative), function return value or function (out) parameter.

You may assume that the input is any valid date between years 0000 and 9999, inclusive.

There won't be any leading spaces in the input, but you may assume that the lines are padded with spaces to a rectangle, which contains at most one trailing column of spaces.

Standard rules apply.

Test Cases

2  3  1  4
0  1  2  3  7
5     67    8
2013-02-27

2  3  1     4
0  1  2  4  5
   5  67 8
2015-12-24

     1234
1    2
5678
2222-11-11

   1     3  24
0  1  2  7  8
57    6     8
1878-02-08

2   4   1   3
0   1   2   6
5       678
2061-02-22

      1 4 2 3
0 1 2 3 4 5 6 8
6 5 7         8
3564-10-28

1234
1
5678
1111-11-11

1 2 3 4
0 1 2 3
8 5 6 7
0123-12-30

Martin Ender

Posted 2015-12-19T14:02:42.533

Reputation: 184 808

11People who write the date in the "black cat" format are the bane of my existence. – Carcigenicate – 2015-12-19T20:02:17.203

1Forgive my ignorance, but how exactly does the weird format correspond with the date? Can't for the life of me work out the pattern. – Tom Carpenter – 2015-12-20T20:37:53.293

2@TomCarpenter The bottom and top line indicate where the numbers in the middle line appear in the date. E.g. 1 is above 2, so the first digit is 2. 2 is above 0, so the second digit is 0. 3 is above 1, 4 is above 3, so we get 2013 as the first four digits. Now 5 is below 0, so the fifth digit is 0, 6 and 7 are both below 2, so both of those digits are 2. And finally, 8 is below 7, so the last digit is 8, and we end up with 2013-02-27. (The hyphens are implicit in xkcd notation because we know at what positions they appear.) – Martin Ender – 2015-12-20T20:40:16.443

Answers

8

CJam, 35 bytes

ll{1$e>}*](l+eeWf%$-8>Wf=\f=2/(o'-*

Try it here. It expects the input lines to be padded with spaces.

Explanation

ll reads two lines of input, and {1$e>}* performs a "scan" on the second one: it it takes all prefixes of its input and computes the maximum of each prefix. For the input line "0 1 2 7 8", this pushes "0001112227778". Our stack now looks like this:

"first line" '0 '0 '0 '1 '1 '1 ...

We need to re-capture the values into a list ourselves using ]; this captures our first line as well, so we pop it back out using (, to get

"0001112227778" "first line"

as expected.

eelee+ enumerates this line, then does the same for a third input line, and concatenates the results, leaving something like this on the top of the stack:

[[0 '5] [1 ' ] [2 ' ] [3 ' ] [4 ' ] [5 ' ] [6 ' ] [7 ' ] [8 '6] [9 '7] [10 '8] [11 ' ] [12 ' ]
 [0 '2] [1 ' ] [2 ' ] [3 ' ] [4 '4] [5 ' ] [6 ' ] [7 ' ] [8 '1] [9 ' ] [10 ' ] [11 ' ] [12 '3]]

Now our stack is ["0001112227778" X] where X is the enumerated list above.

We flip each pair in X (Wf%), sort the pairs lexicographically ($), and leave the last 8 pairs -8>. This gets us something like:

[['1 8] ['2 0] ['3 12] ['4 4] ['5 0] ['6 8] ['7 9] ['8 10]]

This works, because the sort places all the pairs with key ' (space) before all the digits in ascending order.

These are the "x-positions" of the characters 12345678 on the first and third lines: we only need to retrieve the characters from our (modified) second line that are vertically aligned with them.

To do this, we take each position (Wf=), index into the string we made earlier (\f=). We have "20610222" on the stack now: to add the dashes, first we split into segments of length two (2/), print the first segment without a newline ((o) and join the remaining segments with dashes ('-*).

EDIT: cool scan trick, Martin! Saved four bytes.

EDIT 2: saved two more bytes by replacing eelee+ with l+ee; this works, because the lines all have the same lengths, and list indexing in CJam is automatically modulo the list length, so the indices n+0, n+1, n+2... nicely map to 0, 1, 2...

EDIT 3: Martin saved another byte in the final step of the process. Nice!

Lynn

Posted 2015-12-19T14:02:42.533

Reputation: 55 648

6

Pyth, 48 43

j\-cj\-ctuuXN.xsTZK?qJhtHdKJ+hHeHGC.z*9d4 7

Test Suite

Requires padding with spaces into a rectangle.

I don't think this is the best approach, but basically it writes the middle value to the index in a string pointed at by the top or bottom value. Well I guess I had enough time to golf most of the obvious stuff I saw. :P

FryAmTheEggman

Posted 2015-12-19T14:02:42.533

Reputation: 16 206

4

JavaScript (ES7), 115

Anonymous function. Using template strings, there is a newline that is signifiocant and included in byte count.

Requirement: the middle input line cannot to be shorter than the first or the last. This requirement is satisfied when the input is padded with spaces to form a rectangle.

x=>([a,z,b]=o=x.split`
`,d=i=0,[for(c of z)o[a[i]-1]=o[b[i++]-1]=d=+c||d],o.splice(4,2,'-',o[4],o[5],'-'),o.join``)

ES6 version 117 using .map instead of array comprehension

x=>([a,z,b]=o=x.split`
`,d=0,[...z].map((c,i)=>o[a[i]-1]=o[b[i]-1]=d=+c||d],o.splice(4,2,'-',o[4],o[5],'-'),o.join``)

Less golfed

x=>(
  o=[],
  [a,z,b] = x.split`\n`,
  d=i=0,
  [ for(c of z) (
      d = +c||d, // each new digit found in z goes in d (but not the spaces and not the '0' (d starts at 0 anyway)
      o[a[i]-1] = o[b[i]-1] = d, // if the index char is space, that gives index -1 that is ignored when joining later
      ++i
  )],
  o.splice(4,2,'-',o[4],o[5],'-'), // add the dashes in the right places
  o.join``
)

Test snippet

f=x=>(
  [a,z,b]=o=x.split`\n`,
  d=i=0,[for(c of z)o[a[i]-1]=o[b[i++]-1]=d=+c||d],
  o.splice(4,2,'-',o[4],o[5],'-'),o.join``
)


console.log=x=>O.textContent+=x+'\n';

[['2  3  1  4\n0  1  2  3  7\n5     67    8','2013-02-27']
,['2  3  1     4\n0  1  2  4  5\n   5  67 8','2015-12-24']
,['      1234\n1     2   \n5678','2222-11-11']
,['   1     3  24\n0  1  2  7  8 \n57    6     8','1878-02-08']
,['2   4   1   3\n0   1   2   6  \n5       678','2061-02-22']
,['      1 4 2 3\n0 1 2 3 4 5 6 8\n6 5 7         8','3564-10-28']
,['1234\n1   \n5678','1111-11-11']
,['1 2 3 4\n0 1 2 3\n8 5 6 7','0123-12-30']]
.forEach(t=>(k=t[1],r=f(t[0]),console.log(t[0]+'\n'+r+'\n'+(r==k?'OK\n':'Fail\n'))))
<pre id=O></pre>

edc65

Posted 2015-12-19T14:02:42.533

Reputation: 31 086

Congrats on being the first to solve both challenges. :) – Martin Ender – 2015-12-20T14:33:36.873

3

Haskell, 125 106 103 bytes

a#' '=a
a#b=b
f i|[a,b,c]<-('-':)<$>lines i=[o|m<-"1234-56-78",(n,o,p)<-zip3 a(scanl1(#)b)c,m==n||m==p]

Requires padding with spaces to a full rectangle.

Usage example: f " 1 3 24\n0 1 2 7 8 \n57 6 8 " -> "1878-02-08".

How it works:

[a,b,c]<-('-':)<$>lines i          -- split input into lines, prepend a '-' to
                                   -- each, call them a, b and c
               (scanl1(#)b)        -- fill spaces of the middle line with the
                                   -- previous char, e.g.
                                   -- "-0  1  2  7  8 " -> "-00011122277788"
        zip3 a (scanl...) c        -- combine the lines element wise into triples.
                                   -- This is our lookup table for "1234-56-78" 
o|m<-"1234...",  (n,o,p)<-zip...,  m==n||m==p
                                   -- whenever m equals n or p (i.e. was originally
                                   -- in the first or last line), take the
                                   -- corresponding char o (middle line)

nimi

Posted 2015-12-19T14:02:42.533

Reputation: 34 639

2

JavaScript ES6, 231

a=>{r=[];var b=[d,f,e]=a.split`
`.map(n=>n.split``);Array(Math.max(...b.map(n=>n.length))).fill().map((m,i)=>{(m=f[i])&&m!=" "&&(c=m);[d,e].map(m=>(g=m[i])&&g!=" "&&(r[g-1]=c))}),r.splice(4,0,"-"),r.splice(7,0,"-");return r.join``}

Test cases.

Michał Perłakowski

Posted 2015-12-19T14:02:42.533

Reputation: 520

1

Perl, 154 bytes

sub{$_=$_[1];@n=/\d/g;/ +/;map{map{$p[$i++].=$_}unpack"(a$+[0])*";$i=0}@_[0,2];map{map{$r[$_-1]=$n[$i]if/\d/}s plit$"='';$i++}@p;"@r"=~s/....\K(..)/-$1-/r}

Ungolfed & explained

sub{
    $_=$_[1]; # $_[1] is 2nd argument (i.e., 2nd line)
    @n=/\d/g; # @n now contains all digits in 2nd line
    / +/;     # $+[0] now the chunk length in 2nd line
              # Equivalent to /( +)/;$l = 1 + length $1;
    map{      # Perl golfer's for-loop
        map{ 
            $p[$i++] .= $_    # @p contains positions of each digit
        } unpack "(a$+[0])*"; # Split line into same chunk width
        $i=0 # At end of loop so we don't need $i=0 before next one
    } @_[0,2];# Outer map works on 1st and 3rd lines
    map{
        map{
            # Shove $n[$i] into ($_-1)th slot in @r if $_ is a number
            $r[$_-1] = $n[$i] if /\d/
        } split $"=''; # Equivalent to split '', but sets $"='' for free
        $i++
    }@p;
    # Concatenate @r, convert 20130227 to 2013-02-27, and return
    "@r"=~s/....\K(..)/-$1-/r
};

type_outcast

Posted 2015-12-19T14:02:42.533

Reputation: 511

0

Powershell, 119 bytes

$r=,'-'*99
($a=$args-split'
')[1]|% t*y|%{if($_-32){$d=$_}
$a[0,2]|%{$r[$_[+$p]-48]=$d}
$p++}
-join$r[1..4+0+5+6+0+7+8]

Ungolfed test script:

$f = {

$r=,'-'*99                       # init a result as an array of '-' repeated 99 times
($a=$args-split"`n")[1]|% t*y|%{ # split argument string, store a top, middle and bottom to $a, then for each char of the middle line...
    if($_-32){$d=$_}             # store a digit to $d if the current character of the middle is not a space
    $a[0,2]|%{                   # for the top and the bottom lines...
        $r[$_[+$p]-48]=$d        # store a digit to the result array
    }                            # Note: if char in the current position is a space, then expression $_[+$p]-48 less then 0.
                                 # In this case, the expression $r[32-48]=$d changes unused element in a end of the array.
                                 # That is why the array was created by a large.
    $p++                         # next position
}
-join$r[1..4+0+5+6+0+7+8]        # return joined char with specified numbers
                                 # Note: element with index 0 has value '-'
}

@(
,(@"
2  3  1  4   
0  1  2  3  7
5     67    8
"@,"2013-02-27")

,(@"
2  3  1     4
0  1  2  4  5
    5  67 8  
"@,"2015-12-24")

,(@"
     1234
1    2   
5678     
"@,"2222-11-11")

,(@"
1     3  24
0  1  2  7  8 
57    6     8 
"@,"1878-02-08")

,(@"
2   4   1   3
0   1   2   6
5       678  
"@,"2061-02-22")

,(@"
      1 4 2 3  
0 1 2 3 4 5 6 8
6 5 7         8
"@,"3564-10-28")

,(@"
1234
1   
5678
"@,"1111-11-11")

,(@"
1 2 3 4
0 1 2 3
8 5 6 7
"@,"0123-12-30")

) | % {
    $a,$expected = $_
    $result = &$f $a
    "$(""$result"-eq"$expected"): $result"
}

Output:

True: 2013-02-27
True: 2015-12-24
True: 2222-11-11
True: 1878-02-08
True: 2061-02-22
True: 3564-10-28
True: 1111-11-11
True: 0123-12-30

mazzy

Posted 2015-12-19T14:02:42.533

Reputation: 4 832

0

Jelly, 38 bytes

Ỵṙ-Zn⁶Ṫ€œṗƊḊZḟ⁶V€$€;2/p/Ʋ€ẎṢṪ€s2Ḣ;jɗ”-

Try it online!

The helper is only there to make input easier; this is actually a full program. Be sure to care for:

  • The first and last lines ('''), as well as the lines next to them (empty, there for clarity).
    • The actual input format doesn't have the second and penultimate empty lines, and the string starts and ends directly next to the quotes, without a newline in-between, like this:
      '''   1     3  24
      0  1  2  7  8 
      57    6     8 '''
      You can leave the footer in while using this format. This is really a Python multi-line string, and the quotes are necessary for some inputs.
  • Pad the input with trailing spaces! Any correct output without correctly padded input is entirely coincidental, and isn't endorsed by me.

Erik the Outgolfer

Posted 2015-12-19T14:02:42.533

Reputation: 38 134

0

JavaScript (ES6), 131 bytes

s=>[...(r=[,,,,"-",,,"-"],l=s.split`
`)[1]].map((c,i)=>(c>"-"?n=c:0,y=+l[0][i],d=+l[2][i],y?r[y-1]=n:0,d?r[d+(d>6)]=n:0))&&r.join``

Explanation

Requires input to be padded with spaces to form a rectangle.

s=>
  [...(
    r=[,,,,"-",,,"-"], // r = array of result characters, prefill with "-" symbols
    l=s.split`
`                      // l = array of lines
  )[1]].map((c,i)=>(   // for each character on the middle line
    c>"-"?n=c:0,       // n = the most recent digit encountered
    y=+l[0][i],        // y = index on the year line at the current position
    d=+l[2][i],        // d = index on the date line at the current position
    y?r[y-1]=n:0,      // if y is a number, put n at the index y of the result
    d?r[d+(d>6)]=n:0   // if d is a number, put n at the index d (accounting for "-"s)
  ))
  &&r.join``           // return the result as a string

Test

var solution = s=>[...(r=[,,,,"-",,,"-"],l=s.split`
`)[1]].map((c,i)=>(c>"-"?n=c:0,y=+l[0][i],d=+l[2][i],y?r[y-1]=n:0,d?r[d+(d>6)]=n:0))&&r.join``
<textarea id="input" rows="3" cols="30">   1     3  24
0  1  2  7  8 
57    6     8 </textarea><br />
<button onclick="result.textContent=solution(input.value)">Go</button>
<pre id="result"></pre>

user81655

Posted 2015-12-19T14:02:42.533

Reputation: 10 181