14
\$\begingroup\$

This challenge is inspired by Mathematics is fact. Programming is not.


The mathematical notation for a factorial, or a fact is an exclamation mark !. The exclamation mark is also a common symbol for not in many programming languages.

Challenge:

Take a string, containing numerals, and the characters: + ! as input and output the following:

Everything in front of an exclamation mark should be evaluated as a mathematical expression, so 2+2 would be 4.

Everything after a single exclamation mark should be appended as accessories to whatever is in front of it, so: 2+2!5 should give 45, because 2+2=4, and 5 is an accessory. 2+2!5+5 should give 410.

Since ! also means not, anything that's not an accessory after the fact should not be appended. So, 2+2!!5 should give 4, since 5 is not an accessory. Now, not(not(true))==true, so 2+2!!!5 should give 45. 2+2!!5!5+5 should give: 410, because 2+2=4, then followed by a factorial and !5!5+5. The first 5 is not a fact, but 5+5 is after another exclamation mark, and is therefore a fact, yet again.

Clarifications:

  • The exclamation marks will not be adjacent to a + on either side.
  • There will not be leading + for numbers (it's 5, not +5).
  • You may optionally include a leading zero if that's the result of the expression in front of the first !. Both 4 and 04 are accepted output for input: 0+0!4

Executive summary: evaluate each sum (treating ! as separators). Then discard all numbers that appear after an even number of ! (counting from the start of the string). Then remove all !.

Test cases:

!
   <- Empty string

5
5

12!
12

!87
87

!!5
   <- Empty string

5+5!2+2
104

5+5!!2+2
10

1!2!3!4!5!6!7!8!9
12468

10+10!!2+2!!3+3!4+4
208

2!!3!5
25

2!!3!5!7
25

10!!!!!!!5
105

This is so the shortest code in bytes (in each language) wins! Explanations are strongly encouraged!

\$\endgroup\$
4
  • \$\begingroup\$ umm...lets say we have 2!!3!5 here is or is not 5 an accessory of 3? \$\endgroup\$
    – 0xffcourse
    Feb 17, 2017 at 16:22
  • 3
    \$\begingroup\$ @officialaimm It's 25 (see added test case). More importantly 2!!3!5!7 would still give 25, because there's an even number of ! left of the 7 (so you don't just count the run right in front of the number, but all the ! left of it). \$\endgroup\$ Feb 17, 2017 at 16:27
  • \$\begingroup\$ Can the output be a Mathematica Row? \$\endgroup\$
    – user61980
    Feb 17, 2017 at 17:28
  • \$\begingroup\$ Um... so this challenge actually has nothing to do with factorials? \$\endgroup\$
    – DLosc
    Feb 18, 2017 at 8:37

11 Answers 11

6
\$\begingroup\$

Retina, 35 31 29 bytes

Saved 4 bytes by taking some inspiration from ETHproductions.

Thanks to Leo for saving another 2 bytes.

\d+|\+
$*
1+
$.&
1`!

!\d*!?

Try it online!

\$\endgroup\$
2
  • \$\begingroup\$ You can save some bytes in the last lines like this \$\endgroup\$
    – Leo
    Feb 17, 2017 at 20:14
  • 1
    \$\begingroup\$ @Leo That's really neat, thank you. :) \$\endgroup\$ Feb 17, 2017 at 20:22
5
\$\begingroup\$

JavaScript (ES6), 58 56 bytes

Saved two bytes thanks to Martin Ender.

let f =
x=>x.replace(/[^!]+/g,eval).replace(/!(\d*)!?\d*/g,"$1")
<input value="2+2!5+5" oninput="try{O.value=f(value)}catch(e){}"><br>
<input id=O value="410" disabled>

Might be improved somehow...

\$\endgroup\$
4
  • \$\begingroup\$ Nice use of function argument to replace. \$\endgroup\$
    – Neil
    Feb 17, 2017 at 16:36
  • \$\begingroup\$ @Neil Thanks, but I found a better way :-) \$\endgroup\$ Feb 17, 2017 at 16:37
  • \$\begingroup\$ Your code snippet is giving me the wrong answer on 1+1!5. I think you forgot to eval the bit before the !. \$\endgroup\$
    – Value Ink
    Feb 17, 2017 at 21:08
  • \$\begingroup\$ @ValueInk Ah darn, I don't think there's any easy way to fix that. \$\endgroup\$ Feb 17, 2017 at 21:18
2
\$\begingroup\$

Jelly, 16 bytes

ṣ”!µḢW;m2$VṾ$L¡€

Try it online!

Explanation

The key observation here is that we can run the steps "out of order"; instead of evaluating the sums then ignoring the ones we don't like, we can ignore the sums in invalid positions, then evaluate the rest.

ṣ”!µḢW;m2$VṾ$L¡€
ṣ”!                Split input on '!'
   µ               Set as the new default for missing arguments
    Ḣ              Take the first element, removing it from the default
     W;  $         Cons with
       m2            every odd-numbered element of {the tail of the !-split input}
               €   For each remaining element
          VṾ$      Evaluate and de-evaluate it
             L¡      a number of times equal to its length

Evaluating a sum like "10+10" will evaluate it to a number, e.g. 20, then de-evaluate it to a string, "20". Repeating that process has no additional effect (it's idempotent). Thus, we effectively evaluate every element of the string, except the null string, which remains unevaluated because it has a zero length.

\$\endgroup\$
2
  • \$\begingroup\$ That's a great trick to conditionally evaluate each item. Could you somehow take the logical AND of each item and its eval-deval'd value? (I assume the empty string is falsy in Jelly) \$\endgroup\$ Feb 17, 2017 at 20:36
  • \$\begingroup\$ @ETHproductions: Jelly's tacitness makes it unlikely that that would save bytes; it prefers to avoid using the same value twice if possible, and if you do want to reuse a value, you normally have to at least put an extra µ in somewhere (and µ doesn't work inside a loop, meaning that you'd need something even more verbose). I did manage to make it work, as ṣ”!µḢW;m2$ȧVṾ$$€, but it isn't any shorter (and has the characteristic pile up of dollar signs that tends to happen when you push Jelly to the edge of its ability to nest control structures.) \$\endgroup\$
    – user62131
    Feb 17, 2017 at 20:48
2
\$\begingroup\$

Jelly, 18 bytes

ṣ”!µḊm2;@ḢW$LÐfVṾ€

Try it online!

How?

ṣ”!µḊm2;@ḢW$LÐfVṾ€ - Main link: string
ṣ”!                - split on '!' characters
   µ               - monadic chain separation (call that x)      e.g. ['1+1','0+0','0+0','0+0','','1+0','','','']
    Ḋ              - dequeue x (all but the leftmost entry of x) e.g.       ['0+0','0+0','0+0','','1+0','','','']
     m2            - modulo 2 index into that result             e.g.       ['0+0',      '0+0',   '1+0',   '']
           $       - last two links as a monad
         Ḣ         -     head x (the leftmost entry of x)        e.g.  '1+1'
          W        -     wrap                                    e.g. ['1+1']
       ;@          - concatenate with reversed arguments         e.g. ['1+1','0+0',      '0+0',   '1+0',   '']
             Ðf    - filter keep:
            L      -     length (keep that have non-zero length) e.g. ['1+1','0+0',      '0+0',   '1+0']
               V   - eval as jelly code (vectorises)             e.g. [  2,    0,          0,       1]
                      Yes, addition is just + and decimal numbers are just strings of digits in Jelly believe it or not!
                Ṿ€ - uneval €ach (creates a string from each one)e.g. [ '2',  '0',        '0'     ,'1']
                      without the € it would uneval the list and hence yield commas too)
                   - implicit print (prints the resulting list [of characters and possibly
                      lists of characters] as if it were all one string.)
\$\endgroup\$
3
  • \$\begingroup\$ I don't think this works for literal 0+0 in the middle of the input (in a place where it isn't being discarded); it produces the null string, even though it should produce a digit 0. \$\endgroup\$
    – user62131
    Feb 17, 2017 at 20:01
  • \$\begingroup\$ Ah, true - I will have to move to a longer solution :( \$\endgroup\$ Feb 17, 2017 at 20:05
  • \$\begingroup\$ Should be fixed up (possibly golfable now). \$\endgroup\$ Feb 17, 2017 at 20:31
2
+200
\$\begingroup\$

Python 3, 90 bytes

def f(s,e=1,r=''):
 for x in s.split('!'):
  if x:r+=str(eval(x))*e
  else:e=1-e
 return r

Try it online!

  • -8 thanks to 97.100.97.109
  • -1 thanks to Steffan

Explanation

def f(s,e=1,r=''):

Define a function, f, which takes in a string, s.

e is whether to evaluate the next expression, and r is the return string.

 for x in s.split('!'):

Iterate through the string, split by '!'s.

  if x:r+=str(eval(x))*-e

If x is not empty, and e is True, evaluate x and add to the return string.

  else:e=1-e

Otherwise, set e to not e

 return r

Return the final value of r


Python 3.8+, 74 bytes

lambda s,e=1:''.join(e*str(eval(x))for x in s.split('!')if x or(e:=1-e)*0)

(Suggested by 97.100.91.109)

Attempt this online!

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

CJam, 20 bytes

r'!/{_{'+/1b}&}%(\2%

Try it online! (Linefeed-separated test suite.)

\$\endgroup\$
1
\$\begingroup\$

Ruby, 58 56+1 = 59 57 bytes

Uses the -p flag. -2 bytes from Tutleman.

i=0;$_=' '+$_;gsub(/!?([^!]*)/){eval$1if(2>i+=1)||i%2<1}

Try it online! (An extra line of code was added so that it would take all of the input lines and print the output in different lines.)

\$\endgroup\$
2
  • \$\begingroup\$ I think you can drop the parentheses around eval$1, no? \$\endgroup\$
    – Tutleman
    Feb 18, 2017 at 2:38
  • \$\begingroup\$ @Tutleman huh. I don't know what problem I was having that made me add the parens (they weren't present when I started writing the program), but it seems I really can drop them. \$\endgroup\$
    – Value Ink
    Feb 18, 2017 at 2:43
1
\$\begingroup\$

Pip, 18 bytes

I think this is the shortest it gets... although I said that about three iterations ago too.

{VaX++v%2+!v}Ma^'!

Takes input as command-line argument. Try it online!

Explanation

                    a is 1st cmdline arg; global variable v is -1 (implicit)
              a^'!  Split a on !
{           }M      Map this function to the resulting list (note that inside function,
                    a is the function arg):
    ++v              Increment v (so that v tracks the 0-based index of the current
                     element)
       %2            We want to keep the elements where v%2 is 1...
         +!v         ... and also v=0, where v%2 is 0, but adding !v makes it 1
  aX                 String-multiply the argument by the above quantity (turning elements
                     we don't want into empty string)
 V                   Eval it (eval'ing empty string gives nil, but that's okay because
                     nil doesn't output anything)
                    Autoprint the resulting list, concatenated together (implicit)
\$\endgroup\$
0
\$\begingroup\$

Batch, 192 184 bytes

@echo off
set/ps=
call:c . "%s:!=" "%"
echo(%s%
exit/b
:c
set s=
if not %2=="" set/as=%2
:l
shift
shift
if "%1"=="" exit/b
if %1=="" goto l
set/at=%1
set s=%s%%t%
goto l

Having to handle the empty strings is inconvenient.

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

R, 95 bytes

function(x)for(e in strsplit(gsub('!([^!]*)![^!]*','\\1!',x),'!')[[1]])cat(eval(parse(text=e)))

There is probably some room for improvement but at the moment it is the best I can come up with.

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

Japt -P, 14 bytes

q'! kϩYvãOxX

Try it

You can also Try all test cases, with a slight modification in the footer since the -mR flag that is used to run on multiple test cases conflicts with the -P flag that formats the output in the main version.

q'! kϩYvãOxX 
q'!            # Split the string on "!"
    k    Ã     # Remove elements where:
     Ï         #  The index is non-zero
      ©Yv      #  And the index is divisible by 2
          £    # For each remaining element:
           OxX #  Evaluate it as Javascript
-P             # Concatenate the elements and print
\$\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.