# This is an example of a Python comments. Everything after a # symbol is ignored by Phython, so you use it
# explain what your code is doing. What is written above and below here is MarkDown text cells.

"""
Explanation: Jupyter Notebooks
Introduction
A really nice feature of Python is the Jupyter Notebook. These notebooks run in your browser. They use a Markdown, (see this cheatsheet for help) with HTML and LaTex mixed in, to nicely format the text comments, and they use full Python to do computations. There are plugins to do C++ or Julia as well, if you insist.
This document is itself a Jupyter notebook. You can look at it on GitHub, which renders a static picture for you, but if you download it and launch it on your own system, you can make modifications, save the result, and run the Python code directly in the notebook. 
It is possible to run the notebooks on other servers. You can pass the GitHub URL for the "raw" notebook, and upload it to the server so you have your own copy, which you can then modify. Since all this is still someone new, it doesn't always work quite as expected. Initially I looked at Binder, recently I looked at Microsoft Azure. If you really need a server, I can also give you access to my own, running at UNH. Please email me if you do.
We will make use of Jupyter notebooks in Physics 605, and for some homework and lab reports. It is a very convenient way for your to submit your work. You can submit the notebook (.ipynb extension) or submit a GitHub URL pointing to your own repository. 
You can easily convert the notebooks to pure Python scripts, or HTML webpage, or Latex pages. You can print the result to paper or to pdf. A Jupyter notebook can also be exported to pdf directly if you install Pandoc and LaTex.
Getting Started
To get started wiht Jupyter Notebooks, you first need to have installed ipython and the jupyter libraries. Fortunately this is already done for you if you installed the Anaconda Python distribution. See Installing Python on MyCources for details.
There are several ways to get the notebook server started on you own machine:
* You can use the "Anaconda-Navigator" and click on "Launch" for the Jupyter notebooks. 
* You can type "jupyter notebook", or "jupyter-notebook", in a terminal, Powershell, or command window.
Usually you want to start Jupyter from the directory where all your code resides, so that the "home" window shows your code or the directories where your code is. See: Jupyter Documentation for more details.
A Jupyter server will start up on your system, and usually, starts your browser on a page http://localhost:8888 
Entering and running code
In a Jupyter notebook you type text into the input box, and if you are done, you type shift+enter to execute that code. The same is true for the Markdown text, type shift+enter and the text is rendered pretty.
Note that for a notebook with a bunch of Python code in it, you usually need to execute the in[] lines in order, since what was computed before may be needed later in the notebook.
At the top of the notebook document in your browser, you see a standard menu bar. Note the black square for "interrupt kernel". That is what you hit if you had some code that takes too long to complete and is now hogging your computer's CPU. The circular arrow allows you to re-launch the kernel, which is a full reset. That means that the in[] fields need to be executed again from the 1st one.
You switch the type of input, between code, Markdown, Raw, with the selector box next to the arrow. The Markdown input does not a an in[] in front, and usually does not need to be executed again.
To re-edit an input that you previously executed, just double click the input box.
Remember regularly save your work when writing notebooks! 🙂
Document your code
I cannot emphasize enough how important it is that you document what you write. That means, you add comments and explanations to the actual program statements. If you create a notebook, use the MarkDown text cells to describe the code, and use Python comments to describe details of what the code is doing.
End of explanation
"""


# This helps matplotlib to realize you are woring from a notebook.
%matplotlib inline                 
import matplotlib.pyplot as plt    # This statement imports matplotlib.pyplot and renames it "plt", to save typing.

"""
Explanation: Using Matplotlib in a Notebook
If you want matplotlib, the plotting library that we will be using in this course, to work properly in a notebook, the prefered way is to enter the following lines at the very beginning of the Python code in your notebook. You do this so that it knows your code is running inline in the notebook and is not supposed to pop up a window.
End of explanation
"""


print("Hello World")

"""
Explanation: Getting started with some Python
A great place to learn about the Python language is in the Tutorial, which is part of the Python documentation. Here is super brief mini tutorial to get you started. This is not a replacement of a real tutorial!
Here is the most basic bit of Python code, something that just prints "Hello World", which is traditionally the start of any programming course.
End of explanation
"""


for i in range(10):
    print(" "*i + "Hello World")

"""
Explanation: Now, lets make that more interesting. How about 10 lines of "Hello World", each line indented one extra space.
End of explanation
"""


i = 10 # This is an integer
type(i)

i = 0.123 # Now, i is float
type(i)

i=1
alist = [1,23,3.4,5,"Hello you!"] # now i is a list, with integers, floats and a string mixed up inside. Python doesn't care.
print(type(alist))                # You can print just about anything, and usually it looks OK.
print(type(alist[4]))
print(alist)
print("As a binary number we have {} = {} or as hexidecimal ={}".format(alist[i],bin(alist[i]),hex(alist[i])))

"""
Explanation: Here you see some interesting features of Python compared to other languages:
* Python uses indentation to create a program block. In C++,C, or Java, this is done with {}, which is why those languages are sometimes called "bracket hell".
* Instead of the for(int i=0;i&lt;10;i++){} construct in C, we use range(10).
    * The range(10) construct actually produces a list: [0,1,2,3,4,5,6,7,8,9] and then itterates over the list.
* In Python you usually do not need to declare a variable. Python figures out on its own what type a variable is. 
    * If you want to know, use the type() build in function.
* In python many operations, like multiply a space " " by an integer, "just make sense". Except when it doesn't 😬
One of the huge benefits of programming in Python is that you can start an interactive python shell (type ipython on the command line) and try out what the effect is of some bit of code. Or you can do this in a notebook.
Something else to point out: commends start with a #. Everything after the # is ignored by the Python interpreter.
End of explanation
"""


help("if")

"""
Explanation: Here you see another feature of Python. A variable (in this case i) is not reassigned a new value, but instead it is destroyed and recreated. So in the first line i was an integer, in the second line, a new variable, also called i is created and this time it is a float. Usually you do not need to worry about this behavior, but occasionally it can give interesting side effects.
Getting help
For almost everything in Python, help is build right into the system. You can simply type help(object) to get help on that object. The help you get is often a bit terse. Search the internet and you will find a wealth of information.
End of explanation
"""


my_list = ["one",2,"three",3.141]
print(my_list)

"""
Explanation: Tuples, Lists, Arrays, Dictionaries
In Python there are a few different ways of creating a collection of things: tuple, list, set, array and dictionary. In Fortran and C you pretty much only have the idea of an array, which in those languages is essentially a range of computer memory locations. In Java you also have collections, and there are a whole confusing lot more if them. In C++ you have array, and STL containers, and again there are a whole lot of them.
In Python, a list is an object that contains a list of other objects, or values. You create one like this:
End of explanation
"""


print(my_list[0])

"""
Explanation: You can stick any other object in your list, including classes and functions, and you can mix them all up. In that way the list is quite different from most other languages, which require their arrays to have a uniform contents. The items in the list start at item 0, so:
End of explanation
"""


my_tuple = (0,1,'OK',"another string")
print(my_tuple[3])

"""
Explanation: You can change the values in a list by re-assigning them to something different, and you can add new values, or delete values. Learn more about lists here and then here
Tuples look very much like a list, except that once created you cannot change the values that are in a tuple; they are immutable. An example tuple:
End of explanation
"""


a = [(x,x*x,x*x*x) for x in range(100)]
found = [ y for y in a if y[1] > 20 and y[1]<40]   # this selects the correct items in a
ind = [ a.index(i) for i in found]                 # this finds the index.
print(ind)                                     

"""
Explanation: Learn more about tuples here
An important aspect of lists is "List comprehensions". These are fast and compact ways of dealing with lists. A complicated example: create a list that contains tuples (x,x^2,x^3) for x from 0 to 99. Then find the index of the tuple where x^2 is larger than 20 but smaller than 40. (of course we know that is the indexes: 5 and 6, but still):
End of explanation
"""


d = {"one":1, "two":"Wednesday", 4:"February", 3.141:"Pi","Pi":3.14157}
d['Coconuts']="Hard but yummy"   # Add something to the list
print(d["Pi"], " and ",d[3.141])

"""
Explanation: The dictionary is a very handy object in Python. It allows you to build a list of items where the index is some object (like a string) instead of a number. You can create one, add to it, and then look things up in it, like this:
End of explanation
"""


s = 5
if s in d:
    print(d[s])
else:
    print("Not found")

"""
Explanation: Note that if you try to get something from the dictionary that isn't there (e.g. print d[5]), you get a "Key error", the key (the first object for each dictionary item) wasn't there. You can use "in" to test if it exists.
End of explanation
"""


for (key,value) in d.items():
    print("We have "+str(key)+" = "+str(value))

"""
Explanation: You can itterate over the dictionary like this:
End of explanation
"""


