Étant donné un tableau de chaînes, je voudrais trier le tableau en fonction de la longueur de chaque élément.
Par exemple...
array=(
"tiny string"
"the longest string in the list"
"middle string"
"medium string"
"also a medium string"
"short string"
)
Devrait trier pour ...
"the longest string in the list"
"also a medium string"
"medium string"
"middle string"
"short string"
"tiny string"
(En prime, il serait bien que la liste trie les chaînes de la même longueur, par ordre alphabétique. Dans l'exemple ci-dessus, elles ont medium string
été triées avant middle string
même si elles sont de la même longueur. Mais ce n'est pas une exigence "difficile", si cela complique trop la Solution).
C'est OK si le tableau est trié sur place (c'est-à-dire que "tableau" est modifié) ou si un nouveau tableau trié est créé.
bash
shell-script
sort
array
PJ Singh
la source
la source
Réponses:
Si les chaînes ne contiennent pas de sauts de ligne, ce qui suit devrait fonctionner. Il trie les indices du tableau par longueur, en utilisant les chaînes elles-mêmes comme critère de tri secondaire.
Notez que le passage à un vrai langage de programmation peut grandement simplifier la solution, par exemple en Perl, vous pouvez simplement
la source
sorted(array, key=lambda s: (len(s), s))
array.sort { |a| a.size }
Cela lit les valeurs du tableau trié à partir d'une substitution de processus.
La substitution de processus contient une boucle. La boucle affiche chaque élément du tableau précédé de la longueur de l'élément et d'un caractère de tabulation entre les deux.
La sortie de la boucle est triée numériquement du plus grand au plus petit (et par ordre alphabétique si les longueurs sont les mêmes, l' utilisation
-k 2r
à la place de-k 2
renverser l'ordre alphabétique) et le résultat de c'est envoyé à qui supprime la colonne avec les longueurs de chaîne.cut
Tri du script de test suivi d'une exécution de test:
Cela suppose que les chaînes ne contiennent pas de sauts de ligne. Sur les systèmes GNU avec un récent
bash
, vous pouvez prendre en charge les sauts de ligne intégrés dans les données en utilisant le caractère nul comme séparateur d'enregistrement au lieu du saut de ligne:Ici, les données sont imprimées avec la fin
\0
de la boucle au lieu des sauts de ligne,sort
etcut
lit les lignes délimitées par le biais de leurs-z
options GNU etreadarray
lit finalement les données délimitées par-d ''
.la source
-d '\0'
est en fait-d ''
quebash
ne peut pas passer des caractères NUL aux commandes, même ses commandes intégrées. Mais il comprend-d ''
comme signifiant délimiter sur NUL . Notez que vous avez besoin de bash 4.4+ pour cela.'\0'
, il est$'\0'
. Et oui, il se convertit (presque exactement) en''
. Mais c'est un moyen de communiquer aux autres lecteurs l'intention réelle d'utiliser un délimiteur NUL.Je ne répéterai pas complètement ce que j'ai déjà dit sur le tri dans bash , vous pouvez simplement trier dans bash, mais peut-être que vous ne devriez pas. Vous trouverez ci-dessous une implémentation bash uniquement d'un tri par insertion, qui est O (n 2 ), et n'est donc tolérable que pour les petits tableaux. Il trie les éléments du tableau sur place selon leur longueur, dans l'ordre décroissant. Il ne fait pas de tri alphabétique secondaire.
Pour prouver qu'il s'agit d'une solution spécialisée, considérez les délais des trois réponses existantes sur des tableaux de différentes tailles:
Choroba et Kusalananda ont la bonne idée: calculer les longueurs une fois et utiliser des utilitaires dédiés pour le tri et le traitement de texte.
la source
Un hackish? (complexe) et rapide d'une ligne pour trier le tableau par longueur
( sans danger pour les nouvelles lignes et les tableaux clairsemés):
Sur une ligne:
À l'exécution
la source
Cela gère également les éléments du tableau avec des retours à la ligne; il fonctionne en ne passant que par
sort
la longueur et l'index de chaque élément. Cela devrait fonctionner avecbash
etksh
.Si les éléments de même longueur doivent également être triés lexicographiquement, la boucle pourrait être modifiée comme ceci:
Cela passera également aux
sort
chaînes (avec les sauts de ligne modifiés en espaces), mais ils seront toujours copiés de la source vers le tableau de destination par leurs index. Dans les deux exemples, le$(...)
ne verra que les lignes contenant des nombres (et le/
caractère dans le premier exemple), il ne sera donc pas déclenché par des caractères ou des espaces globaux dans les chaînes.la source
$(...)
substitution de commandes ne voit que les index (une liste de nombres séparés par des sauts de ligne), à cause de l'cut -d' ' -f1
after the sort. Cela pourrait être facilement démontré par untee /dev/tty
à la fin du$(...)
.cut
.${!in[@]}
ou${#in[i]}/$i
car elles ne contiennent que des chiffres qui ne sont pas soumis à l'expansion globale et launset IFS
réinitialise l'IFS
espace, l'onglet, la nouvelle ligne. En fait, les citer serait nocif , car cela donnerait la fausse impression qu'une telle citation est utile et efficace, et que le réglageIFS
et / ou le filtrage de la sortie desort
dans le deuxième exemple pourrait être supprimé en toute sécurité.in
contient"testing * here"
etshopt -s nullglob
est défini avant la boucle.Dans le cas où le passage à
zsh
est une option, un moyen hackish (pour les tableaux contenant n'importe quelle séquence d'octets):zsh
permet de définir des ordres de tri pour son expansion globale via des qualificatifs glob. Donc, ici, nous le trompons pour le faire pour les tableaux arbitraires en les globulant/
, mais en les remplaçant/
par les éléments du tableau (e'{reply=("$array[@]")}'
), puis enn
utilisant umericallyo
(en sens inverse en majusculesO
) les éléments en fonction de leur longueur (Oe'{REPLY=$#REPLY}'
).Notez qu'il est basé sur la longueur en nombre de caractères. Pour le nombre d'octets, définissez les paramètres régionaux sur
C
(LC_ALL=C
).Une autre
bash
approche 4.4+ (en supposant un tableau pas trop grand):(c'est la longueur en octets ).
Avec les anciennes versions de
bash
, vous pouviez toujours faire:(qui fonctionne également avec
ksh93
,zsh
,yash
,mksh
).la source