Науки о данных

И. В. Щуров, НИУ ВШЭ

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

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

За разные задачи можно получить разное число баллов. Если не указано обратное, задача весит 1 балл. Максимум за ДЗ можно набрать 23 балла.

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

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

Напишите функцию power_or_nan(a, b), которая возводит вещественное положительное число a в степень, заданную вещественным положителным числом b, и возвращает результат. Если числа оказались слишком большими и в процессе вычисления произошло переполнение, функция должна вернуть специальное значение float("NaN").

Примечание. Python 3 может работать со сколь угодно большими целыми числами (в принципе помещающимися в память), но для чисел с плавающей точкой существует ограничение: слишком большие числа просто невозможно записать. Если вы работаете с целыми числами и в какой-то момент эти числа становятся очень-очень большими, то компьютер будет очень долго думать: например, попробуйте вычислить 9 ** (9 ** 9) (прекратить вычисление можно с помощью Kernel → Interrupt). Если аналогичное вычисление выполнить с числами с плавающей точкой, возникнет переполнение (попробуйте). В данной задаче требуется работать только с числами с плавающей точкой. Если вы хотите записать целое число как число с плавающей точкой, это можно сделать, поставив точку: например, type(9) — это int, а type(9.) — это float. Чтобы преобразовать переменную к типу float нужно использовать одноимённую функцию (например: y = float(x)).

In [ ]:
"# YOUR CODE HERE"
In [ ]:
from math import pi, e, isnan
from sys import float_info
from numpy import isclose

max_float = float_info.max

assert power_or_nan(2., 3.) == 8.
assert isclose(power_or_nan(pi, e), 22.45915771836104)
assert power_or_nan(max_float, 0.99) == max_float ** 0.99
assert isnan(power_or_nan(max_float, 1.01))

assert(isnan(power_or_nan(9., 9. ** 9)))

if isclose(max_float, 1.7976931348623157e+308):
    assert isclose(power_or_nan(1.00000001, (9. ** 9)), 48.144400906189524)
    assert isclose(power_or_nan(1.0000001, (9. ** 9)), 6.690479178194533e+16)
    assert isnan(power_or_nan(1.00001, (9. ** 9)))


ok = True

try:
    power_or_nan('a', 'b')
    ok = False
except:
    pass

assert ok, ("Вам нужно обрабатывать только исключение, "
                   "связанное с переполнением, другие исключения"
                   "обрабатывать не нужно")

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

Написать функцию sum_ints_in_file(filename), принимающую на вход имя файла, содержащего целые числа (каждое число на новой строке, некоторые строки могут быть пустыми). Функция должна вернуть сумму этих чисел.

Не забудьте закрыть файл!

In [ ]:
"# YOUR CODE HERE"
In [ ]:
try:
    del open
except:
    pass
from tempfile import NamedTemporaryFile
import os

testsuite = [([1, 2, 3]),
             ([5]),
             ([111, 23, 123]),
             ([0]),
             ([-1, -10, -123]),
             (0, -1, 1, 100, 500, 9999)]

for testlist in testsuite:
    try:
        f = NamedTemporaryFile(dir='.', delete=False, mode='w')
        name = f.name
        f.file.write("\n".join(map(str, testlist)))
        f.file.close()
        assert sum_ints_in_file(name) == sum(testlist)
    finally:
        os.remove(name)

import io

test_txt = io.StringIO("1\n2\n")

def get_test_txt():
    return test_txt

def open(file, mode = 'r', *args, **kwargs):
    return get_test_txt()

try:
    s = sum_ints_in_file("test.txt")
    assert test_txt.closed, "Вы забыли закрыть файл"
    assert s == 3
finally:
    del open
In [ ]:
try:
    f = NamedTemporaryFile(dir='.', delete=False, mode='w')
    name = f.name
    f.file.write("1\n2\n\n")
    f.file.close()
    assert sum_ints_in_file(name) == 3
except:
    print("Ошибка! Обратите внимание: файл может содержать пустые строки.")
    raise
finally:
    os.remove(name)

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

Написать функцию censore_haha(filename), считывающую файл с именем, лежащем в переменной filename и записывающим его в новый файл, имя которого получается добавлением к концу имени исходного файла .censored.txt. При записи в новый файл все вхождения слова haha должны быть заменены на [censored].

Например, если функция была вызвана как censore_haha('test.txt'), она должна создать файл test.txt.censored.txt и записать в него отцензурированную версию исходного файла.

In [ ]:
"# YOUR CODE HERE"
In [ ]:
from tempfile import NamedTemporaryFile
import os

def test_censore(inp, outp):
    try:
        f = NamedTemporaryFile(dir='.', delete=False, mode='w')
        name = f.name
        f.file.write(inp)
        f.file.close()

        censore_haha(f.name)
        with open(f.name + ".censored.txt") as f:
            content = f.read()
        assert content == outp, ("input file: {inp}, "
                                 "expected output: {outp} "
                                 "obtained output: {content}".format(
                                 inp=inp, outp=outp, content=content
                                 ))
    finally:
        os.remove(name)

test_censore(
    "haha test\nanother haha haha test\nhahahaha hahahaha\n"
    "this is a test\nwell",
    ("[censored] test\nanother [censored] [censored] test\n"
    "[censored][censored] [censored][censored]\nthis is a test\nwell")
)

test_censore(
    (
        "this is a haha haha haha\n"
        "haha ha ha hahahahahaha ha haha\n"
        "\n"
        "ha\n"
        "ha\n"
        "\n"
        "thisisahahahathis\n"
        "well...\n"
        "\n"
        "Hello, world!\n"
    ),
    (
        "this is a [censored] [censored] [censored]\n"
        "[censored] ha ha [censored][censored][censored] ha [censored]\n"
        "\n"
        "ha\n"
        "ha\n"
        "\n"
        "thisisa[censored]hathis\n"
        "well...\n"
        "\n"
        "Hello, world!\n"
    )
)

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

Написать функцию three_best_applicants(portfolio), принимающую на вход имя файла с портфолио в формате, аналогичном этому файлу, и возвращающую список фамилий и имён трёх лучших абитуриентов, упорядоченных по числу набранных ими баллов (по убыванию). Каждый элемент возвращаемого списка должен был кортежем, в котором на первом месте стоит фамилия студента, а на втором — его имя. В файле идет сначала имя, потом фамилия, а потом число баллов, причём между именем и фамилией стоит пробел, а между фамилием и числом баллов — символ табуляции (\t).

In [ ]:
"# YOUR CODE HERE"
In [ ]:
from tempfile import NamedTemporaryFile
import os

def test_portfolio(inp, outp):
    try:
        f = NamedTemporaryFile(dir='.', delete=False, mode='w')
        name = f.name
        f.file.write(inp)
        f.file.close()

        obtained = three_best_applicants(f.name)
        assert obtained == outp, ("input file: {inp}, "
                                 "expected output: {outp} "
                                 "obtained output: {obtained}").format(
            inp=inp, outp=outp, obtained=obtained)
    finally:
        os.remove(name)

test_portfolio(
"""Ann Brown\t25
Emily Calvert\t89
Alice Charr\t78
Bill Taylor\t94
Polly Smith\t32
Jill Acker\t68
Tom Bass\t15
Victoria Greg\t48
Philipp Pruitt\t65
Cristine Evans\t82
""",[('Taylor', 'Bill'), ('Calvert', 'Emily'), ('Evans', 'Cristine')])

test_portfolio(
"""Ann Brown\t125
Emily Calvert\t89
Alice Charr\t78
Bill Taylor\t94
Polly Smith\t932
Victoria Greg\t648
Philipp Pruitt\t65
Cristine Evans\t82
""",[('Smith', 'Polly'), ('Greg', 'Victoria'), ('Brown', 'Ann')])

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

Функция save_bill(clientname, cart, filename) принимает на вход имя клиента, список покупок cart, состоящий из трехэлементных кортежей вида (название товара, количество, цена за единицу), и имя файла filename. Функция должна создать файл, имя которого указано в переменной filename с чеком по заданному образцу и ничего не возвращать. Все числа должны выводиться в файл с двумя значащами цифрами после десятичной точки. Например, save_bill('Alice', [('Oil', 2, 100.11), ('Bread', 0.345, 90), ('Milk', 1, 50.32)], "somefile.txt") должна создать файл somefile.txt со следующим содержимым:

Client name: Alice

Oil x 2.00: 200.22
Bread x 0.34: 31.05
Milk x 1.00: 50.32

Total: 281.59

Подсказка. Записать число с двумя знаками после десятичной точки можно так:

"{:.2f}".format(12.345)

А ещё подставить значение переменной в строку можно так:

x = 12.345
f"{x:.2f}
In [ ]:
"# YOUR CODE HERE"
In [ ]:
from tempfile import NamedTemporaryFile
import os

testsuite = [("Alice", [('Oil', 2, 100.11), ('Bread', 0.345, 90),
                        ('Milk', 1, 50.32)],
                        ('Client name: Alice\n\nOil x 2.00: 200.22\nBread'
                        ' x 0.34: 31.05\nMilk x 1.00: 50.32\n\nTotal: 281'
                        '.59')),
             ("Bill Clinton", [('Thing', 1, 10),
                               ('Other thing', 1.234, 32.32)],
              ('Client name: Bill Clinton\n\nThing x 1.00: 10.0'
               '0\nOther thing x 1.23: 39.88\n\nTotal: 49.88')),
             ("Claudia", [('This', 1.3, 2.12),
                          ('This', 1.6, 2.12)],
             ('Client name: Claudia\n\nThis x 1.30: 2.76\nThis'
              ' x 1.60: 3.39\n\nTotal: 6.15'))
            ]

for clientname, cart, output in testsuite:
    f = NamedTemporaryFile(dir='.', delete=False)
    name = f.name
    f.close()
    try:
        save_bill(clientname, cart, name)
        with open(name) as f:
            collected_output = f.read().strip()
            assert collected_output == output.strip(), (collected_output, output.strip())
    finally:
        os.remove(name)

Задача 6 (сразу на 17 баллов)

Напишите класс Amoeba, описывающий амёб. Каждая амёба имеет имя (атрибут name), цвет (атрибут color) и возраст (атрибут age). Имя и цвет задаются строками, возраст — целым числом (количество дней, прошедших с момента рождения амёбы; в момент рождения инициализируется нулём). Когда амёбе исполняется 3 дня, она становится совершеннолетней и может производить потомство. Размножение у амёб бесполое. Каждая амёба хранит список своих детей (children). При рождении амёбы этот список пустой, с рождением каждого ребёнка он вписывается в этот список (в порядке рождения: то есть каждый новый ребёнок записывается в конец списка). При создании амёбы человеком её цвет указывается явно в момент создания. Если амёба рождаёт ребёнка, его цвет совпадает с цветом амёбы-родителя. Амёба, рождённая другой амёбой, знает, кто её родитель (она хранит эту информацию в атрибуте parent). Если амёбу создал человек, то атрибут parent инициализируется None.

Итак, класс Amoeba должен обладать следующими атрибутами:

  • name: строка
  • color: строка
  • age: целое число
  • parent: экземпляр класса Amoeba
  • children: список экземпляров класса Amoeba (быть может, пустой)

Необходимо реализовать следующие методы:

  • __init__(): конструктор, принимает на вход name, color и parent, если parent не установлен, его следует считать равным None. (1 балл.)
  • __repr__(): строковое представление, имеющее вид "Amoeba(name='{name}', color='{color}')", где {name} и {color} — её имя и цвет. Например, Amoeba(name='Laama', color='green') (1 балл.)
  • __str__(): человекочитаемое строковое представление, имеющее вид <{color} amoeba named {name}>. (Например, <green amoeba named Laama>.) (1 балл.)
  • grow(): увеличить возраст на 1. (1 балл.)
  • is_adult(): проверить, является ли амёба взрослой; если да, вернуть True, иначе вернуть False. (1 балл.)
  • give_birth(): принимает на вход аргумент name. Если амёба является взрослой, она должна родить ребёнка, то есть создать новую амёбу, имя которой равно name и цвет которой совпадает со своим цветом, записать этого ребёнка в список своих детей (в конец списка), записать себя как родителя этого ребёнка и вернуть этого ребёнка. Если амёба не является взрослой, этот метод ничего не делает и возвращает None. (2 балла.)
  • get_brothers(): возвращает список всех амёб, являющихся братьями данной. Амёба не считается братом самой себе. Если у амёбы нет родителя, возвращает None. (2 балла.)
  • get_grandmother(): возвращает бабушку текущей амёбы или None, если у амёбы нет бабушки (то есть она сама или её родитель были созданы человеком). (1 балл.)
  • get_cousins(): возвращает список всех двоюродных братьев (то есть всех внуков моей бабушки, не являющихся моими родными братьями) или None если у неё нет бабушки. Если бабушка есть, но двоюродных братьев нет, то функция должна вернуть пустой список, а не None. (3 балла.)
  • is_relative_to(other): проверяет, является ли данная амёба родственницей амёбе other. Родственниками называются амёбы, если одна из них является предком другой, или если они имеют общего предка (например, дядя и его племянник — родственники, потому что бабушка племянника является мамой дяди). Каждый является родственником самому себе. (4 балла.) Подсказка. При написании этого метода полезно создать метод _all_ancestors(), возвращающий список всех предков. Вам также могут пригодиться множества (set).
In [ ]:
"# YOUR CODE HERE"
In [ ]:
laama = Amoeba("Laama", "green")
assert laama.color == 'green'
assert laama.age == 0
assert laama.children == []
assert laama.parent is None
assert laama.name == "Laama"

quuma = Amoeba("Quuma", "yellow")
assert quuma.color == 'yellow'
assert quuma.age == 0
assert quuma.parent is None
assert laama.children == []
assert quuma.name == 'Quuma'
In [ ]:
laama = Amoeba("Laama", "green")
quuma = Amoeba("Quuma", "yellow")
import re
assert re.match(r"Amoeba\(name=['\"]Laama['\"], color=['\"]green['\"]\)$", repr(laama))
assert repr(laama).count("'") == 4 or repr(laama).count('"') == 4
assert re.match(r"Amoeba\(name=['\"]Quuma['\"], color=['\"]yellow['\"]\)$", repr(quuma))
In [ ]:
laama = Amoeba("Laama", "green")
quuma = Amoeba("Quuma", "yellow")
assert str(laama) == '<green amoeba named Laama>'
assert str(quuma) == '<yellow amoeba named Quuma>'
In [ ]:
laama = Amoeba("Laama", "green")
for i in range(100):
    assert laama.age == i
    laama.grow()
In [ ]:
laama = Amoeba("Laama", "green")
assert not laama.is_adult()
laama.grow()
assert not laama.is_adult()
laama.grow()
assert not laama.is_adult()
laama.grow()
assert laama.is_adult()
laama.grow()
assert laama.is_adult()
In [ ]:
laama = Amoeba("Laama", "green")
assert laama.give_birth("Laamiesh") is None
assert laama.children == []
laama.grow()
laama.grow()
laama.grow()
child = laama.give_birth("Laamiesh")
assert child is not None
assert child is laama.children[0]
assert child.parent is laama
assert child.color == laama.color
assert child.color == 'green'

oldchild = child

laama.grow()
child = laama.give_birth("Laamien")
assert child is not None
assert child is laama.children[1]
assert child.color == laama.color
assert child.color == 'green'
assert child.parent is laama
assert oldchild != child
In [ ]:
laama = Amoeba("Laama", "green")
laama.grow()
laama.grow()
laama.grow()

child1 = laama.give_birth("Lammuq")
child2 = laama.give_birth("Laamaq")
child3 = laama.give_birth("Laameq")

assert set(child1.get_brothers()) == set([child2, child3])
assert set(child2.get_brothers()) == set([child1, child3])
assert set(child3.get_brothers()) == set([child1, child2])

assert laama.get_brothers() is None

quuma = Amoeba("Quuma", "yellow")
quuma.grow()
quuma.grow()
quuma.grow()
quuma_child = quuma.give_birth("Zzz")
assert quuma_child.get_brothers() == []
In [ ]:
laama = Amoeba("Laama", "green")
laama.grow()
laama.grow()
laama.grow()
xeema = laama.give_birth("Xeema")
xeema.grow()
xeema.grow()
xeema.grow()
rooma = xeema.give_birth("Rooma")
assert rooma.get_grandmother() is laama
assert xeema.get_grandmother() is None
assert laama.get_grandmother() is None
In [ ]:
laama = Amoeba("Laama", "green")
laama.grow()
laama.grow()
laama.grow()

child1 = laama.give_birth("Child 1")
child2 = laama.give_birth("Child 2")
child3 = laama.give_birth("Child 3")

child1.grow()
child1.grow()
child1.grow()

child2.grow()
child2.grow()
child2.grow()

child3.grow()
child3.grow()
child3.grow()

grandchild11 = child1.give_birth("Grandchild 1 1")
grandchild12 = child1.give_birth("Grandchild 1 2")

grandchild21 = child2.give_birth("Grandchild 2 1")
grandchild22 = child2.give_birth("Grandchild 2 2")
grandchild23 = child2.give_birth("Grandchild 2 3")

grandchild31 = child3.give_birth("Grandchild 3 1")
grandchild32 = child3.give_birth("Grandchild 3 2")
grandchild33 = child3.give_birth("Grandchild 3 3")
grandchild34 = child3.give_birth("Grandchild 3 4")

assert set(grandchild11.get_cousins()) == set([grandchild21, grandchild22, 
                                              grandchild23, grandchild31,
                                              grandchild32, grandchild33,
                                              grandchild34])

assert set(grandchild12.get_cousins()) == set([grandchild21, grandchild22, 
                                              grandchild23, grandchild31,
                                              grandchild32, grandchild33,
                                              grandchild34])

assert set(grandchild34.get_cousins()) == set([grandchild11, grandchild12,
                                              grandchild21, grandchild22,
                                              grandchild23])

assert set(grandchild31.get_cousins()) == set([grandchild11, grandchild12,
                                              grandchild21, grandchild22,
                                              grandchild23])
In [ ]:
from itertools import product

laama = Amoeba("Laama", "green")
laama.grow()
laama.grow()
laama.grow()

child1 = laama.give_birth("Child 1")
child2 = laama.give_birth("Child 2")
child3 = laama.give_birth("Child 3")

child1.grow()
child1.grow()
child1.grow()

child2.grow()
child2.grow()
child2.grow()

child3.grow()
child3.grow()
child3.grow()

grandchild11 = child1.give_birth("Grandchild 1 1")
grandchild12 = child1.give_birth("Grandchild 1 2")

grandchild21 = child2.give_birth("Grandchild 2 1")
grandchild22 = child2.give_birth("Grandchild 2 2")
grandchild23 = child2.give_birth("Grandchild 2 3")

grandchild31 = child3.give_birth("Grandchild 3 1")
grandchild32 = child3.give_birth("Grandchild 3 2")
grandchild33 = child3.give_birth("Grandchild 3 3")
grandchild34 = child3.give_birth("Grandchild 3 4")

xeema = Amoeba("Xeema", "purple")
xeema.grow()
xeema.grow()
xeema.grow()

xeema_child1 = xeema.give_birth("Xeema Child 1")
xeema_child2 = xeema.give_birth("Xeema Child 2")

xeema_child1.grow()
xeema_child1.grow()
xeema_child1.grow()

xeema_child2.grow()
xeema_child2.grow()
xeema_child2.grow()

xeema_grandchild11 = xeema_child1.give_birth("Xeema Grandchild 1 1")

xeema_grandchild21 = xeema_child2.give_birth("Xeema Grandchild 2 1")

laama_family = [laama, child1, child2, child3, grandchild11, grandchild12,
               grandchild21, grandchild22, grandchild23, grandchild31,
               grandchild32, grandchild32, grandchild33, grandchild34]

xeema_family = [xeema, xeema_child1, xeema_child2, xeema_grandchild11, 
                xeema_grandchild21]

for lf1, lf2 in product(laama_family, repeat=2):
    assert lf1.is_relative_to(lf2)

for xf1, xf2 in product(xeema_family, repeat=2):
    assert xf1.is_relative_to(xf2)

for xf, lf in product(xeema_family, laama_family):
    assert not xf.is_relative_to(lf)
    assert not lf.is_relative_to(xf)