Программирование

Авторы задания: И. В. Щуров, А. Щенников.

Данный notebook является набором задач по курсу «Программирование» (Магистерская программа «Журналистика данных», НИУ ВШЭ, 2018-19).

На странице курса находятся другие материалы.

Домашнее задание №9

Максимум за ДЗ можно набрать 11 баллов.

Чтобы сдать ДЗ, его надо загрузить в nbgr-x в виде ipynb-файла. Получить ipynb-файл можно, выбрав в Jupyter пункт меню File → Download as... → IPython Notebook (.ipynb).

Задача 1

В файле electors.csv приведено количество выборщиков, закреплённых за каждым штатом США (по состоянию на 2016 год). В файле results.csv приведены искусственные результаты выборов (доля голосов за каждого кандидата по каждому штату). Считая, что все штаты инструктируют всех своих выборщиков голосовать за кандидата, набравшего большинство голосов в штате (строго говоря, это неверно: в Мэне и Небраске более сложные правила), найти победителя выборов.

Мы разбили задачу на несколько этапов и даже решили первый из них на занятии. Вам предстоит решить остальные.

Загрузка данных

Следующая ячейка скачает файлы и сохранит их в текущем рабочем каталоге. Её достаточно запустить один раз.

In [ ]:
import requests
r = requests.get("https://raw.githubusercontent.com/ischurov/dj-prog/master/elections_usa_2016/electors.csv")
with open("electors.csv", "wb") as f:
    f.write(r.content)

r = requests.get("https://raw.githubusercontent.com/ischurov/dj-prog/master/elections_usa_2016/results.csv")
with open("results.csv", "wb") as f:
    f.write(r.content)

Этап 0

На занятии мы написали функции read_electors и read_results, считывающие файлы в соответствующие структуры данных. Будем ими пользоваться.

In [ ]:
def read_electors(filename):
    """
    аргументы: 
        filename - имя файла с информацией о выборщиках
    возвращаемое значение: 
        словарь: state => electors 
            (то есть словарь с ключами — названиями штатов, и значениями — числом выборщиков)
                   state: str
                   electors: int
            пример возвращаемого значения: 
                      {'Alabama': 3,
                       'Florida': 32, ...}
    """
    with open(filename, encoding='utf-8') as f:
        electors = {}
        f.readline()
        for line in f:
            state, elect = line.split(",")
            electors[state] = int(elect)
    return electors

def read_results(filename):
    """
    аргументы: 
        filename - имя файла с результатами выборов
    возвращаемое значение:  
        кортеж (participants, table)
             participants: список строк — фамилий кандидатов
                 Например: ['Clinton', 'Trump', ...]
             table: список списков с результатами,
                 первый элемент: название штата (строка),
                 последующие элементы: процент голосов за каждого кандидата
                     в том же порядке, как в participants
                     Пример: [["Florida", 0.216, 0.618, ...], ...]
             
    """
    with open(filename, encoding='utf-8') as f:
        table = []
        participants = f.readline().rstrip().split(",")[1:]
        for line in f:
            row = line.split(",")
            new_row = [row[0]]
            for res in row[1:]:
                new_row.append(float(res))
            table.append(new_row)
    return participants, table

Этап 1 (2 балла)

Напишите функцию get_winners(participants, table), принимающую на вход список участников participants и таблицу с результатами table, в том же формате, как возвращается функцией read_results, и возвращающую словарь, у которого ключами являются названия штатов (строки), а значениями — имена победителей в соответствующем штате (строки). Пример возвращаемого словаря:

{'Florida': 'Trump',
 'Connecticut': 'Trump',
 'Georgia': 'Clinton', ...}

Подсказка. Вам пригодится Задача 6 из ДЗ№7).

In [ ]:
# YOUR CODE HERE
In [ ]:
participants, table = read_results("results.csv")
assert get_winners(participants, table) == {'Florida': 'Trump', 'Connecticut': 'Trump', 'Georgia': 'Clinton', 'Texas': 'Trump', 'Vermont': 'Clinton', 'New Mexico': 'Trump', 'Illinois': 'Clinton', 'Kentucky': 'Clinton', 'Iowa': 'Trump', 'Alaska': 'Trump', 'New York': 'Clinton', 'Massachusetts': 'Clinton', 'Arkansas': 'Trump', 'Missouri': 'Trump', 'Kansas': 'Clinton', 'Idaho': 'Clinton', 'Wisconsin': 'Clinton', 'Mississippi': 'Clinton', 'Washington': 'Clinton', 'Oklahoma': 'Trump', 'California': 'Trump', 'South Carolina': 'Trump', 'Hawaii': 'Trump', 'Maryland': 'Trump', 'Arizona': 'Trump', 'Montana': 'Trump', 'Ohio': 'Trump', 'Oregon': 'Clinton', 'Rhode Island': 'Clinton', 'South Dakota': 'Clinton', 'Alabama': 'Clinton', 'North Dakota': 'Trump', 'Virginia': 'Trump', 'New Jersey': 'Clinton', 'Wyoming': 'Trump', 'Maine': 'Trump', 'D.C.': 'Clinton', 'Tennessee': 'Clinton', 'Pennsylvania': 'Trump', 'Nebraska': 'Trump', 'Delaware': 'Trump', 'Michigan': 'Clinton', 'New Hampshire': 'Trump', 'Indiana': 'Clinton', 'North Carolina': 'Clinton', 'Colorado': 'Clinton', 'West Virginia': 'Trump', 'Utah': 'Clinton', 'Minnesota': 'Trump', 'Louisiana': 'Trump', 'Nevada': 'Clinton'}
assert get_winners(['Q', 'A', 'x'], [['alpha', 0.2, 0.5, 0.3],
                                     ['beta', 0.1, 0.3, 0.6],
                                     ['gamma', 0.5, 0.4, 0.1]]
                  ) == {'alpha': 'A', 'beta': 'x', 'gamma': 'Q'}

Этап 2 (2 балла)

Напишите функцию get_elector_votes(winners, electors), принимающую на вход словарь с информацией о победителях в каждом штате (в формате, возвращённом функцией get_winners) и данные о числе выборщиков (в формате, возвращённом функцией read_electors), и возвращающую словарь, у которого ключами являются имена кандидатов, а значениями — число голосов выборщиков, который набрал данный кандидат. Если кандидат не стал победителем ни в одном штате, его имя не упоминается в получающемся словаре.

Пример возвращаемого словаря:

{'Trump': 1234, 'Clinton': 2311, ...}
In [ ]:
# YOUR CODE HERE
In [ ]:
electors = read_electors("electors.csv")
assert get_elector_votes({'Florida': 'Trump', 'Connecticut': 'Trump', 'Georgia': 'Clinton', 'Texas': 'Trump', 'Vermont': 'Clinton', 'New Mexico': 'Trump', 'Illinois': 'Clinton', 'Kentucky': 'Clinton', 'Iowa': 'Trump', 'Alaska': 'Trump', 'New York': 'Clinton', 'Massachusetts': 'Clinton', 'Arkansas': 'Trump', 'Missouri': 'Trump', 'Kansas': 'Clinton', 'Idaho': 'Clinton', 'Wisconsin': 'Clinton', 'Mississippi': 'Clinton', 'Washington': 'Clinton', 'Oklahoma': 'Trump', 'California': 'Trump', 'South Carolina': 'Trump', 'Hawaii': 'Trump', 'Maryland': 'Trump', 'Arizona': 'Trump', 'Montana': 'Trump', 'Ohio': 'Trump', 'Oregon': 'Clinton', 'Rhode Island': 'Clinton', 'South Dakota': 'Clinton', 'Alabama': 'Clinton', 'North Dakota': 'Trump', 'Virginia': 'Trump', 'New Jersey': 'Clinton', 'Wyoming': 'Trump', 'Maine': 'Trump', 'D.C.': 'Clinton', 'Tennessee': 'Clinton', 'Pennsylvania': 'Trump', 'Nebraska': 'Trump', 'Delaware': 'Trump', 'Michigan': 'Clinton', 'New Hampshire': 'Trump', 'Indiana': 'Clinton', 'North Carolina': 'Clinton', 'Colorado': 'Clinton', 'West Virginia': 'Trump', 'Utah': 'Clinton', 'Minnesota': 'Trump', 'Louisiana': 'Trump', 'Nevada': 'Clinton'}, electors) == {'Trump': 299, 'Clinton': 239}
assert get_elector_votes(
                  {'Florida': 'Qqq', 
                   'Sietch Tabr': 'Stilgar', 
                   'France': 'Qqq', 
                   'Zzz': 'Www'},
                  {'Florida': 123, 'Sietch Tabr': 992, 'France': 12, 
                   'Zzz': 13, 'Www': 15}) == {'Qqq': 135, 'Stilgar': 992, 'Www': 13} == {'Qqq': 135, 'Stilgar': 992, 'Www': 13}

Этап 3 (1 балл)

Напишите функцию get_final_winner(elector_votes), принимающую на вход результат выполнения функции get_elector_votes и возвращающую победителя (имя кандидата, набравшего максимальное число голосов). Если есть несколько кандидатов, набравших максимальное число голосов, выбрать того, кто в elector_votes записан первым.

In [ ]:
# YOUR CODE HERE
In [ ]:
assert get_final_winner({'Trump': 299, 'Clinton': 239}) == 'Trump'
assert get_final_winner({'Trump': 229, 'Clinton': 239}) == 'Clinton'
assert get_final_winner({'Trump': 229, 'Clinton': 239, 'Alice': 2}) == 'Clinton'
assert get_final_winner({'Trump': 229, 'Clinton': 239, 'Alice': 2222}) == 'Alice'

Этап 4 (1 балл)

Напишите функцию files_to_winner(electors_filename, resuls_filename), принимающую на вход имена файлов, аналогичных по формату electors.txt и winners.txt, и возвращующую имя победителя выборов. Эта функция должна использовать функции, написанные на предыдущих этапах, и занимать не больше 6 строк (не включая def, но включая return).

In [ ]:
# YOUR CODE HERE
In [ ]:
assert files_to_winner('electors.csv', 'results.csv') == 'Trump'
with open("electors_fake.csv", "w") as f:
    print("state,electors", file=f)
    print("Aaa,4", file=f)
    print("Bbb,15", file=f)
with open("results_fake.csv", "w") as f:
    print("state,Bill,Bob,Alice", file=f)
    print("Aaa,0.2,0.5,0.3", file=f)
    print("Bbb,0.1,0.2,0.7", file=f)
assert files_to_winner("electors_fake.csv", "results_fake.csv") == 'Alice'
with open("electors_fake.csv", "w") as f:
    print("state,electors", file=f)
    print("Aaa,19", file=f)
    print("Bbb,15", file=f)
with open("results_fake.csv", "w") as f:
    print("state,Bill,Bob,Alice", file=f)
    print("Aaa,0.2,0.5,0.3", file=f)
    print("Bbb,0.1,0.2,0.7", file=f)
assert files_to_winner("electors_fake.csv", "results_fake.csv") == 'Bob'

Задача 2 (2 балла)

Написать функцию any_news_about_harry(url), принимающую на вход адрес веб-страницы url, загружающую эту веб-страницу и проверяющую, встречается ли в ней слово Harry (с большой буквы). Функция должна возвращать True, если встречается, и False в противном случае. Также функция должна возвращать False, если страницу не удалось открыть (например, была получена ошибка 404 Not Found.)

Подсказка. Чтобы загрузить страницу, нужно использовать библиотеку requests:

import requests
r = requests.get(url)

Содержимое страницы затем окажется в r.text. Проверить, что запрос увенчался успехом, можно так:

if r:
    # увенчался успехом

В этой задаче не нужно использовать BeautifulSoup.

In [ ]:
# YOUR CODE HERE
In [ ]:
assert any_news_about_harry("https://en.wikipedia.org/w/index.php?title=J._K._Rowling&oldid=694008857")
assert any_news_about_harry("https://en.wikipedia.org/w/index.php?title=Star_Wars&oldid=694701430")
assert not any_news_about_harry("https://en.wikipedia.org/w/index.php?title=Darth_Vader&oldid=694617684")
In [ ]:
assert not any_news_about_harry("http://math-info.hse.ru/there_is_no_Harry_here")
# assert not any_news_about_harry("http://nonexistent.domain.ji8bohVe/")

Задача 3 (1 балл)

Написать функцию get_strong(html), принимающую на вход html-страницу в виде длинной строки, записанной в переменную html, и возвращающую строчку, содержащуюся в первом теге strong.

Примеры см. в тестах.

Подсказка. Вы можете создать объект BeautifulSoup, передав ему строку с html в качестве параметра. Например:

from bs4 import BeautifulSoup
page = BeautifulSoup("<html><body><p>Hello</p></body></html>", "html.parser")
print(page.p)
In [ ]:
# YOUR CODE HERE
In [ ]:
assert get_strong("<html><body><p>Hello, <strong>World</strong>!") == "World"
html = """<html>
    <body>
        <p>
            Hello,
            <strong>
                World
            </strong>
        </p>
    </body>
</html>"""
assert get_strong(html).strip() == "World"
assert get_strong("<html><body><p>tag &lt;strong&gt; is used in HTML\n to make letters <strong>stronger</strong>") == "stronger"
assert get_strong("<html><body><strong>One\nTwo</strong></body></html>") == "One\nTwo"

Задача 4 (1 балл)

Для вставки картинок в HTML используется тег <img>, содержащий параметр src — адрес файла с картинкой. Например, <img src="https://upload.wikimedia.org/wikipedia/commons/b/bd/Struthio_camelus_portrait_Whipsnade_Zoo.jpg"/>. Написать функцию all_images_src(html), принимающую на вход длинную строчку с HTML-документом, а возвращающую список адресов всех картинок, встречающихся в этом документе (в том порядке, в котором они встречаются в документе).

Подсказка. Для обращения к атрибутам тега нужно использовать квадратные скобки, как если бы тег был словарём.

In [ ]:
# YOUR CODE HERE
In [ ]:
assert all_images_src('<html><body><img src="https://upload.wikimedia.org/wikipedia/commons/b/bd/Struthio_camelus_portrait_Whipsnade_Zoo.jpg"/>') == ["https://upload.wikimedia.org/wikipedia/commons/b/bd/Struthio_camelus_portrait_Whipsnade_Zoo.jpg"]
assert all_images_src( ('<html><body><IMG src="test.jpg">\n'
                        '<p>Some text\n'
                        '<img SRC=\'well.png\'>\n'
                        '</p></body></html>') ) == ["test.jpg", "well.png"]
assert all_images_src('<html><body><p><a href="link.html">'
                      '<img alt="Just a test image" src="this is a test.jpg"><ul>' + "\n"
                      .join("<li><img src='img%04i.png'></li>" % i for i in range(1000)) + 
                      "</ul></p></body></html>"
                     ) == ['this is a test.jpg'] + ['img%04i.png' % i for i in range(1000)]

Задача 5 (2 балла)

Написать функцию get_all_headings(url), принимающую на вход адрес страницы в Википедии и возвращающую список, состоящий из названий разделов статьи (в порядке появления в статье). Если такой страницы не существует, функция должна вернуть список, состоящей из строки "Not found".

Подсказка. С помощью функции вашего браузера inspect element или аналогичной, исследуйте, в каких тегах и с какими классами находятся искомые заголовки. Не во всех страницах есть содержание! Например, ваш код должен корректно обрабатывать эту страницу.

In [ ]:
# YOUR CODE HERE
In [ ]:
from urllib.parse import urlencode
entrypoint = "https://ru.wikipedia.org/w/index.php?"
def mkurl(title, oldid):
    return entrypoint+urlencode(dict(title=title, oldid=oldid))
assert get_all_headings(mkurl("Северовирджинская кампания",75043192)) == ['Предыстория',
                                                                          'Силы сторон',
                                                                          'Сражения',
                                                                          'Последствия',
                                                                          'Примечания',
                                                                          'Литература',
                                                                          'Ссылки']

assert get_all_headings(mkurl('User:Ilya_Voyager/sandbox/h2test',"75055744")) == ['Заголовок', 'Ещё один заголовок', 'Третий заголовок']
assert get_all_headings(mkurl('User:Ilya_Voyager/This Page Will Never Exist', "")) == ["Not found"]
del urlencode, mkurl

Задача 6 (4 балла)

Написать функцию city_tz(name), принимающую на вход название города и возвращающую строку, содержащую часовой пояс, действующий в этом городе (например, 'UTC+3'), согласно данным русской Википедии. Если такого города Википедия не знает, или если у города не указан часовой пояс None.

Предполагается, что вы будете решать эту задачу, обрабатывая HTML-код веб-страницы, а не «викитекст» (типа такого), и не будете пользоваться сторонними библиотеками (кроме urllib, requests, BeautifulSoup).

Подсказка. Как сформировать адрес страницы, зная название статьи, можно подсмотреть в тесте к задаче 6. Впрочем, можно передать адрес страницы напрямую в requests.get, см. официальную документацию.

In [ ]:
# YOUR CODE HERE
In [ ]:
res = [('Абакан', 'UTC+7'), 
       ('Анадырь', 'UTC+12'), 
       ('Киров (Кировская область)', 'UTC+03:00'), 
       ('Южно-Сахалинск', 'UTC+11'), 
       ('Усть-Каменоустюгск', None)]
for city, site in res:
    assert city_tz(city) == site, (site, city_tz(city))