Discussion:
Mysterium i en løkke
(for gammel til at besvare)
Bertel Lund Hansen
2010-10-07 22:36:22 UTC
Permalink
Hej alle

Jeg skal finde mellemrum i en streng efter visse regler. Det kan
jeg sagtens, og jeg har en færdig version af programmet. Men i
stedet for at løbe en delstreng slavisk igennem ville jeg hellere
bruge rfind().

Programmet skal sætte en tekstfil op med lige lange linjer
(maxwidth).

Her er den løkke jeg bruger og som virker. Den skal erstatte et
mellemrum med et linjeskift:

while pos0<len(line)-maxwidth:
pos=line[pos0:].find(' ')
if pos>=0:
if pos<maxwidth:
pos=pos0+maxwidth
while line[pos]!=' ': pos-=1
else:
pos=len(line)
buffer+=line[pos0:pos]+'\n'
pos0=pos+1

Følgende kode går i loop ved behandlingen af allerførste
delstreng. Hvis printlinjen aktiveres, skrives der 64 i em
uendelig løkke.

while pos0<len(line)-maxwidth:
pos=line[pos0:pos0+maxwidth].rfind(' ')
if pos<0:
pos=line[pos0:].find(' ')
if pos<0: pos=len(line)
# print pos
buffer+=line[pos0:pos]+'\n'
pos0=pos+1
--
Bertel
http://bertel.lundhansen.dk/ FIDUSO: http://fiduso.dk/
Adam Sjøgren
2010-10-08 19:05:27 UTC
Permalink
Post by Bertel Lund Hansen
Jeg skal finde mellemrum i en streng efter visse regler. Det kan
jeg sagtens, og jeg har en færdig version af programmet. Men i
stedet for at løbe en delstreng slavisk igennem ville jeg hellere
bruge rfind().
Jeg vil anbefale dig at lave en række tests - lav nogle der tester
forskellige hjørnetilfælde og forskellige almindelige tilfælde.

Afprøv den kode der virker med dem, og se om den virker som forventet.

Prøv derefter dine tests med den kode du ikke kan få til at virke. Hvis
du er heldig afslører de flere problemer, som gør det nemmere for dig at
lure hvad fejlen er.

Alternativt vil jeg foreslå at du poster et minimalt eksempel man kan
køre direkte og se problemet i stedet for ufuldstændige kode-uddrag.


Mvh.
--
"None of them kicks go boom Adam Sjøgren
None of them basslines fill the room" ***@koldfront.dk
Bertel Lund Hansen
2010-10-08 22:42:47 UTC
Permalink
Post by Adam Sjøgren
Alternativt vil jeg foreslå at du poster et minimalt eksempel man kan
køre direkte og se problemet i stedet for ufuldstændige kode-uddrag.
TEMPNEWLINES = 'VWXYZ'
maxwidth=65


def handle_error(nr,prompt):
print 'UPS!'


try:
infile=open(textfile,'r')
content=infile.read()
infile.close()
except:
handle_error(1,textfile)
content=content.replace('\n\n',TEMPNEWLINES)
content=content.replace('\n',' ')
content=content.replace(TEMPNEWLINES,'\n\n')
while ' ' in content: content=content.replace(' ',' ')
lines=content.split('\n\n')
content=''
for line in lines:
pos0=0
buffer=''

if 1: # Virker
while pos0<len(line)-maxwidth:
pos=line[pos0:].find(' ')
if pos>=0:
if pos<maxwidth:
pos=pos0+maxwidth
while line[pos]!=' ': pos-=1
else:
pos=len(line)
buffer+=line[pos0:pos]+'\n'
pos0=pos+1

if 0: # Virker ikke
while pos0<len(line)-maxwidth:
pos=line[pos0:pos0+maxwidth].rfind(' ')
if pos<0:
pos=line[pos0:].find(' ')
if pos<0: pos=len(line)
buffer+=line[pos0:pos]+'\n'
pos0=pos+1

content+=buffer+line[pos0:]+'\n\n'

print content


Man skal blot erstatte "textfile" med en tilfældig tekstfil man
har liggende.
--
Bertel
http://bertel.lundhansen.dk/ FIDUSO: http://fiduso.dk/
Klaus Alexander Seistrup
2010-10-09 06:50:09 UTC
Permalink
Post by Bertel Lund Hansen
while ' ' in content: content=content.replace(' ',' ')
#v+
content = ' '.join(content.split(' ')) # eller blot .split() hvis TAB skal med
#v-

Er du ved at skrive en rutine der ombryder en textblok til en given bredde?
I så fald findes der allerede et textwrap-modul i Python, og jeg har med
held brugt noget i retning af:

#v+

import textwrap

collapse_para = lambda s: ' '.join(s.split())

def collapse_body(text, width=72, fse=False):
paragraphs = []
for para in text.strip().split('\n\n'):
paragraphs.append(
' \n'.join(map(collapse_para, textwrap.wrap(para.strip(), width, fix_sentence_endings=fse)))
)
return '\n\n'.join(paragraphs)

#v-

Eller, hvis der ikke skal gøres brug af textwrap (og kun for et enkelt
afsnit):

#v+

def wrap(text, width=72):
return reduce(
lambda line, word, width=width: ''.join ((
line,
(' ', ' \n')[(len(line)-line.rfind('\n')-1 + len(word.split('\n',1)[0]) >= width)],
word
)),
text.split(' ')
)

#v-

Men det forklarer selvfølgelig ikke hvorfor din løkke med .rfind() ikke
fungerer…

Mvh,
--
Klaus Alexander Seistrup
http://klaus.seistrup.dk/
Bertel Lund Hansen
2010-10-09 07:46:19 UTC
Permalink
Post by Klaus Alexander Seistrup
Er du ved at skrive en rutine der ombryder en textblok til en given bredde?
Ja.
Post by Klaus Alexander Seistrup
I så fald findes der allerede et textwrap-modul i Python, og jeg har med
Det kikker jeg på. Der er flere gode tips.
Post by Klaus Alexander Seistrup
collapse_para = lambda s: ' '.join(s.split())
Jeg har før set noget med lambda i manualen. Kan du forklare hvad
det skal gøre godt for?
Post by Klaus Alexander Seistrup
Men det forklarer selvfølgelig ikke hvorfor din løkke med .rfind() ikke
fungerer…
Nej, jeg er stadig på bar bund.
--
Bertel
http://bertel.lundhansen.dk/ FIDUSO: http://fiduso.dk/
Klaus Alexander Seistrup
2010-10-09 09:54:36 UTC
Permalink
Post by Bertel Lund Hansen
Post by Klaus Alexander Seistrup
collapse_para = lambda s: ' '.join(s.split())
Jeg har før set noget med lambda i manualen. Kan du forklare hvad
det skal gøre godt for?
Lambda er en “anonym funktion”, dvs. man behøver ikke at tildele den et
navn. Ovenstående lambda svarer til

def collapse_para(s):
return ' '.join(s.split())

Hvis man nu har en liste af textafsnit der hver især skal have alle
forekomster af flere på hinanden følgende blanktegn erstattet med et
enkelt mellemrum, har følgende to blokke samme funktion (collapse_para
er én af ovenstående udgaver):

afsn = body.split('\n\n')
afsn = map(collapse_para, afsn)

og

afsn = body.split('\n\n')
afsn = map(lambda s: ' '.join(s.split()), afsn)

Med lambda undgår man altså at skulle definere en funktion udtrykkeligt,
men det har begrænset anvendelse.

(List comprehension er selvfølgelig en nærliggende mulighed for at
kombinere de to, men har ikke noget med lambda at gøre:

afsn = [' '.join(s.split()) for s in body.split('\n\n')]
)

Mvh,
--
Klaus Alexander Seistrup
http://klaus.seistrup.dk/
Bertel Lund Hansen
2010-10-09 08:34:15 UTC
Permalink
Post by Klaus Alexander Seistrup
Post by Bertel Lund Hansen
while ' ' in content: content=content.replace(' ',' ')
content = ' '.join(content.split(' ')) # eller blot .split() hvis TAB skal med
Den linje gør ikke det samme - heller ikke hvis jeg splitter ved ' ' (2 mellemrum).

Pointen er at alle forekomster af mellemrum skal garanteres til kun at være
ét tegn. Der er (mindst) to muligheder, men begge kræver en while:

# Bemærk to mellemrum:
while ' ' in content: content=content.replace(' ',' ')
while ' ' in content: content = ' '.join(content.split(' '))

En tidstest viser at replace() er hurtigere end join() - (56,8 sek. mod 82,9 sek.).
--
Bertel
http://bertel.lundhansen.dk/ FIDUSO: http://fiduso.dk/
Klaus Alexander Seistrup
2010-10-09 09:36:31 UTC
Permalink
Post by Bertel Lund Hansen
Post by Klaus Alexander Seistrup
Post by Bertel Lund Hansen
while ' ' in content: content=content.replace(' ',' ')
content = ' '.join(content.split(' '))
# eller blot .split() hvis TAB skal med
Den linje gør ikke det samme - heller ikke hvis jeg splitter ved ' ' (2 mellemrum).
Nej, det har du ret i. Hvis du undlader at putte et tegn i .split(), eller
hvis der står .split(None), vil der blive splittet ved alle blanktegn, dog
således at flere på hinanden følgende blanktegn kun gælder som ét. Altså:

content = ' '.join(content.split())

På den måde kan du osse nøjes med .split('\n\n') for at få en liste af
afsnit, og undgår den efterfølgende stanza hvor du erstatter linjeskift
med mellemrum.
Post by Bertel Lund Hansen
while ' ' in content: content=content.replace(' ',' ')
while ' ' in content: content = ' '.join(content.split(' '))
En tidstest viser at replace() er hurtigere end join() - (56,8 sek. mod 82,9 sek.).
Sjovt. Så bruger du bare den hurtigste udgave. Jeg synes blot bedst om
udgaven med .split() og .join(), idet jeg finder den mere læsbar.

Mvh,
--
Klaus Alexander Seistrup
http://klaus.seistrup.dk/
Bertel Lund Hansen
2010-10-09 11:06:25 UTC
Permalink
Post by Klaus Alexander Seistrup
Post by Bertel Lund Hansen
En tidstest viser at replace() er hurtigere end join() - (56,8 sek. mod 82,9 sek.).
Sjovt. Så bruger du bare den hurtigste udgave. Jeg synes blot bedst om
udgaven med .split() og .join(), idet jeg finder den mere læsbar.
Det er også det jeg lægger vægt på (når det ikke er tidskritiske
ting). Jeg kørte tidstesten fordi du anbefalede noget andet end
jeg er vant til. Hvis det var hurtigere, ville jeg have lagt min
vane om.
--
Bertel
http://bertel.lundhansen.dk/ FIDUSO: http://fiduso.dk/
Adam Sjøgren
2010-10-09 12:58:01 UTC
Permalink
Jeg kørte tidstesten fordi du anbefalede noget andet end jeg er vant
til. Hvis det var hurtigere, ville jeg have lagt min vane om.
Så du vælger vane efter hvad der er hurtigst, ikke efter hvad der er
mest læseligt?


Mvh.
--
"None of them kicks go boom Adam Sjøgren
None of them basslines fill the room" ***@koldfront.dk
Bertel Lund Hansen
2010-10-09 16:02:33 UTC
Permalink
Post by Adam Sjøgren
Jeg kørte tidstesten fordi du anbefalede noget andet end jeg er vant
til. Hvis det var hurtigere, ville jeg have lagt min vane om.
Så du vælger vane efter hvad der er hurtigst, ikke efter hvad der er
mest læseligt?
Jeg synes at replace() er mest læselig. Derfor ville jeg kun
ændre vane hvis der var en fordel derved.
--
Bertel
http://bertel.lundhansen.dk/ FIDUSO: http://fiduso.dk/
Adam Sjøgren
2010-10-09 19:44:08 UTC
Permalink
Post by Bertel Lund Hansen
Post by Adam Sjøgren
Så du vælger vane efter hvad der er hurtigst, ikke efter hvad der er
mest læseligt?
Jeg synes at replace() er mest læselig. Derfor ville jeg kun
ændre vane hvis der var en fordel derved.
Det var den lange måde at svare ja på :-)


Mvh.
--
"None of them kicks go boom Adam Sjøgren
None of them basslines fill the room" ***@koldfront.dk
Bertel Lund Hansen
2010-10-09 20:32:02 UTC
Permalink
Post by Adam Sjøgren
Post by Bertel Lund Hansen
Post by Adam Sjøgren
Så du vælger vane efter hvad der er hurtigst, ikke efter hvad der er
mest læseligt?
Jeg synes at replace() er mest læselig. Derfor ville jeg kun
ændre vane hvis der var en fordel derved.
Det var den lange måde at svare ja på :-)
Det var vist ikke oplæg til en frugtbar diskussion.

[Mine første seks svar blev slettet af censuren]
--
Bertel
http://bertel.lundhansen.dk/ FIDUSO: http://fiduso.dk/
Bertel Lund Hansen
2010-10-09 11:13:08 UTC
Permalink
Post by Klaus Alexander Seistrup
content = ' '.join(content.split())
På den måde kan du osse nøjes med .split('\n\n') for at få en liste af
afsnit, og undgår den efterfølgende stanza hvor du erstatter linjeskift
med mellemrum.
Jeg skal have overvejet om split() kan give nogle fordele, men
det er ikke så enkelt som du beskriver det. Hvis linjerne er for
korte, skal linjeskiftet erstattes af et mellemrum for atundgå
den slags sammenskrivninger jeg lige lavede.

Men hvis jeg laver en liste med afsnit, kan jeg behandle hvert af
dem med split() uden at ødelægge adskillelsen. Det bliver nok
løsningen.
--
Bertel
http://bertel.lundhansen.dk/ FIDUSO: http://fiduso.dk/
Klaus Alexander Seistrup
2010-10-09 12:09:40 UTC
Permalink
Post by Bertel Lund Hansen
Jeg skal have overvejet om split() kan give nogle fordele, men
det er ikke så enkelt som du beskriver det. Hvis linjerne er for
korte, skal linjeskiftet erstattes af et mellemrum for atundgå
den slags sammenskrivninger jeg lige lavede.
.split() uden argumenter opdeler texten ved \n, \r, \t, \v og \f, og
giver således et array bestående af “ord”. Hvis pågældende array
sættes sammen med ' '.join(), får man én lang linje af “ord” der hver
især er adskilt af netop ét mellemrum. Du vil derfor ikke opleve den
slags sammenskrivninger du nævner:

#v+
Post by Bertel Lund Hansen
s = 'a\nb\rc\td\ve\ff'
s
'a\nb\rc\td\x0be\x0cf'
Post by Bertel Lund Hansen
s.split()
['a', 'b', 'c', 'd', 'e', 'f']
Post by Bertel Lund Hansen
' '.join(s.split())
'a b c d e f'
#v-
Post by Bertel Lund Hansen
Men hvis jeg laver en liste med afsnit, kan jeg behandle hvert af
dem med split() uden at ødelægge adskillelsen. Det bliver nok
løsningen.
Det lyder fornuftigt.

Mvh,
--
Klaus Alexander Seistrup
http://klaus.seistrup.dk/
Klaus Alexander Seistrup
2010-10-09 13:20:41 UTC
Permalink
Post by Bertel Lund Hansen
while ' ' in content: content=content.replace(' ',' ')
while ' ' in content: content = ' '.join(content.split(' '))
En tidstest viser at replace() er hurtigere end join() -
(56,8 sek. mod 82,9 sek.).
Nu læste jeg lige linjerne igen. Det er ikke meningen at der skal
bruges while på join-delen.

Sådan helt uformelt:

#v+

$ cat løkke.py
#!/usr/bin/python
import sys
buf = sys.stdin.read()
while ' ' in buf:
buf = buf.replace(' ', ' ')
# eof

© cat join.py
#!/usr/bin/python
import sys
buf = sys.stdin.read()
buf = ' '.join(buf.split())
# eof

$ for i in {1..100} ; do ps wwwaux ; done > test.txt
$ wc -c test.txt
2986000 test.txt

$ time python løkke.py < test.txt
real 0m0.176s
user 0m0.140s
sys 0m0.040s

$ time python join.py < ps.txt
real 0m0.113s
user 0m0.030s
sys 0m0.090s

#v-

Mvh,
--
Klaus Alexander Seistrup
http://klaus.seistrup.dk/
Adam Sjøgren
2010-10-09 12:45:21 UTC
Permalink
Post by Bertel Lund Hansen
Post by Adam Sjøgren
Alternativt vil jeg foreslå at du poster et minimalt eksempel man kan
køre direkte og se problemet i stedet for ufuldstændige kode-uddrag.
[...]
Post by Bertel Lund Hansen
Man skal blot erstatte "textfile" med en tilfældig tekstfil man
har liggende.
Ok, efter at have tilføjet en linie med:

textfile='input.txt'

og kørt programmet på det første eksempel jeg kom på:

$ cat input.txt
a b c d e f g h i j k l m n o p q r s t u v w x y z
$ ./blh.py
a b c d e f g h i j k l m n o p q r s t u v w x y z


$

bemærker jeg at programmet tilføjer to linieskift til uddata.

Næste eksempel der falder mig ind:

$ cat input.txt
riverrun, past Eve and Adam's, from swerve of shore to bend of
bay, brings us by a commodius vicus of recirculation back to Howth
Castle and Environs.
$ ./blh.py
riverrun, past Eve and Adam's, from swerve of shore to bend of
bay, brings us by a commodius vicus of recirculation back to
Howth Castle and Environs.


$

Her bemærker jeg at de 4 indledende mellemrum i første afsnit af
programmet ændres til et enkelt mellemrum - det virker måske lidt
pudsigt.

Det tredie eksempel der falder mig ind er:

$ cat input.txt
abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
$ ./blh.py
abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTU

0123456789


$

Her bliver uddata helt mærkeligt.

Det fjerde eksempel der falder mig ind er:

$ cat input.txt
a b c d e f g h i j k l m n o p q r s t u v w x y z 0 1 2 3 4 5 6 7 8 9
$ ./blh.py
a b c d e f g h i j k l m n o p q r s t u v w x y z 0 1 2 3 4 5 6
7 8 9


$

Hvis jeg skifter rundt på de to if'er, så jeg får den version der "ikke
virker", så bliver det i stedet:

$ cat input.txt
a b c d e f g h i j k l m n o p q r s t u v w x y z 0 1 2 3 4 5 6 7 8 9
$ ./blh.py
a b c d e f g h i j k l m n o p q r s t u v w x y z 0 1 2 3 4 5
6 7 8 9


$

den anden udgave deler linien tidligere, men ellers ser det ud til at
virke?

Næste skridt må være at bruge PyUnit¹ til at lave nogle tests der
beskriver det ønskede resultat klarere.

Jeg antager at det er en akademisk øvelse at implementere
linieombrydning, siden du ikke benytter et eksisterende, velgennemprøvet
modul til formålet?


Mvh.

Adam


¹ http://docs.python.org/library/unittest.html
--
"None of them kicks go boom Adam Sjøgren
None of them basslines fill the room" ***@koldfront.dk
Adam Sjøgren
2010-10-09 13:35:00 UTC
Permalink
Post by Adam Sjøgren
Jeg antager at det er en akademisk øvelse at implementere
linieombrydning, siden du ikke benytter et eksisterende, velgennemprøvet
modul til formålet?
I Perl ville jeg f.eks. bruge Text::Autoformat:

$ perl -MText::Autoformat -e 'autoformat { left=>1, right=>65, all=>1 }'
a b c d e f g h i j k l m n o p q r s t u v w x y z

riverrun, past Eve and Adam's, from swerve of shore to bend of
bay, brings us by a commodius vicus of recirculation back to Howth
Castle and Environs.

abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789

a b c d e f g h i j k l m n o p q r s t u v w x y z 0 1 2 3 4 5 6 7 8 9
^D
a b c d e f g h i j k l m n o p q r s t u v w x y z

riverrun, past Eve and Adam's, from swerve of shore to bend of
bay, brings us by a commodius vicus of recirculation back to
Howth Castle and Environs.

abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-
0123456789

a b c d e f g h i j k l m n o p q r s t u v w x y z 0 1 2 3
4 5 6 7 8 9
$


Mvh.

Adam
--
"None of them kicks go boom Adam Sjøgren
None of them basslines fill the room" ***@koldfront.dk
Loading...