# Shorthand Combined Functions

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:

<direction><operator>[functions](variables)


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:

»-[f,g](x,y,z)


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:

«+[cos](a,b)


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:

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


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:

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


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]
ELSE
Result += Functions[i] + "(" + variables[i] + ")"
NEXT i
Left shift variables
Result += operator
NEXT j


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.

## Score

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.

# 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 OVERRIDE_USER=8478;
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.rawh\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,language:p,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> 

• 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. Oct 23, 2019 at 0:31
• 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? Oct 23, 2019 at 7:26
• 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? Oct 23, 2019 at 7:31
• 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? Oct 23, 2019 at 9:29
• I don't find the "in the following method" descriptions clear enough to implement this or decide whether current answers are valid. Oct 23, 2019 at 20:48

# JavaScript (ES6),  174  171 bytes

(d,o,f,v,g=(f,V=v)=>f?V.map(v=>f+(${v})):V)=>f+f&&v+v?(d<'»'?f?f.map(f=>g(f).join):g(f):v.map((_,i)=>v.map((_,j)=>g(f[j],[v[i++%v.length]])).join)).join(o):''  Try it online! ### Commented ( 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 ?                    //     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)                 //       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

• In the spec, it says when the operator is *, the expressions are simply concatenated without any symbols Oct 24, 2019 at 3:29
• 170 bytes using ascii Oct 24, 2019 at 6:03
• representation (previous comment was too long to fit the whole text in) Oct 24, 2019 at 6:03

# 05AB1E, 63 bytes

UÇθiVεY€…(ÿ)«}ëDg©DIgα+>∍Œ®ùεζεðK'(ýyðå≠i')«]Dg≠iJ}¸˜Xý³gIg*Ā×


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

Explanation:

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)


• 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. Oct 23, 2019 at 13:13
• 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) Oct 25, 2019 at 1:50
• @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.. >.> Oct 25, 2019 at 6:17

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

(d,o,f,v)=>string.Join(o=="*"?"":o,d<61?f.Select(l=>v.Aggregate("",(a,b)=>a+l+$"({b})")):v.Select((x,y)=>string.Concat(v.Select((i,o)=>o>=f.Count?i:f[(y+o)%f.Count]+$"({i})"))))


Try it online!

# Python 2, 179190 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)

• 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) Oct 25, 2019 at 1:51
• @EmbodimentofIgnorance: Fixed Oct 25, 2019 at 19:23