HSG-MCS-HS21_Julia/JuliaTutorial-master/Tutorial_03_Functions.ipynb
2021-11-15 21:14:51 +01:00

713 lines
18 KiB
Plaintext

{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"collapsed": true
},
"source": [
"# Functions\n",
"\n",
"This notebook shows how to define functions with one (or several) inputs and outputs. It also shows how to use to dot (.) syntax to apply a function to each element of an array."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Load Packages and Extra Functions"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"printyellow (generic function with 1 method)"
]
},
"execution_count": 1,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"using Printf\n",
"\n",
"include(\"jlFiles/printmat.jl\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Functions with One Output"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## The Basic Approach\n",
"\n",
"The basic approach to define a function with the name `fn` is\n",
"```\n",
"function fn(x,b)\n",
" ...(some code)\n",
" return y\n",
"end\n",
"``` \n",
"\n",
"Once you have defined a function, you can use it (call on it) by, for instance, `y1 = fn(2,1)`. This will generate a `y1` variable (not a `y` variable) in the workspace. Inside the function, `x` is then 2 and `b` is 1. Clearly, if `x1=2 and b1=1`, you get the same result by calling as `y1 = fn(x1,b1)`."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"result from fn1(2,1): 0.310\n"
]
}
],
"source": [
"function fn1(x,b) #x and b are the inputs\n",
" c = 0.5 #c is only \"seen\" inside the function\n",
" y = b*(x-1.1)^2 - c\n",
" return y #this is the output\n",
"end\n",
"\n",
"y1 = fn1(2,1)\n",
"printlnPs(\"result from fn1(2,1): \",y1)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Default Values for the Inputs\n",
"\n",
"You can change the first line of the function to specify default values as \n",
"```\n",
"function fn(x,b=1)\n",
"```\n",
"\n",
"In this case you can call on the function as `fn(x)` and the value of `b` will default to 1. (Clearly, inputs with default values must be towards the end of the list of inputs.)"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"result from fn2(2,1) and fn2(2): 0.310 0.310\n"
]
}
],
"source": [
"function fn2(x,b=1) #b=1 is the default in case we call as fn2(x)\n",
" y = b*(x-1.1)^2 - 0.5\n",
" return y\n",
"end\n",
"\n",
"printlnPs(\"result from fn2(2,1) and fn2(2): \",fn2(2,1),fn2(2))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Elementwise Evaluation\n",
"\n",
"To apply the function to each element of arrays `x` and `b`, use the dot syntax (broadcasting): \n",
"```\n",
"y = fn.(x,b)\n",
"```\n",
"This calculates `fn(x[i],b[i])` for each pair `(x[i],b[i])`.\n",
"\n",
"Instead, with `fn.(x,2)`, you calculate `fn(x[i],2)` for each element `x[i]`."
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"result from fn2.(x1,2): \n",
" -0.480\n",
" -0.180\n",
"\n",
"\n",
"result from fn2.(x1,b1): \n",
" -0.480\n",
" -0.180\n",
"\n"
]
}
],
"source": [
"x1 = [1,1.5]\n",
"b1 = [2,2]\n",
"\n",
"println(\"\\nresult from fn2.(x1,2): \")\n",
"printmat(fn2.(x1,2))\n",
"\n",
"println(\"\\nresult from fn2.(x1,b1): \")\n",
"printmat(fn2.(x1,b1))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Elementwise Evaluation over *Some* Inputs (extra)\n",
"\n",
"\n",
"To apply the function to each element of the array `x`, but keep `b` fixed, use : \n",
"```\n",
"y = fn.(x,Ref(b))\n",
"```\n",
"\n",
"For instance, `fn301.(x1,Ref(b1))` in the next cell will create the 2-element vector\n",
"```\n",
"1 + (10+20+30)\n",
"1.5 + (10+20+30)\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"result from fn301.(x1,Ref(b1)):\n",
" 61.000\n",
" 61.500\n",
"\n"
]
}
],
"source": [
"function fn301(x,b)\n",
" y = x + sum(b)\n",
" return y\n",
"end\n",
"\n",
"x1 = [1,1.5]\n",
"b1 = [10,20,30]\n",
"\n",
"println(\"result from fn301.(x1,Ref(b1)):\")\n",
"printmat(fn301.(x1,Ref(b1)))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Short Form (extra)\n",
"\n",
"We can also use short forms of a function as in the cell below. \n",
"\n",
"The first version (`fn3`) is just a single expression. It could span several lines. The second version (`fn3b`) is a sequence of expressions (a \"compound expression\") separated by semicolons (`;`). The last expression is the function output."
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"result from fn3(1.5,1) and fn3b(1.5,1): -0.340 -0.340\n"
]
}
],
"source": [
"fn3(x,b) = b*(x-1.1)^2 - 0.5 #short form of a function\n",
"\n",
"fn3b(x,b) = (c = 0.5;b*(x-1.1)^2 - c) #this works too. Notice the ;\n",
"\n",
"printlnPs(\"result from fn3(1.5,1) and fn3b(1.5,1): \",fn3(1.5,1),fn3b(1.5,1))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Explicit Names of the Inputs: Keyword Arguments\n",
"\n",
"You can also define functions that take *keyword arguments* like in \n",
"```\n",
"fn(x;b,c)\n",
"``` \n",
"Notice the semi-colon (`;`). You can also specify default values as `fn(x;b=1,c=0.5)`\n",
"\n",
"In this case, you *call* on the function by `fn(x,c=3,b=2)` or just `fn(x)` if you want to use the default values. This helps remembering/interpreting what the arguments represent. When calling on the function, you can pass the keyword arguments in any order and you can use comma (`,`) instead of semi-colon (`;`).\n",
"\n",
"(Extra) You can also use a `Dict()` to supply the keyword arguments as in\n",
"```\n",
"opt = Dict(:b=>1,:c=>0.5)\n",
"fn(x;opt...)\n",
"```\n",
"Notice that the dictionary must use symbols and that you need to use `;` and `...` in the call. It is also possible to mix traditional keywords with a dictionary as in `opt = Dict(:c=>0.5); fn(x;b=1,opt...)`"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"result from fn4(1,c=3,b=2): -2.980\n"
]
}
],
"source": [
"function fn4(x;b=1,c=0.5)\n",
" y = b*(x-1.1)^2 - c\n",
" return y\n",
"end\n",
"\n",
"printlnPs(\"result from fn4(1,c=3,b=2): \",fn4(1,c=3,b=2))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Variable Number of Inputs (extra)\n",
"\n",
"When *defining a function*, we can use the `x...` syntax (also called \"slurping\") on the last non-keyword input to capture a variable number of inputs. Later, when calling on this function, several inputs will be combined into a vector `x`.\n",
"\n",
"For instance, `fn5(-9,1,2,b=10)` in the next cell will create the 2-element vector\n",
"```\n",
"-9 + 1*10\n",
"-9 + 2*10\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"result from fn5(-9,1,2,b=10): \n",
" 1.000\n",
" 11.000\n",
"\n"
]
}
],
"source": [
"function fn5(a,x...;b=1) #a is traditional input, x... captures several inputs; b=1 is a keyword input\n",
" n = length(x)\n",
" y = zeros(n)\n",
" for i = 1:n\n",
" y[i] = a + x[i]*b\n",
" end\n",
" return y\n",
"end\n",
"\n",
"y = fn5(-9,1,2,b=10)\n",
"println(\"result from fn5(-9,1,2,b=10): \")\n",
"printmat(y)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Conversely, you can also use the `x...` syntax (also called \"splatting\") when *calling on a function* that requires several inputs.\n",
"\n",
"In the next cell, when running `fn5b([1,2]...)`, `x1` will be 1 and `x2` will be 2."
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"1 and then also 2\n"
]
}
],
"source": [
"function fn5b(x1,x2)\n",
" println(x1,\" and then also \",x2)\n",
" return nothing\n",
"end\n",
"\n",
"fn5b([1,2]...)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Functions with Several Outputs"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Several Outputs 1: Basic Approach\n",
"\n",
"A function can produce a \"tuple\" like `(y1,y2,y3)` as output.\n",
"\n",
"In case you only want the first two outputs, call as `(y1,y2,) = fn(x)`.\n",
"\n",
"Instead, if you only want the second and third outputs, call as `(_,y2,y3) = fn(x)`\n",
"\n",
"You can also extract the second output as `y2 = fn(x)[2]`\n",
"\n",
"If `Y1` is an already existing array, then `(Y1[3],y2,) = fn(x)` will change element 3 of `Y1` (and also assign a value to `y2`)."
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"The first 2 outputs from fn11(1,2): -0.480 2 \n",
"\n",
"The result from calling fn11(1,2)[2]: 2 \n",
"\n",
"Y1 array after calling fn11(1,2): 0.000 0.000 -0.480 0.000 0.000\n"
]
}
],
"source": [
"function fn11(x,b=1)\n",
" y1 = b*(x-1.1)^2 - 0.5\n",
" y2 = b*x\n",
" y3 = 3\n",
" return y1, y2, y3\n",
"end\n",
"\n",
"(y1,y2,) = fn11(1,2)\n",
"printlnPs(\"The first 2 outputs from fn11(1,2): \",y1,y2)\n",
"\n",
"y2 = fn11(1,2)[2] #grab the second output\n",
"printlnPs(\"\\nThe result from calling fn11(1,2)[2]: \",y2)\n",
"\n",
"Y1 = zeros(5) #create an array\n",
"(Y1[3],y2,) = fn11(1,2) #put result in existing array\n",
"printlnPs(\"\\nY1 array after calling fn11(1,2): \",Y1)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Several Outputs 2: Named Tuples and Dictionaries (extra)\n",
"\n",
"\n",
"Instead of returning several values, it might be easier to combine them into either a \"named tuple\" or a dictionary and then export that.\n",
"\n",
"You could end the function with \n",
"\n",
"```\n",
" y = (a=y1,b=y2,c=y3) #named tuple\n",
" return y\n",
"end\n",
"```\n",
"\n",
"or \n",
"\n",
"```\n",
" y = Dict(:a=>y1,:b=>y2,:c=>y3) #dictionary\n",
" return y\n",
"end\n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Several Outputs 3: Elementwise Evaluation (extra)\n",
"\n",
"...can be tricky, because you get an array (same dimension as the input) of tuples instead of a tuple of arrays (which is probably what you want).\n",
"\n",
"One way around this is to reshuffle the output to get a tuple of arrays.\n",
"\n",
"Alternatively, you could loop over the function calls or write the function in such a way that it directly handles array inputs (without the dot). The latter is done in `fn14()`."
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"y, a 2-element vector of tuples (3 elements in each):\n",
"(-0.48, 2.0, 3)\n",
"(-0.18000000000000016, 3.0, 3)\n",
"\n",
"\n",
"the vectors y1, y2 and y3:\n",
" -0.480 2.000 3.000\n",
" -0.180 3.000 3.000\n",
"\n"
]
}
],
"source": [
"y = fn11.([1,1.5],2)\n",
"println(\"y, a 2-element vector of tuples (3 elements in each):\")\n",
"printmat(y)\n",
"\n",
"(y1,y2,y3) = ntuple(i->getindex.(y,i),3) #split up into 3 vectors\n",
"\n",
"println(\"\\nthe vectors y1, y2 and y3:\")\n",
"printmat([y1 y2 y3])"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"result from fn14(x1,2): \n",
" -0.480 2.000\n",
" -0.180 3.000\n",
"\n",
" 3 \n",
"\n"
]
}
],
"source": [
"function fn14(x,b=1) #x can be an array\n",
" y1 = b*(x.-1.1).^2 .- 0.5\n",
" y2 = b*x\n",
" y3 = 3\n",
" return y1, y2, y3\n",
"end\n",
"\n",
"(y1,y2,y3) = fn14(x1,2) #function written to handle arrays\n",
"println(\"result from fn14(x1,2): \")\n",
"printmat([y1 y2])\n",
"printmat(y3)"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": true
},
"source": [
"# Documenting Your Function \n",
"\n",
"To use Julia's help function (`? FunctionName`), put the documentation in triple quotes, just above the function definition. (No empty lines between the last triple quote and the start of the function.) The cell below illustrates a simple case."
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"fn101"
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"\"\"\"\n",
" fn101(x,b=1)\n",
"\n",
"Calculate `b*(x-1.1)^2 - 0.5`.\n",
"\n",
"# Arguments\n",
"- `x::Number`: an important number\n",
"- `b::Number`: another number\n",
"\n",
"\"\"\"\n",
"function fn101(x,b=1)\n",
" y = b*(x-1.1)^2 - 0.5\n",
" return y\n",
"end"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"search: \u001b[0m\u001b[1mf\u001b[22m\u001b[0m\u001b[1mn\u001b[22m\u001b[0m\u001b[1m1\u001b[22m\u001b[0m\u001b[1m0\u001b[22m\u001b[0m\u001b[1m1\u001b[22m\n",
"\n"
]
},
{
"data": {
"text/latex": [
"\\begin{verbatim}\n",
"fn101(x,b=1)\n",
"\\end{verbatim}\n",
"Calculate \\texttt{b*(x-1.1)\\^{}2 - 0.5}.\n",
"\n",
"\\section{Arguments}\n",
"\\begin{itemize}\n",
"\\item \\texttt{x::Number}: an important number\n",
"\n",
"\n",
"\\item \\texttt{b::Number}: another number\n",
"\n",
"\\end{itemize}\n"
],
"text/markdown": [
"```\n",
"fn101(x,b=1)\n",
"```\n",
"\n",
"Calculate `b*(x-1.1)^2 - 0.5`.\n",
"\n",
"# Arguments\n",
"\n",
" * `x::Number`: an important number\n",
" * `b::Number`: another number\n"
],
"text/plain": [
"\u001b[36m fn101(x,b=1)\u001b[39m\n",
"\n",
" Calculate \u001b[36mb*(x-1.1)^2 - 0.5\u001b[39m.\n",
"\n",
"\u001b[1m Arguments\u001b[22m\n",
"\u001b[1m ≡≡≡≡≡≡≡≡≡≡≡\u001b[22m\n",
"\n",
" • \u001b[36mx::Number\u001b[39m: an important number\n",
"\n",
" • \u001b[36mb::Number\u001b[39m: another number"
]
},
"execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"? fn101"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Sending a Function to a Function\n",
"\n",
"You can use a function name (for instance, `cos`) as an input to another function."
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"result from fn201(cos,3.145,10): 9.000\n",
"\n",
"result from fn201(z->mod(z,2),3.145,10): 11.145\n"
]
}
],
"source": [
"function fn201(f,a,b) #f is a function, could also write f::Function\n",
" y = f(a) + b\n",
" return y\n",
"end\n",
"\n",
"#here f is the cos function\n",
"printlnPs(\"result from fn201(cos,3.145,10): \",fn201(cos,3.145,10)) \n",
"\n",
"#here f is z->mod(z,2), an anonymous function\n",
"printlnPs(\"\\nresult from fn201(z->mod(z,2),3.145,10):\",fn201(z->mod(z,2),3.145,10))"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"@webio": {
"lastCommId": null,
"lastKernelId": null
},
"anaconda-cloud": {},
"kernelspec": {
"display_name": "Julia 1.6.1",
"language": "julia",
"name": "julia-1.6"
},
"language_info": {
"file_extension": ".jl",
"mimetype": "application/julia",
"name": "julia",
"version": "1.6.1"
}
},
"nbformat": 4,
"nbformat_minor": 1
}