24
\$\begingroup\$

In CSS, colours can be specified by a "hex triplet" - a three byte (six digit) hexadecimal number where each byte represents the red, green, or blue components of the colour. For instance, #FF0000 is completely red, and is equivalent to rgb(255, 0, 0).

Colours can also be represented by the shorthand notation which uses three hexadecimal digits. The shorthand expands to the six digit form by duplicating each digit. For instance, #ABC becomes #AABBCC.

Since there are fewer digits in the hex shorthand, fewer colours can be represented.

The challenge

Write a program or function that takes a six digit hexadecimal colour code and outputs the closest three-digit colour code.

Here's an example:

  • Input hex code: #28a086
  • Red component
    • 0x28 = 40 (decimal)
    • 0x22 = 34
    • 0x33 = 51
    • 0x22 is closer, so the first digit of the shortened colour code is 2
  • Green component
    • 0xa0 = 160
    • 0x99 = 153
    • 0xaa = 170
    • 0x99 is closer, so the second digit is 9
  • Blue component
    • 0x86 = 134
    • 0x77 = 119
    • 0x88 = 136
    • 0x88 is closer, so the third digit is 8
  • The shortened colour code is #298 (which expands to #229988)

Your program or function must accept as input a six digit hexadecimal colour code prepended with # and output a three digit colour code prepended with #.

Examples

  • #FF0000 → #F00
  • #00FF00 → #0F0
  • #D913C4 → #D1C
  • #C0DD39 → #BD3
  • #28A086 → #298
  • #C0CF6F → #BC7

Scoring

This is a code-golf challenge, so shortest answer in your language wins! Standard rules apply.

\$\endgroup\$
8
  • 1
    \$\begingroup\$ "adding together the difference between each component of the full colour code and the corresponding component of the shorthand colour code" - this part is confusing. There's no adding anywhere, right? \$\endgroup\$ Commented Jun 28, 2019 at 20:21
  • 3
    \$\begingroup\$ Note that if you simply drop alternate digits then each short colour represents an equal number of full colours, so that could be considered to make a better representation than nearest colour. \$\endgroup\$
    – Neil
    Commented Jun 28, 2019 at 20:24
  • 6
    \$\begingroup\$ Saw this in the Sandbox but forgot to mention that I don't think requiring the # adds anything to the challenge. \$\endgroup\$
    – Shaggy
    Commented Jun 28, 2019 at 20:24
  • 2
    \$\begingroup\$ May we output in lowercase? \$\endgroup\$
    – Arnauld
    Commented Jun 28, 2019 at 21:05
  • 2
    \$\begingroup\$ 0x22 is 34, not 30 \$\endgroup\$
    – Kruga
    Commented Jul 1, 2019 at 13:13

23 Answers 23

8
\$\begingroup\$

05AB1E, 13 bytes

ćs2ôH8+17÷hJ«

Try it online!

How?

ćs2ôH8+17÷hJ« | string, S   e.g. stack: "#B23F08"
ć             | decapitate              "B23F08", "#"
 s            | swap                    "#", "B23F08"
  2           | two                     "#", "B23F08", 2
   ô          | chuncks                 "#", ["B2", "3F", "08"]
    H         | from hexadecimal        "#", [178, 63, 8]
     8        | eight                   "#", [178, 63, 8], 8
      +       | add                     "#", [186, 71, 16]
       17     | seventeen               "#", [186, 71, 16], 17
         ÷    | integer divide          "#", [10, 4, 0]
          h   | to hexadecimal          "#", ["A", "4", "0"]
           J  | join                    "#", "A40"
            « | concatenate             "#A40"
              | print top of stack
\$\endgroup\$
3
  • 1
    \$\begingroup\$ I thought about doing N 05AB1E answer too - unless I’ve missed something, hexadecimal conversion in Jelly takes quite a lot of bytes! \$\endgroup\$ Commented Jun 29, 2019 at 6:39
  • 1
    \$\begingroup\$ Yeah, no built-in for any textual base conversions in Jelly. \$\endgroup\$ Commented Jun 29, 2019 at 12:53
  • 1
    \$\begingroup\$ "ć decapitate" That's another way to describe it, lol. :D Nice answer though, +1 from me. \$\endgroup\$ Commented Jul 1, 2019 at 7:13
6
\$\begingroup\$

Japt, 16 bytes

r"%w"²_n16_r17Ãg

Try it or run all test cases

r"%w"²_n16_r17Ãg     :Implicit input of string
r                    :Replace
 "%w"                :RegEx /\w/g
     ²               :Duplicate, giving /\w\w/g
      _              :Pass each match through a function
       n16           :  Convert to decimal
          _          :  Pass through the following function, and convert back to hex
           r17       :    Round to the nearest multiple of 17
              Ã      :  End function
               g     :  Get first character
\$\endgroup\$
5
\$\begingroup\$

8088 Assembly, IBM PC DOS, 59 58 bytes

Unassembled listing:

BE 0082     MOV  SI, 82H    ; SI to begining of input string 
AC          LODSB           ; load first '#' char into AL 
B4 0E       MOV  AH, 0EH    ; BIOS display char function  
CD 10       INT  10H        ; call BIOS 
B3 11       MOV  BL, 17     ; set up for divide by 17 
B9 0304     MOV  CX, 0304H  ; hex byte loop counter (CH=3), shift counter (CL=4) 
        LOOP_BYTE: 
AD          LODSW           ; load next two ASCII hex chars into AX 
B7 02       MOV  BH, 2      ; hex chars loop counter
        LOOP_ALPHA:
2C 30       SUB  AL, '0'    ; convert from ASCII 
3C 0A       CMP  AL, 10     ; is digit > 10 (A-F)? 
7C 02       JL   NOT_ALPHA  ; if not, jump to next char
2C 07       SUB  AL, 7      ; ASCII adjust alpha char to binary 
        NOT_ALPHA: 
86 E0       XCHG AH, AL     ; swap first and second chars 
FE CF       DEC  BH         ; decrement loop counter
75 F2       JNZ  LOOP_ALPHA ; loop to next hex char
D2 E0       SHL  AL, CL     ; shift low nibble to high nibble 
02 C4       ADD  AL, AH     ; add first and second nibbles
32 E4       XOR  AH, AH     ; clear AH for add/division
05 0008     ADD  AX, 8      ; add 0.5 (8/16) to round (with overflow) 
F6 F3       DIV  BL         ; divide by 17 
3C 0A       CMP  AL, 10     ; is digit > 10? 
7C 02       JL   DISP_CHAR  ; if not, jump to display digit 
04 07       ADD  AL, 7      ; binary adjust alpha char to ASCII 
        DISP_CHAR: 
04 30       ADD  AL, '0'    ; convert to ASCII 
B4 0E       MOV  AH, 0EH    ; BIOS display char function  
CD 10       INT  10H        ; call BIOS 
FE CD       DEC  CH         ; decrement loop counter 
75 D4       JNZ  LOOP_BYTE  ; loop to next hex byte
C3          RET             ; return to DOS 

Standalone PC DOS executable. Input is via command line, output is to console.

Most of the code length is handling the conversion of required hex string I/O into bytes, since DOS/x86 machine code has no built-ins for that.

I/O:

enter image description here

Download and test HEXCLR.COM, or xxd hexdump:

0000000: be82 00ac b40e cd10 b311 b904 03ad b702  ................
0000010: 2c30 3c0a 7c02 2c07 86e0 fecf 75f2 d2e0  ,0<.|.,.....u...
0000020: 02c4 32e4 0508 00f6 f33c 0a7c 0204 0704  ..2......<.|....
0000030: 30b4 0ecd 10fe cd75 d4c3                 0......u..
\$\endgroup\$
4
\$\begingroup\$

JavaScript (ES6), 55 bytes

s=>s.replace(/\w./g,x=>(('0x'+x)/17+.5|0).toString(16))

Try it online!

\$\endgroup\$
1
  • \$\begingroup\$ Nice use of toString! I didn't realize it could take a radix param. \$\endgroup\$
    – wrymug
    Commented Jul 8, 2019 at 14:50
3
\$\begingroup\$

Retina 0.8.2, 88 bytes

(\w)(.)
$1,$2;
[A-F]
1$&
T`L`d
\d+
$*
+`1,
,16$*
,
8$*
(1{17})*1*;
$#1;
T`d`L`1\d
B\B|;

Try it online! Link includes test cases. Explanation:

(\w)(.)
$1,$2;

Pair up the hex digits.

[A-F]
1$&
T`L`d

Convert each digit separately to decimal.

\d+
$*

Convert each decimal digit to unary.

+`1,
,16$*

Finish the hexadecimal conversion of the pair of digits.

,
8$*
(1{17})*1*;
$#1;

Add 8 and divide by 17.

T`d`L`1\d
B\B|;

Convert back to hexadecimal.

\$\endgroup\$
3
\$\begingroup\$

PHP, 75 67 bytes

#<?php for($m=3;$m;)echo dechex((hexdec($argn)>>--$m*8&255)/17+.5);

Try it online! or verify all test cases.

\$\endgroup\$
3
\$\begingroup\$

Python 3, 72 70 68 bytes

lambda x:'#'+''.join(f"{(int(x[i:i+2],16)+8)//17:X}"for i in(1,3,5))

Try it online!

This is a port of Grzegorz Oledzkis original answer, which I helped him golfing down.

Two features of Python 3 help us save bytes:

  • Floating point division by default
  • Format string literals

-2 bytes thanx to Jonathan Allan

\$\endgroup\$
1
  • 2
    \$\begingroup\$ (int(x[i:i+2],16)+8)//17 saves 2 \$\endgroup\$ Commented Jun 28, 2019 at 23:32
3
\$\begingroup\$

Jelly, 20 19 bytes

ḊØHiⱮs2ḅ⁴+8:17ịØHṭḢ

Try it online!

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

C# (Visual C# Interactive Compiler), 81 bytes

x=>"".Aggregate("#",(a,b)=>a+$"{(Convert.ToInt32(x[b]+""+x[b+1],16)+8)/17:X}")

Try it online!

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

Wolfram Language (Mathematica), 63 48 bytes

"#"<>Round[15List@@RGBColor@#]~IntegerString~16&

Try it online!

-15 bytes thanks to attinat! Replacing StringJoin with <> and compressing the syntax.

  1. RGBColor@# converts the input string to a color of the form RGBColor[r, g, b] with three floating-point arguments in the range 0..1.

  2. Round[15 List @@ %] multiplies the list of three arguments by 15 and rounds them to the nearest integer. We now have a list of three integer values corresponding to the three desired hexadecimal digits.

  3. %~IntegerString~16 converts this list of three integers to a list of three hexadecimal strings of one character each.

  4. "#"<>% prepends a # character and joins all these characters together.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ 48 bytes \$\endgroup\$
    – att
    Commented Jun 30, 2019 at 2:27
2
\$\begingroup\$

MathGolf, 19 12 bytes

╞2/¢8+F/¢'#▌

Output as character-list. If this is not allowed, an additional trailing y has to be added to join the character-list to a string.

-7 bytes thanks to @maxb, since I looked past a builtin (2ô_2<\1>] to 2/).

Try it online.

Explanation:

╞              # Remove the first character from the (implicit) input-string
 2/            # Split the string into parts of size 2
   ¢           # Convert each part from hexadecimal to integer
    8+         # Add 8 to each integer
      F/       # Integer-divide each integer by 17
        ¢      # Then convert back from integer to hexadecimal
         '#▌  '# Prepend '#' in front of the list
               # (which is output implicitly as result)
\$\endgroup\$
2
\$\begingroup\$

Ruby (2.5.3), 45, 44, 42 bytes

->a{a.gsub(/\w./){|b|"%X"%((8+b.hex)/17)}}

EDIT: saved one byte because we don't need a character group for the second character in the regex (inspired by Neil's answer)

EDIT 2: saved 2 bytes because the dash rocket lambda syntax doesn't need brackets around the argument

\$\endgroup\$
2
  • 2
    \$\begingroup\$ You can save 7 bytes by taking input on stdin and using the -p flag and another 2 by using $& instead of an argument inside the block: tio.run/##KypNqvz/… \$\endgroup\$
    – Jordan
    Commented Jul 2, 2019 at 16:29
  • 1
    \$\begingroup\$ @Jordan Thanks! I didn't know about either of those so that's a real help for future golfing attempts \$\endgroup\$ Commented Jul 3, 2019 at 17:29
1
\$\begingroup\$

Python 2 (109 101 97 85 83 74 bytes)

lambda x:'#'+''.join(hex(int(int(x[i:i+2],16)/17.+.5))[2:]for i in[1,3,5])

The "nearest distance" is handled by division by 17 and rounding.

Improvements:

-8 bytes by using the int(...+.5) trick instead of int(round(...))

-4 bytes by using list comprehension instead of map()

-1 byte by hardcoding # in the output (thanks @movatica)

-10 bytes by not using re.findall("..",...) in favor of explicit String splicing

-2 bytes by not using list comprehension, but an inline generator expression in join (thanks @movatica)

-1 byte by not splicing the :7 ending for blue part

-9 bytes by better iteration over colors - i.e. iterating over indices, not actual characters (thanks @movatica)

\$\endgroup\$
10
  • 1
    \$\begingroup\$ @movatica - you're right, added it \$\endgroup\$ Commented Jun 28, 2019 at 21:36
  • 1
    \$\begingroup\$ Save 1 byte by hardcoding '#' instead of x[0]. \$\endgroup\$
    – movatica
    Commented Jun 28, 2019 at 21:37
  • 1
    \$\begingroup\$ You can skip the list comprehension inside ''.join(...), as it also handles a generator expression. Just remove the [] and save 2 more bytes :) \$\endgroup\$
    – movatica
    Commented Jun 28, 2019 at 21:42
  • 1
    \$\begingroup\$ Thanks! range(1,6,2) is even better with [1,3,5] \$\endgroup\$ Commented Jun 28, 2019 at 21:47
  • 1
    \$\begingroup\$ Jonathan Allen proposed a different trick for rounding in mz Pzthon3 version. It applies here as well: lambda x:'#'+''.join(hex((int(x[i:i+2],16)+8)/17)[2:]for i in[1,3,5]) -> 69 bytes \$\endgroup\$
    – movatica
    Commented Jun 29, 2019 at 7:06
1
\$\begingroup\$

Perl 5 -p, 35 34 bytes

@nwellnhof saved a byte

s|\w.|sprintf'%X',.5+(hex$&)/17|ge

Try it online!

Reads from STDIN, replaces each pair of items that is not # with the appropriate single character using the division by 17 method for finding the nearest, then implicitly outputs (-p) the result.

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

Python 3, 67 bytes

f=lambda x:(f(x[:-2])if x[3:]else"#")+f'{(int(x[-2:],16)+8)//17:X}'
\$\endgroup\$
1
  • \$\begingroup\$ Welcome. Consider adding a description, explanation, or link to an online interpreter, such as TIO where we can run your code. Code-only answers tend to get automatically flagged as low-quality. See other existing answers for examples. \$\endgroup\$
    – mbomb007
    Commented Jul 5, 2019 at 18:18
0
\$\begingroup\$

Red, 103 bytes

func[c][r: to 1 c to #1 rejoin reverse collect[loop 3[keep to-hex/size r % 256 + 8 / 17 1 r: r / 256]]]

Try it online!

It turned out that the current Linux version of Red doesn't have an implementation of the hex-to-rgb function, that's why I make the base conversion "manually" :)

This works fine in the Red GUI console on Windows:

Red, 94 bytes

f: func[c][r: hex-to-rgb c to #1 rejoin collect[repeat n 3[keep to-hex/size r/:n + 8 / 17 1]]]
\$\endgroup\$
0
\$\begingroup\$

Perl 6, 35 bytes

{S:g[\w.]=fmt :16(~$/)/17+.5: '%X'}

Try it online!

\$\endgroup\$
0
\$\begingroup\$

Charcoal, 22 bytes

#F⪪⮌…⮌S⁶¦²⍘÷⁺⁸⍘ι¹⁶¦¹⁷φ

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

#                       Literal `#`
      S                 Input string
     ⮌                  Reversed
    …  ⁶                Truncated to length 6
   ⮌                    Reversed
  ⪪      ²              Split into pairs of characters
 F                      Loop over each pair
               ι        Current pair
              ⍘ ¹⁶      Convert from base 16
            ⁺⁸          Add 8
           ÷       ¹⁷   Integer divide by 17
          ⍘          φ  Convert to large base
                        Implicitly print
\$\endgroup\$
0
\$\begingroup\$

R, 57 bytes

cat("#",sprintf("%x",(col2rgb(scan(,""))+8)%/%17),sep="")

Try it online!

\$\endgroup\$
0
\$\begingroup\$

Pyth, 20 bytes

+\#sm.H/+8id16 17c3t

Try it here.

NOTE: In case the link above raises an ImportError, go here instead; there is currently a bug in the "official" page, and this is a temporary solution by Maltysen. This link may stop working after the official one is fixed.

\$\endgroup\$
0
\$\begingroup\$

Forth (gforth), 87 bytes

: f d>s 1- hex ." #"3. do 2 + dup 2 s>number d>s 17 /mod swap 8 > - 1 .r loop decimal ;

Try it online!

Explanation

  1. Ignore/truncate first character of input (#)
  2. Set interpreter to hexadecimal mode
  3. Output #
  4. Loop 3 times, in each loop:
    1. Add 2 to string starting address
    2. Convert next 2 characters in string to a hexadecimal number
    3. Use division and module by 17 (0x11) to get closest value for shortened component
    4. Output with no preceding space
  5. Set interpreter back to Decimal mode

Code Explanation

: f                    \ start a new word definition
  d>s                  \ convert double-length int to single-length (cheaper drop)
  1- hex               \ subtract 1 from string address, set current base to 10
  ." #"                \ output #
  3. do                \ start a loop from 0 to 2 (inclusive)
    2 + dup            \ add 2 to string starting address and duplicate
    2 s>number         \ parse the next 2 characters to a hexadecimal value
    d>s                \ convert result to single-length value
    17 / mod           \ get the quotient and remainder of dividing by 17
    swap               \ move the remainder to the top of the stack
    8 > -              \ if remainder is greater than 8, add 1 to quotient
    1 .r               \ output result (as hexadecimal) with no space
  loop                 \ end the loop
  decimal              \ set interpreter back to base 10 (decimal)
;                      \ end the word definition
\$\endgroup\$
0
\$\begingroup\$

Stax, 13 bytes

Ç┤!usτ╓♪╩⌂^÷◙

Run and debug it

\$\endgroup\$
0
\$\begingroup\$

K4, 39 bytes

Solution:

"#",{x@_1%17%8+16/:x?y}[.Q.nA]@/:3 2#1_

Explanation:

Uses the same strategy as a lot of these answers (i.e. add 8, divide by 17):

"#",{x@_1%17%8+16/:x?y}[.Q.nA]@/:3 2#1_ / the solution
                                     1_ / drop first character
                                 3 2#   / reshape as 3x2 (e.g. "FF", "00", "00")
                              @/:       / apply each-right to left lambda
    {                 }[     ]          / lambda with first argument populated
                        .Q.nA           / "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
                   x?y                  / get index of hex character, e.g. "AA" => 10 10
               16/:                     / convert from base-16
             8+                         / add 8
          17%                           / 17 divided by...
        1%                              / 1 divided by...
       _                                / floor
     x@                                 / index into .Q.nA to get hex character
"#",                                    / prepend "#"

Extra:

  • "#",{x@*16\:a?&/a:abs(17*!16)-16/:x?y}[.Q.nA]@/:3 2#1_ - my original idea for 54 bytes
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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