Cómo sumr los numbers del partido

Soy nuevo en scripting y necesito ayuda. Apreciaremos tus respuestas.

Obtuve esta tarea, que es encontrar la sum de todos los numbers de cinco dígitos (en el range 10000 – 99999) que contienen exactamente dos de los siguientes dígitos: {4, 5, 6}. Estos pueden repetirse dentro del mismo número, y si es así, countn una vez para cada ocurrencia.

Algunos ejemplos de numbers coincidentes son 42057, 74638 y 89515. Solo tengo este pequeño código, ni siquiera sé si me ayuda.

#! /bin/bash for (( CON1=10000; CON1<=99999; CON1++ )) ; do ## UNKNOWN COMMANDS done 

Solutions Collecting From Web of "Cómo sumr los numbers del partido"

Aquí hay una forma de contar cuántos 4, 5 o 6 aparecen en su número y hacer que bash ejecute una statement en function de si el resultado es dos o no:

 $ con1=1457 $ a=${con1//[^456]/}; [ ${#a} -eq 2 ] && echo Yes Yes 

Supongo que tienes que hacer esto en puro script Bash, pero traducir el algorithm de John1024 a awk da una aceleración considerable :

 awk 'BEGIN{k=0;for(i=10000;i<100000;i++){j=i;if(gsub(/[456]/,"",j)==2)k+=i};print k}' 

Esto se ejecuta en less de 1/20 del time que demora la versión de bash; también es un poco más rápido que una versión de Python que usa el método str.count() Python.

Empezando

Cada vez que tengo un proyecto como este, me gusta abordarlo por etapas. Lo primero que me gustaría hacer es agregar un echo al interior del ciclo y luego ejecutarlo para asegurarme de que el ciclo me da lo que quiero.

 #! /bin/bash for (( CON1=10000; CON1<=99999; CON1++ )) ; do echo $CON1 done 

Ahora, cuando lo ejecute, usaré el head -5 para mostrar las primeras 5 líneas que genera.

 $ ./cmd.bash | head -5 10000 10001 10002 10003 10004 

OK, para que se vea bien, mira el final así:

 $ ./cmd.bash | tail -5 99995 99996 99997 99998 99999 

Eso se ve bien también. Entonces, ahora descubramos algunas forms en que podríamos abordar el siguiente paso para identificar numbers con 2 dígitos del set {4,5,6}. Mi primer instinto aquí es ir por grep . También hay methods para hacer esto solo en Bash, pero me gusta usar las diversas herramientas, grep , awk y sed para hacer este tipo de cosas, principalmente porque así es como funciona mi mente.

Un acercamiento

Entonces, ¿cómo podemos grep líneas que contienen 2 dígitos del set, {4,5,6}? Para esto puede usar una notación establecida, que está escrita así en regex, [456] . También puede especificar cuántos dígitos desea emparejar de este set. Eso está escrito así:

 [456]{#} 

Donde # es un número o range de numbers. Si quisiéramos 3, escribiríamos [456]{3} . Si quisiéramos de 2 a 5 dígitos, escribiríamos [456]{2,5} . Si quería 3 o más, [456] {3,} `.

Entonces, para su escenario, es [456]{2} . Para usar una expresión regular en grep , su versión particular de grep necesita ser compatible con -E swtich. Esto está típicamente disponible en la mayoría de grep estándar.

 $ echo "45123" | grep -E "[456]{2}" 45123 

Parece que funciona, pero si le damos los numbers con 3, empezamos a ver un problema:

 $ echo "45423" | grep -E "[456]{2}" 45423 

Eso es igual también. Esto es porque grep no tiene el concepto del hecho de que estos son dígitos en una cadena. Es tonto. Le dijimos que nos diga si la serie de caracteres en nuestra cadena son de un set y que hay 2 de ellos y hay 2 dígitos en la cadena 45423 .

También falla para estas cadenas:

 $ echo "41412" | grep -E "[456]{2}" $ 

Entonces, ¿este método es utilizable? Es si cambiamos las tácticas un poco, pero tendremos que volver a ajustar la expresión regular.

Ejemplo

 $ echo -e "41123\n44123\n44423\n41423" | grep -E "[^456]*([456][^456]*){2}" 44123 44423 41423 

Lo anterior presenta 4 types de cadenas. El echo -e "41123\n44123\n44423\n41423" solo imprime 4 de los numbers de nuestro range.

 $ echo -e "41123\n44123\n44423\n41423" 41123 44123 44423 41423 

¿Cómo funciona esta expresión regular? Establece un patrón de expresiones regulares de cero o más "no [456]" seguido por uno o más [456] o cero o más caracteres "no [456]", buscando 2 ocurrencias de este último.

Así que ahora hacemos un pequeño ensamblaje en su script.

 for (( CON1=10000; CON1<=99999; CON1++ )) ; do if echo $CON1 | grep -q -E "[^456]*([456][^456]*){2}"; then echo $CON1 fi done 

Usando nuestro truco de head y tail desde arriba, podemos ver que está funcionando:

 $ ./cmd.bash | head -5 10044 10045 10046 10054 10055 $ ./cmd.bash | tail -5 99955 99956 99964 99965 99966 

Pero este método demuestra ser lento como un perro. El problema es ese grep . Es caro, y estamos ejecutando `grep 1 vez, por iteración a través del ciclo, ¡eso es ~ 80k veces!

Para mejorar eso, podríamos mover nuestro command grep fuera del ciclo y ejecutarlo una vez, después de que la list haya sido generada, de esa manera, usando nuestra versión original del script que solo repitió los numbers:

 $ ./cmd.bash | grep -E "[^456]*([456][^456]*){2}" 

NOTA: Podríamos dejar caer el bucle for por completo y usar la herramienta de línea de command, seq . Esto generará la misma secuencia de numbers, seq 10000 99999 .

¿Un trazador de líneas?

Una forma elegante de hacer esto sería usar la secuencia de numbers del command anterior, y luego canalizarlo al command paste que insertía un + entre cada número, y luego ejecutar ese resultado en la calculadora de línea de command, bc .

 $ ./cmd.bash | grep -E "[^456]*([456][^456]*){2}" | paste -s -d"+" 10044+10045+10046+10054+10055+10056+10064+10065+10066+10144+10145+... $ ./cmd.bash | grep -E "[^456]*([456][^456]*){2}" | paste -s -d"+" | bc 2409327540 

Pero esa es una forma completamente diferente de resolver este problema, así que volvamos al bucle for .

Usando puro Bash

Entonces necesitamos algún método para probar si un dígito tiene exactamente 2 dígitos dentro de Bash, pero no es tan caro como llamar a grep 80k veces. Las versiones modernas de Bash incluyen la capacidad de combinar usando el operador =~ , que puede hacer coincidencias similares a grep . Echemos un vistazo a eso a continuación.

 #!/bin/bash for (( CON1=10000; CON1<=99999; CON1++ )) ; if [[ $CON1 =~ [^456]*([456][^456]*){2} ]]; then echo $CON1 fi done 

Ejecutando esto parece hacer exactamente lo que queremos.

 $ ./cmd1.bash | head -5 10044 10045 10046 10054 10055 $ ./cmd1.bash | tail -5 99955 99956 99964 99965 99966 

Al comprobarlo, muestra que ahora funciona con 41511:

 $ ./cmd1.bash | grep 41511 41511 

Referencias

  • La expresión condicional – Bash