Usuń wspólne spacje wiodące

19

Podczas kodowania w Pythonie czasami potrzebujesz ciągu wielowierszowego w funkcji, np

def f():
    s = """\
    Line 1
    Line 2
    Line 3"""

(Ukośnik odwrotny polega na usunięciu wiodącej nowej linii)

Jeśli jednak spróbujesz wydrukować s, otrzymasz

    Line 1
    Line 2
    Line 3

Wcale nie tego chcemy! Zbyt wiele wiodących białych znaków!

Wyzwanie

Biorąc pod uwagę ciąg wielowierszowy składający się wyłącznie ze znaków alfanumerycznych, spacji i nowych linii, usuń wszystkie wspólne spacje na początku każdej linii. Każda linia ma co najmniej jeden znak spacji i nie będzie zawierać spacji końcowych. Wyjście może nie mieć obcych białych znaków, niezależnie od tego, czy będzie to przed, czy po całym wyjściu lub pojedynczym wierszu (z wyjątkiem jednej opcjonalnej końcowej nowej linii).

Dane wejściowe mogą być przesyłane za pośrednictwem argumentu STDIN lub funkcji, a dane wyjściowe mogą być przekazywane za pośrednictwem parametru STDOUT lub zwracanej wartości funkcji. Nie można używać żadnych wbudowanych funkcji, które są przeznaczone do dedentycznego ciągów wielowierszowych lub wykonywania dokładnie tego zadania, np. Pythonatextwrap.dedent .

To jest , więc wygrywa rozwiązanie w najmniejszej liczbie bajtów. Standardowe luki .

Przypadki testowe

"a"                                  ->   "a"
"   abc"                             ->   "abc"
"   abc\n def\n  ghi"                ->   "  abc\ndef\n ghi"
"    a\n    b\n    c"                ->   "a\nb\nc"
"    a\n    b\n    c\nd"             ->   "    a\n    b\n    c\nd"
"   a   b\n     c     d\n    e f"    ->   "a   b\n  c     d\n e f"

Na przykład ostatni przypadek testowy to

   a   b
     c     d
    e f

i powinien wyglądać tak po usunięciu wiodących spacji:

a   b
  c     d
 e f
Sp3000
źródło
Czy dane wyjściowe mogą mieć końcowe białe znaki?
orlp
@orlp Nie, nie może, wyjaśni.
Sp3000,

Odpowiedzi:

12

CJam, 20 14 bajtów

qN/_z{S-}#f>N*

Algorytm :

  • Najpierw dzielimy dane wejściowe na nowe wiersze i robimy kopię (qN/_ )
  • Następnie obliczana jest najmniejsza kolumna ze znakiem spacji, transponując tablicę oddzieloną znakiem nowej linii, a następnie po prostu szukając indeksu pierwszego wiersza nie zawierającego wszystkich spacji (z{S-}# )
  • Następnie po prostu usuwamy tyle znaków z każdej linii (f> )
  • Wreszcie ponownie dołączamy do newline ( N*)

Rozszerzenie kodu

qN/               e# Read the entire input and split it on newline
   _z             e# Take a copy and transpose rows with columns.
                  e# Now we would have a bunch of all space rows. These rows are the ones
                  e# we want to remove (in form of columns) 
     {  }#        e# Get the index of the first item from the transposed array that returns
                  e# true for this block
      S-          e# From each part, remove spaces. If the part is all-space, it will return
                  e# an empty string, which is false in CJam. We finally will get the index
                  e# of the first non-all-space row (or column)
          f>      e# We take that index and remove that many characters from starting of each
                  e# row of the initial newline separated input
            N*    e# Join the array back using newlines and automatically print the result

Wypróbuj online tutaj

Optymalizator
źródło
8

Pyth, 19 18 17 14 bajtów

jbu>R!rhCG6G.z

Implementacja jest całkiem fajna.

  1. u .zpobiera wszystkie linie stdin w tablicy, umieszcza je G. Następnie ocenia ciało wewnętrzne, umieszcza wynik Gi robi to do momentu, aż przestanie się zmieniać (punkt stały).

  2. !rhCG6transponuje G, pobiera pierwszy element transponowanej tablicy (pierwsza kolumna), usuwa go z dowolnej białej spacji i sprawdza, czy pozostały jakieś znaki spoza białej spacji.

  3. Wartość z 2 jest wartością logiczną, którą można postrzegać jako liczbę całkowitą 0 lub 1. >R Gchwyta tę liczbę i odcina tyle znaków z lewej strony każdego wiersza G. Połączone kroki 1, 2 i 3 w zasadzie oznaczają, że będzie nadal usuwał kolumny białych znaków, dopóki nie pozostanie czysta kolumna białych znaków.

  4. jb łączy tablicę wierszy znakiem nowej linii i drukuje ją.

orlp
źródło
2
Czy możesz podać małe wyjaśnienie tego? To jest dla mnie bardzo dziwne!
bobbel
2
@bobbel Dodano wyjaśnienie.
orlp
Naprawdę świetnie, dzięki! Nigdy o tym nie słyszałem! Aby spróbować tego online, znalazłem: pyth.herokuapp.com/…
bobbel
8

sed - 26 bajtów

:;/(^|\n)\S/q;s/^ //mg;b

Biegnij z -rz

Całkiem proste:

  /(^|\n)\S/q;           - quit if there is a line that starts with non-space
              s/^ //mg;  - remove exactly one space in each line
:;                     b - repeat

-ropcja włącza rozszerzone wyrażenia regularne, -zodczytuje całe dane wejściowe jako pojedynczy ciąg (faktycznie używa NUL-bajtu jako separatora linii)

aragaer
źródło
Czy nie potrzebujesz :;N;$!bna początku czegoś podobnego do zebrania linii wejściowych w jedną przestrzeń wzorów? Edycja: nie, nie; po to -zjest flaga.
Toby Speight,
Możesz to :;/^\S/M!s/^ //mg;t-r
zagrać w
7

SWI-Prolog, 233 223 217 bajtów

a(A):-b(A,0,0,0,N),w(A,N,0).
b([A|T],P,K,M,N):-P=1,(A=10,b(T,0,0,M,N);b(T,1,0,M,N));A\=32,(M=0;K<M),b(T,1,0,K,N);I=K+1,b(T,0,I,M,N).
b(_,_,_,N,N).
w([A|T],N,P):-P<N,A=32,Q=P+1,w(T,N,Q);put(A),A=10,w(T,N,0);w(T,N,P);!.

Edytować : całkowicie zmieniłem moją odpowiedź. Teraz używa kodów znaków zamiast ciągów znaków.

Przykładem takiego nazwania byłyby a(` a b\n c d\n e f`).cudzysłowy. Może być konieczne użycie podwójnych cudzysłowów, "jeśli masz starą dystrybucję SWI-Prolog.

Fatalizować
źródło
5

Julia, 93 92 81 bajtów

Oszczędność 10 bajtów dzięki Glenowi O.

s->for i=(p=split(s,"\n")) println(i[min([search(j,r"\S")[1]for j=p]...):end])end

To tworzy nienazwaną funkcję, która akceptuje ciąg i wypisuje na standardowe wyjście.

Niegolfowane + wyjaśnienie:

function f(s)
    # Split s into an array on newlines
    p = split(s, "\n")

    # Get the smallest amount of leading space by finding the
    # position of the first non-space character on each line
    # and taking the minimum
    m = min([search(j, r"\S")[1] for j in p]...)

    # Print each line starting after m
    for i in p
        println(i[m:end])
    end
end
Alex A.
źródło
Możesz zaoszczędzić trochę miejsca, szukając pierwszego spacji, a nie licząc liczby spacji. Zamiast tego minimum([length(search(j, r"^ +")) for j in p])+1użyj minimum([search(j,r"[^ ]")[1]for j=p]). Ponieważ wyzwanie stwierdza, że ​​wszystkie wiersze będą zawierały tekst spacji, jest bezpieczny i oszczędza 9 bajtów (w tym 3 zapisane przy użyciu =zamiast `w ). Still looking to see if more can be saved. (I wish I could drop the [1]`), ale wyszukiwanie tworzy tablicę wyliczającą typu Dowolne, podczas gdy minimum wymaga typu Int)
Glen O
Przepraszam za błąd powyżej - najwyraźniej zużyłem już swoje modyfikacje - to nie jest 9 bajtów, ale 6, ponieważ nie zauważyłem, że użyłeś = w formie golfa. W każdym razie, mogę zapisać dwa kolejne znaki, definiując p podczas uruchamiania pętli for:s->for i=(p=split(s,"\n")) println(i[minimum([search(j,r"[^ ]")[1]for j=p]):end])end
Glen O
OK, oto kolejny, który goli trochę więcej - zamiast używać minimum(x)kiedy xjest tablica, użyj min(x...), aby zaoszczędzić jeden dodatkowy bajt (dodam ten do mojej listy wskazówek golfowych Julii).
Glen O
@GlenO Nice, dzięki za sugestie. Ponadto, ponieważ Julia używa PCRE, znaki spacji można dopasować do znaków \Szamiast [^ ], co oszczędza bajt.
Alex A.,
Hej, dzięki, że o tym wspomniałeś - nie jestem dobry w wyrażeniach regularnych, ale okazało się, że \Sjest to również przydatne w moim rozwiązaniu.
Glen O
4

Java, 159

Ponieważ widoczny jest brak Java ...

void f(String...a){int s=1<<30,b;a=a[0].split("\n");for(String x:a)s=(b=x.length()-x.trim().length())<s?b:s;for(String x:a)System.out.println(x.substring(s));}

To tylko pętle porównujące długość z przyciętą długością, a następnie wypluwające podciągi. Nic nadzwyczajnego. W przypadku osób z zaburzeniami paska przewijania:

void f(String...a){
    int s=1<<30,b;
    a=a[0].split("\n");
    for(String x:a)
        s=(b=x.length()-x.trim().length())<s?b:s;       
    for(String x:a)
        System.out.println(x.substring(s));
}
Geobity
źródło
4

Perl, 47 33

Dzięki @ThisSuitIsBlackNot za sugestię użycia niejawnej pętli Perla

#!/usr/bin/perl -00p
/^( +).*(\n\1.*)*$/&&s/^$1//mg

Powyższe jest oceniane jako 30 bajtów dla linii kodu + 3 dla 00pflag.

Oryginalna wersja, jako funkcja:

sub f{$_=@_[0];/^( +).*(\n\1.*)*$/&&s/^$1//mgr}

Spowoduje to wstawienie argumentu $_, a następnie spróbuje łapczywie dopasować białe znaki występujące we wszystkich liniach z /^( +).*(\n\1.*)*$/- jeśli się powiedzie, $1zawiera teraz najdłuższy wspólny przedrostek i wykonujemy zamianęs/^$1//mgr aby usunąć go z początku każdej linii i zwrócić wynikowy ciąg.

Test

$ cat 53219.data
   a   b
     c     d
    e f
$ ./53219.pl <53219.data 
a   b
  c     d
 e f
Toby Speight
źródło
Bardzo fajny. Możesz ogolić niektóre bajty, uruchamiając w wierszu poleceń: perl -00pe '/^( +).*(\n\1.*)*$/&&s/^$1//mg'(30 bajtów + 3 za 00p).
ThisSuitIsBlackNot
/meidzie do góry -00p; dzięki @ThisSuit
Toby Speight
3

Python 2, 86 79 75 bajtów

Niemal na pewno można to trochę skrócić, ale w tej chwili nie jest źle.

Dzięki xnor za zapisanie 4 bajtów!

s=input().split('\n')
for k in s:print k[min(x.find(x.strip())for x in s):]
Kade
źródło
1
Nieco krótszym sposobem na policzenie wiodących pól jest x.find(x.strip()).
xnor
@xnor dobra rozmowa, dzięki! Cały dzień czekałem na 60-bajtowe rozwiązanie; P
Kade
input()w Python 2 dusiłby te dane.
Steven Rumbalski
@StevenRumbalski, zakładam, że dane wejściowe są otoczone cudzysłowami. Kiedyś dodawałem 2 do liczby bajtów, aby to uwzględnić, ale wiele osób mówiło, że nie muszę.
Kade,
1
Ten program jest smutny:):
HyperNeutrino
3

Rubinowy: 77 73 70 66 65 58 57 40 znaków

f=->t{t.gsub /^#{t.scan(/^ */).min}/,""}

Przykładowy przebieg:

irb(main):001:0> f=->t{t.gsub /^#{t.scan(/^ */).min}/,""}
=> #<Proc:0x00000001855948@(irb):1 (lambda)>

irb(main):002:0> puts f["   a   b\n     c     d\n    e f"]
a   b
  c     d
 e f
=> nil

irb(main):003:0> f["   a   b\n     c     d\n    e f"] == "a   b\n  c     d\n e f"
=> true
człowiek w pracy
źródło
2
Jak o f=->t{t.gsub /^#{t.scan(/^ */).min}/,""}?
Ventero,
To świetnie, @Ventero. Dziękuję Ci.
manatwork
2

C #, 18 + 145 = 163 bajty

Wymaga (18 bajtów):

using System.Linq;

Metoda (145 bajtów):

string R(string s){var l=s.Split('\n');return string.Join("\n",l.Select(x=>string.Concat(x.Skip(l.Select(z=>z.Length-z.Trim().Length).Min()))));}

Metoda oblicza najmniejszą liczbę początkowych spacji na liniach i tworzy nowy ciąg znaków zbudowany ze wszystkich linii, z pominiętymi N znakami (gdzie N jest poprzednio obliczoną liczbą).

ProgramFOX
źródło
1

C #, 149 bajtów ogółem

Praktycznie takie samo rozwiązanie jak ProgramFOX, chociaż liczba znaków do przycięcia jest obliczana ręcznie.

using System.Linq;

A sama funkcja:

string D(string s){var l=s.Split('\n');int i=0;while(l.All(a=>a[i]==' '))i++;return string.Join("\n",l.Select(b=>b.Substring(i)));}
Sok
źródło
@ProgramFOX Nie widziałem twojego rozwiązania, dopóki nie odświeżyłem strony btw: o)
Sok
1

Python 3, 100

def f(s):t=s.split("\n");return"\n".join([c[min([len(c)-len(c.lstrip(" "))for c in t]):]for c in t])
monopole
źródło
1

JavaScript, ES6, 89 86 bajtów

Ten używa wyłącznie dopasowania RegEx i podstawień.

f=x=>eval(`x.replace(/(^|\\n) {${--`
${x}`.match(/\n */g).sort()[0].length}}/g,"$1")`)

// Snippet related stuff
B.onclick=x=>P.innerHTML=f(T.value)
<textarea id=T></textarea><br>
<button id=B>Trim</button>
<pre id=P></pre>

Jak zawsze Firefox tylko od ES6. Dodaje wersję ES5 później.

Optymalizator
źródło
1
Wydaje się, że krótsze byłoby napisanie literału wyrażenia regularnego jako łańcucha, a następnie jego
ewaluacja
@ vihan1086 możesz mieć rację. Pozwól mi spróbować.
Optymalizator
1

K, 31 bajtów

{`0:(&/{(0;#*=x)@*x}'" "=x)_'x}

Pobiera wprowadzoną listę ciągów i wypisuje wynik na standardowe wyjście.

kirbyfan64sos
źródło
1

Haskell, 52 bajty

unlines.until(any(/=' ').map head)(map tail).lines

Przykład użycia: unlines.until(any(/=' ').map head)(map tail).lines $ " abc\n def\n ghi"->" abc\ndef\n ghi\n"

Jak to działa:

                                           lines    -- split the input at newlines into a list of lines
        until                                       -- repeat the 2nd argument, i.e.
                                 map tails          -- cut off the heads of all lines
                                                    -- until the the first argument returns "True", i.e.
             any(/=' ').map head                    -- the list of heads contains at least one non-space
unlines                                             -- transform back to a single string with newlines in-between
nimi
źródło
1

Python, 94/95

lambda (94 bajty):

f=lambda s:'\n'.join(l[min(l.find(l.strip()) for l in s.split('\n')):] for l in s.split('\n'))

def (95 bajtów)

def f(s):l=s.split('\n');m=min(i.find(i.strip())for i in l);return '\n'.join(i[m:] for i in l);
TheCrypt
źródło
1

bash + sed + coreutils, 74 , 56 , 55

Dane testowe

s="\
   a   b
     c     d
    e f"

Odpowiedź

cut -c$[`grep -o '^ *'<<<"$s"|sort|line|wc -c`]-<<<"$s"

Wynik

a   b
  c     d
 e f
Thor
źródło
2
A few simple golf changes brings this down to 56 in my count: cut -c$[`grep -o '^ *'<<<"$s"|sort|sed q|wc -c`]-<<<"$s"
Digital Trauma
1
@DigitalTrauma: Nice, I forgot about $[] arithmetic. Using cut for column selection is much better. I have never seen sed q as an alternative to head -n1, it is a good golfing trick. Thanks!
Thor
2
Jeśli chodzi o head -n1vs sed q, linew pakiecie util-linux znajduje się narzędzie.
manatwork
@manatwork: To oszczędza jedną postać, użyję jej. Zauważ, że jest przestarzałe i może zniknąć w przyszłości, to pochodzi z deprecated.txt w drzewie źródłowym util-linux: „Dlaczego: bezużyteczny, nikt nie używa tego polecenia, head (1) jest lepszy”.
Thor
1

R 118 111 bytes

Korzystanie ze wspaniałych funkcji łańcucha R :) Jest to podobne / takie samo jak w przypadku innych opublikowanych już rozwiązań. Wejście odbywa się poprzez STDIN, a koty do STDOUT.

cat(substring(a<-scan(,'',sep='|'),Reduce(min,lapply(strsplit(a,' '),function(x)min(which(x>''))-1))),sep='\n')

Test i wyjaśnienie

> cat(substring(a<-scan(,'',sep='|'),Reduce(min,lapply(strsplit(a,' '),function(x)min(which(x>''))-1))),sep='\n')
1:                  a<-scan(,'',sep='|') # get the input lines
2:                                                         strsplit(a,' ') # split lines on spaces
3:                                                  lapply(                ,function(x)min(which(x>''))-1) # get min index - 1 for non space of each line
4:                                      ,Reduce(min,                                                      ) # get the min of those
5:        substring(                                                                                       ) # trim it off
6:    cat(                                                                                                  ,sep='\n') # output each line
7:
Read 6 items
              a<-scan(,'',sep='|') # get the input lines
                                                     strsplit(a,' ') # split lines on spaces
                                              lapply(                ,function(x)min(which(x>''))-1) # get min index - 1 for non space of each line
                                  ,Reduce(min,                                                      ) # get the min of those
    substring(                                                                                       ) # trim it off
cat(                                                                                                  ,sep='\n') # output each line
> 
MickyT
źródło
Hey, congrats on 3k rep!
Alex A.
@AlexA. Cheers, didn't think it was important to me ... but :)
MickyT
You mean your life doesn't revolve around fake Internet points? :P
Alex A.
@AlexA. Hopefully not :) congrats on 6k
MickyT
1

Julia, 72 62 61 57 54 49 bytes

g=s->ismatch(r"^\S"m,s)?s:g(replace(s,r"^ "m,""))

Ungolfed:

g(s)=
if ismatch(r"^\S"m,s)       # Determines if there's a newline followed by something other than a space
                            # Note: the m in r"^ "m says to work in multiline mode.
    s                       # If there is, return the string as the final result.
else                        # otherwise...
    m=replace(s,r"^ "m,"")  # Remove first space after each newline, and space at start of string.
    g(m)                    # Feed back into the function for recursion
end

Older solution (57 bytes):

g(s)=ismatch(r"
\S","
"s)?s:g(replace(s,"
 ","
")[2:end])

Original solution (72 bytes):

g(s)=all([i[1]<33for i=split(s,"\n")])?g(replace(s,"\n ","\n")[2:end]):s
Glen O
źródło
1

k (24 bytes)

Takes a string as an argument and returns a string (with trailing new-line).

{`/:(&//&:'~^s)_'s:`\:x}

Example:

k) f:{`/:(&//&:'~^s)_'s:`\:x};
k) f"   a   b\n     c     d\n    e f"
"a   b\n  c     d\n e f\n
skeevey
źródło
1

05AB1E, 10 bytes

|©ζ®gð*Ûζ»

Try it online!

Mr. Xcoder
źródło
Wait, * repeats the string b a amount of times?.. Didn't knew about that feature of *. I usually do s∍ (swap and lengthen) when I want to repeat a certain character.
Kevin Cruijssen
Yes, indeed, that works for strings, mainly because vectorization doesn't quite make sense in the case of strings and и yields a list of characters.
Mr. Xcoder
0

Gawk, 101 100

{match($0,/^( +)/,t);if(t[1]<s||s==""){s=t[1]};z[NR]=$0;}END{for(r in z){sub(s,"",z[r]);print z[r]}}

For example...

cat input.txt | gawk '{match($0,/^( +)/,t);if(t[1]<s||s==""){s=t[1]};z[NR]=$0;}END{for(r in z){sub(s,"",z[r]);print z[r]}}'

Output...

a   b
  c     d
 e f
Rip Leeb
źródło
Just barely tested hints: don't capture /^( +)//^ +/ (then you will have the needed value in t[0] instead of t[1]); change s==""!s; remove the { and } around the code after if; remove the ; before }; using Gawk-specific function to be able to remove the { and } around the code after for: {sub(s,"",z[r]);print z[r]}print gensub(s,"",1,z[r]).
manatwork
Sorry to say, but both your original code and the one with my size optimization are failing on input with an unindented line, other than the last one. (For example "␠one\nzero\n␠one\n␠␠two".)
manatwork
0

C GCC, 74 Bytes

main(_,z){z=1;while(-~(_=getchar()))putchar(_==32&&z?0:(z=_==10?1:0,_));}

Only removes all whitespace, not relating to previous lines, requesting help to finish. ALSO, in terms of common whitespaces, does the OP mean that which line has the fewest leading spaces, that is the number of spaces that is to be removed from each line?

Jake
źródło
Yes, using the line with the fewest leading spaces is correct.
Sp3000
0

Stacked, noncompeting, 43 bytes

:lines'^ +'match$#'"!MIN' '*0# '^'\+''mrepl

Try it online!

This works by finding the amount of spaces at the beginning of each line ('^ +'match$#'"!), getting the minimum, repeat a space that many times, and replacing that with nothing on each line.

Conor O'Brien
źródło
0

Vim, 33, 31 bytes

qq/\v%^(\s.*\n?)*%$
:%s/.
@qq@q## Heading ##

Try it online!

Old version:

qq:g/\v%^(\s.*\n?)*%$/%s/.
n@qq@q
DJMcMayhem
źródło
-1

CoffeeScript, 112 bytes

f=(x)->(a=x.split "\n").map((v)->v[Math.min.apply(null,a.map((v)->(r=/^ +/.exec v)&&r[0].length))...]).join "\n"
rink.attendant.6
źródło
-1

JavaScript (ES6), 106 98 bytes

The newlines are necessary and are counted as 1 byte each:

f=x=>(a=x.split`
`).map(v=>v.slice(Math.min(...a.map(v=>(r=/^ +/.exec(v))&&r[0].length)))).join`
`

Demo

As with other ES6 answers, they only work in Firefox at the moment.

f=x=>(a=x.split`
`).map(v=>v.slice(Math.min(...a.map(v=>(r=/^ +/.exec(v))&&r[0].length)))).join`
`

// For demonstration purposes
console.log = x => X.innerHTML += x + `\n<hr>`;

console.log(f("a"));
console.log(f("   abc"));
console.log(f("   abc\n def\n  ghi"));
console.log(f("    a\n    b\n    c"));
console.log(f("    a\n    b\n    c\nd"));
console.log(f("   a   b\n     c     d\n    e f"));
<pre id=X></pre>

rink.attendant.6
źródło
11
It would be great if the downvoter could explain…
rink.attendant.6
-1

JavaScript ES6, 85 bytes

s=>s.split`
`.map(z=>z.slice(Math.min(...s.match(/^ */gm).map(l=>l.length)))).join`
`

The new lines are significant

ES5 Demo:

function t(s) {
  return s.split("\n").map(function(z) {
    return z.slice(Math.min.apply(0, s.match(/^ */gm).map(function(l) {
      return l.length;
    })));
  }).join('');
}

// Demo
document.getElementById('go').onclick = function() {
  document.getElementById('r').innerHTML = t(document.getElementById('t').value)
};
Input:
<br>
<textarea id="t"></textarea>
<br>
<button id="go">Run</button>
<br>Output:
<br>
<pre style="background-color:#DDD;" id="r"></pre>

Downgoat
źródło
-1

JavaScript (ES6) 56

Recursive, trying to remove one space at a time from each row until a non-space is found.

Test running the snippet below - being ES6, Firefox only

f=s=>(r=s.replace(/^./gm,x=>(k|=x>' ',''),k=0),k?s:f(r))

// Test
test=
[[ "a", "a" ]
,["   abc", "abc" ]
,["   abc\n def\n  ghi", "  abc\ndef\n ghi" ]
,["    a\n    b\n    c", "a\nb\nc" ]
,["    a\n    b\n    c\nd", "    a\n    b\n    c\nd" ]
,["   a   b\n     c     d\n    e f","a   b\n  c     d\n e f" ]]

var tb=''
test.forEach(t=>{
  t[2]=f(t[0])
  t[3]=t[2]==t[1]?'OK':'FAIL'
  tb+='<tr><td>'+t.join('</td><td>')+'</td></tr>'
})
B.innerHTML=tb
td { white-space: pre; font-family: monospace; border: 1px solid#444; vertical-align:top}
#I,#O { height:100px; width: 200px }
<b>Your test:</b>
<table><tr><td><textarea id=I></textarea></td>
<th><button onclick='O.innerHTML=f(I.value)'>-></button></th>
<td id=O></td></tr></table>
<b>Test cases:</b><br>
<table ><tr><th>Input</th><th>Expected</th><th>Output</th><th>Result</th></tr>
<tbody id=B></tbody></table>

edc65
źródło