Como somar os números dos jogos

1

Sou novo em scripts e preciso de ajuda. Apreciará suas respostas.

Eu tenho essa atribuição, que é encontrar a soma de todos os números de cinco dígitos (no intervalo 10000 - 99999) contendo exatamente dois dos seguintes conjuntos de dígitos: {4, 5, 6}. Estes podem repetir dentro do mesmo número e, se for o caso, contar uma vez para cada ocorrência.

Alguns exemplos de números correspondentes são 42057, 74638 e 89515. Eu só tenho esse pequeno pedaço de código.

#! /bin/bash
for (( CON1=10000; CON1<=99999; CON1++ )) ;
    do
        ## UNKNOWN COMMANDS
    done
    
por Panaman 20.10.2014 / 01:45

3 respostas

2

Aqui está uma maneira de contar quantos 4, 5 ou 6 aparecem em seu número e ter bash executar uma instrução com base no fato de o resultado ser dois ou não:

$ con1=1457
$ a=${con1//[^456]/}; [ ${#a} -eq 2 ] && echo Yes
Yes
    
por 20.10.2014 / 03:53
0

Suponho que você tenha que fazer isso em puro script Bash, mas traduzir o algoritmo de John1024 para o awk dá uma aceleração considerável :

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

Isso é executado em menos de 1/20 do tempo que a versão bash leva; também é um pouco mais rápido que uma versão em Python que usa o método str.count() do Python.

    
por 20.10.2014 / 06:04
0

Primeiros passos

Sempre que tenho um projeto como este, gosto de abordá-lo em etapas. A primeira coisa que eu gosto de fazer é adicionar um echo no interior do loop e depois executá-lo, para ter certeza de que o loop está me dando o que eu quero.

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

Agora, quando eu executar, usarei head -5 para mostrar apenas as 5 primeiras linhas geradas.

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

OK, para que fique bem, verifique o final assim:

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

Isso parece bom também. Então, agora vamos descobrir algumas maneiras pelas quais poderíamos abordar a próxima etapa de identificação de números com 2 dígitos do conjunto {4,5,6}. Meu primeiro instinto aqui é ir para grep . Também existem métodos para fazer isso puramente no Bash, mas eu gosto de usar as várias ferramentas, grep , awk e sed para fazer esses tipos de coisas, principalmente porque é assim que minha mente funciona.

Uma abordagem

Então, como podemos grep linhas que contêm 2 dígitos do conjunto {4,5,6}? Para isso, você pode usar uma notação de conjunto, que é escrita assim na regex, [456] . Você também pode especificar quantos dígitos deseja corresponder a partir desse conjunto. Está escrito assim:

[456]{#}

Onde # é um número ou intervalo de números. Se quiséssemos 3, escreveríamos [456]{3} . Se quiséssemos de 2 a 5 dígitos, escreveríamos [456]{2,5} . Se você quisesse 3 ou mais, [456] {3,} '.

Então, para o seu cenário, é [456]{2} . Para usar uma regex em grep , sua versão específica de grep precisa suportar o -E swtich. Isso geralmente está disponível na maioria dos grep 'padrão.

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

Parece funcionar, mas se dermos números com 3, começaremos a ver um problema:

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

Essa correspondência também. Isso ocorre porque grep não tem noção do fato de que esses são dígitos de uma string. É burro. Nós dissemos para nos dizer se a série de caracteres em nossa string é de um conjunto e se há 2 deles e se há 2 dígitos na string 45423 .

Ele também falha nessas sequências:

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

Então este método é utilizável? É se mudarmos um pouco as táticas, mas teremos que rejigar o regex.

Exemplo

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

O texto acima apresenta 4 tipos de strings. O echo -e "41123\n44123\n44423\n41423" apenas imprime 4 dos números do nosso intervalo.

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

Como funciona esse regex? Ele configura um padrão de expressão regular de zero ou mais "não [456]" seguido por 1 ou mais [456] ou zero ou mais caracteres "não [456]", procurando por 2 ocorrências do último.

Agora, fazemos uma pequena montagem no seu script.

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

Usando nosso head & % truque de tail de cima podemos ver que está funcionando:

$ ./cmd.bash | head -5
10044
10045
10046
10054
10055

$ ./cmd.bash | tail -5
99955
99956
99964
99965
99966

Mas este método prova ser lento. O problema é que grep . É caro, e estamos rodando 'grep 1 time, por iteração através do loop, então é ~ 80k vezes!

Para melhorar, poderíamos mover nosso comando grep para fora do loop e executá-lo 1 vez depois que a lista fosse gerada, usando a versão original do script que acabou de reproduzir os números:

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

NOTA: Poderíamos eliminar completamente o loop for e usar a ferramenta de linha de comando, seq . Isso gerará a mesma sequência de números, seq 10000 99999 .

Um forro?

Uma maneira sofisticada de fazer isso seria usar a sequência de números do comando acima e, em seguida, canalizá-la para o comando paste , que inseria um + entre cada número e, em seguida, executaria essa saída no calculadora de linha de comando, 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

Mas essa é uma maneira completamente diferente de resolver esse problema, então vamos voltar para o loop for .

Usando puro Bash

Portanto, precisamos de algum método para testar se um dígito tem exatamente dois dígitos no Bash, mas não é tão caro quanto chamar grep 80k vezes. As versões modernas do Bash incluem a capacidade de corresponder usando o operador =~ , que pode fazer correspondência semelhante como grep . Vamos dar uma olhada nisso em seguida.

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

A execução disso parece fazer exatamente o que queremos.

$ ./cmd1.bash  | head -5
10044
10045
10046
10054
10055

$ ./cmd1.bash  | tail -5
99955
99956
99964
99965
99966

Verificar mostra que funciona com 41511 agora:

$ ./cmd1.bash | grep 41511
41511

Referências

por 20.10.2014 / 04:49