lab 9 task 2

main
Artem-Darius Weber 8 months ago
parent b325520a2d
commit 0db12aa71f

@ -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) # task1 (default)
run: run:
@ -37,6 +37,31 @@ trace-task1:
info-task1: info-task1:
cd task1 && make info 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
help: help:
@echo "Available commands for lab9:" @echo "Available commands for lab9:"

@ -0,0 +1,7 @@
FROM swipl:stable
WORKDIR /app
COPY *.pl ./
CMD ["swipl", "-g", "consult('predicates.pl')", "-t", "halt"]

@ -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"

@ -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.

@ -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.

@ -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.
Loading…
Cancel
Save