<jupyter_start><jupyter_text>Functions In programming, functions assign a name to a block of code, and allow us to execute a more complex set of instructions, without having to replicate the code and the corresponding logic again and again.  Built-in functions Python provides a set of functions that already built-in. You are already familiar with some functions:  `len`, `sum`, `max`, and `min` You have already encountered `len`, `sum`, `max`, and `min`.<jupyter_code>numbers = [3, 41, 12, 9, 74, 15]
# len() takes as a parameter a string (and returns its length in characters)
# or a list/set/dictionary/... (and returns the number of elements)
print("Length:", len(numbers))
# max(sassasssssjajsksncnfja) / min() takes as a parameter a *list* and returns 
# the maximum or minimum element
print("Max:", max(numbers))
print("Min:", min(numbers))
# sum() gets as input a list of numbers and returns their sum
print("Sum:", sum(numbers))<jupyter_output><empty_output><jupyter_text>So, notice how a function works. It has **input** and **output**. In all the examples above, the input is a list of numbers (`numbers`).What is the output?* For `len()`, the output is a number corresponding to the length of the list.* For `max()` the output is a number corresponding to the maximum element of the list.* For `min()` the output is a number corresponding to the minimum element of the list.* For `sum()` the output is a number corresponding to the sum of all the elements in the list. The concept of input and output is very important: In a sense this is what a function does: Takes as input one or more values, and returns back an output. What happens inside the function is something that we do not need to worry about; we can treat the function as a black box.  `round`Let's see another example: Consider the `round()` function.At its simplest form, rounds gets one input: a number. It returns an integer as the output, which is the integer closest to the input number (aka "rounded" number)<jupyter_code>round( 4.6692 )<jupyter_output><empty_output><jupyter_text>The `round()` function can also take two inputs. The first input is the number, and the second input is the number of decimal digits that we want to keep.<jupyter_code>round( 4.6692 , 2 )
round( 4.6692 , 0 )<jupyter_output><empty_output><jupyter_text>Notice the concept: Round accepts as input one or two values, and returns back as output a number. **Input** and **output**, focus on this part.<jupyter_code># And a few more examples
round( 867.5309, 1)
round( 867.5309, 0)
round( 867.5309, -1)<jupyter_output><empty_output><jupyter_text>`sorted` Consider now the `sorted` function. This function takes as **input** as list, and returns as **output** the sorted version of the list. For example<jupyter_code>numbers = [3, 41, 12, 9, 74, 15]
sorted(numbers)
names = ['George', 'James', 'Alex', 'Mary', 'Helen', 'Zoe']
sorted(names)<jupyter_output><empty_output><jupyter_text>So, notice that the function is not limited to retuning just a number. It may return *a list* of numbers. (In fact, it may return a string, or pretty much anything.)Notice also that we do not need to know *how* the function goes from input to output. We put as input an unsorted list, and get back a sorted one. We do not know what sorting technique was used. We only get back the result. This abstraction allow us to write more readable programs, by writing code at a higher level, without having to replicate all the details.  Functions from Libraries We can also add more functions by `import`-ing libraries.  `math` functions  A common example is the `math` library.<jupyter_code>import math
# Square root
math.sqrt(14641)
# Same as above, but with variables
x = 14641
y = math.sqrt(x)
print(y)<jupyter_output><empty_output><jupyter_text>As we mentioned before, this function has an input (`x`) and an output (`y`), which is the square root of x. Again, we do not care how the square root is calculated, we only get back the outcome. Here is another example, where we use the `math.gcd` function to find the _greatest common divisor_ of two numbers, `x` and `y`. (This is the largest number that divides exactly both `x` and `y`.) In this case, the function gets as input two numbers, x and y, and returns an integer as a result. And again, we do not need to know _how_ the function is computing the greatest common divisor, we just get back the result.<jupyter_code>x = 18
y = 60
math.gcd(x, y)
x = 49
y = 56
math.gcd(x, y)<jupyter_output><empty_output><jupyter_text>Exercise Use the `math.sqrt` function to get the square root of the numbers:* 121* 12321* 1234321* 123454321  Solution<jupyter_code>print(math.sqrt(121))
print(math.sqrt(12321))
print(math.sqrt(1234321))
print(math.sqrt(123454321))<jupyter_output><empty_output><jupyter_text>`random` functions Another commonly used library is the `random` library that returns random numbers.<jupyter_code>import random<jupyter_output><empty_output><jupyter_text>For example, the function `random.random()` will return back a random value from 0 to 1, which will be different every time.<jupyter_code>random.random()
# generate 10 random values
for i in range(10):
    x = random.random() # random.random() returns random values from 0 to 1
    print(f"The number is {x:.3f}") # print the number with 3 decimal digits<jupyter_output><empty_output><jupyter_text>So, `random.random()` is a type of function that gets *zero*  inputs, and returns back an integer as the output. Here is another function `random.choice(t)` that accepts as input a list of values, and picks one of them at random.<jupyter_code>t = [ 'Angela', 'Pamela', 'Sandra', 'Rita']
random.choice(t)<jupyter_output><empty_output><jupyter_text>Exercise* Create a list of 8 random integer numbers. (Hint: You can multiply `random.random()` by 100, and then use `round()` to get numbers between 0 and 100.)  Solution<jupyter_code># Using a for loop
result = []  # the list where we will store the results
for i in range(8):  # repeat the process 8 times
    n = random.random()  # create a random number between 0 and 1
    n = 100 * n  # multiply by 100, to get a random number between 0 and 100
    n = round(n)  # get the rounded version, which gives back an integer
    result.append(n)

print(result)
# Using a list comprehension
result = [round(100 * random.random()) for i in range(8)]
print(result)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.09415076634344698, 'fraction_numerical': 0.02549264935877385, 'mean_word_length': 4.99906191369606, 'pattern_counts': {'":': 0, '<': 49, '<?xml version=': 0, '>': 49, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '12', 'dir': 'jupyter-structured-clean-dedup', 'id': '262950', 'license': 'CC0-1.0', 'n_tokens_mistral': 2018, 'n_tokens_neox': 1839, 'n_words': 905, 'path': 'notes/K1-Functions-Builtin_and_Library_Functions.ipynb', 'repo_name': 'agill8781/python', 'size': '6394'}