# Better Hex Color Codes for Your Internet

Hex triplets such as #ffffff (white) or #3764ef (blueish) are often used to denote RGB colors. They consist of # followed by six hexadecimal digits (0-f), or sometimes three digits where the real color is obtained by doubling each digit. For example, #fff is #ffffff and #1a8 is #11aa88.

Sadly, that three digit shorthand was the golfiest the internet had to offer, until now.

Write a program or function that takes in a string of 1 to 7 characters:

• The first character will always be #.
• The other characters will always be hexadecimal digits: 0123456789abcdef.

The input is a shorthand form of a hex triplet (or the full form if 7 characters are given). You need to output a full hex triplet that expands the input shorthand based on these patterns:

Input   -> Output
#       -> #000000    (black)
#U      -> #UUUUUU
#UV     -> #UVUVUV
#UVW    -> #UUVVWW    (usual 3-digit shorthand)
#UVWX   -> #UXVXWX
#UVWXY  -> #UVWXYY
#UVWXYZ -> #UVWXYZ    (not shorthand)


Each of U, V, W, X, Y, and Z may be any hexadecimal digit. The output is always 7 characters.

For example:

Input -> Output
# -> #000000
#0 -> #000000
#4 -> #444444
#f -> #ffffff
#a1 -> #a1a1a1
#0f -> #0f0f0f
#99 -> #999999
#1a8 -> #11aa88
#223 -> #222233
#fff -> #ffffff
#1230 -> #102030
#d767 -> #d77767
#bbb5 -> #b5b5b5
#aabbc -> #aabbcc
#00000 -> #000000
#3764e -> #3764ee
#3764ef -> #3764ef
#123456 -> #123456
#f8f8f8 -> #f8f8f8


# Notes

• The input will always start with # and so must the output.

• You may assume all input letters are lowercase (abcdef) or uppercase (ABCDEF) as you prefer.

• Letters in the output may be in either case as you prefer. You can even mix cases.

• Alpha/transparency is not dealt with here (though there are hex versions of RGBA colors).

The shortest code in bytes wins.

• "Sadly, that three digit shorthand was the golfiest the internet had to offer, until now." - Uh, not exactly. HTML, 0 bytes - it works out of the box – Bergi May 30 '17 at 8:47
• The reverse of this would be a cool challenge too – Beta Decay May 30 '17 at 9:20
• I am unsatisfied with the #UVWXY -> #UVWXYY entry because it allows for a single-value representation for the Blue channel, but there's no similar expression for Red and Green (e.g. if I wanted #889071 I can't abbreviate, but #907188 can be...as #90718) the others all work great. – Draco18s May 30 '17 at 19:07
• @Draco18s I love it. That one and the #UVWX -> #UXVXWX. It's such an inconsistent and arbitrary behaviour that it's hard to believe there isn't a couple of browser that actually currently implement it. – xDaizu May 31 '17 at 7:37
• @xDaizu CSS specification has #RGBA and #RRGGBBAA, so #1234 should be read as rgba(17, 34, 51, 0.25) – tsh May 31 '17 at 7:40

# JavaScript, 8682 77 bytes

x=>([s,a=0,b=a,c,d,e,f]=x,f?x:e?x+e:[s,a,d||a,c?b:a,d||b,v=c||b,d||v].join)


just find out that remove recursive save 4 bytes...

idea from @Arnauld save 4 bytes, +1 more bytes

• ([s,a=0,b=a,c,d,e,f]=x)=>f?x:e?x+e:d?s+a+d+b+d+c+d:c?s+a+a+b+b+c+c:s+a+a+a+b+b+b for 80 bytes – Luke May 30 '17 at 8:45
• @Luke I just got ReferenceError: x is not defined – tsh May 30 '17 at 8:47

# Jelly, 24 bytes



x2
j0ị$m0 0 Ḣ;LĿṁ6$$ A full program (the empty lines are actually empty lines). Try it online! or see a test suite* ### How?  - Link 1 (0 bytes), returns its input (e.g. "U" -> "U") - Link 2 (0 bytes), returns its input (e.g. "UV" -> "UV") x2 - Link 3, doubles up (e.g. "UVW" -> "UUVVWW") j0ị - Link 4, joins with final element (e.g. "UVWX" -> "UXVXWXX") m0 - Link 5, reflects its input (e.g. "UVWXY" -> "UVWXYYXWVU") - Link 6 (0 bytes), returns its input (e.g. "UVWXYZ" -> "UVWXYX") 0 - Link 7, returns zero (link 7 is also link 0 since there are 7 links) Ḣ;LĿṁ6$$ - Main link: string Ḣ - head (get the '#')$ - last two links as a monad:
Ŀ     -   call link at index:
L      -     length
$- last two links as a monad: ṁ6 - mould like 6 (e.g. "UVWXYYXWVU" -> "UVWXYY" - or "UV" -> "UVUVUV") ; - concatenate (prepend the '#' again) - implicit print  * the test-suite program had to be altered by swapping the order of what were the Main link and Link 7, while the footer became the Main Link. Furthermore the # had to be manually replaced, since the program as-is heads it. ## CJam, 45 44 424036 35 bytes q(\0se|_,("6* 3* 2e* )f+ _W=+ "S/=~  Runs various code snippets based on the length of the input. • Some formatting <s>not</s> required.... <s></s> – stevefestl May 30 '17 at 6:30 • @SteveFest But crossed out 44 is still regular 44... – Esolanging Fruit May 30 '17 at 6:44 • Aw! You got me! I forgot that! – stevefestl May 30 '17 at 6:45 # PHP 7.1, 88 bytes #<?for(;$i<6;)echo@$argn[_22222232532233423355224462[5*$i+++strlen($argn|aa)*.85]-1]?:0;  ### PHP 5, 90 88 bytes #<?for(;$i<6;)echo$argn[_10311001122011333002244012345[6*$i+++strlen($argn|aa)-8]+1]?:0;  • I have no Idea how you get this idea but it works. Work _21422112233122444113355123456[6*$i+++strlen($argn|aa)-8] ? – Jörg Hülsermann May 30 '17 at 13:55 • could you please explain how this works? – Brian H. May 30 '17 at 14:10 • This one is nice ! It stores the offset in $argn in 21422112233122444113355123456and selects the correct one based on strlen. aa pads the string to at least 2 chars. On input # there is no $argn[1] so ?:0 outputs a 0. This also works for on 0 in the string. One of the best answers I've seen! Sadly it doesn't pay of too much (got Jörg's answer golfed down to 95). – Christoph May 30 '17 at 14:26 • Haha, this is the best abuse of PHP's automatic strings I've seen in a while. +1 – ETHproductions May 31 '17 at 2:12 • @Christoph The Second version needs a PHP Version under 7.1 and a PHP Version over 5.6 I think add this should make this clearer – Jörg Hülsermann May 31 '17 at 9:58 # Java 8, 228 227 bytes s->{int l=s.length();return l>6?s:l>5?s+s.charAt(5):l>4?s.replaceAll("(.)(.)(.)(.)$","$1$4$2$4$3$4"):l>3?s.replaceAll("([^#])","$1$1"):l>2?s.replaceAll("([^#]{2})","$1$1$1"):l>1?s.replaceAll("([^#])","$1$1$1$1$1$1"):"#000000";}  I have the feeling some parts can be golfed, especially the regexes. Regexes aren't really my expertise.. PS: When I change "$1$1", "$1$1$1" and "$1$1$1$1$1$1" to String t="$1$1";, x, x+"$1" and x+x+x the byte-count stays the same. Explanation: Try it here. s->{ // Method with String parameter and String return-type int l=s.length(); // Length of the String return l>6? // If the length is 7: s // Return the input-String :l>5? // Else-if the length is 6: s+s.charAt(5) // Return the input-String with the last character repeated :l>4? // Else-if the length is 5: s.replaceAll("(.)(.)(.)(.)$","$1$4$2$4$3$4")
//   Use a regex with capture group to return the correct #ADBDCD pattern
:l>3?                                   //  Else-if the length is 4:
s.replaceAll("([^#])","$1$1")          //   Repeat every character, except the starting #
:l>2?                                   //  Else-if the length is 3:
s.replaceAll("([^#]{2})","$1$1$1") // Repeat every two characters three times, except the starting # :l>1? // Else-if the length is 2: s.replaceAll("([^#])","$1$1$1$1$1$1") // Repeat the character 6 times, excluding the starting # : // Else: "#000000"; // Simply return the literal String #000000 } // End of method  ## PHP, 959389 87 <?=strtr(_1.intval([a6n,sot,c8c,lba,vf1,vf2][strlen($argn|aa)-2],33),_134256,$argn.=0);  Basically @JörgHülsermann's answer but greatly golfed down so I decided to post it as a seperate answer. I'd count this answer as an collective effort of me and Jörg. -4 bytes thanks to @JörgHülsermann -1 bytes thanks to @JörgHülsermann's base 33 numbers  # Python 3, 166162160 152 bytes import re lambda x,d='(.)$',b=r'\1':re.sub(*[('$','0'*6),(d,b*6),('(..)$',b*3),('(\w)',b*2),('.'+'(.)'*4,r'#\1\4\2\4\3\4'),(d,b*2),('','')][len(x)-1],x)


I construct a list of regex replacement tuples for each pattern, and then extract the tuple at index len(x)-1, finally splatting (*) it into the arguments of re.sub:

lambda x, d='(.)$', b=r'\1': # lambda expression, save often used strings re.sub( # regex replacement of: * # expand what follows into arguments, i.e. f(*(1,2)) -> f(1,2) [ # list of replacement patterns: # 1 character: replace the end with 6 zeroes ('$', '0'*6),
# 2 chars: repeat the last character 6 times
(d, b*6),
# 3 chars: repeat the two non-#s 3 times.
('(..)$', b*3), # 4 chars: replace every non-# with twice itself ('(\w)', b*2), # 5 chars: has to be somewhat verbose.. ('.'+'(.)'*4, r'#\1\4\2\4\3\4'), # 6 chars: repeat the last character (d, b*2), # 7 chars: complete already, replace nothing with nothing ('', '') ][len(x)-1], # select the element from the list that has the right length x) # replace in argument x  saved 8 bytes by memorizing r'\1' (thanks, Gábor Fekete) • Wouldn't using r'\1' as a named parameter save some bytes? – Gábor Fekete May 30 '17 at 11:49 • You wrote o=r'\1' but use b in your code :D – Gábor Fekete May 31 '17 at 8:08 • @GáborFekete Whoops :D – L3viathan May 31 '17 at 8:11 # APL (Dyalog), 43 bytes Requires ⎕IO←0 which is default on many systems. '#',{6⍴(≢⍵)⊃'0'⍵ ⍵(2/⍵)(∊⍵,¨⊃⌽⍵)(⍵,⌽⍵)⍵}1↓⍞  Try it online! 1↓⍞ drop the first character (the hash) { apply the following anonymous function (≢⍵)⊃ use the length of the argument to pick one of the following seven values: '0' a zero ⍵ the argument ⍵ the argument 2/⍵ two (2) of each (/) of the argument (⍵) ∊⍵,¨⊃⌽⍵ the flattened (∊) argument (⍵) followed each (,¨) by the first (⊃) of the reversed (⌽) argument (⍵) ⍵,⌽⍵ the argument (⍵) prepended (,) to the reversed (⌽) argument (⍵) ⍵ the argument 6⍴ repeat elements from that until a length of six is achieved } end of anonymous function '#', prepend a hash to that # Python 2, 167 165 bytes -2 bytes thanks to Trelzevir z=zip lambda s:'#'+''.join([reduce(lambda x,y:x+y,c)for c in['0'*6,s[1:2]*6,z(s[1:2],s[2:3])*3,z(*z(s[1:2],s[2:3],s[3:4]))*2,z(s[1:4],s[-1]*3),s+s[-1],s][len(s)-1]])  It creates a list of strings and chooses based on the length of the string. • You can save 2 bytes using z=zip. – Trelzevir May 30 '17 at 10:37 # Sed, 119 (118 Bytes + -E) s/#// s/^$/0/
s/^.$/&&/ s/^..$/&&&/
s/^(.)(.)(.)$/\1\1\2\2\3\3/ s/^(.)(.)(.)(.)$/\1\4\2\4\3\4/
s/^....(.)$/&\1/ s/^/#/  Straightforward text substitution. # PHP, 87 Bytes use Base 35 Numbers <?=strtr(_2.intval([i4w,qdi,j1y,apg,ruu,ruv][strlen($argn|aa)-2],35),_234156,$argn.=0);  Try it online! or use Base 33 Numbers <?=strtr(_1.intval([a6n,sot,c8c,lba,vf1,vf2][strlen($argn|aa)-2],33),_134256,$argn.=0);  Try it online! # PHP, 89 Bytes <?=strtr(_1.[11111,21212,12233,42434,23455,23456][strlen($argn|aa)-2],_123456,$argn."0");  Try it online! intval(["8kn",gd8,"9ft",wqq,i3j,i3k][strlen($argn|aa)-2],36) + 3 Bytes using a 36 Base

## PHP, 102 Bytes

<?=strtr("01".substr("11111111112121212233424342345523456",5*strlen($argn)-5,5),str_split($argn."0"));


Try it online!

<?=[str_pad("#",7,($l=strlen($p=substr($argn,1)))?$p:0),"#$p[0]$p[0]$p[1]$p[1]$p[2]$p[2]","#$p[0]$p[3]$p[1]$p[3]$p[2]$p[3]","#$p[0]$p[1]$p[2]$p[3]$p[4]$p[4]",$argn][($l>2)*($l-2)];  Try it online! • I golfed down this version to 95 bytes but I thought it was to different so I posted it as an own answer. Hope you like it :) – Christoph May 30 '17 at 14:29 • @Christoph In the moment I am here with my version Try it online! – Jörg Hülsermann May 30 '17 at 14:41 • Base 33 is an awesome idea! I've been sitting here a while but didn't come up with it damn. – Christoph May 30 '17 at 16:30 • @Christoph it is very similar with your golfing of my first version. It was not easy to golf my approch under your approach – Jörg Hülsermann May 30 '17 at 16:47 • @Christoph Thanks and the base 35 Number system is my approach in our Teamwork – Jörg Hülsermann May 31 '17 at 9:49 # Retina, 90 bytes #(..)$
#$1$1$1 #(.)(.)(.)$
#$1$1$2$2$3 #(.)(.)(.(.))$
#$1$4$2$4$3 #$
#0
+#.{0,4}(.)&$1  Try it online! Includes test cases. Explanation: The first translation handles two digits, the second one three, the third one four, and the fourth one zero. However, neither the second and fourth translations repeat the (last) digit, as that is done at the end anyway to cover all the remaining cases. # Haskell, 130127122118109 95 bytes (by user1472751) y a|l<-[last a]=[y"0",y$a++a,a++a++a,do c<-a;[c,c],(:l)=<<init a,a++l,a]!!length a
f(h:r)=h:y r


Try it online!

• There is a superfluous space behind g . – Laikoni Jun 1 '17 at 15:13
• Also (x:r)!(y:t)=x:y:r!t;e!_=e is shorter than a!b=id=<<[[x,y]|(x,y)<-zip a b]. – Laikoni Jun 1 '17 at 15:15
• As the first char is always # you can do g(a:t)|l<-last t=a:[ ... – Laikoni Jun 1 '17 at 15:18
• @Laikoni indeed, those are great improvements! – bartavelle Jun 1 '17 at 15:52
• I found a 95 byte solution that uses a similar approach to yours (great minds think alike, huh?). You can use it or I can post a separate answer. – user1472751 Feb 13 '18 at 21:52

# JavaScript (ES6), 96 bytes

s=>'#'+(c=[u,v,w,x,y,z]=s.slice(1)||'0',z?c:y?c+y:(x?u+x+v+x+w+x:w?u+u+v+v+w+w:c.repeat(v?3:6)))


f=
s=>'#'+(c=[u,v,w,x,y,z]=s.slice(1)||'0',z?c:y?c+y:(x?u+x+v+x+w+x:w?u+u+v+v+w+w:c.repeat(v?3:6)))

console.log(
f('#'),
f('#U'),
f('#UV'),
f('#UVW'),
f('#UVWX'),
f('#UVWXY'),
f('#UVWXYZ')
)

# Windows batch, 389 372 362 349 231 bytes

I totally copied @Neil code...

@call:c %s:~1,1% %s:~2,1% %s:~3,1% %s:~4,1% %s:~5,1% %s:~6,1%
@exit/b
:c
@for %%r in (#%1%2%3%4%5%6.%6 #%1%2%3%4%5%5.%5 #%1%4%2%4%3%4.%4 %s%%1%2%3.%3
%s%%1%2%1%2.%2 %s%%1%1%1%1%1.%1 #000000.0)do @if not %%~xr.==. @echo %%~nr&exit/b

• replacing %s% with %1 should save you a few bytes. – satibel May 30 '17 at 6:53
• %s:~3,1%%s:~4,1% can be replaced with %s:~3,2%. Also I'm not sure that this works for an input of #. – Neil May 30 '17 at 8:56
• By the way, I tried a different algorithm, and it came out at 243 bytes. – Neil May 30 '17 at 9:31
• May I know what's the algorithm? – stevefestl May 30 '17 at 9:55
• (Sorry, I didn't see your comment due to a lack of @Neil.) There's some boilerplate but the two lines of interest are call:c %s:~1,1% %s:~2,1% %s:~3,1% %s:~4,1% %s:~5,1% %s:~6,1% and for %%r in (#%1%2%3%4%5%6.%6 #%1%2%3%4%5%5.%5 #%1%4%2%4%3%4.%4 %s%%1%2%3.%3 %s%%1%2%1%2.%2 %s%%1%1%1%1%1.%1 #000000.0)do if not %%~xr.==. echo %%~nr&exit/b. – Neil May 31 '17 at 11:12

## Python 2 - 179 bytes

n=raw_input()                                #prompts for string
t=len(n)                                     #the length of the string is stored to 't'
if t==1:n+="0"*6                             #if t is only one char long, it needs to be black, so n is assigned 6 zeroes
if t==2:n+=n[1]*5                            #if t is two chars long, it adds the last character times 5 at the end
if t==3:n+=n[1:3]*2                          #if t is 3 chars, it multiplies the last two digits times 3
if t==4:n="#"+n[1]*2+n[2]*2+n[3]*2           #if t is 4 chars, it multiplies each char by two
if t==5:n=n[:2]+n[4]+n[2]+n[4]+n[3]+n[4]     #if t is 5 chars, it makes it work
if t==6:n+=n[t-1]                            #if t is 6 chars, it adds the last character to the end
print n                                      #it prints out n


Can anyone help me save some bytes? All of the if statements seem like they could be shortened into something shorter, I just don't know what.

• Try putting each snippet in a list and indexing. Also, switching to Python 3 will most likely save bytes and list[len(list)-x] is the same as list[-x]. – CalculatorFeline May 31 '17 at 1:49
• If you want to squeeze out some bytes, consider converting if t==1: to if t<2: (and if t==2: to if t<3:, etc.). It's less readable, to be sure, but more code-golf-able! – J-L Sep 26 '18 at 19:51

## Perl, 61 bytes

say+(/./g,0)[0,1,(unpack+S7,"g+g+ÜRÉ/Â¥[ [")[y/#//c]=~/./g]


Run with perl -nE. Assumes that the input is exactly as described (gives incorrect results if the input has a trailing newline).

The string "g+g+ÜRÉ/Â¥[ [" encodes the 7 16-bit numbers 11111,11111,21212,12233,42434,23455,23456 as 14 latin1 characters. Here’s a hexdump for clarity:

0000001d: 672b 672b dc52 c92f c2a5 9f5b a05b       g+g+.R./...[.[

• I replaced the Latin-1 string with a call to pack(), and got: perl -nE 'say+(/./g,0)[0,1,(unpack+S7,pack "H*","672b672bdc52c92fc2a59f5ba05b")[y/#//c]=~/./g]' . But when I type "#a", I get "#a0a0a0" which I think is wrong. It should be "#aaaaaa". (Maybe I made a mistake in the pack() call.) – J-L Sep 26 '18 at 2:27
• This time I replaced the unpack() & pack() calls with the literal shorts, and got: perl -nE 'say+(/./g,0)[0,1,(11111,11111,21212,12233,42434,23455,23456)[y/#//c]=~/./g]'. It still appears to be wrong, as "#a" still yields the incorrect answer of "#a0a0a0" (instead of "#aaaaaa"). – J-L Sep 26 '18 at 16:04
• Ah! I figured it out! I needed to use the -l switch (that's "ell" as in the "letter L") with the -nE switch, like this: perl -lnE 'say+(/./g,0)[0,1,(11111,11111,21212,12233,42434,23455,23456)[y/#//c]=~/./g]' . Now it works correctly. – J-L Sep 26 '18 at 16:27
• The warning that says "(gives incorrect results if the input has a trailing newline)" can be eliminated by changing "Run with perl -nE" to "Run with perl -lnE". (The -l part of the switch gets rid of the trailing newline for you.) – J-L Sep 26 '18 at 16:33

TXR Lisp: 171 bytes

Indented:

(do let ((s (cdr @1)))
(caseql (length s)
(0 "#000000")
(1 #@s@s@s@s@s@s)
(2 #@s@s@s)
(3 #@[mappend list s s])
(4 #@[s 0]@[s 3]@[s 1]@[s 3]@[s 2]@[s 3])
(5 #@s@[s 4])
(6 #@s))))


This is an anonymous function: the do macro generates a (lambda ...) form.

It is idiomatic coding style, suitable for production; the only golfing is squashing whitespace:

(do let((s(cdr @1)))(caseql(length s)(0"#000000")(1#@s@s@s@s@s@s)(2#@s@s@s)(3#@[mappend list s s])(4#@[s 0]@[s 3]@[s 1]@[s 3]@[s 2]@[s 3])(5#@s@[s 4])(6#@s))))


# Braingolf, 95 bytes

l1-.1e$_!&@4>[!@]|.2e$_!&@!@2!@2|.3e$_<@V2[R<!@!@v]|.4e$_<@VRM&,2>[@v!@R]|.5e$_<@!&@@|.6e$_&@|;


Try it online!

This is effectively Braingolf's equivalent of a switch-case on the amount of characters after the # in the input.

### Explanation

Stuff that always runs:

l1-.1  Implicit input to stack
l      Push length of stack
1-    Decrement last item in stack
.   Duplicate last item in stack
1  Push 1


If #X:

e$_!&@4>[!@]| e If last 2 items (input length - 1 and 1) are equal..$_            ..Pop last item silently
!&@         ..Print entire stack as chars without popping
4>       ..Push 4 and move it to start of stack
[..]   ..While loop, decrements first item in stack when it reaches ]
..If first item in stack is 0 when reaching ], exit loop
..This loop will run 5 times
!@    ....Print last char without popping
|  endif


If #XX

This one may be golfable a little, I might look at it when I get home

.2e$_!&@!@2!@2| .2 Duplicate last item and push 2 e If last 2 items (input length - 1 and 2) are equal..$_            ..Pop last item silently
!&@         ..Print entire stack as chars without popping
!@2      ..Print last 2 items as chars without popping
!@2   ..Print last 2 items as chars without popping
|  Endif


If #XXX

.3e$_<@V2[R<!@!@v]| .3 Duplicate last item and push 3 e If last 2 items (input length - 1 and 3) are equal..$_                ..Pop last item silently
<@              ..Move first item in stack to the end, then pop and print
V2            ..Create new stack and push 2 to it
[.......]   ..While loop, see above for explanation
R<         ....Switch to stack1 and move first item to end of stack
!@!@     ....Print last item on stack twice without popping
v    ....Move to stack2 for loop counting
|  Endif


You get the idea

# Ruby, 127 bytes

c=~/#(.)?(.)?(.)?(.)?(.)?(.)?/
$6?c:'#'+($5?$1+$2+$3+$4+$5*2:$4?$1+$4+$2+$4+$3+$4:$3?$1*2+$2*2+$3*2:$2?($1+$2)*3:$1?$1*6:"0"*6)  Try it online! # Ruby, 118 bytes def f c c.sub!'#','' s=c.size t=c[3] s>5?'#'+c:f(s<1?'0':s<2?c*6:s<3?c*3:s<4?c*2:s<5?c[0]+t+c[1]+t+c[2]+t:c+c[-1]) end  Try it online! # Pyth, 35 bytes +\#@<R6[J|tQ\0K*6JKKs*R2JjeJJ+JeJ)l  Try it online here, or verify all test cases here. +\#@<R6[J|tQ\0K*6JKKs*R2JjeJJ+JeJ)lQ Implicit: Q=eval(input()) Trailing Q inferred tQ Remove first char of input | \0 The above, or "0" if empty J *Store in J (also yields stored value) K*6J *Repeat J 6 times, store in K KK *2 more copies of the above s*R2J *Duplicate each char of J in place jeJJ *Join chars of J on last char of J +JeJ *Append last char of J to J [ ) Wrap the 5 starred results in an array <R6 Trim each to length 6 @ lQ Choose result at index of length of input (Modular indexing, so length 7 selects 0th element) +\# Prepend #, implicit print  # Powershell, 113 111 bytes param($s)-join($s+='0'*($s-eq'#'))[0,1+((,1*5),(2,1*2+2),(1,2,2,3,3),(4,2,4,3,4),(2..5+5),(2..6))[$s.Length-2]]  Explaned test script: $f = {

param($s) # parameter string$s+='0'*($s-eq'#') # append '0' if$s equal to '#'
$i=( # get indexes from array (,1*5), #$i = 1,1,1,1,1 if $s.length-2 = 0 (2,1*2+2), #$i = 2,1,2,1,2 if $s.length-2 = 1 (1,2,2,3,3), #$i = 1,2,2,3,3 if $s.length-2 = 2 (4,2,4,3,4), #$i = 4,2,4,3,4 if $s.length-2 = 3 (2..5+5), #$i = 2,3,4,5,5 if $s.length-2 = 4 (2..6) #$i = 2,3,4,5,6 if $s.length-2 = 5 )[$s.Length-2]
-join$s[0,1+$i]     # join chars from $s by indexes 0, 1 and$i

}

@(
, ("#", "#000000")
, ("#0", "#000000")
, ("#4", "#444444")
, ("#f", "#ffffff")
, ("#a1", "#a1a1a1")
, ("#0f", "#0f0f0f")
, ("#99", "#999999")
, ("#1a8", "#11aa88")
, ("#223", "#222233")
, ("#fff", "#ffffff")
, ("#1230", "#102030")
, ("#d767", "#d77767")
, ("#bbb5", "#b5b5b5")
, ("#aabbc", "#aabbcc")
, ("#00000", "#000000")
, ("#3764e", "#3764ee")
, ("#3764ef", "#3764ef")
, ("#123456", "#123456")
, ("#f8f8f8", "#f8f8f8")
) |% {
$s,$e = $_$r = &$f$s
"$($e-eq$r):$r"
}


Output:

True: #000000
True: #000000
True: #444444
True: #ffffff
True: #a1a1a1
True: #0f0f0f
True: #999999
True: #11aa88
True: #222233
True: #ffffff
True: #102030
True: #d77767
True: #b5b5b5
True: #aabbcc
True: #000000
True: #3764ee
True: #3764ef
True: #123456
True: #f8f8f8
`