📜 ⬆️ ⬇️

Techniques in Elixir for Beginners: Trial and Error (Translation)

The Generate and Test Algorithm


Translation of the article Coding AI Techniques in Elixir: The Generate and Test Algorithm


Recently, I worked on a project in the field of I.I. (Artificial Intelligence). According to my expectations, the work on this project will take more time. The goal was to write a project 100% in Elixir, but before I could make this decision, I needed to make sure that I could implement some of the most popular solutions in the field of II. on elixir. For several days, I studied some of the most effective techniques that researchers in the field of I.I. to solve problems by software.


In this article I will give a brief explanation of the method called Generation and Test Algorithm (also known as trial and error) , and then I will use the variation of this method to solve a simple problem using the programming language Elixir.


In I.I. There are many areas of which the most famous today are machine learning, natural language processing (natural language processing) and robotics. However, for me, my favorite discipline in I.I. known as automated reasoning - Automated reasoning . The program that I will demonstrate will fall into this area



Problem


Suppose you were asked to write a program that should form an interrogative sentence on whether the sum of a group of very large natural numbers is an even number, and then form an answer to this question. Let's try to apply the method of automated reasoning to solve this simple problem.


Decision


The algorithm of automated reasoning is best suited for solving simple problems, such as the one given above. The way the algorithm works is pretty simple. There are 3 main steps.


1.    2.  ,     3.   ,     ,       1  3 

Bearing in mind this concept, let's create a smart program that can answer the question: "Is the result x + y even?". In this exercise, we will only care about even amounts, so that when the system detects that the result of counting the amount is an even number, we will react accordingly confirming that the expected result has been found. Usually, when we get the true answer using this technique, we would have to stop all processing. However, in our case, we only want to stop when all the questions prepared by the system are heard. And finally, for the questions the sum of the numbers in which is not an even number, we will answer No.


Code


When writing a solution for this problem, I noticed that the code you are going to see covers most of the basic features of the Elixir language. So, if you have never used Elixir before this point, these code examples will give you a practical idea of ​​how the language works.


The first step is to create a module called SumAndTest , which will contain all the implementation code.


 defmodule SumAndTest do @moduledoc """ This script is designed to show the functionality of the AI Algorithm that is known as Generate and Test. It will produce the results to a addition question and answer whether or not the answer is even. """ 


We must answer the question: "Is the result x + y an even number?". So let's move on and create a function that contains this question.


  defp addition_question(first_numbaer, second_number) do "Is the result of #{first_numbaer} + #{second_number} even?" end 


Now we have our question. The next step is to determine two possible answers that our system will answer the question. We will use the Elixir feature to give alternative definitions of the same function depending on the arguments passed.


  defp say_answer(true) do "YES! Even result found." end defp say_answer(false) do "No." end 


We have created the opportunity to respond yes and no. But what will initiate this response? We need to determine whether the result on the sum of 2 numbers is an even number. Create the appropriate function.


  defp even?(number) when is_number(number) do if rem(number, 2) == 0, do: true, else: false end 

It is also possible to rewrite the body of the function simply as rem(number, 2) == 0 , which in itself returns true or false (approx. Lane).



The main building blocks are now ready, but we are striving to create automated reasoning. We want the system to automatically come up with the question itself, so that we can make sure that our system really answers the question correctly. Let's create the generate function responsible for this.


  defp generate(amount_of_questions) when is_number(amount_of_questions) do generate(0, amount_of_questions) end defp generate(accumulator, amount_of_questions) when amount_of_questions >= 1 do question = addition_question(Enum.random(1..100_000), Enum.random(1..100_000)) build_list(question) generate(accumulator + 1, amount_of_questions - 1) end defp generate(total, 0) do IO.puts "#{total} addition questions generated." :ok end 


We are pretty close to completion. But there is something important. In the generate/2 function, we called the build_list function. Why do we need it?


Elixir has fixed data structures. This means that the state is not maintained outside of the current function. This is great for II programming, because things can get complicated very quickly if you create a system with a high degree of variability. In an area where you could deal with an astronomical number of unknown possibilities, you need to have some consistency. This consistency is data.


We created these questions, but we need a way to save them, so the system will not lose them after we exit the function. This is where the Elixir agents come in. Agents in Elixir allow the program to retain state. This makes tracking data structure changes over time independent. Using agents is quite simple, and they can be the main reason why Joe Armstrong (the creator of Erlang) says that with Erlang and Elixir "You don't need a database!" . Let's create an agent that will initially contain an empty list. This list will store our questions.


  defp start_list do Agent.start(fn() -> [] end, [name: __MODULE__]) end 


Great! Now, when our agent has the ability to initialize and can contain a state, it is fashionable to move on and implement the build_list function that was previously encountered.


  defp build_list(question) do Agent.update(__MODULE__, fn(list) -> [question | list] end) end 


To see all the questions that the system needs to be answered, you need to get the current state of the agent. We can simply use the Agent.get/2 function. Let's call this function questions .


  defp questions do Agent.get(__MODULE__, &(&1)) end 


Now we need to tie all this logic together, so that all processing can occur in one successive function call (pipeline). Our pipeline should do the following ...


 1.          . 2.     . 3.  . 4.    . 5.  ""  "". 

Here we will use the pipeline operator (or the pipeline operator). This operator represents the muscle tissue of the programming language Elixir, and this makes the processing algorithms I.I. much clearer than they would be in a LISP.


  defp answer_to(question) do Regex.scan(~r/(\d+) \+ (\d+)/, question, [capture: :all_but_first]) |> List.flatten |> Enum.map(&String.to_integer(&1)) |> Enum.reduce(0, fn(n, acc) -> n + acc end) |> even? |> say_answer end 


To work with large numbers, we can slightly optimize the function answer_to/1 changing the regular expression to select only the last digit of each number from the question line, since the parity of the amount does not change (there are no next two options for answer_to/1 in the original article)


  defp answer_to(question) do Regex.scan(~r/\d*(\d) \+ \d*(\d)/, question, [capture: :all_but_first]) |> List.flatten |> Enum.map(&String.to_integer(&1)) |> Enum.reduce(0, fn(n, acc) -> n + acc end) |> even? |> say_answer end 

Or using the Bitwise module, we can take into account only the last digit of a number in the binary system (1 or 0). The ^^^ operator evaluates the bitwise XOR operation of its arguments.


  use Bitwise defp answer_to(question) do Regex.run(~r/(\d+) \+ (\d+)/, question, [capture: :all_but_first]) |> List.flatten |> Enum.map(&String.to_integer(&1)) |> Enum.reduce(0, fn(n, acc) -> acc ^^^ (n &&& 1) end) |> even? |> say_answer end 

Now, in order for the users of the system to see what it does, let's create a display function that shows the question, as well as the answer to it.


  defp display_answer_for(question) do IO.puts(question) IO.puts(answer_to(question)) end 


Finally! We are in the last part of the system. We need a way to trigger this whole process. We need a startup function that performs the following actions ...


 1.  `start_list`,   . 2.     .    20-. 3.          . 

Here is what this function might look like.


  def start do start_list generate(20) Enum.each(questions, &display_answer_for(&1)) end 


We are ready! What happens when we call SumAndTest.start ? The system produces 20 random questions with the appropriate answers. See the output below !!


 $ iex Erlang/OTP 18 [erts-7.3] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [dtrace] Interactive Elixir (1.3.1) - press Ctrl+C to exit (type h() ENTER for help) iex(1)> c("sum_and_test.ex") [SumAndTest] iex(2)> SumAndTest.start 20 addition questions generated. Is the result of 31956 + 95609 even? No. Is the result of 56902 + 37929 even? No. Is the result of 63154 + 23758 even? YES! Even result found. Is the result of 22268 + 66438 even? YES! Even result found. Is the result of 76068 + 36127 even? No. Is the result of 14158 + 84195 even? No. Is the result of 55174 + 13171 even? No. Is the result of 53028 + 68694 even? YES! Even result found. Is the result of 82027 + 39083 even? YES! Even result found. Is the result of 32349 + 70547 even? YES! Even result found. Is the result of 41416 + 37714 even? YES! Even result found. Is the result of 91326 + 32635 even? No. Is the result of 42663 + 21205 even? YES! Even result found. Is the result of 90054 + 71218 even? YES! Even result found. Is the result of 38305 + 69972 even? No. Is the result of 59014 + 3954 even? YES! Even result found. Is the result of 55096 + 34449 even? No. Is the result of 89363 + 16018 even? No. Is the result of 60760 + 12438 even? YES! Even result found. Is the result of 10044 + 47646 even? YES! Even result found. :ok 

Conclusion


This is probably the simplest of all artificial intelligence algorithms. Its simplicity is why I decided to write about this algorithm in my first article on "Automating the Future". I would consider using the trial and error method if I had to deal with a problem in which only a small number of possible outcomes appear.


By: Quentin Thomas


source


')

Source: https://habr.com/ru/post/310744/


All Articles