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 - sinAsinBcan be written assinBcosA - sinBsinAbut 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+baas 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 thinkbazshould be ignored in that case, leading tofoo(x)bar(y)+foo(y)bar(x). Could you please confirm that? \$\endgroup\$