Dette indlæg er alene udtryk for skribentens egen holdning.

Sjov lille sommeropgave for Linux-geeks

10. juli 2011 kl. 22:5473
Artiklen er ældre end 30 dage
Manglende links i teksten kan sandsynligvis findes i bunden af artiklen.

Jeg faldt over en lille opgave den anden dag, som jeg ikke lige havde et af de mange Linux-tools til at løse.
Opgaven er at vise indholdet af et antal tekst-filer ved siden af hinanden - linie for linie - og pænt.

Filen a.txt:

  1. Jeg
  2. læser
  3. v2.dk
  4. nu

Filen b.txt:

  1. holder
  2. du
  3. sommerferie
  4. allerede

Filen c.txt:

  1. fri
  2. blogs
  3. spas
  4. færdig?

Ideen er at få nappet en linie fra hver fil (som her bare har et ord per linie) og printe det efter hinanden på samme linie, herefter tages den næste line fra hver fil og printes på næste linie.
Dette gøres indtil alle linie er processeret. Er en fil blevet løbet til ende må denne blot springes over.

Artiklen fortsætter efter annoncen

Jeg håber en af læserne har koden til at løse det på en Linux-maskine, og det er underordnet om det er skrevet i Perl, Java, Python, Ruby eller andre relevante sprog.
Lad mig kalde det færdige program "pløf" (for at ære "Bruno" fra DR Ramasjang og Cirkus Summarum - han kan kun tælle til "pløf"):

  1. $ pløf a.txt b.txt c.txt
  2. Jeg holder fri
  3. læser du blogs
  4. v2.dk sommerferie spas
  5. nu allerede færdig?

eller

  1. $ pløf a.txt c.txt
  2. Jeg fri
  3. læser blogs
  4. v2.dk spas
  5. nu færdig?

Det bemærkes, at jeg forventer at ordene i hver kolonne er venstrestillet, så de ikke rækker ind over nogle af ordene i forrige kolonne (ellers er det for nemt)

Naturligvis gives der ekstra point for den mest ekstreme one-liner, der løser dette :-)

Fat knappeturet og paste koden ind her (evt. via http://http://pastebin.com/)

/pto

P.S. Jeg løste selv opgaven med Emacs og cut-rectangle/yank-rectangle :)

73 kommentarer.  Hop til debatten
Denne artikel er gratis...

...men det er dyrt at lave god journalistik. Derfor beder vi dig overveje at tegne abonnement på Version2.

Digitaliseringen buldrer derudaf, og it-folkene tegner fremtidens Danmark. Derfor er det vigtigere end nogensinde med et kvalificeret bud på, hvordan it bedst kan være med til at udvikle det danske samfund og erhvervsliv.

Og der har aldrig været mere akut brug for en kritisk vagthund, der råber op, når der tages forkerte it-beslutninger.

Den rolle har Version2 indtaget siden 2006 - og det bliver vi ved med.

Debatten
Log ind eller opret en bruger for at deltage i debatten.
settingsDebatindstillinger
74
25. juli 2011 kl. 23:21

Jeg fik lyst til at komme en kommentar. Oprettede derfor en bruger, men var meget overrasket over, at Version2 ikke anvender OpenID... men lad det ligge.

Det var txt filerne i opgaven, jeg ville kommentere på. Enhver Linux-geek ved da, at det er ikke endelsen, det kommer an på... men indholdet. Filerne burde bare kaldes a, b og c - uden noget txt - for at holde sig til Linux-geek-ånden... ;) Jeg vil vende tilbage snart med et bud på en løsning.

PS: De lange kodelinjer ovenfor har fucket siden helt op - det må udviklerne lige se på, når de kommer tilbage fra ferie...

65
17. juli 2011 kl. 21:42

Når jeg sådan en regnvejrsdag sidder og læser tråden, og til min overraskelse opdager, at der endnu ikke er nogen, der har vist C-udgaven, føler jeg det som min sure pligt at gøre det. Så her er den, komplet med fejl og uden kommentarer:

  1. /* <a href="http://www.version2.dk/blog/sjov-lille-sommeropgave-linux-geeks-29627">http://www.version2.dk/blog/sjov-lille-sommeropgave-linux-geeks-29627</a> */
  2.  
  3. #include <stdlib.h>
  4. #include <stdio.h>
  5. #include <assert.h>
  6. #include <string.h>
  7.  
  8. typedef struct {
  9. int nlines;
  10. char **lines;
  11. size_t maxlinelen;
  12. } finfo;
  13.  
  14. finfo *readfile(char *filename){
  15. FILE <em>fd;
  16. finfo <em>curfile;
  17. char buffer[100], <em>line;
  18. size_t linelen;
  19.  
  20. curfile=(finfo <em>)malloc((size_t)sizeof(finfo));
  21. curfile->nlines=0;
  22. curfile->lines=NULL;
  23. curfile->maxlinelen=0;
  24.  
  25. fd=fopen(filename, "r");
  26. assert(fd!=0);
  27.  
  28. while((line=fgets(buffer,80,fd))!=NULL){
  29. curfile->nlines++;
  30. linelen=strlen(line); line[linelen-1]=0;
  31.  
  32. if(linelen>curfile->maxlinelen){ curfile->maxlinelen=linelen; };
  33.  
  34. curfile->lines=(char **)realloc(curfile->lines,curfile->nlines</em>sizeof(char</em>));
  35. assert(curfile->lines != NULL);
  36.  
  37. curfile->lines[curfile->nlines-1]=(char</em>)malloc(linelen</em>sizeof(char));
  38. assert(curfile->lines[curfile->nlines-1] != NULL);
  39.  
  40. strcpy(curfile->lines[curfile->nlines-1], line);
  41. };
  42.  
  43. fclose(fd);
  44.  
  45. return curfile;
  46. }
  47.  
  48. void spacify_cpy(char *tobuf, int tolen, char *frombuf, int fromlen){
  49. int i;
  50.  
  51. assert(tolen>=fromlen);
  52. for(i=0; i<(tolen-1); i++) tobuf[i]=' ';
  53. tobuf[tolen-1]=0;
  54.  
  55. for(i=0; i<fromlen; i++) tobuf[i]=frombuf[i];
  56. }
  57.  
  58. int main (int argc, const char * argv[])
  59. {
  60. int f, i;
  61.  
  62. finfo *<em>files;
  63. int nfiles, maxstrlen, maxnlines;
  64. char <em>strbuffer;
  65.  
  66. nfiles=argc-1;
  67.  
  68. files=(finfo **)malloc(nfiles</em>sizeof(finfo</em>));
  69.  
  70. maxstrlen=0;
  71. maxnlines=0;
  72. for(f=0; f<nfiles; f++){
  73. files[f]=readfile((char *)argv[f+1]);
  74.  
  75. if(files[f]->maxlinelen > maxstrlen){ maxstrlen = (int)files[f]->maxlinelen; }
  76. if(files[f]->nlines > maxnlines){ maxnlines = files[f]->nlines; }
  77. }
  78.  
  79. strbuffer=(char *)malloc(maxstrlen+1);
  80.  
  81. for(i=0; i<maxnlines; i++){
  82. for(f=0; f<nfiles; f++){
  83. if(i >= files[f]->nlines){
  84. spacify_cpy(strbuffer, (int)files[f]->maxlinelen, NULL, 0);
  85. } else {
  86. spacify_cpy(strbuffer, (int)files[f]->maxlinelen, files[f]->lines[i], (int)strlen(files[f]->lines[i]));
  87. }
  88. printf("%s ", strbuffer);
  89. }
  90. printf("\n");
  91. }
  92. return 0;
  93. }

66
18. juli 2011 kl. 00:41

Nå, så må vi hellere få en simpel singlepass Common lisp udgave også ...

  1. ;;; A simple common lisp version of pløf
  2. (with-open-file (f1 "a.txt")
  3. (with-open-file (f2 "b.txt")
  4. (with-open-file (f3 "c.txt")
  5. (do ((l1 (read-line f1) (read-line f1 nil 'eof))
  6. (l2 (read-line f2) (read-line f2 nil 'eof))
  7. (l3 (read-line f3) (read-line f3 nil 'eof)))
  8. ((and (eq l1 'eof)
  9. (eq l2 'eof)
  10. (eq l3 'eof)) nil)
  11. (when (eq l1 'eof) (setf l1 ""))
  12. (when (eq l2 'eof) (setf l2 ""))
  13. (when (eq l3 'eof) (setf l3 ""))
  14. (format t "~8a ~8a ~8a ~%" l1 l2 l3)))))

67
18. juli 2011 kl. 02:04

C programmet ser ud til at forudsætte at linjer maksimalt kan være 80 tegn. LISP programmet er hardcoded til tre filer af navn a.txt, b.txt og c.txt.

Kan vi ikke få programmer som er uden sådanne kunstige begrænsninger?

For mine utrænede LISP øjne ser det ikke ud til at programmet beregner kolonnebredde. Men det er måske det der menes med singlepass. Jeg vil bare påpege at mange af de andre programmer, inklusiv de korte Scala programmer, også er singlepass. Man indlæser første fil i en struktur. Dernæst indlæses anden fil i samme struktur samtidig med at man fikser kolonnebredden for første fil, som på det tidspunkt er kendt. Man er naturligvis nødt til at vente med at udskrive indtil det hele er indlæst.

57
15. juli 2011 kl. 03:39

Haskell ser ud til at mangle både zipAll og padTo. Så jeg må selv implementere dem og det koster. Også selvom Haskell lader mig lave en polymorf funktion der klarer begge dele.

  1. import System
  2. import List
  3. s r cs=map(\c->(c++replicate((maximum.(map length))cs-(length c))r))cs
  4. main=do
  5. a<-getArgs
  6. c<-foldl(\a b->a>>=(\a1->b>>=(\b1->return((lines b1):a1))))(return [])(map readFile a)
  7. putStrLn$unlines.(map unwords).transpose$(map(s ' ')$s "" c)

PS. Alle Haskell og Scala programmerne klarer spaces uden problemer fordi der ikke laves nogen antagelser omkring input andet end linjeskift.

72
18. juli 2011 kl. 19:28

Haskell 208 tegn

import System import Data.List s d c = map (\x -> take (maximum (map length c)) $ x++cycle d) c main = getArgs >>= mapM readFile >>= putStr.unlines.(map unwords).transpose.((map (s " ").(s [""])).(map lines))

56
15. juli 2011 kl. 03:07

Nu har jeg fået debugget, forkortet og forlænget Python programmet så det er 197 linier.

  1. import itertools,sys
  2. t=[open(f).read().split("\n")[:-1]for f in sys.argv[1:]]
  3. for l in itertools.zip_longest(*t,fillvalue=""):print(" ".join([l[n].ljust(max(map(len,t[n])))for n in range(len(l))]))
Mit tidligere program virkede ikke når der var mellemrum i data-filerne. Til at teste bruger jeg to filer. a.txt som er:
  1. Jeg
  2. har
  3. ferie
  4.  
  5. i
  6. dag
  7. !!!
og b.txt som er:
  1. Min
  2. store
  3. søde
  4. blåstripede zebra
Jeg vil så kunne skrive
  1. $ python3 pløf a.txt b.txt a.txt b.txt
  2. Jeg Min Jeg Min
  3. har store har store
  4. ferie søde ferie søde
  5. blåstripede zebra blåstripede zebra
  6. i i
  7. dag dag
  8. !!! !!!

Paste giver forkert justering med følgende kommandoer: paste a.txt b.txt a.txt b.txt |column -t|sed -E 's/ ( +)/\1/g'

Lars Jeppesen's Ruby program giver forkert resultat når der er mellemrum i linierne.

53
15. juli 2011 kl. 00:35

Der er stadig et stykke ned til den (indtil videre) korteste variant, der fylder 44 tegn:

paste *.txt|column -t|sed -E 's/ ( +)/\1/g'

58
15. juli 2011 kl. 08:58

Den korteste version fungerer kun, hvis alle filerne har lige mange ord.

54
15. juli 2011 kl. 00:39

Til gengæld virker paste/column/sed kombinationen ikke hvis der er dobbelt space i input. Det er ikke til at komme uden om at sed tilføjelsen er et hack som ikke er perfekt.

52
14. juli 2011 kl. 23:06

Arghh, der mangler en edit funktion. Jeg så lige 2 tegn mere der kunne fjernes

  1. puts ARGV.map{|f|IO.read(f).split}.reduce{|a,b|a.fill("",a.size..b.map{|m|m.size}.max-1).zip(b).map{|c|c[0].ljust(a.map{|s|s.size}.max+1)+c[1].to_s}}

55
15. juli 2011 kl. 01:10

Ruby kommer lidt for tæt på så jeg har optimeret Scala lidt mere. Her er en 142 tegns udgave:

  1. args.map(io.Source.fromFile(_).getLines.toList).reduce((a,b)=>a.zipAll(b,"","").map(x=>x.<em>1.padTo(a.map(</em>.size).max+1,' ')+x._2)).map(println)

51
14. juli 2011 kl. 23:01

Det lykkes mig at presse ruby versionen ned på 152 tegn, ved at bruge Troels' reduce tricks. Men Ruby har desværre ikke noget lign. zipAll, ellers kunne det blive endnu mindre.

Det kræver dog ruby 1.9x og -Ku option før at UTF-8 virker korrekt.

  1. puts ARGV.map{|f|IO.read(f).split}.reduce(){|a,b|a.fill("",a.size..b.map{|m|m.size}.max-1).zip(b).map{|c|c[0].ljust(a.map{|s|s.size}.max+1)+c[1].to_s}}

47
13. juli 2011 kl. 14:35

Med ikke videre kønt snyderi (fjernelse af hashbang og whitespace samt forkortelse af variabler) kan jeg komme ned på 227 karaktere:

https://gist.github.com/1080175

  1. $ wc pløf3
  2. 3 19 227 pløf3
  3. $ python3 pløf3 a.txt b.txt c.txt b.txt d.txt

Lars' Ruby oneliner-version kan vel også blive et par karaktere kortere og kører ok på med ruby1.9.1 (ikke ok med ruby1.8). Jeg kender ikke noget til Ruby, men jeg kunne umiddelbart få Lars' version ned på 223 karaktere.

Der er et stykke vej ned til scala-versionerne. Min ældre default Ubuntu scala 2.7.7 kan forøvrigt ikke klare visse elementer i scala-programmerne. De paste-versioner jeg har testet giver ikke det korrekte resultat hvis min b.txt er længere end a.txt: Anden kolonne bliver rykket for langt mod venstre.

48
13. juli 2011 kl. 14:51

Troels' version virker nu fint med v1.9.1. Den kan i øvrigt også bliver et par tegn kortere, hvis man fjerner de yderste parenteser, så det bliver til 147 tegn :)

  1. Jeg holder fri
  2. læser du blogs
  3. v2.dk sommerferie spas
  4. nu allerede du
  5. midter1 færdig?
  6. midter2

49
13. juli 2011 kl. 14:53

Det skulle naturligvis have været v2.9.0.1. Nu blander jeg det sammen med Ruby.

En editeringfunktion ville være rart.

44
13. juli 2011 kl. 08:21

Troels har naturligvis ret. Den rigtige unix (linux) løsning involverer ikke kodning. Hvis man nøjes med column -t kan sed undværes :)

  1. psi@linux-ets5:~/v2spas> paste a.txt b.txt c.txt | column -t
  2. Jeg holder fri
  3. læser du blogs
  4. v2.dk sommerferie spas
  5. nu allerede færdig?
  6. psi@linux-ets5:~/v2spas>

... og så er der sommerferiefisketur i det gode (fiske)vejr! ...

39
12. juli 2011 kl. 14:19

Her kommer en one-liner i ruby. Bemærk at der ikke er brugt ;

  1. [ARGV.map{|f|File.open(f).readlines.map{|l|l.chomp.force_encoding('UTF-8')}}].map {|fi| fi.map{|a|a.fill("", (a.length)..fi.map{|m|m.length}.max-1).map{|a2|a2.ljust(a.map{|s|s.length}.max+1)} }.transpose.each{|i|puts i.join.rstrip}}

->Peter: Det kunne måske være i idé at lægge en test vektor op, så kunne man også checke om man håndtere UTF-8 encoding korrekt.

34
11. juli 2011 kl. 23:50

Jeg kan faktisk (endnu) ikke se at "paste" er løsningen - tæt på men ikke ok. Bardurs sidste skud med paste *txt | column -nts5 giver to mellemrum mellem kolonnerne, og dermed ikke helt perfekt :) Jeg skal have kigget på de andre forslag. Min gode ven Finn rammer vist heller ikke plet (men tæt på)

  1. ./pløf a.txt b.txt c.txt
  2. Jeg holder fri
  3. læser du blogs
  4. v2.dk sommerferie spas
  5. nu allerede færdig?

Er det ikke æøå-helvedet i Python, du rammes af?

46
13. juli 2011 kl. 12:36

@Peter Toft,

Mit program fejler ved Python2, mens det kører udmærket med Python3 (vistnok!? Mine filer og terminal er UTF-8). I Python2 skal man benytte izip_longest i stedet for zip_longest og man skal håndtere Unicode/UTF-8 Python2-problemet. Så det er muligvis æøå-helvedet i Python du er ramt af? :-(

32
11. juli 2011 kl. 21:24

Det er vel meningen at programmet skal kunne håndtere flere input? Hvis man laver b.txt længere kan man også se at der er nogen varianter præsenteret oven over der placerer ordene i den forkerte kolonne. Her har jeg lavet b.txt længere, slettet et ord fra c.txt, inkluderet en d.txt og kørt den igennem mit Python program:

  1. $ ./pløf a.txt b.txt c.txt b.txt d.txt
  2. Jeg holder fri holder er
  3. læser du blogs du der
  4. v2.dk sommerferierne spas sommerferierne nogen
  5. nu allerede allerede hjemme?
  6. i i
  7. Danmark Danmark

31
11. juli 2011 kl. 16:56

paste -d " " *.txt

Ellers kunne : paste *.txt |awk '{print $1 " " $2 " " $3}'

Jo også have løst det

33
11. juli 2011 kl. 21:56

@Tommi

Det vil være god stil at læse både artiklen ordentligt samt det der allerede er skrevet. Peter skriver:

Det bemærkes, at jeg forventer at ordene i hver kolonne er venstrestillet, så de ikke rækker ind over nogle af ordene i forrige kolonne (ellers er det for nemt)

Din løsning ryger direkte ned i kategorien "ellers er det for nemt".

Søren Lund har en løsning der kombinerer paste med column. En meget men og elegant løsning, der dog ikke håndterer filer af forskellig længde korrekt. Jeg har forbedret den en smule:

paste a.txt b.txt c.txt | column -ntc3

Det ser ud til at gøre det korrekte.

  1. baldur@pkunk:~$ paste a.txt b.txt c.txt b.txt d.txt | column -ntc5
  2. Jeg holder fri holder er
  3. læser du blogs du der
  4. v2.dk sommerferie spas sommerferie nogen
  5. nu allerede allerede hjemme?
  6. i i
  7. Danmark Danmark

36
12. juli 2011 kl. 00:52

Ja, det er lidt sjovt som 'column' tilføjer et ekstra mellemrum mellem hver kolonne, men det kan nu let fixes med sed:

  1. mortenhattesen$ paste a.txt b.txt c.txt|column -ntc3|sed -E 's/ ( +)/\1/g'
  2. Jeg holder fri
  3. læser du blogs
  4. v2.dk sommerferie spas
  5. nu allerede færdig?

50
13. juli 2011 kl. 15:45

@Morten

Ja, det må da være den version der er kortest. 48 tegn. Ruby, Scala... pffffff.... :-D

  1. user@host:~/work/version2.dk$ paste *.txt | column -t | sed -E 's/ ( +)/\1/g'
  2. Jeg holder fri
  3. læser du blogs
  4. v2.dk sommerferie spas
  5. nu allerede færdig?

@Palle Nej, sed kan vist ikke undværes... Der er et ekstra whitespace i dit output.

God sommer :-)

59
15. juli 2011 kl. 10:06

@Eydun

Peter's krav til løsningen er: "Opgaven er at vise indholdet af et antal tekst-filer ved siden af hinanden - linie for linie - og pænt." Dette fortolker jeg som at en person (f.eks. Peter) skal synes at output er pænt. Dermed løser paste / column den stillede opgave ;)

Kudo's må dog gå til Søren, som bragte paste på banen.

68
18. juli 2011 kl. 02:08

@Palle

Peter's krav til løsningen er: "Opgaven er at vise indholdet af et antal tekst-filer ved siden af hinanden - linie for linie - og pænt." Dette fortolker jeg som at en person (f.eks. Peter) skal synes at output er pænt. Dermed løser paste / column den stillede opgave ;)

Peter har allerede dømt paste/column løsningen ude. Han er ikke tilfreds med de ekstra mellemrum. Det stod der egentlig ikke noget om i opgavebeskrivelsen men det er ham som er dommer.

Man kan bruge 'sed' til at fjerne de ekstra mellemrum men ikke uden at få problemer med inputfiler som indeholder dobbelt mellemrum.

69
18. juli 2011 kl. 09:39

@Baldur

Som man kunne se med tabs ; paste | column udgaven, vil opgaven kunne løses i ren .sh - selv med de nu ændrede regler og tilfældig input data. Jeg vil fastholde, at et shell script er den traditionelle Unix/linux løsning og med lidt yderligere sed eller awk gymnastik og ved at bruge 'wc -L' og evt 'bc' kan man sætte tabs korrekt.

Commonlisp udgaven er ganske rigtig fornærmende primitiv, så den kan let forbedres.

71
18. juli 2011 kl. 11:55

@Palle

Tabs er snyd som formodentlig ikke løser Peters problem. Men lad ham være dommer over det. Selvfølgelig kan man løse opgaven med Bash programmering men det er ikke nødvendigvis nemmere eller kortere end de andre løsninger.

Her er et forslag til en Bash løsning:

  1. #!/bin/bash
  2.  
  3. function maxlen {
  4. LEN=0
  5. for i in ${!DATA[<em>]}
  6. do
  7. CLEN=${#DATA[$i]}
  8. if (( $CLEN > $LEN ))
  9. then
  10. LEN=$CLEN
  11. fi
  12. done
  13. }
  14.  
  15. function padstr {
  16. STR=$1
  17. while (( ${#STR} < $2 ))
  18. do
  19. STR="$STR "
  20. done
  21. }
  22.  
  23. function paddata {
  24. maxlen
  25. for (( i=0; i<$1; i++ ))
  26. do
  27. padstr "${DATA[$i]}" $LEN
  28. DATA[$i]="$STR"
  29. done
  30. }
  31.  
  32. for file in "$@"
  33. do
  34. mapfile -t IN < $file
  35. paddata ${#IN[</em>]}
  36. for (( i=0; i<${#IN[<em>]}; i++ ))
  37. do
  38. DATA[$i]="${DATA[$i]} ${IN[$i]}"
  39. done
  40. done
  41.  
  42. for (( i=0; i<${#DATA[</em>]}; i++ ))
  43. do
  44. echo "${DATA[$i]:1}"
  45. done

Output:

  1. baldur@pkunk:/tmp$ ./pløf.sh a.txt b.txt c.txt d.txt
  2. Jeg holder foo fri er
  3. læser du blogs der
  4. v2.dk sommerferie spas nogen
  5. nu allerede hjemme?
  6. i
  7. Danmark

60
15. juli 2011 kl. 10:10

@Palle: Synes du følgende er pænt?

  1. $ paste a.txt b.txt c.txt|column -t|sed -E 's/ ( +)/\1/g'
  2. venstre1 midter1 højre1
  3. midter2 højre2
  4. midter3 højre3
  5. højre4
  6. højre5

61
15. juli 2011 kl. 10:52

Hej Henrik

Nu ved jeg ikke, hvad du har puttet i a,b og c. Men jeg har prøvet på OpenSuse 11 og Mint10. I begge tilfælde er det pænt.

Her er en bash version jeg lige har genereret - du/I får lige det hele:

  1. psi@psi-ThinkPad-T61 ~ $ mkdir v2spas
  2. psi@psi-ThinkPad-T61 ~ $ cd v2spas/
  3. psi@psi-ThinkPad-T61 ~/v2spas $ cat > a.txt
  4. venstre1
  5. venstre2
  6. venstre3
  7. psi@psi-ThinkPad-T61 ~/v2spas $ cat > b.txt
  8. midter1
  9. midter2
  10. midter3
  11. psi@psi-ThinkPad-T61 ~/v2spas $ cat > c.txt
  12. højre1
  13. højre2
  14. højre3
  15. psi@psi-ThinkPad-T61 ~/v2spas $ paste *.txt | column -t
  16. venstre1 midter1 højre1
  17. venstre2 midter2 højre2
  18. venstre3 midter3 højre3
  19. psi@psi-ThinkPad-T61 ~/v2spas $
Har også prøvet med sed besværgelsen - det er stadig pænt, dog med et whitespace mindre mellem kolonnerne.

  1. psi@psi-ThinkPad-T61 ~/v2spas $ paste a.txt b.txt c.txt|column -t|sed -E 's/ ( +)/\1/g'
  2. venstre1 midter1 højre1
  3. venstre2 midter2 højre2
  4. venstre3 midter3 højre3
  5. psi@psi-ThinkPad-T61 ~/v2spas $

@Henrik hvordan er din terminal sat op, siden kolonnerne vælter? Har du prøvet med en ren xterm? (xterm&) eller uden Xwin?

Nå - fiskestængerne venter (igen) ;)

./palle

62
15. juli 2011 kl. 10:56

@Palle

  1. $ cat a.txt
  2. venstre1
  3. $ cat b.txt
  4. midter1
  5. midter2
  6. midter3
  7. $ cat c.txt
  8. højre1
  9. højre2
  10. højre3
  11. højre4
  12. højre5

[/code]

64
15. juli 2011 kl. 16:49

@henrik

Hmm ... linien i a.txt er større end default tabstop, som er 8 tegn. Så med hjælp af tabs går det:

  1. psi@psi-ThinkPad-T61 ~/v2spas $ tabs 1 10 18
  2.  
  3. psi@psi-ThinkPad-T61 ~/v2spas $ paste *.txt | column -t -s,
  4. venstre1 midter1 højre1
  5. midter2 højre2
  6. midter3 højre3
  7. højre4
  8. højre5
  9. psi@psi-ThinkPad-T61 ~/v2spas $

Opgaven er så, at lave shellscriptet pløf, så kaldet af tabs indrettes, så der netop er tabstops ift. den længste linie i hver fil uanset antal filer ... men det bliver en anden dag ...

./palle

30
11. juli 2011 kl. 16:40

Sikken en tåbelig opgave. Nu spilder alle Danmarks nørder deres tid.

Mit første forsøg var dette 6-liniers Python program:

https://gist.github.com/1075844

To-liniers for-løkken kan ændres til en join. Programmet virker ikke med filer med forskellig antal linier (måske skal man læse hvad opgaven går ud på før man løser den?). Et andet program med 'join' i stedet for almindelig 'for' og med håndtering af forskellig linieantal er her:

https://gist.github.com/1075931

på 5-linier med Python3.

23
11. juli 2011 kl. 13:45

Jeg syntes ikke opgaveformuleringen var helt klar mht. encoding af filerne ? Men nu skulle det virke med UTF-8 filer.

  1. files = ARGV.map { |f| File.open(f) }
  2. longest = ARGV.map { |f| File.open(f) }.map { |file| file.readlines.inject(0) {|m,s| [m,s.chomp.force_encoding('UTF-8').length].max } }
  3. begin
  4. line = [files, longest].transpose.inject("") { |l,f| l + sprintf("%*s", -(f[1]+1), f[0].eof? ? "" : f[0].readline.chomp.force_encoding('UTF-8')) }.rstrip
  5. puts line
  6. end while !line.empty?

21
11. juli 2011 kl. 13:39

println((((args map {io.Source.fromFile().getLines().toArray} map {column => column map {.padTo((column map {.length}).max + 1, " ").mkString}}).transpose) map {.mkString}).mkString("\n"))

24
11. juli 2011 kl. 14:26

Hvilken version af Scala bruger du? Den virker ikke på min version 2.7.7.

25
11. juli 2011 kl. 14:42

Scala v2.9.0.1

28
11. juli 2011 kl. 14:48

Øv hvor finder man en .deb til Ubuntu med en nogenlunde ny version af Scala?

29
11. juli 2011 kl. 16:07

Jeg lagde ikke lige mærke til krøllen med filer med forskelligt linieantal, så jeg har lavet en ny version, hvor man også kan se, hvad der sker :)

http://pastebin.com/rPQ1TmuB

35
12. juli 2011 kl. 00:44

@Henrik Schmidt Jeg har lavet en variant af din Scala kode. Jeg bruger zip i stedet for for-løkken og foreach til print. Men de andre steder har jeg hugget dine løsninger. :-)

http://pastebin.com/xGeF2trz

37
12. juli 2011 kl. 03:11

Jeg vil også være med på Scala vognen. Her er min version: http://pastebin.com/n3PrzLvT

  1. val data = args.map(io.Source.fromFile(<em>).getLines().toArray)
  2. data.map(</em>.padTo(data map(<em>.size) max, "")).
  3. map(c=>c.map(</em>.padTo (c.map(<em>.size).max+1,' '))).
  4. transpose.
  5. map(</em>.mkString).
  6. foreach(println)

Det er mest en mindre bearbejdning af de andre Scala programmer.

Programmet er egentlig en two-liner. Jeg kunne ikke finde en måde at gøre det til en ægte one-liner. Problemet er genbrug af "data" to steder i udtrykket.

Programmet køres således:

  1. baldur@pkunk:/tmp$ ~/scala-2.9.0.1/bin/scala pløf.scala a.txt b.txt c.txt b.txt d.txt
  2. Jeg holder fri holder er
  3. læser du blogs du der
  4. v2.dk sommerferie spas sommerferie nogen
  5. nu allerede allerede hjemme?
  6. i i
  7. Danmark Danmark

40
12. juli 2011 kl. 21:19

Mit bud er baseret på de foregående Scala programmer, men jeg anvender reduceLeft og zipAll, så programmet kun fylder en enkelt linje, uden at hver fil skal indlæses flere gange.

http://pastebin.com/ghUQASVp

43
13. juli 2011 kl. 08:12

@Troels: Nice! Man kan dog også bruge reduceRight eller bare reduce, da retningen er ligegyldig. :)

45
13. juli 2011 kl. 09:17

@Henrik: Ja, det har du da ret i. Så kan koden jo reduceres til blot 149 tegn. Tak for code review :-)

41
12. juli 2011 kl. 22:13

@Troels Genialt :-).

Scoren er:

Scala(Troels) 153 tegn one-liner Scala(Baldur) 195 tegn Ruby(Lars) 228 tegn one-liner Python(Tue) 260 tegn Scala(Henrik) 280 tegn Python(Finn) 284 tegn Ruby(Lars J) 287 tegn Scala(Lars B) 301 tegn Haskell(Baldur) 360 tegn

Det er uden whitespace efter bedste evne.

Scala styrer så jeg er meget tilfreds med resultatet...

42
12. juli 2011 kl. 22:14

Tak version2 for at smadre indlægget. Jeg prøver igen:

  • Scala(Troels) 153 tegn one-liner
  • Scala(Baldur) 195 tegn
  • Ruby(Lars) 228 tegn one-liner
  • Python(Tue) 260 tegn
  • Scala(Henrik) 280 tegn
  • Python(Finn) 284 tegn
  • Ruby(Lars J) 287 tegn
  • Scala(Lars B) 301 tegn
  • Haskell(Baldur) 360 tegn
38
12. juli 2011 kl. 10:48

@Baldur nice :)

Jeg tror også Finns løsning kan lappes - vi skal vel også se en Python løsning :)

15
11. juli 2011 kl. 12:55

Denne version i ruby skulle gerne tage højde for den længste linie i hver fil og håndtere manglende linier i filer. Hvis man har lyst kan man skrive den på en linie ;)

  1. files = ARGV.map { |f| File.open(f) }
  2. longest = ARGV.map { |f| File.open(f) }.map { |file| file.readlines.inject(0) {|m,s| [m,s.chomp.length].max } }
  3. begin
  4. line = [files, longest].transpose.inject("") { |l,f| l + sprintf("%*s", -(f[1]+1), f[0].eof? ? "" : f[0].readline.chomp) }.rstrip
  5. puts line
  6. end while !line.empty?

17
11. juli 2011 kl. 13:10

Version2 skal vist lige overveje lange linjer i kode blokke.

Jeg får følgende resultat med ruby programmet:

  1. Jeg holder fri
  2. læser du du
  3. v2.dk sommerferie blogs
  4. nu allerede spas
  5. færdig?

Den lille fejl ligner noget UTF-8 vs ascii problem.

Det nye python program giver:

  1. Jeg holder fri
  2. læser du du
  3. v2.dk sommerferie blogs
  4. nu allerede spas
  5. Traceback (most recent call last):
  6. File "/tmp/p4", line 6, in <module>
  7. print ("%%-%ss %%-%ss %%-%ss" % tuple(lens)) % tuple([l[i].strip() for l in lines])
  8. IndexError: list index out of range

26
11. juli 2011 kl. 14:46

Baldur, hvis du opdaterer din c.txt til ikke at have 'du', så skulle det gerne virke efter hensigten til dette eksempel.

Ellers burde http://pastebin.com/rFuPd4qy gøre det.

12
11. juli 2011 kl. 07:55

Der er nok en, der skal mokke CSV filer i dag. Det skal jeg i hvert fald efter at have fået tilføjet paste(1) til mit aktive kommandoforråd. Mon ikke ikke kommandoen join(1) kommer ret hurtigt i spil også, når nu de to CSV filer ikke er helt sorteret som man gerne ville...

14
11. juli 2011 kl. 12:14

Jeg holder på at min version indtil nu er den eneste der faktisk løser opgaven som specificeret.

Output fra mit program (med defekt c.txt som burde være en del af test-suiten):

  1. Jeg holder fri
  2. læser du du
  3. v2.dk sommerferie blogs
  4. nu allerede spas
  5. færdig?

Output fra paste:

  1. Jeg holder fri
  2. læser du du
  3. v2.dk sommerferie blogs
  4. nu allerede spas
  5. færdig?

Output fra perl programmet er det samme: simpel brug af tab uden hensyn til kolonnebredde.

Output fra python program:

  1. Jeg holder fri
  2. læser du du
  3. v2.dk sommerferie blogs
  4. nu allerede spas

Haskell er ikke specielt egnet til lige denne type opgave, så jeg er sikker på at i kan slå den i et bedre sprog. Jeg havde bare lyst til at kode noget haskell :-). Men den er ikke slået før i kommer med løsninger der faktisk gør det samme.

19
11. juli 2011 kl. 13:18

Baldur er vist den eneste, der læser specifikationen helt igennem :-) Så, ja, paste alene er ikke nok. Men kodning er der stadig ikke brug for. Vha. column kan vi få rykket ordene på plads:

paste a.txt b.txt c.txt | column -tc3

næsten... nu er der faktisk for meget luft imellem, så sed må også på banen:

paste a.txt b.txt c.txt | column -tc3 | sed 's/ \( *\)/\1/g'

Den "defekte c.txt" giver et andet output end Baldurs løsning, der er kønnere, men Peters specifikation er ikke helt tydeligt på det punkt ;-)

20
11. juli 2011 kl. 13:20

'

skal erstattes af enkelt-pling/apostrof...

8
11. juli 2011 kl. 06:47

Der er vel ikke meget kodning i at skrive:

paste a.txt b.txt c.txt

Det løser dit problem...

6
11. juli 2011 kl. 06:14

Jeg har lige fjernet "du" fra c.txt - tak!

7
11. juli 2011 kl. 06:16

reelt ~14 linier - ikke dårligt. Kan den slås? :-)

3
11. juli 2011 kl. 00:04

Nja...

Hvis du smider dette i a.txt

  1. DenneLinieBliverVildtLangOgDetErEgentligLamt
  2. hej

og b.txt

  1. v2.dk
  2. læser

så ser jeg

  1.  
  2. $ pr -mt a.txt b.txt
  3. DenneLinieBliverVildtLangOgDetErEge v2.dk
  4. hej læser

Close...

4
11. juli 2011 kl. 00:27

Problemet med pr er vel egentlig at man skal tage højde for bredden af hver linje fra starten. Altså en two-pass metode. pr -mt bruger en tabulator til at separere, så hvis der er mere end en tabulatorlængde imellem linjerne, så bliver output forkert.

I øvrigt så står linjerne heller ikke tilpas tæt (der er et tab imellem), så det er også noget knald.