Last active
June 27, 2021 11:29
-
-
Save lord-alfred/6020e7a5f60f06652101613c490a73f9 to your computer and use it in GitHub Desktop.
Solve/calc math text captchas. C# класс для решения текстовых математических каптч.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Created by SharpDevelop. | |
* User: Lord_Alfred | |
* Date: 18.01.2019; Updated: 15.07.2019 | |
* Time: 11:01 | |
* | |
* To change this template use Tools | Options | Coding | Edit Standard Headers. | |
*/ | |
using System; | |
using System.Linq; | |
using System.Collections.Generic; | |
namespace ZPMathCaptchaSolver | |
{ | |
class Program | |
{ | |
public static void Main(string[] args) | |
{ | |
List<KeyValuePair<string, int>> tests = new List<KeyValuePair<string, int>>() { | |
// unknown left | |
new KeyValuePair<string, int>(" - 3 = 5", 8), | |
new KeyValuePair<string, int>(" + 1 = 6", 5), | |
new KeyValuePair<string, int>(" * 2 = 6", 3), | |
new KeyValuePair<string, int>(" / 5 = 2", 10), | |
// unknown right | |
new KeyValuePair<string, int>("8 - = 2", 6), | |
new KeyValuePair<string, int>("8 + = 12", 4), | |
new KeyValuePair<string, int>("3 * = 6", 2), | |
new KeyValuePair<string, int>("8 / = 2", 4), | |
// unknown result | |
new KeyValuePair<string, int>("5 - 3 = ", 2), | |
new KeyValuePair<string, int>("4 + 2 = ", 6), | |
new KeyValuePair<string, int>("2 * 4 = ", 8), | |
new KeyValuePair<string, int>("6 / 3 = ", 2), | |
new KeyValuePair<string, int>("1 ! 2 = ", 1), | |
new KeyValuePair<string, int>("6 ? 9 = ", 6), | |
new KeyValuePair<string, int>("4 > 1 = ", 4), | |
new KeyValuePair<string, int>("1 > 3 = ", 3), | |
new KeyValuePair<string, int>("3 < 5 = ", 3), | |
new KeyValuePair<string, int>("6 < 2 = ", 2), | |
// hard examples | |
new KeyValuePair<string, int>(" x 6 = twenty four", 4), | |
new KeyValuePair<string, int>(" x 6 = twenty-four", 4), | |
new KeyValuePair<string, int>(" x 6 = fifty four", 9), | |
new KeyValuePair<string, int>("sixty three—forty-two = ", 21), | |
new KeyValuePair<string, int>("× 6 = eighteen", 3), | |
new KeyValuePair<string, int>("sixty three—sixty two = ", 1), | |
new KeyValuePair<string, int>("forty three—forty-three = ", 0), | |
new KeyValuePair<string, int>("forty two+eighty-nine = ", 131), | |
new KeyValuePair<string, int>("forty two+eighty-nine = ", 131), | |
new KeyValuePair<string, int>("zero one*two = ", 2), | |
new KeyValuePair<string, int>("zero / fiftynine = ", 0), | |
new KeyValuePair<string, int>("one plus fifty = ", 51), | |
new KeyValuePair<string, int>("sixty-two minus sixty-one = ", 1), | |
new KeyValuePair<string, int>("six multiply seven = ", 42), | |
new KeyValuePair<string, int>("sixty divide twenty = ", 3), | |
// very hard examples | |
//new KeyValuePair<string, int>("sixty - twenty = ", 40), | |
}; | |
foreach(KeyValuePair<string, int> test in tests) { | |
MathCaptchaSolver s = new MathCaptchaSolver(); | |
int result = s.Solve(test.Key); | |
Console.WriteLine(test.Key + " | " + result); | |
if (result != test.Value) { | |
throw new Exception("Тест провалился"); | |
} | |
} | |
Console.ReadKey(true); | |
} | |
} | |
public class MathCaptchaSolver { | |
const int is_empty_operand = -1; | |
int operand_left = is_empty_operand; // ? + 2 = 3 | |
int operand_right = is_empty_operand; // 1 + ? = 3 | |
int operand_result = is_empty_operand; // 1 + 2 = ? | |
string operation = String.Empty; // 1 ? 2 = 3 | |
string[] valid_operations = {"+", "-", "*", "/", "!", "?", "<", ">"}; // ! - нечётно; ? - чётно; < - меньше; > - больше | |
public int Solve(string captcha_string) { | |
if (String.IsNullOrEmpty(captcha_string)) { | |
throw new Exception("Передана пустая captcha_string"); | |
} | |
captcha_string = Normalize(captcha_string); | |
Parse(captcha_string); | |
ValidateOperands(); | |
int solve_result = FindEmptyOperandAndSolve(); | |
return solve_result; | |
} | |
private int DoActionWithLeftEmpty(string operation_type, int num_right, int num_result) { | |
if (operation_type == "+") { | |
return num_result - num_right; // + 1 = 6 | |
} | |
if (operation_type == "-") { | |
return num_right + num_result; // - 3 = 5 | |
} | |
if (operation_type == "*") { | |
return num_result / num_right; | |
} | |
if (operation_type == "/") { | |
return num_right * num_result; // / 5 = 2 | |
} | |
throw new Exception("Такого не может произойти, потому что есть проверка раньше в коде, но компилятору нужно чтоб данный метод возвращал какое-то значение"); | |
} | |
private int DoActionWithRightEmpty(string operation_type, int num_left, int num_result) { | |
if (operation_type == "+") { | |
return num_result - num_left; | |
} | |
if (operation_type == "-") { | |
return num_left - num_result; // 8 - = 2 | |
} | |
if (operation_type == "*") { | |
return num_result / num_left; | |
} | |
if (operation_type == "/") { | |
return num_left / num_result; // 8 / = 2 | |
} | |
throw new Exception("Такого не может произойти, потому что есть проверка раньше в коде, но компилятору нужно чтоб данный метод возвращал какое-то значение"); | |
} | |
private int DoActionWithResultEmpty(string operation_type, int num1, int num2) { | |
if (operation_type == "+") { | |
return num1 + num2; | |
} | |
if (operation_type == "-") { | |
return num1 - num2; | |
} | |
if (operation_type == "*") { | |
return num1 * num2; | |
} | |
if (operation_type == "/") { | |
return num1 / num2; | |
} | |
// нечётно | |
if (operation_type == "!") { | |
if (num1 % 2 != 0) { | |
return num1; | |
} | |
if (num2 % 2 != 0) { | |
return num2; | |
} | |
throw new Exception("Оба числа чётные!"); | |
} | |
// чётно | |
if (operation_type == "?") { | |
if (num1 % 2 == 0) { | |
return num1; | |
} | |
if (num2 % 2 == 0) { | |
return num2; | |
} | |
throw new Exception("Оба числа нечётные!"); | |
} | |
if (operation_type == "<") { | |
if (num1 < num2) { | |
return num1; | |
} else { | |
return num2; | |
} | |
} | |
if (operation_type == ">") { | |
if (num1 > num2) { | |
return num1; | |
} else { | |
return num2; | |
} | |
} | |
throw new Exception("Такого не может произойти, потому что есть проверка раньше в коде, но компилятору нужно чтоб данный метод возвращал какое-то значение"); | |
} | |
private int FindEmptyOperandAndSolve() { | |
if (operand_left == is_empty_operand) { | |
return DoActionWithLeftEmpty(operation, operand_right, operand_result); | |
} | |
if (operand_right == is_empty_operand) { | |
return DoActionWithRightEmpty(operation, operand_left, operand_result); | |
} | |
if (operand_result == is_empty_operand) { | |
return DoActionWithResultEmpty(operation, operand_left, operand_right); | |
} | |
throw new Exception("Такого не может произойти, потому что есть проверка раньше в коде, но компилятору нужно чтоб данный метод возвращал какое-то значение"); | |
} | |
private void ValidateOperands() { | |
int[] positions = {operand_left, operand_right, operand_result}; | |
int count_empty = 0; // empty values is is_empty_operand variable value | |
foreach (int pos in positions) { | |
if (pos == is_empty_operand) { | |
count_empty++; | |
} | |
} | |
if (count_empty > 1) { | |
throw new Exception("Некорректная строка: слишком много неизвестных чисел в исходной строке"); | |
} | |
if (count_empty == 0) { | |
throw new Exception("Некорректная строка: разве в этой строке нужно что-то считать?"); | |
} | |
} | |
private void Parse(string captcha_string) { | |
string tmp = String.Empty; | |
// parse left | |
tmp = GetNextFromString(captcha_string, true); | |
if (!String.IsNullOrEmpty(tmp)) { | |
operand_left = Convert.ToInt32(tmp); | |
captcha_string = captcha_string.Substring(tmp.Length); | |
} | |
// parse operation | |
tmp = GetNextFromString(captcha_string, false); | |
if (ValidateAction(tmp)) { | |
operation = tmp; | |
captcha_string = captcha_string.Substring(tmp.Length); | |
} else { | |
throw new Exception("Некорректная строка: не нашли действие, которое нужно применить к числам"); | |
} | |
// parse right | |
tmp = GetNextFromString(captcha_string, true); | |
if (!String.IsNullOrEmpty(tmp)) { | |
operand_right = Convert.ToInt32(tmp); | |
captcha_string = captcha_string.Substring(tmp.Length); | |
} | |
// parse '=' | |
tmp = GetNextFromString(captcha_string, false); | |
if (tmp == "=") { | |
captcha_string = captcha_string.Substring(tmp.Length); | |
} else { | |
throw new Exception("Некорректная строка: не нашли символ равно"); | |
} | |
// parse result | |
tmp = GetNextFromString(captcha_string, true); | |
if (!String.IsNullOrEmpty(tmp)) { | |
operand_result = Convert.ToInt32(tmp); | |
captcha_string = captcha_string.Substring(tmp.Length); | |
} | |
if (!String.IsNullOrEmpty(captcha_string)) { | |
throw new Exception("Некорректная строка: после парсинга осталось что-то ещё в хвосте"); | |
} | |
} | |
private bool ValidateAction(string action) { | |
return valid_operations.Contains(action); | |
} | |
private string GetNextFromString(string str, bool is_need_numeric = true) { | |
string tmp = String.Empty; | |
int len = str.Length; | |
if (!is_need_numeric) { | |
len = 1; // parse only 1 next char (fix "1+=3" example) | |
} | |
for (int i = 0; i < len; i++) { | |
if (IsNumeric(str[i]) == is_need_numeric) { | |
tmp = String.Concat(tmp, str[i]); | |
} else { | |
break; | |
} | |
} | |
return tmp; | |
} | |
private bool IsNumeric(char symbol) { | |
int tst; | |
string symbol_str = Convert.ToString(symbol); // Convert.ToInt32 not throw any exceptions if char is not digit (thus I need use int.TryParse with this conversion)... | |
return int.TryParse(symbol_str, out tst); | |
} | |
private string Normalize(string captcha_string) { | |
captcha_string = captcha_string.ToLower(); | |
captcha_string = captcha_string.Replace(" ", ""); | |
List<KeyValuePair<string, string>> replace_data = new List<KeyValuePair<string, string>>() { | |
// numbers 11-19 (need be first) | |
new KeyValuePair<string, string>("eleven", "11"), | |
new KeyValuePair<string, string>("twelve", "12"), | |
new KeyValuePair<string, string>("thirteen", "13"), | |
new KeyValuePair<string, string>("fourteen", "14"), | |
new KeyValuePair<string, string>("fifteen", "15"), | |
new KeyValuePair<string, string>("sixteen", "16"), | |
new KeyValuePair<string, string>("seventeen", "17"), | |
new KeyValuePair<string, string>("eighteen", "18"), | |
new KeyValuePair<string, string>("nineteen", "19"), | |
// numbers 0-10 | |
new KeyValuePair<string, string>("zero", "0"), | |
new KeyValuePair<string, string>("one", "1"), | |
new KeyValuePair<string, string>("two", "2"), | |
new KeyValuePair<string, string>("three", "3"), | |
new KeyValuePair<string, string>("four", "4"), | |
new KeyValuePair<string, string>("five", "5"), | |
new KeyValuePair<string, string>("six", "6"), | |
new KeyValuePair<string, string>("seven", "7"), | |
new KeyValuePair<string, string>("eight", "8"), | |
new KeyValuePair<string, string>("nine", "9"), | |
new KeyValuePair<string, string>("ten", "10"), | |
// operations | |
new KeyValuePair<string, string>("−", "-"), | |
new KeyValuePair<string, string>("—", "-"), | |
new KeyValuePair<string, string>("×", "*"), | |
new KeyValuePair<string, string>("x", "*"), | |
new KeyValuePair<string, string>("plus", "+"), | |
new KeyValuePair<string, string>("minus", "-"), | |
new KeyValuePair<string, string>("multiply", "*"), | |
new KeyValuePair<string, string>("divide", "/"), | |
}; | |
// tens -> "десятки" | |
List<KeyValuePair<string, string>> replace_tens = new List<KeyValuePair<string, string>>() { | |
new KeyValuePair<string, string>("twenty", "20"), | |
new KeyValuePair<string, string>("thirty", "30"), | |
new KeyValuePair<string, string>("forty", "40"), | |
new KeyValuePair<string, string>("fifty", "50"), | |
new KeyValuePair<string, string>("sixty", "60"), | |
new KeyValuePair<string, string>("seventy", "70"), | |
new KeyValuePair<string, string>("eighty", "80"), | |
new KeyValuePair<string, string>("ninety", "90"), | |
// огроменный бл?ть костыль, чтоб код был красив как кот | |
// без него сломается; нужен из-за порядка замены чисел 1-9 и 20-90 | |
new KeyValuePair<string, string>("ty", "0"), | |
new KeyValuePair<string, string>("y", "0"), | |
}; | |
// заменяем обычные числа | |
foreach(KeyValuePair<string, string> data in replace_data) { | |
captcha_string = captcha_string.Replace(data.Key, data.Value); | |
} | |
// заменяем "десятки" с хитрыми условиями | |
foreach(KeyValuePair<string, string> data in replace_tens) { | |
int pos = -1; | |
do { | |
pos = captcha_string.IndexOf(data.Key); | |
if (pos != -1) { | |
string val = data.Value; | |
string key = data.Key; | |
int end_pos = pos + key.Length - 1; | |
if ((end_pos + 1) < captcha_string.Length) { | |
if (captcha_string[end_pos + 1] == '-') { // если следующий символ после найденой "десятки" это тире | |
val = val.Replace("0", ""); | |
key = String.Concat(key, "-"); | |
} | |
if (IsNumeric(captcha_string[end_pos + 1])) { // если следующий символ после найденной "десятки" это число | |
val = val.Replace("0", ""); | |
} | |
} | |
captcha_string = ReplaceFirst(captcha_string, key, val); | |
} | |
} while (pos != -1); | |
} | |
// Console.Write(captcha_string + " << "); | |
return captcha_string; | |
} | |
private string ReplaceFirst(string text, string search, string replace) { | |
int pos = text.IndexOf(search); | |
if (pos < 0) { | |
return text; | |
} | |
return text.Substring(0, pos) + replace + text.Substring(pos + search.Length); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment