29
\$\begingroup\$

Write a program or function that can distinguish the following 12 trigonometric functions: sin, cos, tan, asin, acos, atan, sinh, cosh, tanh, asinh, acosh, atanh.

Your program is given one of the above functions as black box and should output the function's name either as given above or the way it is named in your language.

This is , so the shortest answer in each language wins. You should show that your code works correctly by including test cases with all 12 possible inputs. If the language of your choice does not include build-ins for all of the above functions, you have to provide your own sensible implementations of the missing ones.

Further Clarifications

  • Using complex numbers to query the black box is allowed if the underlying build-ins can handle them.
  • As \$ dom\ acosh \cap dom\ atanh = \emptyset \$ when using only real numbers, queries to the black box function can give domain errors. In this case you should assume that the black box only communicates the existence of an error, but not from which function it originates.
  • If instead of an error some other value, e.g. NaN or null, is returned, then your submission should be able to handle them.

Thanks for the helpful sandbox feedback!

\$\endgroup\$
  • 1
    \$\begingroup\$ Mathematica can handle symbolic inputs so that the function output is only partially evaluated, if at all. The difference it makes is that I could use some pattern-matching instead of computations. \$\endgroup\$ – JungHwan Min Jul 10 '18 at 18:26
  • 1
    \$\begingroup\$ @JungHwanMin If that means you can access the function names from the symbolic output then I'm afraid it is not allowed. \$\endgroup\$ – Laikoni Jul 10 '18 at 18:39

15 Answers 15

22
\$\begingroup\$

Python 3.6.4 on Linux, 99 bytes

Bit of a silly answer, but:

lambda f:"asinh acos cos cosh atan atanh tan sin asin tanh sinh acosh".split()[hash(f(.029))%19%12]

Requires the trigonometric functions to be one out of the built-in cmath module for complex in/output.

\$\endgroup\$
  • 2
    \$\begingroup\$ @JungHwanMin I believe that you are confused. I most certainly take an actual function. Note that my only reference to input f is f(.029) - calling the function with a value. \$\endgroup\$ – orlp Jul 10 '18 at 18:44
  • 1
    \$\begingroup\$ Did you bruteforce this? \$\endgroup\$ – mbomb007 Jul 10 '18 at 20:01
  • 4
    \$\begingroup\$ @mbomb007 If by brute force you mean a loop that does a couple hundred iterations in the blink of an eye, yes. \$\endgroup\$ – orlp Jul 10 '18 at 20:07
  • 3
    \$\begingroup\$ This is both amazing and silly. \$\endgroup\$ – Nit Jul 10 '18 at 20:56
  • 1
    \$\begingroup\$ Relevant? \$\endgroup\$ – mbomb007 Jul 10 '18 at 21:00
6
\$\begingroup\$

Perl 6, 75 bytes

->&f {([X~] ("","a"),<sin cos tan>,("","h")).min({abs(f(2i)-&::($_)(2i))})}

Try it online!

As it happens, all twelve of the functions to be discriminated amongst are built-in and all take complex arguments.

[X~] ("", "a"), <sin cos tan>, ("", "h") generates all twelve function names by reducing the three input lists with cross-product-concatenation. Given those, .min(...) finds the one which the smallest difference from the input function at 2i.

\$\endgroup\$
  • \$\begingroup\$ 59 bytes X can be used for multiple terms, and a few other tricks to golf bytes \$\endgroup\$ – Jo King Jul 11 '18 at 1:59
6
\$\begingroup\$

C (gcc), 178 172 bytes

double d;_;f(double(*x)(double)){d=x(0.9247);_=*(int*)&d%12;puts((char*[]){"acosh","sinh","asinh","atanh","tan","cosh","asin","sin","cos","atan","tanh","acos"}[_<0?-_:_]);}

Try it online!

Old but cool: C (gcc), 194 bytes

double d;_;f(double(*x)(double)){char n[]="asinhacoshatanh";d=x(0.9247);_=*(int*)&d%12;_=(_<0?-_:_);n[(int[]){10,5,5,0,14,10,4,4,9,14,0,9}[_]]=0;puts(n+(int[]){5,1,0,10,11,6,0,1,6,10,11,5}[_]);}

Try it online!

The -lm switch in TIO is merely to test. If you could write a perfect implementation of the standard trig functions you would get the right answer.

Explanation

The idea was to find some input value such that when I interpret the outputs of each of the trig functions as integers they have different remainders modulo 12. This will allow them to be used as array indices.

In order to find such an input value I wrote the following snippet:

#include <math.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>

// Names of trig functions
char *names[12] = {"sin","cos","tan","asin","acos","atan","sinh","cosh","tanh","asinh","acosh","atanh"};

// Pre-computed values of trig functions
double data[12] = {0};

#define ABS(X) ((X) > 0 ? (X) : -(X))

// Performs the "interpret as abs int and modulo by" operation on x and i
int tmod(double x, int i) {
    return ABS((*(int*)&x)%i);
}

// Tests whether m produces unique divisors of each trig function
// If it does, it returns m, otherwise it returns -1
int test(int m) {
    int i,j;
    int h[12] = {0}; // stores the modulos

    // Load the values
    for (i = 0; i < 12; ++i)
        h[i] = tmod(data[i],m);

    // Check for duplicates
    for (i = 0; i < 12; ++i)
        for (j = 0; j < i; ++j)
            if (h[i] == h[j])
                return -1;

    return m;
}

// Prints a nicely formatted table of results
#define TEST(val,i) printf("Value: %9f\n\tsin      \tcos      \ttan      \n  \t%9f\t%9f\t%9f\na \t%9f\t%9f\t%9f\n h\t%9f\t%9f\t%9f\nah\t%9f\t%9f\t%9f\n\n\tsin      \tcos      \ttan      \n  \t%9d\t%9d\t%9d\na \t%9d\t%9d\t%9d\n h\t%9d\t%9d\t%9d\nah\t%9d\t%9d\t%9d\n\n",\
        val,\
        sin(val), cos(val), tan(val), \
        asin(val), acos(val), atan(val),\
        sinh(val), cosh(val), tanh(val),\
        asinh(val), acosh(val), atanh(val),\
        tmod(sin(val),i), tmod(cos(val),i), tmod(tan(val),i), \
        tmod(asin(val),i), tmod(acos(val),i), tmod(atan(val),i),\
        tmod(sinh(val),i), tmod(cosh(val),i), tmod(tanh(val),i),\
        tmod(asinh(val),i), tmod(acosh(val),i), tmod(atanh(val),i))

// Initializes the data array to the trig functions evaluated at val
void initdata(double val) {
    data[0] = sin(val);
    data[1] = cos(val);
    data[2] = tan(val);
    data[3] = asin(val);
    data[4] = acos(val);
    data[5] = atan(val);
    data[6] = sinh(val);
    data[7] = cosh(val);
    data[8] = tanh(val);
    data[9] = asinh(val);
    data[10] = acosh(val);
    data[11] = atanh(val);
}

int main(int argc, char *argv[]) {
    srand(time(0));

    // Loop until we only get 0->11
    for (;;) {
        // Generate a random double near 1.0 but less than it
        // (experimentally this produced good results)
        double val = 1.0 - ((double)(((rand()%1000)+1)))/10000.0;
        initdata(val);
        int i = 0;
        int m;

        // Find the smallest m that works
        do {
            m = test(++i);
        } while (m < 0 && i < 15);

        // We got there!
        if (m == 12) {
            TEST(val,m);
            break;
        }
    }

    return 0;
}

If you run that (which needs to be compiled with -lm) it will spit out that with a value of 0.9247 you get unique values.

Next I reinterpeted as integers, applied modulo by 12, and took the absolute value. This gave each function an index. They were (from 0 -> 11): acosh, sinh, asinh, atanh, tan, cosh, asin, sin, cos, atan, tanh, acos.

Now I could just index into an array of strings, but the names are very long and very similar, so instead I take them out of slices of a string.

To do this I construct the string "asinhacoshatanh" and two arrays. The first array indicates which character in the string to set to the null terminator, while the second indicates which character in the string should be the first one. These arrays contain: 10,5,5,0,14,10,4,4,9,14,0,9 and 5,1,0,10,11,6,0,1,6,10,11,5 respectively.

Finally it was just a matter of implementing the reinterpretation algorithm efficiently in C. Sadly I had to use the double type, and with exactly 3 uses, it was quicker to just use double three times then to use #define D double\nDDD by just 2 characters. The result is above, a description is below:

double d;_;                                 // declare d as a double and _ as an int
f(double(*x)(double)){                      // f takes a function from double to double
    char n[]="asinhacoshatanh";             // n is the string we will manipulate
    int a[]={10,5,5,0,14,10,4,4,9,14,0,9};  // a is the truncation index
    int b[]={5,1,0,10,11,6,0,1,6,10,11,5};  // b is the start index
    d=x(0.9247);                            // d is the value of x at 0.9247
    _=*(int*)&d%12;                         // _ is the remainder of reinterpreting d as an int and dividing by 12
    _=(_<0?-_:_);                           // make _ non-negative
    n[a[_]]=0;                              // truncate the string
    puts(n+b[_]);}                          // print the string starting from the correct location

Edit: Unfortunately just using a raw array is actually shorter, so the code becomes much simpler. Nonetheless the string slicing was fun. In theory an appropriate argument might actually come up with the right slices on its own with some math.

\$\endgroup\$
  • \$\begingroup\$ You can save 20 bytes by replacing puts(...) with printf("%.5s","acoshsinh asinhatanhtan cosh asin sin cos atan tanh acos "+5*(_<0?-_:_)) \$\endgroup\$ – Curtis Bechtel Jul 12 '18 at 0:37
  • \$\begingroup\$ You can save 5 bytes by compiling with -DD=double and replacing all doubles in your code with D. Note that the flag needs to be counted for total bytes. \$\endgroup\$ – user77406 Jul 12 '18 at 17:42
  • \$\begingroup\$ An additional three bytes can be shed by replacing char*[] with int*[], and by changing the ternary operator (?:) to an abs(_) \$\endgroup\$ – user77406 Jul 12 '18 at 17:54
6
\$\begingroup\$

Python 3.6.5 on Linux, 90 85 bytes

h=hash;lambda f:h(f(.0869))%3%2*"a"+"tscaionns"[h(f(.14864))%3::3]+h(f(.511))%5%2*"h"

This builds upon orlp's answer; but instead of finding 1 magic number, we find 3! This basically just saves bytes by avoiding putting the string literals for "sin", "cos", and "tan" multiple times, instead building the answer one part at a time.

The first magic number is used to determine whether it's one of the "arc" trigonometric functions, prepending an "a" accordingly, the second for whether it's one of the "sin", "cos", or "tan" based functions, selecting the appropriate string, and the third for whether it's one of the hyperbolic functions, appending a "h" accordingly.

Like orlp's answer, it uses the functions from Python's built-in cmath module as input.

Saved 5 bytes by using slice indexing into the middle string

Finding the Magic Numbers

For completeness, here's (more or less) the script I used to find these magic numbers. I mostly just worked straight in a python terminal, so the code is messy, but it gets the job done.

import cmath
fns = [(fn, getattr(cmath, fn)) for fn in ["sin","cos","tan","asin","acos","atan","sinh","cosh","tanh","asinh","acosh","atanh"]]

count_length = lambda num, modulus, base_modulus : len(str(num).rstrip('0').lstrip('0')) + (1 + len(str(modulus)) if modulus != base_modulus else 0)

min_length = float("inf")
min_choice = None
for modulus in range(2,10):
   for i in range(1,100000):
      num = i/100000.
      is_valid = True
      for fn in fns:
         val = hash(fn[1](num))%modulus%2
         if (val == 0 and fn[0][0]=="a") or (val == 1 and fn[0][0]!="a"):
            is_valid = False
      if is_valid:
         length = count_length(num, modulus, 2)
         if length < min_length:
            min_length = length
            min_choice = (modulus,num)
print(min_choice)

min_length = float("inf")
min_choice = None
for modulus in range(3,10):
   for i in range(100000):
      num = i/100000.
      mapping = {}
      is_valid = True
      for fn in fns:
         fn_type = "sin" if "sin" in fn[0] else "cos" if "cos" in fn[0] else "tan"
         val = hash(fn[1](num))%modulus%3
         if val in mapping and mapping[val] != fn_type:
            is_valid = False
            break
         mapping[val] = fn_type
      if is_valid:
         length = count_length(num, modulus, 3)
         if length < min_length:
            min_length = length
            min_choice = (modulus, num, mapping)
print(min_choice)

min_length = float("inf")
min_choice = None
for modulus in range(2,10):
   for i in range(1,100000):
      num = i/100000.
      is_valid = True
      for fn in fns:
         val = hash(fn[1](num))%modulus%2
         if (val == 0 and fn[0][-1]=="a") or (val == 1 and fn[0][-1]!="a"):
            is_valid = False
      if is_valid:
         length = count_length(num, modulus, 2)
         if length < min_length:
            min_length = length
            min_choice = (modulus,num)
print(min_choice)
\$\endgroup\$
  • 1
    \$\begingroup\$ Great second answer! Would you mind sharing the program you used to find the magic numbers? \$\endgroup\$ – mbomb007 Jul 12 '18 at 20:16
  • \$\begingroup\$ Thanks! I just added code to find the magic numbers to the answer, although it isn't terribly pretty. \$\endgroup\$ – nthistle Jul 13 '18 at 15:22
4
\$\begingroup\$

Python, 108 94 90 bytes

Compares the result of the input function to the results of all of the functions for the value .2.

from cmath import*
lambda f:[w for w in globals()if w[-1]in'shn'and eval(w)(.2)==f(.2)][0]

Try it online

-14 bytes by Jonathan Allen
-4 bytes by Rod

\$\endgroup\$
4
\$\begingroup\$

Dyalog APL, 25 21 19 bytes

(8-(2○⍨8-⍳15)⍳⎕2)∘○

Try it online!

-3 thanks to H.PWiz
-2 thanks to ngn

Goes trough all the required trig functions (which in APL are 1 2 3 5 6 7 ¯1 ¯2 ¯3 ¯5 ¯6 ¯7○2) plus some more things (this goes trough -7..7), finds which one matches input○2, and outputs that "with" , which outputs as num∘○

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

C (gcc) with -lm, 374 346 324 bytes

Thanks to Giacomo Garabello for the suggestions.

I was able to save a bit more space by having a helper macro do token-pasting in addition to my original macro which does stringizing.

In the tests, I used a couple of non-library trig functions to confirm the validity of the results. As the results between the library and non-library functions weren't exactly the same floating-point value, I compared the difference of the results against a small value ε instead of using equality.

#include <math.h>
#define q(f)f,#f,
#define _(f,g)q(f##sin##g)q(f##cos##g)q(f##tan##g)
#define p for(i=0;i<24;i+=2)
typedef double(*z)(double);*y[]={_(,)_(a,)_(,h)_(a,h)};i,x;*f(z g){int j[24]={0};char*c;double w;for(x=0;x++<9;)p!j[i]&isnan(w=((z)y[i])(x))-isnan(g(x))|fabs(w-g(x))>1E-9?j[i]=1:0;p!j[i]?c=y[i+1]:0;return c;}

Try it online!

\$\endgroup\$
  • \$\begingroup\$ I managed to remove 14 bytes. In the TIO you can find the details. Try it online! \$\endgroup\$ – Giacomo Garabello Jul 11 '18 at 13:10
  • \$\begingroup\$ +1 from me, but I did find a sub 200 solution using a different strategy :) \$\endgroup\$ – LambdaBeta Jul 11 '18 at 19:49
3
\$\begingroup\$

JavaScript, 76 67 66 bytes

Not pretty but I went far too far down the rabbit hole with this over a few beers to not post it. Derived independently from Nit's solution.

b=>Object.getOwnPropertyNames(M=Math).find(x=>M[x](.8)+M==b(.8)+M)

Try it online

  • Saved 6 bytes thanks to Neil
  • Saved 1 bye thanks to l4m2
\$\endgroup\$
  • \$\begingroup\$ b=>Object.getOwnPropertyNames(M=Math).find(x=>M[x](.8)+M==b(.8)+M)? (though I don't quite know why convert to String to compare) \$\endgroup\$ – l4m2 Jul 13 '18 at 2:47
  • \$\begingroup\$ Don't know why I didn't think of that. Thanks, @l4m2. \$\endgroup\$ – Shaggy Jul 13 '18 at 11:47
  • \$\begingroup\$ @l4m2 We need NaN to compare equal to NaN, so it's either that or Object.is. \$\endgroup\$ – Neil Jul 21 '18 at 17:51
2
\$\begingroup\$

Wolfram Language (Mathematica), 86 bytes

#&@@Nearest[a=Join[a={Sin,Cos,Tan,Sinh,Cosh,Tanh},InverseFunction/@a];#@2&/@a->a,#@2]&

Try it online!

\$\endgroup\$
2
\$\begingroup\$

Ruby, 71 67 bytes

->g{Math.methods.find{|h|g[0.5]==Math.send(h,0.5)rescue p}||:acosh}

Try it online!

\$\endgroup\$
2
\$\begingroup\$

JavaScript, 108 70 bytes

I haven't tried golfing in pure Javascript in ages, so I'm sure there's stuff to improve here.

t=>Object.getOwnPropertyNames(m=Math).find(f=>m[f](.9,0)+''==t(.9)+'')

Pretty straightforward, checks every function on the Math prototype against an arbitrary value (0.9, many other values probably work) and compares with the result of the black box function.
Tested in Google Chrome, will break if the input black box function is not one of the trigs.

Cut off a ton of bytes thanks to Shaggy and Neil.

const answer = t=>Object.getOwnPropertyNames(m=Math).find(f=>m[f](.9,0)+''==t(.9)+'');
const tests = [Math.sin, Math.cos, Math.tan, Math.asin, Math.acos, Math.atan, Math.sinh, Math.cosh, Math.tanh, Math.asinh, Math.acosh, Math.atanh];

tests.forEach(test => console.log(test + ' yields ' + answer(test)));

\$\endgroup\$
  • 1
    \$\begingroup\$ Very similar to the solution I was working on over a few beers but couldn't quite figure out. 2 quick savings I can spot: 0.3 -> .3 and assign Math to m within getOwnPropertyNames(). \$\endgroup\$ – Shaggy Jul 10 '18 at 22:16
  • 1
    \$\begingroup\$ I managed to get this down to 71 bytes: t=>Object.getOwnPropertyNames(m=Math).find(f=>m[f](.9,0)+''==t(.9)+'');. I noticed @Shaggy used find as well. The +'' does a string compare, meaning we only have to check one point. The ,0 makes us skip Math.atan2. \$\endgroup\$ – Neil Jul 11 '18 at 8:37
  • \$\begingroup\$ @Neil, it doesn't looks like the ,0 is needed: tio.run/##Lc6xDoMgEMbxvU/RMEFq2TvgG1jdjYknomLkzghp7dPTqEz/… \$\endgroup\$ – Shaggy Jul 11 '18 at 8:41
  • \$\begingroup\$ @Shaggy I guess it's implementation-dependent; in Firefox, atan2 precedes acosh in the array returned by Object.getOwnPropertyNames. \$\endgroup\$ – Neil Jul 11 '18 at 9:19
  • \$\begingroup\$ If anyone was wondering, this solution works because the first non-function from getOwnPropertyNames is Math.E, and all trig functions enumerate before that. \$\endgroup\$ – MattH Jul 11 '18 at 15:07
2
\$\begingroup\$

R, 75 bytes

function(b)Find(function(x)get(x)(1i)==b(1i),apropos('(sin|cos|tan)(h|$)'))

Try it online!

For the moment (R v3.5) it works.
If in a future R version it will be added a function matching this regex, then who knows :P

  • -2 bytes thanks to @Giuseppe
  • -9 bytes thanks to @JayCe
  • -2 bytes using Find instead of for
\$\endgroup\$
  • \$\begingroup\$ wow. Very nice! I think 1i works as well as -1i for -2 bytes. \$\endgroup\$ – Giuseppe Jul 11 '18 at 18:29
  • \$\begingroup\$ @Giuseppe: I was sure I had tested it and it wasn't working... but probably it was only my imagination :D \$\endgroup\$ – digEmAll Jul 11 '18 at 19:57
  • \$\begingroup\$ very Nice! Works on TIO, depends on your config probably in the general case: tio \$\endgroup\$ – JayCe Jul 12 '18 at 1:49
  • \$\begingroup\$ @JayCe: getting the environment through position is risky...for example it doesn't work in RStudio...fortunately I found another function searching for the objects everywhere, with the same bytecount :) \$\endgroup\$ – digEmAll Jul 12 '18 at 12:50
  • \$\begingroup\$ finally...GET is shorter (77) \$\endgroup\$ – JayCe Jul 12 '18 at 15:52
1
\$\begingroup\$

HP 49G RPL, 88.0 bytes excluding 10 byte program header

Another solution using complex numbers! Enter and execute it in COMPLEX, APPROX mode. Takes the function on the stack.

2. SWAP EVAL { SIN COS TAN ASIN ACOS ATAN SINH COSH TANH ASINH ACOSH ATANH }
DUP 1. << 2. SWAP EVAL >> DOLIST ROT - ABS 0. POS GET

(the newlines don't matter)

For the constant 2.0, all twelve trig functions are defined in the complex plane, so we just evaluate all twelve and see which one matches. This time, the iterative solution is longer (111.5 bytes) because of the stack shuffling needed to get it. RPL, as far as I know, doesn't let you break out of a loop early.

\$\endgroup\$
  • \$\begingroup\$ In case they are returned as upper-case, that's fine now as I edited the challenge. \$\endgroup\$ – Laikoni Jul 10 '18 at 20:03
  • \$\begingroup\$ @JungHwanMin They are upper-case. Thanks for the catch! It can be modified to lower-case with ->STR DUP SIZE 3 - " " " " IFTE XOR, 34.5 bytes. (those are supposed to be 4 and 3 spaces, respectively) \$\endgroup\$ – Jason Jul 10 '18 at 20:16
1
\$\begingroup\$

Perl 6, 39 bytes

{i.^methods.first({try $^a.(i)==.(i)})}

Try it online!

By the looks of things, one of the few to use introspection. i here is the complex number, whose value is unique for each trig function, so by iterating through all the methods we can find the matching method and implicitly spit out its name. The try is needed as some (unwanted) methods have the a different signature.

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

JavaScript (Node.js), 72 bytes

f=>eval('for(s=i=0;!s||s(.8)+s!=f(.8)+s;i++)s=Math[t=i.toString(30)];t')

Try it online!

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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