Find the closest three-digit hex colour

23

3

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.

wrymug

Posted 2019-06-28T19:51:43.507

Reputation: 772

1"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? – Grzegorz Oledzki – 2019-06-28T20:21:04.937

3Note 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. – Neil – 2019-06-28T20:24:07.913

6Saw this in the Sandbox but forgot to mention that I don't think requiring the # adds anything to the challenge. – Shaggy – 2019-06-28T20:24:48.247

1@GrzegorzOledzki you're right, that part is confusing. I'll go ahead an remove it because I think the example in that section is enough to explain what I mean. – wrymug – 2019-06-28T20:38:54.303

2May we output in lowercase? – Arnauld – 2019-06-28T21:05:22.600

1@Arnauld yes, that's fine – wrymug – 2019-06-28T21:05:50.087

20x22 is 34, not 30 – Kruga – 2019-07-01T13:13:02.070

1@Kruga thanks, fixed – wrymug – 2019-07-01T14:11:49.837

Answers

4

JavaScript (ES6), 55 bytes

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

Try it online!

Arnauld

Posted 2019-06-28T19:51:43.507

Reputation: 111 334

Nice use of toString! I didn't realize it could take a radix param. – wrymug – 2019-07-08T14:50:13.897

8

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

Jonathan Allan

Posted 2019-06-28T19:51:43.507

Reputation: 67 804

1I thought about doing N 05AB1E answer too - unless I’ve missed something, hexadecimal conversion in Jelly takes quite a lot of bytes! – Nick Kennedy – 2019-06-29T06:39:50.237

1Yeah, no built-in for any textual base conversions in Jelly. – Jonathan Allan – 2019-06-29T12:53:18.720

1"ć decapitate" That's another way to describe it, lol. :D Nice answer though, +1 from me. – Kevin Cruijssen – 2019-07-01T07:13:53.507

6

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

Shaggy

Posted 2019-06-28T19:51:43.507

Reputation: 24 623

5

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..

640KB

Posted 2019-06-28T19:51:43.507

Reputation: 7 149

3

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.

Neil

Posted 2019-06-28T19:51:43.507

Reputation: 95 035

3

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.

640KB

Posted 2019-06-28T19:51:43.507

Reputation: 7 149

3

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

movatica

Posted 2019-06-28T19:51:43.507

Reputation: 635

2(int(x[i:i+2],16)+8)//17 saves 2 – Jonathan Allan – 2019-06-28T23:32:52.453

3

Jelly, 20 19 bytes

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

Try it online!

Nick Kennedy

Posted 2019-06-28T19:51:43.507

Reputation: 11 829

2

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!

Embodiment of Ignorance

Posted 2019-06-28T19:51:43.507

Reputation: 7 014

2

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.

Roman

Posted 2019-06-28T19:51:43.507

Reputation: 1 190

148 bytes – attinat – 2019-06-30T02:27:49.323

2

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)

Kevin Cruijssen

Posted 2019-06-28T19:51:43.507

Reputation: 67 575

2

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

DaveMongoose

Posted 2019-06-28T19:51:43.507

Reputation: 231

2

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: https://tio.run/##KypNqvz/P724NElDP6ZcT1@zWkk1QklVQ8NCW0VNLyO1QlPf0Fyz9v9/ZSOLRAMLs3/5BSWZ@XnF/3ULAA

– Jordan – 2019-07-02T16:29:18.037

1@Jordan Thanks! I didn't know about either of those so that's a real help for future golfing attempts – DaveMongoose – 2019-07-03T17:29:57.673

1

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)

Grzegorz Oledzki

Posted 2019-06-28T19:51:43.507

Reputation: 233

Does not run without import re. The import is required and thus adds to the bytecount! – movatica – 2019-06-28T21:35:53.900

1@movatica - you're right, added it – Grzegorz Oledzki – 2019-06-28T21:36:57.827

1Save 1 byte by hardcoding '#' instead of x[0]. – movatica – 2019-06-28T21:37:18.443

1You can skip the list comprehension inside ''.join(...), as it also handles a generator expression. Just remove the [] and save 2 more bytes :) – movatica – 2019-06-28T21:42:54.433

Nice work so far! 80 bytes: lambda x:'#'+''.join(hex(int(int(x[i:i+2],16)/17.+.5))[2:]for i in range(1,6,2)) – movatica – 2019-06-28T21:46:25.587

1Thanks! range(1,6,2) is even better with [1,3,5] – Grzegorz Oledzki – 2019-06-28T21:47:39.727

You're right! How could I miss that :) – movatica – 2019-06-28T21:49:49.483

Also I bet/hope there's some trick to improve the hex(...)[2:] part. – Grzegorz Oledzki – 2019-06-28T21:51:59.623

1Jonathan 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 – movatica – 2019-06-29T07:06:15.057

1On top of @movatica's latest comment, you can additionally golf 4 more bytes: lambda x:'#'+`[hex((int(x[i:i+2],16)+8)/17)for i in 1,3,5]`[4::7] – Erik the Outgolfer – 2019-06-30T21:37:48.563

1

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.

Xcali

Posted 2019-06-28T19:51:43.507

Reputation: 7 671

1

Python 3, 67 bytes

f=lambda x:(f(x[:-2])if x[3:]else"#")+f'{(int(x[-2:],16)+8)//17:X}'

GSy

Posted 2019-06-28T19:51:43.507

Reputation: 11

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.

– mbomb007 – 2019-07-05T18:18:55.903

0

R, 57 bytes

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

Try it online!

Nick Kennedy

Posted 2019-06-28T19:51:43.507

Reputation: 11 829

0

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]]]

Galen Ivanov

Posted 2019-06-28T19:51:43.507

Reputation: 13 815

0

Perl 6, 35 bytes

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

Try it online!

nwellnhof

Posted 2019-06-28T19:51:43.507

Reputation: 10 037

0

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

Neil

Posted 2019-06-28T19:51:43.507

Reputation: 95 035

0

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.

Erik the Outgolfer

Posted 2019-06-28T19:51:43.507

Reputation: 38 134

0

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

reffu

Posted 2019-06-28T19:51:43.507

Reputation: 1 361

0

Stax, 13 bytes

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

Run and debug it

recursive

Posted 2019-06-28T19:51:43.507

Reputation: 8 616

0

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

streetster

Posted 2019-06-28T19:51:43.507

Reputation: 3 635