"""setup.py for building numpy-api-bench package.

Extension modules require setup.py so we can't use PEP 517 setup.cfg.

.. codeauthor:: Derek Huang <djh458@stern.nyu.edu>
"""

from numpy import get_include
import platform
from setuptools import Extension, find_packages, setup

from npapibench import __package__, __version__

# package name (underscores converted to dashes anyways in PyPI) + short desc
_PACKAGE_NAME = "numpy-api-bench"
_SHORT_DESC = (
    "A small Python package showcasing speed differences between NumPy's "
    "Python and C APIs."
)
# include paths for functimer subpackage header files and for NumPy headers
_FUNCTIMER_INCLUDE_DIR = f"{__package__}/functimer/include"
_NUMPY_INCLUDE_DIR = get_include()

# extra extension compilation args. must specify C99+ for older Linux gccs.
if platform.system() == "Windows":
    _EXTRA_COMPILE_ARGS = ["/std:c11"]
else:
    _EXTRA_COMPILE_ARGS = ["-std=c11"]

def _get_ext_modules():
    """Returns a list of setuptools.Extension modules to build.

    .. note::

       The extensions must be true modules, i.e. define a ``PyInit_*``
       function. Use alternate means to build foreign C code.
    """
    # use get_include to get numpy include directory + add -std=gnu11 so that
    # the extension will build on older distros with old gcc like 4.8.2
    return [
        # C implementation of the stdscale function in pyimpl
        Extension(
            name="cimpl",
            sources=[f"{__package__}/cimpl.c"],
            include_dirs=[_NUMPY_INCLUDE_DIR],
            extra_compile_args=_EXTRA_COMPILE_ARGS
        ),
        # _timeapi module of the functimer subpackage
        Extension(
            name="functimer._timeapi",
            sources=[f"{__package__}/functimer/_timeapi.c"],
            include_dirs=[_FUNCTIMER_INCLUDE_DIR, _NUMPY_INCLUDE_DIR],
            extra_compile_args=_EXTRA_COMPILE_ARGS
        ),
        # _timeresult module of the functimer subpackage
        Extension(
            name="functimer._timeresult",
            sources=[f"{__package__}/functimer/_timeresult.c"],
            include_dirs=[_FUNCTIMER_INCLUDE_DIR, _NUMPY_INCLUDE_DIR],
            extra_compile_args=_EXTRA_COMPILE_ARGS
        ),
        # _timeunit module of the functimer subpackage
        Extension(
            name="functimer._timeunit",
            sources=[f"{__package__}/functimer/_timeunit.c"],
            include_dirs=[_FUNCTIMER_INCLUDE_DIR],
            extra_compile_args=_EXTRA_COMPILE_ARGS
        )
    ]

def _setup():
    # get long description from README.rst
    with open("README.rst", "r") as rf:
        long_desc = rf.read()
    # perform setup
    setup(
        name=_PACKAGE_NAME,
        version=__version__,
        description=_SHORT_DESC,
        long_description=long_desc,
        long_description_content_type="text/x-rst",
        author="Derek Huang",
        author_email="djh458@stern.nyu.edu",
        license="MIT",
        url="https://github.com/phetdam/numpy-api-bench",
        classifiers=[
            "License :: OSI Approved :: MIT License",
            "Operating System :: POSIX :: Linux",
            "Operating System :: Microsoft :: Windows",
            "Operating System :: MacOS",
            "Programming Language :: Python :: 3.6",
            "Programming Language :: Python :: 3.7",
            "Programming Language :: Python :: 3.8",
            "Programming Language :: Python :: 3.9"
        ],
        project_urls={"Source": "https://github.com/phetdam/numpy-api-bench"},
        python_requires=">=3.6",
        packages=find_packages(),
        # benchmarking script
        entry_points={
            "console_scripts": [f"{__package__} = {__package__}.bench:main"]
        },
        install_requires=["numpy>=1.19"],
        extras_require={"tests": ["pytest>=6.0.1"]},
        ext_package=__package__,
        ext_modules=_get_ext_modules()
    )

if __name__ == "__main__":
    _setup()


#   Copyright 2020 Joseph T. Iosue
#
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.

"""_qubomatrix.py.

This file contains the QUBOMatrix object.

"""

import numpy as np
from . import PUBOMatrix, qubo_value, solve_qubo_bruteforce

__all__ = 'QUBOMatrix', 'matrix_to_qubo', 'qubo_to_matrix'

class QUBOMatrix(PUBOMatrix):
    """QUBOMatrix.

    ``QUBOMatrix`` inherits some methods from ``PUBOMatrix``, see
    ``help(qubovert.utils.PUBOMattrix)``.

    A class to handle QUBO matrices. It is the same thing as a dictionary
    with some methods modified. Note that each key must be a tuple of two
    integers >= 0.

    One method is that values will always default to 0. Consider the following
    example:

    >>> d = QUBOMatrix()
    >>> print(d[(0,)]) # will print 0
    >>> d[(0,)] += 1
    >>> print(d) # will print {(0,): 1}

    Compared to an ordinary dictionary.

    >>> g = dict()
    >>> print(g[(0,)]) # will raise KeyError
    >>> g[(0,)] += 1 # will raise KeyError, since (0,) was never set

    One method of QUBOMatrix is that it will always keep the QUBO
    upper triangular! Consider the following example:

    >>> d = QUBOMatrix()
    >>> d[(1, 0)] += 2
    >>> print(d)
    >>> # will print {(0, 1): 2}

    One method is that if we set an item to 0, it will be removed. Consider
    the following example:

    >>> d = QUBOMatrix()
    >>> d[(0,)] += 1
    >>> d[(0,)] -= 1
    >>> print(d) # will print {}

    One method is that if we initialize QUBOMatrix with a previous dictionary
    it will be reinitialized to ensure that the QUBOMatrix is upper
    triangular and contains no zero values. Consider the following example:

    >>> d = QUBOMatrix({(0, 0): 1, (1, 0): 2, (2, 0): 0})
    >>> print(d) # will print {(0, 0): 1, (0, 1): 2}

    We also change the update method so that it follows all the conventions.

    >>> d = QUBOMatrix({(0, 0): 1, (0, 1): 2})
    >>> d.update({(0, 0): 0, (1, 0): 1, (1, 1): -1})
    >>> print(d)  # will print {(0, 1): 1, (1,): -1}

    We also include arithmetic, addition, subtraction, scalar division,
    multiplication, and all those in place. For example,

    >>> d = QUBOMatrix((0, 0)=1, (0, 1)=-2)
    >>> g = d + {(0, 0): -1}
    >>> print(g) # will print {(0, 1): -2}
    >>> g *= 4
    >>> print(g) # will print {(0, 1): -8}
    >>> g -= {(0, 1): -8}
    >>> print(g) # will print {}

    >>> d = QUBOMatrix({(0, 0): 1, (0, 1): -1})
    >>> g = {(0,): -1, (1,): 1}
    >>> d *= g
    >>> print(d)
    {(0,): -1, (0, 1): 1}

    >>> d = QUBOMatrix({(0, 0): 1, (0, 1): -1})
    >>> print(d ** 2 == d * d)
    True

    Adding or subtracting constants will update the () element of the
    dict.

    >>> d = QUBOMatrix()
    >>> d += 5
    >>> print(d)
    {(): 5}

    Finally, if you try to access a key out of order, it will sort and squash
    the key. Be careful with this, it can cause unexpected behavior if you
    don't know it. For example,

    >>> d = QUBOMatrix()
    >>> d[(0, 1)] += 2
    >>> print(d[(1, 0)])  # will print 2

    >>> d = QUBOMatrix()
    >>> d[(0, 0)] += 2
    >>> print(d[(0,)])  # will print 2
    >>> print(d[(0, 0)])  # will print 2
    >>> print(d)  # will print {(0,): 2}

    """

    @staticmethod
    def _check_key_valid(key):
        """_check_key_valid.

        Internal method to check if an input key to the dictionary is valid.
        Checks to see if ``key`` is a tuple of non negative integers with <=
        2 unique integers.

        Parameters
        ----------
        key : anything, but must be a tuple to be valid.

        Returns
        -------
        k : the squashed key.

        Raises
        ------
        KeyError if the key is invalid.

        """
        k = PUBOMatrix.squash_key(key)
        if len(k) > 2:
            raise KeyError(
                "Key formatted incorrectly, must be tuple of <= 2 integers "
                "See PUBOMatrix instead.")
        return k

    def value(self, x):
        r"""value.

        Find the value of the QUBO. Calling
        ``self.value(x)`` is the same as calling
        ``qubovert.utils.qubo_value(x, self)``.

        Parameters
        ----------
        x : dict or iterable.
            Maps boolean variable indices to their boolean values, 0 or 1. Ie
            ``x[i]`` must be the boolean value of variable i.

        Return
        ------
        value : float.
            The value of the QUBO with the given assignment `x`. Ie

        Example
        -------
        >>> from qubovert.utils import QUBOMatrix, PUBOMatrix
        >>> from qubovert import QUBO, PUBO

        >>> P = PUBOMatrix({(0, 0): 1, (0, 1): -1})
        >>> x = {0: 1, 1: 0}
        >>> P.value(x)
        1

        >>> Q = QUBOMatrix({(0, 0): 1, (0, 1): -1})
        >>> x = {0: 1, 1: 0}
        >>> Q.value(x)
        1

        >>> P = PUBO({(0, 0): 1, (0, 1): -1})
        >>> x = {0: 1, 1: 0}
        >>> P.value(x)
        1

        >>> Q = QUBO({(0, 0): 1, (0, 1): -1})
        >>> x = {0: 1, 1: 0}
        >>> Q.value(x)
        1

        """
        return qubo_value(x, self)

    def solve_bruteforce(self, all_solutions=False):
        """solve_bruteforce.

        Solve the problem bruteforce. THIS SHOULD NOT BE USED FOR LARGE
        PROBLEMS! This is the exact same as calling
        ``qubovert.utils.solve_qubo_bruteforce(
            self, all_solutions, self.is_solution_valid)[1]``.

        Parameters
        ----------
        all_solutions : bool.
            See the description of the ``all_solutions`` parameter in
            ``qubovert.utils.solve_qubo_bruteforce``.

        Return
        ------
        res : the second element of the two element tuple that is returned from
            ``qubovert.utils.solve_qubo_bruteforce``.

        """
        return solve_qubo_bruteforce(self,
                                     all_solutions, self.is_solution_valid)[1]

    @property
    def Q(self):
        """Q.

        Return a plain dictionary representing the QUBO. Each key is a tuple
        of two integers, ie (1, 1) corresponds to (1,). Note that the offset
        in the QUBOMatrix is ignored (ie the value corresponding to the key
        ()). See the ``offset`` property to access it.

        Returns
        -------
        Q : dict.
            Plain dictionary representing the QUBO in standard form.

        """
        return {k * (3 - len(k)): v for k, v in self.items() if k}

def matrix_to_qubo(matrix):
    r"""matrix_to_qubo.

    Convert a matrix to a QUBO dictionary.

    Parameters
    ----------
    matrix : list of lists or 2-dimensional numpy array.
        ``matrix[i][j]`` is equal to :math:`Q_{ij}`.

    Return
    ------
    Q : qubovert.utils.QUBOMatrix object.
       The upper triangular QUBO dictionary. See
       ``help(qubovert.utils.QUBOMatrix)``.

    """
    if not isinstance(matrix, np.ndarray):
        matrix = np.array(matrix)

    if len(matrix.shape) != 2 or matrix.shape[0] != matrix.shape[1]:
        raise ValueError("Input matrix must be square and two-dimensional")

    Q = QUBOMatrix()
    for i in range(matrix.shape[0]):
        for j in range(matrix.shape[1]):
            Q[(i, j)] += matrix[i][j]

    return Q

def qubo_to_matrix(Q, symmetric=False, array=True):
    r"""qubo_to_matrix.

    Convert a QUBO dictionary to its matrix form. The indices of the ``Q``
    dictionary should be integers from 0 to ``n-1``, where there are ``n``
    binary variables in the QUBO problem.

    Parameters
    ----------
    Q : dict or qubovert.utils.QUBOMatrix object.
        Input QUBO dictionary, where ``Q[(i, j)]`` corresponds to
        :math:`Q_{ij}`.
    symmetric : bool (optional, defaults to False).
        Whether the returned matrix should be symmetric or upper-triangular.
        If ``symmetric`` is True, then the matrix will be symmetric, ie
        ``matrix[i][j] == matrix[j][i]``. Otherwise, it will be
        upper-triangular, ie ``marix[i][j] == 0`` if ``i > j``.
    array : bool (optional, defaults to True).
        Whether the returned matrix should be a numpy array or list of lists.
        If ``array`` is True, then it will be a numpy array, otherwise, it
        will be a list of lists.

    Return
    ------
    matrix : numpy array or list of lists.
        The matrix representing the QUBO. See the arguments ``symmetric`` and
        ``array`` for info on the return type of ``matrix``.

    """
    if not Q:
        raise ValueError("QUBO dictionary is empty")
    elif not isinstance(Q, QUBOMatrix):
        Q = QUBOMatrix(Q)

    if Q[()] != 0:
        raise ValueError("QUBO cannot have a constant when converting "
                         "to a matrix")

    matrix = np.zeros((Q.max_index+1,)*2)
    for k, v in Q.items():
        if len(k) == 1:
            matrix[k[0]][k[0]] = v
        elif symmetric:
            i, j = k
            matrix[i][j] = v / 2
            matrix[j][i] = v / 2
        else:
            i, j = k
            matrix[i][j] = v

    if not array:
        return matrix.tolist()
    return matrix


# pylint: disable=missing-function-docstring, missing-module-docstring/
# coding: utf-8

from pyccel.stdlib.internal.mpi import mpi_init
from pyccel.stdlib.internal.mpi import mpi_finalize
from pyccel.stdlib.internal.mpi import mpi_comm_size
from pyccel.stdlib.internal.mpi import mpi_comm_rank
from pyccel.stdlib.internal.mpi import mpi_comm_world
from pyccel.stdlib.internal.mpi import mpi_status_size
from pyccel.stdlib.internal.mpi import mpi_recv
from pyccel.stdlib.internal.mpi import mpi_send
from pyccel.stdlib.internal.mpi import mpi_type_vector
from pyccel.stdlib.internal.mpi import mpi_type_commit
from pyccel.stdlib.internal.mpi import mpi_type_free
from pyccel.stdlib.internal.mpi import MPI_INTEGER8

import numpy as np

if __name__ == '__main__':
    # we need to declare these variables somehow,
    # since we are calling mpi subroutines
    ierr = np.int32(-1)
    sizes = np.int32(-1)
    rank = np.int32(-1)

    mpi_init(ierr)

    comm = mpi_comm_world

    mpi_comm_size (comm, sizes, ierr)
    mpi_comm_rank (comm, rank, ierr)

    nb_lines   = np.int32(3)
    nb_columns = np.int32(4)
    tag        = np.int32(100)

    a      = np.zeros ((nb_lines, nb_columns), 'int')
    status = np.zeros (mpi_status_size, 'int32')

    # Initialization of the matrix on each process
    a[:,:] = 1000 + rank

    # Definition of the type_line datatype
    blocklength = np.int32(1)
    type_line = np.int32(-1)
    mpi_type_vector (nb_columns, blocklength, nb_lines, MPI_INTEGER8, type_line, ierr)

    # Validation of the type_line datatype
    mpi_type_commit (type_line, ierr)

    # Sending of the first column
    if ( rank == 0 ):
        dest = np.int32(1)
        mpi_send (a[1,0], nb_columns, MPI_INTEGER8, dest, tag, comm , ierr)

    # Reception in the last column
    if ( rank == 1 ):
        count  = np.int32(1)
        source = np.int32(0)
        mpi_recv (a[nb_lines-1,0], count, type_line, source, tag, comm, status, ierr)

    print('I process ', rank, ', has a = ', a)

    # Free the datatype
    mpi_type_free (type_line, ierr)

    mpi_finalize(ierr)


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Sun Jun 13 20:10:07 2021

@author: surajitrana
"""

import matplotlib.pyplot as plt
import numpy as np

def plot_chart():
    x_series = np.array(["May 01, 2021", "May 10, 2021",
                        "May 20, 2021", "May 29, 2021", "June 01, 2021"])
    y_series = np.array([120, 457, 897, 23, 0])

    # Creating font styles for title and axes labels
    fonttitle = {'family': 'arial', 'color': 'green', 'size': 20}
    fontx = {'family': 'serif', 'color': 'blue', 'size': 15}
    fonty = {'family': 'serif', 'color': 'darkred', 'size': 15}

    # Creating title and axes labels and assigning font styles
    plt.title("Total sales over a period of time (in millions)",
              fontdict=fonttitle, loc="left", pad="20")
    plt.xlabel("Timeline", fontdict=fontx)
    plt.ylabel("Total Sales", fontdict=fonty)

    # Adding grids to the plot
    plt.grid(axis="both", color="blue", linestyle="--", linewidth="1")

    plt.plot(x_series, y_series, linestyle="-.",
             color="hotpink", linewidth="3", marker="o")
    plt.show()

if __name__ == '__main__':
    plot_chart()


import numpy as np

class Layer(object):
    def __init__(self):
        self.input_shape = None

    def name(self):
        return self.__class__.__name__

    def forward(self, data):
        return NotImplementedError()

    def backward(self, grads, **kwargs):
        return NotImplementedError()

    def initialize(self, initializer, otimizer, input_shape, **kwargs):
        pass

    def output_shape(self):
        return NotImplementedError()

    def params(self):
        return NotImplementedError()

    def dparams(self):
        return NotImplementedError()


#   Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved.
# # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at #
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from __future__ import print_function

import sys
sys.path.append("..")

import paddle
import paddle.fluid.core as core
import paddle.fluid as fluid
import paddle.tensor as tensor
import unittest
import numpy as np
from op_test import OpTest
from op_test_xpu import XPUOpTest
from paddle.fluid.framework import Program, program_guard
from xpu.get_test_cover_info import create_test_class, get_xpu_op_support_types, XPUOpTestWrapper

paddle.enable_static()

class XPUTestTrilTriuOp(XPUOpTestWrapper):
    def __init__(self):
        self.op_name = 'tril_triu'
        self.use_dynamic_create_class = False

    class TestTrilTriuOp(XPUOpTest):
        def setUp(self):
            self.init_dtype()
            self.initTestCase()
            self.real_op_type = np.random.choice(['triu', 'tril'])
            self.real_np_op = getattr(np, self.real_op_type)
            self.set_xpu()
            self.op_type = "tril_triu"
            if self.dtype == np.int32:
                self.X = np.arange(
                    1, self.get_Xshape_prod() + 1,
                    dtype=self.dtype).reshape(self.Xshape)
            else:
                self.X = np.random.random(self.Xshape).astype(dtype=self.dtype)
            self.inputs = {'X': self.X}
            self.attrs = {
                'diagonal': self.diagonal,
                'lower': True if self.real_op_type == 'tril' else False,
            }
            self.outputs = {
                'Out': self.real_np_op(self.X, self.diagonal)
                if self.diagonal else self.real_np_op(self.X)
            }

        def init_dtype(self):
            self.dtype = self.in_type

        def get_Xshape_prod(self):
            ret = 1
            for v in self.Xshape:
                ret *= v
            return ret

        def set_xpu(self):
            self.__class__.use_xpu = True
            self.__class__.no_need_check_grad = True
            self.__class__.op_type = self.real_op_type

        def test_check_output(self):
            if paddle.is_compiled_with_xpu():
                place = paddle.XPUPlace(0)
                self.check_output_with_place(place)

        def initTestCase(self):
            self.diagonal = None
            self.Xshape = (10, 10)

    class TestTrilTriuOp1(TestTrilTriuOp):
        def initTestCase(self):
            self.diagonal = -3
            self.Xshape = (5, 5)

    class TestTrilTriuOp2(TestTrilTriuOp):
        def initTestCase(self):
            self.diagonal = 4
            self.Xshape = (11, 17)

    class TestTrilTriuOp3(TestTrilTriuOp):
        def initTestCase(self):
            self.diagonal = 10
            self.Xshape = (2, 25, 25)

    class TestTrilTriuOp4(TestTrilTriuOp):
        def initTestCase(self):
            self.diagonal = -10
            self.Xshape = (1, 2, 33, 11)

    class TestTrilTriuOp5(TestTrilTriuOp):
        def initTestCase(self):
            self.diagonal = 11
            self.Xshape = (1, 1, 99)

    class TestTrilTriuOp6(TestTrilTriuOp):
        def initTestCase(self):
            self.diagonal = 5
            self.Xshape = (1, 2, 3, 5, 99)

    class TestTrilTriuOp7(TestTrilTriuOp):
        def initTestCase(self):
            self.diagonal = -100
            self.Xshape = (2, 2, 3, 4, 5)

class TestTrilTriuOpError(unittest.TestCase):
    def test_errors1(self):
        paddle.enable_static()
        data = fluid.data(shape=(20, 22), dtype='float32', name="data1")
        op_type = np.random.choice(['triu', 'tril'])
        errmsg = {
            "diagonal: TypeError":
            "diagonal in {} must be a python Int".format(op_type),
        }
        expected = list(errmsg.keys())[0]
        with self.assertRaisesRegex(
                eval(expected.split(':')[-1]), errmsg[expected]):
            getattr(tensor, op_type)(x=data, diagonal='2022')

    def test_errors2(self):
        paddle.enable_static()
        data = fluid.data(shape=(200, ), dtype='float32', name="data2")
        op_type = np.random.choice(['triu', 'tril'])
        errmsg = {
            "input: ValueError":
            "x shape in {} must be at least 2-D".format(op_type),
        }
        expected = list(errmsg.keys())[0]
        with self.assertRaisesRegex(
                eval(expected.split(':')[-1]), errmsg[expected]):
            getattr(tensor, op_type)(x=data, diagonal=[None])

support_types = get_xpu_op_support_types('tril_triu')
for stype in support_types:
    create_test_class(globals(), XPUTestTrilTriuOp, stype)

if __name__ == '__main__':
    unittest.main()


import numpy as np #the numpy library
import matplotlib.pyplot as plt #Matplotlib's pyplot

import sys #gives access to a C-like sys library
import os #gives access to the operating system

print(sys.argv) #print any command line arguments
print(os.getcwd()) #prints current working directory


from __future__ import print_function
import sys
import numpy as np
import scipy
import matplotlib
import h5py
import platform

print("Platform:", sys.platform)
print("Python:", sys.version)
print("Machine and architecture", platform.machine(), *platform.architecture())
print("NumPy:", np.version.version)
print("SciPy:", scipy.version.version)
print("Matplotlib:", matplotlib.__version__)
print("h5py:", h5py.__version__)


"""
2021 Day 10
https://adventofcode.com/2021/day/10
"""

from collections import deque
from enum import Enum
from functools import reduce
from typing import Dict, Tuple
from numpy import median
import aocd  # type: ignore

class ErrorType(Enum):
    INCOMPLETE = 0
    CORRUPTED = 1

BRACKETS: Dict[str, str] = {
    "(": ")",
    "[": "]",
    "{": "}",
    "<": ">",
}

def error_on_line(line: str) -> Tuple[ErrorType, str]:
    chunks: deque[str] = deque()
    for char in line:
        if char in BRACKETS:
            chunks.appendleft(char)
        elif len(chunks) == 0:
            return (ErrorType.CORRUPTED, char)
        elif char != BRACKETS[chunks.popleft()]:
            return (ErrorType.CORRUPTED, char)
    return (ErrorType.INCOMPLETE, "".join(BRACKETS[char] for char in chunks))

CORRUPT_ERROR_VALUES: Dict[str, int] = {
    "": 0,
    ")": 3,
    "]": 57,
    "}": 1197,
    ">": 25137,
}

def total_corrupted_errors(subsystem: str) -> int:
    return sum(
        CORRUPT_ERROR_VALUES[char]
        for errortype, char in [error_on_line(line) for line in subsystem.split("\n")]
        if errortype == ErrorType.CORRUPTED
    )

INCOMPLETE_ERROR_VALUES: Dict[str, int] = {
    ")": 1,
    "]": 2,
    "}": 3,
    ">": 4,
}

def incomplete_error_score(brackets: str) -> int:
    return reduce(
        lambda total, bracket: (total * 5) + INCOMPLETE_ERROR_VALUES[bracket],
        brackets,
        0,
    )

def median_incomplete_error(subsystem: str) -> int:
    return int(
        median(
            [
                incomplete_error_score(brackets)
                for errortype, brackets in [
                    error_on_line(line) for line in subsystem.split("\n")
                ]
                if errortype == ErrorType.INCOMPLETE
            ]
        )
    )

def test_part1() -> None:
    """
    Examples for Part 1.
    """
    assert error_on_line("{([(<{}[<>[]}>{[]{[(<()>") == (ErrorType.CORRUPTED, "}")
    assert error_on_line("[[<[([]))<([[{}[[()]]]") == (ErrorType.CORRUPTED, ")")
    assert error_on_line("[{[{({}]{}}([{[{{{}}([]") == (ErrorType.CORRUPTED, "]")
    assert error_on_line("[<(<(<(<{}))><([]([]()") == (ErrorType.CORRUPTED, ")")
    assert error_on_line("<{([([[(<>()){}]>(<<{{") == (ErrorType.CORRUPTED, ">")
    assert (
        total_corrupted_errors(
            "\n".join(
                (
                    "[({(<(())[]>[[{[]{<()<>>",
                    "[(()[<>])]({[<{<<[]>>(",
                    "{([(<{}[<>[]}>{[]{[(<()>",
                    "(((({<>}<{<{<>}{[]{[]{}",
                    "[[<[([]))<([[{}[[()]]]",
                    "[{[{({}]{}}([{[{{{}}([]",
                    "{<[[]]>}<{[{[{[]{()[[[]",
                    "[<(<(<(<{}))><([]([]()",
                    "<{([([[(<>()){}]>(<<{{",
                    "<{([{{}}[<[[[<>{}]]]>[]]",
                )
            )
        )
        == 26397
    )

def test_part2() -> None:
    """
    Examples for Part 2.
    """
    assert error_on_line("[({(<(())[]>[[{[]{<()<>>") == (
        ErrorType.INCOMPLETE,
        "}}]])})]",
    )
    assert error_on_line("[(()[<>])]({[<{<<[]>>(") == (ErrorType.INCOMPLETE, ")}>]})")
    assert error_on_line("(((({<>}<{<{<>}{[]{[]{}") == (
        ErrorType.INCOMPLETE,
        "}}>}>))))",
    )
    assert error_on_line("{<[[]]>}<{[{[{[]{()[[[]") == (
        ErrorType.INCOMPLETE,
        "]]}}]}]}>",
    )
    assert error_on_line("<{([{{}}[<[[[<>{}]]]>[]]") == (ErrorType.INCOMPLETE, "])}>")
    assert incomplete_error_score("])}>") == 294
    assert incomplete_error_score("}}]])})]") == 288957
    assert incomplete_error_score(")}>]})") == 5566
    assert incomplete_error_score("}}>}>))))") == 1480781
    assert incomplete_error_score("]]}}]}]}>") == 995444
    assert incomplete_error_score("])}>") == 294
    assert (
        median_incomplete_error(
            "\n".join(
                (
                    "[({(<(())[]>[[{[]{<()<>>",
                    "[(()[<>])]({[<{<<[]>>(",
                    "{([(<{}[<>[]}>{[]{[(<()>",
                    "(((({<>}<{<{<>}{[]{[]{}",
                    "[[<[([]))<([[{}[[()]]]",
                    "[{[{({}]{}}([{[{{{}}([]",
                    "{<[[]]>}<{[{[{[]{()[[[]",
                    "[<(<(<(<{}))><([]([]()",
                    "<{([([[(<>()){}]>(<<{{",
                    "<{([{{}}[<[[[<>{}]]]>[]]",
                )
            )
        )
        == 288957
    )

def main() -> None:
    """
    Calculate and output the solutions based on the real puzzle input.
    """
    data = aocd.get_data(year=2021, day=10)

    print(f"Part 1: {total_corrupted_errors(data)}")
    print(f"Part 2: {median_incomplete_error(data)}")

if __name__ == "__main__":
    main()


import csv
import pandas as pd
import numpy as np


#!/usr/bin/env python
# coding: utf-8

# # Dirichlet Distribution

# In[260]:

# In[261]:

# In[262]:

# In[1]:

from IPython.display import  HTML

def load_d3_in_cell_output():
  display(HTML(""))
get_ipython().events.register('pre_run_cell', load_d3_in_cell_output)

# ## The Chinese Restaurant Process

# In the thought problem, we will be examing a situation where a hungery person (🤔) enters a restrauant and needs to choose a table (⚪).
#
# This original was developed by xxx and a great resource to consider is Pasupat's (xxx).
#
# Here are the ground rules for this thought problem.
#

# ## Rules for Our Thought Problem

# ### 1. An Infinite Amount of Tables (⚪)
#
# We are depicting five tables (⚪⚪⚪⚪⚪) but we need to consider a situation where the number of tables are infinite.
#
# * ⚪ = ∞

# ### 2. A Hungry Person (🤔) Only Two Options
#
# When a hungry person (🤔) walks into the the restraunt they have two options:
#
# * Either they sit a table (⚪) with someone else (😃)
# * or pick a new table  (⚪)
#
# To simplify this, here a decision chart.

# In[2]:

from IPython.display import SVG, display
display(SVG(url='https://raw.githubusercontent.com/dudaspm/LDA_Bias_Data/main/images/startCondition.svg'))

# And to further reduce this down, we will be using this:

# In[3]:

from IPython.display import SVG, display
display(SVG(url='https://raw.githubusercontent.com/dudaspm/LDA_Bias_Data/main/images/simpleStartCondition.svg'))

# ### 3. Many ⚪ & 😃, Only One Empty ⚪
#
# This goes with #2, but in our scenario there will number of tables (⚪) with people (😃), but when considering an empty table (⚪). We will only consider *one* of the infinite number of tables (⚪) open. Another way to consider this is either a hungry person (🤔):
# * sits at the *one of possible many* tables (⚪) with someone else (😃)
# * *OR* they sit at the *one* new table  (⚪)

# ### All Tables (⚪) are Equal
# Notice that all the tables are equal distance away. So, there is no weighting based on the distance and each table is equally likely to be picked.

# In[4]:

get_ipython().run_cell_magic('html', '', '<input type="button" onclick="runWeight()" value="Run Animation">\n

\n\n')

# ### Key for Thought Problem
#
# > 🤔 - hungry person
# * The person who needs to find a seat at a table
#
# > 😃 - person eating
# * A person already at a table
#
# > ⚪ - a possible table
# * A potential seat for the hungry person to sit at
#
# > ⚫ - a not possible table
# * Not a potential seat for the hungry person to sit at (see Rule #3)

# ## All Solutions 💥TO THE EXTREME💥

# :::{note}
# "To the extreme!" was a popular phrase from the early 1990s. Meaning "to take something to its furthest limits." Most credit [Robert Matthew Van Winkle](https://en.wikipedia.org/wiki/Vanilla_Ice) for the phrase.
# :::

# Now that we have our ground rules, let's approach this problem from, what I am calling, the extreme positions. Up to this point, we have not mentioned a single bit of math, but this section will contain conversations around probabilities. Here are three scenarios for our extreme positions.
#
# 1. The Social Butterfly
# 2. The Gambler
# 3. The Long Day

# ### 1. The Social Butterfly
#
# The social butterfly assumes every person that enters the restraunts wants to sit at the table with the most people.

# In[5]:

get_ipython().run_cell_magic('html', '', '<input type="button" onclick="social1()" value="Run Animation">\n

\n\n')

# In[6]:

get_ipython().run_cell_magic('html', '', '<input type="button" onclick="social2()" value="Run Animation">\n

\n\n')

# In[7]:

get_ipython().run_cell_magic('html', '', '<input type="button" onclick="social3()" value="Run Animation">\n

\n\n')

# In[8]:

get_ipython().run_cell_magic('html', '', '<input type="button" onclick="social4()" value="Run Animation">\n

\n\n')

# In[9]:

get_ipython().run_cell_magic('html', '', '<input type="button" onclick="social5()" value="Run Animation">\n

\n\n')

# ### 2. The Gambler
#
# The Gambler is the person who only cares about the probabilites. Meaning, if there is two tables (xx), then they have a 50/50 choice, and they do not care at all about the people sitting there or not.

# In[10]:

get_ipython().run_cell_magic('html', '', '<input type="button" onclick="gambler1()" value="Run Animation">\n

\n\n')

# In[11]:

get_ipython().run_cell_magic('html', '', '<input type="button" onclick="gambler2()" value="Run Animation">\n

\n\n')

# In[12]:

get_ipython().run_cell_magic('html', '', '<input type="button" onclick="gambler3()" value="Run Animation">\n

\n\n')

# In[13]:

get_ipython().run_cell_magic('html', '', '<input type="button" onclick="gambler4()" value="Run Animation">\n

\n\n')

# In[14]:

get_ipython().run_cell_magic('html', '', '<input type="button" onclick="gambler5()" value="Run Animation">\n

\n\n')

# ### 3. The Long Day
#
# The Long Day scenerio describes a situation where customers (xx) coming into the restraunt had a reeeeeeeeeeeeeeeally long day. All they want is a table (xx) to themselves to eat their food, pay, and go home. This is the opposite of the Social Butterfly, where if there are people at a table (😃 & xx). They will find an empty table (xxx).
#

# In[15]:

get_ipython().run_cell_magic('html', '', '<input type="button" onclick="long1()" value="Run Animation">\n

\n\n')

# In[16]:

get_ipython().run_cell_magic('html', '', '<input type="button" onclick="long2()" value="Run Animation">\n

\n\n')

# In[17]:

get_ipython().run_cell_magic('html', '', '<input type="button" onclick="long3()" value="Run Animation">\n

\n\n')

# In[18]:

get_ipython().run_cell_magic('html', '', '<input type="button" onclick="long4()" value="Run Animation">\n

\n\n')

# In[19]:

get_ipython().run_cell_magic('html', '', '<input type="button" onclick="long5()" value="Run Animation">\n

\n\n')

# In[ ]:

# ## The Conclusions
#
# ### ✨1st Conclusion✨
#
# So, let's take a look at all three of these scenario results.

# In[20]:

get_ipython().run_cell_magic('html', '', '<input type="button" value="✨1st Conclusion✨" style="font-size:20px" onclick="conclusion1()">\n

\n\n')

# Our ✨1st Conclusion✨ is that for each scenerio, the total probablities (when added together), equal 1. This is our first connection the *Dirichlet Distribution*.

# ```{admonition} Dirichlet Distribution Always Sum to 1
# :class: tip
# Regardless of the number of tables (⚪), the number of people at the tables (😃), or a hungry persons' (🤔) strategy. The total probability will be 1. This is also consider to be a *probability mass function* or PMF property.
# ```

# ### ✨2nd Conclusion✨
#
# This easiest to see with our "The Gambler" scenerio.

# In[21]:

get_ipython().run_cell_magic('html', '', '<input type="button" value="✨2nd Conclusion✨" style="font-size:20px" onclick="conclusion2()">\n

\n\n')

# ```{admonition} When All Possibility Are Equally Likely
# :class: tip
# In situations where are all possibility are equally likely (equally likely to sit at a table with someone else (⚪&😃) or sit at a new table (⚪)), we can abbreviate this to a simple probablity:
#
# $\frac{😃}{😃😃😃😃}$ $=$ $\frac{Number of people sitting at table(⚪&😃)}{All people (😃😃😃😃)}$ $= $ $\frac{Nj}{N}$
# ```

# In[22]:

from scipy.stats import dirichlet
import numpy as np

# In[23]:

alpha = np.array([0.01, 0.01, 0.01, 0.01, 0.01])
np.around(dirichlet.rvs(alpha, size=5), decimals=1)

# In[24]:

alpha = np.array([0.1, 0.1, 0.1, 0.1, 0.1])
np.around(dirichlet.rvs(alpha, size=5), decimals=1)

# In[25]:

alpha = np.array([1, 1, 1, 1, 1])
np.around(dirichlet.rvs(alpha, size=5), decimals=1)

# In[26]:

alpha = np.array([5, 5, 5, 5, 5])
np.around(dirichlet.rvs(alpha, size=5), decimals=1)

# In[27]:

alpha = np.array([20, 20, 20, 20, 20])
np.around(dirichlet.rvs(alpha, size=5), decimals=1)

# In[28]:

alpha = np.array([100, 100, 100, 100, 100])
np.around(dirichlet.rvs(alpha, size=5), decimals=1)

# In[29]:

alpha = np.array([0.01, .1, 1, 10, 10])

np.around(dirichlet.mean(alpha), decimals=3)

# In[ ]:


# -------------------------------------------------------------------------------
# Name:        Histogram.py
# Purpose:     Make an histogram plot based on the results of LTSpice.py
#
# Author:      Nuno Brum (nuno.brum@gmail.com)
#
# Created:     17-01-2017
# Licence:     Free
# -------------------------------------------------------------------------------

__author__ = "Nuno Canto Brum <me@nunobrum.com>"
__copyright__ = "Copyright 2017, Fribourg Switzerland"

#!/usr/bin/env python
import numpy as np
import matplotlib.mlab as mlab
import matplotlib.pyplot as plt
from optparse import OptionParser

usage = "usage: %prog [options] LOG_FILE TRACE"
opts = OptionParser(usage=usage, version="%prog 0.1")
#opts.add_option('v', "var", action="store", type="string", dest="trace", help="The trace to be used in the histogram")
opts.add_option('-s',"--sigma", action ="store", type="int", dest="sigma", default=3, help="Sigma to be used in the distribution fit. Default=3")
opts.add_option('-n', "--nbins", action="store",  type="int", dest="nbins", default=20, help="Number of bins to be used in the histogram. Default=20")
opts.add_option('-c', "--condition", action="append", type="string", dest="filters",
                help="Filter condition writen in python. More than one expression can be added but each expression should be preceded by -f.\n" +
                     "EXAMPLE: -c V(N001)>4 -c parameter==1 -c  I(V1)<0.5" )
opts.add_option('-f', "--format", action="store", type="string", dest="format", help="Format string for the X axis. Example: -f %3.4f")
#opts.add_option('-p', "--scaling",action="store", type="string", dest="prescaling", help="Prescaling function to be applied to the input value.")
opts.add_option('-t', "--title", action="store", type="string", dest="title", help="Title to appear on the top of the histogram.")
opts.add_option('-r', "--range", action="store", type="string", dest="range", help="Range of the X axis to use for the histogram in the form min:max. Example: -r -1:1")
opts.add_option('-C', "--clipboard", action="store_true", dest="clipboard", help="If the data from the clipboard is to be used.")
#opts.add_option('-x', "--xname", action="store", dest="xname", help="Name for the variable displayed")
opts.add_option('-i', "--image", action="store", type="string", dest="imagefile", help="Name of the image File. extension 'png'")

(options, args) = opts.parse_args()

values = []

if options.clipboard:
    try:
        import clipboard
    except ImportError:
        print("Failed to load clipboard package. Use PiP to install it.")
        exit(1)
    if len(args) > 0:
        TRACE = args[-1]
    else:
        TRACE = "var"
    text = clipboard.paste()
    for line in text.split('\n'):
        try:
            values.append(float(line))
        except ValueError:
            print("Failed to process ")
            print(line)
elif len(args)==0:
    opts.print_help()
    exit(-1)
else:
    if len(args) < 2:
        opts.error("Wrong number of parameters.")
        opts.print_help()
        exit(-1)
    # if (len(args)==1): # This will search for the most recent file
    #     newer_date = 0
    #     filename = None
    #     for f in os.listdir():
    #         date = os.path.getmtime(f)
    #         if date > newer_date and f.endswith(".tlog"):
    #             newer_date = date
    #             filename = f
    #     if filename == None:
    #         opts.error("A LOG_FILE should be given")
    TRACE = args[1]
    logfile = args[0]

    if not options.filters is None:
        print("Filters Applied:", options.filters)
    else:
        print("No filters defined")

    log = open(logfile,'r')
    header = log.readline().rstrip('\n')
    vars = header.split('\t')
    try:
        sav_col = vars.index(TRACE)
    except ValueError:
        log.close()
        print("File '%s' doesn't have trace '%s'" % (logfile, TRACE))
        print("LOG FILE contains %s" % vars)
        exit(-1)

    if (options.filters is None) or (len(options.filters) == 0):
        for line in log:
            #print(line)
            vs = line.split('\t')
            values.append(float(vs[sav_col]))
    else:
        for line in log:
            vs = map(float,line.split('\t'))
            env = dict(zip(vars,vs))

            for expression in options.filters:
                test = eval(expression, None, env)
                if test == False:
                    break
            else:
                values.append(float(env[TRACE]))

    log.close()

if len(values) == 0:
    print("No elements found")
elif len(values) < options.nbins:
    print("Not enough elements for an histogram")
else:
    x = np.array(values, dtype=float)
    mu = x.mean()
    mn = x.min()
    mx = x.max()
    sd = np.std(x)
    sigmin = mu - options.sigma*sd
    sigmax = mu + options.sigma*sd

    if options.range is None:
        # Automatic calculation of the range
        axisXmin = mu - (options.sigma+1)*sd
        axisXmax = mu + (options.sigma + 1) * sd

        if mn < axisXmin:
            axisXmin = mn

        if mx > axisXmax:
            axisXmax = mx
    else:
        try:
            smin, smax = options.range.split(":")
            axisXmin = float(smin)
            axisXmax = float(smax)
        except:
            opts.error("Invalid range setting")
            exit(-1)
    if options.format:
        fmt = options.format
    else:
        fmt = "%f"

    print("Collected %d elements" % len(values))
    print("Distributing in %d bins" % options.nbins)
    print("Minimum is " + fmt % mn)
    print("Maximum is " + fmt % mx)
    print("Mean is " + fmt % mu)
    print("Standard Deviation is " + fmt % sd)
    print(("Sigma %d boundaries are " + fmt + " and " + fmt) % (options.sigma, sigmin, sigmax))
    n, bins, patches = plt.hist(x, options.nbins, normed=True, facecolor='green', alpha=0.75, range=(axisXmin, axisXmax))
    axisYmax = n.max() * 1.1

    # add a 'best fit' line
    y = mlab.normpdf( bins, mu, sd)
    l = plt.plot(bins, y, 'r--', linewidth=1)
    plt.axvspan(mu - options.sigma*sd, mu + options.sigma*sd, alpha=0.2, color="cyan")
    plt.xlabel(TRACE)
    plt.ylabel('Distribution [Normalised]')

    if options.title is None:
        title = (r'$\mathrm{Histogram\ of\ %s:}\ \mu='+fmt+r',\ stdev='+fmt+r',\ \sigma=%d$') % (TRACE, mu, sd, options.sigma)
    else:
        title = options.title
    plt.title(title)

    plt.axis([axisXmin, axisXmax, 0, axisYmax ])
    plt.grid(True)
    if options.imagefile is not None:
        plt.savefig(options.imagefile)
    else:
        plt.show()


#!/usr/bin/env python
# coding: utf-8

# In[1]:

import sys, os
if 'google.colab' in sys.modules and not os.path.exists('.setup_complete'):
    get_ipython().system('wget -q https://raw.githubusercontent.com/yandexdataschool/Practical_RL/master/setup_colab.sh -O- | bash')

    get_ipython().system('touch .setup_complete')

# This code creates a virtual display to draw game images on.
# It will have no effect if your machine has a monitor.
if type(os.environ.get("DISPLAY")) is not str or len(os.environ.get("DISPLAY")) == 0:
    get_ipython().system('bash ../xvfb start')
    os.environ['DISPLAY'] = ':1'

# In[2]:

import numpy as np
import matplotlib.pyplot as plt
get_ipython().run_line_magic('matplotlib', 'inline')

# ### OpenAI Gym
#
# We're gonna spend several next weeks learning algorithms that solve decision processes. We are then in need of some interesting decision problems to test our algorithms.
#
# That's where OpenAI Gym comes into play. It's a Python library that wraps many classical decision problems including robot control, videogames and board games.
#
# So here's how it works:

# In[3]:

import gym

env = gym.make("MountainCar-v0")
env.reset()

plt.imshow(env.render('rgb_array'))
print("Observation space:", env.observation_space)
print("Action space:", env.action_space)

# Note: if you're running this on your local machine, you'll see a window pop up with the image above. Don't close it, just alt-tab away.

# ### Gym interface
#
# The three main methods of an environment are
# * `reset()`: reset environment to the initial state, _return first observation_
# * `render()`: show current environment state (a more colorful version :) )
# * `step(a)`: commit action `a` and return `(new_observation, reward, is_done, info)`
#  * `new_observation`: an observation right after committing the action `a`
#  * `reward`: a number representing your reward for committing action `a`
#  * `is_done`: True if the MDP has just finished, False if still in progress
#  * `info`: some auxiliary stuff about what just happened. For now, ignore it.

# In[4]:

obs0 = env.reset()
print("initial observation code:", obs0)

# Note: in MountainCar, observation is just two numbers: car position and velocity

# In[5]:

print("taking action 2 (right)")
new_obs, reward, is_done, _ = env.step(2)

print("new observation code:", new_obs)
print("reward:", reward)
print("is game over?:", is_done)

# Note: as you can see, the car has moved to the right slightly (around 0.0005)

# ### Play with it
#
# Below is the code that drives the car to the right. However, if you simply use the default policy, the car will not reach the flag at the far right due to gravity.
#
# __Your task__ is to fix it. Find a strategy that reaches the flag.
#
# You are not required to build any sophisticated algorithms for now, and you definitely don't need to know any reinforcement learning for this. Feel free to hard-code :)

# In[6]:

from IPython import display

# Create env manually to set time limit. Please don't change this.
TIME_LIMIT = 250
env = gym.wrappers.TimeLimit(
    gym.envs.classic_control.MountainCarEnv(),
    max_episode_steps=TIME_LIMIT + 1,
)
actions = {'left': 0, 'stop': 1, 'right': 2}

# In[7]:

def policy(obs, t):
    # Write the code for your policy here. You can use the observation
    # (a tuple of position and velocity), the current time step, or both,
    # if you want.
    position, velocity = obs

    VELOCITY_THRESHOLD = 1e-3
    if velocity >= VELOCITY_THRESHOLD or velocity < 0 and velocity > -VELOCITY_THRESHOLD:
        return actions['right']
    if velocity <= -VELOCITY_THRESHOLD or velocity > 0 and velocity < VELOCITY_THRESHOLD:
        return actions['left']

    return actions['right']

# In[8]:

plt.figure(figsize=(4, 3))
display.clear_output(wait=True)

obs = env.reset()
for t in range(TIME_LIMIT):
    plt.gca().clear()

    action = policy(obs, t)  # Call your policy
    obs, reward, done, _ = env.step(action)  # Pass the action chosen by the policy to the environment

    # We don't do anything with reward here because MountainCar is a very simple environment,
    # and reward is a constant -1. Therefore, your goal is to end the episode as quickly as possible.

    # Draw game image on display.
    plt.imshow(env.render('rgb_array'))
    plt.title(obs)

    display.display(plt.gcf())
    display.clear_output(wait=True)

    if done:
        print("Well done!")
        break
else:
    print("Time limit exceeded. Try again.")

display.clear_output(wait=True)

# In[10]:

assert obs[0] > 0.47
print("You solved it!")

# In[ ]:


#!/usr/bin/env python
# coding: utf-8

# # Workshop Notebook

# ## Notebook Introduction

# ### How to Use this Notebook

# ### References

# I know it tradition to have the refences at the end of books, but when you are standing on the shoulders of giants. You thank them first.

# ```{bibliography}
# ```

# ### Thank you!
#
# Also, a huge *thank you* to Adam Lavely (https://github.com/adamlavely) for developing some of the intial notebooks!

# ## Introduction to JupyterLab

# ### Where am I? (JupyterLab Notebook)

# Jupyter is a powerful suite of tools that allows us to do many things.
#
# Jupyter is capable of running **Ju**lia, **Pyt**hon and **R**, as well as some other things.
#

# ### Cells

# Each box is called a cell.

# #### Two types of Cells

# ##### Text

# Text Cells allow you to add text (via Markdown), which includes tables, images, links, bullet lists, numbered lists, LaTeX, blockquote, among other things.

# ###### Table
#
# ```markdown
# | This | is   |
# |------|------|
# |   a  | table|
# ```
#
# | This | is   |
# |------|------|
# |   a  | table|

# ###### Image
# ```markdown
# ![Wheat Field with Cypresses](images/vangogh.jpg)
# ```
#
# ![Wheat Field with Cypresses](images/vangogh.jpg)
#
#
#
#

# ###### Link
# ```markdown
# [Attribution](https://www.metmuseum.org/art/collection/search/436535)
# ```
# Vincent van Gogh / Public domain
# The Metropolitan Museum of Art, New York - Purchase, The Annenberg Foundation Gift, 1993 -
# [Attribution](https://www.metmuseum.org/art/collection/search/436535)

# ###### Bullet List
# ```markdown
# * I am a
#     * bullet
# * list
# ```
# * I am a
#     * bullet
# * list
#
#

# ###### Numbered List
# ```markdown
# 1. I am a
#   1. numbered
# 1. list
# ```
# 1. I am a
#   1. numbered
# 1. list

# ###### LaTeX
# ```markdown
# $$e=mc^2$$
# ```
#
#
# $$e=mc^2$$

# ###### Blockquotes
# ```markdown
# > This is a blockquote.
# ```
# > This is a blockquote.

# ##### Code

# Cells can be run using the Run button ► or selecting one of the run options under the Run menu.
#
# Try this out! You can change what is in the cell and rerun the same cell, which is useful for debugging.

# In[1]:

2 + 2

# ### Your turn!

# In a new cell, figure out what **5315 + 5618** is.

# In[2]:

## remove and type out 5315 + 5618
## then hit the play button

# ## Introduction to Python

# In this section, I wanted to introduce a few basic concepts and give an outline of this section.

# ### Comments in Python

# In Python, we can create comments in the code itself. Considering we can use markdown language (as you see here 😁), we won't use this too much in this notebook. Though, here is an example.
#
# Basically, you use the... umm... hashtag? Number sign? Pound sign?
#
# This thing -> #

# In[3]:

# I am a comment in Python
# Here is 2 + 2
2 + 2
# As you can see, these are not "computed" using Python.
# We are just comments for the person looking at this.
# Or... you!

# ### Print Function

# We will being using...
#
# ```python
# print()
# ```
#
# ...several times in this notebook.
#
# *print()* is a function to print out strings, variables, numbers, functions, etc.
#
# Let's use the classic example.

# In[4]:

print( "hello, world!" )

# OR

# In[5]:

print("hello, world!")

# *print()* can do some fun things as well. As in, giving it more than one thing to print with commas between them. This will print both things with spaces.

# In[6]:

print( "hello,", "world!" )

# ### Help Function

# The...
#
# ```python
# help()
# ```
#
# ... function is exactly what it is. It is a function to 🌟 help 🌟 you understand the basic usage of another function.

# In[7]:

help(print)

# ### Resources

# Highly suggest looking for answers using [StackOverflow](https://stackoverflow.com/help/searching)

# ### Common Errors

# One of the most common errors in Python is the dreaded
#
# ```python
# 2 + 2
#  3 + 3
#
#   File "<ipython-input-1-0dcc020fd5cb>", line 2
#     3 + 3
#     ^
# IndentationError: unexpected indent
# ```
#
# Why does this occur? Well, because Python uses spacing or tabs to distinguish where things like loops, functions, and if/else statements start and end. So, if you add an extra space or tab at the beginning of the statement, you will see this message. If you do, check your spacing.

# ```{note}
# Python can get weird with this issue. As you can, technically, start code wherever as long as you are consistent. The next cell shows an example of this... oddity.
#
# ```

# In[8]:

2+2
3+3

# ### Your turn!

# ## Learning about Variables

# When we are developing our idea, we sometimes need to use values multiple times or change the value based on our code. This concept is where variables become very helpful. Let's look at an example.
#
# In this example, we are adding a few numbers together. In this instance, if all we care about is getting the result (similar to a calculator). Then variables are not needed.

# In[9]:

5 + 3 + 16

# But let's look at an example where we need to get the circumference of a circle using multiple radii. The equation for the circumference of a circle is: $C = 2 \pi r$

# Let's say the radius is 5

# In[10]:

2 * 3.14159265359 * 5

# OK, how about radius 10 and 11 and 4 and ...
# Well, in this example, we might not want to rewrite 3.14159265359 over and over. So, in this case, we want to create a variable for this, and we will call it pi.

# In[11]:

pi = 3.14159265359

# Now, every time we reference the variable called **pi** it will refer to the number **3.14159265359**
#
# Let's try those radii again (10, 11, 4)

# In[12]:

2 * pi * 10

# In[13]:

2 * pi * 11

# In[14]:

2 * pi * 4

# By the way, if you happen to get an error:
# ```javascript
# NameError: name 'pi' is not defined
# ```
# Make sure you go to the cell that has
# ```python
# pi = 3.14159265359
# ```
# and run this cell *first* then try the other calculations.

# ### Type of Variables

# There are multiple types of variables. The most common (and the ones we will talk about) are:
#
# * Integers (whole numbers)
# * Float (Floating points or numbers with a decimal)
# * Text
# * Lists
# * Dictionaries
#
# The nice thing about Python is that we do **not** need to specify (or declare) which type we are using. Python will figure this out for us!
#
# BUT FIRST, a quick detour...
#
# We need to talk about Camel Casing.

# #### Camel Case

#
# Variable names must be one continuous string of letters/numbers. So, let's say we wanted to create a variable called "number of kittens." Instead calling this variable number of kittens, I would call it numberOfKittens. Why the capitalization? Because it makes it easier to separate the words in the name. As in, numberofkittens vs. numberOfKittens. We have a fun name for this: camel case.

# File:CamelCase new.svg. (2020, April 15). Wikimedia Commons, the free media repository. Retrieved 15:25, June 3, 2020 from https://commons.wikimedia.org/w/index.php?title=File:CamelCase_new.svg&oldid=411544943.

# #### Integers or int

# As mentioned, integers are whole numbers. Let's create an example. How about we use our numberOfKittens. We will then set this value to 0. As in, we have 0 kittens.

# In[15]:

numberOfKittens = 0

# One thing we might want to do is to have Python tell us what **type** this variable is. Well, Python has a function for this called
#
# ```python
# type()
# ```

# In[16]:

type( numberOfKittens )

# So this checks out, we made an int, and it is showing us we have an int.
#
# Now, once we have a variable, it is not static. We can change the value as much as we need to. Running the next cell will continually add 10 to our original variable.
#
# Try running this a few times.

# In[17]:

numberOfKittens = numberOfKittens + 10
numberOfKittens

# #### Floating points or floats

# Floats are similar to integers, but with more precision.
# Float comes from a Floating point or a number with a decimal point.
#
# This example starts at 0, but note that this is .0
# Adding the decimal tells Python that we should have a float value instead of an integer.

# In[18]:

aFloatVariable = .0

# Let's again, check the variable type.

# In[19]:

type( aFloatVariable )

# Looks good.
#
# And again, we will add 10 to this. There is something specific interesting here; see if you spot it.

# aFloatVariable = aFloatVariable + 10
# aFloatVariable

# If you guessed "mixing a float and an integer," you got it. Let's see an example.

# ##### Mixing integers and floats

# In Python (3, more specifically), the variable will always take the form of the most precision. So, by default, a float.

# In[20]:

letsSeeWhatHappens = numberOfKittens + aFloatVariable
letsSeeWhatHappens

# We can force variables to be a certain type. We call this 'type-cast' and can be used to:
#
# * make an integer into a float
# * a float to an integer
# * an integer to a string (we have not discussed this yet)
# * a float to a string (we have not discussed this yet)
# * etc...

# ##### type-cast

# ```{note}
# type-cast is temporary. If you do not use a type-cast, the variable will revert to its original variable type.
# ```

# Let's switch our numberOfKittens to a float using
# ```python
# float()
# ```
#
# and turn our aFloatVariable to an integer using
#
# ```python
# int()
# ```

# In[21]:

float(numberOfKittens)

# In[22]:

int(aFloatVariable)

# #### String or str

# So, up to this point, we started our conversation working with numbers. Well, what about the other things that are not numbers... like text? Well, for text, we use something called a String or str.
#
# Strings allow us to capture a single character up to thousands of characters (actually, much more than this). Let's go through a traditional example of "Hello, World!" but with my slight spin to it.

# In[23]:

helloStatement = "Hello, everyone!"

# As you can see, can capture text and other alphanumeric and special characters. There are several unique functions for strings but first, let's double-check and see what type we from our helloStatement.

# In[24]:

type( helloStatement )

# Not too surprising, we see this is type str or string.

# ##### String Indexing/String Slicing

# One of the first ways to interact with our string is to take a look at individual characters by using their **index**.
#
# The **index** is position (or multiple positions) for each character in the string. So, if we look at our string, we have Hello, everyone! If we wanted to see the first letter *H*, we could reference this using the index or the position where the letter is in the string.

# In[25]:

helloStatement[1]

# ohh.. wait a minute. We were expecting the letter *H*, but we got *e*. What happened?

# ```{note}
# For indexes, we always start at the number 0. So, 0 is the first thing, 1 is the second thing, and so on.
# ```

# Let's try this again.

# In[26]:

helloStatement[0]

# There we go!

# Visually, this is how the string looks to Python.
#
# ![Hello, everyone! text](https://raw.githubusercontent.com/dudaspm/JupyterLab-Python/main/images/helloEveryone.png)

# ###### Indexing Multiple Letters

# In[27]:

print( helloStatement[0:5] )

# Wait a second!
#
# ![Hello, everyone! text](https://raw.githubusercontent.com/dudaspm/JupyterLab-Python/main/images/helloEveryone.png)

# The way you should think of this is:
#
# ```python
# helloStatement[0 : 5 - 1]
# helloStatement[(starting number) to (ending number - 1)]
# ```
#
# There is also a shortcut way of writing this, without the 0.

# In[28]:

print( helloStatement[:5] )

# In[29]:

print( helloStatement[5:] )

# ##### String functions

# ###### Formatting

# In[30]:

print( helloStatement.capitalize() )
print( helloStatement.lower() )

# ###### Split

# In[31]:

print( helloStatement.split(" ") )

# ```{note}
# *.split()* will eventually become your best friend. *.split()* is a **great** function to use when using uniquelly spaced data.
# As in comma separated values or CSV.
# ```

# ##### Concatenating Strings
#
# When you want to put two strings together, we say you *concatenate* the strings. There are multiple ways of doing this but presented are what I believe to be the three most common ways.

# ###### + Method

# This is the most straightforward method of the three, but there can be some issues. You simply add a plus sign *+* between your strings. Let's take a look at this.

# In[32]:

print ( "hello, " + "everyone!")

# This works fine, but when you add a number to this idea. We run into issues.

# ```python
# print ( "hello, " + "every" + 1 + "!")
#
# ---------------------------------------------------------------------------
# TypeError                                 Traceback (most recent call last)
# <ipython-input-41-1f53f06cad5c> in <module>
# ----> 1 print ( "hello, " + "every" + 1 + "!")
#
# TypeError: can only concatenate str (not "int") to str
# ```

# In this case we need to *type-cast* the integer as a string using
# ```python
# str()
# ```

# In[33]:

print ( "hello, " + "every" + str(1) + "!")

# ###### % Method

# This is my favorite method out of the three. Let's see how this works with the same example.
#
# In this case, we use a %s (s = string) for each string we want to embed in our overall string.

# In[34]:

print ( "%s, %s" % ("hello", "everyone") )

# There are three parts to this.
#
# *The format*
# * ```python
# "%s, %s"
# ```
#
# *The break*
# * ```python
# %
# ```
#
# *The fill*
# * ```python
# ("hello", "everyone")
# ```
#
# We have two %s, meaning we need to feed it with two strings.

# OK, but what about numbers?

# In[35]:

print ( "%s, %s%s%s" % ("hello","every",1,"!") )

# Still works! This reason is why I like this method. You pick the formating and feed in the strings.

# ###### join() Method

# The .join() method uses a function called
# ```python
# .join()
# ```
# This is a create function to be aware of, as it will allow you the ability to join strings with a specific, static format. What do I mean by static formatting? Well, unlike the % method, that can be formatted exactly how I want it. The .join() method requires a specific pattern. Example time!

# In[36]:

print ( " ".join(["hello, ", "everyone!"]) )

# There are two parts to this.
#
# *The splitter*
# * ```python
# " "
# ```
#
# *The fill*
# * ```python
# .join(["hello, ", "everyone!"])
# ```
#
# Notice that the join has the brackets around it. Technically, you are feeding this an array or list (we have not talked about this yet). This function again, like *.split()*, will be a great asset to you in the future.
#
# Let's show this with our number again.

# ```python
# print ( " ".join(["hello, ", "every", 1, "!"]) )
#
# ---------------------------------------------------------------------------
# TypeError                                 Traceback (most recent call last)
# <ipython-input-54-e926f0c4c025> in <module>
# ----> 1 print ( " ".join(["hello, ", "every", 1, "!"]) )
#
# TypeError: sequence item 2: expected str instance, int found
# ```

# The same issue as before, we need to type-cast.

# In[37]:

print ( " ".join(["hello, ", "every", str(1), "!"]) )

# Notice the spaces? Again, we are saying with *the splitter* what each string is going to be seperated by, so in this case, everything will be split by spaces.

# #### Booleans

# Booleans are used to do comparisions (true/false), (1/0), (yes/no)

# In[38]:

someCondition = True
type( someCondition )

# ##### Boolean Logic

# We will talk about boolean logic more in the next section (Comparisons)

# In[39]:

(someCondition == False)

# In[40]:

if (False):
    print( "yes for False!" )
if (True):
    print( "yes for True!" )

# ```{note}
# A more "traditional" way to do booleans is to use 0 and 1. In Python, any number other than 0 is True. Including negative numbers and decimals.
# ```

# In[41]:

if (0):
    print( "yes for 0!" )
if (1):
    print( "yes for 1!" )
if (2):
    print( "yes for 2!" )
if (-3):
    print( "yes for -3!" )
if (.4):
    print( "yes for .4!" )

# ### Lists

# Lists (or also known as Arrays) are exactly that. A list of data.
#
# There are two options for creating a *List*.
#
# 1. Define the list initially

# In[42]:

groceryList = ["apple", "banana", "eggs"]
print( groceryList )

# 2. Create a list and add to it using
#
# ```python
# .append()
# ```

# In[43]:

groceryList = []
groceryList.append("apple")
groceryList.append("banana")
groceryList.append("eggs")
print( groceryList )

# ```{note}
# For indexes, we always start at the number 0. So, 0 is the first thing, 1 is the second thing, and so on.
# ```

# In[44]:

print( groceryList[2] )
print( groceryList[0] )
print( groceryList[1] )

# So what happens if we use an *index* outside of our list?

# ```python
# print( groceryList[3] )
#
# ---------------------------------------------------------------------------
# IndexError                                Traceback (most recent call last)
# <ipython-input-44-0a77fb05d512> in <module>
#     print( groceryList[3] )
#
# IndexError: list index out of range
# ```

# ```{note}
# Typically, going through an array, one index at a time is not how we want to use lists.
# We will talk about going through lists using a *loop* in an upcoming notebook.
# ```

# #### Dictionary

# Dictionaries are used to index based on a specific key. As in:
#
# dictionary[\"street adddress\" (key)] = "123 Apple St." (value)

# In[45]:

personalInformation = {}
personalInformation["streetAddress"] = "123 Apple St."
personalInformation["firstName"] = "Patrick"
personalInformation["lastName"] = "Dudas"
print( personalInformation )

# Note the order.

# Again, to do this more efficiently, we will be using loops (we will talk about later).

# ### Your turn!

# ## Comparison Operators

# We need to be able to compare different variables.  We will be working on:
# * Are these things the same?
# * Are these things not the same?
# * How do these things compare?
#
# We can compare any data type, and our output will be a boolean (True or False).  The other things we will cover are:
# * Comparing different data types
# * Making multiple comparisons at once
#
# Comparison operators are important on their own (how do these things compare?) and are also useful for sorting and switching (see the next notebook).

# ### Are these things the same?

# #### Numeric Comparisons
# We have already initiated variables by setting something equal to something else - let's do that here by setting kitten 🐈 equal to 10 and then setting dog 🐕 equal to kitten 🐈. Finally, 🐝 bee will be equal to 11.
#
# So...
#
# 🐈 = 10
#
# 🐕 = 🐈
#
# 🐝 = 11

# In[46]:

kitten = 10
dog = kitten
bee = 11

print( "kitten =", kitten, "; dog =", dog, "; bee = ", bee )

# The first comparison operator is '==', which tests to see if two variables are equal.

# In[47]:

print( "kitten =", kitten, "; dog =", dog, "; bee = ", bee )

print( "Is kitten equal to dog?")
print( kitten == dog )

print( "Is kitten equal to bee?")
print( kitten == bee )

# This tells us that kitten is equal to dog, because it returns *True* and kitten is not equal to bee, as that returns *False*.

# #### Character Comparisons
# We can also do comparisons with other variable types.  Here's an example with strings instead of integers.
#
# Let's think about some foods, how about:
#
# - food1 = 🍎
# - food2 = 🍪
# - food3 = 🍎

# In[48]:

food1 = 'apple'
food2 = 'cookie'
food3 = 'apple'
print( "food1=", food1,"; food2 =", food2,"; food3 = ", food3 )

print( "Is food1 equal to food2?")
print( food1 == food2 )

print( "Is food1 equal to food3?")
print( food1 == food3 )

# ### Are these things different?

# #### This is Logical... NOT!
# We can also test to see if two values are not equal using the '!=' operator.

# In[49]:

print( "food1 =", food1,"; food2 =", food2,"; food3 =", food3 )

print( "Is food1 not equal to food2?")
print( food1 != food2 )

print( "Is food1 not equal to food3?")
print( food1 != food3 )

# This gives us the opposite of what we had before.
#
# So, what did we learn?
#
# 🍎 == 🍎 = *True*
#
# 🍎 != 🍪 = *True*

# ### How do these things compare?

# #### Math Comparisons 101
# We can also compare the magnitude of values using '<', '<=', '>'and '>=', which will return 'True' if the condition is being met.

# In[50]:

print( "kitten =", kitten, "; dog =", dog, "; bee = ", bee )

# In[51]:

print( "Is kitten less than dog?")
print( kitten < dog )

print( "Is kitten less than or equal to dog?")
print( kitten <= dog )

print( "Is kitten greater than or equal to dog?")
print( kitten >= dog )

print( "Is kitten greater than dog?")
print( kitten > dog )

# ```{note}
# We do have to watch out for our types. Characters and numerics are **not** the same.
# ```
#

# In[52]:

TheCharacters = "10"
TheNumbers = 10

print( "Is TheNumbers equal to TheCharacters?")
print( TheNumbers == TheCharacters )
print( "TheNumbers type is ", type( TheNumbers ), "; and TheCharacters type is ", type( TheCharacters ) )

# We can compare integers and floats (!) but not other disparate data types.
#
# If you let python take care of your data-types, be warned that they could be different from what you think they are!

# ```{note}
# varible = varible is **not** the same thing as variable == variable
#
# varible = varible will **always** return true
# ```

# ### Multiple Comparisons

# We can make multiple comparisons at once by stringing the statements
# * and
# * not
# * or
#
# together.
#
# The individual testable (true/false) components need to be broken apart. For example,
# * If the *V* CATA bus is coming around the corner, then I need to run towards the bus stop.
#
# requires several things for it to be true and to require running.  We can break these things out with:
# * If there is a vehicle coming around the corner **AND** that vehicle is a CATA bus **AND** that CATA bus is a V
#     * then I need to run towards the bus stop
#
# We will only run towards the bus stop if all of the statements are true.

# #### AND

# ```{note}
# the **and** operator will return True if all of the conditions are met
# ```

# Let's create another scenario for this around clothes. For this, let's assume:
#
# face = 😎
#
# shirt = 👕
#
# pants = 👖
#
#
#
#
#

# In[53]:

face = "sunglasses"
shirt = "tshirt"
pants = "jeans"

print ( "Am I wearing sunglasses and jeans?" )
print (face == "sunglasses")
print (pants == "jeans")
print( (face == "sunglasses") and (pants == "jeans") )

print ( "Am I wearing sweater and jeans?" )
print (shirt == "sweater")
print (pants == "jeans")
print( (shirt == "sweater") and (pants == "jeans") )

# We can also string as many comparisons together as we want.

# In[54]:

print( (1 < 2) and (1 < 3) and (1 < 4) and (1 < 5) and (1 < 6) and (1 < 7) and (1 < 8) )

# #### OR

# ```{note}
# the **or** operator will return True if at least *1* of the conditions is met
# ```

# In[55]:

print( "face =", face, "; shirt =", shirt, "; pants = ", pants )

print ( "Am I wearing sunglasses or jeans?" )
print (face == "sunglasses")
print (pants == "jeans")
print( (face == "sunglasses") or (pants == "jeans") )

print ( "Am I wearing sweater or jeans?" )
print (shirt == "sweater")
print (pants == "jeans")
print( (shirt == "sweater") or (pants == "jeans") )

# #### Not

# ```{note}
# the **not** will reverse or switch the  meaning of the and/or operators
# ```

# In[56]:

print( "face =", face, "; shirt =", shirt, "; pants = ", pants )

print ( "Am I wearing sunglasses and not jeans?" )
print (face == "sunglasses")
print (not (pants == "jeans"))
print( (face == "sunglasses") and not (pants == "jeans") )

print ( "Am I wearing jeans and not a sweater?" )
print (not (shirt == "sweater"))
print (pants == "jeans")
print( not (shirt == "sweater") and (pants == "jeans") )

# ### Your Turn!

# Try to fill in code to fulfill the request!  Here are some variables used in the exercise

# In[57]:

dogA_color = 'brown'
dogA_mass = 42
dogA_sex = 'male'
dogA_age = 5
dogA_name = 'chip'

dogB_color = 'white'
dogB_mass = 19
dogB_sex = 'female'
dogB_age = 2
dogB_name = 'lady'

# Is dogA the same color as dogB? (False)

# In[58]:

# Example:
print( dogA_color == dogB_color )

# Does dogA have the same name as dogB? (False)

# In[59]:

# Try it out here:

# Is dogA older than dogB? (True)

# In[60]:

# Try it out here:

# Is dogA the same sex as dogB? (False)

# In[61]:

# Try it out here:

# Is dogA heavier than dogB and have a different name than dogB? (True)

# In[62]:

# Try it out here:

# Does dogA have a different age than dogB and not a different sex than dogB? (False)

# In[63]:

# Try it out here:

# ## If-Else Conditions

# We can condition our data using if-else statements and switch cases.  If-else statements allow us to do different things if a certain criterion is met or not. We can count the odds and evens in our someNumbers list.

# ### if

# The *if* statement starts with if and then lists a condition that may or may not is met. If the condition is true, we do what is listed. If it is not, we move on.
#
# Our example here is straightforward; if answer is greater than 30, print something.

# In[64]:

answer = 42

if answer > 30:
    print( "This number is greater than 30")

# OK, same concept.

# In[65]:

answer = 42

if answer > 50:
    print( "This number is greater than 50")

# ```{note}
# Note the structure of a Python if/else statement where some languages use { } to denote the start and end of the if/else statement. Python uses spaces.
#
# if (condition): <-colon
#
#  <- space or tab
#
# Anything that is also spaced or tab is *part* of the if statement.
#
# ```

# #### Where the if Starts and Ends

# As mentioned in our note, the if/else statement uses spacing to indicate where it starts and ends. To highlight this, let's look at an example.

# In[66]:

print("Into the If/Else!")

if (10 < 2):
    print("In the If/Else!")

    print("Still in the If/Else!")

    print("How do I get out of here!?")

print("Out of the If/Else!")

# ### else

# In these examples, only the numbers that are greater than 30 and 50 will get any response.  We can add a response for values that do not meet the conditional statement found within the if using an *else* statement.

# In[67]:

answer = 42

if answer > 30:
    print( answer, "> 30")
else:
    print( answer, "< 30")

if answer > 50:
    print( answer, "> 50")
else:
    print( answer, "< 50")

# ### elif (else if)

# If-else statements can also be stacked together to allow for additional sorting using multiple conditions.  The way this is done in python is by using
# ```python
# elif
# ```
#
# This will chain conditions, but once one condition is true. It will stop ✋
#
# Let's take a look at an example.

# In[68]:

favoriteColor = "Yellow"

if (favoriteColor == "Red"):
    print ("My favorite color is red.")
elif (favoriteColor == "Orange"):
    print ("My favorite color is orange.")
elif (favoriteColor == "Yellow"):
    print ("My favorite color is yellow.")
elif (favoriteColor == "Green"):
    print ("My favorite color is green.")
elif (favoriteColor == "Blue"):
    print ("My favorite color is blue.")
elif (favoriteColor == "Indigo"):
    print ("My favorite color is indigo.")
elif (favoriteColor == "Violet"):
    print ("My favorite color is violet.")
else:
    print ("I don't have a favorite color.")

# ## Loops

# One of the programming features is that we have many options to do the same tasking multiple times.  The three methods we will be looking at are:
# * Functions (later notebook)
# * For loops
# * While Loops
#

# ### For Loops

# Loops allow us to do the same thing to each item in a list or array. One of the most basic types of loops is a *for loop* - this allows us to iterate over any sequence.
#
# We set up a for loop using 2 things:
# * loop variable - the value of the sequence currently being used
# * sequence - the data we iterate over
#
# The sequence can be any list.  We set up *for loop* using the *for* and *in* keywords, a colon, and all of the code within the *for loop* indented.

# In[69]:

exampleList = ['a', 'niner', 6, 6.1, 'V@@@', 1001/2, 42]

print( exampleList )

# Now, before we talked about accessing elements in a list or array by their index. Meaning, if we wanted to print this out, we would need to...

# In[70]:

print( exampleList[0] )
print( exampleList[1] )
print( exampleList[2] )
print( exampleList[3] )
print( exampleList[4] )
print( exampleList[5] )
print( exampleList[6] )

# #### Looping Over Values

# Very time consuming and frustrating 😤.
#
# Loops make this sooooooo much easier. There are three parts to a *for loop*.
#
# ```python
#
# for variable_name_we_make_up in our_list_name:
#     do_something_with_each_value( variable_name_we_make_up )
#
# ```
#
# As stated, variable_name_we_make_up is something we makeup and is used to represent the value as we loop through our, well,... loop.
#
# ```python
# groceryList = ["apple", "banana", "eggs"]
# ```
#
# Remember me?

# In[71]:

groceryList = ["apple", "banana", "eggs"]

for itemInOurList in groceryList:
    print (itemInOurList)

# Like mentioned, we name the variable. Here is the same idea again.

# In[72]:

groceryList = ["apple", "banana", "eggs"]

for steve in groceryList:
    print (steve)

# Going back to our original list. See how much easier it is to print these values?

# In[73]:

for item in exampleList:
    print (item)

# #### Looping Over Indices

# Sometimes, it's helpful to iterate using indices.  For example, linear algebra heavy calculations will almost always use indices to make working with vectors and matrices easier.
#
# We can use the
# ```python
# len()
# ```
# and
# ```python
# range()
# ```
#
# functions to show the length and create indices.  We can then iterate using the index rather than the values. Let's show off these functions.

# In[74]:

groceryList = ["apple", "banana", "eggs"]
print ( len(groceryList) )

# In[75]:

print ( range(3) )

# ```{note}
# *range()* can be a bit misleading. The range is always one less than what you might expect. Meaning, *range(0,3)* goes from 0 to 1 to 2 to... that's it. So when using *range()* think about it as *range(starting number, ending number - 1)*
# ```

# In[76]:

for index in range(len(groceryList)):
    print("index:",index,"value:",groceryList[index])

# You may have noticed that the second line is indented.  Like we saw before with If/Else statements. This indent is how we indicate what is in the loop.  Our loop can have many lines (all indented).  The first line that isn't indented indicates we are out of the loop.  This indent is the python syntax for in and out of the loop; other coding languages use other things such as braces {}.  Note that blank lines don't matter, just indentation.

# In[77]:

print( "Starting the loop" )
for val in groceryList:
    print( "\t", "item:", val )

    print( "\t", "Inside the loop" )
print( "Outside the loop" )

# ### While loops

# For loops are used when you have something you are iterating over - you know the length.  You can use a while loop if you don't know the number of times something will run. The while loop code requires a conditional statement; the loop will continue to run as long as this is true and will not run again as soon as it is false.

# ##### Conceptual Example
# You can think about taking a test in two different ways.
#
# > Scenario: You are looking through your junk drawer for your sunglasses
#
# For loop:
# ```python
# for item in junk_drawer:
#     if (item == "sunglasses"):
#         "put them on" 😎
#     else:
#         "keep looking"
# ```
#
# While loop:
# ```python
# while item != "sunglasses":
#     "keep looking"
#     item = "some item in the junk drawer"
# "put them on" 😎
# ```
#
# Can you see where each has their unique take on looping? Of course, you don't; you are wearing sunglasses indoors. Take them off first, then check out their uniqueness.

# The condition being set by the while statement will cause this to run as long as the statement is true.

# In[78]:

counting = 0

while (counting < 10):
    print ( "before:", counting )
    counting = counting + 1
    print ("\t","after:",counting)

# One thing to note is that the while loop won't ever be entered if the condition is false when the statement begins as false.

# In[79]:

startAtTen = 10

while (startAtTen < 10):
    print ( "before:", startAtTen )
    counting = counting + 1
    print ("\t","after:",startAtTen )

# ###### 😈 A VERY MEAN Example 😈

# Let's see where we can use this type of loop, in this 😈 VERY MEAN Example 😈. We are creating a set of 30 random numbers from 1 to 50. The *while* will run until it hits its first even number and print this out. Can you spot its MEAN intention?

# In[80]:

import random
randomList = [random.randrange(1, 50, 1) for i in range(30)]
print ( randomList[0:5] )

index = 0
print ("start loop")
while ( randomList[index] % 2 ):
    index = index + 1
print ( "the first even number is:", randomList[index])

# So why is this very mean?! Look at our warning.

# ```{warning}
# While loops will keep iterating as long as the statement stays true.  Infinite loops are caused by a condition that always stays true.  Use the stop button ( 🔲 but filled in ) to stop this erroneous code. Here is an example of this type of code.
# ```

# ```python
# counting = 0
#
# while (counting < 0):
#     print ( "This the loop that never ends. Yes, it goes on and on, my friend!" )
#     print ( "Some people started looping it not knowing what it was, " )
#     print ( "and they'll continue looping it forever just because..." )
#     counting = counting + 1
# ```

# This is 😈 A VERY MEAN Example 😈 because it is possible to have a set without a single even number. The odds of picking an even or an odd is a coin flip (50%). Now do this 30 times. What are the odds of flipping a coin 30 times without a single "Tails?"
#
# $\frac{1}{2}$ = 1 coin
#
# $\frac{1}{2} * \frac{1}{2}$ = 2 coins
#
# $\frac{1}{2} * \frac{1}{2} * \frac{1}{2}$ = 3 coins
#
# $(\frac{1}{2})^n$ = n coin
#
# $(\frac{1}{2})^{30}$ = 30 coin = $(\frac{1}{1073741824})$  OR one in 1 billion, 73 million, 741 thousand, 824.
#
# Meaning, a person out of 1073741824 will have an infinite loop!
#
# MUAHAHAHA!!!

# ### Your Turn!

# Try to fill in code to fulfill the request!  Here is a variable used in the excercises

# In[81]:

aListOfNumbers = [6, 3, 4, 5, 7, 8, 9 ]

# Write a function that returns the length of aListOfNumbers as well as the maximum value. Hint: max() is a built-in function

# In[82]:

# Try it here:

# Use a for loop to add up all of the numbers in aListOfNumbers.

# In[83]:

# Try it here:

# Use a while loop to find the first number in aListOfNumbers that is both greater than 5 and a multiple of 4.

# In[84]:

# Try it here:

# Count the number of values in aListOfNumbers that are:
# * even
# * odd and divisible by three
# * odd and not divisible by three
#
# using if, elif and else.

# In[85]:

# Try it here:

# Create a dictionary with keys 1-8 corresponding to the words one, two, three, etc. Loop through aListofNumbers to print out the word corresponding to the digit and provide a default value of 'Not Found' if the key is not contained within the dictionary.  You should get: six three four five seven eight Not Found

# In[86]:

# Try it here:

# ## Loading a Library

# Module or Library?

# Modules are python's way of organizing functions, variables and constructors, similar to libraries in other languages.  In this section, we will look at:
# * Using existing python modules
# * Building our own modules
# * Finding the things that are within modules

# ### Built in Modules

# Python uses modules to make additional functionality available.  Modules can be thought of as libraries with many functions, data types, and characteristics that can be used once loaded.

# We load modules using the import statement:
# * Highly recommend import using a name (import module as name)
# * Use the name to keep multiply defined functions separate
# * You can import only individual functions from a module
# * You can also rename functions.

# In[87]:

# Import all functions using a name
import numpy as np
# We then use the name to refer to functions from this module
print( np.sin( 1./2. * np.pi ) )

# We can also import just some of the functions, as well as change their names
from math import cos as mathCos
print( mathCos( np.pi ) )

# Some common python modules are:
# * numpy
# * matplotlib
# * math
# * scipy
# * pandas
#
# Modules based on their topic can be found: https://wiki.python.org/moin/UsefulModules

# Some modules are already included on the system.  You may have to add or update some yourself. Python uses pip for module addition, which includes dependencies. Typically users will put modules in their own space using --user, rather than install them globally. For example, to add cython and to update matplolib you would run in a cell:
# ```javascript
# !pip install cython --user
#
# !pip install matplotlib --user --upgrade
# ```

# We can also use dir to see what is currently available to use:

# ### Your Turn!

# Call the math version of tan() mathTan and print out tangent of pi/2.  (Hint, pi can come from math or numpy).

# In[88]:

# Try it here

# Does numpy include functions called log10 and banana?

# In[89]:

# Try it here

# ## Creating a Function

# Functions allow us to do repeated tasks easily by writing the code only once.  Functions will have a name, inputs, and outputs and can be called anywhere the task is repeated.
#
# There are functions that are built into python; for example, we have already been using the type() function, which tells us the type of variable we are using.  Note that print is also a function!

# In[90]:

aVal = 10.0
print( type( aVal ) )

# Functions have four typical parts:
# * Name - what you call your function
# * Input arguments - what you provide
# * Outputs - what the function gives back
# * Math/Magic - what the function does

# ### Creating Our Own Function

# In python, we use def to define a function with the function name and inputs followed by a colon.  The python function is then separated from the rest of the code by a tab. Some languages use braces rather than indentation.
# ````python
# def functionName( inputs ):
#     # Operate on the inputs
#     ouputs = inputs + 5
#     # Return what we want to back
#     return outputs;
#    ````

# Let's look at an example function, which changes degrees Fahrenheit to Celsius.

# In[91]:

def changeFromFToC( farVal ):
    cVal = (farVal - 32.0) * 5.0 / 9.0
    return cVal

# Here, our function name is *changeFromFToC*, the input is *farVal*, the temperature in Fahrenheit, the output is *cVal*, and the temperature in Celsius. We can print or store the output from the function.  Note that the function has to be defined before we use it - the cell with the function definition has to have run before we can call the function.

# In[92]:

print( "Change 14 deg F to Celsius" )
print( changeFromFToC( 14 ) )

print( "Change from 68 deg F to Celsius" )
niceTempC = changeFromFToC( 68 )
print( niceTempC )

# Your turn! What is the temperature today? Convert it to Celsius.
#
# For those who have the temperature in Celsius and want to convert it to Fahrenheit. Define a new function to do this.

# #### Multiple inputs and outputs

# Here is an example of multiple outputs. We can actually work the output in a couple of different ways.

# ##### Multiple Output Function

# In[93]:

def changeFromFToCAndK( farVal ):
    # Change the temperature from Fahrenheit to Celsius and Kelvin
    cVal = (farVal - 32.0) * 5.0 / 9.0
    kVal= cVal + 273.15
    return cVal, kVal

# ##### Output: List

# In[94]:

def changeFromFToCAndK( farVal ):
    # Change the temperature from Fahrenheit to Celsius and Kelvin
    cVal = (farVal - 32.0) * 5.0 / 9.0
    kVal= cVal + 273.15
    return cVal, kVal

print( "Change 14 deg F to Celsius and Kelvin" )
print( changeFromFToCAndK( 14 ) )

print( "Change 32 deg F to Celsius and Kelvin" )
freezing = changeFromFToCAndK( 32 )
print( freezing[0] )
print( freezing[1] )

# ##### Output: Multiple Variables

# In[95]:

print( "Change 212 deg F to Celsius and Kelvin" )
boilingC, boilingK = changeFromFToCAndK( 212 )
print( boilingC )
print( boilingK )

# ##### Multiple Input Function

# In[96]:

def changeFromFToCOrK( farVal, tempType ):
    if (tempType == "C"):
        return (farVal - 32.0) * 5.0 / 9.0
    elif (tempType == "K"):
        return ((farVal - 32.0) * 5.0 / 9.0) + 273.15
    else:
        return "invalid temperature type"

# In[97]:

print ( changeFromFToCOrK(70,"C") )

# In[98]:

print ( changeFromFToCOrK(70,"K") )

# In[99]:

print ( changeFromFToCOrK(70,"W") )

# #### Function Gotcha! 😆

# ```{note}
# The biggest gotcha on functions is with variable scope:
# * Variables defined in a function are not accessible from the outside
# * Functions have access to more than just the variables passed in
# ```

# In[100]:

def addAnAnimal( animal ):
    print ("\t","in the function")
    print ("\t","I have access to dog:",dog)
    print ("\t","I have access to animal:",animal)
    newValue = animal + 1
    print ("\t","I have access to newValue:",newValue)
    return newValue

print ("outside the function")
dog = 10
print("dog:", dog)
print ("function output:",addAnAnimal( dog ))

# If we would add:
#
# ```python
# print (newValue)
# ```
#
# to the bottom, we would end up with this:

# ```python
# def addAnAnimal( animal ):
#     print ("\t","in the function")
#     print ("\t","I have access to dog:",dog)
#     print ("\t","I have access to animal:",animal)
#     newValue = animal + 1
#     print ("\t","I have access to newValue:",newValue)
#     return newValue
#
# print ("outside the function")
# dog = 10
# print("dog:", dog)
# print ("function output:",addAnAnimal( dog ))
# print (newValue)
# ```
#
# outside the function
#
# dog: 10
#
#       in the function
#
#       I have access to dog: 10
#
#       I have access to animal: 10
#
#       I have access to newValue: 11
#
# function output: 11
#
# ```python
# ---------------------------------------------------------------------------
# NameError                                 Traceback (most recent call last)
# <ipython-input-32-07cce689eb00> in <module>
#      11 print("dog:", dog)
#      12 print ("function output:",addAnAnimal( dog ))
# ---> 13 print (newValue)
#
# NameError: name 'newValue' is not defined
# ```
#

# ### Your Turn!

# Try to fill in code to fulfill the request!  Here is a variable used in the excercises.

# In[101]:

aListOfNumbers = [6, 3, 4, 5, 7, 8, 9 ]

# Write a function that returns the length of aListOfNumbers as well as the maximum value. Hint: max() is a built-in function

# In[102]:

## try here!