<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 Real World Tutorial 1
Step2:  That looks like any other Python code! But this example is a bit silly.
How do we leverage Noodles to earn an honest living? Here's a slightly less
silly example (but only just!). We will build a small translation engine
that translates sentences by submitting each word to an online dictionary
over a Rest API. To do this we make loops ("For thou shalt make loops of 
blue"). First we build the program as you would do in Python, then we
sprinkle some Noodles magic and make it work parallel! Furthermore, we'll
see how to
Step3:  We start with a list of strings that desparately need translation. And add a little
routine to print it in a gracious manner.
Step4:  Beginning Python programmers like to append things; this is not how you are
supposed to program in Python; if you do, please go and read Jeff Knupp's Writing Idiomatic Python.
Step5:  Rather use a comprehension like so
Step6:  Or use map
Step8:  Noodlify!
If your connection is a bit slow, you may find that the translations take a while to process. Wouldn't it be nice to do it in parallel? How much code would we have to change to get there in Noodles? Let's take the slow part of the program and add a @schedule decorator, and run! Sadly, it is not that simple. We can add @schedule to the word method. This means that it will return a promise. 
Rule
Step9:  Let's take stock of the mutations to the original. We've added a @schedule decorator to word, and changed a function call in sentence. Also we added the __str__ method; this is only needed to plot the workflow graph. Let's run the new script.
Step10:  The last peculiar thing that you may notice, is the gather function. It collects the promises that map generates and creates a single new promise. The definition of gather is very simple
Step11:  Dealing with repetition
In the following example we have a line with some repetition.
Step12:  Let's run the example workflows now, but focus on the actions taken, looking at the logs. The function run_and_print_log in the tutorial module runs our workflow with four parallel threads and caches results in a Sqlite3 database.
To see how this program is being run, we monitor the job submission, retrieval and result storage. First, should you have run this tutorial before, remove the database file.
Step13:  Running the workflow, we can now see that at the second occurence of the word 'oote', the function call is attached to the first job that asked for the same result. The job word_size('oote') is run only once.
Step14:  Now, running a similar workflow again, notice that previous results are retrieved from the database.
Step15:  Although the result of every single job is retrieved we still had to go through the trouble of looking up the results of word_size('Oote'), word_size('oote'), and word_size('Boe') to find out that we wanted the result from the format_string. If you want to cache the result of an entire workflow, pack the workflow in another scheduled function!
Versioning
We may add a version string to a function. This version is taken into account when looking up results in the database.
Step16:  See how the first job is evaluated to return a new workflow. Note that if the version is omitted, it is automatically generated from the source of the function. For example, let's say we decided the function word_size_phrase should return a dictionary of all word sizes in stead of a string. Here we use the function called lift to transform a dictionary containing promises to a promise of a dictionary. lift can handle lists, dictionaries, sets, tuples and objects that are constructable from their __dict__ member.
Step17:  Be careful with versions! Noodles will believe you upon your word! If we lie about the version, it will go ahead and retrieve the result belonging to the old function
<ASSISTANT_TASK:>
Python Code:
from noodles import run_single
from noodles.tutorial import (add, sub, mul)
u = add(5, 4)
v = sub(u, 3)
w = sub(u, 2)
x = mul(v, w)
answer = run_single(x)
print("The answer is {0}.".format(answer))
Explanation: Real World Tutorial 1: Translating Poetry
First example
We build workflows by calling functions. The simplest example of this
is the "diamond workflow":
End of explanation
import urllib.request
import json
import re
class Translate:
    Translate words and sentences in the worst possible way. The Glosbe dictionary
    has a nice REST interface that we query for a phrase. We then take the first result.
    To translate a sentence, we cut it in pieces, translate it and paste it back into
    a Frankenstein monster.
    def __init__(self, src_lang='en', tgt_lang='fy'):
        self.src = src_lang
        self.tgt = tgt_lang
        self.url = 'https://glosbe.com/gapi/translate?' \
                   'from={src}&dest={tgt}&' \
                   'phrase={{phrase}}&format=json'.format(
                        src=src_lang, tgt=tgt_lang)
    
    def query_phrase(self, phrase):
        with urllib.request.urlopen(self.url.format(phrase=phrase.lower())) as response:
            translation = json.loads(response.read().decode())
        return translation
    def word(self, phrase):
        translation = self.query_phrase(phrase)
        #translation = {'tuc': [{'phrase': {'text': phrase.lower()[::-1]}}]}
        if len(translation['tuc']) > 0 and 'phrase' in translation['tuc'][0]:
            result = translation['tuc'][0]['phrase']['text']
            if phrase[0].isupper():
                return result.title()
            else:
                return result            
        else:
            return "<" + phrase + ">"
    
    def sentence(self, phrase):
        words = re.sub("[^\w]", " ", phrase).split()
        space = re.sub("[\w]+", "{}", phrase)
        return space.format(*map(self.word, words))
Explanation: That looks like any other Python code! But this example is a bit silly.
How do we leverage Noodles to earn an honest living? Here's a slightly less
silly example (but only just!). We will build a small translation engine
that translates sentences by submitting each word to an online dictionary
over a Rest API. To do this we make loops ("For thou shalt make loops of 
blue"). First we build the program as you would do in Python, then we
sprinkle some Noodles magic and make it work parallel! Furthermore, we'll
see how to:
make more loops
cache results for reuse
Making loops
Thats all swell, but how do we make a parallel loop? Let's look at a map operation; in Python there are several ways to perform a function on all elements in an array. For this example, we will translate some words using the Glosbe service, which has a nice REST interface. We first build some functionality to use this interface.
End of explanation
shakespeare = [
    "If music be the food of love, play on,",
    "Give me excess of it; that surfeiting,",
    "The appetite may sicken, and so die."]
def print_poem(intro, poem):
    print(intro)
    for line in poem:
        print("     ", line)
    print()
print_poem("Original:", shakespeare)
Explanation: We start with a list of strings that desparately need translation. And add a little
routine to print it in a gracious manner.
End of explanation
shakespeare_auf_deutsch = []
for line in shakespeare:
    shakespeare_auf_deutsch.append(
        Translate('en', 'de').sentence(line))
print_poem("Auf Deutsch:", shakespeare_auf_deutsch)
Explanation: Beginning Python programmers like to append things; this is not how you are
supposed to program in Python; if you do, please go and read Jeff Knupp's Writing Idiomatic Python.
End of explanation
shakespeare_ynt_frysk = \
    (Translate('en', 'fy').sentence(line) for line in shakespeare)
print_poem("Yn it Frysk:", shakespeare_ynt_frysk)
Explanation: Rather use a comprehension like so:
End of explanation
shakespeare_pa_dansk = \
    map(Translate('en', 'da').sentence, shakespeare)
print_poem("På Dansk:", shakespeare_pa_dansk)
Explanation: Or use map:
End of explanation
from noodles import schedule
@schedule
def format_string(s, *args, **kwargs):
    return s.format(*args, **kwargs)
import urllib.request
import json
import re
class Translate:
    Translate words and sentences in the worst possible way. The Glosbe dictionary
    has a nice REST interface that we query for a phrase. We then take the first result.
    To translate a sentence, we cut it in pieces, translate it and paste it back into
    a Frankenstein monster.
    def __init__(self, src_lang='en', tgt_lang='fy'):
        self.src = src_lang
        self.tgt = tgt_lang
        self.url = 'https://glosbe.com/gapi/translate?' \
                   'from={src}&dest={tgt}&' \
                   'phrase={{phrase}}&format=json'.format(
                        src=src_lang, tgt=tgt_lang)
    
    def query_phrase(self, phrase):
        with urllib.request.urlopen(self.url.format(phrase=phrase.lower())) as response:
            translation = json.loads(response.read().decode())
        return translation
    
    @schedule
    def word(self, phrase):
        #translation = {'tuc': [{'phrase': {'text': phrase.lower()[::-1]}}]}
        translation = self.query_phrase(phrase)
        
        if len(translation['tuc']) > 0 and 'phrase' in translation['tuc'][0]:
            result = translation['tuc'][0]['phrase']['text']
            if phrase[0].isupper():
                return result.title()
            else:
                return result            
        else:
            return "<" + phrase + ">"
        
    def sentence(self, phrase):
        words = re.sub("[^\w]", " ", phrase).split()
        space = re.sub("[\w]+", "{}", phrase)
        return format_string(space, *map(self.word, words))
    
    def __str__(self):
        return "[{} -> {}]".format(self.src, self.tgt)
    
    def __serialize__(self, pack):
        return pack({'src_lang': self.src,
                     'tgt_lang': self.tgt})
    @classmethod
    def __construct__(cls, msg):
        return cls(**msg)
Explanation: Noodlify!
If your connection is a bit slow, you may find that the translations take a while to process. Wouldn't it be nice to do it in parallel? How much code would we have to change to get there in Noodles? Let's take the slow part of the program and add a @schedule decorator, and run! Sadly, it is not that simple. We can add @schedule to the word method. This means that it will return a promise. 
Rule: Functions that take promises need to be scheduled functions, or refer to a scheduled function at some level. 
We could write
return schedule(space.format)(*(self.word(w) for w in words))
in the last line of the sentence method, but the string format method doesn't support wrapping. We rely on getting the signature of a function by calling inspect.signature. In some cases of build-in function this raises an exception. We may find a work around for these cases in future versions of Noodles. For the moment we'll have to define a little wrapper function.
End of explanation
from noodles import gather, run_parallel
from noodles.tutorial import get_workflow_graph
shakespeare_en_esperanto = \
    map(Translate('en', 'eo').sentence, shakespeare)
wf = gather(*shakespeare_en_esperanto)
workflow_graph = get_workflow_graph(wf._workflow)
result = run_parallel(wf, n_threads=8)
print_poem("Shakespeare en Esperanto:", result)
Explanation: Let's take stock of the mutations to the original. We've added a @schedule decorator to word, and changed a function call in sentence. Also we added the __str__ method; this is only needed to plot the workflow graph. Let's run the new script.
End of explanation
workflow_graph.attr(size='10')
workflow_graph
Explanation: The last peculiar thing that you may notice, is the gather function. It collects the promises that map generates and creates a single new promise. The definition of gather is very simple:
@schedule
def gather(*lst):
    return lst
The workflow graph of the Esperanto translator script looks like this:
End of explanation
from noodles import (schedule, gather_all)
import re
@schedule
def word_size(word):
    return len(word)
@schedule
def format_string(s, *args, **kwargs):
    return s.format(*args, **kwargs)
def word_size_phrase(phrase):
    words = re.sub("[^\w]", " ", phrase).split()
    space = re.sub("[\w]+", "{}", phrase)
    word_lengths = map(word_size, words)
    return format_string(space, *word_lengths)
from noodles.tutorial import display_workflows, run_and_print_log
display_workflows(
    prefix='poetry',
    sizes=word_size_phrase("Oote oote oote, Boe"))
Explanation: Dealing with repetition
In the following example we have a line with some repetition.
End of explanation
# remove the database if it already exists
!rm -f tutorial.db
Explanation: Let's run the example workflows now, but focus on the actions taken, looking at the logs. The function run_and_print_log in the tutorial module runs our workflow with four parallel threads and caches results in a Sqlite3 database.
To see how this program is being run, we monitor the job submission, retrieval and result storage. First, should you have run this tutorial before, remove the database file.
End of explanation
run_and_print_log(word_size_phrase("Oote oote oote, Boe"), highlight=range(4, 8))
Explanation: Running the workflow, we can now see that at the second occurence of the word 'oote', the function call is attached to the first job that asked for the same result. The job word_size('oote') is run only once.
End of explanation
run_and_print_log(word_size_phrase("Oe oe oote oote oote"), highlight=range(5, 10))
Explanation: Now, running a similar workflow again, notice that previous results are retrieved from the database.
End of explanation
@schedule(version='1.0')
def word_size_phrase(phrase):
    words = re.sub("[^\w]", " ", phrase).split()
    space = re.sub("[\w]+", "{}", phrase)
    word_lengths = map(word_size, words)
    return format_string(space, *word_lengths)
run_and_print_log(
    word_size_phrase("Kneu kneu kneu kneu ote kneu eur"),
    highlight=[1, 17])
Explanation: Although the result of every single job is retrieved we still had to go through the trouble of looking up the results of word_size('Oote'), word_size('oote'), and word_size('Boe') to find out that we wanted the result from the format_string. If you want to cache the result of an entire workflow, pack the workflow in another scheduled function!
Versioning
We may add a version string to a function. This version is taken into account when looking up results in the database.
End of explanation
from noodles import lift
def word_size_phrase(phrase):
    words = re.sub("[^\w]", " ", phrase).split()
    return lift({word: word_size(word) for word in words})
display_workflows(prefix='poetry', lift=word_size_phrase("Kneu kneu kneu kneu ote kneu eur"))
run_and_print_log(word_size_phrase("Kneu kneu kneu kneu ote kneu eur"))
Explanation: See how the first job is evaluated to return a new workflow. Note that if the version is omitted, it is automatically generated from the source of the function. For example, let's say we decided the function word_size_phrase should return a dictionary of all word sizes in stead of a string. Here we use the function called lift to transform a dictionary containing promises to a promise of a dictionary. lift can handle lists, dictionaries, sets, tuples and objects that are constructable from their __dict__ member.
End of explanation
@schedule(version='1.0')
def word_size_phrase(phrase):
    words = re.sub("[^\w]", " ", phrase).split()
    return lift({word: word_size(word) for word in words})
run_and_print_log(
    word_size_phrase("Kneu kneu kneu kneu ote kneu eur"),
    highlight=[1])
Explanation: Be careful with versions! Noodles will believe you upon your word! If we lie about the version, it will go ahead and retrieve the result belonging to the old function:
End of explanation
<END_TASK>