16
\$\begingroup\$

Write a function or program that encodes a string into a Code 39 format barcode, where each character is encoded as five bars separated by four gaps. Either two of the bars and one of the gaps are wide and others are narrow (10*4 codes), or three of the gaps are wide and none of the bars are (4 codes). This gives 44 different codes, from which one is a reserved code that is used to denote the start and end of the encoded string.

The challenge

The input is a string containing only characters from the set

1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ-. +/$%

The output is the string encoded as a barcode string. The narrow gap and intercharacter gaps are a single space and a wide gap is three spaces. The narrow bar is the UTF-8 byte sequence for the Unicode character "Full block", █, i.e. 0xE2 0x96 0x88 and wide bar is three such byte sequences / characters (███). The full list of codes is:

      Spaces
      0100 0010 0001 1000 1011 1101 1110 0111
Bars
00000                       +    /    $   %
10001   1    A    K    U
01001   2    B    L    V
11000   3    C    M    W
00101   4    D    N    X
10100   5    E    O    Y
01100   6    F    P    Z
00011   7    G    Q    -
10010   8    H    R    .
01010   9    I    S  space          1=wide
00110   0    J    T  start/end      0=narrow

The bars and spaces are interleaved, starting at a bar, so for example Q is

bar   0 0 0  1     1 
code  █ █ █ ███   ███
space  0 0 0    1

After encoding all the characters, the string is delimited at both ends with █ █ ███ ███ █. The intercharacter gap, a single space, is inserted between every letter. Your solution may output trailing spaces and a trailing newline (in that order).

Examples

""     → "█   █ ███ ███ █ █   █ ███ ███ █"

"A"    → "█   █ ███ ███ █ ███ █ █   █ ███ █   █ ███ ███ █"

"C++"  → "█   █ ███ ███ █ ███ ███ █   █ █ █   █ █   █   █ █   █ █   █   █ █   █ ███ ███ █"

"GOLF" → "█   █ ███ ███ █ █ █ █   ███ ███ ███ █ ███ █   █ █ ███ █ █   ███ █ ███ ███   █ █ █   █ ███ ███ █"

Standard input/output formats are allowed and standard loopholes are disallowed. This is , so the shortest code measured in bytes wins!

\$\endgroup\$
  • 1
    \$\begingroup\$ May we use a printable ASCII character (you can choose which one to allow) instead of █? \$\endgroup\$ – Erik the Outgolfer May 2 '18 at 12:33
  • \$\begingroup\$ For language like BrainFuck, what count as "one usage"? \$\endgroup\$ – l4m2 May 2 '18 at 12:53
  • 1
    \$\begingroup\$ @Angs I meant on the output basically, not in the code. It's not very fair to give penalties to uses of the # character, since, for example, "#" isn't the only reason it could be used in a language. \$\endgroup\$ – Erik the Outgolfer May 2 '18 at 12:56
  • \$\begingroup\$ @EriktheOutgolfer What I meant was in string literals and the like, but considering l4m2's point it might be best to disallow it. After all, are there any languages that can't output three bytes? \$\endgroup\$ – Angs May 2 '18 at 13:00
  • 2
    \$\begingroup\$ If it helps anyone, here is a pastebin with all 9-bit binary Strings and decimal conversion for each character. \$\endgroup\$ – Kevin Cruijssen May 3 '18 at 9:14
7
\$\begingroup\$

JavaScript (ES6), 225 212 bytes

Saved 4 bytes thanks to @l4m2

s=>`#${s}#`.replace(/./g,c=>'0202020202'.replace(/./g,(j,k)=>[C='█',C+C+C,' ','   '][(i="%+/$U1AKV2BLW3CMX4DNY5EOZ6FP-7GQ.8HR 9IS#0JT".indexOf(c),[170,257,260,5,272,17,20,320,65,68,80][i>>2]^2<<i%4*2)>>k&1|j]))

Try it online!

How?

The table can be rearranged such that the 9-bit binary mask of the character is quickly deduced from its row and column using the following formula:

n = m XOR (2 << k)

with:

  m  | k: 0   2   4   6
-----+------------------      Examples:
 170 |    %   +   /   $
 257 |    U   1   A   K         'G' = 320 XOR (2 << 4) = 320 XOR 32 = 352
 260 |    V   2   B   L
   5 |    W   3   C   M         '+' = 170 XOR (2 << 2) = 170 XOR 8  = 162
 272 |    X   4   D   N
  17 |    Y   5   E   O
  20 |    Z   6   F   P
 320 |    -   7   G   Q
  65 |    .   8   H   R
  68 |   spc  9   I   S
  80 |    #   0   J   T
\$\endgroup\$
  • \$\begingroup\$ s=>`#${s}#`.replace(/./g,c=>'0202020202'.replace(/./g,(j,k)=>[C='#',C+C+C,' ',' '][(i="%+/$U1AKV2BLW3CMX4DNY5EOZ6FP-7GQ.8HR 9IS#0JT".indexOf(c),[,257,260,5,272,17,20,320,65,68,80][i>>2]|(2<<i%4*2^(i<4)*170))>>k&1|j]))(221) \$\endgroup\$ – l4m2 May 2 '18 at 13:07
  • \$\begingroup\$ @l4m2 That doesn't seem to be valid. However, I've managed to find a fix for 221 bytes. \$\endgroup\$ – Erik the Outgolfer May 2 '18 at 13:08
  • \$\begingroup\$ @EriktheOutgolfer I think that's basically what l4m2 submitted with a full block instead of '#'. Or am I missing something? \$\endgroup\$ – Arnauld May 2 '18 at 13:17
  • \$\begingroup\$ @Arnauld Their original doesn't space properly. \$\endgroup\$ – Erik the Outgolfer May 2 '18 at 13:18
3
\$\begingroup\$

Red, 452 445 bytes

func[s][q: func[x y z][append/dup x y z]append insert s"*""*"b:
func[n][v: copy[]until[append v n % 2 * 2 + 1
1 > n: n / 2]q v 1 5 - length? v
reverse v]a: q":1234567890:ABCDEFGHIJ:KLMNOPQRST:UVWXYZ-. *"" "44
a/45: #"+"a/56: #"/"a/67: #"$"a/78: #"%"foreach t s[i: index? find a t
k: b pick[0 17 9 24 5 20 12 3 18 10 6]either 0 = j: i % 11[11][j]m:
b pick[8 4 2 16 22 26 28 14]i - 1 / 11 + 1 z: copy""repeat n 5[q z"█"k/(n)
q z" "m/(n)]prin z]]

Try it online!

I'll try to golf it further, but I don't expect much from this naive solution.

\$\endgroup\$
2
\$\begingroup\$

Java 10, 455 bytes

s->{String r="",R=r;for(var c:("~"+s+"~").split(""))r+=r.format("%9s",Long.toString(new int[]{148,289,97,352,49,304,112,37,292,100,52,265,73,328,25,280,88,13,268,76,28,259,67,322,19,274,82,7,262,70,22,385,193,448,145,400,208,133,388,196,138,162,168,42}["~1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ-. +/$%".indexOf(c)],2)).replace(' ','0');for(int l=r.length(),i=0,c;i<l;R+=""+(i%9%2<1?c<49?"█":"███":c<49?" ":"   ")+(i%9>7&++i<l?" ":""))c=r.charAt(i);return R;}

Try it online.

Explanation:

s->{                     // Method with String as both parameter and return-type
  String r="",           //  Temp-String, staring empty
         R=r;            //  Result-String, starting empty as well
  for(var c:("~"+s+"~")  //  Prepend and append "~" to the input
            .split(""))  //  And loop over each character
    r+=r.format("%9s",Long.toString( 
                         //   Convert the following integer to a 9-bit binary String:
       new int[]{148,289,97,352,49,304,112,37,292,100,52,
                 265,73,328,25,280,88,13,268,76,28,259,67,322,
                 19,274,82,7,262,70,22,385,193,448,145,400,208,
                 133,388,196,138,162,168,42}
                         //    Array containing all decimal values of the binary Strings
       ["~1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ-. +/$%".indexOf(c)]
                         //    with the index based on the current character
   ,2)).replace(' ','0');
                         //   (also part of the integer to 9-bit binary String conversion)
  for(int l=r.length(),i=0,c;i<l;
                         //  Loop `i` over the temp-String
      R+=                //    After every iteration: append the following to the result:
         ""+(i%9%2<1?    //     If `i` modulo-9 modulo-2 is 0:
              c<49?      //      And `c` is '0':
               "█"       //       Append a single block
              :          //      Else:
               "███"     //       Append three blocks
             :c<49?      //     Else-if `c` is '0':
              " "        //      Append a single space
             :           //     Else:
              "   ")     //      Append three spaces
         +(i%9>7         //     If `i` modulo-9 is 8,
           &++i<l?       //     and this is not the very last character
            " "          //      Append a space delimiter
           :             //     Else:
            ""))         //      Append nothing more
    c=r.charAt(i);       //   Set `c` to the current character in `r`
  return R;}             //  Return the result
\$\endgroup\$
2
\$\begingroup\$

C (gcc), 311, 303 bytes

(*P)()=printf;i;j;w;m[]={170,257,260,5,272,17,20,320,65,68,80};char*b=" \0\xE2\x96\x88",*t="%+/$U1AKV2BLW3CMX4DNY5EOZ6FP-7GQ.8HR 9IS#0JT",*c;p(x){j=256;w=2;while(j){P(b+w);if(x&1){P(b+w);P(b+w);}x/=2;j>>=1;w=2*!w;}P(" ");}f(char*_){p(82);for(c=_;*c;c++){i=strchr(t,*c)-t;p(m[i>>2]^(2<<(i&3)*2));}p(82);}

Try it online!

-8 thanks to ceilingcat

Uses encoding strategy from Arnauld's answer. TIO link includes -w switch and boilerplate to remove warnings, these are unnecessary and thus not included in the score.

Aside from the encoding scheme as explained by Arnauld, the other trick here is to maintain the w variable as toggling between 2 and 0 (w=2*!w). This allows me to choose between the first and second strings in b. The first is a space, the second is the filled rectangle.

\$\endgroup\$
  • \$\begingroup\$ clever, you should post it :) \$\endgroup\$ – LambdaBeta May 7 '18 at 20:42
2
\$\begingroup\$

C (gcc), 241 239 227 213 207 bytes

#define P printf(i%2?" ":"█")
i;p(x){for(i=9;i--;x/=2)P,x&1&&P+P;P;}f(char*_){p(82);for(char*t="%+/$U1AKV2BLW3CMX4DNY5EOZ6FP-7GQ.8HR 9IS#0JT";*_;p(L"ªāĄ\5Đ\21\24ŀADP"[i/4]^2<<i%4*2))i=index(t,*_++)-t;p(82);}

Try it online!

Based on @LambdaBeta's implementation.

Slightly less golfed:

#define P printf(i%2?" ":"█")
i;
p(x){
 for(i=9;i--;x/=2)
  P,
  x&1&&
   P+
   P;
 P;
}
f(char*_){
 p(82);
 for(char*t="%+/$U1AKV2BLW3CMX4DNY5EOZ6FP-7GQ.8HR 9IS#0JT";*_;p(L"ªāĄ\5Đ\21\24ŀADP"[i/4]^2<<i%4*2))
  i=index(t,*_++)-t;
 p(82);
}
\$\endgroup\$
1
\$\begingroup\$

Charcoal, 90 bytes

⪫EE⪫**S⌕⁺⁺⭆…αχ﹪⊕μχα-. *+/$%ι⪫E⁵⁺⎇μ× ∨⁼›ι³⁹⁼²﹪⁻÷ι∨›ι³⁹χμ⁴¦³ω×█∨›ι³⁹∨§↨℅§.6':+3<-59ι²⊕μ³ω 

Try it online! Note: Trailing space. Link is to verbose version of code. Explanation:

⪫EE⪫**S...⪫E⁵⁺...ω 

Wrap the input string in *s and then map over it twice, finally joining the result with spaces. For the second map, there is then an additional map over the implicit range 0..4, where two substrings are concatenated, and those results are then joined with the predefined empty string constant.

⌕⁺⁺⭆…αχ﹪⊕μχα-. *+/$%ι

For the first inner map, create a string formed by taking the incremented digits, the uppercase alphabet, and the symbols -. *+/$%, and look up the position of the mapped input character. For example, C++ would map to [12, 40, 40].

⎇μ× ∨⁼›ι³⁹⁼²﹪⁻÷ι∨›ι³⁹χμ⁴¦³ω

The first substring represents the spaces before the bars. There is nothing before the first bar, but the other bars depend on the position of the mapped input character: if it is over 39, then only one place has a single space, while if it is under 40, then only one place has three spaces, and the position is also converted to a column by dividing it by 10. If the column and the loop index differ by 2 (modulo 4) then that is the odd place out.

×█∨›ι³⁹∨§↨℅§.6':+3<-59ι²⊕μ³

The second substring represents the bars. If the position is over 39 then there is always one bar otherwise the position is looked up in an array of bits mapped to characters. For instance, if the position is 12, then that is circularly indexed to the character ', which is 100111 in binary, indicating wide bars in columns 1 and 2. (The leading 1 is ignored, it simply ensures a consistent bit count.)

\$\endgroup\$
1
\$\begingroup\$

Perl 5, 244 bytes

$l="{ ,   ,...,.........}"x9;@h{$",qw($ % + - . /),0..9,'',A..Z}=(<"$l">)[q{..=9...Z.>>...J=9..j.%r1...=).[.>=-..Ux?V.^=8.>.6o+ax_.=(..B=,..Yx6..b=(..#r'p.^...G.<=,..Sx5B.\=(.V.It..z:..-z.=<6.}=~s/./ord$&/gre=~/.{6}/g];s/^|./$h{$&} /g;$\=$h{''}

Try it online!

Contains many unprintables and high-byte characters, TIO link provides an xxd representation. I'd hoped this would have ended up smaller, and I might still be able to pack the data in a more efficient way so I'll see how I go. This builds up all permutations of " "," ","█","███" and then maps the indicies of the list to the corresponding characters.

\$\endgroup\$
1
\$\begingroup\$

Haskell, 275 270 bytes

z=0:z
x!0=[]
x!n=mod n x:x!div n x
e c|Just a<-lookup c.zip"W3YZ56C$EF. 89*HM/ORU1QGNTDJ7%40LSBIP2-+XVKA".((++)<*>(reverse<$>))$take 9.(++z).(2!)<$>scanl(+)7(67!0x117CDBC49F9EEEF11C3A659CACB31236)=zipWith(\l c->c<$[1..2*l+1])(a++[0])(cycle"█ ")>>=id
f s='*':s++"*">>=e

Try it online!

The operator x!n that calculates the x-base digits of n, is used twice to decompress the codes. The codes are compressed first as binary strings with wide = 1 and narrow = 0, with no regard to color, e.g. R↔10000110↔262. These numbers are then sorted and differenced to get numbers in the range [3,66], which are compressed with reverse of the binary digit algorithm as 0x117CDBC49F9EEEF11C3A659CACB31236. This contains only half of the codes, the rest are reverses of these.

Ungolfed:

z=0:z                       -- infinite zeroes for padding
x!0=[]                      -- digits of n in base x, recursion base case
x!n=mod n x:x!div n x       -- digits of n in base x
e c | Just a                -- read upwards from (*)
  <-lookup c                -- lookup code, given letter
  . zip"W3YZ56C$EF. 89*HM/ORU1QGNTDJ7%40LSBIP2-+XVKA" 
                            -- combine codes with correct letters
  . ((++)<*>(reverse<$>))   -- concatenate list and list with reverse codes
  $ take 9                  -- take nine
  . (++z)                   -- pad with infinite zeroes on right
  . (2!)                    -- convert to binary digits – [[1,1,1],[1,0,1,1]…]
  <$> scanl (+) 7           -- cumulative sum starting from 7 – [7,13,19,22,25…]
        (67!0x117CDBC49F9EEEF11C3A659CACB31236)       
                            -- (*) digits in base 67 – [6,6,3,3,3,9,5,7…]
  = zipWith
      (\l c->c<$[1..2*l+1]) -- combine width and color, length is 2*l+1
      (a++[0])              -- list of widths as 0/1, 0 added for interchar gap
      (cycle"█ ")           -- infinite list of bar colors, leftovers are unused
  >>=id                     -- concatenate
f s='*':s++"*">>=e          -- surround string with delimiters, map e and concat
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.