I was doing some investigation into trig functions using compound angles recently, and noticed that the results are really long and tedious to write: $$ \cos(A+B) = \cos A \cos B - \sin A \sin B \\ \cos(A-B) = \cos A \cos B + \sin A \sin B \\ \sin(A+B) = \sin A \cos B + \sin B \cos A \\ \sin(A-B) = \sin A \cos B - \sin B \cos A \\ $$ $$ \tan(A+B) = \frac{\tan A + \tan B}{1 - \tan A \tan B} $$ $$ \tan(A-B) = \frac{\tan A - \tan B}{1 + \tan A \tan B} $$

Realising this, I devised a shorter, golfier way, of writing such expressions:


Where direction is either « or » and operator is in the string +-*/

Also, when there is more variables than functions, the variables are, in a sense, left shifted through the functions, with unused variables simply being concatenated to the expression:


Turns into:

$$ f(x)g(y)z - f(y)g(z)x - f(z)g(x)y $$

Note that the variables x, y and z are left shifting through the functions

Note that when there is only one function given, it is applied as if the expression was an algebraic expansion. For example:


Turns into

$$ cos(a) + cos(b) $$

For an expression like «+[f, g, h](foo, bar, baz), each function in the function list (f, g, h) is applied to every variable, as if the expression was being algebraically expanded:


These expressions are then concatenated with the provided operator (when the operator is *, the expressions are simply concatenated without any symbols):

f(foo)f(bar)f(baz) + g(foo)g(bar)g(baz) + h(foo)h(bar)h(baz)

For an expression like »+[f, g, h](foo, bar, baz), each function in the function list (f, g, h) is applied to the variables in sequence, as if the variables were "shifting" through the functions:


These expressions are once again concatenated using the above method:

f(foo)g(bar)h(baz) + f(bar)g(baz)h(foo) + f(baz)g(foo)h(bar)

Some Clarifications

When the direction is » and there are more variables than functions , the following method is applied:

FOR j = 0 TO length of functions
 FOR i = 0 TO length of variables
  IF i >= length of functions - 1 THEN
   Result += variables[i]
   Result += Functions[i] + "(" + variables[i] + ")"
 Left shift variables
 Result += operator

Given the expression »-[f,g](x,y,z), this would turn into:

f(x)g(y)z - f(y)g(z)x - f(z)g(x)y

Empty function lists should return an empty expression, as should empty variable lists.

When there are more functions than variables and the direction is », simply ignore the functions in the functions list whose index is greater than the length of the variables list. For example, given »+[f,g,h](x,y):

f(x)g(y) + f(y)g(x)

The Challenge

Given a shorthanded string as described above, output the expanded expression

Test Cases

Input -> Output
«+[cos,sin](a,b) -> cos(a)cos(b) + sin(a)sin(b)
»+[cos,sin](a,b) -> cos(a)sin(b) + cos(b)sin(a)
«[f](x,y,z,n) -> f(x)f(y)f(z)f(n)
«+[f](x,y,z,n) -> f(x) + f(y) + f(z) + f(n)
«+[f,g]() -> 
»*[f,g]() ->
»-[f,g](a,b) -> f(a)g(b) - f(b)g(a)
»[g,f](a,b) -> g(a)f(b)g(b)f(a)
«+[tan](a,b) -> tan(a) + tan(b)
»+[](a,b) -> 
»-[f,g](x,y,z) -> f(x)g(y)z - f(y)g(z)x - f(z)g(x)y
«/[f,g](x,y,z) -> f(x)f(y)f(z) / g(x)g(y)g(z)
«[]() ->
»[]() -> 
»+[x,y](foo,bar,baz) -> x(foo)y(bar)baz + x(bar)y(baz)foo + x(baz)y(foo)bar
»+[f,g](x) -> f(x) + g(x)
«+[f,g](x) -> f(x) + g(x)
»+[f,g,h](x,y) -> f(x)g(y) + f(y)g(x)

Let it be known that:

  • The direction will always be given
  • The operator is optional
  • The [] denoting the functions will always be present, but will not always contain functions
  • The () denoting the variables will always be present, but will not always contain variables
  • Functions can be outputted in any order just as long as the expressions are in order (cosAsinB - sinAsinB can be written as sinBcosA - sinBsinA but not as sinAsinB - cosAsinB)
  • Variable names can be of any length
  • The « and » can be taken as < and > if wanted (« and » are preferred however)

Therefore, if no functions/variables are present, then an empty output is required. Whitespacing within the answer doesn't matter (i.e. newlines/extra spaces don't invalidate outputs).

Input can also be taken as:

[direction, operator, [functions], [variables]]

In which case, direction will be a string, operator will be either an empty string or an operator, functions will be a (sometimes empty) list of strings, as will variables.


This is code golf, so fewest bytes wins. Standard loopholes are forbidden.


Here is a Stack Snippet to generate both a regular leaderboard and an overview of winners by language.

To make sure that your answer shows up, please start your answer with a headline, using the following Markdown template:

# Language Name, N bytes

where N is the size of your submission. If you improve your score, you can keep old scores in the headline, by striking them through. For instance:

# Ruby, <s>104</s> <s>101</s> 96 bytes

If there you want to include multiple numbers in your header (e.g. because your score is the sum of two files or you want to list interpreter flag penalties separately), make sure that the actual score is the last number in the header:

# Perl, 43 + 2 (-p flag) = 45 bytes

You can also make the language name a link which will then show up in the leaderboard snippet:

# [><>](http://esolangs.org/wiki/Fish), 121 bytes

var QUESTION_ID=194719;
var ANSWER_FILTER="!t)IWYnsLAZle2tQ3KqrVveCRJfxcRLe",COMMENT_FILTER="!)Q2B_A2kjfAiU78X(md6BoYk",answers=[],answers_hash,answer_ids,answer_page=1,more_answers=!0,comment_page;function answersUrl(d){return"https://api.stackexchange.com/2.2/questions/"+QUESTION_ID+"/answers?page="+d+"&pagesize=100&order=desc&sort=creation&site=codegolf&filter="+ANSWER_FILTER}function commentUrl(d,e){return"https://api.stackexchange.com/2.2/answers/"+e.join(";")+"/comments?page="+d+"&pagesize=100&order=desc&sort=creation&site=codegolf&filter="+COMMENT_FILTER}function getAnswers(){jQuery.ajax({url:answersUrl(answer_page++),method:"get",dataType:"jsonp",crossDomain:!0,success:function(d){answers.push.apply(answers,d.items),answers_hash=[],answer_ids=[],d.items.forEach(function(e){e.comments=[];var f=+e.share_link.match(/\d+/);answer_ids.push(f),answers_hash[f]=e}),d.has_more||(more_answers=!1),comment_page=1,getComments()}})}function getComments(){jQuery.ajax({url:commentUrl(comment_page++,answer_ids),method:"get",dataType:"jsonp",crossDomain:!0,success:function(d){d.items.forEach(function(e){e.owner.user_id===OVERRIDE_USER&&answers_hash[e.post_id].comments.push(e)}),d.has_more?getComments():more_answers?getAnswers():process()}})}getAnswers();var SCORE_REG=function(){var d=String.raw`h\d`,e=String.raw`\-?\d+\.?\d*`,f=String.raw`[^\n<>]*`,g=String.raw`<s>${f}</s>|<strike>${f}</strike>|<del>${f}</del>`,h=String.raw`[^\n\d<>]*`,j=String.raw`<[^\n<>]+>`;return new RegExp(String.raw`<${d}>`+String.raw`\s*([^\n,]*[^\s,]),.*?`+String.raw`(${e})`+String.raw`(?=`+String.raw`${h}`+String.raw`(?:(?:${g}|${j})${h})*`+String.raw`</${d}>`+String.raw`)`)}(),OVERRIDE_REG=/^Override\s*header:\s*/i;function getAuthorName(d){return d.owner.display_name}function process(){var d=[];answers.forEach(function(n){var o=n.body;n.comments.forEach(function(q){OVERRIDE_REG.test(q.body)&&(o="<h1>"+q.body.replace(OVERRIDE_REG,"")+"</h1>")});var p=o.match(SCORE_REG);p&&d.push({user:getAuthorName(n),size:+p[2],language:p[1],link:n.share_link})}),d.sort(function(n,o){var p=n.size,q=o.size;return p-q});var e={},f=1,g=null,h=1;d.forEach(function(n){n.size!=g&&(h=f),g=n.size,++f;var o=jQuery("#answer-template").html();o=o.replace("{{PLACE}}",h+".").replace("{{NAME}}",n.user).replace("{{LANGUAGE}}",n.language).replace("{{SIZE}}",n.size).replace("{{LINK}}",n.link),o=jQuery(o),jQuery("#answers").append(o);var p=n.language;p=jQuery("<i>"+n.language+"</i>").text().toLowerCase(),e[p]=e[p]||{lang:n.language,user:n.user,size:n.size,link:n.link,uniq:p}});var j=[];for(var k in e)e.hasOwnProperty(k)&&j.push(e[k]);j.sort(function(n,o){return n.uniq>o.uniq?1:n.uniq<o.uniq?-1:0});for(var l=0;l<j.length;++l){var m=jQuery("#language-template").html(),k=j[l];m=m.replace("{{LANGUAGE}}",k.lang).replace("{{NAME}}",k.user).replace("{{SIZE}}",k.size).replace("{{LINK}}",k.link),m=jQuery(m),jQuery("#languages").append(m)}}
body{text-align:left!important}#answer-list{padding:10px;float:left}#language-list{padding:10px;float:left}table thead{font-weight:700}table td{padding:5px}
 <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <link rel="stylesheet" type="text/css" href="https://cdn.sstatic.net/Sites/codegolf/primary.css?v=f52df912b654"> <div id="language-list"> <h2>Winners by Language</h2> <table class="language-list"> <thead> <tr><td>Language</td><td>User</td><td>Score</td></tr></thead> <tbody id="languages"> </tbody> </table> </div><div id="answer-list"> <h2>Leaderboard</h2> <table class="answer-list"> <thead> <tr><td></td><td>Author</td><td>Language</td><td>Size</td></tr></thead> <tbody id="answers"> </tbody> </table> </div><table style="display: none"> <tbody id="answer-template"> <tr><td>{{PLACE}}</td><td>{{NAME}}</td><td>{{LANGUAGE}}</td><td><a href="{{LINK}}">{{SIZE}}</a></td></tr></tbody> </table> <table style="display: none"> <tbody id="language-template"> <tr><td>{{LANGUAGE}}</td><td>{{NAME}}</td><td><a href="{{LINK}}">{{SIZE}}</a></td></tr></tbody> </table> 

  • 2
    \$\begingroup\$ I know the spec says that the output should be empty if no functions are given, but »+[](a,b) really could be ab+ba as per the more variables than functions rule. \$\endgroup\$
    – Arnauld
    Oct 23, 2019 at 0:31
  • 1
    \$\begingroup\$ Can we take the inputs in a different order or more convenient format (i.e. operator, direction, [[functions], [variables]])? Or is either "operatorDirection[function](variables)" as string or [direction,operator,[functions],[variables]] as list or loose parameters in this exact order and format mandatory? \$\endgroup\$ Oct 23, 2019 at 7:26
  • 1
    \$\begingroup\$ Also, could you add some test cases with 3 or more functions? Or is the amount of functions guaranteed to be 0, 1, or 2? \$\endgroup\$ Oct 23, 2019 at 7:31
  • 1
    \$\begingroup\$ Suggested test case: +»(x,y)(foo,bar,baz). I think baz should be ignored in that case, leading to foo(x)bar(y)+foo(y)bar(x). Could you please confirm that? \$\endgroup\$
    – Arnauld
    Oct 23, 2019 at 9:29
  • 1
    \$\begingroup\$ I don't find the "in the following method" descriptions clear enough to implement this or decide whether current answers are valid. \$\endgroup\$ Oct 23, 2019 at 20:48

4 Answers 4


JavaScript (ES6),  174  171 bytes


Try it online!


( d,                              // d   = direction
  o,                              // o   = operator
  f,                              // f[] = list of functions
  v,                              // v[] = list of variables
  g = (f, V = v) =>               // g = helper function taking (f, V[])
    f ?                           //   if f is defined:
      V.map(v =>                  //     for each variable v in V[]:
        f + `(${v})`              //       return f, followed by '(v)'
      )                           //
    :                             //   else:
      V                           //     just return V[] as-is
) =>                              //
  f + f && v + v ?                // if both f[] and v[] are non-empty:
    ( d < '»' ?                   //   if the direction is '«':
        f[1] ?                    //     if there are at least 2 functions:
          f.map(f =>              //       for each function f_i in f[]:
            g(f).join``           //         return 'f_i(v_0)f_i(v_1)...'
          )                       //       end of map()
        :                         //     else:
          g(f[0])                 //       return [ 'f_0(v_0)', 'f_0(v_1)', ... ]
      :                           //   else (the direction is '»'):
        v.map((_, i) =>           //     for each variable at position i:
          v.map((_, j) =>         //       for each variable at position j:
            g(                    //         return [ 'f_j(v_k)' ]
              f[j],               //         where k = i mod v.length
              [v[i++ % v.length]] //         and increment i
            )                     //
          ).join``                //       end of inner map(); join the list
        )                         //     end of outer map()
    ).join(o)                     //   join the final list with the operator
  :                               // else:
    ''                            //   just return an empty string
  • \$\begingroup\$ In the spec, it says when the operator is *, the expressions are simply concatenated without any symbols \$\endgroup\$
    – Gymhgy
    Oct 24, 2019 at 3:29
  • \$\begingroup\$ 170 bytes using ascii \$\endgroup\$
    – lyxal
    Oct 24, 2019 at 6:03
  • \$\begingroup\$ representation (previous comment was too long to fit the whole text in) \$\endgroup\$
    – lyxal
    Oct 24, 2019 at 6:03

05AB1E, 63 bytes


Four loose inputs in the order operator, direction, [variables], [functions].

Try it online or verify all test cases.


U                   # Pop and store the 1st (implicit) input `operator` in variable `X`
Ç                   # Convert the 2nd (implicit) input `direction` to a list of codepoint
 `                  # Push this codepoint to the stack
  θ                 # Only leave its last digit
   i                # If this digit is 1 (so the direction is "«"):
    V               #  Pop and store the 3rd (implicit) input `variables` in variable `Y`
    ε       }       #  Map over the 4th (implicit) input `functions`:
     Y              #   Push the `variable`-list from variable `Y`
      €…(ÿ)         #   Surround each variable with parenthesis
           «        #   And append it to the current `function` we're mapping
   ë                # Else (so the direction is "»" instead):
    D               #  Duplicate the 3rd (implicit) input `variables`
     g              #  Pop and push the length to get the amount of `variables`
      ©             #  Store it in variable `®` (without popping)
       D            #  Duplicate this length
        I           #  Push the fourth input `functions`
         g          #  Pop and take its length as well
          α         #  Take the absolute difference between the two amounts
           +        #  And add it to the duplicated amount of `variables`
            >       #  And increase it by 1
             ∍      #  Then extend the duplicated `variables` list to that length
    Π              #  Get all sublists of this list
     ®ù             #  Only leave the sublists of a length equal to variable `®`
       ε            #  Map each remaining sublist to:
        ζ           #   Create pairs with the 4th (implicit) input `functions`,
                    #   with a default space as filler character if the lengths differ
         ε          #   Map each pair:
          ðK        #    Remove the spaces
          '(ý      '#    Join the pair with a ")" delimiter
          yðå≠i     #    If the current pair did NOT contain a space:
               ')« '#     Append a ")"
]                   # Close all open if-statements and maps
 D                  # Duplicate the resulting list
  g≠i }             # Pop and if its length is NOT 1:
     J              #  Join the inner-most list together to a string
       ¸            # Then wrap it into a list (in case it wasn't a list yet)
        ˜           # And deep flatten the list
         Xý         # Join it by the operator we stores in variable `X`
           ³g       # Get the length of the 3rd input-list `variables`
             Ig     # And the length of the 4th input-list `functions`
               *    # Multiply them with each other
                Ā   # Truthify it (0 remains 0; everything else becomes 1)
                 ×  # And repeat the string that many times
                    # (so it becomes an empty string if either list was empty,
                    #  or remains unchanged otherwise)
                    # (after which this is output implicitly as result)
  • \$\begingroup\$ NOTE: Currently fails for test cases with 3+ functions (in both directions) like »+[f,g,h](x,y) or «+[f,g,h](x,y). Will fix them and add the explanation after OP clarified what the intended output should be and some 3+ function test cases are added. \$\endgroup\$ Oct 23, 2019 at 13:13
  • \$\begingroup\$ Like Arnauld's answer, your answer does not concatenate the expressions together with an empty string if the operator is * (source:when the operator is *, the expressions are simply concatenated without any symbols) \$\endgroup\$
    – Gymhgy
    Oct 25, 2019 at 1:50
  • \$\begingroup\$ @EmbodimentofIgnorance You're right, noticed that as well. Should be straight-forward to fix. I'm still waiting for those test cases with 3 or more functions though, so I know what the intended solution should be for those before fixing everything.. >.> \$\endgroup\$ Oct 25, 2019 at 6:17

C# (Visual C# Interactive Compiler), 177 bytes


Try it online!


Python 2, 179 190 194 bytes

lambda d,o,F,V,S='%s(%s)':(o*(o>'*')).join([(''.join(S%(u,v)for v in V)for u in F if V),(''.join(map(lambda u,v:v and u and S%(u,v)or v or'',F,(V+V)[i:i+len(V)]))for i in range(len(V)))][d>'<'])

Try it online!

11 bytes lost to fixing a bug.

A function that takes a direction, an operator, a list of functions, and a list of variables. For direction, uses <,> instead of «,» because utf-x reasons. This assumes that if the direction is >, and there are more functions than variables, that we ignore the excess function(s); e.g., >*[f,g,h],[c,d]) => f(c)g(d)*f(d)g(c)

  • \$\begingroup\$ Like Arnauld's answer, your answer does not concatenate the expressions together with an empty string if the operator is * (source:when the operator is *, the expressions are simply concatenated without any symbols) \$\endgroup\$
    – Gymhgy
    Oct 25, 2019 at 1:51
  • \$\begingroup\$ @EmbodimentofIgnorance: Fixed \$\endgroup\$
    – Chas Brown
    Oct 25, 2019 at 19:23

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.