Skip to content

Instantly share code, notes, and snippets.

@lord-alfred
Last active June 27, 2021 11:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lord-alfred/6020e7a5f60f06652101613c490a73f9 to your computer and use it in GitHub Desktop.
Save lord-alfred/6020e7a5f60f06652101613c490a73f9 to your computer and use it in GitHub Desktop.
Solve/calc math text captchas. C# класс для решения текстовых математических каптч.
/*
* 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