There are 5 different "special characters": % & # $ !

These special characters are in a string with numbers. Like so: "5 ! 60%2$5.3" (valid). You must determine the validity of the string and there are certain rules in order to determine validity of the string.


  • These special characters can exist in a string, however they cannot be next to each other (ignoring spaces). For example:

    • "!%" is invalid
    • "% %" is invalid
    • "# % $" is invalid
  • The only exception to the rule above is with the exclamation mark. Only one exclamation mark may be after another special character. These assume there are numbers before and after the special characters. For example:

    • "!!" is valid
    • "!!!" is invalid
    • "$!" is valid
    • "$!!" is invalid
    • "!$" is invalid
  • If a special character is at the end of the string, there MUST be a number after it (ignoring spaces). If a special character is at the start of the string, there MUST be a number before it. For example:

    • "5!" is invalid
    • "5!5" is valid
    • "5#!5" is valid
    • "%5" is invalid
  • The only exception to the rule above is with the exclamation mark. There does not need to be a number before the exclamation mark assuming there is a number after it. For example:

    • "!5" is valid
    • ”!!5" is invalid
    • "5!!5” is valid
  • If there is a string with only numbers, that string is valid, if there is a space between the space is invalid. For example:

    • "2 5" is invalid
    • "25" is valid
  • If a string only contains operators it is automatically invalid. For example:

    • "!#%" is invalid
    • "#" is invalid

Extra Examples of Strings that are VALID

  • "5%!5"
  • "!5 !5 %!5"
  • "5.4 $ 2.9 !3.4 &! 89.8213"

Extra Examples of Strings that are INVALID

  • "5%%5" (reason: two "%%")
  • "!5!!!!!!!!%%%5" (reason: way too many operators next to each other)
  • "!5 !5 !" (reason: no number after the last "!")
  • "2.0%" (reason: no number after "%")
  • "!6.9#" (reason: no number after "#")
  • "$6.9&" (reason: no number after "#" AND no number before "$")

A number is a run of 0-9 digits with a possible . to indicate decimals.

You can assume you will only get a string with either numbers and/or these special characters inside. You can also assume the numbers will not have floating zeros like 9.000 or 0004 or 00.00. Treat a single zero as any other number, so for example 0$0 is valid. You can assume you won't receive an empty string.

Game winning criteria objective: code-golf

(If you want a more difficult challenge try using python3 without using eval or exec.) This was originally planned.

  • 3
    \$\begingroup\$ You need to specify what a number is. (I guess it is a run of 0-9 digits, possibly containing a single .. But will the numbers have leading zeros? If so, should it be considered valid?) Also, any challenge needs an objective winning criterion; I recommend code-golf unless you have a good reason to use another scoring method. Other than that, I think it is a good first challenge. \$\endgroup\$ – Bubbler Mar 10 at 2:16
  • 3
    \$\begingroup\$ "If a special character is at the end of the string, there MUST be a number after it", doesn't this mean a special character cannot be at the end of a string? Is there a difference? \$\endgroup\$ – Ad Hoc Garf Hunter Mar 10 at 3:22
  • 1
    \$\begingroup\$ For the first couple of sets of examples, I assume you don't mean that the string !! by itself is valid, but rather that those strings may appear in the input \$\endgroup\$ – Jo King Mar 10 at 3:55
  • 1
    \$\begingroup\$ How about .5 or 5.? No floating zeros, but what to do with floating decimal points? \$\endgroup\$ – Shieru Asakoto Mar 10 at 5:20
  • 2
    \$\begingroup\$ What about !!8? Valid or not? \$\endgroup\$ – Xcali Mar 10 at 6:00

Raku, 41 bytes


Try it online!

Checks if the input matches against a regex.


{?/^                                 $/}  # Check if the input is
    [                     ]+  # A series of
        \!?                   # An optional ! followed by
              \d+\.?\d*       # A number
     \s*   \s*         \s*    # Interspersed with optional whitespace
                            %           # Joined by
                             <[    ]>   # Any of
                               %&#!     # The special characters
|improve this answer|||||
  • \$\begingroup\$ Awesome, this is impressive. Looks like you're the winner so far \$\endgroup\$ – Freelance Video and Photo Edit Mar 10 at 11:04
  • \$\begingroup\$ If you've got time you can try with python 3 without using eval or exec . That's what I had to do so it'll be interesting to see what another comes up with \$\endgroup\$ – Freelance Video and Photo Edit Mar 10 at 11:20

05AB1E, 46 45  44  43 bytes

Crossed out &nbsp;44&nbsp; is no longer 44 :)


-2 bytes thanks to @ExpiredData.

Outputs 1 for truthy and 0 or a positive integer for falsey (NOTE: only 1 is truthy in 05AB1E, so this is completely fine with the truthy/falsey definition of the meta).

Try it online or verify all test cases.


Unfortunately 05AB1E doesn't have any regexes. So instead I'll:

Convert the input to something that's easier to work with:

þ             # Only leave the digits of the (implicit) input-string
 '.ª         '# Convert the number to a list of digits, and append a "." to this list
žQ            # Push a constant with all printable ASCII characters (range [32,126])
  7£          # Only leave the first 7: ' !"#$%&'
    `         # Push them all separated to the stack
     )        # Wrap everything on the stack into a list
      5L      # Push a list [1,2,3,4,5]
        .Þ    # Extend the trailing item infinitely: [1,2,3,4,5,5,5,...]
          :   # Replace all characters in the (implicit) input-string with these
              # We now have a number consisting only of the digits [1,2,3,5],
              # where 1 is a digit or "."; 2 are spaces; 3 is "!"; and 4 is one of "%&#$"
              #  i.e. "5.4 $ 2.9 !3.4 &! 89.8213" → 1112521112311125321111111

After which it's easier to check whether it complies to all the rules:

Rule 5: check that there are no two numbers with spaces in between:

D             # Duplicate the number
 Ô            # Connected uniquify them (i.e. 1112521112311125321111111 → 1252123125321)
  ƵK          # Push compressed integer 121
    å≠        # And check that the number does NOT contains 121 as substring

Remove all spaces. And then rules 3 and 4: check that the input ends with a digit, and starts with a "!" or digit:

s             # Swap to get the number at the top again
 2K           # Remove all 2s (the spaces)
   ¤          # Push the last digit (without popping the number itself)
    s         # Swap again
     ¬        # And push the first digit (without popping the number itself)
      4%      # And take modulo 4, so 1 remains 1, and 5 becomes 1

Rules 1 and 2: check that it does not contain "!S"/"!!"/"!!!"/S!! (where S is one of the special characters "%&#$"):

s             # Swap to get the number at the top again
 •8мZ_r•      # Push compressed integer 35055333533
        ₄в    # Convert it to base-1000 as list: [35,55,333,533]
          å≠  # Check for each that the number does NOT contain it as substring
            ` # And push all four checks separated to the stack

And finally check if all were truthy:

P             # Then take the product of all values on the stack
              # (after which this is output implicitly as result)

See this 05AB1E tip of mine (sections How to compress large integers? and How to compress integer lists?) to understand why ƵK is 121; •8мZ_r• is 35055333533; and •8мZ_r•₄в is [35,55,333,533].

|improve this answer|||||
  • \$\begingroup\$ Nice detail and effort. Works. Very nice detailed explanation \$\endgroup\$ – Freelance Video and Photo Edit Mar 10 at 11:07
  • \$\begingroup\$ @ExpiredData Thanks for the -1 with ₆<38Ÿç (will modify in a bit). As for your þ'.ªžQ7∍S`)5L.Þ:, its certainly a smart -2, but unfortunately the •n¨w!•Ƶêв will then increase to •1δ!»K•534в, neutralizing that -2. :( \$\endgroup\$ – Kevin Cruijssen Mar 10 at 12:57
  • 1
    \$\begingroup\$ @ExpiredData If I see correctly the S is removed, right? In which case it would be the same byte-count as right now, after your earlier suggested ₆<38Ÿç already saved a byte. But, I think the •1δ!»K•534в can be shortened to •8мZ_r•₄в, so then it would still save bytes, so thanks again! :) \$\endgroup\$ – Kevin Cruijssen Mar 10 at 13:15

Perl 5 -p, 72 bytes


Try it online!

|improve this answer|||||

Python 3, 103 bytes

A function that does nothing if the string is valid, and raises SyntaxError if the string is invalid.

import re
def f(s):exec(r("[%&#$!]","+",s)+";1or["+r(" ","",r("!","*",r("[%&#$]",",",s)))+"]")

Try it online!


The idea is to exploit Python eval or exec, which can check if a string is a syntactically valid Python expression. This approach is longer than simply using regex to check, but I'll still post it since it's rather interesting.

Given a string, I want to transform it in a way such that only valid strings map to syntactically valid Python expressions. Specifically, I want to keep the numbers unchanged, and map the 5 special characters to Python operators.

Unfortunately, I wasn't able to find a simple mapping that works. Instead, I have to use 2 different transformations:

1) Replace all special characters with +.
For example !1.2 $ 3.4 &! 56.78 becomes +1.2 + 3.4 ++ 56.78.
If the result is valid Python, then the original string satisfies the last 2 rules (there must be at least 1 number, at least 1 operator between 2 numbers, and cannot end with an operator). Note that since + is both binary and unary, chained sequence of + is still valid Python.

2) Remove all spaces, map ! to *, map all other special characters to ,, and enclose everything within a pair of [] brackets.
For example !1.2 &! 3.4 !! 56.78 becomes [*1.2,*3.4**56.78] which is a list construction expression.
This way:

  • n ! n (where n is a number) becomes n*n, a valid product expression.
  • n # n becomes n,n, aka valid list elements.
  • ! n becomes *n, which is valid Python since * is interpreted as splat/unpack operator during list creation.
  • n #! n becomes n,*n, again valid list elements (where the later element is a splat expression).
  • n !! n becomes n**n, which is just the valid power operator.
  • n ## n becomes n,,n which is invalid.
  • n !# n becomes n*,n which is invalid.
  • n #!! n becomes n,**n which is invalid.
  • n !!! n becomes n***n which is invalid.

This satisfies the first 4 rules. Note that although a valid string will produce syntactically valid Python expression, trying to exec or eval the resulting expression will potentially raises TypeError, since the splat operator can only applied to container types, not numeric types (e.g *5 is syntactically valid, but if evaluated will raise TypeError). To get around this problem, I prepended 1 or to the resulting list expression, aka 1 or [...]. This way, the list construction will not be evaluated due to boolean short-circuiting.

|improve this answer|||||
  • \$\begingroup\$ Wow this is interesting, I was focusing on python too so good job. Nice detailed explanation. How do you think it would be without using eval or exec? That was my trouble \$\endgroup\$ – Freelance Video and Photo Edit Mar 10 at 11:17

JavaScript (Node.js), 59 bytes

s=>/^!?\d+(\.\d+)?( *([%$#&!] *!?) *\d+(\.\d+)?)*$/.test(s)

Try it online!

This should be valid for all cases above. Assuming that floating decimal point won't occur.

|improve this answer|||||
  • \$\begingroup\$ @Bubbler Fixed. \$\endgroup\$ – Shieru Asakoto Mar 10 at 7:14

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