Convertir une table d'art ASCII en table UTF-8

13

Quand j'écris de la documentation, des commentaires, etc. J'adore créer des tableaux ASCII. Ils finissent généralement par être assez beaux, mais j'ai toujours l'impression qu'ils pourraient être encore meilleurs - d'autant plus que UTF-8 / Unicode inclut les personnages de dessin de boîte . Cependant, ces caractères sont très lourds à utiliser, nécessitant plusieurs appuis sur les touches pour être insérés. Ta tâche? Écrivez un programme ou une fonction qui peut convertir automatiquement les tables ASCII en équivalent UTF-8 / Unicode.

Ce défi a été mis en bac à sable .

Défi

Écrire un programme, qui, étant donné une table ASCII en tant que chaîne d'entrée, génère la table redessinée avec les caractères de dessin de boîte Unicode / UTF-8. Plus précisément, les caractères qui font partie du tableau doivent être traduits comme suit:

(Unicode, 3 bytes each in UTF-8)
- to ─ (\u2500)
| to │ (\u2502)
= to ═ (\u2550)

and + to one of:
   ┌ (\u250C), ┐ (\u2510), └ (\u2514), ┘ (\u2518),
   ├ (\u251C), ┤ (\u2524), ┬ (\u252C), ┴ (\u2534),
   ┼ (\u253C)
or, if '=' on either side:
   ╒ (\u2552), ╕ (\u2555), ╘ (\u2558), ╛ (\u255D),
   ╞ (\u255E), ╡ (\u2561), ╤ (\u2564), ╧ (\u2567),
   ╪ (\u256A)

Détails

E / S:

  • Les E / S par défaut sont autorisées
  • Vous pouvez prendre des entrées dans n'importe quel format raisonnable, y compris la table sous forme de chaîne ou un chemin d'accès à un fichier contenant la table.
  • Vous pouvez exporter vers un fichier et prendre le nom de fichier comme argument supplémentaire.
    • Cependant, vous ne pouvez pas modifier le fichier d'entrée . (Il doit être conservé pour faciliter la modification future)

Contribution:

  • Vous pouvez supposer que chaque ligne d'entrée a été complétée pour avoir la même longueur avec .
  • Vous ne pouvez pas supposer que le premier caractère après une nouvelle ligne fait partie des bordures du tableau (car il peut s'agir d'espaces).
  • L'entrée est considérée comme une table valide si tous les caractères (qui font partie de la table) -=|sont connectés à exactement deux caractères et+ sont connectés à au moins un caractère à la fois horizontalement et verticalement.
  • Votre programme peut ne produire aucune erreur avec des entrées valides.
  • Si l'entrée n'est pas valide, le comportement n'est pas défini et vous pouvez produire n'importe quelle sortie.
  • L'entrée peut contenir tous les caractères UTF-8, y compris les caractères de dessin de boîte.

Production:

  • N'importe lequel des personnages -=|+ qui ne font pas partie du tableau doivent être laissés tels quels.
  • De même, tous les autres caractères doivent être laissés tels quels.
  • Une seule nouvelle ligne de début et / ou de fin est autorisée.

Autre:

  • Failles standard sont interdites, comme d'habitude.
  • Si votre langue préférée a une fonction intégrée qui résout ce problème, vous ne pouvez pas l'utiliser.
    • Cela signifie des programmes, des fonctions, des sous-programmes ou des instructions qui seraient des soumissions valides pour ce défi sans ajouts.
  • Chacun des caractères nécessaires à ce défi fait trois octets de long lorsqu'ils sont encodés en UTF-8.

Personnages connectés :

Un personnage est connecté à un autre si:

  • Il est |et est directement au-dessus ou en dessous +ou |;
  • Il est -et est directement avant ou après +ou -;
  • Il est =et est directement avant ou après +ou =;
  • Il est +et est directement au-dessus ou au-dessous de |ou +, ou est directement avant ou après -, =ou +.

Un caractère est considéré comme faisant partie de la table s'il est connecté à n'importe quel caractère faisant partie de la table. Par définition, le premier +de l'entrée fait partie du tableau.

Exemples

Exemples disponibles ici en version copiable-collable.

 Input:                    Output:
+------------------+      ┌──────────────────┐
|   Hello+World!   |      │   Hello+World!   │
+==================+      ╞══════════════════╡
| This is+my first |  ->  │ This is+my first │
|+-+ code|golf  +-+|      │+-+ code|golf  +-+│
|+-+chall|enge! +-+|      │+-+chall|enge! +-+│
+------------------+      └──────────────────┘

     +===+===+===+             ╒═══╤═══╤═══╕
     | 1 | 2 | 3 |             │ 1 │ 2 │ 3 │
 +---+===+===+===+         ┌───╪═══╪═══╪═══╡
 | 1 | 1 | 2 | 3 |         │ 1 │ 1 │ 2 │ 3 │
 +---+---+---+---+    ->   ├───┼───┼───┼───┤
 | 2 | 2 | 4 | 6 |         │ 2 │ 2 │ 4 │ 6 │
 +---+---+---+---+         ├───┼───┼───┼───┤
 |-3 |-3 |-6 |-9 |         │-3 │-3 │-6 │-9 │
 +===+---+---+---+         ╘═══╧───┴───┴───┘

      +-----+         ->      <Undefined>

      +-----+         ->      ┌─────┐
      +-----+                 └─────┘

+-----------------+
|  Hello, World!  |
| This is invalid |   ->      <Undefined>
|      input      |
 -----------------+

       ++++                      ┌┬┬┐
       ++++           ->         ├┼┼┤
       ++++                      └┴┴┘

       +--+
       ++++           ->      <Undefined>
       +--+

Finalement...

Il s'agit de , donc le moins d'octets gagne. Bon golf!


la source
Dans le premier exemple, pourquoi les +-+extraits consécutifs ne sont-ils pas considérés comme formant une table connectée?
récursif
Si une fonction peut-être 16 bits utilise un seul octet pour représenter ╡, comment est le nombre d'octets?
l4m2
@recursive Si vous parlez du premier Hello Worldtableau, les tableaux intérieurs ne sont pas considérés comme formant un tableau car le texte à l'intérieur du tableau doit rester inchangé et ils ne sont pas considérés comme faisant partie des bordures extérieures du tableau car ils ne sont pas correctement connectés.
Si vous voulez dire le premier +----+exemple, ce serait parce que la direction des coins serait ambiguë.
1
Oh, l'exigence "pas de tables dans les tables à moins qu'elles ne se connectent pour étendre la table la plus externe possible" rend cela beaucoup plus difficile.
Jonathan Allan

Réponses:

2

Python 3, 392 281 octets

Il l'a joué un peu plus et converti en une solution récursive au lieu d'une solution itérative:

def h(a):
 def g(i):
  k=-3;d=a[i]=='=';z[i]=''
  for p,j,r in zip((1,2,4,8),(i+1,i+w,i-1,i-w),('+-=','+|')*2):
   if 0<=j<len(a)and{a[i],a[j]}<={*r}:k+=p;d|=a[j]=='=';z[j]and g(j)
  z[i]="┌╒!!─═┐╕┬╤@@└╘││├╞┘╛┴╧┤╡┼╪"[2*k+d]
 w=a.find('\n')+1;z=[*a];g(a.find('+'))
 return''.join(z)

Prend une chaîne de lignes de longueur égale séparées par des retours à la ligne et renvoie une chaîne au même format. Peut lever une exception sur une entrée non valide.

Solution précédente:

def h(a):
 i=a.find('+');q=[i];w=a.find('\n')+1;z=[*a]
 while q:
  i=q.pop();c=a[i];k=-5
  for p,j in enumerate((i+1,i-1,i+w,i-w)):
   r='++-|='[p>1::2]
   if 0<=j<len(a)and a[i]in r and a[j]in r:
    k+=1<<p;q+=[j][:z[j]<'─']
  z[i]='│'if c>'='else'─═'[a[i]>'-']if c>'+'else"┌╒┐╕┬╤@@└╘┘╛┴╧##├╞┤╡┼╪$$"['='in a[abs(i-1):i+2]::2][k]
 return''.join(z)

Version non golfée:

def h(a):
    i = a.find('+')         # find index of first '+'. It is first node
    q = [i]                 # in the queue of indexes to convert to unicode
    w = a.find('\n')+1      # width of the table
    z = [*a]                # strings are immutable, so copy it to a list

    while q:                # while the queue isn't empty
        i=q.pop()           # get the next index to process
        c=a[i]              # and the associated character

        k=-5                # 'k' is the index into the unicode string, U.  The way they
                            # are encoded, the first unicode value is at index 5. 

                 # directions  E   W   S   N
        for p,j in enumerate((i+1,i-1,i+w,i-w)):  # j is the index of an adjacent cell

            # r='++-|='[p>1::2] is equivalent to:
            if p > 1:
                r = '+|'    # compatible symbols for vertical connections
            else:
                r = '+-='   # compatible symbols for horizontal connections

            # if adjacent cell index is valid and the characters are compatible
            if 0 <= j < len(a) and a[i] in r and a[j] in r:
                k += 1<<p                 # update the unicode symbol index

                # q += [j][:z[j]<'─'] is equivalent to:
                if z[j] < '-':            # if the adjacent cell hasn't been converted already
                    q.append(j)           #  append it's index to the queue

            if c > '=':
                z[i] = '│'                # replace a '|' with a '│'

            elif c > '+':
                z[i] = '─═'[a[i]>'-']     # replace a '-' or '=' with '─' or '═' respectively 

            else:                                      # it's a '+'
                U = "┌╒┐╕┬╤@@└╘┘╛┴╧##├╞┤╡┼╪$$"         # even indexes are single horizontal line, 
                                                       # double horizontal lines are at odd indexes

                z[i] = U['='in a[abs(i-1):i+2]::2][k]  # '='in a[abs(i-1):i+2] is true if there is an '=' to the left or right
                                                       # so this selects the odd chars from U
                                                       #  then [k] selects the correct char

 return''.join(z)
RootTwo
la source
3

Python 3 , 914 898 827 823 594 587 569 540 469 octets

Edit: stratégie considérablement modifiée, créant désormais un champ de bits de voisins (similaire à la réponse de Dead-Possum). J'ai laissé la version précédente ci-dessous.

H='─│═-|=└╘++++┌╒├╞++┘╛++┴╧┐╕┤╡┬╤┼╪'
def n(l):
 def j(r,c,t=0):O=(0,r-1,c),(1,r,c+1),(2,r+1,c),(3,r,c-1);v=f(r,c);b=t|any(f(Y,X)=='='for i,Y,X in O);l[r][c]={'+':H[b+2*sum((f(Y,X)in H)<<x for x,Y,X in O)],**dict(zip(H[3:6],H))}.get(v,v);[f(Y,X)!=';'and v in'+++-|='[i%2::2]and j(Y,X,v=='=')for i,Y,X in O]
 for i,I in enumerate(l):
  if'+'in I:f=lambda r,c:l[r][c]if len(l)>r>=0and 0<=c<len(l[r])else';';j(i,I.index('+'));break

Essayez-le en ligne!

L'entrée se présente sous la forme d'une liste de listes de caractères, qui est modifiée sur place. Récursif du premier + qu'il trouve.

x=range
C='┌┐└┘','╒╕╘╛'
D='┬┤┴├','╤╡╧╞'
A='┼╪'
H,V,T='─│═'
J={'-':H,'|':V,'=':T}
K=C[1]+D[1]+A[1]+'='+T
E=('+|','+-=')*2
F=['+|'+V,'+-='+H+T]*2
O=(0,-1,0),(1,0,1),(2,1,0),(3,0,-1)
for i in x(4):
 for j in{0,1,2,3}-{i}:F[i+2&3]+=D[0][j]+D[1][j]
 h=C[0][i]+C[1][i];F[i&2]+=h;F[3-2*(i&1)]+=h
def n(l):
 for i,I in enumerate(l):
  if'+'in I:r=i;c=I.index('+');break
 else:return l
 def f(r,c):
  try:assert c>=0 and r>=0;return l[r][c]
  except:return'\0'
 def j(r,c):
  v=f(r,c)
  l[r][c]=J.get(v,v)
  if v=='+':
   X=[f(r+Y,c+X)for i,Y,X in O];B=any(x in K for x in X);X=[X[x]in F[x]for x in x(4)];L=sum(X)
   if L in(2,3,4):l[r][c]=D[B][X.index(False)]if L==3 else C[B][X[0]*2+X[3]]if L==2 else A[B]
  for i,Y,X in O:
   if v in E[i]and f(r+Y,c+X)in E[i]:j(r+Y,c+X)
 j(r,c);return l

Essayez-le en ligne!

Voici la chose la plus proche que j'ai à une version non golfée:

def tr(s):
    t='┌┐└┘','╒╕╘╛'
    t2='┬┤┴├','╤╡╧╞'
    A = '┼','╪'
    H,V,T = '─│═'
    Th = ''.join(x[1]for x in (t,t2,A))+'='+T
    ps = ['+|'+V, '+-='+H+T, '+|'+V, '+-='+H+T]
    ps2 = ('+|', '+-=')*2
    for i in range(4):
        for j in {0,1,2,3}-{i}:
            ps[(i+2)%4] += t2[0][j]+t2[1][j]
        h=t[0][i] + t[1][i]
        ps[i & 2] += h
        ps[3 - 2 * (i & 1)] += h

    l = [list(x) for x in s.split('\n')]
    r = 0
    for i,I in enumerate(l):
        if'+'in I:
            r=i;c=I.index('+')
            break
    def g(r,c): return l[r][c]
    def G(r,c):
        if r >= 0 and r < len(l) and c >= 0 and c < len(l[r]):
            return g(r,c)
        return '\0'
    def process(r,c):
        v = g(r,c)
        if v == '-': l[r][c] = H
        elif v == '|': l[r][c] = V
        elif v == '=': l[r][c] = T
        elif v == '+':
            all=[G(r-1,c),G(r,c+1),G(r+1,c),G(r,c-1)]
            bold=any(x in Th for x in all)
            for i in range(4):all[i] = all[i] in ps[i]
            N,E,S,W=all
            tt=sum(all)
            if tt == 3:
                l[r][c]=t2[bold][all.index(False)]
            elif tt == 2:
                l[r][c]=t[bold][N*2+W]
            elif tt == 4:
                l[r][c]=A[bold]
            else: return
        for i,(dy,dx) in enumerate(((-1,0),(0,1),(1,0),(0,-1))):
            if v in ps2[i] and G(r+dy,c+dx) in ps2[i]:
                process(r+dy,c+dx)
    process(r,c)
    return l
pbfy0
la source
Améliorations mineures pour économiser 9 octets (jusqu'à 814) bit.ly/2NOu7HF
mypetlion
Moins pour un autre 9 octets (805 octets maintenant) bit.ly/2pYom0x
mypetlion
Jusqu'à
mypetlion
1

JavaScript, 311 307 octets

X=>(O=[...X],P=(I,j=0,_=0)=>!P[I]&&(P[I]=1,['-─1','|│','=═1'].map(([a,b,n=X.indexOf('\n')+1])=>[-n,+n].map(n=>{for(i=I;X[i+=n]==a;)O[i]=b
if(X[i]=='+')j|=[1,2,4,8,I-i>1&&17,i-I>1&&18][_],P(i)
_++})),O[I]='┘└┴ ┐┌┬ ┤├┼     ╛╘╧ ╕╒╤ ╡╞╪'[j-5]),P(X.indexOf`+`),O.join``)

Explication

À partir de la première +jonction trouvée , le programme tente de trouver des chemins vers d'autres jonctions dans toutes les directions, effectuant des remplacements au fur et à mesure. Il stocke les directions trouvées et l'état "à double bordure" dans une image bitmap, qui détermine ensuite le caractère de jonction approprié.

// Take an input string X
f = X => {
    // Copy the input string into an array so characters can be overwritten and eventually output
    O = [...X]

    // Define a function that processes a junction ("+" symbol) at index I in the input string X:
    P = I => {
        // Make a bitmap to keep track of the direction coming out of the junction and double borders
        // Bits from right to left: west, east, north, south, double border
        // E.g. a double-bordered south/east junction corresponds to the binary number 11010 ("╒")
        let j = 0

        // A counter
        let _ = 0

        // Ensure this junction hasn't already been processed
        if(!P[I]){
            P[I] = 1,

            // We'll walk away from the junction in each of the four directions, then west and east again to check for double borders
            // i.e. walk along `a`, replace with `b`, move index `i` by `n`
            // 1st pass: walk along "-", replace with "─", move index by 1
            // 2nd pass: walk along "|", replace with "│", move index by the width of the input (plus 1 for the newline) to traverse vertically
            // 3rd pass: walk along "=", replace with "═", move index by 1
            ['-─1','|│','=═1'].map(([a, b, n = X.indexOf('\n') + 1])=>
                // We'll walk in the negative and positive directions for each pass
                [-n,+n].map(n=>{
                    // Start the walk
                    i=I
                    // Keep walking (incrementing by n) as long as we're on a "path" character, "a"
                    while(i += n, X[i] == a)
                        // Replace the corresponding character in the output with "b"
                        O[i] = b

                    // Upon reaching another junction at index i:
                    if(X[i] == '+'){
                        // OR the bitmap according to the direction we walked
                        j |= [
                            // Pass 1: Horizontal
                            1, // west
                            2, // east

                            // Pass 2: Vertical
                            4, // north
                            8, // south

                            // Pass 3: Double Horizontal (only if we've walked more than 1 step)
                            I-i > 1 && 17, // west, double border
                            i-I > 1 && 18 // east, double border
                        ][_]

                        // Process the junction we walked to
                        P(i)
                    }
                    _++
                })
            )

            // Finally, replace the "+" with a proper junction character based on the bitmap value
            O[I] = '     ┘└┴ ┐┌┬ ┤├┼     ╛╘╧ ╕╒╤ ╡╞╪'[j]
        }
    }

    // Process the first junction to kick off the recursion
    P(X.indexOf`+`)

    // Return our modified character array as a joined string
    return O.join``
}
darrylyeo
la source
Corrigé - Je devais avoir regardé le nombre de caractères et non le nombre d'octets.
darrylyeo
1

Python 3 , 599 octets

Je ne suis pas vraiment bon au golf en Python 3, mais (à ma honte) je n'ai pas pu obtenir une sortie normale de caractères UTF-8 en Python 2. Alors nous y sommes.

Je suppose que la seule astuce intéressante ici est de décider + transformation.
J'ai encodé toutes les variantes possibles avec des adresses 4 bits. Chaque bit d'adresse ressemble à la connexion à la cellule voisine. Donc 0 - pas de connexion et 1 - connexion.
1111is
0011is
etc
Certaines configurations de connexions ne sont pas valides et sont remplacées par des valeurs fictives:'012┐45┌┬8┘0┤└┴├┼'

Si une cellule voisine contient =, la deuxième liste sera utilisée avec des lignes doublées.

['012┐45┌┬8┘0┤└┴├┼','012╕45╒╤8╛0╡╘╧╞╪']['='in r]

L'adresse est combinée ici.

r=''.join([str(int(V(y,x)))+W(y,x)for y,x in[(Y-1,X),(Y,X+1),(Y+1,X),(Y,X-1)]])

rcontient une longueur de chaîne 8, où tous les deux caractères sont 1/0 et les caractères voisins réels.
Par exemple: 1+0y1-1|.
Ceci est utilisé pour choisir la liste des substitutions comme indiqué ci-dessus. Et ensuite contracté pour adresser:int(r[0::2],2)

Ce lambda sert à vérifier que les coordonnées de cellule sont valides et que le caractère de la cellule est l'un de '+ - | ='

V=lambda y,x:~0<x<len(I[0])and~0<y<len(I)and I[y][x]in'+-|='

Ce lambda recevait des chars de la cellule. Renvoie ' 'si les coordonnées ne sont pas valides. (peut certainement être joué au golf)

W=lambda y,x:V(y,x)and I[y][x]or' '

Conditions de récursivité. Peut aussi être jouable au golf.

if Z in'+-=':F(Y,X+1);F(Y,X-1)
if Z in'+|':F(Y-1,X);F(Y+1,X)

I=eval(input())
J=[i[:]for i in I]
V=lambda y,x:~0<x<len(I[0])and~0<y<len(I)and I[y][x]in'+-|='
W=lambda y,x:V(y,x)and I[y][x]or' '
def F(Y,X):
 if V(Y,X)and I[Y][X]==J[Y][X]:
  Z=I[Y][X]
  if','>Z:
   r=''.join([str(int(V(y,x)))+W(y,x)for y,x in[(Y-1,X),(Y,X+1),(Y+1,X),(Y,X-1)]])
   J[Y][X]=['012┐45┌┬8┘0┤└┴├┼','012╕45╒╤8╛0╡╘╧╞╪']['='in r][int(r[0::2],2)]
  else:J[Y][X]=dict(zip('|-=','│─═'))[Z]
  if Z in'+-=':F(Y,X+1);F(Y,X-1)
  if Z in'+|':F(Y-1,X);F(Y+1,X)
e=enumerate
F(*[(y,x)for y,r in e(I)for x,c in e(r)if'+'==c][0])
for r in J:print(''.join(r))

Essayez-le en ligne!

Possum mort
la source