Black Box Trigonometry

29

1

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!

Laikoni

Posted 2018-07-10T17:50:34.407

Reputation: 23 676

1Mathematica 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. – JungHwan Min – 2018-07-10T18:26:59.863

1@JungHwanMin If that means you can access the function names from the symbolic output then I'm afraid it is not allowed. – Laikoni – 2018-07-10T18:39:47.673

Answers

22

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.

orlp

Posted 2018-07-10T17:50:34.407

Reputation: 37 067

2@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. – orlp – 2018-07-10T18:44:26.367

You are correct. My bad, I misread your program (+1) – JungHwan Min – 2018-07-10T18:45:48.563

1Did you bruteforce this? – mbomb007 – 2018-07-10T20:01:50.523

4@mbomb007 If by brute force you mean a loop that does a couple hundred iterations in the blink of an eye, yes. – orlp – 2018-07-10T20:07:38.343

3This is both amazing and silly. – Nit – 2018-07-10T20:56:00.057

How comes this works only in this specific version? Does the hash function change from release to release? – Laikoni – 2018-07-10T20:56:26.870

@Laikoni I don't know if it only works in this specific version - I just listed the specific version I used just in case the hash function does change. – orlp – 2018-07-10T20:57:08.480

1Relevant? – mbomb007 – 2018-07-10T21:00:24.337

@mbomb007 From the docs it doesn't appear that Python randomizes hashes of complex types. I've tested the program multiple times now and it still works.

– orlp – 2018-07-10T21:04:03.400

1Instead of listing every function, is there a way you could use a regex? (e.g. "a?(sin|cos|tan)h?") – OldBunny2800 – 2018-07-10T22:00:09.037

1@OldBunny2800 No, Python has no ability to generate all possible strings that a regex recognizes sadly. – orlp – 2018-07-10T22:14:31.000

@orlp You could filter a list to get the strings like I did. Also, it does have the ability, but it's not a built-in, and it's not likely to be short. – mbomb007 – 2018-07-11T13:40:37.063

@mbomb007 When I said it doesn't have the ability, I meant built-in. It's a turing complete language so it obviously has the ability :P The filtering of globals like you did doesn't work because I need the functions in a specific order matching the hash function. – orlp – 2018-07-11T23:02:29.230

@orlp Right. I was wondering if you could use my earlier filtering method that ended with 13 function names, and find a mapping that ignores isinf. – mbomb007 – 2018-07-12T03:20:36.530

@orlp [x+y+z for x in["","a"]for y in["sin","cos","tan"]for z in["","h"]] is a little shorter than "...".split() but unfortunately the order is different :( – ngn – 2018-07-12T19:18:09.427

6

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.

Sean

Posted 2018-07-10T17:50:34.407

Reputation: 4 136

59 bytes X can be used for multiple terms, and a few other tricks to golf bytes – Jo King – 2018-07-11T01:59:54.873

6

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.

LambdaBeta

Posted 2018-07-10T17:50:34.407

Reputation: 2 499

You can save 20 bytes by replacing puts(...) with printf("%.5s","acoshsinh asinhatanhtan cosh asin sin cos atan tanh acos "+5*(_<0?-_:_)) – Curtis Bechtel – 2018-07-12T00:37:11.410

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. – None – 2018-07-12T17:42:53.050

An additional three bytes can be shed by replacing char*[] with int*[], and by changing the ternary operator (?:) to an abs(_) – None – 2018-07-12T17:54:58.190

6

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)

nthistle

Posted 2018-07-10T17:50:34.407

Reputation: 81

1Great second answer! Would you mind sharing the program you used to find the magic numbers? – mbomb007 – 2018-07-12T20:16:32.087

Thanks! I just added code to find the magic numbers to the answer, although it isn't terribly pretty. – nthistle – 2018-07-13T15:22:33.503

4

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

mbomb007

Posted 2018-07-10T17:50:34.407

Reputation: 21 944

No need for re, just get the ones needed with slicing: lambda f,d=dir(cmath):[s for s in d[4:12]+d[22:]if eval("cmath."+s)(.2)==f(.2)][0] (rewritten to work on TIO as the import must happen before d=dir(cmath) yet F= must be in the header to not be counted).

– Jonathan Allan – 2018-07-10T23:18:44.510

...or better yet lambda f:[s for s in dir(cmath)if s[-1]in'shn'and eval("cmath."+s)(.2)==f(.2)][0]

– Jonathan Allan – 2018-07-10T23:26:56.117

Very nice! Thanks – mbomb007 – 2018-07-11T01:41:01.020

4

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∘○

dzaima

Posted 2018-07-10T17:50:34.407

Reputation: 19 048

3

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

Shaggy

Posted 2018-07-10T17:50:34.407

Reputation: 24 623

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) – l4m2 – 2018-07-13T02:47:24.630

Don't know why I didn't think of that. Thanks, @l4m2. – Shaggy – 2018-07-13T11:47:24.520

@l4m2 We need NaN to compare equal to NaN, so it's either that or Object.is. – Neil – 2018-07-21T17:51:43.543

3

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!

ErikF

Posted 2018-07-10T17:50:34.407

Reputation: 2 149

I managed to remove 14 bytes. In the TIO you can find the details. Try it online!

– Giacomo Garabello – 2018-07-11T13:10:49.557

+1 from me, but I did find a sub 200 solution using a different strategy :) – LambdaBeta – 2018-07-11T19:49:22.060

2

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)));

Nit

Posted 2018-07-10T17:50:34.407

Reputation: 2 667

1Very 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(). – Shaggy – 2018-07-10T22:16:46.213

1I 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. – Neil – 2018-07-11T08:37:21.847

@Shaggy I guess it's implementation-dependent; in Firefox, atan2 precedes acosh in the array returned by Object.getOwnPropertyNames. – Neil – 2018-07-11T09:19:02.740

If anyone was wondering, this solution works because the first non-function from getOwnPropertyNames is Math.E, and all trig functions enumerate before that. – MattH – 2018-07-11T15:07:49.733

@MattH Yeah, I had a check for the type at first, but then I realized I didn't need it. – Nit – 2018-07-11T16:49:18.997

Don't meet something like E? – l4m2 – 2018-07-12T13:56:00.080

@l4m2 Nope, see MattH's comment. – Nit – 2018-07-12T17:00:29.350

2

Wolfram Language (Mathematica), 86 bytes

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

Try it online!

JungHwan Min

Posted 2018-07-10T17:50:34.407

Reputation: 13 290

2

Ruby, 71 67 bytes

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

Try it online!

Asone Tuhid

Posted 2018-07-10T17:50:34.407

Reputation: 1 944

2

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

digEmAll

Posted 2018-07-10T17:50:34.407

Reputation: 4 599

wow. Very nice! I think 1i works as well as -1i for -2 bytes. – Giuseppe – 2018-07-11T18:29:53.273

@Giuseppe: I was sure I had tested it and it wasn't working... but probably it was only my imagination :D – digEmAll – 2018-07-11T19:57:08.553

very Nice! Works on TIO, depends on your config probably in the general case: tio

– JayCe – 2018-07-12T01:49:33.213

@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 :) – digEmAll – 2018-07-12T12:50:06.147

finally...GET is shorter (77)

– JayCe – 2018-07-12T15:52:34.427

@JayCe : save 2 more bytes ! :D – digEmAll – 2018-07-13T07:51:25.317

Sweet! I have never used Find... – JayCe – 2018-07-13T12:16:36.090

1

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.

Jason

Posted 2018-07-10T17:50:34.407

Reputation: 191

In case they are returned as upper-case, that's fine now as I edited the challenge. – Laikoni – 2018-07-10T20:03:09.513

@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) – Jason – 2018-07-10T20:16:47.753

1

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.

Phil H

Posted 2018-07-10T17:50:34.407

Reputation: 1 376

0

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!

l4m2

Posted 2018-07-10T17:50:34.407

Reputation: 5 985