Mise en œuvre de la Justification du texte par une programmation dynamique

j'essaie de comprendre le concept de programmation dynamique, via le cours sur MIT OCW ici. L'explication sur OCW video est super et tout, mais j'ai l'impression que je ne comprends pas vraiment jusqu'à ce que je implémente l'explication dans le code. Lors de la mise en œuvre, je me réfère à certaines notes de la note de cours ici, en particulier la page 3 de la note.

le problème est que je n'ai aucune idée de comment traduire une partie de la notation mathématique en code. Voici quelques une partie de la solution que j'ai mis en oeuvre (et pense qu'il est mis en œuvre à droite):

import math

paragraph = "Some long lorem ipsum text."
words = paragraph.split(" ")

# Count total length for all strings in a list of strings.
# This function will be used by the badness function below.
def total_length(str_arr):
    total = 0

    for string in str_arr:
        total = total + len(string)

    total = total + len(str_arr) # spaces
    return total

# Calculate the badness score for a word.
# str_arr is assumed be send as word[i:j] as in the notes
# we don't make i and j as argument since it will require
# global vars then.
def badness(str_arr, page_width):
    line_len = total_length(str_arr)
    if line_len > page_width:
        return float('nan') 
    else:
        return math.pow(page_width - line_len, 3)

maintenant, la partie que je ne comprends pas est sur les points 3 à 5 dans les notes de conférence. Je ne comprends pas et je ne sais pas où commencer à les mettre en œuvre. Jusqu'à présent, j'ai essayé d'itérer la liste des mots, et de compter la méchanceté de chaque soi-disant fin de ligne, comme ceci:

def justifier(str_arr, page_width):
    paragraph = str_arr
    par_len = len(paragraph)
    result = [] # stores each line as list of strings
    for i in range(0, par_len):
        if i == (par_len - 1):
            result.append(paragraph)
        else:
            dag = [badness(paragraph[i:j], page_width) + justifier(paragraph[j:], page_width) for j in range(i + 1, par_len + 1)] 
            # Should I do a min(dag), get the index, and declares it as end of line?

mais alors, je ne sais pas comment je peux continuer la fonction, et pour être honnête, je ne comprends pas cela ligne:

dag = [badness(paragraph[i:j], page_width) + justifier(paragraph[j:], page_width) for j in range(i + 1, par_len + 1)] 

et comment je vais retourner justifierint (puisque je l'ai déjà décidé de stocker la valeur de retour result, qui est une liste de. Dois-je faire une autre fonction et recommencer à partir de là? Il y a de la récursivité?

Pourriez-vous svp me montrer quoi faire ensuite, et expliquer en quoi c'est de la programmation dynamique? Je ne vois vraiment pas où est la récursion, et quel est le sous-problème.

Merci avant.

17
demandé sur kenshinji 2013-08-13 08:17:45

4 réponses

Dans le cas où vous avez du mal à comprendre l'idée de base de la programmation dynamique elle-même, ici est mon prendre sur elle:

La programmation dynamique sacrifie essentiellement espace complexitéfuseau complexité (mais l'espace supplémentaire que vous utilisez est généralement très peu par rapport au temps que vous économisez, ce qui rend la programmation dynamique totalement valable si elle est mise en œuvre correctement). Vous stocker les valeurs de chaque appel récursif comme vous (par exemple, dans un tableau ou une dictionnaire) de sorte que vous pouvez éviter de calculer pour la deuxième fois lorsque vous rencontrez le même appel récursif dans une autre branche de l'arbre de récursion.

Et non, vous n' utiliser la récursivité. Voici mon implémentation de la question sur laquelle vous travailliez en utilisant just loops. J'ai suivi le Textalignement.pdf très étroitement lié par AlexSilva. Nous espérons que vous trouverez ce utile.


def length(wordLengths, i, j):
    return sum(wordLengths[i- 1:j]) + j - i + 1


def breakLine(text, L):
    # wl = lengths of words
    wl = [len(word) for word in text.split()]

    # n = number of words in the text
    n = len(wl)    

    # total badness of a text l1 ... li
    m = dict()
    # initialization
    m[0] = 0    

    # auxiliary array
    s = dict()

    # the actual algorithm
    for i in range(1, n + 1):
        sums = dict()
        k = i
        while (length(wl, k, i) <= L and k > 0):
            sums[(L - length(wl, k, i))**3 + m[k - 1]] = k
            k -= 1
        m[i] = min(sums)
        s[i] = sums[min(sums)]

    # actually do the splitting by working backwords
    line = 1
    while n > 1:
        print("line " + str(line) + ": " + str(s[n]) + "->" + str(n))
        n = s[n] - 1
        line += 1

19
répondu Joohwan 2013-08-13 18:47:23

pour toute autre personne encore intéressée par ceci: la clé est de revenir en arrière à partir de la fin du texte (comme mentionné ici). Si vous le faites, il vous suffit de comparer des éléments déjà mémorisés.

Dire words est une liste de cordes à emballer selon textwidth. Puis, dans la notation de la conférence, la tâche se réduit à trois lignes de code:

import numpy as np

textwidth = 80

DP = [0]*(len(words)+1)

for i in range(len(words)-1,-1,-1):
    DP[i] = np.min([DP[j] + badness(words[i:j],textwidth) for j in range(i+1,len(words)+1)])

Avec:

def badness(line,textwidth):

    # Number of gaps
    length_line = len(line) - 1

    for word in line:
        length_line += len(word)

    if length_line > textwidth: return float('inf')

    return ( textwidth - length_line )**3

il mentionne qu'on peut ajouter une deuxième liste pour garder la trace de les positions de rupture. Vous pouvez le faire en modifiant le code:

DP = [0]*(len(words)+1)
breaks = [0]*(len(words)+1)

for i in range(len(words)-1,-1,-1):
    temp = [DP[j] + badness(words[i:j],args.textwidth) for j in range(i+1,len(words)+1)]

    index = np.argmin(temp)

    # Index plus position in upper list
    breaks[i] = index + i + 1
    DP[i] = temp[index]

Pour récupérer le texte, il suffit d'utiliser la liste de rupture positions:

def reconstruct_text(words,breaks):                                                                                                                

    lines = []
    linebreaks = []

    i = 0 
    while True:

        linebreaks.append(breaks[i])
        i = breaks[i]

        if i == len(words):
            linebreaks.append(0)
            break

    for i in range( len(linebreaks) ):
        lines.append( ' '.join( words[ linebreaks[i-1] : linebreaks[i] ] ).strip() )

    return lines

Résultat: (text = reconstruct_text(words,breaks))

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy
eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam
voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet
clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit
amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam
nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed
diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet
clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

On pourrait être tenté d'ajouter un peu d'espaces. C'est assez délicat (puisque l'on pourrait arriver à diverses règles esthétiques) mais un essai naïf pourrait être:

import re

def spacing(text,textwidth,maxspace=4):

    for i in range(len(text)):

        length_line = len(text[i])

        if length_line < textwidth:

            status_length = length_line
            whitespaces_remain = textwidth - status_length
            Nwhitespaces = text[i].count(' ')

            # If whitespaces (to add) per whitespace exeeds
            # maxspace, don't do anything.
            if whitespaces_remain/Nwhitespaces > maxspace-1:pass
            else:
                text[i] = text[i].replace(' ',' '*( 1 + int(whitespaces_remain/Nwhitespaces)) )
                status_length = len(text[i])

                # Periods have highest priority for whitespace insertion
                periods = text[i].split('.')

                # Can we add a whitespace behind each period?
                if len(periods) - 1 + status_length <= textwidth:
                    text[i] = '. '.join(periods).strip()

                status_length = len(text[i])
                whitespaces_remain = textwidth - status_length
                Nwords = len(text[i].split())
                Ngaps = Nwords - 1

                if whitespaces_remain != 0:factor = Ngaps / whitespaces_remain

                # List of whitespaces in line i
                gaps = re.findall('\s+', text[i])

                temp = text[i].split()
                for k in range(Ngaps):
                    temp[k] = ''.join([temp[k],gaps[k]])

                for j in range(whitespaces_remain):
                    if status_length >= textwidth:pass
                    else:
                        replace = temp[int(factor*j)]
                        replace = ''.join([replace, " "])
                        temp[int(factor*j)] = replace

                text[i] = ''.join(temp)

    return text

Ce qui donne:text = spacing(text,textwidth))

Lorem  ipsum  dolor  sit  amet, consetetur  sadipscing  elitr,  sed  diam nonumy
eirmod  tempor  invidunt  ut labore  et  dolore  magna aliquyam  erat,  sed diam
voluptua.   At  vero eos  et accusam  et justo  duo dolores  et ea  rebum.  Stet
clita  kasd  gubergren,  no  sea  takimata sanctus  est  Lorem  ipsum  dolor sit
amet.   Lorem  ipsum  dolor  sit amet,  consetetur  sadipscing  elitr,  sed diam
nonumy  eirmod  tempor invidunt  ut labore  et dolore  magna aliquyam  erat, sed
diam  voluptua.  At vero eos et accusam et  justo duo dolores et ea rebum.  Stet
clita  kasd gubergren, no sea  takimata sanctus est Lorem  ipsum dolor sit amet.
3
répondu Suuuehgi 2017-03-13 22:46:10

je viens de voir la conférence et j'ai pensé mettre ici tout ce que je pouvais comprendre. J'ai mis le code dans le même format que celui de l'interlocuteur. J'ai utilisé la récursivité ici, comme la conférence l'a expliqué.

Point #3, définit la récurrence. Il s'agit essentiellement d'un bas à l'approche, où dans vous calculez une valeur de la fonction se rapportant à une entrée plus élevée plus tôt, et puis l'utiliser pour calculer le pour la valeur plus faible entrée.

La conférence l'explique comme :

DP(i) = min(DP(j) + méchanceté(i, j))

pour j qui varie de i+1 à n.

Ici, je varie de n à 0 (de bas en haut!).

As DP (n) = 0 ,

DP(n-1) = DP(n) + méchanceté(n-1, n)

puis vous calculez D(n-2) à partir de D(n-1) et D (n) et vous en retirez un minimum.

De cette façon, vous pouvez descendre jusqu'à i=0 et c'est la réponse finale de la méchanceté!

Au point 4, comme vous pouvez le voir, il y a deux les boucles se passe ici. Un pour moi et l'autre pour j.

Par conséquent, lorsque i=0, j(max) = n, i = 1, j(max) = n-1, ... i = n , j(max) = 0.

par conséquent, temps total = addition de ces valeurs = n (n+1)/2.

D'où O(N^2).

Le point 5 identifie simplement la solution que DP[0]!

Espérons que cette aide!

import math

justification_map = {}
min_map = {}

def total_length(str_arr):
    total = 0

    for string in str_arr:
        total = total + len(string)

    total = total + len(str_arr) - 1 # spaces
    return total

def badness(str_arr, page_width):
    line_len = total_length(str_arr)
    if line_len > page_width:
        return float('nan') 
    else:
        return math.pow(page_width - line_len, 3)

def justify(i, n, words, page_width):
    if i == n:

        return 0
    ans = []
    for j in range(i+1, n+1):
        #ans.append(justify(j, n, words, page_width)+ badness(words[i:j], page_width))
        ans.append(justification_map[j]+ badness(words[i:j], page_width))
    min_map[i] = ans.index(min(ans)) + 1
    return min(ans)

def main():
    print "Enter page width"
    page_width = input()
    print "Enter text"
    paragraph = input() 
    words = paragraph.split(' ')
    n = len(words)
    #justification_map[n] = 0 
    for i in reversed(range(n+1)):
        justification_map[i] = justify(i, n, words, page_width)

    print "Minimum badness achieved: ", justification_map[0]

    key = 0
    while(key <n):
        key = key + min_map[key]
        print key

if __name__ == '__main__':
    main()
1
répondu Rindojiterika 2016-08-20 19:21:43

C'est ce que je pense selon votre définition.

import math

class Text(object):
    def __init__(self, words, width):
        self.words = words
        self.page_width = width
        self.str_arr = words
        self.memo = {}

    def total_length(self, str):
        total = 0
        for string in str:
            total = total + len(string)
        total = total + len(str) # spaces
        return total

    def badness(self, str):
        line_len = self.total_length(str)
        if line_len > self.page_width:
            return float('nan') 
        else:
            return math.pow(self.page_width - line_len, 3)

    def dp(self):
        n = len(self.str_arr)
        self.memo[n-1] = 0

        return self.judge(0)

    def judge(self, i):
        if i in self.memo:
            return self.memo[i]

        self.memo[i] = float('inf') 
        for j in range(i+1, len(self.str_arr)):
            bad = self.judge(j) + self.badness(self.str_arr[i:j])
            if bad < self.memo[i]:
                self.memo[i] = bad

        return self.memo[i]
0
répondu user6043912 2016-12-17 13:41:35