How to resolve the algorithm Amb step by step in the C++ programming language
How to resolve the algorithm Amb step by step in the C++ programming language
Table of Contents
Problem Statement
Define and give an example of the Amb operator. The Amb operator (short for "ambiguous") expresses nondeterminism. This doesn't refer to randomness (as in "nondeterministic universe") but is closely related to the term as it is used in automata theory ("non-deterministic finite automaton"). The Amb operator takes a variable number of expressions (or values if that's simpler in the language) and yields a correct one which will satisfy a constraint in some future computation, thereby avoiding failure. Problems whose solution the Amb operator naturally expresses can be approached with other tools, such as explicit nested iterations over data sets, or with pattern matching. By contrast, the Amb operator appears integrated into the language. Invocations of Amb are not wrapped in any visible loops or other search patterns; they appear to be independent. Essentially Amb(x, y, z) splits the computation into three possible futures: a future in which the value x is yielded, a future in which the value y is yielded and a future in which the value z is yielded. The future which leads to a successful subsequent computation is chosen. The other "parallel universes" somehow go away. Amb called with no arguments fails. For simplicity, one of the domain values usable with Amb may denote failure, if that is convenient. For instance, it is convenient if a Boolean false denotes failure, so that Amb(false) fails, and thus constraints can be expressed using Boolean expressions like Amb(x * y == 8) which unless x and y add to four. A pseudo-code program which satisfies this constraint might look like: The output is 2 4 because Amb(1, 2, 3) correctly chooses the future in which x has value 2, Amb(7, 6, 4, 5) chooses 4 and consequently Amb(x * y = 8) produces a success. Alternatively, failure could be represented using strictly Amb(): Or else Amb could take the form of two operators or functions: one for producing values and one for enforcing constraints: where Ambassert behaves like Amb() if the Boolean expression is false, otherwise it allows the future computation to take place, without yielding any value. The task is to somehow implement Amb, and demonstrate it with a program which chooses one word from each of the following four sets of character strings to generate a four-word sentence: The constraint to be satisfied is that the last character of each word (other than the last) is the same as the first character of its successor. The only successful sentence is "that thing grows slowly"; other combinations do not satisfy the constraint and thus fail. The goal of this task isn't to simply process the four lists of words with explicit, deterministic program flow such as nested iteration, to trivially demonstrate the correct output. The goal is to implement the Amb operator, or a facsimile thereof that is possible within the language limitations.
Let's start with the solution:
Step by Step solution about How to resolve the algorithm Amb step by step in the C++ programming language
Amb Function:
- This function finds values that satisfy a given constraint from a set of potential values.
Boost.Hana Implementation:
- Defines the
Amb
function, which takes a constraint and a variable number of potential values. - Creates a set of all possible solutions and finds one that meets the constraint using
fold_right
.
Example 1: Algebraic Equation Solver:
- Uses
hana::tuple
to represent variable values. - Defines a constraint to solve the equation
x * y == 8
. - Finds the solution using
Amb
and prints the result.
Example 2: String Word Matching:
- Uses
hana::tuple
to represent lists of words. - Defines a constraint that enforces a connection between letters of adjacent words.
- Finds a solution using
Amb
and prints the result.
Standard C++ Implementation:
- Defines a
join
function to concatenate strings with a delimiter. - Implements the
amb
function which takes a constraint, a list of lists of options, and a previous string. - Recursively searches for a solution that meets the constraint and returns a vector of selected options.
- Defines the
Amb
function as a wrapper foramb
and a constraint. - Finds a solution for a list of word lists and prints the result.
Key Concepts:
- Constraint Programming: Solving a problem by finding values that satisfy a set of constraints.
- Algebraic Data Types (ADTs): Data structures that represent algebraic expressions or operations.
- Recursion: A function that calls itself to solve a problem.
- String Manipulation: Functions for working with strings, such as
join
andamb
.
Ambiguous Function (First Implementation):
The first implementation of the Amb
function uses Boost.Hana, a metaprogramming library. It takes a constraint and a variable number of potential values as parameters. The constraint is a function that receives a tuple of values and returns a boolean indicating whether the tuple satisfies the constraint. The function returns the first tuple of values that satisfies the constraint.
Here's how the function works:
- It creates the set of all possible solutions by taking the Cartesian product of the tuples of potential values.
- It defines a fold operation that applies the constraint to each tuple and returns the first tuple that satisfies it.
- It uses the
hana::fold_right
function to apply the fold operation to the set of all possible solutions, returning the first tuple that satisfies the constraint.
Example:
In the AlgebraExample
function, the Amb
function is used to find the values of x
and y
such that x * y == 8
. The constraint is a lambda function that receives a tuple of two values and returns true
if the product of the values is 8, and false
otherwise. The Amb
function finds the tuple (2, 4)
as the solution.
Second Implementation of Ambiguous Function:
The second implementation of the Amb
function uses C++ lambdas and functions from the standard library. It takes a constraint and a list of lists of potential values as parameters. The constraint is a function that receives two strings by reference and returns a boolean indicating whether the strings satisfy the constraint. The function returns a vector of strings that satisfies the constraint, or an empty vector if no solution is found.
Here's how the function works:
- It iterates over the first list of potential values.
- For each value in the first list, it checks if the constraint is satisfied with the previous value and the current value.
- If the constraint is satisfied, it recursively calls the
amb
function with the remaining lists of potential values and the current value as the previous value. - If the recursive call returns a non-empty vector, it concatenates the current value to the vector and returns it.
- If the recursive call returns an empty vector, it continues to the next value in the first list.
Example:
In the second main
function, the Amb
function is used to find a sequence of words such that the last letter of each word is the same as the first letter of the next word. The constraint is a lambda function that receives two strings by reference and returns true
if the last letter of the first string is equal to the first letter of the second string, and false
otherwise. The Amb
function finds the sequence "the that frog grows quickly" as a solution.
Source code in the cpp programming language
#include <iostream>
#include <string_view>
#include <boost/hana.hpp>
#include <boost/hana/experimental/printable.hpp>
using namespace std;
namespace hana = boost::hana;
// Define the Amb function. The first parameter is the constraint to be
// enforced followed by the potential values.
constexpr auto Amb(auto constraint, auto&& ...params)
{
// create the set of all possible solutions
auto possibleSolutions = hana::cartesian_product(hana::tuple(params...));
// find one that matches the constraint
auto foldOperation = [constraint](auto a, auto b)
{
bool meetsConstraint = constraint(a);
return meetsConstraint ? a : b;
};
return hana::fold_right(possibleSolutions, foldOperation);
}
void AlgebraExample()
{
// use a tuple to hold the possible values of each variable
constexpr hana::tuple x{1, 2, 3};
constexpr hana::tuple y{7, 6, 4, 5};
// the constraint enforcing x * y == 8
constexpr auto constraint = [](auto t)
{
return t[hana::size_c<0>] * t[hana::size_c<1>] == 8;
};
// find the solution using the Amb function
auto result = Amb(constraint, x, y);
cout << "\nx = " << hana::experimental::print(x);
cout << "\ny = " << hana::experimental::print(y);
cout << "\nx * y == 8: " << hana::experimental::print(result);
}
void StringExample()
{
// the word lists to choose from
constexpr hana::tuple words1 {"the"sv, "that"sv, "a"sv};
constexpr hana::tuple words2 {"frog"sv, "elephant"sv, "thing"sv};
constexpr hana::tuple words3 {"walked"sv, "treaded"sv, "grows"sv};
constexpr hana::tuple words4 {"slowly"sv, "quickly"sv};
// the constraint that the first letter of a word is the same as the last
// letter of the previous word
constexpr auto constraint = [](const auto t)
{
auto adjacent = hana::zip(hana::drop_back(t), hana::drop_front(t));
return hana::all_of(adjacent, [](auto t)
{
return t[hana::size_c<0>].back() == t[hana::size_c<1>].front();
});
};
// find the solution using the Amb function
auto wordResult = Amb(constraint, words1, words2, words3, words4);
cout << "\n\nWords 1: " << hana::experimental::print(words1);
cout << "\nWords 2: " << hana::experimental::print(words2);
cout << "\nWords 3: " << hana::experimental::print(words3);
cout << "\nWords 4: " << hana::experimental::print(words4);
cout << "\nSolution: " << hana::experimental::print(wordResult) << "\n";
}
int main()
{
AlgebraExample();
StringExample();
}
#include <functional>
#include <iostream>
#include <numeric>
#include <string>
#include <vector>
std::string join(const std::string& delimiter, const std::vector<std::string>& list) {
return list.empty() ? "" : std::accumulate(++list.begin(), list.end(), list[0],
[delimiter](auto& a, auto& b) { return a + delimiter + b; });
}
std::vector<std::string> amb(std::function<bool(std::string&, std::string&)> func,
std::vector<std::vector<std::string>> options, std::string previous) {
if ( options.empty() ) {
return std::vector<std::string>();
}
for ( std::string& option : options.front() ) {
if ( ! previous.empty() && ! func(previous, option) ) {
continue;
}
if ( options.size() == 1 ) {
return std::vector<std::string>(1, option);
}
std::vector<std::vector<std::string>> next_options(options.begin() + 1, options.end());
std::vector<std::string> result = amb(func, next_options, option);
if ( ! result.empty() ) {
result.emplace(result.begin(), option);
return result;
}
}
return std::vector<std::string>();
}
std::string Amb(std::vector<std::vector<std::string>> options) {
std::function<bool(std::string, std::string)> continues =
[](std::string before, std::string after) { return before.back() == after.front(); };
std::vector<std::string> amb_result = amb(continues, options, "");
return ( amb_result.empty() ) ? "No match found" : join(" ", amb_result);
}
int main() {
std::vector<std::vector<std::string>> words = { { "the", "that", "a" },
{ "frog", "elephant", "thing" },
{ "walked", "treaded", "grows" },
{ "slowly", "quickly" } };
std::cout << Amb(words) << std::endl;
}
You may also check:How to resolve the algorithm Go Fish step by step in the Perl programming language
You may also check:How to resolve the algorithm Dot product step by step in the Euphoria programming language
You may also check:How to resolve the algorithm Empty program step by step in the Yabasic programming language
You may also check:How to resolve the algorithm Stack step by step in the VBA programming language
You may also check:How to resolve the algorithm Literals/String step by step in the Axe programming language