diff --git a/cpp/memgraph b/cpp/memgraph index 7749ad7cd..27614c8a5 160000 --- a/cpp/memgraph +++ b/cpp/memgraph @@ -1 +1 @@ -Subproject commit 7749ad7cddfb595928379d392a1efa39f8113a63 +Subproject commit 27614c8a5deaaadbe3af94bc57dce058cccc14ee diff --git a/cpp/text_module/algorithm/text.cpp b/cpp/text_module/algorithm/text.cpp index 5891bc0b7..ba5bf70a7 100644 --- a/cpp/text_module/algorithm/text.cpp +++ b/cpp/text_module/algorithm/text.cpp @@ -1,5 +1,6 @@ #include "text.hpp" +#include #include #include @@ -101,3 +102,93 @@ void Text::RegexGroups(mgp_list *args, mgp_graph * /*memgraph_graph*/, mgp_resul record_factory.SetErrorMessage(e.what()); } } + +void Text::Replace(mgp_list *args, mgp_func_context * /*ctx*/, mgp_func_result *result, mgp_memory *memory) { + mgp::MemoryDispatcherGuard guard{memory}; + const auto arguments = mgp::List(args); + mgp::Result result_obj(result); + + try { + auto text = std::string(arguments[0].ValueString()); + const auto regex = std::string(arguments[1].ValueString()); + const auto replacement = std::string(arguments[2].ValueString()); + + if (regex.size() == 0) { + result_obj.SetValue(text); + return; + } + + size_t pos = 0; + while ((pos = text.find(regex, pos)) != std::string::npos) { + text.replace(pos, regex.length(), replacement); + pos += replacement.length(); + } + + result_obj.SetValue(std::move(text)); + } catch (const std::exception &e) { + result_obj.SetErrorMessage(e.what()); + return; + } +} + +void Text::RegReplace(mgp_list *args, mgp_func_context * /*ctx*/, mgp_func_result *result, mgp_memory *memory) { + mgp::MemoryDispatcherGuard guard{memory}; + const auto arguments = mgp::List(args); + mgp::Result result_obj(result); + + try { + const auto text = std::string(arguments[0].ValueString()); + const auto regex = std::string(arguments[1].ValueString()); + const auto replacement = std::string(arguments[2].ValueString()); + + if (regex.size() == 0) { + result_obj.SetValue(text); + return; + } + + std::regex pattern(regex); + std::string result_str = std::regex_replace(text, pattern, replacement); + + result_obj.SetValue(std::move(result_str)); + } catch (const std::exception &e) { + result_obj.SetErrorMessage(e.what()); + return; + } +} + +void Text::Distance(mgp_list *args, mgp_func_context * /*ctx*/, mgp_func_result *result, mgp_memory *memory) { + mgp::MemoryDispatcherGuard guard{memory}; + const auto arguments = mgp::List(args); + mgp::Result result_obj(result); + + try { + const auto text1 = std::string(arguments[0].ValueString()); + const auto text2 = std::string(arguments[1].ValueString()); + + const size_t m = text1.length(); + const size_t n = text2.length(); + std::vector> dp(m + 1, std::vector(n + 1)); + + for (size_t i = 0; i <= m; i++) { + dp[i][0] = i; + } + for (size_t j = 0; j <= n; j++) { + dp[0][j] = j; + } + + for (size_t i = 1; i <= m; i++) { + for (size_t j = 1; j <= n; j++) { + if (text1[i - 1] == text2[j - 1]) { + dp[i][j] = dp[i - 1][j - 1]; + } else { + dp[i][j] = 1 + std::min({dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]}); + } + } + } + + result_obj.SetValue(static_cast(dp[m][n])); + } catch (const std::exception &e) { + result_obj.SetErrorMessage(e.what()); + return; + } +} diff --git a/cpp/text_module/algorithm/text.hpp b/cpp/text_module/algorithm/text.hpp index 3c2f5814f..f14de26fb 100644 --- a/cpp/text_module/algorithm/text.hpp +++ b/cpp/text_module/algorithm/text.hpp @@ -1,5 +1,8 @@ #pragma once +#include +#include + #include namespace Text { @@ -19,8 +22,21 @@ constexpr std::string_view kProcedureRegexGroups = "regexGroups"; constexpr std::string_view kInput = "input"; constexpr std::string_view kRegex = "regex"; constexpr std::string_view kResultRegexGroups = "results"; +/* replace constants */ +constexpr std::string_view kProcedureReplace = "replace"; +constexpr std::string_view kText = "text"; +constexpr std::string_view kReplacement = "replacement"; +/* regreplace constants */ +constexpr std::string_view kProcedureRegReplace = "regreplace"; +/* distance constants */ +constexpr std::string_view kProcedureDistance = "distance"; +constexpr std::string_view kText1 = "text1"; +constexpr std::string_view kText2 = "text2"; void Join(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory); void Format(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory); void RegexGroups(mgp_list *args, mgp_graph *memgraph_graph, mgp_result *result, mgp_memory *memory); +void Replace(mgp_list *args, mgp_func_context *ctx, mgp_func_result *result, mgp_memory *memory); +void RegReplace(mgp_list *args, mgp_func_context *ctx, mgp_func_result *result, mgp_memory *memory); +void Distance(mgp_list *args, mgp_func_context *ctx, mgp_func_result *result, mgp_memory *memory); } // namespace Text diff --git a/cpp/text_module/text_module.cpp b/cpp/text_module/text_module.cpp index 84958a967..b23b18293 100644 --- a/cpp/text_module/text_module.cpp +++ b/cpp/text_module/text_module.cpp @@ -20,6 +20,23 @@ extern "C" int mgp_init_module(struct mgp_module *module, struct mgp_memory *mem {mgp::Parameter(Text::kInput, mgp::Type::String), mgp::Parameter(Text::kRegex, mgp::Type::String)}, {mgp::Return(Text::kResultRegexGroups, {mgp::Type::List, mgp::Type::Any})}, module, memory); + + mgp::AddFunction(Text::Replace, Text::kProcedureReplace, + {mgp::Parameter(Text::kText, mgp::Type::String), + mgp::Parameter(Text::kRegex, mgp::Type::String), + mgp::Parameter(Text::kReplacement, mgp::Type::String)}, + module, memory); + + mgp::AddFunction(Text::RegReplace, Text::kProcedureRegReplace, + {mgp::Parameter(Text::kText, mgp::Type::String), + mgp::Parameter(Text::kRegex, mgp::Type::String), + mgp::Parameter(Text::kReplacement, mgp::Type::String)}, + module, memory); + + mgp::AddFunction(Text::Distance, Text::kProcedureDistance, + {mgp::Parameter(Text::kText1, mgp::Type::String), + mgp::Parameter(Text::kText2, mgp::Type::String)}, + module, memory); } catch (const std::exception &e) { return 1; } diff --git a/e2e/text_test/test_distance_basic/input.cyp b/e2e/text_test/test_distance_basic/input.cyp new file mode 100644 index 000000000..e69de29bb diff --git a/e2e/text_test/test_distance_basic/test.yml b/e2e/text_test/test_distance_basic/test.yml new file mode 100644 index 000000000..7ea992fa3 --- /dev/null +++ b/e2e/text_test/test_distance_basic/test.yml @@ -0,0 +1,4 @@ +query: | + RETURN text.distance("Levenshtein", "Levenstein") AS output; +output: + - output: 1 diff --git a/e2e/text_test/test_distance_different/input.cyp b/e2e/text_test/test_distance_different/input.cyp new file mode 100644 index 000000000..e69de29bb diff --git a/e2e/text_test/test_distance_different/test.yml b/e2e/text_test/test_distance_different/test.yml new file mode 100644 index 000000000..6e3e60194 --- /dev/null +++ b/e2e/text_test/test_distance_different/test.yml @@ -0,0 +1,4 @@ +query: | + RETURN text.distance("Hello", "World") AS output; +output: + - output: 4 diff --git a/e2e/text_test/test_distance_empty/input.cyp b/e2e/text_test/test_distance_empty/input.cyp new file mode 100644 index 000000000..e69de29bb diff --git a/e2e/text_test/test_distance_empty/test.yml b/e2e/text_test/test_distance_empty/test.yml new file mode 100644 index 000000000..542c58c8a --- /dev/null +++ b/e2e/text_test/test_distance_empty/test.yml @@ -0,0 +1,4 @@ +query: | + RETURN text.distance("", "") AS output; +output: + - output: 0 diff --git a/e2e/text_test/test_distance_levenshtein/input.cyp b/e2e/text_test/test_distance_levenshtein/input.cyp new file mode 100644 index 000000000..e69de29bb diff --git a/e2e/text_test/test_distance_levenshtein/test.yml b/e2e/text_test/test_distance_levenshtein/test.yml new file mode 100644 index 000000000..7ea992fa3 --- /dev/null +++ b/e2e/text_test/test_distance_levenshtein/test.yml @@ -0,0 +1,4 @@ +query: | + RETURN text.distance("Levenshtein", "Levenstein") AS output; +output: + - output: 1 diff --git a/e2e/text_test/test_regreplace_basic/input.cyp b/e2e/text_test/test_regreplace_basic/input.cyp new file mode 100644 index 000000000..e69de29bb diff --git a/e2e/text_test/test_regreplace_basic/test.yml b/e2e/text_test/test_regreplace_basic/test.yml new file mode 100644 index 000000000..d7dcdb761 --- /dev/null +++ b/e2e/text_test/test_regreplace_basic/test.yml @@ -0,0 +1,5 @@ +query: > + RETURN text.regreplace("Hello World!", "W[a-z]+", "Memgraph") AS output; + +output: + - output: "Hello Memgraph!" diff --git a/e2e/text_test/test_regreplace_empty/input.cyp b/e2e/text_test/test_regreplace_empty/input.cyp new file mode 100644 index 000000000..e69de29bb diff --git a/e2e/text_test/test_regreplace_empty/test.yml b/e2e/text_test/test_regreplace_empty/test.yml new file mode 100644 index 000000000..158c07dcc --- /dev/null +++ b/e2e/text_test/test_regreplace_empty/test.yml @@ -0,0 +1,5 @@ +query: > + RETURN text.regreplace("Hello World!", "", "Memgraph") AS output; + +output: + - output: "Hello World!" diff --git a/e2e/text_test/test_regreplace_mage/input.cyp b/e2e/text_test/test_regreplace_mage/input.cyp new file mode 100644 index 000000000..e69de29bb diff --git a/e2e/text_test/test_regreplace_mage/test.yml b/e2e/text_test/test_regreplace_mage/test.yml new file mode 100644 index 000000000..d393952f4 --- /dev/null +++ b/e2e/text_test/test_regreplace_mage/test.yml @@ -0,0 +1,5 @@ +query: > + RETURN text.regreplace("Memgraph MAGE Memgraph MAGE!", "MAGE", "GQLAlchemy") AS output; + +output: + - output: "Memgraph GQLAlchemy Memgraph GQLAlchemy!" diff --git a/e2e/text_test/test_regreplace_multiple/input.cyp b/e2e/text_test/test_regreplace_multiple/input.cyp new file mode 100644 index 000000000..e69de29bb diff --git a/e2e/text_test/test_regreplace_multiple/test.yml b/e2e/text_test/test_regreplace_multiple/test.yml new file mode 100644 index 000000000..57e9cec05 --- /dev/null +++ b/e2e/text_test/test_regreplace_multiple/test.yml @@ -0,0 +1,5 @@ +query: > + RETURN text.regreplace("Hello World! Hello World!", "W[a-z]+", "Memgraph") AS output; + +output: + - output: "Hello Memgraph! Hello Memgraph!" diff --git a/e2e/text_test/test_replace_basic/input.cyp b/e2e/text_test/test_replace_basic/input.cyp new file mode 100644 index 000000000..e69de29bb diff --git a/e2e/text_test/test_replace_basic/test.yml b/e2e/text_test/test_replace_basic/test.yml new file mode 100644 index 000000000..e201f7756 --- /dev/null +++ b/e2e/text_test/test_replace_basic/test.yml @@ -0,0 +1,5 @@ +query: > + RETURN text.replace("Hello World!", "World", "Memgraph") AS output; + +output: + - output: "Hello Memgraph!" diff --git a/e2e/text_test/test_replace_empty/input.cyp b/e2e/text_test/test_replace_empty/input.cyp new file mode 100644 index 000000000..e69de29bb diff --git a/e2e/text_test/test_replace_empty/test.yml b/e2e/text_test/test_replace_empty/test.yml new file mode 100644 index 000000000..29fe5d4c0 --- /dev/null +++ b/e2e/text_test/test_replace_empty/test.yml @@ -0,0 +1,5 @@ +query: > + RETURN text.replace("Hello World!", "", "Memgraph") AS output; + +output: + - output: "Hello World!" diff --git a/e2e/text_test/test_replace_mage/input.cyp b/e2e/text_test/test_replace_mage/input.cyp new file mode 100644 index 000000000..e69de29bb diff --git a/e2e/text_test/test_replace_mage/test.yml b/e2e/text_test/test_replace_mage/test.yml new file mode 100644 index 000000000..8b7b1ba34 --- /dev/null +++ b/e2e/text_test/test_replace_mage/test.yml @@ -0,0 +1,5 @@ +query: > + RETURN text.replace("MAGE is a Memgraph product", "MAGE", "GQLAlchemy") AS output; + +output: + - output: "GQLAlchemy is a Memgraph product" diff --git a/e2e/text_test/test_replace_multiple/input.cyp b/e2e/text_test/test_replace_multiple/input.cyp new file mode 100644 index 000000000..e69de29bb diff --git a/e2e/text_test/test_replace_multiple/test.yml b/e2e/text_test/test_replace_multiple/test.yml new file mode 100644 index 000000000..efd838641 --- /dev/null +++ b/e2e/text_test/test_replace_multiple/test.yml @@ -0,0 +1,5 @@ +query: > + RETURN text.replace("Hello World! Hello World!", "World", "Memgraph") AS output; + +output: + - output: "Hello Memgraph! Hello Memgraph!"