mirror of
https://github.com/TheAlgorithms/Python.git
synced 2026-09-28 21:45:27 +08:00
* feat(ciphers): add scytale (skytale) transposition cipher with doctests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * chore(ciphers): satisfy ruff UP006/UP035 by using builtin generics * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * style(ciphers): fix import block formatting (isort I001) * feat(ciphers): add columnar transposition cipher with doctests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * refactor(ciphers): improve variable naming for clarity in columnar transposition cipher * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * updating DIRECTORY.md --------- Co-authored-by: Saiprashanth Pulisetti <itspulisetti@pm.me> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Christian Clauss <cclauss@me.com> Co-authored-by: cclauss <cclauss@users.noreply.github.com>
104 lines
2.9 KiB
Python
104 lines
2.9 KiB
Python
"""Scytale (Skytale) transposition cipher.
|
|
|
|
A classical transposition cipher used in ancient Greece. The sender wraps a
|
|
strip of parchment around a rod (scytale) and writes the message along the rod.
|
|
The recipient with a rod of the same diameter can read the message.
|
|
|
|
Reference: https://en.wikipedia.org/wiki/Scytale
|
|
|
|
Functions here keep characters as-is (including spaces). The key is a positive
|
|
integer representing the circumference count (number of rows).
|
|
|
|
>>> encrypt("WE ARE DISCOVERED FLEE AT ONCE", 3)
|
|
'WA SVEFETNERDCEDL C EIOR EAOE'
|
|
>>> decrypt('WA SVEFETNERDCEDL C EIOR EAOE', 3)
|
|
'WE ARE DISCOVERED FLEE AT ONCE'
|
|
|
|
Edge cases:
|
|
>>> encrypt("HELLO", 1)
|
|
'HELLO'
|
|
>>> decrypt("HELLO", 1)
|
|
'HELLO'
|
|
>>> encrypt("HELLO", 5) # key equals length
|
|
'HELLO'
|
|
>>> decrypt("HELLO", 5)
|
|
'HELLO'
|
|
>>> encrypt("HELLO", 0)
|
|
Traceback (most recent call last):
|
|
...
|
|
ValueError: Key must be a positive integer
|
|
>>> decrypt("HELLO", -2)
|
|
Traceback (most recent call last):
|
|
...
|
|
ValueError: Key must be a positive integer
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
def encrypt(plaintext: str, key: int) -> str:
|
|
"""Encrypt plaintext using Scytale transposition.
|
|
|
|
Write characters around a rod with `key` rows, then read off by rows.
|
|
|
|
:param plaintext: Input message to encrypt
|
|
:param key: Positive integer number of rows
|
|
:return: Ciphertext string
|
|
:raises ValueError: if key <= 0
|
|
"""
|
|
if key <= 0:
|
|
raise ValueError("Key must be a positive integer")
|
|
if key == 1 or len(plaintext) <= key:
|
|
return plaintext
|
|
|
|
# Read every key-th character starting from each row offset
|
|
return "".join(plaintext[row::key] for row in range(key))
|
|
|
|
|
|
def decrypt(ciphertext: str, key: int) -> str:
|
|
"""Decrypt Scytale ciphertext.
|
|
|
|
Reconstruct rows by their lengths and interleave by columns.
|
|
|
|
:param ciphertext: Encrypted string
|
|
:param key: Positive integer number of rows
|
|
:return: Decrypted plaintext
|
|
:raises ValueError: if key <= 0
|
|
"""
|
|
if key <= 0:
|
|
raise ValueError("Key must be a positive integer")
|
|
if key == 1 or len(ciphertext) <= key:
|
|
return ciphertext
|
|
|
|
length = len(ciphertext)
|
|
base = length // key
|
|
extra = length % key
|
|
|
|
# Determine each row length
|
|
row_lengths: list[int] = [base + (1 if r < extra else 0) for r in range(key)]
|
|
|
|
# Slice ciphertext into rows
|
|
rows: list[str] = []
|
|
idx = 0
|
|
for r_len in row_lengths:
|
|
rows.append(ciphertext[idx : idx + r_len])
|
|
idx += r_len
|
|
|
|
# Pointers to current index in each row
|
|
pointers = [0] * key
|
|
|
|
# Reconstruct by taking characters column-wise across rows
|
|
result_chars: list[str] = []
|
|
for i in range(length):
|
|
r = i % key
|
|
if pointers[r] < len(rows[r]):
|
|
result_chars.append(rows[r][pointers[r]])
|
|
pointers[r] += 1
|
|
return "".join(result_chars)
|
|
|
|
|
|
if __name__ == "__main__": # pragma: no cover
|
|
import doctest
|
|
|
|
doctest.testmod()
|