diff --git a/roman-to-integer-python/LICENSE b/LICENSE similarity index 100% rename from roman-to-integer-python/LICENSE rename to LICENSE diff --git a/README.md b/README.md index 7d73c9a..18b4397 100644 --- a/README.md +++ b/README.md @@ -1,65 +1,178 @@ -# πŸ›οΈ Roman to Integer Converter (Python) +# Roman to Integer Converter in Python πŸ›οΈ -Convert **Roman numerals** to **integers** in Python using a clean and beginner-friendly approach. -This simple script demonstrates how to implement Roman numeral conversion logic using Python data structures and control flow. +![GitHub release](https://img.shields.io/github/release/Gabi111243/roman-to-integer-python.svg) ![Python](https://img.shields.io/badge/Python-3.8%2B-blue.svg) ![Open Source](https://img.shields.io/badge/Open%20Source-Yes-brightgreen.svg) ---- +Welcome to the **Roman to Integer Converter** repository! This project provides a comprehensive solution for converting between Roman numerals and integers using Python. Featuring multiple interfaces and validation, this tool is perfect for both educational purposes and practical use. -## πŸ“Œ Overview +## Table of Contents -Roman numerals are composed of the following seven symbols: +- [Introduction](#introduction) +- [Installation](#installation) +- [Usage](#usage) +- [Features](#features) +- [Algorithm](#algorithm) +- [Contributing](#contributing) +- [Repo Structure](#repo-structure) +- [License](#license) +- [Releases](#releases) -| Symbol | Value | -|--------|-------| -| I | 1 | -| V | 5 | -| X | 10 | -| L | 50 | -| C | 100 | -| D | 500 | -| M | 1000 | +## Introduction -### Special Cases: +Roman numerals have been used for centuries and are still relevant today. This project aims to bridge the gap between ancient numeral systems and modern programming. By converting between Roman numerals and integers, you can easily perform calculations and data manipulations. -- I before V or X (e.g. IV = 4, IX = 9) -- X before L or C (e.g. XL = 40, XC = 90) -- C before D or M (e.g. CD = 400, CM = 900) +## Installation ---- +To get started with this project, you need to have Python 3.8 or higher installed on your machine. You can download Python from the official website: [Python Downloads](https://www.python.org/downloads/). -## βœ… Features +Once you have Python installed, you can clone this repository using the following command: -- Converts valid Roman numerals (1 - 3999) into integers -- Handles all subtractive cases -- Clean O(n) algorithm -- Beginner-friendly code -- Can be integrated into larger Python projects +```bash +git clone https://github.com/Gabi111243/roman-to-integer-python.git +``` ---- +Navigate to the project directory: -## πŸš€ Usage +```bash +cd roman-to-integer-python ``` -# Example usage -if __name__ == "__main__": - converter = Solution() - print(converter.romanToInt("XI")) # Output: 11 + +You can also install the required packages using pip: + +```bash +pip install -r requirements.txt ``` -## πŸ“‚ Project Structure + +## Usage + +### Command Line Interface +Convert Roman numerals to integers: + +```bash +python main.py XIV ``` -roman-to-integer-python/ -β”œβ”€β”€ roman_converter.py # Main script -β”œβ”€β”€ test_cases.py # (Optional) Unit tests -β”œβ”€β”€ README.md # Project documentation -└── LICENSE # MIT or any license +Convert integers to Roman numerals: + +```bash +python main.py 14 --reverse +``` +Validate Roman numerals: + +```bash +python main.py XIV --validate +``` +Graphical User Interface +Launch the graphical interface: +```bash +python main.py --gui ``` -## πŸ“„ License -This project is licensed under the **MIT License**. -Feel free to use, modify, and distribute it for personal or commercial use. +OR +```bash +python gui.py +``` + +You can also test various Roman numerals by running the script multiple times with different inputs. + +## Features + +- **Beginner-Friendly**: The code is simple and easy to understand, making it suitable for beginners. +- **Dual Conversion**: Convert both Roman numerals to integers and integers to Roman numerals. +- **Input Validation**: Validate Roman numerals for correctness. +- **Educational**: Learn about Roman numerals and their conversion process. +- **Command-Line Tool**: Easily convert Roman numerals from the command line. +- **Open Source**: Contribute to the project and improve it further. +- **Data Structures**: Understand how to use dictionaries and lists effectively. + +## Algorithm + +The algorithm used in this project is straightforward. Here’s a brief overview: + +1. **Mapping**: Create a mapping of Roman numerals to their integer values. + +2. **Iteration**: Iterate through the Roman numeral string from right to left. -## πŸ‘¨β€πŸ’» Author -Muawiya Amir -+ πŸ”— GitHub: [Muawiya-contact](https://github.com/Muawiya-contact) -+ πŸ“Ί YouTube: [@Coding_Moves](https://www.youtube.com/@Coding_Moves) +3. **Comparison**: If the current numeral is less than the previous numeral, subtract its value. Otherwise, add its value. +4. **Return Result**: After processing the entire string, return the total value. + +For integer to Roman conversion: + +1. **Value Mapping**: Create lists of integer values and their corresponding Roman symbols. + +2. **Iterative Subtraction**: For each value, subtract it from the number as many times as possible, adding the corresponding symbol to the result. + +Here is a simple code snippet that illustrates the algorithm: + +```python +def roman_to_int(s: str) -> int: + roman_map = { + 'I': 1, + 'V': 5, + 'X': 10, + 'L': 50, + 'C': 100, + 'D': 500, + 'M': 1000 + } + + total = 0 + prev_value = 0 + + for char in reversed(s): + value = roman_map[char] + if value < prev_value: + total -= value + else: + total += value + prev_value = value + + return total +``` +## Repo Structure +```bash +roman-to-integer-python/ +β”‚ +β”œβ”€β”€ roman_converter/ +β”‚ β”œβ”€β”€ __init__.py +β”‚ β”œβ”€β”€ converter.py +β”‚ └── validator.py +β”‚ +β”œβ”€β”€ tests/ +β”‚ β”œβ”€β”€ __init__.py +β”‚ └── test_converter.py +β”‚ +β”œβ”€β”€ gui.py +β”œβ”€β”€ main.py +β”œβ”€β”€ requirements.txt +└── README.md +``` +**roman_converter/**: Package containing core conversion and validation functions + +**tests/**: Unit tests for all functionality + +**gui.py**: Graphical user interface using Tkinter + +**main.py**: Main entry point with multiple usage options + +## Contributing + +We welcome contributions! If you have ideas for improvements or new features, feel free to submit a pull request. Please ensure your code follows the existing style and includes tests. + +1. Fork the repository. +2. Create a new branch for your feature. +3. Make your changes and commit them. +4. Push to your branch and create a pull request. + +## License + +This project is licensed under the MIT License. Feel free to use, modify, and distribute it as you wish. + +## Releases + +You can find the latest releases of this project [here](https://github.com/Gabi111243/roman-to-integer-python/releases). Please download the necessary files and execute them as per the instructions provided. + +For further updates and information, check the "Releases" section in the repository. + +--- +Thank you for checking out the **Roman to Integer Converter**! We hope this project helps you understand both Roman numerals and Python programming better. Happy coding! \ No newline at end of file diff --git a/gui.py b/gui.py new file mode 100644 index 0000000..0128794 --- /dev/null +++ b/gui.py @@ -0,0 +1,97 @@ +""" +Graphical User Interface for Roman numeral converter +""" +import tkinter as tk +from tkinter import messagebox +from roman_converter import roman_to_int, int_to_roman + +class RomanConverterApp: + """ + GUI application for Roman numeral conversion + """ + def __init__(self, root): + self.root = root + self.root.title("Roman to Integer Converter") + self.root.geometry("400x400") + + self.create_widgets() + + def create_widgets(self): + # Input field for Roman numeral + self.label = tk.Label(self.root, text="Enter Roman Numeral:") + self.label.pack(pady=10) + + self.entry = tk.Entry(self.root, width=30) + self.entry.pack(pady=5) + + # Convert button + self.convert_btn = tk.Button(self.root, text="Convert to Integer", command=self.convert) + self.convert_btn.pack(pady=10) + + # Result display + self.result_label = tk.Label(self.root, text="Result: ") + self.result_label.pack(pady=10) + + # Separator + separator = tk.Frame(self.root, height=2, bd=1, relief=tk.SUNKEN) + separator.pack(fill=tk.X, padx=5, pady=10) + + # Reverse conversion section + self.reverse_label = tk.Label(self.root, text="Or enter Integer to convert to Roman:") + self.reverse_label.pack(pady=5) + + self.int_entry = tk.Entry(self.root, width=30) + self.int_entry.pack(pady=5) + + self.reverse_btn = tk.Button(self.root, text="Convert to Roman", command=self.reverse_convert) + self.reverse_btn.pack(pady=10) + + self.roman_result_label = tk.Label(self.root, text="Roman Result: ") + self.roman_result_label.pack(pady=10) + + # Clear button + self.clear_btn = tk.Button(self.root, text="Clear All", command=self.clear) + self.clear_btn.pack(pady=10) + + def convert(self): + # Convert Roman to integer + roman = self.entry.get().strip().upper() + if not roman: + messagebox.showerror("Error", "Please enter a Roman numeral") + return + + try: + result = roman_to_int(roman) + self.result_label.config(text=f"Result: {result}") + except ValueError as e: + messagebox.showerror("Error", str(e)) + + def reverse_convert(self): + # Convert integer to Roman + num_str = self.int_entry.get().strip() + if not num_str: + messagebox.showerror("Error", "Please enter an integer") + return + + try: + num = int(num_str) + result = int_to_roman(num) + self.roman_result_label.config(text=f"Roman Result: {result}") + except ValueError as e: + messagebox.showerror("Error", str(e)) + + def clear(self): + # Clear all fields + self.entry.delete(0, tk.END) + self.int_entry.delete(0, tk.END) + self.result_label.config(text="Result: ") + self.roman_result_label.config(text="Roman Result: ") + +def main(): + """Launch the GUI application""" + root = tk.Tk() + app = RomanConverterApp(root) + root.mainloop() + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..768e8a0 --- /dev/null +++ b/main.py @@ -0,0 +1,56 @@ +""" +Main entry point for Roman numeral converter +""" +import sys +from roman_converter import roman_to_int, int_to_roman +from gui import main as gui_main + +def print_usage(): + """Print usage instructions""" + print("Roman Numeral Converter") + print("Usage:") + print(" python main.py # Convert Roman to integer") + print(" python main.py --reverse # Convert integer to Roman") + print(" python main.py --gui # Launch graphical interface") + print("\nExamples:") + print(" python main.py XIV") + print(" python main.py 14 --reverse") + print(" python main.py --gui") + +def main(): + """Main function with simple command-line interface""" + if len(sys.argv) == 1 or '--help' in sys.argv or '-h' in sys.argv: + print_usage() + return + + if '--gui' in sys.argv: + gui_main() + return + + reverse = '--reverse' in sys.argv or '-r' in sys.argv + + # Extract the input value (first non-flag argument) + input_val = None + for arg in sys.argv[1:]: + if not arg.startswith('--'): + input_val = arg + break + + if not input_val: + print("Error: No input value provided") + print_usage() + return + + try: + if reverse: + num = int(input_val) + result = int_to_roman(num) + print(f"{num} = {result}") + else: + result = roman_to_int(input_val.upper()) + print(f"{input_val} = {result}") + except ValueError as e: + print(f"Error: {e}") + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..31c6dff --- /dev/null +++ b/requirements.txt @@ -0,0 +1,7 @@ +tkinter +unittest +argparse +os +sys + +# No external packages needed - all dependencies are in Python standard library \ No newline at end of file diff --git a/roman-to-integer-python/roman_converter.py b/roman-to-integer-python/roman_converter.py deleted file mode 100644 index 3777eda..0000000 --- a/roman-to-integer-python/roman_converter.py +++ /dev/null @@ -1,29 +0,0 @@ -class Solution(object): - def romanToInt(self, s): - """ - Convert a Roman numeral to an integer. - :type s: str - :rtype: int - """ - roman_map = { - 'I': 1, 'V': 5, 'X': 10, 'L': 50, - 'C': 100, 'D': 500, 'M': 1000 - } - - total = 0 - previous_value = 0 - - for char in reversed(s): - current_value = roman_map[char] - if current_value < previous_value: - total -= current_value - else: - total += current_value - previous_value = current_value - - return total - -# Example usage -if __name__ == "__main__": - converter = Solution() - print(converter.romanToInt("XI")) # Output: 11 diff --git a/roman-to-integer-python/test_cases.py b/roman-to-integer-python/test_cases.py deleted file mode 100644 index ce8dd13..0000000 --- a/roman-to-integer-python/test_cases.py +++ /dev/null @@ -1,17 +0,0 @@ -import unittest -from roman_converter import Solution - -class TestRomanToInt(unittest.TestCase): - def setUp(self): - self.converter = Solution() - - def test_cases(self): - self.assertEqual(self.converter.romanToInt("III"), 3) - self.assertEqual(self.converter.romanToInt("IX"), 9) - self.assertEqual(self.converter.romanToInt("LVIII"), 58) - self.assertEqual(self.converter.romanToInt("MCMXCIV"), 1994) - self.assertEqual(self.converter.romanToInt("XI"), 11) - self.assertEqual(self.converter.romanToInt("CDXLIV"), 444) - -if __name__ == '__main__': - unittest.main() diff --git a/roman_converter/__init__.py b/roman_converter/__init__.py new file mode 100644 index 0000000..f5d873f --- /dev/null +++ b/roman_converter/__init__.py @@ -0,0 +1,8 @@ +""" +Roman numeral converter package +""" +from .converter import roman_to_int, int_to_roman +from .validator import is_valid_roman + +__version__ = "1.0.0" +__all__ = ["roman_to_int", "int_to_roman", "is_valid_roman"] \ No newline at end of file diff --git a/roman_converter/converter.py b/roman_converter/converter.py new file mode 100644 index 0000000..ee5fe2c --- /dev/null +++ b/roman_converter/converter.py @@ -0,0 +1,60 @@ +""" +Core conversion functions for Roman numerals +""" + +def roman_to_int(s): + """ + Convert Roman numeral to integer + """ + roman_map = { + 'I': 1, 'V': 5, 'X': 10, 'L': 50, + 'C': 100, 'D': 500, 'M': 1000 + } + + from .validator import is_valid_roman + if not is_valid_roman(s): + raise ValueError("Invalid Roman numeral") + + total = 0 + prev_value = 0 + + # Process each character from right to left + for char in reversed(s): + value = roman_map[char] + if value < prev_value: + total -= value + else: + total += value + prev_value = value + + return total + +def int_to_roman(num): + """ + Convert integer to Roman numeral + """ + if not isinstance(num, int) or num <= 0 or num > 3999: + raise ValueError("Input must be an integer between 1 and 3999") + + val = [ + 1000, 900, 500, 400, + 100, 90, 50, 40, + 10, 9, 5, 4, + 1 + ] + syms = [ + "M", "CM", "D", "CD", + "C", "XC", "L", "XL", + "X", "IX", "V", "IV", + "I" + ] + + roman_num = '' + i = 0 + while num > 0: + # Add the largest possible symbol + for _ in range(num // val[i]): + roman_num += syms[i] + num -= val[i] + i += 1 + return roman_num \ No newline at end of file diff --git a/roman_converter/validator.py b/roman_converter/validator.py new file mode 100644 index 0000000..89fe050 --- /dev/null +++ b/roman_converter/validator.py @@ -0,0 +1,32 @@ +""" +Validation functions for Roman numerals +""" + +def is_valid_roman(s): + """ + Validate if input is a proper Roman numeral + """ + if not s or not isinstance(s, str): + return False + + # Check for valid characters only + valid_chars = {'I', 'V', 'X', 'L', 'C', 'D', 'M'} + if not all(char in valid_chars for char in s.upper()): + return False + + # Check for invalid patterns (basic validation) + s_upper = s.upper() + invalid_patterns = [ + "IIII", "XXXX", "CCCC", "MMMM", # More than 3 repeats + "VV", "LL", "DD", # Cannot repeat V, L, or D + "IL", "IC", "ID", "IM", # Invalid subtractive combinations + "VX", "VL", "VC", "VD", "VM", + "XD", "XM", + "LC", "LD", "LM" + ] + + for pattern in invalid_patterns: + if pattern in s_upper: + return False + + return True \ No newline at end of file diff --git a/tests/test_converter.py b/tests/test_converter.py new file mode 100644 index 0000000..05df9f8 --- /dev/null +++ b/tests/test_converter.py @@ -0,0 +1,59 @@ +""" +Unit tests for the Roman numeral converter +""" +import unittest +import sys +import os + +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) + +from roman_converter import roman_to_int, int_to_roman, is_valid_roman + +class TestRomanConverter(unittest.TestCase): + """ + Unit tests for the conversion functions + """ + + def test_roman_to_int_basic(self): + self.assertEqual(roman_to_int('III'), 3) + self.assertEqual(roman_to_int('IV'), 4) + self.assertEqual(roman_to_int('IX'), 9) + self.assertEqual(roman_to_int('LVIII'), 58) + self.assertEqual(roman_to_int('MCMXCIV'), 1994) + + def test_int_to_roman_basic(self): + self.assertEqual(int_to_roman(3), 'III') + self.assertEqual(int_to_roman(4), 'IV') + self.assertEqual(int_to_roman(9), 'IX') + self.assertEqual(int_to_roman(58), 'LVIII') + self.assertEqual(int_to_roman(1994), 'MCMXCIV') + + def test_invalid_input(self): + with self.assertRaises(ValueError): + roman_to_int('IIII') # Invalid Roman + with self.assertRaises(ValueError): + roman_to_int('ABC') # Invalid characters + with self.assertRaises(ValueError): + int_to_roman(0) # Number too small + with self.assertRaises(ValueError): + int_to_roman(4000) # Number too large + + def test_validation(self): + self.assertTrue(is_valid_roman('III')) + self.assertTrue(is_valid_roman('XIV')) + self.assertTrue(is_valid_roman('MCMXC')) + + self.assertFalse(is_valid_roman('IIII')) # Too many I's + self.assertFalse(is_valid_roman('VV')) # V cannot repeat + self.assertFalse(is_valid_roman('IC')) # Invalid subtraction + self.assertFalse(is_valid_roman('ABC')) # Invalid characters + + def test_conversion_consistency(self): + # Test that converting both ways gives original value + for i in range(1, 100): + roman = int_to_roman(i) + result = roman_to_int(roman) + self.assertEqual(i, result) + +if __name__ == '__main__': + unittest.main() \ No newline at end of file