25
\$\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\$ 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\$ Commented Jun 28, 2019 at 20:24
  • 2
    \$\begingroup\$ May we output in lowercase? \$\endgroup\$ Commented Jun 28, 2019 at 21:05
  • 2
    \$\begingroup\$ 0x22 is 34, not 30 \$\endgroup\$ 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\$ 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\$ 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\$ 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
2
\$\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\$ Commented Jul 5, 2019 at 18:18
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\$ 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\$ 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\$ 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
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\$

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.