42
\$\begingroup\$

Recently, I've found a rant from 2004 about Internet Explorer's color parsing. Turns out, MS made sure that just about anything will be parsed as a color somehow and I think it makes for a neat challenge.

Well, how does IE parse colors?

Let's give it an input with a length of...

  • ...less than 4 chars:
    1. Replace chars not matching [0-9a-fA-F] with "0"
    2. Append "0" until the length is 3 chars
    3. Split into 3 parts, 1 char each
    4. Prepend each part with "0"
  • more or equal than 4 chars:
    1. Replace chars not matching [0-9a-fA-F] with "0"
    2. Append "0" until the length is divisible by 3
    3. Split into 3 parts of equal size
    4. Remove chars from the front of each part until they are 8 chars each
    5. Remove chars from the front of each part until one of them starts with something that isn't "0" or until all parts are empty
    6. Look at the length of the parts: Are shorter than 2? If so, prepend each part with a "0" until each part is 2 chars long. If not, remove characters from the back of each part until they are 2 chars each

... w h a t

It's true! Here are some examples (irrelevant steps omitted):

F 
--> F00        // append "0" until length is 3
--> F, 0, 0    // split into 3 parts
--> 0F, 00, 00 // prepend 0
--> #0F0000    // done

FLUFF 
--> F00FF      // replace invalid chars with 0
--> F00FF0     // pad to length that is divisible by 3
--> F0, 0F, F0 // split into 3 parts
--> #F00FF0    // done

rADioACtivE
--> 0AD00AC000E       // replace invalid chars with 0
--> 0AD00AC000E0      // pad to length that is divisible by 3
--> 0AD0, 0AC0, 00E0  // split into 3 parts
--> AD0, AC0, 0E0     // remove "0"s from the front of each part
                      // until one part doesn't start with a "0"
--> AD, AC, 0E        // remove chars from the back of each part until
                      // each part is of length 2
--> #ADAC0E           // done

1234567890ABCDE1234567890ABCDE 
--> 1234567890, ABCDE12345, 67890ABCDE // split into 3 parts
--> 34567890, CDE12345, 890ABCDE       // remove chars from the front until
                                       // each part is of length 8
--> 34, CD, 89                         // remove chars from the back of 
                                       // each part until each part is of length 2
--> #34CD89                            // done

rules 
--> 000E0      // replace invalid chars with "0"
--> 000E00     // pad to length divisible by 3
--> 00, 0E, 00 // split into 3 parts
--> 0, E, 0    // remove "0"s from the front of each part until one part 
               // doesn't start with a "0"
--> 00 0E 00   // parts are too short, prepend a "0"
--> #000E00    // done

GHIJKL
--> 000000      // replace invalid chars with "0"
--> 00, 00, 00  // split into 3 parts
--> , , ,       // remove "0" from the front of each part until empty
--> 00, 00, 00  // parts are too short, prepend "0" until they are length 2
--> #000000     // done

Rules

  • This is , shortest answer wins
  • No standard loopholes
  • Expect any input matching [a-zA-Z0-9]*. Yes, empty input as well (results in #000000).
  • Output any representation of the resulting RGB color (including but not limited to the actual color, tuples, hex codes, with or without #, any case, ...)
  • For the sake of this challenge, special color names are not respected but instead processed according to the rules. Hence, "red" will result in #000E0D and not #FF0000

Test cases

Note that the # is completely optional

red                              -> #000E0D
rules                            -> #000E00
1234567890ABCDE1234567890ABCDE   -> #34CD89
rADioACtivE                      -> #ADAC0E
FLUFF                            -> #F00FF0
F                                -> #0F0000
                                 -> #000000
zqbttv                           -> #00B000 
6db6ec49efd278cd0bc92d1e5e072d68 -> #6ECDE0
102300450067                     -> #100000
GHIJKL                           -> #000000
000000                           -> #000000

EDIT

These are the longest Jelly and Vyxal answers I've seen on this site up to this point :o

EDIT 2

Lecdi pointed out that the specification had a flaw that caused undefined behaviour if the input turned into 000000 at some point, such as GHIJKL. As of now, all answers seem to handle this with the exception of lyxal's 64 byte Vyxal answer.

The flaw in the spec should now be fixed. Sorry!

\$\endgroup\$
14
  • 7
    \$\begingroup\$ A perfect mix of sadism and creativity. \$\endgroup\$
    – Arnauld
    Commented Jul 31, 2022 at 13:54
  • 2
    \$\begingroup\$ @JonathanAllan About the mixed cases, that was a inconsistency I made, it's fixed now. Casing is irrelevant as described by the 4th rule's very last point. I also added your suggested test case, as I concur that the correct solution should be #100000. \$\endgroup\$ Commented Jul 31, 2022 at 19:15
  • 4
    \$\begingroup\$ Are JavaScript answers allowed to require IE as an execution requirement? :P \$\endgroup\$
    – Bergi
    Commented Aug 1, 2022 at 0:38
  • 6
    \$\begingroup\$ Related Q&A on Stack Overflow: Why does HTML think “chucknorris” is a color? \$\endgroup\$
    – Bergi
    Commented Aug 1, 2022 at 0:39
  • 3
    \$\begingroup\$ @Bergi thought of that too ;-) and I made a 44 solution that I didn't test on IE v=>((s=document.body.style).color=v,s.color), but the last rule "special color names are not respected" would make it fail anyway... \$\endgroup\$
    – Kaiido
    Commented Aug 1, 2022 at 1:57

15 Answers 15

7
\$\begingroup\$

Vyxal, 51 50 47 44 bytes

⇧ƛk^$c∧;ṅ3/\0ÞḞṅ3/8Nvȯ∩\03*:£øl:L2∵Ẏ¥p¥p2Nȯ∩

Try it Online!

A huge mess, but at least I outgolfed lyxal.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ You outgolfed lyxal, but you forgot about me evil laugh \$\endgroup\$
    – emanresu A
    Commented Aug 3, 2022 at 2:05
7
\$\begingroup\$

JavaScript + HTML, 50 bytes

document.body.setAttribute('bgcolor','#'+prompt())

Works in any browser, as the legacy behavior was kept with bgcolor (even though it won't work with CSS). Displays as graphical output.

<script>document.body.setAttribute("bgcolor",'#'+prompt());</script>

If graphical output is not allowed, then:

JavaScript + HTML, 97 bytes

c=document.body;c.setAttribute('bgcolor','#'+prompt());alert(getComputedStyle(c).backgroundColor)

<script>c=document.body;c.setAttribute('bgcolor','#'+prompt());alert(getComputedStyle(c).backgroundColor)</script>

Uses the computed styles generated. Will output like rgb(X, Y, Z).

To output a hex code, this would be 156 bytes:

c=document.body;c.setAttribute('bgcolor','#'+prompt());alert(getComputedStyle(c).backgroundColor.match(/\d+/g).map(x=>(x|256).toString(16).slice(1)).join``)

<script>c=document.body;c.setAttribute('bgcolor','#'+prompt());alert(getComputedStyle(c).backgroundColor.match(/\d+/g).map(x=>(x|256).toString(16).slice(1)).join``)</script>

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

JavaScript (Browser), 138 bytes, non-competitive

c=>(document.body.innerHTML=`<font id=g color=${c}>`,getComputedStyle(g)).color.match(/\d+/g).map(n=>(n|256).toString(16).slice(1)).join``

The same color parsing algorithm also works on browsers other than IE (I had tested on my Firefox 103 and it works). But the code will output #ff0000 for input red so marked non-competitive.

f=

c=>(document.body.innerHTML=`<font id=g color=${c}>`,getComputedStyle(g)).color.match(/\d+/g).map(n=>(n|256).toString(16).slice(1)).join``

console.log(f('red                             '));
console.log(f('rules                           '));
console.log(f('1234567890ABCDE1234567890ABCDE  '));
console.log(f('rADioACtivE                     '));
console.log(f('FLUFF                           '));
console.log(f('F                               '));
console.log(f('                                '));
console.log(f('zqbttv                          '));
console.log(f('6db6ec49efd278cd0bc92d1e5e072d68'));
console.log(f('102300450067                    '));

Save ~30 bytes by Kaiido

\$\endgroup\$
6
  • \$\begingroup\$ 149: f=c=>eval(((document.body.innerHTML=`<font id=g color=${c}>`),getComputedStyle(g).color));rgb=(...a)=>a.map(n=>(n+256).toString(16).slice(1)).join`` \$\endgroup\$
    – Kaiido
    Commented Aug 1, 2022 at 12:54
  • 3
    \$\begingroup\$ Shouldn't this be 'HTML+Javascript', since you need a DOM environment for execution? \$\endgroup\$
    – ccprog
    Commented Aug 1, 2022 at 14:43
  • \$\begingroup\$ You can do this in HTML in 74 bytes, output letter X in the actual color: <font>X<input oninput=this.parentElement.setAttribute('color',this.value) \$\endgroup\$
    – ccprog
    Commented Aug 1, 2022 at 15:18
  • \$\begingroup\$ @ccprog the goal is to output an hex string, not to set an element's color. (And you could outgolf your HTML: onevent attributes use the target as context, so this is never needed there, but setting an id on the parent makes it available as a global, so you could shave 17 bytes if I count correctly <font id=f>X<input oninput=f.setAttribute('color',value) (untested, I'm on a phone, but that doesn't answer the question anyway) (Oh and since you don't read it I don't think you need setAttribute, .color should work, if I'm not mistaken). \$\endgroup\$
    – Kaiido
    Commented Aug 1, 2022 at 15:43
  • 1
    \$\begingroup\$ @Kaiido quote: "Output any representation of the resulting RGB color (including but not limited to the actual color". Otherwise, you are right: <font id=f>X<input oninput=f.color=value works just fine with 41 bytes. \$\endgroup\$
    – ccprog
    Commented Aug 1, 2022 at 16:04
4
\$\begingroup\$

Jelly,  44  39 bytes

-5 thanks to UnrelatedString (clever use of ;Ṫ for the empty string special case).

ØhḊiⱮŒla;Ṫ$œs3z0ZŻ€ṫ€-7ZḊẸ€Ḣ¬Ɗ¡ƬḊƇṪḣ2ZF

A full program that accepts a string and prints the resulting colour code (keeping character casing from the input*).

Try it online! Or see the test-suite.

How?

ØhḊiⱮŒla;Ṫ$œs3z0ZŻ€ṫ€-7ZḊẸ€Ḣ¬Ɗ¡ƬḊƇṪḣ2ZF - ...f(X)
Øh                                     - hex characters = "0123456789abcdef"
  Ḋ                                    - dequeue -> "123456789abcdef"
     Œl                                - lower-case X
    Ɱ                                  - map with:
   i                                   -   first 1-indexed index or 0 if not found
          $                            - last two links as a monad - f(X):
         Ṫ                             -   tail (yields zero when X is empty)
        ;                              -   X (without its tail) concatenated with that (its tail or a zero)
       a                               - logical AND the hex-char-indicator list with X or [0] (vectorises)
           œs3                         - split into three equal chunks
                 Ż€                    - prefix each with a zero
                   ṫ€-7                - tail each from index -7 -> last (up to) eight values
                       Z               - transpose
                              Ƭ        - collect up while distinct, applying:
                             ¡         -   repeat...
                            Ɗ          -   ...number of times: last three links as a monad:
                        Ẹ€             -     any? for each
                          Ḣ            -     head
                           ¬           -     logical NOT
                       Ḋ               -   ...action: dequeue
                                Ƈ      - keep those which are truthy under:
                               Ḋ       -   dequeue
                                 Ṫ     - tail
                                  ḣ2   - head to index two
                                    Z  - transpose
                                     F - flatten
                                       - implicit, smashing print
\$\endgroup\$
2
4
\$\begingroup\$

JavaScript, 238 234 bytes

Way longer than the others, but actually competing!

c=>(M=Math,s='slice',t=M.ceil(c.length/3),c=c.padEnd(M.max(t*3,3)).replace(/[g-zG-Z ]/g,'0'),c=[c[s](0,t),c[s](t,t*2),c[s](t*2)]).map(p=>p[s](i=M.min(...c.map(p=>(r=p.search(/[^0].{0,7}$/),r<0?3e333:r))),i+2).padStart(2,'0')).join('')

Less golfed:

c => (
  M = Math, s = 'slice', // these are shorter
  t = M.ceil(c.length / 3), // prepare length for the parts
  c = c
    .padEnd(M.max(t*3, 3)) // pad with spaces until length divisible by 3, with a minimum of 3
    .replace(/[g-zG-Z ]/g, '0'), // replace all non-hex characters with '0'
  c = [c[s](0, t), c[s](t, t*2), c[s](t*2)] // split into 3 equal parts
).map(p => (
  i = M.min(...c.map(p=>(r=p.search(/[^0].{0,7}$/),r<0?3e333:r))), // search for a character that's not '0' in the last 8 characters, returning its index, or infinity when negative (no match found), and take the lowest of the parts
  p[s](i, i+2).padStart(2, '0') // slice max two characters from the lowest non-'0', and pad to two characters
)).join('')
\$\endgroup\$
3
\$\begingroup\$

Vyxal, 67 64 bytes

k6:⇧∪ṅ\^pøB?0øṙ₅:ǒ3εǒ+↲›3/?L4<[2↳›|8Nvȯ{:vh0J≈|ƛh0=ßḢ}ƛ₃[0p|2Ẏ}ṅ

Try it Online!

A big mess of unicode and lesser-used overloads of things

Explained

There are 10 parts here:

Generating the hex-digit regex: k6:⇧∪ṅ\^pøB
Replacing non-hex with 0: ?0øṙ
Padding to nearest multiple of 3: ₅:ǒ3εǒ+↲›
Splitting into 3 equal parts: 3/
The length 4 conditional branch: ?L4<[
  Making each bit 2 digits with 0s if needed: 2↳›
|
  Getting the last 8 characters: 8Nvȯ
  Removing 0s until something doesn't start with a 0: {:vh0J≈|ƛh0=ßḢ}
  Either prepending a 0 or getting the last 2 chars: ƛ₃[0p|2Ẏ}
Outputting the result: ṅ

Generating the hex-digit regex

k6:⇧∪ṅ\^pøB
k6           # The string "0123456789abcdef"
  :⇧         # Uppercased ("0123456789ABCDEF")
    ∪ṅ       # Unioned with the lowercase string ("0123456789abcdefABCDEF")
       \^p   # With a caret prepended ("^0123456789abcdefABCDEF")
          øB # Surrounded in "[]" ("[^0123456789abcdefABCDEF]")

This regex ([^0123456789abcdefABCDEF], called re from here) will be used in the next step to determine which characters to replace with 0s.

Replacing non-hex with 0

?0øṙ # Before this part, the stack is [re]
?    # Push the input : [re, input]
 0   # Push a 0 to the stack : [re, input, 0]
  øṙ # Replace matches of re in input with 0

This completes the step of replacing invalid characters with 0s, which is common to strings less than 4 chars and strings with at least 4 chars. This string will be called 0-str from here.

Padding to nearest multiple of 3

₅:ǒ3εǒ+↲› # Before this part, the stack is [0-str]
₅:        # Push two copies of the length of 0-str without popping it
  ǒ3ε     # Modulo the first copy by 3 and take the absolute difference of that and 3 to get how far away the length is from the next multiple of 3. Note that this may result in the result being 3 because a string of length that is divisible by 3 will return 3, as 3 - (length % 3 => 0) = 3
     ǒ    # Modulo 3 again to make a 3 a 0
      +↲› # Add that to the remaining copy of the length to get how many characters should be in the padded string, left pad 0-str with spaces to that many characters and replace all spaces with 0s.

The result of this step is common to both string length possibilities and will be referred to as pad-0-str.

Splitting into 3 equal parts

3/ # Before this part, the stack is [pad-0-str]
3/ # Divide the string into 3 equal parts

Yes, there really is a built-in for this. The result will be referred to as 3-part-pad.

The length 4 conditional branch

?L4<[...|...} # Before this part, the stack is [3-pad-str]
?L            # Is the length of the input
  4<          # Less than 4?
    [...      # If so, move onto the steps for a string with less than 4 characters
        |...} # Otherwise, move onto the steps for a string with at least 4 characters. A `}` is used instead of a `]` because it saves a byte over `;]` later on.

The stack hasn't changed after this step.

Less than 4 - Making each bit 2 digits with 0s if needed

2↳› # Before this part, the stack is [3-pad-str]
2↳  # Right pad each part of 3-pad-str to be two characters, using spaces. This is needed because components of 3-pad-str will be empty if the input is the empty string. In the case of the empty string, this returns "  ". Otherwise, it returns " " + char
  › # Replace the spaces with 0s. This turns "  " into "00" and everything else into "0" + char.

The result of this will be called res.

At least 4 - Getting the last 8 characters

8Nvȯ # Before this part, the stack is [3-pad-str]
8N   # Push -8 to the stack : [3-pad-str, -8]
  vȯ # Push [part[-8:] for part in 3-pad-str]

The result of this will be referred to as last-8

At least 4 - Removing 0s until something doesn't start with a 0

{:vh0J≈|ƛh0=ßḢ}   # Before this part, the stack is [last-8]
{                  # While ...
 :vh               # the first character of each part of last-8 (leaving last-8 on the stack)...
    0J≈            # are all equal to 0:
       |ƛh0=       # For each item, check if the first character is 0
            ßḢ}    # And if so, remove the head of that part. The `}` here closes both the map lambda and the while loop.

This is probably the messiest section. The result of this will be referred to as second-last.

At least 4 - Either prepending a 0 or getting the last 2 chars

ƛ₃[0p|2Ẏ} # Before this part, the stack is [second-last]
ƛ         # For each item in second-last, which has leading 0s removed:
 ₃[       # If the length is 1:
   0p     #   Prepend a 0
     |2Ẏ} # Else: take the last two characters.
          # The `}` here closes both this inner if-statement, the map lambda, and the outer if-statement

The result of this part will be referred to as res. This is analogous to the step taken in the less than 4 branch.

Outputting the result

ṅ # Join res on empty string - concatenate into a single string.

I could have used the s flag here, but at this point, we're already play the long golf game, so I thought I might as well go flagless.

\$\endgroup\$
2
  • \$\begingroup\$ Would a map not be shorter than a regex? I’m on a phone rn so I can’t test \$\endgroup\$
    – Seggan
    Commented Jul 31, 2022 at 15:43
  • \$\begingroup\$ Get outgolfed lol \$\endgroup\$
    – naffetS
    Commented Jul 31, 2022 at 19:09
3
\$\begingroup\$

Retina 0.8.2, 117 bytes

T`op`a-fA-FRd
$
00
^((.)*?)((?<_-2>.)*)((?<-_>.)*)0?0?$
00$1¶00$3¶00$4¶
.+(.{8})
$1
+`^0(..+¶)0(.+¶)0
$1$2
(..).*¶
$1

Try it online! Link includes test cases. Explanation:

T`op`a-fA-FRd

Replace non-hex digits with zeros.

$
00

Append two zeros in case the length is not a multiple of 3.

^((.)*?)((?<_-2>.)*)((?<-_>.)*)0?0?$
00$1¶00$3¶00$4¶

Divide the string into three equal parts, discarding the trailing zeros just added if necessary. Prefix 00 to each part in case it is short.

.+(.{8})
$1

Trim each part to the last 8 digits.

+`^0(..+¶)0(.+¶)0
$1$2

Trim leading zeros while all parts have one, but don't trim to fewer than two digits.

(..).*¶
$1

Keep only the first two digits of each part and join everything together.

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

05AB1E, 48 47 bytes

lA6.$S0:₄¦«3ô¨S3ä8δ.£'e¶:øJ0Û¶'e:2£₄¦D‚ì2.£€Sø˜

Output as a lowercase list of characters without #.

Try it online or verify all test cases.

Explanation:

Step 1: Convert the input to lowercase, and replace all invalid letters with a 0:

l              # Lowercase the (implicit) input-string
 A             # Push the lowercase alphabet
  6.$          # Remove its first 6 characters: "ghijklmnopqrstuvwxyz"
     S         # Convert it to a list of characters
      0:       # And replace all those characters with a "0" in the uppercase input

Try just step 1 online.

Step 2: Pad with trailing 0s, and split it into three equal-sized parts (with each part as a list of characters):

₄              # Push 1000
 ¦             # Remove it's first character: "000"
  «            # Append it
   3ô          # Split it into parts of size 3
     ¨         # Remove the last (potentially shorter) part
      S        # Convert it to a flattened list of characters
       3ä      # Split it into 3 equal-sized parts

Try just the first two steps online.

Step 3: Only keep the last 8 characters of each inner list:

 δ             # Map over each inner list
8 .£           # Only keep (up to) the last 8 characters

Try just the first three steps online.

Step 4: Remove leading 0s from each list, until a column doesn't start with a 0 anymore:

'e¶:          '# Replace all "e" with a newline
               # (workaround, because "0e0" would be interpret as 0 in 05AB1E)
    ø          # Zip/transpose; swapping rows/columns
     J         # Join each inner list together
      0Û       # Trim all leading 0s
        ¶'e:  '# Undo the workaround, by replacing all newlines back to "e"s

Try just the first four steps online.

Step 5: Only keep the first two characters of each row, potentially prepending the result with 0s, and output the final result:

2£             # Only keep the first two triplets of the list
  ₄¦           # Push "000" again
    D‚         # Pair it with itself: ["000","000"]
      ì        # Prepend this in front of the list
       2.£     # Now only keep the last two triplets
          €S   # Convert each inner string back to a list of characters
            ø  # Zip/transpose back; swapping rows/columns
             ˜ # Flatten the pair of triplets of characters
               # (after which the sixtet of characters is output implicitly as result)
\$\endgroup\$
3
\$\begingroup\$

Rust, 372 367 340 bytes

|mut s:Vec<u8>|{for c in &mut s{if!c.is_ascii_hexdigit(){*c=48}}s.extend(b"000");let d:Vec<_>=s.chunks((s.len()-1).max(3)/3).take(3).map(|c|&c[c.len().saturating_sub(8)..]).collect();d.iter().flat_map(|c|match&c[d.iter().map(|c|c.iter().take_while(|c|**c==48).count()).min().unwrap()..]{[]=>*b"00",&[c]=>[48,c],&[x,y,..]=>[x,y]}).collect()}

Ungolfed:

fn f(mut s: Vec<u8>) -> Vec<u8> {
    for c in &mut s {
        if !c.is_ascii_hexdigit() {
            *c = b'0'
        }
    }
    s.extend(b"000");

    let chunks: Vec<_> = s
        .chunks((s.len() - 1).max(3) / 3)
        .take(3)
        .map(|c| &c[c.len().saturating_sub(8)..])
        .collect();

    let to_remove = chunks
        .iter()
        .map(|c| c.iter().take_while(|c| **c == b'0').count())
        .min()
        .unwrap();

    chunks
        .iter()
        .flat_map(|c| match &c[to_remove..] {
            [] => *b"00",
            &[c] => [b'0', c],
            &[c1, c2, ..] => [c1, c2],
        })
        .collect()
}

Playground

The chunking is a little tricky, but otherwise a relatively boring solution.

\$\endgroup\$
2
  • 3
    \$\begingroup\$ Why allow the Clippy lint? c.is_ascii_hexdigit() is shorter than (*c as char).is_digit(16). \$\endgroup\$
    – alephalpha
    Commented Aug 2, 2022 at 10:47
  • 2
    \$\begingroup\$ Because I totally missed that that method also exists for u8! I silenced the lint in an earlier iteration where c was a char, so c.is_digit(16) actually was shorter than c.is_ascii_hexdigit(), and then didn't pay attention when refactoring. Thanks! \$\endgroup\$
    – Lamdba
    Commented Aug 2, 2022 at 12:30
3
\$\begingroup\$

Vyxal, 29 bytes

S⇩3ẇ3↲∑k6Ǐ~vḟİ∑3/⟑8NȯHH2∆Z2Ẏ₴

Try it Online!

S⇩                            # Stringify ("" -> 0 edgecase) and lowercase
  3ẇ                          # Cut into chunks of length 3
     3↲                       # Pad each to length 3 with spaces (will be zeroed)
      ∑                       # Concatenate
       k6                     # Lowercase hex
          Ǐ                   # Append the leading zero
          ~vḟ                 # Without popping, for each char in input, find it in the hex
             İ                # Index into the hex + 0 - ones not found, -1s, get indexed into the end
              ∑               # Concat into a string
               3/             # Divide into 3 parts
                 ⟑            # Over each...
                  8Nȯ         # Get last 8 chars
                     HH       # Convert to/from hex to remove leading zeroes
                       2∆Z    # zfill each to length 2
                          2Ẏ  # Get the first two chars of each
                            ₴ # Print 
\$\endgroup\$
6
  • \$\begingroup\$ @Steffan Fixed both. \$\endgroup\$
    – emanresu A
    Commented Aug 3, 2022 at 2:17
  • \$\begingroup\$ This still doesn't work for an empty string, it's just that Vyxal treats an empty input as 0 \$\endgroup\$
    – naffetS
    Commented Aug 3, 2022 at 2:18
  • \$\begingroup\$ @Steffan It works when empty input is given, so it’s fine \$\endgroup\$
    – emanresu A
    Commented Aug 3, 2022 at 3:40
  • \$\begingroup\$ It's not empty input, it's no input. \$\endgroup\$
    – naffetS
    Commented Aug 3, 2022 at 3:41
  • 1
    \$\begingroup\$ @Steffan passing the correct strings in with a consistent I/O format (plain, without quotes) is perfectly valid. \$\endgroup\$
    – emanresu A
    Commented Aug 3, 2022 at 3:44
3
\$\begingroup\$

C#, 235 229 227 bytes

-6 bytes thanks to Kevin Cruijssen

s=>{int l;for(s=Regex.Replace(s,"[^0-9a-fA-F]|^$","0");(l=s.Length)%3>0;s+="0");for(s=l<4?"0"+s[0]+0+s[1]+0+s[2]:s;(l=s.Length/3)>8|s[0]+s[l]+s[2*l]<145&l>2;s=string.Concat(s.Where((_,i)=>i%l>0)));return s.Where((_,i)=>i%l<2);}

Try it online!


Ungolfed:

input => {
    //the three while loops are golfed into for loops in the submission, with the last two then being combined into one

    // replace non hex chars with 0
    input = Regex.Replace(input, "[^0-9a-fA-F]|^$", "0");

    // pad input to next multiple of 3
    while (input.Length % 3 > 0) {
        input += "0"; 
    }
    // add 0 at the start of each section if the input is now 3 chars long
    // the final two zeroes here get coerced to strings
    if (input.Length == 3) input = "0" + input[0] + 0 + input[1] + 0 + input[2];

    // used to keep track of the current section length (changes as 0s are removed)
    int partLength = input.Length / 3;

    // if part length is >8 continuously remove the first char from each part (notice the first char of each part is always 0 mod partLength)
    while (partLength > 8) {
        // because LINQ, the .Where has to be cast to something to actually evaluate within a while, string.Concat is the shortest way (including other changes to have other variables be the correct type)
        input = string.Concat(input.Where((_, i) => i % partLength > 0));
    }

    //if every part starts with 0, remove the first char from each
    //this is golfed into a sum using char codes in the submission ((int)'0' = 48)
    while (input[0] == '0' && input[partLength] == '0' && input[2 * partLength] == '0' && partLength > 2) {
        input = string.Concat(input.Where((_, i) => i % partLength > 0));
    }
    //return the first two chars from each part
    return input.Where((_, i) => i % partLength < 2);
}
\$\endgroup\$
2
  • 1
    \$\begingroup\$ Nice answer! :) You can save a few bytes: -1 byte by adding a variable for s.Length: int l; to int L,l; + s.Length%3>0 to (L=s.Length)%3>0 + s.Length<4? to L<4?. Then -4 bytes by changing "0"+s[0]+"0"+s[1]+"0"+s[2] to "0"+s[0]+0+s[1]+0+s[2]. And you don't have to count the trailing ; of the lambda, so it can move to the footer for another -1. Try it online - 229 bytes \$\endgroup\$ Commented Aug 8, 2022 at 14:41
  • 1
    \$\begingroup\$ It turns out I can actually just use l for s.length instead of a new variable L and save another 2 bytes. \$\endgroup\$
    – Acer
    Commented Aug 11, 2022 at 9:07
1
\$\begingroup\$

C (clang), 273 bytes

int p(char*c){unsigned int l=(strlen(c)+2)/3,i,j,r,g,b,v;for(j=0;j<3;j++){v=0;for(i=0;i<l;i++)v=v<<4|(*c?*c>47&&*c<58?*c++-48:*c>64&&*c<71?*c++-55:*c>96&&*c<103?*c++-87:*c++&0:0);!j?r=v:j==1?g=v:(b=v);}while(r>255||g>255||b>255){r/=16;g/=16;b/=16;}return (r<<16)|(g<<8)|b;}

Try it online!

Ungolfed

int parse(char *c) {
    unsigned int length;
    unsigned int i, j;
    unsigned int r, g, b;
    unsigned int value;

    /* Round up to multiple of 3, then divide by 3 to get the component length */
    length = (strlen(c) + 2) / 3;

    /* For each component (r,g,b) */
    for (j = 0; j < 3; j++) {
        value = 0; /* Start at 0 */

        for (i = 0; i < l; i++) {
            int char_value;

            if (*c != '\0') {
                /* Parse a hex digit */
                if (*c > '0' && *c < '9') {
                    char_value = *c - '0';
                } else if (*c > 'A' && *c <= 'F') {
                    char_value = *c - 'A' + 10;
                } else if (*c >= 'a' && *c <= 'f') {
                    char_value = *c - 'a' + 10;
                } else {
                    /* Invalid digits are 0 */
                    char_value = 0;
                }

                /* Next character */
                *c++;
            } else {
                /* At the end of the string. Pretend string is right padded with '0' */
                char_value = 0;
            }

            /* Now append. If there are more than 8 digits, the leftmost ones will be lost to overflow */
            value = value << 4 | char_value;
        }

        /* Set the correct component value */
        if (j == 0) {
            r = value;
        } else if (j == 1) {
            g = value;
        } else {
            b = value;
        }
    }

    /* Remove the last few digits until all are within the range 0-255 */
    while (r > 255 || g > 255 || b > 255) {
        r /= 16;
        g /= 16;
        b /= 16;
    }

    /* Return the colour as an integer in the form 0x00RRGGBB */
    return (r << 16) | (g << 8) | b;
}

Explanation

This algorithm is different from the one given, so why does it work? Well when I was reading the rant I noticed the part where if the component length was greater than 8 digits, it would remove the leftmost digits until there were 8. The number 8 stuck out to me as 8 hex digits fit neatly inside a 32-bit integer. And the whole removing leading '0' thing also fits with the whole integer idea, as you can't tell a leading '0' from no leading '0' anyway in an integer. Also the steps for lengths <= 3 seemed to be superfluous as they could be explained using just the general case.

Going through each of the steps given:

  1. "Replace chars not matching [0-9a-fA-F] with '0'". This is handled by invalid hex digits being treated as "0". Essentially, just as 'a' and 'A' both equal 10, '0' and 'G' both equal 0.

  2. "Append "0" until the length is divisible by 3". This is handled in two ways. First the length of the string is rounded up to a multiple of 3 (using (length + 2) / 3 * 3), then if the end of the string is encountered, treat it as if it were 0 without incrementing the pointer (Here be UB).

  3. "Split into 3 parts of equal size". This is simply dividing the rounded up length by 3 to get the component length (which simplifies into (length + 2) / 3 since the rounded up length is not used anywhere else) and counting this many characters for each component.

  4. "Remove chars from the front of each part until they are 8 chars each". This is handled with integer overflow; the leading digits get shifted out once 8 characters are parsed.

  5. "Remove chars from the front of each part until one of them starts with something that isn't "0" or until all parts are empty". This is done for free with integers.

  6. "Look at the length of the parts: Are shorter than 2? If so, prepend each part with a "0" until each part is 2 chars long. If not, remove characters from the back of each part until they are 2 chars each". The prepending part is also done for free. The while loop handles the case where the components are longer than 2 characters.

As for the case with 3 or fewer characters: those steps actually turn out to line up with the steps for 4 or more anyway:

  1. "Replace chars not matching [0-9a-fA-F] with "0"". Literally identical.

  2. "Append "0" until the length is 3 chars". For lengths 1-3, this is equivalent to step 2 of the general case. For length of 0 the general step would result in "" rather than "000" at this stage.

  3. "Split into 3 parts, 1 char each". This is also equivalent to step 3 of the general case, although "" would be split into the parts "", "", "", whereas "000" would become "0", "0", "0".

  4. "Prepend each part with "0"". This is essentially equivalent to step 6 of the general case. As the maximum length of a component is 1, step 4 of the general case does not apply. Step 5 would turn "0", "0", "0" into "", "", "" making the two equivalent again. Then, since they are shorter than 2, they would be prepended with a 0. "", "", "" would be prepended with 2 zeros giving us "00", "00", "00", which is what it should be.

Notes

  • This could be how it was implemented in IE. The implementation seems sensible for a hex code parser. That it accepts "long" colours could be future proofing for if 36-bit or 48-bit colour becomes popular (In that case I think the fact that #ABC becomes #0A0B0C rather than something like #AABBCC may have been a bug). That it "pads" may have been some sort of fix. That it accepts invalid characters may just be the nature of their hex parsing implementation.

  • For some reason (TIO) clang does not like it when I do !j?r=v:j==1?g=v:b=v, forcing me to add parentheses to make !j?r=v:j==1?g=v:(b=v). In contrast MSVC didn't care. I am not digging through the spec to find out who is wrong, so I took the 2 byte penalty instead.

\$\endgroup\$
2
  • \$\begingroup\$ Welcome to CGCC! 234 bytes \$\endgroup\$
    – ceilingcat
    Commented Dec 25, 2022 at 6:36
  • \$\begingroup\$ 233 bytes \$\endgroup\$
    – ceilingcat
    Commented Dec 25, 2022 at 6:48
0
\$\begingroup\$

Python 3, 339 337 bytes

def f(s):
 s=re.sub('[g-zG-Z ]','0',s)
 if len(s)<4:s=s.ljust(3,'0');return['0'+h for h in s]
 while len(s)%3:s+='0'
 L=len(s);l=[s[i:i+L//3]for i in range(0,L,L//3)];l=[j[len(j)-8:]for j in l]
 while l[0]and all(k[0]=='0'for k in l):l=[m[1:]for m in l]
 if len(l[0])<2:return[n.rjust(2,'0')for n in l]
 return[o[:2]for o in l]
import re

Try it online!

Commented

def f(s):                      # Create a function, f, which takes in a string, s
 s=re.sub('[g-zG-Z ]','0',s)   # Apply the regex so all non-hex characters are replaced with '0'
 if len(s)<4:                  # If the length is less than 4:
  s=s.ljust(3,'0')             #  Append '0' until the length is 3
  return['0'+h for h in s]     #  Return the elements, prepended by a '0'
 while len(s)%3:               # Until the length is divisible by 3:
  s+='0'                       #  Append '0'
 L=len(s)                      # Assign L to the length of s
 l=[s[i:i+L//3]for i in        # Split s into
    range(0,L,L//3)]           # three equal chunks
 l=[j[len(j)-8:]for j in l]    # Remove characters from the front if the length is more than 8
 while l[0]and all(            # While the strings are not empty
       k[0]=='0'for k in l):   # And all the strings start with '0':
  l=[m[1:]for m in l]          #  Remove the first character from each string
 if len(l[0])<2:               # If the length of the first string is less than 2:
  return[n.rjust(2,'0')        # Return each string,
         for n in l]           # with a '0' prepended until the length is 2
 return[o[:2]for o in l]       # Return the first two characters of each string
import re                      # Import the re module for regex handling
\$\endgroup\$
0
\$\begingroup\$

Go, 359 bytes

import."regexp"
func f(s string)(k string){s=MustCompile(`[^0-9a-fA-F]`).ReplaceAllString(s,"0")
for;len(s)%3>0;s+="0"{}
L:=len(s)/3
S:=[]string{s[:L],s[L:2*L],s[2*L:]}
if len(s)<4{return "0"+S[0]+"0"+S[1]+"0"+S[2]}
for i:=range S{a:=S[i]
for;len(a)>8;a=a[1:]{}
for;a!=""&&a[0]!='0';a=a[1:]{}
for;len(a)<2;a="0"+a{}
for;len(a)>2;a=a[:len(a)-1]{}
k+=a}
return}

Attempt This Online!

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

Japt v2.0a0, 52 bytes

u r/[^1-9A-F]/S
Êc3 ª3
úSV òVz3)®t8n)x2Ãù ®¯2 ù2 rS0

Try it

Outputs as an array of hex values.

Explanation:

u r/[^1-9A-F]/S 
u               # Convert input to uppercase
  r/[^1-9A-F]/S # Replace everything other than 1-9 and A-F with spaces
                # Store as U

Êc3 ª3 
Ê      # The length of U
 c3    # Rounded up to the nearest multiple of 3
    ª3 # If the result is 0, use 3 instead
       # Store as V

úSV òVz3)®t8n)x2Ãù ®¯2 ù2 rS0
úSV                           # Right-pad U with spaces until the length is V
    ò   )                     # Split into substrings with length:
     Vz3                      #  V divided by 3
         ®      Ã             # For each substring:
          t8n)                #  Get the last 8 characters
              x2              #  Trim any leading spaces
                 ù            # Left-pad each substring to the same length
                   ®          # For each padded substring:
                    ¯2        #  Get the first 2 characters
                       ù2     #  Left-pad with spaces to length 2
                          rS0 #  Replace all spaces with "0"
\$\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.