Comment imprimer le numéro le plus long d'une chaîne?


11

Je recherche une méthode pour imprimer le plus long nombre dans une chaîne.

Par exemple: si j'ai la chaîne

212334123434test233

comment imprimer

212334123434

?

Remarque: je recherche la séquence de nombres continue la plus longue, pas la valeur numériquement supérieure.


Edit: Merci pour les réponses, tout le monde. La réponse à cette question a été assez écrasante. J'ai marqué le message de @ HaukeLaging comme réponse acceptée car cela convenait très bien à mon cas spécifique, mais je voudrais souligner que toutes les réponses sont également valables. C'est toujours bien d'avoir plusieurs options différentes pour résoudre un problème.


Que voulez-vous que la méthode fasse lorsqu'il y a plusieurs séquences continues également longues? Prends le premier? Le dernier? Un hasard?
Anthon

@Anthon Huh, je n'y avais pas pensé. Heureusement, ce n'est pas un problème dans mon cas spécifique. Je suppose que toutes les options conviendraient.
Glutanimate

3
Notez que la réponse que vous avez acceptée (et toutes les autres jusqu'à présent sauf une ) ne traitera pas des nombres décimaux. Je ne sais pas si c'est un problème pour toi.
terdon

@terdon: Ce n'est pas un problème dans mon cas spécifique car je traite des identifiants plutôt que des chiffres réels, mais je voudrais quand même vous remercier pour votre réponse! Je suis sûr que quelqu'un d'autre le trouvera très utile à l'avenir.
Glutanimate

Souhaitez-vous que la solution soit capable de gérer les nombres négatifs? Et si oui - le signe moins compte-t-il dans la longueur?
Floris

Réponses:


7
echo 212334123434test233abc44 | 
awk '{gsub("[^0-9]+","\n"); print;}' | 
awk '{ if (length($0) > max) {max = length($0); maxline = $0} } 
  END { print maxline }'

212334123434

13

Je crois que vous pouvez le faire avec juste grep, sortet tailaussi bien. Voici quelques exemples de chaînes.

$ echo <str> | grep -oP "\d+" | sort -n | tail -1

<str>est notre chaîne en question.

Exemple

$ set -o posix; set | grep "str[0-9]"
str0=212334123434test233
str1=212334123434test233abc44
str2=233test212334123434
str3=a212334123434test233abc44
str4=a91234b212334123434abc

Maintenant, si je les exécute grep ...à tour de rôle via ma commande.

$ echo $str0 | grep -oP "\d+" | sort -n | tail -1
212334123434
$ echo $str1 | grep -oP "\d+" | sort -n | tail -1
212334123434
$ echo $str2 | grep -oP "\d+" | sort -n | tail -1
212334123434
$ echo $str3 | grep -oP "\d+" | sort -n | tail -1
212334123434
$ echo $str4 | grep -oP "\d+" | sort -n | tail -1
212334123434

Cette approche fonctionne en sélectionnant toutes les sous-chaînes qui sont des séquences de chiffres. Nous trions ensuite cette sortie numériquement, sort -npuis récupérons la dernière valeur de la liste à l'aide de tail -1. Ce sera la sous-chaîne la plus longue.

Vous pouvez voir comment cela fonctionne en retirant tail -1et en réexécutant l'un des exemples:

$ echo $str4 | grep -oP "\d+" | sort -n
91234
212334123434

Chaînes commençant par des zéros

L'approche ci-dessus fonctionne pour toutes les situations que je pourrais concevoir sauf une. @terdon a mentionné dans le chat ce scénario qui déjoue l'approche ci-dessus.

  • 0000000000001
  • 2

Donc, pour y faire face, vous devrez légèrement changer de tactique. Le noyau de l'approche ci-dessus peut encore être exploité, mais nous devons également injecter le nombre de caractères dans les résultats. Cela donne à tri la possibilité de trier les résultats par nombre de caractères dans les chaînes et leurs valeurs.

$ for i in $(echo $str0 | grep -oP "\d+");do a=$(echo "$i" | wc -c); \
    echo "$a $i"; done | sort -n | tail -1 | cut -d" " -f2

Résultats:

$ echo $str0
0000000000001a2test

$ for i in $(echo $str0 | grep -oP "\d+");do a=$(echo "$i" | wc -c); \
    echo "$a $i"; done | sort -n | tail -1 | cut -d" " -f2
0000000000001

Vous pouvez condenser cela un peu en utilisant la capacité de Bash à déterminer la longueur d'une variable à l'aide ${#var}.

$ for i in $(echo $str0 | grep -oP "\d+");do echo "${#i} $i"; done | \
    sort -n | tail -1 | cut -d" " -f2
0000000000001

Utilisation de `grep -P

J'ai choisi d'utiliser grep -P ...ci-dessus parce que moi, étant un développeur Perl, j'aime la syntaxe de classe de dire tous les chiffres comme ceci:, \d+au lieu de [[:digit:]]\+ou [0-9]\+. Mais pour ce problème particulier, il n'est pas vraiment nécessaire. Vous pouvez tout aussi facilement échanger celui grepque j'ai utilisé comme ceci:

$ .... grep -o "[0-9]\+" ....

Par exemple:

$ for i in $(echo $str0 | grep -o "[0-9]\+");do echo "${#i} $i"; done | \
    sort -n | tail -1 | cut -d" " -f2
0000000000001

2
Utiliser ${#i}pour obtenir la longueur de la chaîne peut vous éviter d'appeler wc, si vous voulez aller spécifique à bash
glenn jackman

@glennjackman - merci d'avoir ajouté votre amélioration à mon A 8-)
slm

GNU grep 2.16 (au moins) dit que -P est "hautement expérimental". Vous pouvez utiliser à la grep -o "[0-9]\+"place degrep -oP "\d+"
David Conrad

1
@DavidConrad - a également ajouté ces détails au A, merci!
slm

8

Une solution en perl:

echo 212334123434test233abc44 |
perl -nle 'print ((
    map { $_->[0] }
    sort{ $a->[1] <=> $b->[1] }
    map { [$_,length] }
    split /\D+/, $_)[-1]
    )'
212334123434

Les références


2
Aimez une belle transformation Schwartzian!
glenn jackman

7

En utilisant python avec la chaîne passée sur la ligne de commande et en supposant que vous voulez la première séquence de longueur maximale:

import sys

longest = current = ""
for x in sys.argv[1]:
    if current and not x.isdigit():
        if len(current) > len(longest):
            longest = current
        current = ""
    else:
        current += x 
print(longest)

2
ou laconiquementpython -c "import re,sys; print max(re.split(r'\D+', sys.argv[1]), key=len)"
iruvar

7

Voici une autre approche Perl qui peut traiter aussi bien les décimales que les entiers:

echo "0.212334123434test233" | 
 perl -lne 'while(/([\d.]+)/g){$max=$1 if length($1) > length($max)} print $max'

Notez qu'aucune des réponses publiées jusqu'à présent ne traitera des décimales et puisque vous spécifiez que vous voulez le nombre le plus long et non le plus grand numériquement, je suppose que vous avez réellement besoin de décimales.

Explication

  • perl -lne: Le -nmoyen "lit l'entrée ligne par ligne, et exécute le script donné par -edessus". Le -lajoute une nouvelle ligne à chaque printappel (et d'autres choses non pertinentes ici).
  • while(/([\d.]+)/g): parcourez tous les nombres ( \dsignifie [0-9]que [\d.]les chiffres correspondront ainsi .. Si vous souhaitez également rechercher des nombres négatifs, ajoutez -. Les parenthèses capturent la chaîne correspondante telle $1qu'elle est utilisée à l'étape suivante.
  • $max=$1 if length($1) > length($max): Si la longueur de la correspondance actuelle est supérieure à la plus longue jusqu'à présent ( $max), enregistrez la correspondance sous $max.
  • print $max: affiche la plus longue chaîne de nombres trouvée. Cela sera exécuté une fois la boucle while terminée, donc une fois tous les nombres trouvés.

1
+1 Votre expression régulière est cependant un peu trop générique. Il correspondrait par exemple aux adresses IP. Je propose quelque chose comme à la \D(\d+(?:\.\d+)?)\Dplace.
Joseph R.

Devrait aussi fonctionner sans les \Dancres ...
Joseph R.

@JosephR. hmm, c'est vrai, je n'avais pas considéré consécutif .comme dans les adresses IP.
terdon

6

Donné

str="212334123434test233"

puis en bash

max=""
while read num; do 
  (( ${#num} > ${#max} )) && max=$num
done < <(grep -Eo '[0-9]+' <<< "$str")
echo $max
212334123434

Une solution bash peut-être plus pure utilisant un tableau construit en remplaçant les caractères non numériques de la chaîne par des espaces, à la place de grep

max=""
declare -a nums="${str//[^[:digit:]]/ }"
for num in ${nums[@]}; do 
  (( ${#num} > ${#max} )) && max=$num
done
echo $max

4

S'appuyant sur la réponse de @mikeserv, voici encore une autre alternative. Il extrait les nombres (selon la méthode de mikeserv), puis les trie par ordre numérique et prend le dernier. À l'exception des zéros non significatifs, cela vous donnera le plus grand nombre (sans tenir compte du signe):

echo 1111askdlfm2234 |  printf %s\\n $(tr -sc 0-9 \ ) | sort -n | tail -1

Celui-ci fonctionne réellement - pas le mien. J'avais le '\ r' du mauvais côté! Je vais le supprimer. Vous pouvez également utiliser le shell comme -set -- $(echo $str | tr ... ) ; b=${#1} ; for d ; do [ ${#d} -gt $b ] && b=${#d} n=$d ; done ; echo $n
mikeserv

1
J'ai supprimé mon horrible message et vous avez traité avec douceur avec moi. Comme vous l'utilisez déjà de trtoute façon, je ne vous en voudrai pas si vous intégriez ce qui précède. C'est probablement sort plus rapide, mais, là encore, il attend la fin du flux de la même manière que le $(subshell). Je ne sais pas. En tout cas, la vôtre est déjà une excellente réponse, mais si vous avez envie d'ajouter la boucle de shell ci-dessus, n'hésitez pas. Et au fait - il est possible que vous puissiez vous passer sortcomplètement d'un peu de manipulation créative de wc -Let teedans le flux ... J'en ai fini avec cette question - je suis gêné.
mikeserv

Une dernière chose cependant - vous pourriez aussi bien vous retirer trdu sous-shell et vous en débarrasser printf. Faites-le '0-9' '\n'.
mikeserv

@mikeserv - la bonne chose à propos de ce site est que nous apprenons les uns des autres. Merci de votre aide; sans votre réponse, je n'aurais même pas commencé par moi-même ...
Floris

2

bash et tri GNU

IFS=$'\0' read -r l _ < <(tr -cs '[:digit:]' '[\0*]' <<<'11abcde1234556ghijk22'| sort -znr)
echo $l
1234556

2

Utilisez des caractères non numériques pour fractionner la chaîne et recherchez la séquence la plus longue ou la plus grande valeur numérique (pour les nombres de longueur égale) avec un opérateur ternaire.

$ echo "212334123434test233" | awk -F'[^0-9]+' '{for(i=1;i<=NF;i++){m=length($i)>=length(m)||$i>m?$i:m}};END{print m}'
212334123434

Vous pouvez également définir le séparateur d'enregistrement awk ( RS) comme n'importe quelle chaîne de caractères non numérique:

$ echo "212334123434test233" \
    | awk -v RS='[^0-9]+' '
        length(longest) < length($0) {longest = $0};
        END{print longest}'
212334123434

2
Pourquoi ne pas simplement définir RS = '[^0-9]+'et utiliser la boucle inhérente d'Awk? echo "212334123434test233" | awk -v RS='[^0-9]+' 'length(longest) < length($0) {longest = $0};END{print longest}' 212334123434

@awk_FTW, vous devriez également noter cela comme réponse. :) Merci de me montrer la RSvariable, je dois admettre que c'est la première fois que je la vois. Vous avez plus de conseils à offrir awkque moi hahaha!
hjk
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.