Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

Couper des lignes qui ne contiennent aucun espace ?

12 réponses
Avatar
Nicolas Cavigneaux
Bonjour tout le monde,

je suis entrain d'écrire une petite fonction qui coupe les lignes trop
longues en plusieurs lignes (un wrapper donc ...). Je ne compte pas
révolutionner ce qui existe déjà, c'est juste pour apprendre à le
faire. Pour progresser. Ma fonction fonctionne plutôt bien, elle sait
très bien couper lignes qui contiennent un espace ou une tabulation
lorsqu'il se trouve à un numéro de colonne étant un multiple de la
longueur maximale que j'ai fixée. Elle sait également couper les lignes
avec des mots très long en revenant au dernier caractère non-visible
disponible. La seule fonctionnalité manquante est de pouvoir couper les
lignes très longues sans espace. Donc en la découpant en "n" parties de
longueur maximale MAX (MAX étant la longueur maximale fixée pour une
ligne).

Voici donc le code qui me permet de faire mes test, je préfère le copier
en entier pour être sûr de ne pas avoir loupé un détail ailleurs que
dans la partie qui nous intéresse pour le découpage des longues lignes
sans espace.


#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define MAX 1000 /* max line length on input */
#define FOLD 72 /* max line length for folding */
#define IN 1 /* we are in a word */
#define OUT 0 /* we are not in a word */

void fold(char string[]);

int main(void)
{
char line[MAX];

while (fgets(line, MAX, stdin) != NULL)
{
fold(line);
printf("%s", line);
}

return EXIT_SUCCESS;
}

void fold(char string[])
{
char new_string[MAX];
int i = 0, j = 0, pos = 0, current = 0, state = 0;

while (current <= (int) strlen(string))
{
/* looking if we are in a word or not */
if (string[current] == ' ' || string[current] == '\t')
state = OUT;
else
state = IN;
/* is there enough characters or not ? */
if (pos / FOLD < 1)
{
new_string[i++] = string[current++];
pos++;
}
else
{
/* We are not in a word so we can fold the line */
if (state == OUT)
{
new_string[i++] = '\n';
new_string[i++] = string[current++];
}
/* We are in a word, we have to go back to the last invisible character */
else
{
for (j = i; new_string[j] != ' ' && new_string[j] != '\t' && new_string[j] != '\n' && j > 0; j--);
/* The line has no invisible character */
if (j == 0 || new_string[j] == '\n')
{
/* BUG */
while (j < i)
{
if (j % FOLD == 0 && j != 0)
{
memmove(new_string+j+1, new_string+j, MAX-j-1);
new_string[j++] = '\n';
}
j++;
}
}
/* There is an invisible character where we can insert a '\n' */
else
new_string[j] = '\n';
}
pos = 0;
}
}
strcpy(string, new_string);
}

J'ai essayer avec toute sorte de modifications sans succès, je n'arrive
pas à comprendre pourquoi les lignes sans espace de plus de 72
caractères sont, après le passage dans ma fonction, affichée jusqu'au
72ème caractère, avec le retour à ligne puis plus rien alors que le
reste de la chaîne devrait s'afficher à la ligne suivante ...

Merci pour vos conseils et votre aide.
--
Nicolas Cavigneaux | GPG KeyID : F0954C41
bounga@altern.org | http://bounga.ath.cx

10 réponses

1 2
Avatar
Alexandre BACQUART
Nicolas Cavigneaux wrote:
Bonjour tout le monde,

je suis entrain d'écrire une petite fonction qui coupe les lignes trop
longues en plusieurs lignes (un wrapper donc ...). Je ne compte pas
révolutionner ce qui existe déjà, c'est juste pour apprendre à le
faire. Pour progresser. Ma fonction fonctionne plutôt bien, elle sait
très bien couper lignes qui contiennent un espace ou une tabulation
lorsqu'il se trouve à un numéro de colonne étant un multiple de la
longueur maximale que j'ai fixée. Elle sait également couper les lignes
avec des mots très long en revenant au dernier caractère non-visible
disponible. La seule fonctionnalité manquante est de pouvoir couper les
lignes très longues sans espace. Donc en la découpant en "n" parties de
longueur maximale MAX (MAX étant la longueur maximale fixée pour une
ligne).

Voici donc le code qui me permet de faire mes test, je préfère le copier
en entier pour être sûr de ne pas avoir loupé un détail ailleurs que
dans la partie qui nous intéresse pour le découpage des longues lignes
sans espace.


#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define MAX 1000 /* max line length on input */
#define FOLD 72 /* max line length for folding */
#define IN 1 /* we are in a word */
#define OUT 0 /* we are not in a word */

void fold(char string[]);

int main(void)
{
char line[MAX];

while (fgets(line, MAX, stdin) != NULL)
{
fold(line);
printf("%s", line);
}

return EXIT_SUCCESS;
}

void fold(char string[])
{
char new_string[MAX];


Attention aux grosses variables automatiques comme celle-ci... à éviter.

int i = 0, j = 0, pos = 0, current = 0, state = 0;

while (current <= (int) strlen(string))
{
/* looking if we are in a word or not */
if (string[current] == ' ' || string[current] == 't')
state = OUT;
else
state = IN;
/* is there enough characters or not ? */
if (pos / FOLD < 1)


Pourquoi pas plus simplement :

if (pos < FOLD)

C'est idem et plus efficace.

{
new_string[i++] = string[current++];
pos++;
}
else
{
/* We are not in a word so we can fold the line */
if (state == OUT)
{
new_string[i++] = 'n';
new_string[i++] = string[current++];
}
/* We are in a word, we have to go back to the last invisible character */
else
{
for (j = i; new_string[j] != ' ' && new_string[j] != 't' && new_string[j] != 'n' && j > 0; j--);


Plus lisible (et éventuellement plus efficace) :

for (j = i; !isspace(new_string[j]) && j > 0; j--)

... à moins bien-sûr que tu ne veuilles volontairement traiter que
certains whitespaces (les whitespaces de ispace() étant ' ', 't', 'n,
'r', 'v' et 'f').

/* The line has no invisible character */
if (j == 0 || new_string[j] == 'n')
{
/* BUG */
while (j < i)
{
if (j % FOLD == 0 && j != 0)
{
memmove(new_string+j+1, new_string+j, MAX-j-1);
new_string[j++] = 'n';
}
j++;
}


Je vois plusieurs erreurs ici.

D'abord j++ utilisé 2 fois de suite. Tu mets 'n', tout en passant au
caractère suivant et en sortant du bloc if, tu refais j++. Que devient
new_string[j] (celui de la sortie du bloc if) ? Tu peux alors tomber sur
le cas où j "saute" par-dessus ta sentinelle (j % FOLD == 0).

De plus si tu es dans le cas j == 0, tu ne vas faire que j++ i fois
jusqu'à ce que j == i (à ce stade, i >= FOLD). Puis tu sors de la
boucle. Ce qui au total n'aura eu aucun effet (ni même de bord, car tu
initialises bien j ailleurs avant de l'utiliser, cf. le for au-dessus).

Dans le cas newstring_string[j] == 'n', il y a ton memmove(). Si je
suis bien ton raisonnement, tu décales le tableau (presque 1000 chars !)
d'un cran à gauche en partant de j jusqu'à la fin, et tu répètes
l'opération jusqu'à ce que j >= i.

}
/* There is an invisible character where we can insert a 'n' */
else
new_string[j] = 'n';
}
pos = 0;
}
}


new_string[i] = '';

strcpy(string, new_string);


...sinon c'est mal.

Mais je me permets de te faire remarquer (si tu ne le sais pas) que ton
code pêche par son efficacité à plusieurs égards.

}

J'ai essayer avec toute sorte de modifications sans succès, je n'arrive
pas à comprendre pourquoi les lignes sans espace de plus de 72
caractères sont, après le passage dans ma fonction, affichée jusqu'au
72ème caractère, avec le retour à ligne puis plus rien alors que le
reste de la chaîne devrait s'afficher à la ligne suivante ...


Je suis sûr que tu pourras trouver la solution au regard des
informations que je t'ai donné. Note que je n'ai volontairement utilisé
que des termes relatifs à ta logique dans le code, pas ce que tu veux en
faire, puisque tu dis faire cela "juste pour apprendre", le mieux est de
trouver la solution toi-même :) (et ça m'évite de le faire).


--
Tek

Avatar
Alexandre BACQUART
Alexandre BACQUART wrote:

De plus si tu es dans le cas j == 0, tu ne vas faire que j++ i fois
jusqu'à ce que j == i (à ce stade, i >= FOLD). Puis tu sors de la
boucle. Ce qui au total n'aura eu aucun effet (ni même de bord, car tu
initialises bien j ailleurs avant de l'utiliser, cf. le for au-dessus).


Non pardon, cette affirmation est fausse ! j n'est plus == 0 après le
premier passage.


--
Tek

Avatar
Yves ROMAN
Quelques remarques :



#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define MAX 1000 /* max line length on input */
#define FOLD 72 /* max line length for folding */
#define IN 1 /* we are in a word */
#define OUT 0 /* we are not in a word */

[...]


void fold(char string[])
{
char new_string[MAX];


Attention : tu ajoutes un 'n' a chaque découpage.
Ta new_string peut donc etre plus grand que string
(La difference maxi doit etre de l'ordre de MAX/FOLD)

int i = 0, j = 0, pos = 0, current = 0, state = 0;


Plutot utiliser IN ou OUT pour initialiser state , ca sera plus clair

[...]

Attention a la gestion de pos : il represente la position depuis le debut de la
ligne
courante dans ligne_new du caractere en cours de traitement.

Quand tu es en dehors d'un mot tu ajoutes un 'n' puis le caractere d'espace.
Il me semble que pos devrait valoir 1
Nota: dans la version suivante, remplaces supprime les espaces précédents et
suivants
par le 'n'

Quand tu es dans un mot, tu places le 'n' avant la position courante.
Il faut calculer la distance entre le 'n' placé et la position courante, et ne
pas
mettre 0 systématiquement

Avatar
Nicolas Cavigneaux

void fold(char string[])
{
char new_string[MAX];


Attention aux grosses variables automatiques comme celle-ci... à éviter.


Comment puis-je procéder autrement ? et pourquoi est-ce à éviter ?

Pourquoi pas plus simplement :

if (pos < FOLD)

C'est idem et plus efficace.


En effet, mon test était idiot.

Plus lisible (et éventuellement plus efficace) :

for (j = i; !isspace(new_string[j]) && j > 0; j--)


Une fois encore, c'est beaucoup mieux, il me manque visiblement de
l'entraînement :-).

... à moins bien-sûr que tu ne veuilles volontairement traiter que
certains whitespaces (les whitespaces de ispace() étant ' ', 't', 'n,
'r', 'v' et 'f').


Non, je recherche bien tous les caractères correspondants à un espace
quelque soit sa nature.

/* The line has no invisible character */ if (j == 0 ||
new_string[j] == 'n')
{
/* BUG */
while (j < i)
{
if (j % FOLD == 0 && j != 0)
{
memmove(new_string+j+1, new_string+j,
MAX-j-1); new_string[j++] = 'n';
}
j++;
}
}
Je vois plusieurs erreurs ici.


D'abord j++ utilisé 2 fois de suite. Tu mets 'n', tout en passant au
caractère suivant et en sortant du bloc if, tu refais j++. Que devient
new_string[j] (celui de la sortie du bloc if) ? Tu peux alors tomber sur
le cas où j "saute" par-dessus ta sentinelle (j % FOLD == 0).


Ah oui, encore une erreur stupide. je "sautais" un caractères toutes les
72 colonnes.

Dans le cas newstring_string[j] == 'n', il y a ton memmove(). Si je
suis bien ton raisonnement, tu décales le tableau (presque 1000 chars
!) d'un cran à gauche en partant de j jusqu'à la fin, et tu répètes
l'opération jusqu'à ce que j >= i.


Là je ne comprend pas. Ce que je cherche à faire c'est que pour toutes
les colonnes multiples de 72, je décale toute la partie droite de la
chaîne d'une colonne vers la droite. Puis après ce la j'insère mon 'n'
dans la 'case' vide. Ce n'est pas cela que mon code fait ?

/* There is an invisible character where we can insert
a 'n' */ else
new_string[j] = 'n';
}
pos = 0;
}
}
}
new_string[i] = '';


strcpy(string, new_string);


...sinon c'est mal.


Mais mon '' est bien présent ici puisque j'utilise:
while (current <= (int) strlen(string))
strlen(string) me donne par exemple pour 'toton' 5, string[current]
commence à 0 et va jusqu'à 5. il copie donc 6 'cases' donc le '', mon
'' est donc automatiquement ajouté à la fin de ma chaîne. Mon
raisonnement serait-il bancale ?

Mais je me permets de te faire remarquer (si tu ne le sais pas) que ton
code pêche par son efficacité à plusieurs égards.


Je m'en doute, j'essaie justement de recueillir toute information à ce
sujet pour pouvoir progresser. Je suis preneur de toute explication sur
l'embellissement et l'optimisation de mon code.

Bonne soirée.
--
Nicolas Cavigneaux | GPG KeyID : F0954C41
| http://bounga.ath.cx


Avatar
Nicolas Cavigneaux

Attention : tu ajoutes un 'n' a chaque découpage. Ta new_string peut
donc etre plus grand que string (La difference maxi doit etre de l'ordre
de MAX/FOLD)


Ah oui bien vu, je vais corriger ça.

int i = 0, j = 0, pos = 0, current = 0, state = 0;


Plutot utiliser IN ou OUT pour initialiser state , ca sera plus clair


Très juste, je le modifie également.

Attention a la gestion de pos : il represente la position depuis le debut
de la ligne
courante dans ligne_new du caractere en cours de traitement.


Oui, et ?

Quand tu es en dehors d'un mot tu ajoutes un 'n' puis le caractere
d'espace. Il me semble que pos devrait valoir 1 Nota: dans la version
suivante, remplaces supprime les espaces précédents et suivants
par le 'n'


Il me semble plutôt que lorsque je suis en dehors d'un mot, je remplace
l'espace par 'n'. Le caractère espace disparaît donc, il n'est pas
ajouté à la suite. Je me trompe ?

Quand tu es dans un mot, tu places le 'n' avant la position courante. Il
faut calculer la distance entre le 'n' placé et la position courante, et
ne pas
mettre 0 systématiquement


Ah oui, je n'avais pas du tout pensé à ça.

Merci beaucoup pour ton aide.
--
Nicolas Cavigneaux | GPG KeyID : F0954C41
| http://bounga.ath.cx


Avatar
Alexandre BACQUART
Nicolas Cavigneaux wrote:


void fold(char string[])
{
char new_string[MAX];


Attention aux grosses variables automatiques comme celle-ci... à éviter.


Comment puis-je procéder autrement ?


Le tas plutôt que la pile : malloc()/free().

et pourquoi est-ce à éviter ?


La pile, tant ignorée et pourtant si cruciale. Ce n'est pas vraiment du
ressort de la norme, c'est vrai, mais il faut en avoir conscience. Les
variables automatiques utilisent en principe la pile, et c'est une
resource limitée. Sur les machines modernes, on a tendance à ne pas trop
s'en soucier car on a des piles assez grosses, mais le jour où la pile
déborde et que tu te retrouves avec un bug "fantôme" (ie. qui ne vient
pas de ton programme), tu vas chercher des heures ce bug monstrueux qui
n'existe en fait pas du tout pour finallement jurer comme un chartier
"c'est cette boudiou de pile !!!". Ce jour là, l'espace de la pile
désormais tu respecteras :)

Dans le cas newstring_string[j] == 'n', il y a ton memmove(). Si je
suis bien ton raisonnement, tu décales le tableau (presque 1000 chars
!) d'un cran à gauche en partant de j jusqu'à la fin, et tu répètes
^^^^^^


...je suis un bouffon... droite en fait :)

l'opération jusqu'à ce que j >= i.



Là je ne comprend pas. Ce que je cherche à faire c'est que pour toutes
les colonnes multiples de 72, je décale toute la partie droite de la
chaîne d'une colonne vers la droite. Puis après ce la j'insère mon 'n'
dans la 'case' vide. Ce n'est pas cela que mon code fait ?


Si si, bien à droite :)

Mais tu boucles sur les expressions (j%FOLD == 0) et (j++) FOLD fois (-1
à cause du "saut" de j), cela perd beaucoup de temps !

/* There is an invisible character where we can insert
a 'n' */ else
new_string[j] = 'n';
}
pos = 0;
}
}
}


new_string[i] = '';


strcpy(string, new_string);


...sinon c'est mal.


Mais mon '' est bien présent ici puisque j'utilise:
while (current <= (int) strlen(string))
strlen(string) me donne par exemple pour 'toton' 5, string[current]
commence à 0 et va jusqu'à 5. il copie donc 6 'cases' donc le '', mon
'' est donc automatiquement ajouté à la fin de ma chaîne. Mon
raisonnement serait-il bancale ?


Non, tu as raison. Je n'ai pas assez bien regardé ton code. Pour le
reste, Yves a trouvé d'où vient le problème que tu citais (bien que ce
n'était pas le seul).

Mais je me permets de te faire remarquer (si tu ne le sais pas) que ton
code pêche par son efficacité à plusieurs égards.


Je m'en doute, j'essaie justement de recueillir toute information à ce
sujet pour pouvoir progresser. Je suis preneur de toute explication sur
l'embellissement


De ce coté, rien à dire. Question de goût. Ton code n'est pas laid,
juste un peu maladroit.

et l'optimisation de mon code.


Hé bien :

while(j<i)
{
if(j%FOLD == 0 && j != 0)
{
memmove(new_string+j+1, new_string+j, MAX-j-1);
new_string[j] = 'n';
}
j++;
}

(j'ai enlevé le premier ++) pourraît être remplacé par :

for (j=FOLD; j<i; j+=FOLD)
{
memmove(new_string+j+1, new_string+j, MAX-j-1);
new_string[j] = 'n';
}

Le j=FOLD du for() assure l'absence du cas j==0 (qui ne fait rien si ce
n'est justement incrémenter j jusqu'à j==FOLD). Un test de boucle en moins.

Quand au j+=FOLD, ca permet de passer directement au FOLD suivant plutôt
que de boucler FOLD fois sur j++. Encore une expression de boucle en
moins (et surtout, beaucoup moins de boucles !!!).

Enfin bon, si ça amuse des gens aussi de faire des lignes de plus de 72
caractères sans whitespace, tant pis pour eux tu me diras :). Ce n'est
pas très grave à cet endroit là. Tu fais en général des boucles bateau
quand tu développes et si ça marche, tu les revois lors de la mise au point.




--
Tek



Avatar
Nicolas Cavigneaux

Le tas plutôt que la pile : malloc()/free().


Je vais me renseigner sur cela, je ne sais pas vraiment quelle est la
différence entre les deux.

et pourquoi est-ce à éviter ?


La pile, tant ignorée et pourtant si cruciale. Ce n'est pas vraiment du
ressort de la norme, c'est vrai, mais il faut en avoir conscience. Les
variables automatiques utilisent en principe la pile, et c'est une
resource limitée. Sur les machines modernes, on a tendance à ne pas trop
s'en soucier car on a des piles assez grosses, mais le jour où la pile
déborde et que tu te retrouves avec un bug "fantôme" (ie. qui ne vient
pas de ton programme), tu vas chercher des heures ce bug monstrueux qui
n'existe en fait pas du tout pour finallement jurer comme un chartier
"c'est cette boudiou de pile !!!". Ce jour là, l'espace de la pile
désormais tu respecteras :)


OK, je n'étais pas du tout au courant de cela :-) Maintenant j'y penserai
et je vais me documenter pour mieux comprendre les problèmes que peut
entraîner le débordement de pile et comment les éviter.

Le j=FOLD du for() assure l'absence du cas j==0 (qui ne fait rien si ce
n'est justement incrémenter j jusqu'à j==FOLD). Un test de boucle en
moins.

Quand au j+=FOLD, ca permet de passer directement au FOLD suivant plutôt
que de boucler FOLD fois sur j++. Encore une expression de boucle en moins
(et surtout, beaucoup moins de boucles !!!).


Ah oui cette méthode est largement plus intéressante. Très bonne idée,
il faut que je fasse attention à mes boucles inutiles.

Enfin bon, si ça amuse des gens aussi de faire des lignes de plus de 72
caractères sans whitespace, tant pis pour eux tu me diras :). Ce n'est


Eh eh ;-) C'est vrai que c'est un peu "étrange" d'écrire des lignes de
plus de 72 caractères sans espace ... Mais je trouve intéressant de
savoir gérer ce cas.

Encore merci pour toutes les explications.

Bonne soirée.
--
Nicolas Cavigneaux | GPG KeyID : F0954C41
| http://bounga.ath.cx


Avatar
Nicolas Cavigneaux

Le j=FOLD du for() assure l'absence du cas j==0 (qui ne fait rien si ce
n'est justement incrémenter j jusqu'à j==FOLD). Un test de boucle en
moins.

Quand au j+=FOLD, ca permet de passer directement au FOLD suivant
plutôt que de boucler FOLD fois sur j++. Encore une expression de
boucle en moins (et surtout, beaucoup moins de boucles !!!).


Ah oui cette méthode est largement plus intéressante. Très bonne idée,
il faut que je fasse attention à mes boucles inutiles.


En fait cette méthode pose un problème, on a le code suivant:

for (j = i; !isspace(new_string[j]) && j > 0; j--);
/* The line has no invisible character */
if (j == 0 || new_string[j] == 'n')
{
/* BUG */
for (j = FOLD; j < i; j += FOLD)
{
memmove(new_string+j+1, new_string+j, MAX-j-1);
new_string[j] = 'n';
}
}

Donc si on tombe sur le cas new_string[j] == 'n', il ne faut plus
initialiser j à FOLD, sinon on va ajouter des 'n' à des endroits où
ils sont déjà présents. On va donc se retrouver avec des doubles sauts
de ligne puisque l'ancien 'n' aura été décalé d'une case vers la
droite.

Je me vois obligé de retourner à l'ancienne version. A moins que
j'aurais mal saisie le fonctionnement du code que tu m'as proposé ...

Bonne soirée.
--
Nicolas Cavigneaux | GPG KeyID : F0954C41
| http://bounga.ath.cx


Avatar
Alexandre BACQUART
Nicolas Cavigneaux wrote:



Le j=FOLD du for() assure l'absence du cas j==0 (qui ne fait rien si ce
n'est justement incrémenter j jusqu'à j==FOLD). Un test de boucle en
moins.

Quand au j+=FOLD, ca permet de passer directement au FOLD suivant
plutôt que de boucler FOLD fois sur j++. Encore une expression de
boucle en moins (et surtout, beaucoup moins de boucles !!!).


Ah oui cette méthode est largement plus intéressante. Très bonne idée,
il faut que je fasse attention à mes boucles inutiles.



En fait cette méthode pose un problème, on a le code suivant:

for (j = i; !isspace(new_string[j]) && j > 0; j--);
/* The line has no invisible character */
if (j == 0 || new_string[j] == 'n')
{
/* BUG */
for (j = FOLD; j < i; j += FOLD)
{
memmove(new_string+j+1, new_string+j, MAX-j-1);
new_string[j] = 'n';
}
}

Donc si on tombe sur le cas new_string[j] == 'n', il ne faut plus
initialiser j à FOLD, sinon on va ajouter des 'n' à des endroits où
ils sont déjà présents. On va donc se retrouver avec des doubles sauts
de ligne puisque l'ancien 'n' aura été décalé d'une case vers la
droite.


En effet.

Je me vois obligé de retourner à l'ancienne version. A moins que
j'aurais mal saisie le fonctionnement du code que tu m'as proposé ...


Tu n'as alors qu'à rajouter :

if (j==0)
j = FOLD;

...avant le for() (sans oublier d'enlever l'initialisation dans le for()).

ou faire :

for (j = (j==0)? FOLD : j; j < i; j += FOLD)

...mais je trouve ça moins joli dans ce cas.





--
Tek



Avatar
Nicolas Cavigneaux

Tu n'as alors qu'à rajouter :

if (j==0)
j = FOLD;


Oui ou plutôt:
if (j == 0)
j = FOLD;
else
j += FOLD;

Sinon j'aurai à nouveau le problème du double 'n' au premier tour je
pense.

...avant le for() (sans oublier d'enlever l'initialisation dans le
for()).


Pour ce genre de construction je préfère remplacer le for() par un
while() mais bon ça ce n'est qu'une histoire de goût.
--
Nicolas Cavigneaux | GPG KeyID : F0954C41
| http://bounga.ath.cx

1 2