"It is also possible to get multiple elements by using slices."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c48ac07f-3a4e-4bb8-9551-2973ea8d2f9c",
"metadata": {},
"outputs": [],
"source": [
"@show t4[1:3];"
]
},
{
{
"cell_type": "markdown",
"cell_type": "markdown",
"id": "aeda8602-f9da-4173-a692-54e2e9a0e178",
"id": "aeda8602-f9da-4173-a692-54e2e9a0e178",
...
@@ -591,10 +609,358 @@
...
@@ -591,10 +609,358 @@
"updated_person = (person..., number = \"0711 ...\")"
"updated_person = (person..., number = \"0711 ...\")"
]
]
},
},
{
"cell_type": "markdown",
"id": "347fe4ea-5b63-4fb8-91bc-2b7b8a0c6a30",
"metadata": {},
"source": [
"### Exercises\n",
" - Write a function `swap_first_last` that takes a tuple and returns a new tuple with the first and last elements swapped. Tip: use a combination of the `...` operator and of this slice `[2:end-1]`."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3721b654-b182-426d-b799-a69e0b53a550",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"id": "647530cd-480a-414f-a8ec-1375e05a9e1b",
"metadata": {},
"source": [
"## Structs"
]
},
{
"cell_type": "markdown",
"id": "00b9bdb1-86ad-47f6-b8c3-88e85878eaff",
"metadata": {},
"source": [
"At this point you know how to use functions to organize code and built-in types to organize data. The next step is to learn how to build your own types to organize both code and data.\n",
"\n",
"We have used many of Julia’s built-in types; now we are going to define a new type. As an example, we will create a type called Point that represents a point in two-dimensional space.\n",
" \n",
"There are several ways we might represent points in Julia:\n",
" - We could store the coordinates separately in two variables, x and y.\n",
" - We could store the coordinates as elements in an tuple.\n",
" - We could create a new type to represent points as objects.\n",
"\n",
"Here we will investigate the third option."
]
},
{
"cell_type": "markdown",
"id": "9aa53d46-033b-4559-a5d8-e95357a257a5",
"metadata": {},
"source": [
"A programmer-defined composite type is also called a struct. The struct definition for a point looks like this:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c2e3f646-2d82-46a4-a265-01b919412c6d",
"metadata": {},
"outputs": [],
"source": [
"struct Point\n",
" x::Real\n",
" y::Real\n",
"end"
]
},
{
"cell_type": "markdown",
"id": "fb9be134-9730-4dbd-9e05-51dd94e9d8e0",
"metadata": {},
"source": [
"The header indicates that the new struct is called Point. The body defines the attributes or fields of the struct. The Point struct has two fields: x and y.\n",
"\n",
"A struct is like a factory for creating objects. To create a point, you call Point as if it were a function having as arguments the values of the fields. When Point is used as a function, it is called a constructor. The constructor returns an instance of the object."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "197339a2-0d8b-4882-bab2-80ba56410f21",
"metadata": {},
"outputs": [],
"source": [
"p = Point(3.0, 4.0)\n",
"@show p;"
]
},
{
"cell_type": "markdown",
"id": "b0134aa7-f356-4d71-9d46-959bd3cfe89a",
"metadata": {},
"source": [
"The type speficication (`::Real`) is optional, but can be helpful to enforce correct usage of the struct."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "849f1460-d228-4b22-a797-1077d75d1a2d",
"metadata": {},
"outputs": [],
"source": [
"p = Point(\"a\", 4.0)"
]
},
{
"cell_type": "markdown",
"id": "e3011b23-816d-4aa2-be61-7eb8f4aee9d1",
"metadata": {},
"source": [
"The values of the fields can be accessed using the `.` operator."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "00d090e4-d74d-4fad-ae8b-369c1046e48b",
"metadata": {},
"outputs": [],
"source": [
"@show p.x p.y;\n",
"@show distance = sqrt(p.x^2 + p.y^2);"
]
},
{
"cell_type": "markdown",
"id": "08748a8c-f76c-4f14-b3d2-9db99fb28e87",
"metadata": {},
"source": [
"Structs are however by default immutable, after construction the fields can not change value:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "91bd6248-a358-43e3-90cc-6b8398445aaf",
"metadata": {},
"outputs": [],
"source": [
"p.x = 2"
]
},
{
"cell_type": "markdown",
"id": "629c1004-5d0f-4093-b546-473c94c91b44",
"metadata": {},
"source": [
"Where required, mutable composite types can be declared with the keyword mutable struct. Here is the definition of a mutable point:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "804c2c32-7a84-4261-88d8-1bad6ba5e18c",
"metadata": {},
"outputs": [],
"source": [
"mutable struct MPoint\n",
" x::Real\n",
" y::Real\n",
"end\n",
"p = MPoint(3.0, 4.0)\n",
"p.x = 2.0\n",
"@show p;"
]
},
{
"cell_type": "markdown",
"id": "1bd558dd-e6c3-4dc0-9476-768c59178708",
"metadata": {},
"source": [
"A third option is to let some fields of an unmutable struct to be mutable. For example a dictionary inside an unmutable struct can be modified."
"You can pass an instance as an argument in the usual way. For example:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e6113221-0c03-484a-99e0-f7d4fab70f03",
"metadata": {},
"outputs": [],
"source": [
"function printpoint(p)\n",
" println(\"($(p.x), $(p.y))\")\n",
"end\n",
"printpoint(p)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d5c6fa9e-c31d-4aff-a3fd-db3449904519",
"metadata": {},
"outputs": [],
"source": [
"function printbook(book)\n",
" println(\"Title: $(book.title)\")\n",
" println(\"Author: $(book.author)\")\n",
" available = book.properties[\"available\"]\n",
" println(\"Available: $(available)\")\n",
"end\n",
"printbook(book)"
]
},
{
"cell_type": "markdown",
"id": "3b12a004-81d9-4bbc-9e2e-d2c281dcbe18",
"metadata": {},
"source": [
"Functions can return instances as return values."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9c57c9f7-c4fd-4263-a55f-62f123aaaa8a",
"metadata": {},
"outputs": [],
"source": [
"function find_center(point1, point2)\n",
" x = (point1.x + point2.x)/2.0\n",
" y = (point1.y + point2.y)/2.0\n",
" return Point(x, y)\n",
"end\n",
"\n",
"point1 = Point(0.0, 0.0)\n",
"point2 = Point(10.0, 10.0)\n",
"\n",
"@show find_center(point1, point2);"
]
},
{
"cell_type": "markdown",
"id": "078b31ab-11e1-4596-8271-f0d33418eab8",
"metadata": {},
"source": [
"### Exercise\n",
"\n",
" - Write a function called `point_distance` which takes two points as arguments and returns the Euclidean distance between them."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b5957a05-e396-41c3-9b8f-f14f089115ab",
"metadata": {},
"outputs": [],
"source": []
},
{
"attachments": {},
"cell_type": "markdown",
"id": "0fb1f6bc-e6e5-4897-bbb6-880e40e3bf20",
"metadata": {},
"source": [
"### References and values\n",
"\n",
"Each object (instance of a struct) is stored at some memory address. The operator `===` checks if two variables point to the same memory address of the object. For example\n",
"\n",
"book_copy = book\n",
"@show book_copy === book;"
]
},
{
"cell_type": "markdown",
"id": "636b6713-f4ed-4220-a159-2918bd44f78e",
"metadata": {},
"source": [
"This means that any change made to `book_copy` will also modify `book`."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a9c46bac-d234-4616-a604-63bdf03b1393",
"metadata": {},
"outputs": [],
"source": [
"book.properties[\"available\"] = true\n",
"book_copy.properties[\"available\"] = false\n",
"\n",
"@show book;"
]
},
{
"cell_type": "markdown",
"id": "6bcbf983-40fa-4fc8-8eca-7cd7c45778dc",
"metadata": {},
"source": [
"If a new, distinct, object is needed (in other words, a copy by value), we can use the function `deepcopy`. "
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c36d23c8-2292-4568-aef4-7416c6f3e538",
"metadata": {},
"outputs": [],
"source": [
"book_copy = deepcopy(book)\n",
"@show book_copy === book;"
]
},
{
"cell_type": "markdown",
"id": "02cc3ec7-e712-4c4b-abf5-9bfc98e68a91",
"metadata": {},
"source": [
"Finally, the `==` operator between structs defaults to the `===` operator as Julia has no way of knowing how to compare custom structs. However, it is always possible to reimplement the `==` operator for our custom types."
A dictionary contains a collection of indices, which are called keys, and a collection of values. Each key is associated with a single value. The association of a key and a value is called a key-value pair or sometimes an item.
A dictionary contains a collection of indices, which are called keys, and a collection of values. Each key is associated with a single value. The association of a key and a value is called a key-value pair or sometimes an item.
The function `Dict` creates a new dictionary with no items. Because Dict is the name of a built-in function, you should avoid using it as a variable name.
The function `Dict` creates a new dictionary with no items. Because Dict is the name of a built-in function, you should avoid using it as a variable name.
To see whether something appears as a value in a dictionary, you can use the function `values`, which returns a collection of values, and then use the `∈` operator:
To see whether something appears as a value in a dictionary, you can use the function `values`, which returns a collection of values, and then use the `∈` operator:
Using the bracket operator it is possible to access an element. Note, differently from other programming languages (for example C, C++, Python) in Julia the first element is 1.
Using the bracket operator it is possible to access an element. Note, differently from other programming languages (for example C, C++, Python) in Julia the first element is 1.
Strictly speaking, a function can only return one value, but if the value is a tuple, the effect is the same as returning multiple values. For example, if you want to divide two integers and compute the quotient and remainder, it is inefficient to compute x ÷ y and then x % y. It is better to compute them both at the same time.
Strictly speaking, a function can only return one value, but if the value is a tuple, the effect is the same as returning multiple values. For example, if you want to divide two integers and compute the quotient and remainder, it is inefficient to compute x ÷ y and then x % y. It is better to compute them both at the same time.
The built-in function `divrem` takes two arguments and returns a tuple of two values, the quotient and remainder. You can store the result as a tuple:
The built-in function `divrem` takes two arguments and returns a tuple of two values, the quotient and remainder. You can store the result as a tuple:
Functions can take a variable number of arguments. A parameter name that ends with `...` gathers arguments into a tuple. For example, `printall` takes any number of arguments and prints them one by line:
Functions can take a variable number of arguments. A parameter name that ends with `...` gathers arguments into a tuple. For example, `printall` takes any number of arguments and prints them one by line:
The complement of gather is scatter. If you have a sequence of values and you want to pass it to a function as multiple arguments, you can use the `...` operator. For example, `divrem` takes exactly two arguments; it doesn’t work with a tuple:
The complement of gather is scatter. If you have a sequence of values and you want to pass it to a function as multiple arguments, you can use the `...` operator. For example, `divrem` takes exactly two arguments; it doesn’t work with a tuple:
It is common to use tuples as keys in dictionaries. For example, a telephone directory might map from last-name, first-name pairs to telephone numbers. Assuming that we have defined last, first and number, we could write:
It is common to use tuples as keys in dictionaries. For example, a telephone directory might map from last-name, first-name pairs to telephone numbers. Assuming that we have defined last, first and number, we could write:
Named tuples are a special type of tuple in Julia where each element has a specific name associated with it. This allows you to access elements by their names, making the code more readable and self-explanatory.
Named tuples are a special type of tuple in Julia where each element has a specific name associated with it. This allows you to access elements by their names, making the code more readable and self-explanatory.
- Write a function `swap_first_last` that takes a tuple and returns a new tuple with the first and last elements swapped. Tip: use a combination of the `...` operator and of this slice `[2:end-1]`.
At this point you know how to use functions to organize code and built-in types to organize data. The next step is to learn how to build your own types to organize both code and data.
We have used many of Julia’s built-in types; now we are going to define a new type. As an example, we will create a type called Point that represents a point in two-dimensional space.
There are several ways we might represent points in Julia:
- We could store the coordinates separately in two variables, x and y.
- We could store the coordinates as elements in an tuple.
- We could create a new type to represent points as objects.
The header indicates that the new struct is called Point. The body defines the attributes or fields of the struct. The Point struct has two fields: x and y.
A struct is like a factory for creating objects. To create a point, you call Point as if it were a function having as arguments the values of the fields. When Point is used as a function, it is called a constructor. The constructor returns an instance of the object.
Each object (instance of a struct) is stored at some memory address. The operator `===` checks if two variables point to the same memory address of the object. For example
Finally, the `==` operator between structs defaults to the `===` operator as Julia has no way of knowing how to compare custom structs. However, it is always possible to reimplement the `==` operator for our custom types.