chap 2 - python

Playing around with some python syntax, including types and comprehensions.

The first part of this is part of the homework I assigned due Jan 28.

Jim Mahoney

find an average from a csv file

The vernon_1850.csv file looks like this

lastname,firstname,age
Aldrich,Ann,46
Aldrich,Dwight,11
Aldrich,George,1
Aldrich,Henrietta,8
Aldrich,Henry,16
Aldrich,Margaret,6
Aldrich,Moses,58

But there is one catch: the ages are sometimes things like "3/12", which I think means a fraction with 12 in the bottom, so that "3/12" means "three months old".

I am assuming means "three months old", and for those cases need to calculate the number.

I converted this data from which I found at http://us-census.org/pub/usgenweb/census/vt/windham/1850/ , which I found starting from http://www.us-census.org/ .

In [2]:
from typing import List, Tuple, Any, Union, Iterable
import re

Number = Union[int, float]
StrOrNumber = Union[str, int, float]

def convert(value: str) -> StrOrNumber:
    """ strip whitespace, convert to int or float if appropriate """
    value = value.strip()
    try:
        if value.isdigit():                     # See the string.isdigit docs.
            result = int(value)
            #print(" int : result = ", result)  # Getting this to work took some debugging...
        else:
            result = float(value)
            #print(" float: result = ", value)
    except ValueError:
            result = value
            #print(f" str: result = '{result}'")
    try:         
        # Special case : turn '4/12' into a float 4/12
        # I'm using a regular expression to find <digits>/<digits>
        if re.match('^(\d+)(/)(\d+)$', result):
            (top, bottom) = result.split('/')
            result = float(top) / float(bottom)
    except:
        pass
    return result

assert convert('ab  \n') == 'ab'
assert convert('12') == 12
assert convert('12.3') == 12.3
assert convert('6/12') == 0.5

def line2values(line: str) -> Tuple[StrOrNumber] :
    """ split at commas, strip whitespace, convert to number if approprite """
    return tuple(convert(value) for value in line.split(','))

assert line2values('a, bob,1  , 2.3 \n') == ('a', 'bob', 1, 2.3)

def readcsv(filename: str) -> List[dict]:
    """ Read a .csv file with fields in the first line """
    # and convert to integers where possible
    lines = open(filename, 'r').readlines()
    result = []
    fields = line2values(lines[0])
    for line in lines[1:]:
        #print(f"line: '{line}'")
        values = line2values(line)
        result.append({field: value for (field, value) in zip(fields, values)})
    return result

def average(values: Iterable[Number]) -> float:
    return sum(values) / len(values)

assert average([1, 2, 3.0]) == 2.0
In [70]:
# Read the csv file in and look at the first few entries 
# to make sure it looks OK.

people = readcsv('vernon_1850.csv')
print(f"The number of people is {len(people)}.")
n = 10
print(f"The first {n} are :")
for i in range(10):
    p = people[i]
    print(f"  {p['firstname']} {p['lastname']} , {p['age']}")
The number of people is 821.
The first 10 are :
  Ann Aldrich , 46
  Dwight Aldrich , 11
  George Aldrich , 1
  Henrietta Aldrich , 8
  Henry Aldrich , 16
  Margaret Aldrich , 6
  Moses Aldrich , 58
  Rhoda Alger , 65
  Esther Arling , 55
  Wright Arling , 54
In [71]:
# Find the average and print it.

average_age = average([p['age'] for p in people])
print(f"Their average age is {average_age:.4f}.")
Their average age is 25.4045.

advent of code 209 day 4

Click on the link here to read the problem.

Part 1 : How many numbers have (a) increasing digits and (b) two adjacent digits?

-- examples --
yes: 334567 , 333359
no: 345678, 334401

Part 2 : How many numbers from part 1 don't have their two adjacent digits part of a longer run?

-- examples --
no: 333579
yes: 335579 

Using puzzle input range 307237 - 769058, the answers should turn out to be 889 and 589.

In [72]:
def integer2tuple(number: int) -> Tuple[int]:
    """ Convert i.e. 112234 to i.e. (1, 1, 2, 2, 3, 4) """
    return tuple(int(i) for i in str(number))

assert integer2tuple(112234) == (1, 1, 2, 2, 3, 4)

def is_increasing(numbers: Tuple[int]) -> bool:
    """ Is this tuple increasing ? """
    return all(pair[0] <= pair[1] for pair in zip(numbers[:-1], numbers[1:]))

assert is_increasing((1, 2, 3, 4)) == True
assert is_increasing((1, 1, 1, 1)) == True
assert is_increasing((1, 1, 1, 0)) == False

def has_duplicate(numbers: Tuple[int]) -> bool:
    """ Does this tuple have an adjacent pair of the same number ? """
    return any(pair[0] == pair[1] for pair in zip(numbers[:-1], numbers[1:]))

assert has_duplicate((1, 2, 3, 4)) == False
assert has_duplicate((1, 1, 3, 4)) == True
assert has_duplicate((1, 3, 3, 3)) == True

def is_part1(number: int) -> bool:
    """ Does this integer satisfy the part 1 criteria? """
    numbers = integer2tuple(number)
    return is_increasing(numbers) and has_duplicate(numbers)

assert is_part1(111111) == True     # These three tests are given in the problem description.
assert is_part1(223450) == False
assert is_part1(123789) == False
In [73]:
low = 307237
high = 769058

# How many of the numbers from low to high satisify part1 
# ... using len() and a list comprehension.

len([n for n in range(low, high + 1) if is_part1(n)])
Out[73]:
889
In [74]:
# part 2 : two numbers consecutive (a "double"), but not three the same.

def has_double(numbers: Tuple[int]) -> bool:
    """ Does this tuple have a pair which is not part of a triple? """
    # I want to look for (a,b,c,d) where b==c, a!=b, c!=d.
    # But at the ends of the tuple this won't work.
    # I'll make it work by adding extra 1st and last "bookend" values of -1,
    # then loop with an index i from 0 to 4 back from the end.
    # example : 
    #    numbers =            (1, 1, 3, 4, 5, 6)
    #    with_bookends =  (-1, 1, 1, 3, 4, 5, 6, -1)
    #    i=0 1st group :   -1, 1, 1, 3                  # i.e. "i in range(5)"
    #    i=4 last group:                4, 5, 6, -1     # and 5 is len(numbers)-1
    #    
    with_bookends = (-1,) + numbers + (-1,)
    for i in range(len(numbers) - 1):
        (a, b, c, d) = with_bookends[i: i + 4]
        if a != b and b == c and c != d:
            return True
    return False

assert has_double((1, 2, 3, 3, 5, 6)) == True
assert has_double((1, 2, 3, 3, 3, 6)) == False

def is_part2(number: int) -> bool:
    """ Does this number satisfy the part 2 rule? """
    numbers = integer2tuple(number)
    return is_increasing(numbers) and has_double(numbers)

assert is_part2(112233) == True     # tests from problem statement
assert is_part2(123444) == False
assert is_part2(111122) == True
In [75]:
# So the part 2 answer is
len([n for n in range(low, high + 1) if is_part2(n)])
Out[75]:
589