Source

Day 2 - Advent of Code 2018

Description

String manipulation functions for an “inventory management system.”

My solution

1
import pytest

Simple checksum

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
def contains_n_any(mystr, n):
    '''
    Return True if `mystr` contains any character repeated exactly
    `n` times, otherwise false.
    '''
    if not mystr or n <= 0:
        return False
    for char in mystr:
        if mystr.count(char) == n:
            return True
    return False


def checksum(strs, reps):
    '''
    For each integer `n` in an iterable `reps`, count the number of 
    items in the iterable `ids` that contain any character repeated 
    exactly `n` times. Return the product of these counts, or 0 if 
    `ids` or `reps` is empty.
    '''
    if not strs or not reps:
        return 0
    ret = 1
    for r in reps:
        count = 0
        for s in strs:
            if contains_n_any(s, r):
                count += 1
        ret *= count
    return ret


def test_invalid_input():
    assert not contains_n_any('', 1)
    assert not contains_n_any('foo', -1)
    assert not contains_n_any('bar', 0)
    assert not checksum([], [])
    assert not checksum([], 1)
    assert not checksum(['foo'], [])


def test_one_char():
    assert contains_n_any('a', 1)
    assert not contains_n_any('a', 2)
    assert checksum(['a'], [1]) == 1
    assert checksum(['a'], [0, 1]) == 0
    assert checksum(['a'], [1, 1]) == 1


def test_two_chars():
    assert contains_n_any('ab', 1)
    assert checksum(['ab'], [1, 2]) == 0
    assert contains_n_any('aa', 2)
    assert checksum(['aa'], [1, 2]) == 0
    assert not contains_n_any('aa', 1)
    assert not contains_n_any('ab', 2)

Difference of exactly one character

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def off_by_one(str1, str2):
    '''
    Return True if `str1` and `str2` are the same length and they differ
    by exactly one character, otherwise False.
    '''
    if len(str1) != len(str2):
        return False
    count = 0
    for idx, char in enumerate(str1):
        if str2[idx] != char:
            count += 1
            if count > 1:
                return False
    return True if count == 1 else False


def test_off_by_one():
    assert not off_by_one('abcde', 'abcdef')
    assert not off_by_one('abcde', 'fghij')
    assert not off_by_one('abcde', 'abcfg')
    assert off_by_one('fghij', 'fguij')

Common characters

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def common_chars(str1, str2):
    '''
    Return a string containing all characters that appear in the same
    index position in `str1` and `str2`.
    '''
    ret = ''
    for idx, char in enumerate(str1):
        if str2[idx] == char:
            ret += char
    return ret
 

def test_common_chars():
    assert common_chars('foo', 'bar') == ''
    assert common_chars('fghij', 'fguij') == 'fgij'

“Match” strings that differ by one exactly character

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def match(strs):
    '''
    Return a string containing the characters shared by the first two
    elements of `strs` that differ by exactly one character, or an 
    empty string otherwise.
    '''
    strslen = len(strs)
    for i in range(strslen):
        str1 = strs[i]
        for j in range(i + 1, strslen):
            str2 = strs[j]
            if off_by_one(str1, str2):
                return common_chars(str1, str2)
    return ''


def test_match():
    assert match(['abcde', 'fghij']) == ''
    assert match(['fghij', 'fguij']) == 'fgij'
    assert match(['abcde', 'fghij', 'lmnop', 'qrstu', 'fguij']) ==  'fgij'

Tests provided with challenge

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
def test_provided():
    # abcdef contains no letters that appear exactly two or three times.
    assert not contains_n_any('abcdef', 2)
    assert not contains_n_any('abcdef', 3)

    # bababc contains two a and three b, so it counts for both.
    assert contains_n_any('bababc', 2)
    assert contains_n_any('bababc', 3)

    # abbcde contains two b, but no letter appears exactly three times.
    assert contains_n_any('abbcde', 2)
    assert not contains_n_any('abbcde', 3)

    # abcccd contains three c, but no letter appears exactly two times.
    assert contains_n_any('abcccd', 3)
    assert not contains_n_any('abcccd', 2)

    # aabcdd contains two a and two d, but it only counts once.
    assert contains_n_any('aabcdd', 2)

    # abcdee contains two e.
    assert contains_n_any('abcdee', 2)

    # ababab contains three a and three b, but it only counts once.
    assert contains_n_any('ababab', 3)

    ids = ['abcdef', 'bababc', 'abbcde', 'abcccd', 
           'aabcdd', 'abcdee', 'ababab']
    assert checksum(ids, [2, 3]) == 12

    ids = ['abcde', 'fghij', 'klmno', 'pqrst', 
           'fguij', 'axcye', 'wvxyz']    
    assert match(ids) == 'fgij'

Run all tests

1
pytest.main([__file__])

Solution using data provided with challenge

1
2
3
4
with open('./input.txt') as f:
    ids = f.readlines()
    print('Solution to part 1:', checksum(ids, [2, 3]))
    print('Solution to part 2:', match(ids))