(1 курс ФИИТ) CS101. Основы программирования — практика

Да.

Приведу пару примеров элегантных решений задач из вчерашней лабораторной.

1. Удаление максимума из несортированного списка List<integer>

Лобовая реализация выглядела так:

result := data.Max();
data.remove(result);

Недостаток этого решения в том, что он два раза проходится по «списку». Тип List предоставляет эффективный доступ по индексу и если бы мы написали обычный цикл for с поиском индекса максимального элемента, то удаление выполнялось бы с помощью RemoveAt(i), что потенциально быстрее, чем Remove, который выполняет линейный поиск.

Возникает вопрос: нельзя использовать удаление по индексу RemoveAt(i) вместо менее эффективного Remove, но при этом не искать максимум вручную? Положительный ответ на этот вопрос даёт следующий код.

var p := Range(0, data.Count - 1).ZipTuple(data).MaxBy(p -> p.Item2);
Result := p[1];
data.RemoveAt(p[0]);

В первой строке мы генерируем индексы для каждого элемента списка (Range) и прикрепляем к каждому элементу списка его индекс (ZipTuple). В результате получается последовательность пар (i, x): элемент x и его индекс. В этой последовательности находится пара с максимальным x с помощью MaxBy. Полученная p это максимум и его индекс.

2. Проверка отсортированности списка

В проверке реализации очереди с приоритетом предлагалось перемещать все элементы из очереди в список и проверить его отсортированность. В качестве списка можно опять использовать List<integer>. Сделать это можно с помощью такой функции:

function IsSorted(list1: List<integer>): boolean;
begin
  var list2 := new List<integer>(list1);
  result := list2.OrderBy(x -> 1 / x).SequenceEqual(list1);
end;

Конечно, это неэффективное решение, но для проверки вполне допустимо. Можно придумать и другое решение на основе методов последовательностей, которое:

  • более эффективно,
  • напоминает алгоритм, который многие программировали циклом
  • не содержит явных циклов.

Приведу его позже, если эти вызовут интерес.

Второе задание ДЗ.

Дано бинарное дерево. Дополнить его до полного значениями default(T), не увеличивая количество уровней.

На всякий случай переспрошу. Тут имеется ввиду full binary tree, в котором каждый узел имеет 0 или 2 потомка, или complete binary tree, в котором все узлы имеют 2 потомка (кроме листьев), а все листья находятся на одном уровне?

Первое.

в 6 ДЗ по рекурсии в B части 2 задание: должен ли учитываться регистр слова? там получается 2 подпрограммы: вспомогательная рекурсивная и основная. Можно же в основной для обработки текста(разбиение на слова) использовать циклы?

Не должен. Можно.

Пожалуйста: когда задаёте вопрос про конкретную задачу, приводите здесь её текст целиком.

Опубликовано первое ДЗ третьего модуля — самое короткое в мире.

Настолько короткое, что его вообще нет? :smile:

Виноват: торопился перейти к подготовке празднования 9 мая…

Что загружать в ДЗ, если я его выполнил на лабораторной?

Ничего.

На станице курса опубликовано последнее бонусное задание со сроком выполнения в две недели.

Опубликовано ДЗ.

Просьба к следующему занятию разобрать материал последней лекции «Наследование».

Открыт заключительный тест по второму модулю, неделя на выполнение. Не спрашивайте, почему так поздно: надо было напомнить…

Точно открыт? А то год немного не тот=)

Спасибо, исправил.

Всем привет! Хочу сделать комментарий по поводу вчерашнего задания на наследование с калькуляторами.

Дробные числа. Я пришёл к выводу, что подход описанный в задании плохой. Он не работал при повторном нажатии плюс: вычисление 1 + 2.3 + 4 давало неверный результат. Многие этого не заметили. Хотя это не смертельно и задача была всё-таки в первую очередь посмотреть на наследование, но всё же имейте ввиду:

если вы не нашли указанной ошибки, это повод задуматься о том, насколько тщательно вы тестируете свои программы.

На данный момент я получил решение, которое проходит следующие тесты.

  // инициализация
  var calc := new AddRatioCalculator;  Assert(calc.ToString = '0', 'test-1');
  
  // набор числа
  calc.Digit(3);    Assert(calc.ToString = '3',   'test-2-1');
  calc.Dot;         Assert(calc.ToString = '3,',  'test-2-2');
  calc.Digit(1);    Assert(calc.ToString = '3,1', 'test-2-3');

  calc.CE;

  // три слагаемых: целое + дробное + целое
  calc.Digit(1);  Assert(calc.ToString = '1');    // 1
  calc.Plus;      Assert(calc.ToString = '1');    // +
  calc.Digit(2);  Assert(calc.ToString = '2');    // 2
  calc.Dot;       Assert(calc.ToString = '2,');   // .
  calc.Digit(3);  Assert(calc.ToString = '2,3');  // 3
  calc.Plus;      Assert(calc.ToString = '3,3');  // +
  calc.Digit(4);  Assert(calc.ToString = '4');    // 4
  calc.Result;    Assert(calc.ToString = '7,3');  // =

  // не очищая результат прибавим ещё дробное
  calc.Plus;      Assert(calc.ToString = '7,3');    // +
  calc.Digit(2);  Assert(calc.ToString = '2');      // 2
  calc.Digit(1);  Assert(calc.ToString = '21');     // 1
  calc.Dot;       Assert(calc.ToString = '21,');    // .
  calc.Digit(5);  Assert(calc.ToString = '21,5');   // 3
  calc.Digit(5);  Assert(calc.ToString = '21,55');  // 3
  calc.Result;    Assert(calc.ToString = '28,85');  // =

Принципиальная ошибка была считать кнопку с точкой командной кнопкой. Я убрал Dot из перечисления Command и добавил DotB в перечисление Button.

В классе-наследнике я завёл вдобавок к указанному в задании полю-счётчику цифр после точки, ещё одно логическое поле fDotPressed и метод для их очистки:

    procedure ClearDot();
    begin
      fdigitsAfterDot := 0;
      fDotPressed := false;
    end;

Этот метод приходится вызывать в действиях CE, Plus, Result, потом их следует переопределять с вызовом унаследованных методов перед очисткой (ClearDot).

Флаг fDotPressed взводится в методе Dot (ранее назывался DotDec, я сократил). Там же приходится добавить одну проверку: когда точку жмут после =

      if (fLastButton = ComB) and (fLastCommand = Res) then
        CE();

аналогичная проверка есть в Digit предка (внутри case).

Константу DotB можно было вообще не добавлять, я её добавил и делаю fLastButton := DotB чтобы проверить это переопределённом в потомке Tostring, чтобы дописывать символ запятой (см. например test-2-2 в примере тестов).

Наконец, Digit в потомке такой же, как говорился, только условный оператор проверяет флаг fDotPressed, а не fLastCommand.

в домашнем задании есть такой пункт: (из 3 лабораторной) Дополнительная задача. Проект «Статистика текста» В одной из прошлых лабораторных вы реализовывали БДП для подсчёта вхождений слов в тексте. Реализуйте класс TextStatistics для углублённой работы со статистикой текста (на основе БДП).

Объект класса должен инициализироваться текстом и предоставлять следующие возможности: …

Вопрос: я должен наследовать свой класс от того, что мы делали ранее или писать новый?

Наследовать не надо. Можно использовать включение.

Объект класса должен инициализироваться текстом и предоставлять следующие возможности:

Количество слов в тексте — свойство на чтение Count.

Проверка наличия заданного слова в тексте (метод Contains).

Индексное свойство, которое по данному слову возвращает число вхождений слова в текст. Если такое слово не встречается в тексте, должно возвращаться значение 0.

Поиск любого слова с минимальным числом вхождений в текст, не меньшим заданного значения. Если такого слова нет, вернуть nil. Реализация должна быть нерекурсивной — используйте очередь!

Пример. В тексте есть слова с числом вхождений 5, 10, 8, 2, 4, 6. Нужно найти слово с минимальным числом вхождений, не меньшим значения 3. Результат — слово с числом вхождений 4.

Поиск любого слова с максимальным числом вхождений в текст, не превышающим заданное значение (реализация любая, какая вам больше нравится).

Получение списка (односвязного или двусвязного) с информацией о словах, начинающихся на заданную непустую подстроку.

Печать статистики по словам в алфавитном порядке.

Насчет задания со списком: слова должна находиться в каком-то лексикографическом порядке?