4. Основы Kotlin. Списки

/3-osnovy-kotlin-rekursii-i-tsikly/

Введение

Начиная с этого раздела мы переходим к изучению составных типов данных, включающих в себя несколько элементов простых типов. Такие типы очень часто необходимы в программировании. Вспомним, например, задачу про поиск минимального корня биквадратного уравнения ax4 + bx2 + c = 0 из урока 2. В гораздо более распространённой формулировке она выглядела бы так: найти ВСЕ корни биквадратного уравнения.

Можно ли написать функцию, которая эту задачу решит? Конечно, да, но результатом подобной функции должен быть список найденных корней биквадратного уравнения. Список — это и есть один из очень распространённых составных типов со следующими свойствами:

  • список может включать в себя произвольное количество элементов (от нуля до бесконечности);
  • количество элементов в списке называется его размером;
  • все элементы списка имеют один и тот же тип (в свою очередь, этот тип может быть простым — список вещественных чисел, или составным — список строк, или список списков целых чисел, или любые другие варианты);
  • в остальном элементы списка независимы друг от друга.

Рассмотрим решение задачи о поиске корней биквадратного уравнения на Котлине:

Данное решение построено по алгоритму, приведённому в конце второго урока, с той лишь разницей, что здесь мы ищем все имеющиеся корни:

  1. Первый if рассматривает тривиальный случай a = 0 и более простое уравнение bx2 = -c. Оно либо не имеет корней (с / b > 0), либо имеет один корень 0 (c / b = 0), либо два корня (c / b < 0).
  2. Затем мы делаем замену y = x2 и считаем дискриминант d = b2 — 4ac. Если он отрицателен, уравнение не имеет корней.
  3. Если дискриминант равен 0, уравнение ay2 + by + c = 0 имеет один корень. В зависимости от его знака, биквадратное уравнение либо не имеет корней, либо имеет один корень 0, либо имеет два корня.
  4. В противном случае дискриминант положителен и уравнение ay2 + by + c = 0 имеет два корня. Каждый из них, в зависимости от его знака, превращается в ноль, один или два корней биквадратного уравнения.

Посмотрите на тип результата функции biRoots — он указан как List<Double>List в Котлине — это и есть список. В угловых скобках <> указывается так называемый типовой аргумент — тип элементов списка, то есть List<Double>вместе — это список вещественных чисел.

Для создания списков, удобно использовать функцию listOf(). Аргументы этой функции — это элементы создаваемого списка, их может быть произвольное количество (в том числе 0). В ряде случаев, когда биквадратное уравнение не имеет корней, функция biRoots возвращает пустой список результатов.

В последнем, самом сложном случае, когда уравнение ay2 + by + c = 0 имеет два корня y1 и y2, мы формируем решения уравнений x2 = y1 и x2 = y2 в виде списков part1 и part2. Обе эти промежуточные переменные имеют тип List<Double> — в этом можно убедиться в IDE, поставив на них курсор ввода и нажав комбинацию клавиш Ctrl+Q. В последнем операторе return мы складываем два этих списка друг с другом: return part1 + part2, образуя таким образом третий список, содержащий в себе все элементы двух предыдущих.

Функцию biRoots можно несколько упростить, обратив внимание на то, что мы в ней четыре раза решаем одну и ту же задачу: поиск корней уравнения x2 = y. Для программиста такая ситуация должна сразу превращаться в сигнал — следует написать для решения этой задачи отдельную, более простую функцию:

Посмотрите внимательнее на оператор if..else if..else. Первые две его ветки формируют результат сразу же, используя listOf() и listOf(0.0). А вот ветка else вначале создаёт промежуточную переменную root и уже потом формирует результат listOf(-root, root). Запомните: результат ветки в таких случаях формирует последний её оператор.

Эту же функцию можно переписать с использованием оператора when:

С использованием sqRoots функция biRoots примет следующий вид:

Из исходных 24 строчек осталось только 11, да и понимание текста функции стало существенно проще.

Напишем теперь тестовую функцию для проверки работы функции biRoots. Для этой цели последовательно решим с её помощью следующие уравнения:

  • 0x4 + 0x2 + 1 = 0 (корней нет)
  • 0x4 + 1x2 + 2 = 0 (корней нет)
  • 0x4 + 1x2 — 4 = 0 (корни -2, 2)
  • 1x4 — 2x2 + 4 = 0 (корней нет)
  • 1x4 — 2x2 + 1 = 0 (корни -1, 1)
  • 1x4 + 3x2 + 2 = 0 (корней нет)
  • 1x4 — 5x2 + 4 = 0 (корни -2, -1, 1, 2)

Обратите внимание, что здесь мы используем запись listOf<Double>() для создания пустого списка. Дело в том, что для вызовов вроде listOf(-2.0, 2.0) тип элементов создаваемого списка понятен из аргументов функции — это List<Double>. А вот вызов listOf() без аргументов не даёт никакой информации о типе элементов списка, в то же время, например, пустой список строк и пустой список целых чисел — с точки зрения Котлина не одно и то же.

Во многих случаях Котлин, тем не менее, может понять, о каком списке идёт речь. Например, функция biRoots имеет результат List<Double>, а значит, все списки, используемые в операторах return, должны иметь такой же тип. Случай с вызовом assertEquals, однако, не несёт достаточной информации, чтобы понять тип элементов, и мы вынуждены записать вызов функции более подробно — listOf<Double>(), указывая типовой аргумент <Double> между именем вызываемой функции и списком её аргументов в круглых скобках.

Запустим теперь написанную тестовую функцию. Мы получим проваленный тест из-за последней проверки:

То есть мы ожидали список корней -2, -1, 1, 2, а получили вместо этого -2, 2, -1, 1. Дело в том, что списки в Котлине считаются равными, если совпадают их размеры, и соответствующие элементы списков равны. Списки, состоящие из одних и тех же элементов, но на разных местах, считаются разными.

В этом месте программист должен задуматься, а что, собственно, он хочет в точности от функции biRoots. Должны ли найденные корни быть упорядочены по возрастанию, или они могут присутствовать в списке в любом порядке? Если должны, то он должен исправить функцию biRoots, а если нет — то тестовую функцию, так как она требует от тестируемой функции больше, чем та по факту даёт.

В обоих случаях нам придётся отсортировать список найденных корней перед сравнением. В Котлине это можно сделать, вызвав функцию .sorted():

В уроке 3 мы уже встречались с функциями с получателем .toInt() и .toDouble(). Функция .sorted() также требует наличия получателя: вызов list.sorted() создаёт список того же размера, что и исходный, но его элементы будут упорядочены по возрастанию.

Комментариев: 1
  1. Виктор Демихов

    Примеры не жизненные какие-то. Для математиков, а не для программистов.

Добавить комментарий