Sunday, January 17, 2016

Days 7 & 8 - Rest, and challenge

Day 7, Saturday, was our day of rest.  I still worked on my columns for US News and Time for a bit of Saturday morning.  Saturday afternoon, though, was just rest and relaxation.

Day 8, Sunday, was a fiasco.  I was up early, made food for the week, did some laundry...and broke my little budge app. The rest of the day was spent fixing the budget app.  It's 7 pm and I've manage to restore the app to yesterday's functionality with a few marginal improvements.  On the bright side, I have a large, well-seasoned pot of beans and some clean laundry.

What did I learn today?  Don't push every element of a program to the limit (or beyond) of your capabilities.  When something breaks, you don't understand all the elements well enough to follow what's going on around the part that broke.

Also: learn version control so this doesn't happen again.

Update: 8:45 pm

I went for a short walk, came back and figured out some more useful elements.  One of my goals for this code school project is to demonstrate that I can still work through setbacks.  The hardest part, for me, is not taking failed attempt at coding as a judgement on my worth and skill.  I want to get to where I care deeply about making clean, functional, elegant code but I don't see every mistake as an indictment.  It's a hard process, and it's ok to make mistakes.  The key is process: be methodical, document, test, move on.  Also, version control.

Friday, January 15, 2016

An all-day project

I made this (link to python file on dropbox). It won't run in the trinket.io page, but you could look at the code if you were so inclined.  The (minimum) design specs are in the red text surrounded by ''' (i.e. the long comments at beginning and end)

"It" it is a basic budget app.  You interact with it via command line (as with everything else I've made in python so far).  It's only 217 lines, and half of those empty space, but it's some of the densest code I've written.  It's mostly an exercise in memory management using dictionaries and data i/o using csv files.

Features:

1. Asks user for item/cost/recurring and stores that info in hash, using a counter variable as the key.

2. Maintains a speed-lookup dictionary with name: key pairs (where name is the name of the item...important since some app functions require lookup by item name and this allows quick lookup of where an item is stored.

3. All functions (save for a few specialized uses) take a state variable as their only argument.  The state variable contains the dictionary of items in the budget, the counter variable for generating keys, the speed lookup dictionary, the file name that is the source of any data, or the destination of data to be written.

4.  Instead of a simple while loop that ends when the q(for quit) key is pressed, q calls a function that saves the current budget inventory in a "temp.csv" file and uses "raise SystemExit()" to terminate the program.  I remember losing an hour of work once when using a crappy old Pascal program to process data, and I didn't quit properly.

5. A dictionary for switching among functions based on user input.  The switch uses dict.get() so that I can handle edge cases with an "error" function that tells the user to re-enter their choice.

6. a file import function that opens a csv and imports it into the dictionary used in the script.  The script will parse the key values written in the source file, and set the state counter to the max key value among the imported data.  (The "add new item" function increments the counter before an item is actually added).

7.  Item delete, with the user being able to type in the item name.  Again, a get function provides some edge case handling.

Future:

1. a user input checking/cleaning module
2. differentiate between input/output files
3. dynamic column names/dictionary keys depending on user input and/or csv contents
4. delete column based on user input
5. forecasting expenses.

Not bad for one day's work at this stage.

Thursday, January 14, 2016

Day 5 - Learning clean code...and I make a thing unnecessarily recursive

Another moment of undiluted joy today: I built a recursive function, mostly from scratch.  That was one good lesson.  The other was ensuring that each function does one thing well...and generally one thing only.  That makes the function more portable, and makes code easier to understand and to troubleshoot.  

The project itself was a fairly simple exercise in opening and closing files and writing functions.  Given a file of 1000 lines, split it into two new files of 500 lines.  Doing so was pretty straightforward, and a little bit more work got it cleaned up (after a discussion of functional hygiene, so to speak).  I've had a tendency to treat functions like sub-scripts and just list a bunch of operations until a function "felt" unwieldy, then call in a new one to finish the job.  

I like this approach better.  I'm getting a better feel for how code works, and how the pieces fit together like legos.  You can build better stuff using legos, when you have lots of little pieces that each do one thing well.  Having large, unwieldy, highly-specialized pieces may let you build one thing well (like a space ship) but isn't very useful for building any of the other things you can imagine.

And yes, I went on to build a recursive version of the file-splitting program.  It will take a file of any size, and split it into files of a specified number of lines.  I know there's limitations to this approach (it's resource intensive, it's hard to read, it's slower than simpler versions that do the same thing...).  The point, though, is that I made a working recursive program, and I did it by following the arguments through the logical steps needed to make it work. I didn't give up when my script ran without errors, but terminated without producing the files I expected.  (That was an interesting new troubleshooting experience.)

So.  We're getting into recursive functions next week, formally speaking.  I'm relieved to know I can make one work.  I'm looking forward to learning the situations in which it's useful.

In other news, I woke up at 6 this morning with a great idea to make my "variable_catcher"aka "error_checker" function work better.  I had enough time to test my idea in the terminal, and it may work.  I didn't have time to play with it though.  Tomorrow is supposed to be review...so perhaps then.





Wednesday, January 13, 2016

Day 4 - churning

Day for was all about nested loops and basic functions.  Although I already have a good handle on basic function definitions, I learned some good lessons on making code more concise...as well as the perils of such.  For example: suppose you want to collect the name, age, and city of origin of a user, and store that in a list.  You might write a function such as:

def user_info():
    name = raw_input("Q1 - What is your name?")
    age = raw_input("Q2 - What is your age?")
    city = raw_input("Q3 - What is your city?")
   
    return [name, age, city]
 A shorter way is to use:
 def user_info():
     return [raw_input("Q1..."), raw_input("Q2..."), raw_input("Q3...")]

The exercise was intended to emphasize refactoring code to make it cleaner.  Once I'd run through the basics, though, I went to look at edge cases such as: What if someone puts in numbers in the city name "Bost0n" or writes out their age (when your code expects integers)?  Embedding the 'raw_input' functions in the returned array makes it impossible to do any error checking immediately after data is entered.  You could write a separate "cleaning" function to catch errors in the array, and to prevent that data from contaminating other functions.  More useful, though, is to immediately detect problems in the entered data and to prompt the user to correct their entry until they get it right.

I chose the latter path, and created two short "error checking functions", one to screen names, and one to screen integers.

>>>while age.isdigit() == False:
      ....age = raw_input("Please enter your age using numbers.")

To screen the name answers, however, I went to Stack Overflow and searched for methods of detecting punctuation and/or numbers in a string. (I knew I could do it by iterating through the string and testing each character, but that's pretty inefficient.)  Instead, I chose the regex function in the linked example and modified it for my purposes

def only_letters(input_string):
   """Returns 'False' if any character other than lowercase letters a to z are in the string."""
    match = re.match("^[a-z]*$", input_string.lower())
    return match is not None
while only_letters(name) == False:
   name = raw_input("Please use only letters in spelling your name")

The drawback of this approach is that I didn't write the regex function, and I couldn't explain very well how it works.  Also, names with apostrophes would be flagged as inappropriate, and it would not accept unicode or non-english-standard-letters.  By this point, though, I'd also added in functionality to include an arbitrary number of people for data collection, each with their own dictionary entry, using their name as a key (although I could have used a unique number as the key), and a print function to display the collected information neatly.

User 1:    Name: Jane    Age: 44    City: Boston
User 2:    Name: Bob    Age: 33    City: Seattle

So, that was my disappearing day.  There were other things: researching the inability of my error_checker script to work properly as an imported module, working through some other 'extra credit' exercises, trying to follow a recursion exercise...plus a long awaited trip to the supermarket to pick up some groceries.

Tuesday, January 12, 2016

Day 3 - Getting looped

I got a 'coding buzz' today.  I'm not sure if that's even a thing, but I had a moment where a difficult challenge was falling into place, and I'd just run through a set of coding challenges in record time, and I really felt in a 'flow state' with coding.  It was joyous.  I've been terrified of making this change, and doubting whether I really belong, whether I'm good enough, whether I can "think like a coder".  Maybe I drank too deep of the elitist kool-aid on various forums wherein the cognoscenti opine on who is fit to be a coder, and who is merely a poser hacking at web frameworks.  One good day is certainly not enough to base a career on, but...it felt so good.

Today was all about loops and data structures.  As it turns out, this is a topic with which I'm pretty comfortable, and one which I've used a bit in project Euler and project Rosalind.  I got a little hung up on a set of nested dictionaries, and aggregating all the numbers hidden in values in keys on various levels.

Instead, I used my extra work time to build a small debugging tool that displays all the variables, their types, and their values at a give point in a script.  The idea came from a moment in lecture about following a variable through a script and keeping track of the type and value associated with a particular namespace by using a table:

a = 5
b = a
c = str(a)
name          type           value
a                 int              5
b                 int              5
c                 str               '5'

(yeah, it's a trivial example, but its 930 pm after a long day and still have some things to read for tomorrow).

To automate this process for more complex code, I wrote a series of functions that would collect the variables currently in use (using dir(), which supplies the names in use), remove the 'magic' variables since those clutter the table of variables declared by the user, and collect the type and value of said variables, and then display that all in a table as a print-out in the command line. (credit to Jonathan for introducing the table display and Jeremy for suggesting the dir() function as a means of collecting variable names).

The first version of this was assembled at the end of one of the coding challenges.  It was a mess, with global variables called in multiple places.  Plus, having this code at the end meant that any error that occurred higher in the script would would never be caught by my variable-catcher.

The next iteration cleaned up the global variables into functions...whereupon I discovered that dir() called within a function finds only the local variables.  Version 0.3 saw all the function definitions moved to the top of the script (so they would be available when called anywhere below).  The whole process was now called in a function 'print_table' that took dir() as it's only parameter.  That way, "print_table(dir())" could be inserted at an arbitrary point and dir() would harvest the names in the scope in which it was called.  The list generated could then be processed and displayed.

Version 0.4 saw the various function definitions moved to an independent file (error_checker.py on github).  Now, instead of having to copy my code into a problematic python script, you can simply copy the error_checker.py file into the same directory, and add "from error_checker import *" to your code.  Finally, just type "print_table(dir())" at the point where you wish to see the names in use and the table will be printed to the terminal.

Improvements I'd like to make: output to a csv file instead of terminal, dynamically catch the 'magic' variables that should be removed, and a few cleaner implementations of search functions.

So, hardly perfect, but I'm pretty happy with having gotten it to work so well, mostly on my own (beyond the dir() suggestion, and some suggestions for making the code cleaner and more compact), and entirely in the time after the main challenge work of the day was completed.

Monday, January 11, 2016

Day 2 - The Coding Begins

Today was a lot of orientation, a good chunk of lecture, and a review of the prework material: basic elements of python like data types, string functions, conditional statements, and so on.

It was all review since I've been working with these things for a while.  Regardless, there are always things to learn.

1. Push yourself

Meeting the minimum specs today would have been easy.  On one exercise, I finished early and helped a neighbor. Later, though, I made challenges for myself.  One exercise was to build a simple questionnaire to learn more about other students.  It was to run in the command line and ask 'yes' or 'no' questions.  I built it from the start to be case agnostic and to accept 'y' or 'yes' as equivalent answers (or 'n' or 'no').    The questionnaire also accepted short sentences for some of the questions and returned an answer based on wether user input contained a certain word or phrase.  One of the questions used a while loop with a counter.  An answer recognized as correct was registered, and the loop broken.  Otherwise, 3 wrong answers would break the while loop and move on.  For a half-hour project, it was ok.  (Although I went back later that night and cleaned it up a bit and fixed a weird bug in the while loop.)

The sample is here, with code and functional in-browser implementation courtesy of trinket.io. A useable version is embedded below:

(I'm writing all this down in part to remind myself, later, of how far I've come.  I know this stuff is trivial, but one of my projects while here to work on consistently pushing myself to do more and better than what's required.)

I learned, too, by comparing notes with my neighbor and seeing that she had made a list of questions that were accessed in order whereas I had simply created a script that was executed from top to bottom with a series of conditional statements.

2.  Keep going until it looks 'right'

Python, I hear, is notable for having many alternative ways of getting something accomplished.  Not all of them are equal in terms of speed, elegance, understandability, or "python-ness".  Learning when something 'looks right' is another long term goal.  There was a long series of tests of basic skills as part of the work.  One test (completed by setting a variable r = to some statement) was

# use the 'in' operator to see if 3 is in the array
a = [1,2,3,4]
assert r == True

My first answer was

if 3 in a:
    r = True

That works, and it's easy to understand, but it takes two lines. Defining r didn't sit right, either, since it seems that r should evaluate to True. My second effort:

r = 3 in a

which satisfies both the single line aesthetics and that the value of r is determined by evaluating an expression.

Another challenge asked

#get all the keys of hash a and set it to r

a = {'a':1,'b':2}
assert r == ['a','b']

My solution was

r = [x for x in a]

I've really struggled with this syntax in using an iterator to generate a list.  Trivial, again, but I pushed myself to do it the compact way.

Tomorrow, more things.  And now to sleep.

Sunday, January 10, 2016

First Day

The first day of code school is finally done.  Code-wise, there's not much to report since today was all logistics: getting to the site, getting settled, meeting people, etc.

It's interesting to see the mix of people here.  There's only 9 students (of whom I've met 7).  I won't go into specifics (Hi everybody!)...but it is interesting that so many of us have tried various other paths and been disappointed.  I'm not the only former adjunct professor here, for one.

That's the modern mode, right?  Endless self-reinvention.  I read blog posts by people with PhDs (in microbiology, like me!) who parlayed that into being CEO of a biotech company by age 45.  Or people like my friend from high school who got a CS degree and has steadily worked his way up the ladder to where he's recently joined a startup...and he did it never having left Seattle.  The promised land.

Regardless, I'm here, and I'm very, very ready to get started.