J'essaie de comprendre le concept de programmation dynamique via le cours sur MIT OCW here . L’explication de la vidéo OCW est excellente, mais j’ai l’impression que je ne la comprends pas vraiment tant que j’ai incorporé l’explication dans le code. Lors de la mise en œuvre, je me réfère à certaines notes de la note de conférence ici , en particulier à la page 3 de la note.
Le problème est que je ne sais pas comment traduire une partie de la notation mathématique en code. Voici une partie de la solution que j'ai mise en œuvre (et pensez l'avoir mise en œuvre correctement):
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 se trouve aux points 3 à 5 des notes de cours. Je ne comprends littéralement pas et je ne sais pas par où commencer pour les mettre en œuvre. Jusqu'ici, j'ai essayé d'itérer la liste de mots et de compter le mal de chacun des prétendus fins de lignes, 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 cette 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 renverrai justifier
en tant que int
(puisque j’ai déjà décidé de stocker la valeur de retour dans result
, qui est une liste. Devrais-je créer une autre fonction et renvoyer à partir de là? Devrait-il y avoir une récursion?
Pourriez-vous s'il vous plaît me montrer quoi faire ensuite et expliquer en quoi c'est une programmation dynamique? Je ne vois vraiment pas où se trouve la récursivité ni quel est le sous-problème.
Merci avant.
Si vous avez du mal à comprendre l’idée de base de la programmation dynamique, voici mon point de vue:
La programmation dynamique sacrifie essentiellement complexité d'espace pour complexité temporelle (mais l'espace supplémentaire que vous utilisez est généralement très peu comparé au temps que vous gagnez, rendant la programmation dynamique totalement vaut la peine si mis en œuvre correctement). Vous stockez les valeurs de chaque appel récursif au fur et à mesure (dans un tableau ou un dictionnaire, par exemple), ce qui vous permet d'éviter de procéder à un nouveau calcul lorsque vous rencontrez le même appel récursif dans une autre branche de l'arborescence de la récursivité.
Et non, vous not devez utiliser la récursivité. Voici ma mise en œuvre de la question sur laquelle vous travailliez en utilisant simplement des boucles. J'ai suivi très attentivement le TextAlignment.pdf lié par AlexSilva. J'espère que vous trouvez cela 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
Pour ceux qui sont encore intéressés par ceci: La clé est de revenir en arrière à partir de la fin du texte (comme mentionné ici ). Si vous le faites, vous ne faites que comparer des éléments déjà mémorisés.
Dites, words
est une liste de chaînes à emballer selon textwidth
. Ensuite, 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 que l'on peut ajouter une seconde liste pour suivre les positions de rupture. Vous pouvez le faire en modifiant le code en:
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, utilisez simplement la liste des positions de rupture:
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 des espaces. C'est assez délicat (car on peut trouver 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
Qu'est-ce qui vous 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.
je viens de voir la conférence et je pensais mettre ici tout ce que je pouvais comprendre. J'ai mis dans le code dans le même format que celui de l'interrogateur. J'ai utilisé la récursion ici, comme l'explique la conférence.
Le point 3 définit la récurrence. Il s’agit en gros d’une approche de fond, dans laquelle vous calculez plus tôt une valeur de la fonction se rapportant à une entrée plus élevée, puis vous l’utilisez pour calculer l’entrée de la valeur la plus basse.
La conférence explique comme suit:
DP (i) = min (DP (j) + badness (i, j))
pour j qui varie de i + 1 à n.
Ici, je varie de n à 0 (de bas en haut!).
En tant que DP (n) = 0,
DP (n-1) = DP (n) + méchanceté (n-1, n)
et ensuite vous calculez D(n-2) à partir de D(n-1) et D(n) et prenez un minimum d'eux.
De cette façon, vous pouvez descendre jusqu'à i = 0 et c'est la réponse finale du mal!
Au point 4, comme vous pouvez le constater, deux boucles se déroulent ici. Un pour moi et l'autre à l'intérieur je pour j.
Par conséquent, lorsque i = 0, j(max) = n, i = 1, j(max) = n-1, ... i = n , j(max) = 0.
donc temps total = addition de ceux-ci = n (n + 1)/2.
D'où O (n ^ 2).
Le point 5 identifie simplement la solution que DP [0]!
J'espère que cela t'aides!
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 __== '__main__':
main()
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]
Implémentation Java Etant donné la largeur de trait maximale L, l'idée de justifier le texte T est de considérer tous les suffixes du texte (considérer les mots au lieu des caractères pour former des suffixes de manière précise). La programmation dynamique n'est rien d'autre que "Attention minutieuse" ". Si vous envisagez l'approche de la force brute, vous devez procéder comme suit.
Au lieu de cela, considérons simplement le problème pour déterminer le coût de la mise d'un mot au début d'une ligne. En général, nous pouvons définir DP (i) comme étant le coût pour considérer le (i-1) mot comme le début d'une ligne.
Comment pouvons-nous former une relation de récurrence pour DP (i)?
Si jth Word est le début de la ligne suivante, la ligne en cours contiendra les mots [i: j) (j exclusif) et le coût du jth Word correspondant au début de la ligne suivante sera DP (j). D'où DP (i) = DP (j) + coût de l'insertion des mots [i: j) dans la ligne courante. Comme nous voulons minimiser le coût total, DP (i) peut être défini comme suit.
Relation réccurente:
DP (i) = min {DP (j) + coût d’ajout de mots [i: j dans la ligne courante} pour tout j dans [i + 1, n]
Remarque j = n signifie qu'il ne reste plus de mots à insérer dans la ligne suivante.
Le cas de base: DP (n) = 0 => à ce stade, il ne reste plus de mot à écrire.
Pour résumer:
Maintenant, même si nous avons calculé le coût minimum pour justifier le texte, nous devons également résoudre le problème initial en gardant une trace de la valeur j définie comme minimum dans l'expression ci-dessus, afin que nous puissions ensuite utiliser le même résultat pour imprimer le texte justifié. texte. L'idée est de garder le pointeur parent.
J'espère que cela vous aide à comprendre la solution. Ci-dessous, la simple mise en œuvre de l'idée ci-dessus.
public class TextJustify {
class IntPair {
//The cost or badness
final int x;
//The index of Word at the beginning of a line
final int y;
IntPair(int x, int y) {this.x=x;this.y=y;}
}
public List<String> fullJustify(String[] words, int L) {
IntPair[] memo = new IntPair[words.length + 1];
//Base case
memo[words.length] = new IntPair(0, 0);
for(int i = words.length - 1; i >= 0; i--) {
int score = Integer.MAX_VALUE;
int nextLineIndex = i + 1;
for(int j = i + 1; j <= words.length; j++) {
int badness = calcBadness(words, i, j, L);
if(badness < 0 || badness == Integer.MAX_VALUE) break;
int currScore = badness + memo[j].x;
if(currScore < 0 || currScore == Integer.MAX_VALUE) break;
if(score > currScore) {
score = currScore;
nextLineIndex = j;
}
}
memo[i] = new IntPair(score, nextLineIndex);
}
List<String> result = new ArrayList<>();
int i = 0;
while(i < words.length) {
String line = getLine(words, i, memo[i].y);
result.add(line);
i = memo[i].y;
}
return result;
}
private int calcBadness(String[] words, int start, int end, int width) {
int length = 0;
for(int i = start; i < end; i++) {
length += words[i].length();
if(length > width) return Integer.MAX_VALUE;
length++;
}
length--;
int temp = width - length;
return temp * temp;
}
private String getLine(String[] words, int start, int end) {
StringBuilder sb = new StringBuilder();
for(int i = start; i < end - 1; i++) {
sb.append(words[i] + " ");
}
sb.append(words[end - 1]);
return sb.toString();
}
}