Supprimer les espaces de tête communs

19

Lors du codage en Python, parfois vous voulez une chaîne multiligne dans une fonction, par exemple

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

(La barre oblique inverse consiste à supprimer une nouvelle ligne principale)

sCependant, si vous essayez d'imprimer , vous obtiendrez

    Line 1
    Line 2
    Line 3

Ce n'est pas du tout ce que nous voulons! Il y a trop d'espace blanc de premier plan!

Le défi

Étant donné une chaîne multiligne composée uniquement de caractères alphanumériques, d'espaces et de retours à la ligne, supprimez tous les espaces communs au début de chaque ligne. Chaque ligne est garantie d'avoir au moins un caractère non-espace et n'aura aucun espace de fin. La sortie peut ne pas avoir d'espace blanc superflu, que ce soit avant ou après la sortie entière ou une ligne individuelle (à l'exception d'une seule nouvelle ligne de fin facultative).

L'entrée peut être via STDIN ou un argument de fonction, et la sortie peut être via STDOUT ou une valeur de retour de fonction. Vous ne pouvez pas utiliser de commandes intégrées conçues pour déduire des chaînes multilignes ou effectuer cette tâche exacte, par exemple Python textwrap.dedent.

Il s'agit de , donc la solution dans le moins d'octets est gagnante. Des échappatoires standard s'appliquent.

Cas de 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"

Par exemple, le dernier cas de test est

   a   b
     c     d
    e f

et devrait ressembler à ceci après avoir supprimé les espaces de tête:

a   b
  c     d
 e f
Sp3000
la source
La sortie peut-elle avoir un espace de fin?
orlp
@orlp Non, ce ne sera pas le cas, clarifiera.
Sp3000

Réponses:

12

CJam, 20 14 octets

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

Algorithme :

  • Nous divisons d'abord l'entrée sur les nouvelles lignes et prenons une copie ( qN/_)
  • Ensuite, la plus petite colonne avec un caractère non espace est calculée en transposant le tableau séparé par des sauts de ligne et en recherchant simplement l'index de la première ligne sans espace ( z{S-}#)
  • Ensuite, nous supprimons simplement autant de caractères de chaque ligne ( f>)
  • Enfin, nous rejoignons par la nouvelle ligne ( N*)

Expansion du code

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

Essayez-le en ligne ici

Optimiseur
la source
8

Pyth, 19 18 17 14 octets

jbu>R!rhCG6G.z

L'implémentation est plutôt cool.

  1. u .zsaisit toutes les lignes de stdin dans un tableau, le place G. Ensuite, il évalue le corps intérieur, met le résultat Get continue de le faire jusqu'à ce qu'il ne change plus (point fixe).

  2. !rhCG6transpose G, obtient le premier élément du tableau transposé (la première colonne), le supprime de tout espace blanc et vérifie s'il reste des caractères non blancs.

  3. La valeur de 2 est un booléen, qui peut être vu comme un entier 0 ou 1. >R Gsaisit ce nombre et tranche ce nombre de caractères à gauche de chaque ligne G. Les étapes 1, 2 et 3 combinées signifient essentiellement qu'il continuera à supprimer les colonnes d'espaces blancs jusqu'à ce qu'il ne reste plus de colonne d'espaces purs.

  4. jb joint le tableau de lignes par des retours à la ligne et l'imprime.

orlp
la source
2
Pouvez-vous s'il vous plaît donner une petite explication à cela? C'est très bizarre pour moi!
bobbel
2
@bobbel Explication ajoutée.
orlp
Vraiment super, merci! Je n'en ai jamais entendu parler! Pour l'essayer en ligne, j'ai trouvé: pyth.herokuapp.com/…
bobbel
8

sed - 26 octets

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

courir avec -rz

Assez simple:

  /(^|\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

-r l'option active les expressions régulières étendues, -z lit l'entrée entière comme une seule chaîne (utilise en fait l'octet NUL comme délimiteur de ligne)

aragaer
la source
N'avez-vous pas besoin :;N;$!bou similaire pour commencer, pour rassembler les lignes d'entrée dans un espace de motif unique? Edit: non, vous ne le faites pas; c'est à ça que -zsert le drapeau.
Toby Speight
Vous pouvez :;/^\S/M!s/^ //mg;t-r
jouer au
7

SWI-Prolog, 233 223 217 octets

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);!.

Edit : complètement changé ma réponse. Il utilise désormais des codes de caractères au lieu de chaînes.

Un exemple d'appeler cela serait a(` a b\n c d\n e f`)., avec des guillemets. Vous devrez peut-être utiliser des guillemets doubles à la "place si vous avez une ancienne distribution SWI-Prolog.

Fatalize
la source
5

Julia, 93 92 81 octets

10 octets enregistrés grâce à Glen O.

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

Cela crée une fonction sans nom qui accepte une chaîne et s'imprime sur stdout.

Non golfé + explication:

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.
la source
Vous pouvez économiser de l'espace en recherchant le premier non-espace, plutôt qu'en comptant le nombre d'espaces. Plutôt que d' minimum([length(search(j, r"^ +")) for j in p])+1utiliser minimum([search(j,r"[^ ]")[1]for j=p]). Étant donné que le défi indique que toutes les lignes auront du texte non-espace, il est sûr et vous économise 9 octets (dont 3 enregistrés en utilisant =au lieu de `dans ). Still looking to see if more can be saved. (I wish I could drop the [1]`, mais la recherche produit un tableau d'énumérateur de type Any, tandis que le minimum requiert un type Int)
Glen O
Excusez l'erreur ci-dessus - apparemment, j'ai utilisé mes modifications - ce n'est pas 9 octets, mais 6, parce que je n'ai pas noté que vous aviez utilisé = dans la forme golfée. Quoi qu'il en soit, je peux enregistrer deux autres caractères en définissant p en démarrant la boucle for:s->for i=(p=split(s,"\n")) println(i[minimum([search(j,r"[^ ]")[1]for j=p]):end])end
Glen O
OK, voici un autre pour raser un peu plus - plutôt que d'utiliser minimum(x)quand xun tableau, utilisez min(x...)-le pour un octet supplémentaire enregistré (je vais ajouter celui-ci à ma liste de conseils de golf Julia).
Glen O
@GlenO Nice, merci pour les suggestions. De plus, comme Julia utilise PCRE, les caractères non-espace peuvent être mis en correspondance avec \Splutôt que [^ ], ce qui économise un octet.
Alex A.
Hé, merci d'avoir mentionné cela - je ne suis pas bon avec l'expression régulière, mais il s'est avéré que cela \Sest également utile pour ma solution.
Glen O
4

Java, 159

Parce qu'il y a un manque flagrant de 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));}

C'est juste des boucles comparant la longueur à la longueur coupée, puis crachant des sous-chaînes. Rien d'extraordinaire. Pour les personnes déficientes avec la barre de défilement:

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));
}
Géobits
la source
4

Perl, 47 33

Merci @ThisSuitIsBlackNot pour la suggestion d'utiliser la boucle implicite de Perl

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

Ce qui précède est marqué comme 30 octets pour la ligne de code + 3 pour les 00pdrapeaux.

Version originale, en fonction:

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

Cela place l'argument dans $_, puis tente de faire correspondre avec avidité les espaces blancs présents sur toutes les lignes avec /^( +).*(\n\1.*)*$/- en cas de succès, $1contient maintenant le préfixe commun le plus long, et nous exécutons le remplacements/^$1//mgr pour le supprimer du début de chaque ligne et renvoyer la chaîne résultante.

Tester

$ cat 53219.data
   a   b
     c     d
    e f
$ ./53219.pl <53219.data 
a   b
  c     d
 e f
Toby Speight
la source
Très cool. Vous pouvez raser certains octets en exécutant sur la ligne de commande: perl -00pe '/^( +).*(\n\1.*)*$/&&s/^$1//mg'(30 octets + 3 pour 00p).
ThisSuitIsBlackNot
/mese dirige vers le haut -00p; merci @ThisSuit
Toby Speight
3

Python 2, 86 79 75 octets

Cela peut presque certainement être raccourci un peu plus, mais pour l'instant ce n'est pas mauvais.

Merci à xnor pour avoir économisé 4 octets!

s=input().split('\n')
for k in s:print k[min(x.find(x.strip())for x in s):]
Kade
la source
1
Une façon légèrement plus courte de compter les espaces de tête est x.find(x.strip()).
xnor
@xnor bon appel, merci! J'ai attendu une solution de 60 octets de votre part toute la journée; P
Kade
input() en Python 2 s'étoufferait avec ces données.
Steven Rumbalski
@StevenRumbalski, je suppose que l'entrée est entourée de guillemets. J'avais l'habitude d'ajouter 2 au nombre d'octets pour tenir compte de cela, mais plusieurs personnes ont dit que je n'en avais pas besoin.
Kade
1
Ce programme est triste:):
HyperNeutrino
3

Ruby: 77 73 70 66 65 58 57 40 caractères

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

Exemple d'exécution:

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
homme au travail
la source
2
Et alors f=->t{t.gsub /^#{t.scan(/^ */).min}/,""}?
Ventero
C'est super, @Ventero. Je vous remercie.
manatwork
2

C #, 18 + 145 = 163 octets

Requiert (18 octets):

using System.Linq;

Méthode (145 octets):

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()))));}

La méthode calcule le plus petit nombre d'espaces de tête sur les lignes et crée une nouvelle chaîne construite à partir de toutes les lignes, avec N caractères ignorés (où N est le nombre calculé précédemment).

ProgramFOX
la source
1

C #, 149 octets au total

Pratiquement la même solution que celle de ProgramFOX, bien que le nombre de caractères à découper soit calculé manuellement.

using System.Linq;

Et la fonction elle-même:

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
la source
@ProgramFOX Je n'avais pas vu votre solution avant d'avoir rafraîchi la page 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
la source
1

JavaScript, ES6, 89 86 octets

Celui-ci utilise totalement la correspondance et les substitutions RegEx.

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>

Comme toujours, Firefox uniquement, depuis ES6. Ajoutera la version ES5 plus tard.

Optimiseur
la source
1
Il semble que ce serait plus court d'écrire un littéral d'expression régulière sous forme de chaîne, puis de l'
évaluer
@ vihan1086 vous avez peut-être raison. Permettez-moi de l'essayer.
Optimizer
1

K, 31 octets

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

Prend en entrée une liste de chaînes et imprime le résultat sur stdout.

kirbyfan64sos
la source
1

Haskell, 52 octets

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

Exemple d'utilisation: unlines.until(any(/=' ').map head)(map tail).lines $ " abc\n def\n ghi"->" abc\ndef\n ghi\n"

Comment ça fonctionne:

                                           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
la source
1

Python, 94/95

lambda (94 octets):

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

déf (95 octets)

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
la source
1

bash + sed + coreutils, 74 , 56 , 55

Données de test

s="\
   a   b
     c     d
    e f"

Répondre

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

Production

a   b
  c     d
 e f
Thor
la source
2
Quelques changements de golf simples ramènent cela à 56 dans mon décompte:cut -c$[`grep -o '^ *'<<<"$s"|sort|sed q|wc -c`]-<<<"$s"
Digital Trauma
1
@DigitalTrauma: Nice, j'ai oublié l' $[]arithmétique. L'utilisation cutde la sélection de colonnes est bien meilleure. Je n'ai jamais vu sed qd'alternative à head -n1, c'est un bon truc de golf. Merci!
Thor
2
Concernant head -n1vs sed q, il y a un lineoutil dans le paquet util-linux.
manatwork
@manatwork: Cela enregistre un caractère, je vais l'utiliser. Notez qu'il est obsolète et pourrait disparaître à l'avenir, cela provient de deprecated.txt dans l'arborescence source d'util-linux: "Pourquoi: inutile, personne n'utilise cette commande, head (1) est mieux".
Thor
1

R, 118 111 octets

Utilisation des merveilleuses fonctions de chaîne de R :) Ceci est similaire / identique aux autres solutions déjà publiées. L'entrée se fait via STDIN et les chats vers STDOUT.

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

Test et explication

> 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
la source
Hé, félicitations pour 3k rep!
Alex A.
@AlexA. À la vôtre, je ne pensais pas que c'était important pour moi ... mais :)
MickyT
Vous voulez dire que votre vie ne tourne pas autour de faux points Internet? : P
Alex A.
@AlexA. Espérons que non :) félicitations pour 6k
MickyT
1

Julia, 72 62 61 57 54 49 octets

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

Non golfé:

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

Ancienne solution (57 octets):

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

Solution d'origine (72 octets):

g(s)=all([i[1]<33for i=split(s,"\n")])?g(replace(s,"\n ","\n")[2:end]):s
Glen O
la source
1

k (24 octets)

Prend une chaîne comme argument et renvoie une chaîne (avec une nouvelle ligne de fin).

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

Exemple:

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
la source
1

05AB1E , 10 octets

|©ζ®gð*Ûζ»

Essayez-le en ligne!

M. Xcoder
la source
Attendez, *répète la chaîne b un certain nombre de fois? .. Ne connaissait pas cette fonctionnalité de *. Je fais d'habitude s∍(permuter et allonger) quand je veux répéter un certain caractère.
Kevin Cruijssen
Oui, en effet, cela fonctionne pour les chaînes, principalement parce que la vectorisation n'a pas vraiment de sens dans le cas des chaînes et иproduit une liste de caractères.
M. 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]}}

Par exemple...

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]}}'

Production...

a   b
  c     d
 e f
Rip Leeb
la source
Astuces à peine testées: ne capturez pas /^( +)//^ +/(vous aurez alors la valeur nécessaire à la t[0]place de t[1]); changer s==""!s; supprimer le {et }autour du code après if; supprimer l' ;avant }; en utilisant la fonction spécifique à Gawk pour pouvoir supprimer le {et }autour du code après for: {sub(s,"",z[r]);print z[r]}print gensub(s,"",1,z[r]).
manatwork
Désolé de le dire, mais votre code d'origine et celui avec mon optimisation de taille échouent à la saisie avec une ligne non indentée, autre que la dernière. (Par exemple "␠one \ nzero \ n␠one \ n␠␠two".)
manatwork
0

C GCC, 74 octets

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

Supprime uniquement tous les espaces, sans rapport avec les lignes précédentes, demandant de l'aide pour terminer. ÉGALEMENT, en termes d'espaces communs, le PO signifie-t-il que la ligne a le moins d'espaces de tête, c'est-à-dire le nombre d'espaces à supprimer de chaque ligne?

Jake
la source
Oui, utiliser la ligne avec le moins d'espaces de tête est correct.
Sp3000
0

Empilé , non concurrentiel, 43 octets

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

Essayez-le en ligne!

Cela fonctionne en trouvant la quantité d'espaces au début de chaque ligne ( '^ +'match$#'"!), en obtenant le minimum, en répétant un espace autant de fois et en le remplaçant par rien sur chaque ligne.

Conor O'Brien
la source
0

Vim, 33 , 31 octets

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

Essayez-le en ligne!

Ancienne version:

qq:g/\v%^(\s.*\n?)*%$/%s/.
n@qq@q
DJMcMayhem
la source
-1

CoffeeScript, 112 octets

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
la source
-1

JavaScript (ES6), 106 98 octets

Les sauts de ligne sont nécessaires et comptent pour 1 octet chacun:

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

Démo

Comme pour les autres réponses ES6, elles ne fonctionnent que dans Firefox pour le 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
la source
11
Ce serait génial si le downvoter pouvait expliquer…
rink.attendant.6
-1

JavaScript ES6, 85 octets

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

Les nouvelles lignes sont significatives

Démo ES5:

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
la source
-1

JavaScript ( ES6 ) 56

Récursif, essayant de supprimer un espace à la fois de chaque ligne jusqu'à ce qu'un non-espace soit trouvé.

Testez l'exécution de l'extrait ci-dessous - étant ES6, Firefox uniquement

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
la source