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 assinBcosA - sinBsinA
but not assinAsinB - 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.
Leaderboards
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 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.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>
»+[](a,b)
really could beab+ba
as per the more variables than functions rule. \$\endgroup\$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\$+»(x,y)(foo,bar,baz)
. I thinkbaz
should be ignored in that case, leading tofoo(x)bar(y)+foo(y)bar(x)
. Could you please confirm that? \$\endgroup\$