Parse the Parcel

24

Thanks to your help in the Mark My Mail challenge, PPCG-Post has successfully stamped all of its parcels with the generated barcodes!

Now, it's time to decode them.

In this challenge your program will, given a barcode generated from the Mark My Mail challenge, decode it and return the encoded integer.

But watch out! The barcode might be upside down...


4-state barcodes

In the case you missed the encoding challenge you'll need to know what kind of barcodes we're talking about. A 4-state barcode is a row of bars with four possible states, each representing a base-4 integer:

            |       |
Bar:    |   |   |   |
                |   |

Digit:  0   1   2   3

Rendered in ASCII, the barcodes will take up three lines of text, using the pipe (|) character to represent part of a bar, and a space () to represent an empty section. There will be a single space in between each bar. An example barcode may look like this:

|   | |   | | | |   |     |   |
| | | | | | | | | | | | | | | | |
    | |   |   | | |   |     |

To convert a barcode back to the integer it encodes, map each bar to its corresponding base-4 digit, concatenate these, and convert it to decimal.

As each barcode will also represent a different barcode when upside down, we implement a start/stop sequence so the orienation can be calculated. For the purpose of this challenge, we will be using the start/stop sequence specified by Australia Post: each barcode begins and ends with a 1 0 sequence.


The Challenge

Your task is to, given an ASCII 4-state barcode, parse it and return the integer it encodes - essentially the reverse of Mark My Mail.

But to spice things up, there's a catch - the barcode may be given upside down. As in the real world, it will be left to the barcode reader (your program) to determine the correct orientation using the start/stop sequence.

Example:

Given the following barcode:

    | |   |     |
| | | | | | | | | | |
  | |     |   |     |

We can clearly see that the first and last pairs of digits are 0, 2 and not 1, 0. This means that the barcode is upside down - so we must rotate it by 180 degrees (not just flip each bar) to achieve the correct orientation:

|     |   |     | |  
| | | | | | | | | | |
    |     |   | |    

Now, we can begin the decoding. We map each bar to its corresponding base-4 digit, ignoring the start/stop sequences as they do not encode the data.

|     |   |     | |  
| | | | | | | | | | |
    |     |   | |    

- - 2 1 0 3 0 2 3 - -

We concatenate this to the base-4 integer 2103023, then convert it to its decimal representation 9419 for the final result.


Rules

  • The input will always be a valid, 4-state barcode, rendered in ASCII as set out above, with the described start/stop sequence.
    • You may request trailing spaces, or stripped lines, as well as a trailing newline - whichever format suits your golfing.
    • It may or may not be in the correct orientation - your program must determine whether to read it upside down, by using the start/stop sequence.
    • It will not encode leading zero-digits in the base-4 integer.
  • You may take the input as a list of lines, or a string with newlines.
  • The output should be an integer in your language's standard integer base, representing the data that was encoded by the barcode.
  • As postage stamps are small and can fit very little code on them, your code will need to be as short as possible: this is a - so the shortest program (in bytes) wins!

Test Cases

| | | | | | | | | | |
  |             |   |

= 4096 (flipped)

      | | | |     | | | |
| | | | | | | | | | | | | | | |
  | |   | |   | |   | |   |   |

= 7313145 (flipped)

    | |   |     |
| | | | | | | | | | |
  | |     |   |     |

= 9419 (flipped)

|   | | | |   |   
| | | | | | | | | 
    | |   | |     

= 990 (not flipped)

|   |       |   | |   
| | | | | | | | | | | 
    |       | |       

= 12345 (not flipped)

FlipTack

Posted 2017-12-18T18:19:48.020

Reputation: 13 242

-1 the titles on the two posts aren't the same format. ("P the P" vs "M my M", pick either 'the' or 'my' :p) – Rɪᴋᴇʀ – 2017-12-18T18:56:10.977

1Would "list of lines" be any of [String], [{#Char}], [{Char}], [[Char]]?, given that String is equivalent to {#Char} – Οurous – 2017-12-18T20:48:24.497

3@Ouros What notation is that? I strongly suspect the answer is "yes", because data types are very different between languages, so coding challenges generally accept "whatever the equivalent is in your language", but I've no idea what different representations you're trying to represent. – IMSoP – 2017-12-19T09:38:03.703

1What's next? Emit the email? Transmit the Telegram? Process the Post? – Sanchises – 2017-12-19T13:15:56.823

1@Ourous yes, data type is flexible, as IMSoP described. – FlipTack – 2017-12-19T16:43:34.107

It's odd that you even have the middle line of characters, they will always be filled with |. – Magic Octopus Urn – 2017-12-19T21:56:51.730

@MagicOctopusUrn It's most likely for ease of scanning by real-life barcode machines - my guess is that the scanner works best when there's always a bar, so in the case that there is neither an ascender (top part) nor a descender (bottom part), there is still the central section (called the tracker) of a bar. – FlipTack – 2017-12-19T22:00:55.427

@FlipTack am I allowed to remove that as part of the input or nah? Saves me 1 byte – Magic Octopus Urn – 2017-12-19T22:04:57.243

@MagicOctopusUrn You can't remove it, it's a part of the barcode. Parsing the necessary information from the barcode is part of the challenge (that everyone else has managed to complete without complaint). – FlipTack – 2017-12-19T22:10:37.443

@FlipTack guess I more importantly should've asked if it can be taken as an array of 3 strings. That's what I really wanted to ask. – Magic Octopus Urn – 2017-12-19T22:58:25.717

@MagicOctopusUm As stated in the question: You may take the input as a list of lines, or a string with newlines. – FlipTack – 2017-12-20T09:13:38.770

Answers

6

Jelly, 18 bytes

>⁶m2Zm2UFUi¡1ṫ4Ḅ:⁴

Try it online!

Leaky Nun

Posted 2017-12-18T18:19:48.020

Reputation: 45 011

5

Husk, 16 bytes

ḋṁẊ=hhttĊ2T§▼↔m↔

Try it online!

Input is a list of lines (the TIO link uses a multiline string for clarity). The lines must have equal lengths, and there must not be extra trailing spaces.

Explanation

ḋṁẊ=hhttĊ2T§▼↔m↔  Input is a list of strings x.
           §▼     Lexicographic minimum of
             ↔    x reversed and
              m↔  x with each line reversed.
          T       Transpose. Now we have a list of columns.
        Ċ2        Get every second column, removing the blank ones.
    hhtt          Remove first 2 and last 2 (the orientation markers).
 ṁ                Map and concatenate
  Ẋ=              equality of adjacent pairs.
                  This turns a column like "|| " into [1,0], and these pairs are concatenated.
ḋ                 Convert from binary to integer.

Zgarb

Posted 2017-12-18T18:19:48.020

Reputation: 39 083

4

SOGL V0.12, 40 32 bytes

K@=«I⁷G@Κ¹;⌡I2n{j_{@≠}«+}¹jkjk4│

Try it Here!

dzaima

Posted 2017-12-18T18:19:48.020

Reputation: 19 048

3

Python 2, 113 105 bytes

lambda x:int(''.join([`2*(c>'#')+(a>'#')`for a,b,c in zip(*x[::1-2*(x[-1]>'#')].split('\n'))][4:-4:2]),4)

Try it online!

Rod

Posted 2017-12-18T18:19:48.020

Reputation: 17 588

2

Python 2, 96 bytes

lambda a:int(`[2*(r==q)+(p==q)for p,q,r in zip(*a[::(a>'|')*2-1].split('\n'))[4:-4:2]]`[1::3],4)

Try it online!

Lynn

Posted 2017-12-18T18:19:48.020

Reputation: 55 648

2

Retina, 71 bytes

(.).
$1
sO$^`^\|.*|.

^..|..¶.*¶..|..$
¶
\|
 |
+`\| 
 ||||
.*¶$
$&$&
\|

Try it online! Link includes smaller test cases. Requires the first and last lines to be space-padded to the length of the middle line. Explanation:

(.).
$1

Delete the unnecessary spaces.

sO$^`^\|.*|.

Reverse the characters in the code, but if the bar code begins with a |, then select the entire code, otherwise split it in to characters. Then, reverse them. This flips the code if it begins with a 0.

^..|..¶.*¶..|..$
¶

Delete the start/stop sequence and the middle row (which is of no use to us).

\|
 |
+`\| 
 ||||

Convert the spaces and |s from base 4 to unary.

.*¶$
$&$&

Double the last line.

\|

Convert to decimal.

Neil

Posted 2017-12-18T18:19:48.020

Reputation: 95 035

2

Clean, 191 ... 161 144 bytes

import StdEnv
?' '=0;?_=1
r=reverse
$a b|hd a<'|'= $(r b)(r a)=[?(b!!n)*2+ ?(a!!n)\\n<-[4,6..length b-4]]
@[a,_,b]=sum[d*4^p\\d<- $a b&p<-[0..]]

Try it online!

Οurous

Posted 2017-12-18T18:19:48.020

Reputation: 7 916

2

Java 8, 208 166 157 151 bytes

Trying it, probably can be better, reduced 42 due unnecessary checks, -9 removing variables, -6 thanks to Luke Stevens

Input is a char[][3]

(a)->{int i=2,c,s=0;c=(a[0][0]>32)?1:-1;for(;i<a.length-2;i++){s+=((a[i][1-c]>32?1:0)+(a[i][1+c]>32?2:0))*Math.pow(4,c==1?a.length-i-3:i-2);}return s;}

ungolfed:

int b(char[][] a) {
    int i=2,c,s=0;                      //i for looping, c to check if it's reversed, s is the sum, n the number
    c=(a[0][0]>32)?1:-1; //Check if must be reversed

    for(;i<a.length-2;i++){         //Looping elements

            //(Checking value for 1 + Checking value for 2) * Power, if reversed increasing, else decreasing
        s+=((a[i][1-c]>32?1:0)+(a[i][1+c]>32?2:0))*Math.pow(4,c==1?a.length-i-3:i-2);   
    }
    return s;
}

Java Gonzar

Posted 2017-12-18T18:19:48.020

Reputation: 173

1If you use an array of chars rather than Strings, you can check for | by using >32 rather than =="|" which saves 2 bytes for each usage (6 overall). You can also remove the brackets around a when declaring the lambda for another two bytes. – Luke Stevens – 2017-12-20T08:37:57.930

2

Java (OpenJDK 8), 181 160 bytes

Not too shabby for a java solution, I'm sure there are optimisations I can make, but I've already been staring at this for too long.

Cut down a few bytes by shortening the loop rather than using substring.

Golfed

c->{String r="";Integer i,j,l=c[0].length;for(i=4;i<l-4;i+=2){j=c[0][0]>32?i:l-1-i;r+=c[0][j]%8/4*(j==i?1:2)+c[2][j]%8/4*(j==i?2:1)+"";}return l.parseInt(r,4);}

Try it online!

Ungolfed

String r = "";
Integer i, j, l = c[0].length;
for(i=4; i<l-4; i+=2){
    j = c[0][0]>32 ? i : l-1-i;
    r += c[0][j]%8/4 * (j==i?1:2) + c[2][j]%8/4 * (j==i?2:1) + "";
}
return l.parseInt(r, 4);

Luke Stevens

Posted 2017-12-18T18:19:48.020

Reputation: 979

Just tried your code but with the reversed barcode of the example

"| | |"

"| | | | | | | | | | |"

and give me 1024, Ok I cant format this, but the one on the example but, try the exmple you have but without reversing it, maybe I did a bad input :) – Java Gonzar – 2017-12-19T17:08:04.613

@JavaGonzalezArribas I tried the first example (non flipped) and it seems to works fine. Could it be you put one too few spaces in the first line of the input? – Luke Stevens – 2017-12-20T08:33:03.437

That probably is the problem, great answer :) – Java Gonzar – 2017-12-20T09:34:28.840

Suggest l+~i instead of l-1-i – ceilingcat – 2019-11-08T23:57:55.703

1

Pip, 46 43 42 bytes

IsQ@@gg:RV*RVgY^gR'|1(J@UW2*y@2+@y)TM2FB:4

Takes the lines of the barcode as three command-line arguments. The first and third lines must be padded to the length of the second line with spaces. Try it online!

Explanation

First some prepwork:

IsQ@@gg:RV*RVg Y^gR'|1

                        g is cmdline args; s is space (implicit)
IsQ                     If space equals
   @@g                  the first character of the first line, then:
           RVg           Reverse the order of the rows in g
        RV*              then reverse the characters in each row
      g:                 and assign the result back to g
                 gR'|1  In g, replace pipe character with 1
                ^       Split each row into a list of characters
               Y        Yank the result into y

Now observe that if we ignore the middle row and treat | as 1 and as 0, each bar is just a 2-bit binary number:

(J@UW2*y@2+@y)TM2FB:4

       y@2             Third row
     2*                Multiply by 2, turning 1 into 2 and space into 0
           @y          First row
          +            Add
   UW                  Unweave: creates a list of two lists, the first containing all
                       even-indexed elements (in our case, the actual data), the second
                       containing all odd-indexed elements (the space separators)
  @                    First item of that list
 J                     Join the list of digits into a string
(            )TM2      Trim 2 characters from the beginning and end
                 FB:4  Convert from base 4 (: makes the precedence lower than TM)
                       Autoprint

DLosc

Posted 2017-12-18T18:19:48.020

Reputation: 21 213

1

Husk, 39 38 bytes

B4ththmȯ%4+3%5f≠192Ḟz+zṀ·*cN?↔m↔(='|←←

Takes input as a list of strings: Try it online or try the test-suite!

Explanation

B4ththm(%4+3%5)f≠192Ḟz+zṀ·*cN?↔m↔(='|←←)
                             ?   (='|←←)  -- if the very first character == '|'
                              ↔           --   reverse the lines
                                          -- else
                               m↔         --   reverse each line
                       zṀ·*cN             -- zip the lines with [1,2,3..] under..
                        Ṁ·*c              --   convert each character to its codepoint and multiply by one of [1,2,3]
                    Ḟz+                   -- reduce the lines under zipWith(+) (this sums the columns)

               f≠192                      -- only keep elements ≠ 192 (gets rid of the separating lines)

-- for the example from the challenge we would now have:
--   [652,376,468,652,376,744,376,468,744,652,376]

      m(      )                           -- map the following function
        %4+3%5                            --   λx . ((x % 5) + 3) % 4
-- this gives us: [1,0,2,1,0,3,0,2,3,1,0]
    th                                    -- remove first & last element
  th                                      -- remove first & last element
B4                                        -- interpret as base4 number

ბიმო

Posted 2017-12-18T18:19:48.020

Reputation: 15 345

1

Perl 5, 152 + 2 (-F) bytes

push@a,[@F]}{for$i(0..$#{$a[1]}){$_.=($a[0][$i]eq'|')+2*($a[2][$i]eq'|')}$_=reverse y/12/21/r if/^0/;s/^0*1000|10*$//g;s/.\K0//g;$\=$_+$\*4for/./g;say$\

Try it online!

Xcali

Posted 2017-12-18T18:19:48.020

Reputation: 7 671

0

Octave, 80 75 68 bytes

@(b)bi2de({rot90(t=~~(b-32)([3,1],[1:2:end]),2),t}{2-t(2)}(5:end-4))

Try it online!

Curiously, bi2de is default MSB on the right rather than the left, leading to some headaches while I made this... I think I should have the optimal way of flipping the array before indexing it, but there are extremely many ways to do it (either in the first indexing, or with flipud, fliplr, rot90, ' (transpose), the final indexing...). Takes a rectangular array with spaces and |s (so, trailing spaces required)

@(b)                    % Define anonymous function taking input b

t=                      % Inline-define t,
~~(b-32)                % which is the input, converted to 1's and 0's,
([3 1],[1:2:end])       % but only keep the relevant rows (1 and 3 flipped vertically) and columns (only odd) 

{rot90(t,2),t}          % Flip 180 degrees
{2-t(2)                 % depending on whether the second element of t is 1 or 0.}

(5:end-4)               % Flatten array, discarding control bits
bi2de( ... )            % and convert from binary to decimal,

Sanchises

Posted 2017-12-18T18:19:48.020

Reputation: 8 530

0

JavaScript (ES6), 184 181 bytes

I'm not an experienced golfer - I'm sure this can be improved, but I loved this challenge! I've always wondered about those marks.

Function f takes a list of strings with required trailing spaces. Newlines added to the code below for clarity (not included in byte count).

f=t=>((o=[[...t[0]],[...t[2]]])[0][0]=='|'?o:(z=a=>a.reverse())(o.map(z)))
.map((a,r)=>a.filter((_,i)=>~i%2).map(e=>(1+r)*(e=='|')).slice(2,-2))
.reduce((a,b)=>a+parseInt(b.join``,4),0)

Usage

t=['                     ','| | | | | | | | | | |','  |             |   |']
console.log(f(t)) // 4096

Ungolfed version with explanation

// take list of strings, t, as input:
f = t => ( 

  // create output variable o, discard middle row, turn other rows into lists:
  ( o = [ [...t[0]], [...t[2]] ] )

  // if top-left position isn't a pipe, rotate 180 degrees.
  // Alias [].reverse to save 3 bytes :D
  [0][0] == '|' ? o : ( z = a=> a.reverse() )( o.map(z) )

).map( (a,r) => 

  // remove even-numbered positions (non-encoding spaces):
  a.filter( (_,i) => ~i%2 )

  // convert non-pipes into zeros, and pipes into 1 or 2;
  // top row becomes the base4 1-component, bottom row is 2:
  .map( e => (1+r) * (e=='|') ).slice(2,-2)

// convert rows to base4 component strings, then decimal, then sum them:
).reduce( (a,b) => a + parseInt(b.join``,4),0)

Chris M

Posted 2017-12-18T18:19:48.020

Reputation: 191