51 Commits

Author SHA1 Message Date
9f7e34f7cd Добавил все искажения 2024-11-23 17:23:23 +03:00
ddabccfd2b Закодированный текст 2024-11-23 17:14:58 +03:00
bfa7c918b2 Дописки про алфавит 2024-11-21 15:34:40 +03:00
b816f9f17c Обновлённые результаты 2024-11-21 15:34:28 +03:00
d8db6809e4 Алфавит считывается из файла при декодировании 2024-11-21 15:12:08 +03:00
09b722a8eb Сохранение алфавита в файл 2024-11-21 15:08:04 +03:00
66279d0ff4 Сохранения зашифрованного текста в файл 2024-11-21 15:04:13 +03:00
1ea541e8c4 Заключение 2024-11-19 20:34:58 +03:00
377cc25109 Результаты работы программы 2024-11-19 20:27:17 +03:00
3065249023 чтение данных из изображения 2024-11-19 20:05:57 +03:00
ae0bc951aa Сохранение данных в картинке 2024-11-19 20:03:02 +03:00
2097a70cbf Работа с файлами 2024-11-19 19:49:49 +03:00
33c029ce7f Про биты 2024-11-19 19:21:39 +03:00
2781fbabbc Кодирование и декодирование 2024-11-19 19:12:34 +03:00
8c75ccf7ef Шифр Цезаря в мат описании 2024-11-19 18:51:23 +03:00
1d7ceb8fc9 Изображение и текст 2024-11-19 18:31:38 +03:00
842bffc8cd Заготовка для отчёта 2024-11-19 17:21:35 +03:00
622224db79 Добавил кол-во бит в название 2024-11-19 15:18:21 +03:00
b9cf0e7fe3 Баг с извлечением смещения 2024-11-19 14:58:01 +03:00
e90dfd6816 Ввод смещения и кол-ва бит 2024-11-19 14:53:23 +03:00
0faffa20ce Ограничил список доступных функций 2024-11-19 14:37:33 +03:00
19b8202fa8 Извлечение ключа из имени файла 2024-11-19 14:32:34 +03:00
01003c1ee0 Красивенький вывод в консольку 2024-11-19 14:17:30 +03:00
2dedc4416a Дешифровка текста из картинки 2024-11-19 13:56:31 +03:00
3205247f1c Сохранение кода в картинке 2024-11-19 12:33:19 +03:00
54e266b899 Заготовка для генератора картинки 2024-11-18 03:45:29 +03:00
5711244be4 Искажение и сохранение изображения 2024-11-18 02:07:14 +03:00
e6d636cff2 Биты хранятся в Vector 2024-11-18 01:35:40 +03:00
abb078e1b6 Чтение изображения с JuicyPixels 2024-11-17 20:48:05 +03:00
d9076f7dfa Форматирование кода 2024-11-17 20:20:23 +03:00
a69a81d89a Функция декодирования цезаря 2024-11-17 20:19:01 +03:00
f1fd3c1dea Из битов в текст 2024-11-17 20:10:32 +03:00
2dc18efbe4 Текст в список бит 2024-11-17 19:47:13 +03:00
ef9f0448e4 Шифрование по цезарю 2024-11-17 19:21:47 +03:00
098db8a7f0 Динамическое извлечение алфавита 2024-11-17 19:09:02 +03:00
035f2baf76 Задание в текстовом виде 2024-11-17 18:21:09 +03:00
bb1dcb815f Фрагмент биографии 2024-11-17 16:29:25 +03:00
a4ed564c62 Портрет в bmp 2024-11-17 16:13:26 +03:00
40b467cba5 stack project initialized 2024-11-17 16:00:25 +03:00
7b452ed326 Курсач и 3 лаба сами задания 2024-11-17 14:17:47 +03:00
b8b5dd1d2b Актуальный код в fern.hs 2024-11-14 19:53:56 +03:00
ee1bcd7551 Исправил отступы в коде прощающей стратегии 2024-11-14 19:50:09 +03:00
5bad80df69 Обновил код Неша 2024-11-14 19:49:49 +03:00
9d7bfab252 Таблица исходов дилеммы заключённого в виде матрицы 2024-11-14 19:44:49 +03:00
0bc49ecd9a Разделы не заканчиваются картинками 2024-11-14 19:39:06 +03:00
14225318ea Подпись листингов сверху 2024-11-14 19:34:18 +03:00
da72a98baa Merge branch 'test' 2024-11-14 19:31:31 +03:00
080d71d0db Безмонадная версия папоротника в отчёте 2024-11-14 16:12:50 +03:00
767189d5ae Вынес randomSeed 2024-11-14 15:50:33 +03:00
0d29c786db Папоротник без монад 2024-11-14 15:46:06 +03:00
4ff6c6d204 Альтернативный Нэш 2024-11-14 15:29:14 +03:00
34 changed files with 1091 additions and 105 deletions

BIN
coursework/task.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

View File

@@ -1,4 +1,4 @@
import System.Random (randomRIO)
import System.Random (StdGen, mkStdGen, randomR)
type Point = (Float, Float)
@@ -21,22 +21,20 @@ applyTransformation point random
| random < 0.93 = transformation3 point
| otherwise = transformation4 point
genNextPoint :: Point -> IO Point
genNextPoint point = do
random <- randomRIO (0.0, 1.0 :: Float)
return $ applyTransformation point random
genNextPoint :: StdGen -> Point -> (Point, StdGen)
genNextPoint gen point =
let (random, newGen) = randomR (0.0, 1.0 :: Float) gen
in (applyTransformation point random, newGen)
barnsleyFern :: Point -> Int -> IO [Point]
barnsleyFern _ 0 = return []
barnsleyFern startPoint n = do
x' <- genNextPoint startPoint
xs <- barnsleyFern x' (n - 1)
return (startPoint : xs)
barnsleyFern :: StdGen -> Point -> Int -> [Point]
barnsleyFern _ _ 0 = []
barnsleyFern gen startPoint n =
let (nextPoint, newGen) = genNextPoint gen startPoint
in startPoint : barnsleyFern newGen nextPoint (n - 1)
n = 1000
randomSeed = 17
main :: IO ()
main = do
putStrLn "Укажите количество шагов рекурсии:"
input <- getLine
let n = read input :: Int
fractal <- barnsleyFern (0, 0) n
print fractal
main = print $ barnsleyFern (mkStdGen randomSeed) (0, 0) n

View File

@@ -0,0 +1,44 @@
-- Не стоит в первой же лабе использовать монады, можно и без них
import System.Random (randomRIO)
type Point = (Float, Float)
transformation1 :: Point -> Point
transformation1 (_, y) = (0, 0.16 * y)
transformation2 :: Point -> Point
transformation2 (x, y) = (0.85 * x + 0.04 * y, -0.04 * x + 0.85 * y + 1.6)
transformation3 :: Point -> Point
transformation3 (x, y) = (0.2 * x - 0.26 * y, 0.23 * x + 0.22 * y + 1.6)
transformation4 :: Point -> Point
transformation4 (x, y) = (-0.15 * x + 0.28 * y, 0.26 * x + 0.24 * y + 0.44)
applyTransformation :: Point -> Float -> Point
applyTransformation point random
| random < 0.01 = transformation1 point
| random < 0.86 = transformation2 point
| random < 0.93 = transformation3 point
| otherwise = transformation4 point
genNextPoint :: Point -> IO Point
genNextPoint point = do
random <- randomRIO (0.0, 1.0 :: Float)
return $ applyTransformation point random
barnsleyFern :: Point -> Int -> IO [Point]
barnsleyFern _ 0 = return []
barnsleyFern startPoint n = do
x' <- genNextPoint startPoint
xs <- barnsleyFern x' (n - 1)
return (startPoint : xs)
main :: IO ()
main = do
putStrLn "Укажите количество шагов рекурсии:"
input <- getLine
let n = read input :: Int
fractal <- barnsleyFern (0, 0) n
print fractal

View File

@@ -21,13 +21,12 @@ nashEquilibriumStrategy opponentMoves generatedMoves n =
nashEquilibriumStrategy (tail opponentMoves) (generatedMoves ++ [nextStep]) (n + 1)
else generatedMoves
where
cases = [['П', 'С'], ['П', 'П']]
results = [[0, 10], [5, 5]]
result =
[ min (results !! 0 !! 1) (results !! 1 !! 1),
max (results !! 0 !! 0) (results !! 1 !! 0)
]
nextStep = cases !! indexOf results result !! 1
cases = [['С', 'С'], ['С', 'П'], ['П', 'С'], ['П', 'П']]
results = [[1, 1], [10, 0], [0, 10], [5, 5]]
p_years = min (results !! 1 !! 1) (results !! 3 !! 1)
s_years = min (results !! 0 !! 1) (results !! 2 !! 1)
nextStep | p_years <= s_years = 'П'
| otherwise = 'С'
getScore :: [Char] -> [Char] -> Int -> Int -> (Int, Int)
getScore moves1 moves2 score1 score2 =
@@ -47,7 +46,8 @@ game playerMoves gameStrategy
playerMoves = ['С', 'П', 'С', 'С', 'П', 'П', 'С', 'С', 'П', 'П']
gameStrategy = forgivingStrategy -- forgivingStrategy, nashEquilibriumStrategy
-- gameStrategy = forgivingStrategy
gameStrategy = nashEquilibriumStrategy
computerMoves = game playerMoves gameStrategy
(score1, score2) = getScore playerMoves computerMoves 0 0

View File

@@ -103,7 +103,7 @@
extendedchars=\true,
inputencoding=utf8,
keepspaces=true,
captionpos=b,
captionpos=t,
}
\begin{document} % начало документа
@@ -187,29 +187,30 @@
P(T_1) = 0.01, \quad P(T_2) = 0.85, \quad P(T_3) = 0.07, \quad P(T_4) = 0.07.
\]
\begin{figure}[h!]
\centering
\includegraphics[width=0.31\linewidth]{img/fern500.png}
\caption{Папоротник Барнсли для n = 500.}
\label{fig:fern500}
\end{figure}
\begin{figure}[h!]
\centering
\includegraphics[width=0.31\linewidth]{img/fern5000.png}
\caption{Папоротник Барнсли для n = 5000.}
\label{fig:fern5000}
\end{figure}
\begin{figure}[h!]
\centering
\includegraphics[width=0.31\linewidth]{img/fern50000.png}
\caption{Папоротник Барнсли для n = 50000.}
\label{fig:fern50000}
\end{figure}
На рисунках 1-3 приведены примеры папоротника Барнсли для разного количества точек (n). Во всех примерах в качестве начальной была выбрана точка с координатами (0, 0).
\begin{figure}[h!]
\centering
\includegraphics[width=0.41\linewidth]{img/fern500.png}
\caption{Папоротник Барнсли для n = 500.}
\label{fig:fern500}
\end{figure}
\begin{figure}[h!]
\centering
\includegraphics[width=0.41\linewidth]{img/fern5000.png}
\caption{Папоротник Барнсли для n = 5000.}
\label{fig:fern5000}
\end{figure}
\begin{figure}[h!]
\centering
\includegraphics[width=0.41\linewidth]{img/fern50000.png}
\caption{Папоротник Барнсли для n = 50000.}
\label{fig:fern50000}
\end{figure}
\newpage
\subsection{Дилемма заключённого}
Дилемма заключённого — фундаментальная проблема в теории игр, согласно которой рациональные игроки не всегда будут сотрудничать друг с другом, даже если это в их интересах. Предполагается, что игрок (<<заключённый>>) максимизирует свой собственный выигрыш, не заботясь о выгоде других. В классическом варианте дилеммы заключённого два игрока могут выбрать одно из двух действий:
@@ -230,24 +231,21 @@
\begin{table}[h!]
\centering
\caption{Исходы дилеммы заключённого}
\caption{Исходы дилеммы заключённого: \( \text{С} \) — сотрудничество, \( \text{П} \) — предательство}
\label{tbl:exodus}
\footnotesize
\begin{tabularx}{\textwidth}{|p{3.5cm}|p{3.5cm}|X|}
\begin{tabular}{|c|c|c|}
\hline
\textbf{Действие игрока 1} & \textbf{Действие игрока 2} & \textbf{Наказание игрока 1 / Наказание игрока 2} \\
& \textbf{Игрок 2: С} & \textbf{Игрок 2: П} \\
\hline
Сотрудничество & Сотрудничество & 1 год / 1 год \\
\textbf{Игрок 1: С} & 1 год / 1 год & 10 лет / 0 лет \\
\hline
Сотрудничество & Предательство & 10 лет / 0 лет \\
\textbf{Игрок 1: П} & 0 лет / 10 лет & 5 лет / 5 лет \\
\hline
Предательство & Сотрудничество & 0 лет / 10 лет \\
\hline
Предательство & Предательство & 5 лет / 5 лет \\
\hline
\end{tabularx}
\end{tabular}
\end{table}
В повторяющейся дилемме заключённого игра происходит периодически, и каждый игрок может «наказать» другого за несотрудничество ранее.
\subsection{Равновесие Нэша}
@@ -311,10 +309,10 @@
\subsubsection{Генерация новой точки}
Генерация новой точки происходит с помощью функции \texttt{genNextPoint} и вспомогательной функции \texttt{applyTransformation}, код которых представлен в листинге~\ref{lst:genDot}. \texttt{applyTransformation} принимает на вход исходную точку и случайное число от 0 до 1, затем выбирает и применяет к точке трансформацию в соответствии с заданными вероятностями, и возвращает новую точку. \texttt{genNextPoint} принимает на вход исходную точку, генерирует случайное число от 0 до 1, применяет функцию \texttt{applyTransformation} и возвращает новую точку.
Генерация новой точки происходит с помощью функции \texttt{genNextPoint} и вспомогательной функции \texttt{applyTransformation}, код которых представлен в листинге~\ref{lst:genDot}. \texttt{applyTransformation} принимает на вход исходную точку и случайное число от 0 до 1, затем выбирает и применяет к точке трансформацию в соответствии с заданными вероятностями, и возвращает новую точку. \texttt{genNextPoint} принимает на вход исходную точку и состояние генератора случайных чисел -- \texttt{gen}, генерирует случайное число от 0 до 1, применяет функцию \texttt{applyTransformation} и возвращает пару -- новую точку и новое состояние генератора случайных чисел.
\begin{lstlisting}[caption={Код функций для генераций новых точек в папоротнике Барнсли.}, label={lst:genDot}]
import System.Random (randomRIO)
import System.Random (StdGen, mkStdGen, randomR)
applyTransformation :: Point -> Float -> Point
applyTransformation point random
@@ -322,40 +320,33 @@
| random < 0.86 = transformation2 point
| random < 0.93 = transformation3 point
| otherwise = transformation4 point
genNextPoint :: Point -> IO Point
genNextPoint point = do
random <- randomRIO (0.0, 1.0 :: Float)
return $ applyTransformation point random
\end{lstlisting}
genNextPoint :: StdGen -> Point -> (Point, StdGen)
genNextPoint gen point =
let (random, newGen) = randomR (0.0, 1.0 :: Float) gen
in (applyTransformation point random, newGen)
\end{lstlisting}
\subsubsection{Рекурсивная генерация папоротника Барнсли}
Функция \texttt{barnsleyFern}, код которой представлен в листинге~\ref{lst:barnsleyFern}, реализует рекурсивный алгоритм генерации списка точек, из которых состоит папоротник Барнсли. Функция принимает на вход начальную точку и число -- количество шагов рекурсии, а возвращает список точек папоротника Барнсли.
Функция \texttt{barnsleyFern}, код которой представлен в листинге~\ref{lst:barnsleyFern}, реализует рекурсивный алгоритм генерации списка точек, из которых состоит папоротник Барнсли. Функция принимает на вход текущее состояние генератора случайных чисел, начальную точку и число -- количество шагов рекурсии, а возвращает список точек папоротника Барнсли.
\begin{lstlisting}[caption={Код функции для построения папоротника Барнсли.}, label={lst:barnsleyFern}]
barnsleyFern :: Point -> Int -> IO [Point]
barnsleyFern _ 0 = return []
barnsleyFern startPoint n = do
x' <- genNextPoint startPoint
xs <- barnsleyFern x' (n - 1)
return (startPoint : xs)
\end{lstlisting}
barnsleyFern :: StdGen -> Point -> Int -> [Point]
barnsleyFern _ _ 0 = []
barnsleyFern gen startPoint n =
let (nextPoint, newGen) = genNextPoint gen startPoint
in startPoint : barnsleyFern newGen nextPoint (n - 1)
\end{lstlisting}
\subsection{Повторяющаяся дилемма заключённого}
\subsubsection{Равновесие Нэша}
Функция \texttt{nashEquilibriumStrategy}, код которой представлен в листинге~\ref{lst:nashEquilibriumStrategy}, реализует равновесие Нэша. Функция рекурсивная, она принимает на вход список действий оппонента, список уже сгенерированных действий и число -- счётчик количества ходов. Функция завершается, когда достигается максимум ходов, либо когда заканчиваются ходы оппонента. Также она использует вспомогательную функцию \texttt{indexOf}. Она принимает на вход некоторый список и элемент этого списка, а возвращает индекс первого совпавшего с указанным элемента. Она возвращает список ходов игрока в соответствии со стратегией.
Функция \texttt{nashEquilibriumStrategy}, код которой представлен в листинге~\ref{lst:nashEquilibriumStrategy}, реализует равновесие Нэша. Функция рекурсивная, она принимает на вход список действий оппонента, список уже сгенерированных действий и число -- счётчик количества ходов. Функция завершается, когда достигается максимум ходов, либо когда заканчиваются ходы оппонента. Она возвращает список ходов игрока в соответствии со стратегией.
\begin{lstlisting}[caption={Код функций, резализующих стратегию в соответствии с равновесием Нэша.}, label={lst:nashEquilibriumStrategy}]
indexOf :: (Eq t) => [t] -> t -> Int
indexOf [] _ = -1
indexOf (x : xs) target
| x == target = 0
| otherwise = 1 + indexOf xs target
\begin{lstlisting}[caption={Код функции, резализующей стратегию в соответствии с равновесием Нэша.}, label={lst:nashEquilibriumStrategy}]
nashEquilibriumStrategy :: [Char] -> [Char] -> Int -> [Char]
nashEquilibriumStrategy opponentMoves generatedMoves n =
if n <= 100 && length opponentMoves > 0
@@ -363,13 +354,12 @@
nashEquilibriumStrategy (tail opponentMoves) (generatedMoves ++ [nextStep]) (n + 1)
else generatedMoves
where
cases = [[' П', ' С'], [' П', ' П']]
results = [[0, 10], [5, 5]]
result =
[ min (results !! 0 !! 1) (results !! 1 !! 1),
max (results !! 0 !! 0) (results !! 1 !! 0)
]
nextStep = cases !! indexOf results result !! 1
cases = [[' С', ' С'], [' С', ' П'], [' П', ' С'], [' П', ' П']]
results = [[1, 1], [10, 0], [0, 10], [5, 5]]
p_years = min (results !! 1 !! 1) (results !! 3 !! 1)
s_years = min (results !! 0 !! 1) (results !! 2 !! 1)
nextStep | p_years <= s_years = ' П'
| otherwise = ' С'
\end{lstlisting}
\subsubsection{Прощающая стратегия}
@@ -379,13 +369,13 @@
\begin{lstlisting}[caption={Код функции, реализующей прощающую стратегию.}, label={lst:forgivingStrategy}]
forgivingStrategy :: [Char] -> [Char] -> Int -> [Char]
forgivingStrategy opponentMoves generatedMoves n
| n > 100 || length opponentMoves == 1 = generatedMoves
| n == 0
= forgivingStrategy opponentMoves (generatedMoves ++ ['С']) (n + 1)
| head opponentMoves == 'С'
= forgivingStrategy (tail opponentMoves) (generatedMoves ++ ['С']) (n + 1)
| otherwise
= forgivingStrategy (tail opponentMoves) (generatedMoves ++ ['П']) (n + 1)
| n > 100 || length opponentMoves == 1 = generatedMoves
| n == 0
= forgivingStrategy opponentMoves (generatedMoves ++ ['С']) (n + 1)
| head opponentMoves == 'С'
= forgivingStrategy (tail opponentMoves) (generatedMoves ++ ['С']) (n + 1)
| otherwise
= forgivingStrategy (tail opponentMoves) (generatedMoves ++ ['П']) (n + 1)
\end{lstlisting}
\subsubsection{Подсчёт очков}
@@ -427,22 +417,21 @@
\label{fig:output1}
\end{figure}
На Рис.~\ref{fig:output2} представлена повторяющаяся дилемма заключённого для прощающей стретегии при заданных ходах игрока: \texttt{['С', 'П', 'С', 'С', 'П', 'П', 'С', 'С', 'П', 'П']}. А на Рис.~\ref{fig:output3} для равновесия по Нэшу.
\begin{figure}[h]
\centering
\includegraphics[width=0.7\linewidth]{img/output2.png}
\caption{Повторяющаяся дилемма заключённого при прощающей стратегии.}
\label{fig:output2}
\centering
\includegraphics[width=0.7\linewidth]{img/output2.png}
\caption{Повторяющаяся дилемма заключённого при прощающей стратегии.}
\label{fig:output2}
\end{figure}
\begin{figure}[h]
\centering
\includegraphics[width=0.7\linewidth]{img/output3.png}
\caption{Повторяющаяся дилемма заключённого для равновесия по Нэшу.}
\label{fig:output3}
\end{figure}
\end{figure}
На Рис.~\ref{fig:output2} представлена повторяющаяся дилемма заключённого для прощающей стретегии при заданных ходах игрока: \texttt{['С', 'П', 'С', 'С', 'П', 'П', 'С', 'С', 'П', 'П']}. А на Рис.~\ref{fig:output3} для равновесия по Нэшу.
\newpage
\section*{Заключение}

3
lab3/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
.stack-work/
*~
!*.txt

11
lab3/CHANGELOG.md Normal file
View File

@@ -0,0 +1,11 @@
# Changelog for `lab3`
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to the
[Haskell Package Versioning Policy](https://pvp.haskell.org/).
## Unreleased
## 0.1.0.0 - YYYY-MM-DD

26
lab3/LICENSE Normal file
View File

@@ -0,0 +1,26 @@
Copyright 2024 Author name here
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

1
lab3/README.md Normal file
View File

@@ -0,0 +1 @@
# lab3

2
lab3/Setup.hs Normal file
View File

@@ -0,0 +1,2 @@
import Distribution.Simple
main = defaultMain

83
lab3/app/Main.hs Normal file
View File

@@ -0,0 +1,83 @@
module Main (main) where
import Text.Read (readMaybe)
import Codec.Picture
import qualified Data.Vector.Unboxed as VU
import Lib
main :: IO ()
main = do
putStrLn "Введите значение сдвига для шифра Цезаря:"
inputCaesarShift <- getLine
let caesarShift = case readMaybe inputCaesarShift of
Just value -> value
Nothing -> error "Ожидалось целое число!"
putStrLn "Введите количество бит для кодирования:"
inputBitsPerByte <- getLine
let bitsPerByte = case readMaybe inputBitsPerByte of
Just value -> value
Nothing -> error "Ожидалось целое число!"
let sourceTextPath = "resources/biography.txt"
let sourceImagePath = "resources/david.bmp"
let alphabetPath = "tmp/alphabet.txt"
let encryptedTextPath = "tmp/biography_encrypted.txt"
let encodedImagePath = "tmp/david_" ++ show bitsPerByte ++ "_" ++ show caesarShift ++ ".bmp"
let decodedTextPath = "tmp/biography.txt"
putStrLn $ "\nЧтение текста из файла \"" ++ sourceTextPath ++ "\""
inputText <- readFile sourceTextPath
putStrLn $ "10 символов текста: \"" ++ take 10 inputText ++ "\""
putStrLn "\nШифрование текста"
let alphabet = createAlphabetFromText inputText
putStrLn $ "Размер алфавита: " ++ show (length alphabet)
writeFile alphabetPath alphabet
putStrLn $ "Алфавит сохранён в файл \"" ++ alphabetPath ++ "\""
let encryptedText = encryptCaesar alphabet caesarShift inputText
putStrLn $ "10 символов шифра: \"" ++ take 10 encryptedText ++ "\""
writeFile encryptedTextPath encryptedText
putStrLn $ "Зашифрованный текст сохранён в файл \"" ++ encryptedTextPath ++ "\""
let encryptedTextBits = textToBits encryptedText
putStrLn $ "10 битов шифра: \"" ++ show (take 10 $ VU.toList encryptedTextBits) ++ "\""
putStrLn "\nКодирование текста в изображение"
readSourceImageResult <- readImage sourceImagePath
case readSourceImageResult of
Left err -> putStrLn $ "Ошибка при чтении изображения: " ++ err
Right dynImg -> do
let img = convertRGB8 dynImg
let width = imageWidth img
let height = imageHeight img
let totalBits = width * height * 3 * bitsPerByte
let bits = encryptedTextBits VU.++ VU.replicate (totalBits - VU.length encryptedTextBits) 0
let resultImage = generateImage (encodePixel bitsPerByte img bits) width height
saveBmpImage encodedImagePath (ImageRGB8 resultImage)
putStrLn $ "Изображение сохранено по пути: \"" ++ encodedImagePath ++ "\""
putStrLn $ "\nЧтение алфавита из файла \"" ++ alphabetPath ++ "\""
alphabetFromFile <- readFile alphabetPath
putStrLn "\nДекодирование текста из изображения"
case extractShift encodedImagePath of
Just extractedCaesarShift -> do
putStrLn $ "Из названия файла извлечён ключ: " ++ show extractedCaesarShift
readEncodedImageResult <- readImage encodedImagePath
case readEncodedImageResult of
Left err -> putStrLn $ "Ошибка при чтении изображения: " ++ err
Right dynImg -> do
let img = convertRGB8 dynImg
let bits = VU.fromList $ extractBitsFromImage bitsPerByte img
putStrLn $ "10 битов шифра: \"" ++ show (take 10 $ VU.toList bits) ++ "\""
let encryptedTextFromImage = takeWhile (/= '\NUL') (bitsToText bits)
putStrLn $ "10 символов шифра: \"" ++ take 10 encryptedTextFromImage ++ "\""
let decryptedText = decryptCaesar alphabetFromFile extractedCaesarShift encryptedTextFromImage
putStrLn $ "10 символов текста: \"" ++ take 10 decryptedText ++ "\""
writeFile decodedTextPath decryptedText
putStrLn $ "Текст сохранён по пути: \"" ++ decodedTextPath ++ "\""
Nothing -> putStrLn "Не удалось извлечь ключ."

73
lab3/lab3.cabal Normal file
View File

@@ -0,0 +1,73 @@
cabal-version: 2.2
-- This file has been generated from package.yaml by hpack version 0.37.0.
--
-- see: https://github.com/sol/hpack
name: lab3
version: 0.1.0.0
description: Please see the README on GitHub at <https://github.com/githubuser/lab3#readme>
homepage: https://github.com/githubuser/lab3#readme
bug-reports: https://github.com/githubuser/lab3/issues
author: Author name here
maintainer: example@example.com
copyright: 2024 Author name here
license: BSD-3-Clause
license-file: LICENSE
build-type: Simple
extra-source-files:
README.md
CHANGELOG.md
source-repository head
type: git
location: https://github.com/githubuser/lab3
library
exposed-modules:
Lib
other-modules:
Paths_lab3
autogen-modules:
Paths_lab3
hs-source-dirs:
src
ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints
build-depends:
JuicyPixels
, base >=4.7 && <5
, vector
default-language: Haskell2010
executable lab3-exe
main-is: Main.hs
other-modules:
Paths_lab3
autogen-modules:
Paths_lab3
hs-source-dirs:
app
ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -threaded -rtsopts -with-rtsopts=-N
build-depends:
JuicyPixels
, base >=4.7 && <5
, lab3
, vector
default-language: Haskell2010
test-suite lab3-test
type: exitcode-stdio-1.0
main-is: Spec.hs
other-modules:
Paths_lab3
autogen-modules:
Paths_lab3
hs-source-dirs:
test
ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -threaded -rtsopts -with-rtsopts=-N
build-depends:
JuicyPixels
, base >=4.7 && <5
, lab3
, vector
default-language: Haskell2010

61
lab3/package.yaml Normal file
View File

@@ -0,0 +1,61 @@
name: lab3
version: 0.1.0.0
github: "githubuser/lab3"
license: BSD-3-Clause
author: "Author name here"
maintainer: "example@example.com"
copyright: "2024 Author name here"
extra-source-files:
- README.md
- CHANGELOG.md
# Metadata used when publishing your package
# synopsis: Short description of your package
# category: Web
# To avoid duplicated efforts in documentation and dealing with the
# complications of embedding Haddock markup inside cabal files, it is
# common to point users to the README.md file.
description: Please see the README on GitHub at <https://github.com/githubuser/lab3#readme>
dependencies:
- base >= 4.7 && < 5
- JuicyPixels
- vector
ghc-options:
- -Wall
- -Wcompat
- -Widentities
- -Wincomplete-record-updates
- -Wincomplete-uni-patterns
- -Wmissing-export-lists
- -Wmissing-home-modules
- -Wpartial-fields
- -Wredundant-constraints
library:
source-dirs: src
executables:
lab3-exe:
main: Main.hs
source-dirs: app
ghc-options:
- -threaded
- -rtsopts
- -with-rtsopts=-N
dependencies:
- lab3
tests:
lab3-test:
main: Spec.hs
source-dirs: test
ghc-options:
- -threaded
- -rtsopts
- -with-rtsopts=-N
dependencies:
- lab3

5
lab3/report/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
**/*
!.gitignore
!report.tex
!img
!img/*

BIN
lab3/report/img/david.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

BIN
lab3/report/img/results.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

463
lab3/report/report.tex Normal file
View File

@@ -0,0 +1,463 @@
\documentclass[a4paper, final]{article}
%\usepackage{literat} % Нормальные шрифты
\usepackage[14pt]{extsizes} % для того чтобы задать нестандартный 14-ый размер шрифта
\usepackage{tabularx}
\usepackage[T2A]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage[russian]{babel}
\usepackage{amsmath}
\usepackage[left=25mm, top=20mm, right=20mm, bottom=20mm, footskip=10mm]{geometry}
\usepackage{ragged2e} %для растягивания по ширине
\usepackage{setspace} %для межстрочного интервала
\usepackage{moreverb} %для работы с листингами
\usepackage{indentfirst} % для абзацного отступа
\usepackage{moreverb} %для печати в листинге исходного кода программ
\usepackage{pdfpages} %для вставки других pdf файлов
\usepackage{tikz}
\usepackage{graphicx}
\usepackage{afterpage}
\usepackage{longtable}
\usepackage{float}
% \usepackage[paper=A4,DIV=12]{typearea}
\usepackage{pdflscape}
% \usepackage{lscape}
\usepackage{array}
\usepackage{multirow}
\renewcommand\verbatimtabsize{4\relax}
\renewcommand\listingoffset{0.2em} %отступ от номеров строк в листинге
\renewcommand{\arraystretch}{1.4} % изменяю высоту строки в таблице
\usepackage[font=small, singlelinecheck=false, justification=centering, format=plain, labelsep=period]{caption} %для настройки заголовка таблицы
\usepackage{listings} %листинги
\usepackage{xcolor} % цвета
\usepackage{hyperref}% для гиперссылок
\usepackage{enumitem} %для перечислений
% Настраиваем листинги, чтобы они использовали счётчик figure
% \AtBeginDocument{
% \renewcommand{\thelstlisting}{\thefigure} % Листинги используют тот же счетчик, что и рисунки
% \renewcommand{\lstlistingname}{Рис.} % Меняем подпись на "Рисунок"
% }
% Автоматически увеличиваем счетчик figure перед каждым листингом
% \let\oldlstlisting\lstlisting
% \renewcommand{\lstlisting}[1][]{%
% \refstepcounter{figure}% Увеличиваем счетчик figure
% \oldlstlisting[#1]% Вызываем оригинальную команду lstlisting
% }
\newcommand{\specialcell}[2][l]{\begin{tabular}[#1]{@{}l@{}}#2\end{tabular}}
\setlist[enumerate,itemize]{leftmargin=1.2cm} %отступ в перечислениях
\hypersetup{colorlinks,
allcolors=[RGB]{010 090 200}} %красивые гиперссылки (не красные)
% подгружаемые языки — подробнее в документации listings (это всё для листингов)
\lstloadlanguages{ Haskell}
% включаем кириллицу и добавляем кое−какие опции
\lstset{tabsize=2,
breaklines,
basicstyle=\footnotesize,
columns=fullflexible,
flexiblecolumns,
numbers=left,
numberstyle={\footnotesize},
keywordstyle=\color{blue},
inputencoding=cp1251,
extendedchars=true
}
\lstdefinelanguage{MyC}{
language=Haskell,
% ndkeywordstyle=\color{darkgray}\bfseries,
% identifierstyle=\color{black},
% morecomment=[n]{/**}{*/},
% commentstyle=\color{blue}\ttfamily,
% stringstyle=\color{red}\ttfamily,
% morestring=[b]",
% showstringspaces=false,
% morecomment=[l][\color{gray}]{//},
keepspaces=true,
escapechar=\%,
texcl=true
}
\textheight=24cm % высота текста
\textwidth=16cm % ширина текста
\oddsidemargin=0pt % отступ от левого края
\topmargin=-1.5cm % отступ от верхнего края
\parindent=24pt % абзацный отступ
\parskip=5pt % интервал между абзацами
\tolerance=2000 % терпимость к "жидким" строкам
\flushbottom % выравнивание высоты страниц
% Настройка листингов
\lstset{
language=Haskell,
extendedchars=\true,
inputencoding=utf8,
keepspaces=true,
captionpos=t,
}
\begin{document} % начало документа
% НАЧАЛО ТИТУЛЬНОГО ЛИСТА
\begin{center}
\hfill \break
\hfill \break
\normalsize{МИНИСТЕРСТВО НАУКИ И ВЫСШЕГО ОБРАЗОВАНИЯ РОССИЙСКОЙ ФЕДЕРАЦИИ\\
федеральное государственное автономное образовательное учреждение высшего образования «Санкт-Петербургский политехнический университет Петра Великого»\\[10pt]}
\normalsize{Институт компьютерных наук и кибербезопасности}\\[10pt]
\normalsize{Высшая школа технологий искусственного интеллекта}\\[10pt]
\normalsize{Направление: 02.03.01 <<Математика и компьютерные науки>>}\\
\hfill \break
\hfill \break
\hfill \break
\hfill \break
\large{Отчет по лабораторной работе №3}\\
\large{по дисциплине}\\
\large{<<Функциональное программирование>>}\\
\large{Вариант 20}\\
\hfill \break
% \hfill \break
% \hfill \break
\end{center}
\small{
\begin{tabular}{lrrl}
\!\!\!Студент, & \hspace{2cm} & & \\
\!\!\!группы 5130201/20102 & \hspace{2cm} & \underline{\hspace{3cm}} &Тищенко А. А. \\\\
\!\!\!Преподаватель,\\ \hspace{-5pt}к. т. н., доц. & \hspace{2cm} & \underline{\hspace{3cm}} & Моторин Д. Е. \\\\
&&\hspace{4cm}
\end{tabular}
\begin{flushright}
<<\underline{\hspace{1cm}}>>\underline{\hspace{2.5cm}} 2024г.
\end{flushright}
}
\hfill \break
% \hfill \break
\begin{center} \small{Санкт-Петербург, 2024} \end{center}
\thispagestyle{empty} % выключаем отображение номера для этой страницы
% КОНЕЦ ТИТУЛЬНОГО ЛИСТА
\newpage
\tableofcontents
% \newpage
% \section*{Введение}
% \addcontentsline{toc}{section}{Введение}
\newpage
\section {Постановка задачи}
Для выполнения лабораторной работы необходимо было сделать следующее. Создать проект в stack. Все чистые функции записать в библиотеку Lib.hs и ограничить доступ к вспомогательным функциям. Использовать do-нотацию для работы с внешними файлами. Найти портрет Дэвида Дойча. Перевести изображение в формат .bmp (24-разрядный). Сохранить в файл формата .txt фрагмент биографии (не менее 1000 символов без пробелов, текст не должен обрываться на середине слова или предложения). Закодировать текст в изображение шифром Цезаря (смещение задается пользователем). Ключ к шифру записывается в имя файла. Написать функцию расшифровывающую текст из изображения используя ключ из имени файла и сохраняющую результат в отдельный текстовый файл. Создать функции шифрующие текст в последний бит каждого байта, последние два бита
каждого байта, …, все биты в байте. В отчете привести примеры искажений изображения.
\newpage
\section {Математическое описание}
\subsection{Шифр Цезаря}
Шифр Цезаря (лат. Notae Caesarianae), также известный как шифр сдвига или код Цезаря — разновидность шифра подстановки, в котором каждый символ в открытом тексте заменяется символом, находящимся на некотором постоянном числе позиций левее или правее него в алфавите (так, в шифре со сдвигом вправо на 3, А была бы заменена на Г, Б станет Д, и так далее). Шифр был назван в честь римского полководца Гая Юлия Цезаря, использовавшего его для секретной переписки со своими военачальниками.
Если сопоставить каждому символу алфавита его порядковый номер (нумеруя с 0), то шифрование и дешифрование можно выразить формулами модульной арифметики~\cite{caesar}:
\[
y = (x + k) \mod n
\]
\[
x = (y - k) \mod n
\]
где: \\
$x$ — символ открытого текста, \\
$y$ — символ шифрованного текста, \\
$n$ — мощность алфавита, \\
$k$ — ключ.
\newpage
\section {Особенности реализации}
\subsection{Исходное изображение и текст}
Для выполнения лабораторной работы необходимо было найти изображение Дэвида Дойча (см. Рис.~\ref{fig:david}). Изображение было переведено из формата \texttt{jpeg} в формат \texttt{bmp} с помощью сайта~\cite{convertio}.
\begin{figure}[h]
\centering
\includegraphics[width=0.5\linewidth]{img/david.jpg}
\caption{Изображение Дэвида Дойча, размещённое на его личном сайте~\cite{david}.}
\label{fig:david}
\end{figure}
Отрывок биографии Дэвида Дойча длиною в 1157 символов без учёта пробелов представлен ниже.
\texttt{
David Elieser Deutsch FRS (DOYTCH; born 18 May 1953) is a British physicist at the University of Oxford. He is a visiting professor in the Department of Atomic and Laser Physics at the Centre for Quantum Computation (CQC) in the Clarendon Laboratory of the University of Oxford. He pioneered the field of quantum computation by formulating a description for a quantum Turing machine, as well as specifying an algorithm designed to run on a quantum computer. He is a proponent of the many-worlds interpretation of quantum mechanics.
Deutsch was born to a Jewish family in Haifa, Israel on 18 May 1953, the son of Oskar and Tikva Deutsch. In London, David attended Geneva House school in Cricklewood (his parents owned and ran the Alma restaurant on Cricklewood Broadway), followed by William Ellis School in Highgate before reading Natural Sciences at Clare College, Cambridge and taking Part III of the Mathematical Tripos. He went on to Wolfson College, Oxford for his doctorate in theoretical physics, about quantum field theory in curved space-time, supervised by Dennis Sciama and Philip Candelas.
His work on quantum algorithms began with a 1985 paper, later expanded in 1992 along with Richard Jozsa, to produce the DeutschJozsa algorithm, one of the first examples of a quantum algorithm that is exponentially faster than any possible deterministic classical algorithm.
}
\subsection{Кодирование и декодирование текста с помощью шифра Цезаря}
Код функций для кодирования и декодирования текста с помощью шифра Цезаря представлен в листинге~\ref{lst:encrypt-caesar}. Функция \texttt{encryptCaesar} принимает алфавит в виде списка символов, смещение и сам текст, а возвращает зашифрованный текст. В её коде используется вспомогательная функция \texttt{indexOf}. Функция принимает список и элемент списка, а возвращает индекс этого элемента. Для создания алфавита используется функция \texttt{createAlphabetFromText}. Она принимает текст, а возвращает алфавит, который в нём используется, в виде списка символов. Для декодирования текста используется функция \texttt{decryptCaesar}, которая, по-сути, является лишь обёрткой над функцией \texttt{encryptCaesar}, так как процесс кодирования осуществляется почти так же как и декодирования. Функция \texttt{decryptCaesar} принимает на вход алфавит, смещение и закодированный текст, а возвращает декодированный текст. Алфавит сохраняется в отдельный файл и должен передаваться вместе с зашифрованным текстом, чтобы этот текст можно было дешифровать.
\begin{lstlisting}[caption={Функции для кодирования и декодирования текста с помощью шифра Цезаря.}, label={lst:encrypt-caesar}]
encryptCaesar :: [Char] -> Int -> String -> String
encryptCaesar alphabet shift text = map caesarChar text
where
caesarChar c = alphabet !! ((indexOf alphabet c + shift) `mod` length alphabet)
indexOf :: (Eq t) => [t] -> t -> Int
indexOf [] _ = -1
indexOf (x : xs) target
| x == target = 0
| otherwise = 1 + indexOf xs target
createAlphabetFromText :: String -> [Char]
createAlphabetFromText [] = []
createAlphabetFromText (x:xs)
| x `elem` alphabet = alphabet
| otherwise = x : alphabet
where
alphabet = createAlphabetFromText xs
decryptCaesar :: [Char] -> Int -> String -> String
decryptCaesar alphabet shift =
encryptCaesar alphabet (alphabetLength - (shift `mod` alphabetLength))
where
alphabetLength = length alphabet
\end{lstlisting}
Пример закодированного с помощью шифра Цезаря текста биографии Дэвида Дойча для смещения 5 представлен ниже.
\noindent
\texttt{
uiHF rWtFaga.ruabYgo;r3qkrNuCLS59Grcm.lrDwrvinrDJRBTrFgrirM.FYFg;rs;ngFo\\
FgYriYrY;arAlFHa.gFYnrmerCdem. Ur9arFgrirHFgFYFlhrs.meaggm.rFlrY;aruasi.\\
YQalYrmerIYmQForil r)iga.r8;ngFogriYrY;ar5alY.arem.r(bilYbQr5mQsbYiYFmlr\\
N5(5TrFlrY;ar5ti.al mlr)icm.iYm.nrmerY;arAlFHa.gFYnrmerCdem. Ur9arsFmlaa\\
.a rY;areFat rmerpbilYbQromQsbYiYFmlrcnrem.QbtiYFlhrir ago.FsYFmlrem.rir\\
pbilYbQrSb.FlhrQio;Flayrigr,attrigrgsaoFenFlhrilrithm.FY;Qr agFhla rYmr.\\
blrmlrirpbilYbQromQsbYa.Ur9arFgrirs.msmlalYrmerY;arQiln,m.t grFlYa.s.aYi\\
YFmlrmerpbilYbQrQao;ilFogU1uabYgo;r,igrcm.lrYmrirxa,Fg;reiQFtnrFlr9iFeiy\\
r-g.iatrmlrDwrvinrDJRByrY;argmlrmerCg2i.ril rSF2HiruabYgo;Ur-lr)ml mlyru\\
iHF riYYal a rEalaHir9mbgargo;mmtrFlr5.Fo2ta,mm rN;Fgrsi.alYgrm,la ril r\\
.ilrY;arItQir.agYib.ilYrmlr5.Fo2ta,mm rM.mi ,inTyremttm,a rcnrPFttFiQrWt\\
tFgrko;mmtrFlr9Fh;hiYarcaem.ar.ai FlhrOiYb.itrkoFaloagriYr5ti.ar5mttahay\\
r5iQc.F haril rYi2Flhr8i.Yr---rmerY;arviY;aQiYFoitrS.FsmgUr9ar,alYrmlrYm\\
rPmtegmlr5mttahayrCdem. rem.r;Fgr moYm.iYarFlrY;am.aYFoitrs;ngFogyricmbY\\
rpbilYbQreFat rY;am.nrFlrob.Ha rgsioaYFQayrgbsa.HFga rcnruallFgrkoFiQiri\\
l r8;FtFsr5il atigU19Fgr,m.2rmlrpbilYbQrithm.FY;Qgrcahilr,FY;rirDJwRrsis\\
a.yrtiYa.radsil a rFlrDJJzritmlhr,FY;rqFo;i. rxmfgiyrYmrs.m boarY;aruabY\\
go;xmfgirithm.FY;QyrmlarmerY;areF.gYradiQstagrmerirpbilYbQrithm.FY;QrY;i\\
YrFgradsmlalYFittnreigYa.rY;ilrilnrsmggFctar aYa.QFlFgYForotiggFoitrithm\\
.FY;QU}
\subsection{Представление текста в виде последовательности бит}
Код функций для преобразования текста в последовательность бит и обратно представлен в листинге~\ref{lst:bit}. Функция \texttt{textToBits} принимает текст в виде строки и возвращает его представление в виде вектора бит. Она использует вспомогательную функцию \texttt{charToBits}, которая преобразует символ в список бит, представляющих его код ASCII в двоичном виде. Для преобразования последовательности бит обратно в текст используется функция \texttt{bitsToText}. Она рекурсивно делит вектор бит на блоки по 8 бит, преобразует каждый блок в символ ASCII и объединяет их в строку. В процессе этого преобразования используется функция \texttt{bitsToInt}, которая преобразует вектор бит в целое число, интерпретируя их как двоичное представление этого числа.
\begin{lstlisting}[caption={Функции для конвертации текста в последовательность бит и обратно.}, label={lst:bit}]
textToBits :: String -> VU.Vector Int
textToBits text = VU.fromList $ concatMap charToBits text
charToBits :: Char -> [Int]
charToBits c = [if testBit (ord c) i then 1 else 0 | i <- [7,6..0]]
bitsToText :: VU.Vector Int -> String
bitsToText bits
| VU.null bits = []
| otherwise = (chr $ bitsToInt (VU.take 8 bits)) : bitsToText (VU.drop 8 bits)
bitsToInt :: VU.Vector Int -> Int
bitsToInt bits =
sum [bit * (2 ^ index) | (bit, index) <- zip (VU.toList bits) [len,(len - 1)..0]]
where
len = VU.length bits - 1
\end{lstlisting}
\subsection{Работа с файлами}
Для работы с текстовыми файлами использовались базовые функции Haskell -- \texttt{readFile} (читает содержимое файла и возвращает его как строку) и \texttt{writeFile} (записывает строку в файл, заменяя его содержимое).
Для работы с изображениями использовалась библиотека \texttt{JuicyPixels}~\cite{JuicyPixels}. С её помощью можно как прочитать изображение в любом популярном формате, так и сохранить его. В частности в работе использовались функции: \texttt{readImage} -- для чтения изображения из указанного файла, \texttt{saveBmpImage} -- для сохранения изображения в формате bmp.
\subsection{Сохранение зашифрованных данных в изображении}
Код функций для создания изображения с закодированными данными представлен в листинге~\ref{lst:genImg}. Функция \texttt{encodePixel} отвечает за кодирование последовательности бит в определённый пиксель изображения. Она принимает количество бит данных, которое будет сохранено в каждый байт изображения, исходное изображение, вектор бит зашифрованных данных, координаты пикселя (\(x, y\)) и возвращает новый пиксель с закодированными данными. Для этого функция вычисляет индекс пикселя в изображении, извлекает соответствующую часть вектора бит данных, преобразует её в целые числа, накладывает битовую маску, которая соответствует количество изменяемых бит в байте, и записывает закодированные данные. Для создания маски используется вспомогательная функция \texttt{createMask}.
Функция \texttt{encodePixel} затем используется вместе с функцией \texttt{generateImage} из библиотеки JuicyPixels для генерации нового изображения.
При сохранении изображения в файл, в его названии сохраняется смещение шифра Цезаря и количество бит в байте, отведённых для хранения зашифрованных данных. Например, название изображения \texttt{david\_2\_10.bmp} означает, что при кодировании использовался код Цезаря со смещением 10, а для хранения закодированных данных в каждом байте изображения использовалось 2 бита.
\begin{lstlisting}[caption={Функции для создания изображения с закодированными данными.}, label={lst:genImg}]
createMask :: Int -> Int
createMask shift = shiftL (complement 0) shift .&. complement 0
encodePixel :: Int -> Image PixelRGB8 -> VU.Vector Int -> Int -> Int -> PixelRGB8
encodePixel bitsPerByte img bits x y = PixelRGB8 newR newG newB
where
width = imageWidth img
index = x + y * width
startPos = index * 3 * bitsPerByte
pixelBits = VU.slice startPos (3 * bitsPerByte) bits
bitsIntR = bitsToInt $ VU.slice 0 bitsPerByte pixelBits
bitsIntG = bitsToInt $ VU.slice bitsPerByte bitsPerByte pixelBits
bitsIntB = bitsToInt $ VU.slice (2 * bitsPerByte) bitsPerByte pixelBits
mask = createMask bitsPerByte
PixelRGB8 r g b = pixelAt img x y
newR = intToWord8 $ ((word8ToInt r) .&. mask) .|. bitsIntR
newG = intToWord8 $ ((word8ToInt g) .&. mask) .|. bitsIntG
newB = intToWord8 $ ((word8ToInt b) .&. mask) .|. bitsIntB
\end{lstlisting}
\subsection{Чтение зашифрованных данных из изображения}
Код функций для чтения зашифрованных данных из изображения представлен в листинге~\ref{lst:readImg}. Функция \texttt{extractBits} извлекает заданное количество бит из одного байта пикселя. Она принимает число бит на байт и байт пикселя, возвращая список бит. Функция \texttt{extractBitsFromPixel} предназначена для извлечения бит из всех трёх цветовых каналов (\(R, G, B\)) пикселя. Она объединяет списки бит из каждого канала в один общий список. Для извлечения бит из всего изображения используется функция \texttt{extractBitsFromImage}. Она последовательно обрабатывает все пиксели изображения, извлекая биты с помощью \texttt{extractBitsFromPixel}, и объединяет их в общий список.
Функция \texttt{extractShift} извлекает смещения для шифра Цезаря из названия файла изображения.
\begin{lstlisting}[caption={Функции для чтения зашифрованных данных из изображения.}, label={lst:readImg}]
extractBits :: Int -> Pixel8 -> [Int]
extractBits bitsPerByte pixelByte =
[ if testBit pixelByte i then 1 else 0 | i <- [bitsPerByte-1, bitsPerByte-2..0] ]
extractBitsFromPixel :: Int -> PixelRGB8 -> [Int]
extractBitsFromPixel bitsPerByte (PixelRGB8 r g b) =
let bitsR = extractBits bitsPerByte r
bitsG = extractBits bitsPerByte g
bitsB = extractBits bitsPerByte b
in bitsR ++ bitsG ++ bitsB
extractBitsFromImage :: Int -> Image PixelRGB8 -> [Int]
extractBitsFromImage bitsPerByte img =
let width = imageWidth img
height = imageHeight img
pixels = [ pixelAt img x y | y <- [0..height - 1], x <- [0..width - 1] ]
in concatMap (extractBitsFromPixel bitsPerByte) pixels
extractShift :: String -> Maybe Int
extractShift path =
let shift = takeWhile (`elem` ['0'..'9']) (reverse $ takeWhile (/= '_') (reverse path))
in readMaybe shift
\end{lstlisting}
\newpage
\section {Результаты работы программы}
При успешном завершении программа создаёт четыре файла: файл изображения с закодированных текстом, текстовый файл с закодированным текстом, текстовый файл с алфавитом и текстовый файл с декодированным текстом.
\begin{figure}[h]
\centering
\includegraphics[width=0.65\linewidth]{img/results.png}
\caption{Результаты работы программы в консоли.}
\label{fig:results}
\end{figure}
На Рис.~\ref{fig:results} представлены результаты работы программы в консоли.
\begin{figure}[h!]
\centering
\includegraphics[width=0.33\linewidth]{img/david_1_20.jpg}
\caption{Изображение с зашифрованными данными (1 бит).}
\label{fig:david1}
\end{figure}
\begin{figure}[h!]
\centering
\includegraphics[width=0.33\linewidth]{img/david_2_20.jpg}
\caption{Изображение с зашифрованными данными (2 бит).}
\label{fig:david2}
\end{figure}
\begin{figure}[h!]
\centering
\includegraphics[width=0.33\linewidth]{img/david_3_20.jpg}
\caption{Изображение с зашифрованными данными (3 бит).}
\label{fig:david3}
\end{figure}
\begin{figure}[h!]
\centering
\includegraphics[width=0.33\linewidth]{img/david_4_20.jpg}
\caption{Изображение с зашифрованными данными (4 бит).}
\label{fig:david4}
\end{figure}
\begin{figure}[h!]
\centering
\includegraphics[width=0.33\linewidth]{img/david_5_20.jpg}
\caption{Изображение с зашифрованными данными (5 бит).}
\label{fig:david5}
\end{figure}
\begin{figure}[h!]
\centering
\includegraphics[width=0.33\linewidth]{img/david_6_20.jpg}
\caption{Изображение с зашифрованными данными (6 бит).}
\label{fig:david6}
\end{figure}
\begin{figure}[h!]
\centering
\includegraphics[width=0.33\linewidth]{img/david_7_20.jpg}
\caption{Изображение с зашифрованными данными (7 бит).}
\label{fig:david7}
\end{figure}
\begin{figure}[h!]
\centering
\includegraphics[width=0.33\linewidth]{img/david_8_20.jpg}
\caption{Изображение с зашифрованными данными (8 бит).}
\label{fig:david8}
\end{figure}
\newpage
\phantom{text}
\newpage
\phantom{text}
\newpage
На Рис.~\ref{fig:david1}-\ref{fig:david8} представлены результирующие изображения с разным количеством бит, отведённых под зашифрованные данные.
\newpage
\section*{Заключение}
\addcontentsline{toc}{section}{Заключение}
В результате выполнения лабораторной работы была создана программа на языке Haskell, которая способна кодировать текстовые данных из указанного файла с помощью шифра Цезаря и сохранять эти данные внутрь изображения. Причём программа позволяет выбрать как смещение для шифра Цезаря, так и количество бит, которое будет использовано в каждом байте изображения для хранения данных.
\newpage
\section*{Список литературы}
\addcontentsline{toc}{section}{Список литературы}
\vspace{-1.5cm}
\begin{thebibliography}{0}
\bibitem{caesar}
Luciano, D., Prichett, G., Cryptology: From Caesar Ciphers to Public-Key Cryptosystems, The College Mathematics Journal, 1987.
\bibitem{david}
David Deutsch -- personal website, URL: \url{https://www.daviddeutsch.org.uk/}, Дата обращения: 19.11.2024
\bibitem{convertio}
Convertio -- BPM to JPG online converter, URL: \url{https://convertio.co/ru/bmp-jpg/}, Дата обращения: 19.11.2024
\bibitem{JuicyPixels}
Hackage -- JuicyPixels: Picture loading/serialization, URL: \url{https://hackage.haskell.org/package/JuicyPixels}, Дата обращения: 19.11.2024
\end{thebibliography}
\end{document}

View File

@@ -0,0 +1,3 @@
David Elieser Deutsch FRS (DOYTCH; born 18 May 1953) is a British physicist at the University of Oxford. He is a visiting professor in the Department of Atomic and Laser Physics at the Centre for Quantum Computation (CQC) in the Clarendon Laboratory of the University of Oxford. He pioneered the field of quantum computation by formulating a description for a quantum Turing machine, as well as specifying an algorithm designed to run on a quantum computer. He is a proponent of the many-worlds interpretation of quantum mechanics.
Deutsch was born to a Jewish family in Haifa, Israel on 18 May 1953, the son of Oskar and Tikva Deutsch. In London, David attended Geneva House school in Cricklewood (his parents owned and ran the Alma restaurant on Cricklewood Broadway), followed by William Ellis School in Highgate before reading Natural Sciences at Clare College, Cambridge and taking Part III of the Mathematical Tripos. He went on to Wolfson College, Oxford for his doctorate in theoretical physics, about quantum field theory in curved space-time, supervised by Dennis Sciama and Philip Candelas.
His work on quantum algorithms began with a 1985 paper, later expanded in 1992 along with Richard Jozsa, to produce the DeutschJozsa algorithm, one of the first examples of a quantum algorithm that is exponentially faster than any possible deterministic classical algorithm.

BIN
lab3/resources/david.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

BIN
lab3/resources/david.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

117
lab3/src/Lib.hs Normal file
View File

@@ -0,0 +1,117 @@
module Lib
(
createAlphabetFromText,
encryptCaesar,
decryptCaesar,
textToBits,
bitsToText,
encodePixel,
extractShift,
extractBitsFromImage
)
where
import Codec.Picture
import Text.Read (readMaybe)
import Data.Word (Word8)
import Data.Char (ord, chr)
import Data.Bits (testBit, shiftL, complement, (.|.), (.&.))
import qualified Data.Vector.Unboxed as VU
createAlphabetFromText :: String -> [Char]
createAlphabetFromText [] = []
createAlphabetFromText (x:xs)
| x `elem` alphabet = alphabet
| otherwise = x : alphabet
where
alphabet = createAlphabetFromText xs
indexOf :: (Eq t) => [t] -> t -> Int
indexOf [] _ = -1
indexOf (x : xs) target
| x == target = 0
| otherwise = 1 + indexOf xs target
encryptCaesar :: [Char] -> Int -> String -> String
encryptCaesar alphabet shift text = map caesarChar text
where
caesarChar c = alphabet !! ((indexOf alphabet c + shift) `mod` length alphabet)
decryptCaesar :: [Char] -> Int -> String -> String
decryptCaesar alphabet shift =
encryptCaesar alphabet (alphabetLength - (shift `mod` alphabetLength))
where
alphabetLength = length alphabet
textToBits :: String -> VU.Vector Int
textToBits text = VU.fromList $ concatMap charToBits text
charToBits :: Char -> [Int]
charToBits c = [if testBit (ord c) i then 1 else 0 | i <- [7,6..0]]
-- intToBits :: Int -> [Int]
-- intToBits n = [if testBit n i then 1 else 0 | i <- [31,30..0]]
bitsToText :: VU.Vector Int -> String
bitsToText bits
| VU.null bits = []
| otherwise = (chr $ bitsToInt (VU.take 8 bits)) : bitsToText (VU.drop 8 bits)
bitsToInt :: VU.Vector Int -> Int
bitsToInt bits =
sum [bit * (2 ^ index) | (bit, index) <- zip (VU.toList bits) [len,(len - 1)..0]]
where
len = VU.length bits - 1
intToWord8 :: Int -> Word8
intToWord8 x = fromIntegral x
word8ToInt :: Word8 -> Int
word8ToInt x = fromIntegral x
createMask :: Int -> Int
createMask shift = shiftL (complement 0) shift .&. complement 0
encodePixel :: Int -> Image PixelRGB8 -> VU.Vector Int -> Int -> Int -> PixelRGB8
encodePixel bitsPerByte img bits x y = PixelRGB8 newR newG newB
where
width = imageWidth img
index = x + y * width
startPos = index * 3 * bitsPerByte
pixelBits = VU.slice startPos (3 * bitsPerByte) bits
bitsIntR = bitsToInt $ VU.slice 0 bitsPerByte pixelBits
bitsIntG = bitsToInt $ VU.slice bitsPerByte bitsPerByte pixelBits
bitsIntB = bitsToInt $ VU.slice (2 * bitsPerByte) bitsPerByte pixelBits
mask = createMask bitsPerByte
PixelRGB8 r g b = pixelAt img x y
newR = intToWord8 $ ((word8ToInt r) .&. mask) .|. bitsIntR
newG = intToWord8 $ ((word8ToInt g) .&. mask) .|. bitsIntG
newB = intToWord8 $ ((word8ToInt b) .&. mask) .|. bitsIntB
extractBits :: Int -> Pixel8 -> [Int]
extractBits bitsPerByte pixelByte =
[ if testBit pixelByte i then 1 else 0 | i <- [bitsPerByte-1, bitsPerByte-2..0] ]
extractBitsFromPixel :: Int -> PixelRGB8 -> [Int]
extractBitsFromPixel bitsPerByte (PixelRGB8 r g b) =
let bitsR = extractBits bitsPerByte r
bitsG = extractBits bitsPerByte g
bitsB = extractBits bitsPerByte b
in bitsR ++ bitsG ++ bitsB
extractBitsFromImage :: Int -> Image PixelRGB8 -> [Int]
extractBitsFromImage bitsPerByte img =
let width = imageWidth img
height = imageHeight img
pixels = [ pixelAt img x y | y <- [0..height - 1], x <- [0..width - 1] ]
in concatMap (extractBitsFromPixel bitsPerByte) pixels
extractShift :: String -> Maybe Int
extractShift path =
let shift = takeWhile (`elem` ['0'..'9']) (reverse $ takeWhile (/= '_') (reverse path))
in readMaybe shift

67
lab3/stack.yaml Normal file
View File

@@ -0,0 +1,67 @@
# This file was automatically generated by 'stack init'
#
# Some commonly used options have been documented as comments in this file.
# For advanced use and comprehensive documentation of the format, please see:
# https://docs.haskellstack.org/en/stable/yaml_configuration/
# A 'specific' Stackage snapshot or a compiler version.
# A snapshot resolver dictates the compiler version and the set of packages
# to be used for project dependencies. For example:
#
# snapshot: lts-22.28
# snapshot: nightly-2024-07-05
# snapshot: ghc-9.6.6
#
# The location of a snapshot can be provided as a file or url. Stack assumes
# a snapshot provided as a file might change, whereas a url resource does not.
#
# snapshot: ./custom-snapshot.yaml
# snapshot: https://example.com/snapshots/2024-01-01.yaml
snapshot:
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/22/41.yaml
# User packages to be built.
# Various formats can be used as shown in the example below.
#
# packages:
# - some-directory
# - https://example.com/foo/bar/baz-0.0.2.tar.gz
# subdirs:
# - auto-update
# - wai
packages:
- .
# Dependency packages to be pulled from upstream that are not in the snapshot.
# These entries can reference officially published versions as well as
# forks / in-progress versions pinned to a git hash. For example:
#
# extra-deps:
# - acme-missiles-0.3
# - git: https://github.com/commercialhaskell/stack.git
# commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a
#
# extra-deps: []
# Override default flag values for project packages and extra-deps
# flags: {}
# Extra package databases containing global packages
# extra-package-dbs: []
# Control whether we use the GHC we find on the path
# system-ghc: true
#
# Require a specific version of Stack, using version ranges
# require-stack-version: -any # Default
# require-stack-version: ">=3.1"
#
# Override the architecture used by Stack, especially useful on Windows
# arch: i386
# arch: x86_64
#
# Extra directories used by Stack for building
# extra-include-dirs: [/path/to/dir]
# extra-lib-dirs: [/path/to/dir]
#
# Allow a newer minor version of GHC than the snapshot specifies
# compiler-check: newer-minor

13
lab3/stack.yaml.lock Normal file
View File

@@ -0,0 +1,13 @@
# This file was autogenerated by Stack.
# You should not edit this file by hand.
# For more information, please see the documentation at:
# https://docs.haskellstack.org/en/stable/lock_files
packages: []
snapshots:
- completed:
sha256: 1e32b51d9082fdf6f3bd92accc9dfffd4ddaf406404427fb10bf76d2bc03cbbb
size: 720263
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/22/41.yaml
original:
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/22/41.yaml

BIN
lab3/task.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 KiB

25
lab3/task.txt Normal file
View File

@@ -0,0 +1,25 @@
Практическое задание 3. Обработка файлов в Haskell
№ 20
Создать проект в stack. Все чистые функции записать в библиотеку Lib.hs и ограничить
доступ к вспомогательным функциям. Использовать do-нотацию для работы с внешними
файлами. Найти портрет указанного человека:
Дойч, Дэвид
Перевести изображение в формат .bmp (24-разрядный), при необходимости изменить
ширину и высоту изображения без искажений. Сохранить в файл формата .txt фрагмент
биографии (не менее 1000 символов без пробелов, текст не должен обрываться на середине
слова или предложения). Закодировать текст в изображение методом:
Шифром Цезаря. Смещение задается пользователем
Ключ к шифру записывается в имя файла. Написать функцию расшифровывающую текст
из изображения используя ключ из имени файла и сохраняющую результат в отдельный
текстовый файл.
Создать функции шифрующие текст в последний бит каждого байта, последние два бита
каждого байта, …, все биты в байте. В отчете привести примеры искажений изображения.
Задача со звездочкой*: Разобрать сложный формат данных с использованием библиотек
доступных в Hackage. Зашифровать и расшифровать данные.
Задача с двумя звездочками**: Разобрать сложный формат данных самостоятельно
используя только стандартные библиотеки. Зашифровать и расшифровать данные.

2
lab3/test/Spec.hs Normal file
View File

@@ -0,0 +1,2 @@
main :: IO ()
main = putStrLn "Test suite not yet implemented"