From 0db12aa71f6293d6d0df432e21be6b523d6d7594 Mon Sep 17 00:00:00 2001 From: Artem-Darius Weber Date: Sat, 19 Apr 2025 10:22:47 +0300 Subject: [PATCH] lab 9 task 2 --- lab9/Makefile | 27 ++- lab9/task2/Dockerfile | 7 + lab9/task2/Makefile | 45 ++++ lab9/task2/demo.pl | 486 ++++++++++++++++++++++++++++++++++++++ lab9/task2/predicates.pl | 492 +++++++++++++++++++++++++++++++++++++++ lab9/task2/tests.pl | 393 +++++++++++++++++++++++++++++++ 6 files changed, 1449 insertions(+), 1 deletion(-) create mode 100644 lab9/task2/Dockerfile create mode 100644 lab9/task2/Makefile create mode 100644 lab9/task2/demo.pl create mode 100644 lab9/task2/predicates.pl create mode 100644 lab9/task2/tests.pl diff --git a/lab9/Makefile b/lab9/Makefile index 8d46b7a..f189b5d 100644 --- a/lab9/Makefile +++ b/lab9/Makefile @@ -1,4 +1,4 @@ -.PHONY: run build clean task1 build-task1 clean-task1 interactive-task1 test-task1 demo-task1 trace-task1 info-task1 +.PHONY: run build clean task1 build-task1 clean-task1 interactive-task1 test-task1 demo-task1 trace-task1 info-task1 task2 build-task2 clean-task2 interactive-task2 test-task2 demo-task2 trace-task2 info-task2 # task1 (default) run: @@ -37,6 +37,31 @@ trace-task1: info-task1: cd task1 && make info +# task2 +task2: + cd task2 && make run + +build-task2: + cd task2 && make build + +clean-task2: + cd task2 && make clean + +interactive-task2: + cd task2 && make interactive + +test-task2: + cd task2 && make test + +demo-task2: + cd task2 && make demo + +trace-task2: + cd task2 && make trace + +info-task2: + cd task2 && make info + # Help help: @echo "Available commands for lab9:" diff --git a/lab9/task2/Dockerfile b/lab9/task2/Dockerfile new file mode 100644 index 0000000..48147b3 --- /dev/null +++ b/lab9/task2/Dockerfile @@ -0,0 +1,7 @@ +FROM swipl:stable + +WORKDIR /app + +COPY *.pl ./ + +CMD ["swipl", "-g", "consult('predicates.pl')", "-t", "halt"] \ No newline at end of file diff --git a/lab9/task2/Makefile b/lab9/task2/Makefile new file mode 100644 index 0000000..533ce63 --- /dev/null +++ b/lab9/task2/Makefile @@ -0,0 +1,45 @@ +.PHONY: build run interactive clean test trace demo info + +# Build Docker image +build: + docker build -t lab9-task2-prolog . + +# Run Prolog with predicates (show basic info and exit) +run: build + docker run --rm -v $(PWD):/app lab9-task2-prolog swipl -g "consult('predicates.pl'), write('=== LAB9 TASK2 PREDICATES ==='), nl, task_info, halt" + +# Interactive Prolog session +interactive: build + docker run -it --rm -v $(PWD):/app lab9-task2-prolog swipl predicates.pl + +# Run test queries +test: build + docker run --rm -v $(PWD):/app lab9-task2-prolog swipl -g "consult('predicates.pl'), consult('tests.pl'), run_tests, halt" + +# Run with tracing enabled +trace: build + docker run --rm -v $(PWD):/app lab9-task2-prolog swipl -g "consult('predicates.pl'), trace, test_all_predicates, notrace, halt" + +# Show basic info about task2 +info: build + docker run --rm -v $(PWD):/app lab9-task2-prolog swipl -g "consult('predicates.pl'), write('=== TASK2 INFO ==='), nl, task_info, show_examples, halt" + +# Run demo +demo: build + docker run --rm -v $(PWD):/app lab9-task2-prolog swipl -g "consult('predicates.pl'), consult('demo.pl'), demo, halt" + +# Clean Docker images +clean: + docker rmi lab9-task2-prolog || true + +# Help +help: + @echo "Available commands for lab9 task2:" + @echo " make build - Build Docker image" + @echo " make run - Run and show basic predicates info" + @echo " make info - Show detailed task2 information" + @echo " make demo - Run demonstration queries" + @echo " make test - Run test queries" + @echo " make interactive - Start interactive Prolog session" + @echo " make trace - Run with tracing enabled" + @echo " make clean - Clean Docker images" \ No newline at end of file diff --git a/lab9/task2/demo.pl b/lab9/task2/demo.pl new file mode 100644 index 0000000..0ca6803 --- /dev/null +++ b/lab9/task2/demo.pl @@ -0,0 +1,486 @@ +% =============================================== +% ДЕМОНСТРАЦИЯ LAB9 TASK2 +% Полная демонстрация специальных предикатов +% =============================================== + +% Главная демонстрация +demo :- + write('=== ДЕМОНСТРАЦИЯ LAB9 TASK2: СПЕЦИАЛЬНЫЕ ПРЕДИКАТЫ PROLOG ==='), nl, nl, + + % 1. Демонстрация произведения цифр + write('1. ПРОИЗВЕДЕНИЕ ЦИФР ЧИСЛА'), nl, + write('==========================='), nl, + demo_digit_product, + nl, + + % 2. Демонстрация максимальной цифры не делящейся на 3 + write('2. МАКСИМАЛЬНАЯ ЦИФРА НЕ ДЕЛЯЩАЯСЯ НА 3'), nl, + write('======================================='), nl, + demo_max_digit_not_div3, + nl, + + % 3. Демонстрация количества делителей + write('3. КОЛИЧЕСТВО ДЕЛИТЕЛЕЙ ЧИСЛА'), nl, + write('============================='), nl, + demo_divisor_count, + nl, + + % 4. Сравнительный анализ рекурсий + write('4. СРАВНИТЕЛЬНЫЙ АНАЛИЗ РЕКУРСИЙ'), nl, + write('================================'), nl, + demo_recursion_comparison, + nl, + + % 5. Анализ унификации переменных + write('5. АНАЛИЗ УНИФИКАЦИИ ПЕРЕМЕННЫХ'), nl, + write('==============================='), nl, + demo_unification_analysis, + nl, + + % 6. Практические примеры + write('6. ПРАКТИЧЕСКИЕ ПРИМЕРЫ'), nl, + write('======================='), nl, + demo_practical_examples, + nl, + + % 7. Производительность и оптимизация + write('7. ПРОИЗВОДИТЕЛЬНОСТЬ И ОПТИМИЗАЦИЯ'), nl, + write('==================================='), nl, + demo_performance_analysis, + nl, + + write('=== ДЕМОНСТРАЦИЯ TASK2 ЗАВЕРШЕНА ==='), nl. + +% ----------------------------------------------- +% Демонстрация произведения цифр +% ----------------------------------------------- +demo_digit_product :- + write('Предикаты digit_product_up/2 и digit_product_down/2 вычисляют'), nl, + write('произведение всех цифр числа.'), nl, nl, + + write('ПРИНЦИП РАБОТЫ:'), nl, + write('- digit_product_up: рекурсия вверх, вычисления при возврате'), nl, + write('- digit_product_down: рекурсия вниз с аккумулятором'), nl, nl, + + TestNumbers = [0, 1, 123, 456, 789, 102, 555, 12345], + + write('Сравнение результатов:'), nl, + write('Число | Произведение (вверх) | Произведение (вниз) | Время (вверх) | Время (вниз)'), nl, + write('--------|----------------------|---------------------|---------------|-------------'), nl, + + forall(member(N, TestNumbers), demo_digit_product_case(N)), + + nl, + write('ОСОБЫЕ СЛУЧАИ:'), nl, + write('- Произведение цифр 0 = 1 (по определению)'), nl, + write('- Если число содержит 0, произведение = 0'), nl, + write('- Для однозначных чисел произведение = само число'), nl, nl, + + write('ДЕМОНСТРАЦИЯ УНИФИКАЦИИ для digit_product_up(123, P):'), nl, + write('1. N=123 (унифицированная), P - неунифицированная'), nl, + write('2. Digit = 123 mod 10 = 3 (унифицируется)'), nl, + write('3. N1 = 123 // 10 = 12 (унифицируется)'), nl, + write('4. Рекурсивный вызов digit_product_up(12, SubProduct)'), nl, + write('5. SubProduct = 2 (унифицируется при возврате)'), nl, + write('6. P = 3 * 2 = 6 (унифицируется с результатом)'), nl. + +demo_digit_product_case(N) :- + get_time(T1), + digit_product_up(N, P1), + get_time(T2), + digit_product_down(N, P2), + get_time(T3), + + TimeUp is T2 - T1, + TimeDown is T3 - T2, + + format('~w~7| | ~w~19| | ~w~18| | ~6f~9| | ~6f~n', [N, P1, P2, TimeUp, TimeDown]). + +% ----------------------------------------------- +% Демонстрация максимальной цифры не делящейся на 3 +% ----------------------------------------------- +demo_max_digit_not_div3 :- + write('Предикаты max_digit_not_div3_up/2 и max_digit_not_div3_down/2'), nl, + write('находят максимальную цифру числа, которая не делится на 3.'), nl, nl, + + write('ПРИНЦИП РАБОТЫ:'), nl, + write('- Проверяем каждую цифру на деление на 3'), nl, + write('- Среди не делящихся на 3 находим максимальную'), nl, + write('- Если все цифры делятся на 3, возвращаем -1'), nl, nl, + + TestNumbers = [0, 123, 456, 789, 369, 147, 98765, 36960, 12457], + + write('Анализ цифр по числам:'), nl, + write('Число | Цифры | Не дел. на 3 | Максимум (вверх) | Максимум (вниз)'), nl, + write('--------|------------|--------------|------------------|----------------'), nl, + + forall(member(N, TestNumbers), demo_max_digit_case(N)), + + nl, + write('ОБЪЯСНЕНИЕ РЕЗУЛЬТАТОВ:'), nl, + write('- 123: цифры [1,2,3], не дел. на 3: [1,2] → макс: 2'), nl, + write('- 369: цифры [3,6,9], все делятся на 3 → результат: -1'), nl, + write('- 147: цифры [1,4,7], не дел. на 3: [1,4,7] → макс: 7'), nl, nl, + + write('ДЕМОНСТРАЦИЯ УНИФИКАЦИИ для max_digit_not_div3_up(147, M):'), nl, + write('1. N=147 (унифицированная), M - неунифицированная'), nl, + write('2. Digit = 147 mod 10 = 7 (унифицируется)'), nl, + write('3. N1 = 147 // 10 = 14 (унифицируется)'), nl, + write('4. Рекурсивный вызов max_digit_not_div3_up(14, SubMax)'), nl, + write('5. SubMax = 4 (унифицируется при возврате)'), nl, + write('6. combine_max_not_div3(7, 4, M): 7 не дел. на 3, M = max(7,4) = 7'), nl. + +demo_max_digit_case(N) :- + % Получаем список цифр + number_digits(N, Digits), + % Фильтруем не делящиеся на 3 + include(not_divisible_by_3, Digits, NotDiv3), + + max_digit_not_div3_up(N, M1), + max_digit_not_div3_down(N, M2), + + format('~w~7| | ~w~9| | ~w~11| | ~w~15| | ~w~n', [N, Digits, NotDiv3, M1, M2]). + +% Вспомогательные предикаты для демонстрации +number_digits(0, [0]) :- !. +number_digits(N, Digits) :- + N > 0, + number_digits_helper(N, [], Digits). + +number_digits_helper(0, Acc, Acc) :- !. +number_digits_helper(N, Acc, Digits) :- + N > 0, + Digit is N mod 10, + N1 is N // 10, + number_digits_helper(N1, [Digit|Acc], Digits). + +not_divisible_by_3(Digit) :- + Digit mod 3 =\= 0. + +% ----------------------------------------------- +% Демонстрация количества делителей +% ----------------------------------------------- +demo_divisor_count :- + write('Предикаты для подсчета количества делителей числа:'), nl, + write('- divisor_count_up/2: рекурсия вверх'), nl, + write('- divisor_count_down/2: рекурсия вниз с аккумулятором'), nl, + write('- divisor_count_optimized/2: оптимизированный алгоритм'), nl, nl, + + write('ПРИНЦИП ОПТИМИЗАЦИИ:'), nl, + write('- Обычный алгоритм: проверяем все числа от 1 до N'), nl, + write('- Оптимизированный: проверяем только до sqrt(N)'), nl, + write('- Для каждого делителя d находим пару N/d'), nl, + write('- Если d = sqrt(N), добавляем только один раз'), nl, nl, + + TestNumbers = [1, 6, 12, 24, 36, 100, 144, 1000], + + write('Сравнение методов:'), nl, + write('Число | Делители | Кол-во | Время (up) | Время (down) | Время (opt)'), nl, + write('------|-------------------------------|--------|------------|--------------|------------'), nl, + + forall(member(N, TestNumbers), demo_divisor_case(N)), + + nl, + write('АНАЛИЗ СЛОЖНОСТИ:'), nl, + write('- Обычный алгоритм: O(N) - проверяем все числа до N'), nl, + write('- Оптимизированный: O(sqrt(N)) - проверяем только до sqrt(N)'), nl, + write('- Для N=10000: обычный ~10000 операций, оптим. ~100 операций'), nl, nl, + + write('ДЕМОНСТРАЦИЯ УНИФИКАЦИИ для divisor_count_down(12, C):'), nl, + write('1. N=12 (унифицированная), C - неунифицированная'), nl, + write('2. Вызов helper(12, 1, 0, C)'), nl, + write('3. Current=1, Acc=0: 12 mod 1 = 0 → Acc1 = 1'), nl, + write('4. Current=2, Acc=1: 12 mod 2 = 0 → Acc1 = 2'), nl, + write('5. Current=3, Acc=2: 12 mod 3 = 0 → Acc1 = 3'), nl, + write('6. Current=4, Acc=3: 12 mod 4 = 0 → Acc1 = 4'), nl, + write('7. Current=5, Acc=4: 12 mod 5 ≠ 0 → Acc1 = 4'), nl, + write('8. Current=6, Acc=4: 12 mod 6 = 0 → Acc1 = 5'), nl, + write('9. Current=7..11: не делители → Acc1 = 5'), nl, + write('10. Current=12, Acc=5: 12 mod 12 = 0 → Acc1 = 6'), nl, + write('11. Current=13 > 12 → C унифицируется с 6'), nl. + +demo_divisor_case(N) :- + % Находим все делители для отображения + findall(D, (between(1, N, D), N mod D =:= 0), Divisors), + + get_time(T1), + divisor_count_up(N, C1), + get_time(T2), + divisor_count_down(N, C2), + get_time(T3), + divisor_count_optimized(N, C3), + get_time(T4), + + TimeUp is T2 - T1, + TimeDown is T3 - T2, + TimeOpt is T4 - T3, + + format('~w~5| | ~w~28| | ~w~6| | ~6f~4| | ~6f~6| | ~6f~n', + [N, Divisors, C1, TimeUp, TimeDown, TimeOpt]). + +% ----------------------------------------------- +% Сравнительный анализ рекурсий +% ----------------------------------------------- +demo_recursion_comparison :- + write('Сравнение эффективности рекурсии вверх и вниз:'), nl, nl, + + write('ТЕОРЕТИЧЕСКИЕ РАЗЛИЧИЯ:'), nl, + write('┌─────────────────┬──────────────────────┬────────────────────────┐'), nl, + write('│ Характеристика │ Рекурсия вверх │ Рекурсия вниз │'), nl, + write('├─────────────────┼──────────────────────┼────────────────────────┤'), nl, + write('│ Стек вызовов │ Растет с глубиной │ Оптимизируется │'), nl, + write('│ Вычисления │ При возврате │ При погружении │'), nl, + write('│ Память │ O(глубина) │ O(1) для хвост. рек. │'), nl, + write('│ Понимание │ Интуитивно │ Требует практики │'), nl, + write('│ Оптимизация │ Ограничена │ Компилятор может помочь│'), nl, + write('└─────────────────┴──────────────────────┴────────────────────────┘'), nl, nl, + + write('ПРАКТИЧЕСКОЕ СРАВНЕНИЕ:'), nl, + TestNumbers = [12345, 987654, 1234567], + forall(member(N, TestNumbers), demo_recursion_performance(N)), + + nl, + write('ВЫВОДЫ:'), nl, + write('1. Для небольших чисел разница незначительна'), nl, + write('2. Для больших чисел рекурсия вниз может быть эффективнее'), nl, + write('3. Рекурсия вниз безопаснее при глубокой рекурсии'), nl, + write('4. Оптимизированные алгоритмы дают наибольшее ускорение'), nl. + +demo_recursion_performance(N) :- + write('Тестирование для N = '), write(N), write(':'), nl, + + % Произведение цифр + get_time(T1), + digit_product_up(N, P1), + get_time(T2), + digit_product_down(N, P2), + get_time(T3), + + TimeProductUp is T2 - T1, + TimeProductDown is T3 - T2, + + write(' Произведение цифр: рекурсия вверх = '), write(TimeProductUp), write('с, '), + write('рекурсия вниз = '), write(TimeProductDown), write('с'), nl, + + % Максимальная цифра + get_time(T4), + max_digit_not_div3_up(N, M1), + get_time(T5), + max_digit_not_div3_down(N, M2), + get_time(T6), + + TimeMaxUp is T5 - T4, + TimeMaxDown is T6 - T5, + + write(' Макс. цифра: рекурсия вверх = '), write(TimeMaxUp), write('с, '), + write('рекурсия вниз = '), write(TimeMaxDown), write('с'), nl, nl. + +% ----------------------------------------------- +% Анализ унификации переменных +% ----------------------------------------------- +demo_unification_analysis :- + write('АНАЛИЗ УНИФИКАЦИИ В ПРЕДИКАТАХ:'), nl, nl, + + write('1. ТИПЫ ПЕРЕМЕННЫХ В PROLOG:'), nl, + write(' • Унифицированные (+): уже имеют значение при вызове'), nl, + write(' • Неунифицированные (-): получают значение в процессе выполнения'), nl, + write(' • Переменные режима (?): могут быть и теми, и другими'), nl, nl, + + write('2. ПРИМЕРЫ УНИФИКАЦИИ В НАШИХ ПРЕДИКАТАХ:'), nl, nl, + + % Пример 1: digit_product_up + write('ПРИМЕР 1: digit_product_up(+N, -Product)'), nl, + write('Вызов: digit_product_up(123, P)'), nl, + write('• N = 123 (унифицированная при вызове)'), nl, + write('• Product = неунифицированная переменная'), nl, + write('• Digit is N mod 10 → Digit унифицируется с 3'), nl, + write('• N1 is N // 10 → N1 унифицируется с 12'), nl, + write('• При возврате из рекурсии SubProduct унифицируется с 2'), nl, + write('• Product is 3 * 2 → Product унифицируется с 6'), nl, nl, + + % Пример 2: max_digit_not_div3_down + write('ПРИМЕР 2: max_digit_not_div3_down(+N, -MaxDigit)'), nl, + write('Вызов: max_digit_not_div3_down(147, M)'), nl, + write('• N = 147, M = неунифицированная'), nl, + write('• Вызов helper(147, -1, M) → CurrentMax = -1 (унифицированная)'), nl, + write('• Digit = 7, update_max_not_div3(7, -1, NewMax)'), nl, + write('• 7 mod 3 ≠ 0, CurrentMax = -1 → NewMax унифицируется с 7'), nl, + write('• Процесс продолжается с NewMax = 7 (теперь унифицированная)'), nl, nl, + + % Пример 3: divisor_count_optimized + write('ПРИМЕР 3: divisor_count_optimized(+N, -Count)'), nl, + write('Вызов: divisor_count_optimized(16, C)'), nl, + write('• N = 16, C = неунифицированная'), nl, + write('• Sqrt is floor(sqrt(16)) → Sqrt унифицируется с 4'), nl, + write('• Current = 1: 16 mod 1 = 0, Current ≠ sqrt(16) → Acc1 = 0 + 2 = 2'), nl, + write('• Current = 2: 16 mod 2 = 0, Current ≠ sqrt(16) → Acc1 = 2 + 2 = 4'), nl, + write('• Current = 4: 16 mod 4 = 0, Current = sqrt(16) → Acc1 = 4 + 1 = 5'), nl, + write('• Current = 5 > Sqrt → C унифицируется с 5'), nl, nl, + + write('3. ВАЖНЫЕ ОСОБЕННОСТИ УНИФИКАЦИИ:'), nl, + write(' • Унификация происходит один раз и необратима'), nl, + write(' • Переменная может унифицироваться только с одним значением'), nl, + write(' • В рекурсии каждый вызов создает новую область видимости'), nl, + write(' • Аккумуляторы передают состояние между вызовами'), nl. + +% ----------------------------------------------- +% Практические примеры +% ----------------------------------------------- +demo_practical_examples :- + write('ПРАКТИЧЕСКИЕ ПРИМЕНЕНИЯ ПРЕДИКАТОВ:'), nl, nl, + + write('СЦЕНАРИЙ 1: Криптографический анализ'), nl, + write('─────────────────────────────────────'), nl, + demo_crypto_analysis, + nl, + + write('СЦЕНАРИЙ 2: Числовая теория'), nl, + write('───────────────────────────'), nl, + demo_number_theory, + nl, + + write('СЦЕНАРИЙ 3: Анализ данных'), nl, + write('─────────────────────────'), nl, + demo_data_analysis, + nl. + +demo_crypto_analysis :- + Numbers = [1234, 5678, 9876], + write('Анализ чисел для криптографических свойств:'), nl, + forall(member(N, Numbers), demo_crypto_number(N)). + +demo_crypto_number(N) :- + digit_product_up(N, Product), + max_digit_not_div3_up(N, MaxDigit), + divisor_count_optimized(N, DivCount), + + write('Число '), write(N), write(':'), nl, + write(' Произведение цифр: '), write(Product), + (Product =:= 0 -> write(' (содержит 0 - слабо)') ; write(' (без нулей - лучше)')), nl, + write(' Макс. цифра не дел. на 3: '), write(MaxDigit), + (MaxDigit =:= -1 -> write(' (все дел. на 3 - предсказуемо)') ; write(' (есть разнообразие)')), nl, + write(' Количество делителей: '), write(DivCount), + (DivCount =:= 2 -> write(' (простое - хорошо для криптографии)') ; write(' (составное)')), nl. + +demo_number_theory :- + write('Исследование числовых свойств:'), nl, + + % Поиск чисел с интересными свойствами + write('Поиск чисел от 1 до 100 с особыми свойствами:'), nl, + + % Числа, где произведение цифр равно количеству делителей + findall(N, (between(1, 100, N), + digit_product_up(N, P), + divisor_count_optimized(N, D), + P =:= D), SpecialNumbers), + write('Числа, где произведение цифр = количеству делителей: '), write(SpecialNumbers), nl, + + % Числа без цифр, делящихся на 3, но само число делится на 3 + findall(N, (between(10, 100, N), + N mod 3 =:= 0, + max_digit_not_div3_up(N, M), + M =\= -1), ParadoxNumbers), + write('Числа дел. на 3, но есть цифры не дел. на 3: '), write(ParadoxNumbers), nl. + +demo_data_analysis :- + write('Анализ последовательности чисел:'), nl, + + Sequence = [123, 234, 345, 456, 567, 678, 789], + write('Последовательность: '), write(Sequence), nl, + + % Анализ произведений цифр + findall(P, (member(N, Sequence), digit_product_up(N, P)), Products), + sum_list(Products, TotalProduct), + length(Products, Len), + AvgProduct is TotalProduct / Len, + write('Произведения цифр: '), write(Products), nl, + write('Среднее произведение: '), write(AvgProduct), nl, + + % Анализ максимальных цифр + findall(M, (member(N, Sequence), max_digit_not_div3_up(N, M)), MaxDigits), + write('Макс. цифры не дел. на 3: '), write(MaxDigits), nl. + +% ----------------------------------------------- +% Производительность и оптимизация +% ----------------------------------------------- +demo_performance_analysis :- + write('АНАЛИЗ ПРОИЗВОДИТЕЛЬНОСТИ И ОПТИМИЗАЦИИ:'), nl, nl, + + write('1. СРАВНЕНИЕ АЛГОРИТМОВ ПОДСЧЕТА ДЕЛИТЕЛЕЙ:'), nl, + write(' Тестирование на различных размерах чисел'), nl, nl, + + TestSizes = [100, 1000, 10000], + forall(member(Size, TestSizes), demo_divisor_scaling(Size)), + + nl, + write('2. ВЛИЯНИЕ ДЛИНЫ ЧИСЛА НА ЦИФРОВЫЕ ОПЕРАЦИИ:'), nl, + LengthTests = [12, 1234, 123456, 12345678], + forall(member(N, LengthTests), demo_length_impact(N)), + + nl, + write('3. ОПТИМИЗАЦИЯ И РЕКОМЕНДАЦИИ:'), nl, + write(' • Для подсчета делителей всегда используйте оптимизированную версию'), nl, + write(' • Рекурсия вниз предпочтительна для глубокой рекурсии'), nl, + write(' • Для цифровых операций длина числа влияет линейно'), nl, + write(' • Кэширование результатов может ускорить повторные вычисления'), nl. + +demo_divisor_scaling(N) :- + get_time(T1), + divisor_count_down(N, C1), + get_time(T2), + divisor_count_optimized(N, C2), + get_time(T3), + + TimeNormal is T2 - T1, + TimeOpt is T3 - T2, + SpeedUp is TimeNormal / TimeOpt, + + write('N='), write(N), write(': обычный='), write(TimeNormal), + write('с, оптим.='), write(TimeOpt), write('с, ускорение='), write(SpeedUp), write('x'), nl. + +demo_length_impact(N) :- + atom_chars(N, Chars), + length(Chars, Length), + + get_time(T1), + digit_product_down(N, _), + get_time(T2), + max_digit_not_div3_down(N, _), + get_time(T3), + + TimeProduct is T2 - T1, + TimeMaxDigit is T3 - T2, + + write('Длина='), write(Length), write(', произведение='), write(TimeProduct), + write('с, макс.цифра='), write(TimeMaxDigit), write('с'), nl. + +% ----------------------------------------------- +% Интерактивные примеры +% ----------------------------------------------- +demo_interactive_examples :- + write('=== ИНТЕРАКТИВНЫЕ ПРИМЕРЫ TASK2 ==='), nl, + write('Вы можете попробовать следующие запросы в интерактивной сессии:'), nl, nl, + + write('1. Произведение цифр:'), nl, + write(' ?- digit_product_up(123, P). % P = 6'), nl, + write(' ?- digit_product_down(456, P). % P = 120'), nl, + write(' ?- digit_product_up(102, P). % P = 0 (содержит 0)'), nl, nl, + + write('2. Максимальная цифра не делящаяся на 3:'), nl, + write(' ?- max_digit_not_div3_up(123, M). % M = 2'), nl, + write(' ?- max_digit_not_div3_down(369, M). % M = -1 (все дел. на 3)'), nl, + write(' ?- max_digit_not_div3_up(147, M). % M = 7'), nl, nl, + + write('3. Количество делителей:'), nl, + write(' ?- divisor_count_up(12, C). % C = 6'), nl, + write(' ?- divisor_count_down(16, C). % C = 5'), nl, + write(' ?- divisor_count_optimized(100, C). % C = 9'), nl, nl, + + write('4. Сравнение и анализ:'), nl, + write(' ?- compare_recursions(12345). % Сравнение всех методов'), nl, + write(' ?- benchmark_predicates(98765). % Измерение производительности'), nl, + write(' ?- interactive_demo. % Интерактивное меню'), nl, nl, + + write('5. Проверка эквивалентности:'), nl, + write(' ?- digit_product_up(X, 24), digit_product_down(X, 24). % Поиск чисел'), nl, + write(' ?- max_digit_not_div3_up(X, 8), X > 0, X < 1000. % Числа с макс. цифрой 8'), nl, nl. \ No newline at end of file diff --git a/lab9/task2/predicates.pl b/lab9/task2/predicates.pl new file mode 100644 index 0000000..086822f --- /dev/null +++ b/lab9/task2/predicates.pl @@ -0,0 +1,492 @@ +% =============================================== +% LAB9 TASK2 - СПЕЦИАЛЬНЫЕ ПРЕДИКАТЫ PROLOG +% Реализация рекурсивных предикатов с комментариями +% =============================================== + +% =============================================== +% 1. ПРОИЗВЕДЕНИЕ ЦИФР ЧИСЛА +% =============================================== + +/** + * digit_product_up(+N, -Product) + * Вычисляет произведение цифр числа с помощью рекурсии вверх. + * + * @param N - входное неотрицательное целое число (унифицированная переменная) + * @param Product - произведение цифр числа (неунифицированная переменная) + * + * Принцип работы: + * - Базовый случай: digit_product_up(0, 1) - произведение цифр нуля равно 1 + * - Рекурсивный случай: произведение цифр N = последняя_цифра * произведение_цифр(N//10) + * - Вычисления происходят при возврате из рекурсии + */ +digit_product_up(0, 1) :- !. % Базовый случай: произведение цифр 0 равно 1 +digit_product_up(N, Product) :- + N > 0, % N должно быть положительным (проверка унификации) + Digit is N mod 10, % Digit унифицируется с последней цифрой N + N1 is N // 10, % N1 унифицируется с N без последней цифры + digit_product_up(N1, SubProduct), % Рекурсивный вызов (SubProduct - неунифицированная) + Product is Digit * SubProduct. % Product унифицируется с произведением + +/** + * digit_product_down(+N, -Product) + * Вычисляет произведение цифр числа с помощью рекурсии вниз (с аккумулятором). + * + * @param N - входное неотрицательное целое число (унифицированная переменная) + * @param Product - произведение цифр числа (неунифицированная переменная) + * + * Использует вспомогательный предикат с аккумулятором для хвостовой рекурсии. + */ +digit_product_down(N, Product) :- + digit_product_down_helper(N, 1, Product). % Начинаем с аккумулятора = 1 + +/** + * digit_product_down_helper(+N, +Acc, -Product) + * Вспомогательный предикат для вычисления произведения цифр (рекурсия вниз). + * + * @param N - оставшаяся часть числа (унифицированная переменная) + * @param Acc - аккумулятор произведения (унифицированная переменная) + * @param Product - результат (неунифицированная переменная при вызове) + * + * Принцип работы: + * - Базовый случай: когда N = 0, Product унифицируется с аккумулятором + * - Рекурсивный случай: обновляем аккумулятор и продолжаем с N//10 + */ +digit_product_down_helper(0, Acc, Acc) :- !. % Product унифицируется с Acc +digit_product_down_helper(N, Acc, Product) :- + N > 0, % Проверка что N положительное + Digit is N mod 10, % Digit унифицируется с последней цифрой + N1 is N // 10, % N1 унифицируется с числом без последней цифры + Acc1 is Acc * Digit, % Acc1 унифицируется с новым аккумулятором + digit_product_down_helper(N1, Acc1, Product). % Хвостовая рекурсия + +% =============================================== +% 2. МАКСИМАЛЬНАЯ ЦИФРА, НЕ ДЕЛЯЩАЯСЯ НА 3 +% =============================================== + +/** + * max_digit_not_div3_up(+N, -MaxDigit) + * Находит максимальную цифру числа, которая не делится на 3 (рекурсия вверх). + * + * @param N - входное неотрицательное целое число (унифицированная переменная) + * @param MaxDigit - максимальная цифра не делящаяся на 3, или -1 если таких нет + * + * Принцип работы: + * - Базовый случай: для 0 возвращаем -1 (0 делится на 3) + * - Рекурсивный случай: сравниваем текущую цифру с максимумом из остальных + */ +max_digit_not_div3_up(0, -1) :- !. % 0 делится на 3, возвращаем -1 +max_digit_not_div3_up(N, MaxDigit) :- + N > 0, % N должно быть положительным + Digit is N mod 10, % Digit унифицируется с последней цифрой + N1 is N // 10, % N1 унифицируется с числом без последней цифры + max_digit_not_div3_up(N1, SubMax), % SubMax - максимум из остальных цифр + combine_max_not_div3(Digit, SubMax, MaxDigit). % Комбинируем результаты + +/** + * combine_max_not_div3(+Digit, +SubMax, -MaxDigit) + * Вспомогательный предикат для комбинирования цифры и подмаксимума. + * + * @param Digit - текущая цифра (унифицированная переменная) + * @param SubMax - максимум из остальных цифр (унифицированная переменная) + * @param MaxDigit - результирующий максимум (неунифицированная переменная) + */ +combine_max_not_div3(Digit, SubMax, MaxDigit) :- + ( Digit mod 3 =\= 0 -> % Если Digit не делится на 3 + ( SubMax =:= -1 -> % Если нет других подходящих цифр + MaxDigit = Digit % MaxDigit унифицируется с Digit + ; MaxDigit is max(Digit, SubMax) % MaxDigit унифицируется с максимумом + ) + ; MaxDigit = SubMax % Если Digit делится на 3, используем SubMax + ). + +/** + * max_digit_not_div3_down(+N, -MaxDigit) + * Находит максимальную цифру числа, которая не делится на 3 (рекурсия вниз). + * + * @param N - входное неотрицательное целое число (унифицированная переменная) + * @param MaxDigit - максимальная цифра не делящаяся на 3, или -1 если таких нет + */ +max_digit_not_div3_down(N, MaxDigit) :- + max_digit_not_div3_down_helper(N, -1, MaxDigit). % Начинаем с Max = -1 + +/** + * max_digit_not_div3_down_helper(+N, +CurrentMax, -MaxDigit) + * Вспомогательный предикат для поиска максимальной цифры (рекурсия вниз). + * + * @param N - оставшаяся часть числа (унифицированная переменная) + * @param CurrentMax - текущий максимум (унифицированная переменная) + * @param MaxDigit - результат (неунифицированная переменная при первом вызове) + */ +max_digit_not_div3_down_helper(0, CurrentMax, CurrentMax) :- !. % MaxDigit унифицируется с CurrentMax +max_digit_not_div3_down_helper(N, CurrentMax, MaxDigit) :- + N > 0, % Проверка положительности N + Digit is N mod 10, % Digit унифицируется с последней цифрой + N1 is N // 10, % N1 унифицируется с числом без последней цифры + update_max_not_div3(Digit, CurrentMax, NewMax), % NewMax - обновленный максимум + max_digit_not_div3_down_helper(N1, NewMax, MaxDigit). % Хвостовая рекурсия + +/** + * update_max_not_div3(+Digit, +CurrentMax, -NewMax) + * Обновляет текущий максимум с учетом новой цифры. + * + * @param Digit - новая цифра (унифицированная переменная) + * @param CurrentMax - текущий максимум (унифицированная переменная) + * @param NewMax - новый максимум (неунифицированная переменная) + */ +update_max_not_div3(Digit, CurrentMax, NewMax) :- + ( Digit mod 3 =\= 0 -> % Если цифра не делится на 3 + ( CurrentMax =:= -1 -> % Если это первая подходящая цифра + NewMax = Digit % NewMax унифицируется с Digit + ; NewMax is max(Digit, CurrentMax) % NewMax унифицируется с максимумом + ) + ; NewMax = CurrentMax % Если цифра делится на 3, максимум не меняется + ). + +% =============================================== +% 3. КОЛИЧЕСТВО ДЕЛИТЕЛЕЙ ЧИСЛА +% =============================================== + +/** + * divisor_count_up(+N, -Count) + * Подсчитывает количество делителей числа с помощью рекурсии вверх. + * + * @param N - входное положительное целое число (унифицированная переменная) + * @param Count - количество делителей (неунифицированная переменная) + * + * Принцип работы: + * - Проверяем каждое число от 1 до N на то, является ли оно делителем + * - Используем рекурсию для подсчета + */ +divisor_count_up(N, Count) :- + N > 0, % N должно быть положительным + divisor_count_up_helper(N, 1, Count). % Начинаем проверку с 1 + +/** + * divisor_count_up_helper(+N, +Current, -Count) + * Вспомогательный предикат для подсчета делителей (рекурсия вверх). + * + * @param N - число, для которого ищем делители (унифицированная переменная) + * @param Current - текущий проверяемый делитель (унифицированная переменная) + * @param Count - количество делителей (неунифицированная переменная) + */ +divisor_count_up_helper(N, Current, 0) :- + Current > N, !. % Базовый случай: превысили N, делителей 0 +divisor_count_up_helper(N, Current, Count) :- + Current =< N, % Current не превышает N + Next is Current + 1, % Next унифицируется со следующим числом + divisor_count_up_helper(N, Next, RestCount), % RestCount - количество остальных делителей + ( N mod Current =:= 0 -> % Если Current является делителем N + Count is RestCount + 1 % Count унифицируется с RestCount + 1 + ; Count = RestCount % Иначе Count унифицируется с RestCount + ). + +/** + * divisor_count_down(+N, -Count) + * Подсчитывает количество делителей числа с помощью рекурсии вниз. + * + * @param N - входное положительное целое число (унифицированная переменная) + * @param Count - количество делителей (неунифицированная переменная) + */ +divisor_count_down(N, Count) :- + N > 0, % N должно быть положительным + divisor_count_down_helper(N, 1, 0, Count). % Начинаем с делителя 1 и счетчика 0 + +/** + * divisor_count_down_helper(+N, +Current, +Acc, -Count) + * Вспомогательный предикат для подсчета делителей (рекурсия вниз). + * + * @param N - число, для которого ищем делители (унифицированная переменная) + * @param Current - текущий проверяемый делитель (унифицированная переменная) + * @param Acc - аккумулятор количества делителей (унифицированная переменная) + * @param Count - результат (неунифицированная переменная при первом вызове) + */ +divisor_count_down_helper(N, Current, Acc, Acc) :- + Current > N, !. % Базовый случай: Count унифицируется с Acc +divisor_count_down_helper(N, Current, Acc, Count) :- + Current =< N, % Current не превышает N + ( N mod Current =:= 0 -> % Если Current является делителем + Acc1 is Acc + 1 % Acc1 унифицируется с увеличенным аккумулятором + ; Acc1 = Acc % Иначе Acc1 унифицируется с неизмененным Acc + ), + Next is Current + 1, % Next унифицируется со следующим числом + divisor_count_down_helper(N, Next, Acc1, Count). % Хвостовая рекурсия + +% =============================================== +% ОПТИМИЗИРОВАННЫЕ ВЕРСИИ (ДОПОЛНИТЕЛЬНЫЕ) +% =============================================== + +/** + * divisor_count_optimized(+N, -Count) + * Оптимизированный подсчет делителей (проверяем только до sqrt(N)). + * + * @param N - входное положительное целое число (унифицированная переменная) + * @param Count - количество делителей (неунифицированная переменная) + * + * Принцип оптимизации: + * - Делители существуют парами: если d делит N, то N/d тоже делит N + * - Достаточно проверить числа от 1 до sqrt(N) + * - Для каждого найденного делителя d < sqrt(N) добавляем 2 к счетчику + * - Если N - точный квадрат, sqrt(N) добавляем только один раз + */ +divisor_count_optimized(N, Count) :- + N > 0, % N должно быть положительным + Sqrt is floor(sqrt(N)), % Sqrt унифицируется с floor(sqrt(N)) + divisor_count_optimized_helper(N, 1, Sqrt, 0, Count). + +/** + * divisor_count_optimized_helper(+N, +Current, +Sqrt, +Acc, -Count) + * Вспомогательный предикат для оптимизированного подсчета делителей. + */ +divisor_count_optimized_helper(N, Current, Sqrt, Acc, Acc) :- + Current > Sqrt, !. % Базовый случай +divisor_count_optimized_helper(N, Current, Sqrt, Acc, Count) :- + Current =< Sqrt, % Current не превышает Sqrt + ( N mod Current =:= 0 -> % Если Current является делителем + ( Current * Current =:= N -> % Если Current = sqrt(N) + Acc1 is Acc + 1 % Добавляем только 1 (сам делитель) + ; Acc1 is Acc + 2 % Добавляем 2 (делитель и его пару) + ) + ; Acc1 = Acc % Если не делитель, аккумулятор не изменяется + ), + Next is Current + 1, % Next унифицируется со следующим числом + divisor_count_optimized_helper(N, Next, Sqrt, Acc1, Count). + +% =============================================== +% ВСПОМОГАТЕЛЬНЫЕ И ДЕМОНСТРАЦИОННЫЕ ПРЕДИКАТЫ +% =============================================== + +/** + * task_info/0 + * Выводит информацию о реализованных предикатах. + * Все переменные являются унифицированными константами. + */ +task_info :- + write('РЕАЛИЗОВАННЫЕ ПРЕДИКАТЫ TASK2:'), nl, + write('1. digit_product_up(+N, -Product) - произведение цифр (рекурсия вверх)'), nl, + write('2. digit_product_down(+N, -Product) - произведение цифр (рекурсия вниз)'), nl, + write('3. max_digit_not_div3_up(+N, -MaxDigit) - макс. цифра не дел. на 3 (рек. вверх)'), nl, + write('4. max_digit_not_div3_down(+N, -MaxDigit) - макс. цифра не дел. на 3 (рек. вниз)'), nl, + write('5. divisor_count_up(+N, -Count) - количество делителей (рекурсия вверх)'), nl, + write('6. divisor_count_down(+N, -Count) - количество делителей (рекурсия вниз)'), nl, + write('7. divisor_count_optimized(+N, -Count) - оптимизированный подсчет делителей'), nl. + +/** + * show_examples/0 + * Показывает примеры использования предикатов. + */ +show_examples :- + write('ПРИМЕРЫ ИСПОЛЬЗОВАНИЯ:'), nl, + write('?- digit_product_up(123, P). % P = 6 (1*2*3)'), nl, + write('?- digit_product_down(123, P). % P = 6'), nl, + write('?- max_digit_not_div3_up(39627, M). % M = 7 (макс. не дел. на 3)'), nl, + write('?- max_digit_not_div3_down(39627, M). % M = 7'), nl, + write('?- divisor_count_up(12, C). % C = 6 (1,2,3,4,6,12)'), nl, + write('?- divisor_count_down(12, C). % C = 6'), nl, + write('?- divisor_count_optimized(12, C). % C = 6'), nl. + +/** + * test_all_predicates/0 + * Быстрое тестирование всех предикатов с примерами. + */ +test_all_predicates :- + write('=== ТЕСТИРОВАНИЕ ВСЕХ ПРЕДИКАТОВ ==='), nl, + + % Тест произведения цифр + write('Тест произведения цифр: '), + digit_product_up(123, P1), + digit_product_down(123, P2), + write('digit_product_up(123) = '), write(P1), + write(', digit_product_down(123) = '), write(P2), nl, + + % Тест максимальной цифры не делящейся на 3 + write('Тест макс. цифры не дел. на 3: '), + max_digit_not_div3_up(39627, M1), + max_digit_not_div3_down(39627, M2), + write('max_digit_not_div3_up(39627) = '), write(M1), + write(', max_digit_not_div3_down(39627) = '), write(M2), nl, + + % Тест количества делителей + write('Тест количества делителей: '), + divisor_count_up(12, C1), + divisor_count_down(12, C2), + divisor_count_optimized(12, C3), + write('divisor_count_up(12) = '), write(C1), + write(', divisor_count_down(12) = '), write(C2), + write(', divisor_count_optimized(12) = '), write(C3), nl, + + write('=== ТЕСТИРОВАНИЕ ЗАВЕРШЕНО ==='), nl. + +/** + * compare_recursions(+N)/1 + * Сравнивает результаты рекурсий вверх и вниз для заданного числа N. + * + * @param N - число для сравнения (унифицированная переменная) + */ +compare_recursions(N) :- + write('Сравнение рекурсий для числа '), write(N), write(':'), nl, + + % Произведение цифр + digit_product_up(N, P1), + digit_product_down(N, P2), + write(' Произведение цифр (вверх): '), write(P1), nl, + write(' Произведение цифр (вниз): '), write(P2), nl, + write(' Произведения равны: '), (P1 =:= P2 -> write('да') ; write('нет')), nl, + + % Максимальная цифра не делящаяся на 3 + max_digit_not_div3_up(N, M1), + max_digit_not_div3_down(N, M2), + write(' Макс. цифра не дел. на 3 (вверх): '), write(M1), nl, + write(' Макс. цифра не дел. на 3 (вниз): '), write(M2), nl, + write(' Максимумы равны: '), (M1 =:= M2 -> write('да') ; write('нет')), nl, + + % Количество делителей + divisor_count_up(N, C1), + divisor_count_down(N, C2), + divisor_count_optimized(N, C3), + write(' Количество делителей (вверх): '), write(C1), nl, + write(' Количество делителей (вниз): '), write(C2), nl, + write(' Количество делителей (оптим.): '), write(C3), nl, + write(' Все количества равны: '), + ((C1 =:= C2, C2 =:= C3) -> write('да') ; write('нет')), nl. + +/** + * benchmark_predicates(+N)/1 + * Измеряет производительность предикатов для числа N. + * + * @param N - число для бенчмарка (унифицированная переменная) + */ +benchmark_predicates(N) :- + write('Бенчмарк предикатов для N = '), write(N), write(':'), nl, + + % Бенчмарк произведения цифр + get_time(T1), + digit_product_up(N, _), + get_time(T2), + digit_product_down(N, _), + get_time(T3), + + TimeProductUp is T2 - T1, + TimeProductDown is T3 - T2, + + write(' Произведение цифр (вверх): '), write(TimeProductUp), write(' сек'), nl, + write(' Произведение цифр (вниз): '), write(TimeProductDown), write(' сек'), nl, + + % Бенчмарк максимальной цифры + get_time(T4), + max_digit_not_div3_up(N, _), + get_time(T5), + max_digit_not_div3_down(N, _), + get_time(T6), + + TimeMaxUp is T5 - T4, + TimeMaxDown is T6 - T5, + + write(' Макс. цифра (вверх): '), write(TimeMaxUp), write(' сек'), nl, + write(' Макс. цифра (вниз): '), write(TimeMaxDown), write(' сек'), nl, + + % Бенчмарк делителей + get_time(T7), + divisor_count_up(N, _), + get_time(T8), + divisor_count_down(N, _), + get_time(T9), + divisor_count_optimized(N, _), + get_time(T10), + + TimeDivUp is T8 - T7, + TimeDivDown is T9 - T8, + TimeDivOpt is T10 - T9, + + write(' Делители (вверх): '), write(TimeDivUp), write(' сек'), nl, + write(' Делители (вниз): '), write(TimeDivDown), write(' сек'), nl, + write(' Делители (оптим.): '), write(TimeDivOpt), write(' сек'), nl. + +/** + * interactive_demo/0 + * Интерактивная демонстрация всех предикатов. + */ +interactive_demo :- + write('=== ИНТЕРАКТИВНАЯ ДЕМОНСТРАЦИЯ TASK2 ==='), nl, + write('Выберите операцию:'), nl, + write('1. Произведение цифр числа'), nl, + write('2. Максимальная цифра не делящаяся на 3'), nl, + write('3. Количество делителей'), nl, + write('4. Сравнение рекурсий'), nl, + write('5. Бенчмарк производительности'), nl, + write('6. Выход'), nl, + write('Введите номер (1-6): '), + read(Choice), + handle_choice(Choice). + +/** + * handle_choice(+Choice)/1 + * Обрабатывает выбор пользователя в интерактивном режиме. + * + * @param Choice - выбор пользователя (унифицированная переменная) + */ +handle_choice(1) :- + write('Введите число для вычисления произведения цифр: '), + read(N), + ( N >= 0 -> + digit_product_up(N, P1), + digit_product_down(N, P2), + write('Произведение цифр '), write(N), write(' (рекурсия вверх) = '), write(P1), nl, + write('Произведение цифр '), write(N), write(' (рекурсия вниз) = '), write(P2), nl + ; write('Число должно быть неотрицательным!'), nl + ), + interactive_demo. + +handle_choice(2) :- + write('Введите число для поиска максимальной цифры не делящейся на 3: '), + read(N), + ( N >= 0 -> + max_digit_not_div3_up(N, M1), + max_digit_not_div3_down(N, M2), + write('Максимальная цифра '), write(N), write(' не дел. на 3 (рекурсия вверх) = '), write(M1), nl, + write('Максимальная цифра '), write(N), write(' не дел. на 3 (рекурсия вниз) = '), write(M2), nl, + ( M1 =:= -1 -> + write('Все цифры числа делятся на 3'), nl + ; true + ) + ; write('Число должно быть неотрицательным!'), nl + ), + interactive_demo. + +handle_choice(3) :- + write('Введите число для подсчета количества делителей: '), + read(N), + ( N > 0 -> + divisor_count_up(N, C1), + divisor_count_down(N, C2), + divisor_count_optimized(N, C3), + write('Количество делителей '), write(N), write(' (рекурсия вверх) = '), write(C1), nl, + write('Количество делителей '), write(N), write(' (рекурсия вниз) = '), write(C2), nl, + write('Количество делителей '), write(N), write(' (оптимизированный) = '), write(C3), nl + ; write('Число должно быть положительным!'), nl + ), + interactive_demo. + +handle_choice(4) :- + write('Введите число для сравнения рекурсий: '), + read(N), + ( N >= 0 -> + compare_recursions(N) + ; write('Число должно быть неотрицательным!'), nl + ), + interactive_demo. + +handle_choice(5) :- + write('Введите число для бенчмарка: '), + read(N), + ( N >= 0 -> + benchmark_predicates(N) + ; write('Число должно быть неотрицательным!'), nl + ), + interactive_demo. + +handle_choice(6) :- + write('До свидания!'), nl. + +handle_choice(_) :- + write('Неверный выбор! Попробуйте снова.'), nl, + interactive_demo. \ No newline at end of file diff --git a/lab9/task2/tests.pl b/lab9/task2/tests.pl new file mode 100644 index 0000000..cf6e7b8 --- /dev/null +++ b/lab9/task2/tests.pl @@ -0,0 +1,393 @@ +% =============================================== +% ТЕСТЫ ДЛЯ LAB9 TASK2 +% Автоматические тесты всех предикатов +% =============================================== + +% Запуск всех тестов +run_tests :- + write('=== ЗАПУСК АВТОМАТИЧЕСКИХ ТЕСТОВ TASK2 ==='), nl, + test_digit_product, + test_max_digit_not_div3, + test_divisor_count, + test_recursion_equivalence, + test_edge_cases, + test_performance, + write('=== ВСЕ ТЕСТЫ TASK2 ЗАВЕРШЕНЫ ==='), nl. + +% =============================================== +% ТЕСТЫ ДЛЯ ПРОИЗВЕДЕНИЯ ЦИФР +% =============================================== +test_digit_product :- + write('--- Тестирование произведения цифр ---'), nl, + + % Тест базового случая + (digit_product_up(0, 1) -> + write('✓ digit_product_up(0,1) - ПРОЙДЕН') ; + write('✗ digit_product_up(0,1) - ПРОВАЛЕН')), nl, + + (digit_product_down(0, 1) -> + write('✓ digit_product_down(0,1) - ПРОЙДЕН') ; + write('✗ digit_product_down(0,1) - ПРОВАЛЕН')), nl, + + % Тест известных значений + TestCases = [ + (123, 6), % 1*2*3 = 6 + (456, 120), % 4*5*6 = 120 + (789, 504), % 7*8*9 = 504 + (102, 0), % 1*0*2 = 0 + (555, 125), % 5*5*5 = 125 + (1, 1), % 1 = 1 + (9, 9) % 9 = 9 + ], + forall(member((N, Expected), TestCases), test_digit_product_case(N, Expected)), + + % Тест эквивалентности рекурсий + test_digit_product_equivalence([0, 1, 12, 123, 1234, 5555, 102, 9876]), + nl. + +test_digit_product_case(N, Expected) :- + digit_product_up(N, P1), + digit_product_down(N, P2), + write('Произведение цифр '), write(N), write(': up='), write(P1), + write(', down='), write(P2), write(', ожидалось='), write(Expected), + ((P1 =:= Expected, P2 =:= Expected) -> write(' ✓') ; write(' ✗')), nl. + +test_digit_product_equivalence([]). +test_digit_product_equivalence([N|Rest]) :- + digit_product_up(N, P1), + digit_product_down(N, P2), + (P1 =:= P2 -> + write('✓ Произведения цифр '), write(N), write(' эквивалентны') ; + write('✗ Произведения цифр '), write(N), write(' НЕ эквивалентны')), nl, + test_digit_product_equivalence(Rest). + +% =============================================== +% ТЕСТЫ ДЛЯ МАКСИМАЛЬНОЙ ЦИФРЫ НЕ ДЕЛЯЩЕЙСЯ НА 3 +% =============================================== +test_max_digit_not_div3 :- + write('--- Тестирование максимальной цифры не делящейся на 3 ---'), nl, + + % Тест базового случая + (max_digit_not_div3_up(0, -1) -> + write('✓ max_digit_not_div3_up(0,-1) - ПРОЙДЕН') ; + write('✗ max_digit_not_div3_up(0,-1) - ПРОВАЛЕН')), nl, + + (max_digit_not_div3_down(0, -1) -> + write('✓ max_digit_not_div3_down(0,-1) - ПРОЙДЕН') ; + write('✗ max_digit_not_div3_down(0,-1) - ПРОВАЛЕН')), nl, + + % Тест известных значений + TestCases = [ + (123, 2), % цифры: 1,2,3 -> не дел. на 3: 1,2 -> макс: 2 + (456, 5), % цифры: 4,5,6 -> не дел. на 3: 4,5 -> макс: 5 + (789, 8), % цифры: 7,8,9 -> не дел. на 3: 7,8 -> макс: 8 + (369, -1), % цифры: 3,6,9 -> все дел. на 3 -> -1 + (147, 7), % цифры: 1,4,7 -> не дел. на 3: 1,4,7 -> макс: 7 + (1, 1), % цифра: 1 -> не дел. на 3: 1 -> макс: 1 + (39, -1), % цифры: 3,9 -> все дел. на 3 -> -1 + (12457, 7), % цифры: 1,2,4,5,7 -> не дел. на 3: 1,2,4,5,7 -> макс: 7 + (36960, -1) % цифры: 3,6,9,6,0 -> все дел. на 3 -> -1 + ], + forall(member((N, Expected), TestCases), test_max_digit_not_div3_case(N, Expected)), + + % Тест эквивалентности рекурсий + test_max_digit_not_div3_equivalence([0, 123, 456, 369, 147, 12457, 98765]), + nl. + +test_max_digit_not_div3_case(N, Expected) :- + max_digit_not_div3_up(N, M1), + max_digit_not_div3_down(N, M2), + write('Макс. цифра не дел. на 3 для '), write(N), write(': up='), write(M1), + write(', down='), write(M2), write(', ожидалось='), write(Expected), + ((M1 =:= Expected, M2 =:= Expected) -> write(' ✓') ; write(' ✗')), nl. + +test_max_digit_not_div3_equivalence([]). +test_max_digit_not_div3_equivalence([N|Rest]) :- + max_digit_not_div3_up(N, M1), + max_digit_not_div3_down(N, M2), + (M1 =:= M2 -> + write('✓ Макс. цифры не дел. на 3 для '), write(N), write(' эквивалентны') ; + write('✗ Макс. цифры не дел. на 3 для '), write(N), write(' НЕ эквивалентны')), nl, + test_max_digit_not_div3_equivalence(Rest). + +% =============================================== +% ТЕСТЫ ДЛЯ КОЛИЧЕСТВА ДЕЛИТЕЛЕЙ +% =============================================== +test_divisor_count :- + write('--- Тестирование количества делителей ---'), nl, + + % Тест известных значений + TestCases = [ + (1, 1), % делители: 1 + (2, 2), % делители: 1, 2 + (6, 4), % делители: 1, 2, 3, 6 + (12, 6), % делители: 1, 2, 3, 4, 6, 12 + (16, 5), % делители: 1, 2, 4, 8, 16 + (24, 8), % делители: 1, 2, 3, 4, 6, 8, 12, 24 + (100, 9), % делители: 1, 2, 4, 5, 10, 20, 25, 50, 100 + (7, 2), % делители: 1, 7 (простое число) + (13, 2) % делители: 1, 13 (простое число) + ], + forall(member((N, Expected), TestCases), test_divisor_count_case(N, Expected)), + + % Тест эквивалентности всех методов + test_divisor_count_equivalence([1, 2, 6, 12, 16, 24, 36, 48]), + nl. + +test_divisor_count_case(N, Expected) :- + divisor_count_up(N, C1), + divisor_count_down(N, C2), + divisor_count_optimized(N, C3), + write('Количество делителей '), write(N), write(': up='), write(C1), + write(', down='), write(C2), write(', opt='), write(C3), + write(', ожидалось='), write(Expected), + ((C1 =:= Expected, C2 =:= Expected, C3 =:= Expected) -> write(' ✓') ; write(' ✗')), nl. + +test_divisor_count_equivalence([]). +test_divisor_count_equivalence([N|Rest]) :- + divisor_count_up(N, C1), + divisor_count_down(N, C2), + divisor_count_optimized(N, C3), + ((C1 =:= C2, C2 =:= C3) -> + write('✓ Количества делителей '), write(N), write(' эквивалентны') ; + write('✗ Количества делителей '), write(N), write(' НЕ эквивалентны')), nl, + test_divisor_count_equivalence(Rest). + +% =============================================== +% ТЕСТЫ ЭКВИВАЛЕНТНОСТИ РЕКУРСИЙ +% =============================================== +test_recursion_equivalence :- + write('--- Тестирование эквивалентности рекурсий ---'), nl, + + TestNumbers = [0, 1, 12, 123, 1234, 5555, 9876, 102030], + write('Проверка эквивалентности для чисел: '), write(TestNumbers), nl, + + forall(member(N, TestNumbers), test_single_number_equivalence(N)), + nl. + +test_single_number_equivalence(N) :- + % Произведение цифр + digit_product_up(N, P1), + digit_product_down(N, P2), + ProductOK = (P1 =:= P2), + + % Максимальная цифра не делящаяся на 3 + max_digit_not_div3_up(N, M1), + max_digit_not_div3_down(N, M2), + MaxDigitOK = (M1 =:= M2), + + % Количество делителей (только для положительных) + ( N > 0 -> + divisor_count_up(N, C1), + divisor_count_down(N, C2), + divisor_count_optimized(N, C3), + DivisorOK = (C1 =:= C2, C2 =:= C3) + ; DivisorOK = true + ), + + % Общий результат + ( (ProductOK, MaxDigitOK, DivisorOK) -> + write('✓ ') + ; write('✗ ') + ), + write('Число '), write(N), write(' - все рекурсии эквивалентны'), nl. + +% =============================================== +% ТЕСТЫ ГРАНИЧНЫХ СЛУЧАЕВ +% =============================================== +test_edge_cases :- + write('--- Тестирование граничных случаев ---'), nl, + + % Тест с числом, содержащим только нули + write('Тест с числом 1000:'), nl, + digit_product_up(1000, P1000), + write(' Произведение цифр 1000 = '), write(P1000), + (P1000 =:= 0 -> write(' ✓') ; write(' ✗')), nl, + + % Тест с числом, все цифры которого делятся на 3 + write('Тест с числом 3699 (все цифры дел. на 3):'), nl, + max_digit_not_div3_up(3699, M3699), + write(' Макс. цифра не дел. на 3 для 3699 = '), write(M3699), + (M3699 =:= -1 -> write(' ✓') ; write(' ✗')), nl, + + % Тест с простыми числами + write('Тест с простыми числами:'), nl, + PrimeNumbers = [2, 3, 5, 7, 11, 13, 17, 19], + forall(member(P, PrimeNumbers), test_prime_divisors(P)), + + % Тест с точными квадратами + write('Тест с точными квадратами:'), nl, + Squares = [1, 4, 9, 16, 25, 36, 49], + forall(member(S, Squares), test_square_divisors(S)), + nl. + +test_prime_divisors(P) :- + divisor_count_optimized(P, Count), + write(' Простое число '), write(P), write(' имеет '), write(Count), write(' делителей'), + (Count =:= 2 -> write(' ✓') ; write(' ✗')), nl. + +test_square_divisors(S) :- + divisor_count_optimized(S, Count), + Sqrt is floor(sqrt(S)), + ( Sqrt * Sqrt =:= S -> + write(' Точный квадрат '), write(S), write(' имеет '), write(Count), write(' делителей ✓') + ; write(' Ошибка: '), write(S), write(' не является точным квадратом ✗') + ), nl. + +% =============================================== +% ТЕСТЫ ПРОИЗВОДИТЕЛЬНОСТИ +% =============================================== +test_performance :- + write('--- Тесты производительности ---'), nl, + + write('Сравнение производительности для больших чисел:'), nl, + TestNumbers = [12345, 123456, 1234567], + forall(member(N, TestNumbers), test_performance_single(N)), + + write('Сравнение оптимизированного алгоритма делителей:'), nl, + LargeNumbers = [100, 1000, 10000], + forall(member(N, LargeNumbers), test_divisor_performance(N)), + nl. + +test_performance_single(N) :- + write('Тестирование производительности для N = '), write(N), write(':'), nl, + + % Произведение цифр + get_time(T1), + digit_product_up(N, _), + get_time(T2), + digit_product_down(N, _), + get_time(T3), + + TimeProductUp is T2 - T1, + TimeProductDown is T3 - T2, + + write(' Произведение цифр: up='), write(TimeProductUp), + write('с, down='), write(TimeProductDown), write('с'), nl, + + % Максимальная цифра + get_time(T4), + max_digit_not_div3_up(N, _), + get_time(T5), + max_digit_not_div3_down(N, _), + get_time(T6), + + TimeMaxUp is T5 - T4, + TimeMaxDown is T6 - T5, + + write(' Макс. цифра: up='), write(TimeMaxUp), + write('с, down='), write(TimeMaxDown), write('с'), nl. + +test_divisor_performance(N) :- + write('Тестирование делителей для N = '), write(N), write(':'), nl, + + get_time(T1), + divisor_count_up(N, C1), + get_time(T2), + divisor_count_down(N, C2), + get_time(T3), + divisor_count_optimized(N, C3), + get_time(T4), + + TimeUp is T2 - T1, + TimeDown is T3 - T2, + TimeOpt is T4 - T3, + + write(' Результаты: up='), write(C1), write(', down='), write(C2), + write(', opt='), write(C3), nl, + write(' Времена: up='), write(TimeUp), write('с, down='), write(TimeDown), + write('с, opt='), write(TimeOpt), write('с'), nl, + + % Проверка ускорения оптимизированной версии + ( TimeOpt < TimeUp -> + SpeedUp is TimeUp / TimeOpt, + write(' Ускорение оптимизированной версии: '), write(SpeedUp), write('x ✓') + ; write(' Оптимизированная версия медленнее ✗') + ), nl. + +% =============================================== +% СТРЕСС-ТЕСТЫ +% =============================================== +stress_test :- + write('--- Стресс-тесты ---'), nl, + + write('Тестирование 50 случайных чисел:'), nl, + stress_test_random_numbers(50), + + write('Тестирование больших чисел:'), nl, + LargeNumbers = [99999, 123456789, 987654321], + forall(member(N, LargeNumbers), stress_test_large_number(N)), + nl. + +stress_test_random_numbers(0) :- !. +stress_test_random_numbers(N) :- + N > 0, + random(0, 10000, TestNumber), + + % Быстрая проверка корректности + ( TestNumber > 0 -> + divisor_count_up(TestNumber, C1), + divisor_count_optimized(TestNumber, C2), + (C1 =:= C2 -> Status = 'OK' ; Status = 'ERROR') + ; Status = 'SKIP' + ), + + (N mod 10 =:= 0 -> + (write('Тест '), write(N), write(': '), write(TestNumber), + write(' - '), write(Status), nl) + ; true), + + N1 is N - 1, + stress_test_random_numbers(N1). + +stress_test_large_number(N) :- + write('Тестирование большого числа '), write(N), write(':'), nl, + + get_time(T1), + digit_product_down(N, Product), + get_time(T2), + max_digit_not_div3_down(N, MaxDigit), + get_time(T3), + + TimeProduct is T2 - T1, + TimeMaxDigit is T3 - T2, + + write(' Произведение цифр: '), write(Product), + write(' (время: '), write(TimeProduct), write('с)'), nl, + write(' Макс. цифра не дел. на 3: '), write(MaxDigit), + write(' (время: '), write(TimeMaxDigit), write('с)'), nl. + +% =============================================== +% ДОПОЛНИТЕЛЬНЫЕ ТЕСТЫ +% =============================================== + +% Тест специальных случаев для произведения цифр +test_digit_product_special :- + write('--- Специальные тесты произведения цифр ---'), nl, + + % Числа с нулями + ZeroNumbers = [10, 102, 1020, 10203], + forall(member(N, ZeroNumbers), test_zero_product(N)), + + % Числа из одинаковых цифр + SameDigitNumbers = [111, 222, 3333, 55555], + forall(member(N, SameDigitNumbers), test_same_digit_product(N)), + nl. + +test_zero_product(N) :- + digit_product_up(N, Product), + write('Произведение цифр '), write(N), write(' = '), write(Product), + (Product =:= 0 -> write(' ✓') ; write(' ✗')), nl. + +test_same_digit_product(N) :- + digit_product_up(N, Product), + % Получаем первую цифру + FirstDigit is N mod 10, + % Подсчитываем количество цифр + atom_chars(N, Chars), + length(Chars, DigitCount), + % Ожидаемое произведение + Expected is FirstDigit ^ DigitCount, + write('Произведение цифр '), write(N), write(' = '), write(Product), + write(', ожидалось '), write(Expected), + (Product =:= Expected -> write(' ✓') ; write(' ✗')), nl. \ No newline at end of file