Determinar se um número é múltiplo de dez ou está dentro de um determinado conjunto de intervalos

103

Tenho alguns loops de que preciso no meu programa. Posso escrever o pseudocódigo, mas não tenho certeza de como escrevê-los logicamente.

Eu preciso -

if (num is a multiple of 10) { do this }

if (num is within 11-20, 31-40, 51-60, 71-80, 91-100) { do this }
else { do this } //this part is for 1-10, 21-30, 41-50, 61-70, 81-90

Este é um jogo de tabuleiro de cobras e escadas, se fizer mais sentido para a minha pergunta.

Imagino a primeira instrução if que vou precisar usar modulus, estaria if (num == 100%10)correta?

O segundo eu não tenho ideia. Posso escrever assim, if (num > 10 && num is < 21 || etc)mas tem que haver algo mais inteligente do que isso.

user3419168
fonte
16
Geralmente, o comprimento de um bom código é proporcional ao comprimento do inglês que descreve o que ele faz. Portanto, quando sua "especificação" diz 11-20, 31-40, 51-60, 71-80, 91-100, você pode esperar que seu código mencione esses números também. Se esses números vierem de algum lugar ou foram gerados por algum motivo, veja se você pode codificar o motivo em vez dos números.
luqui de
39
@ user3419168: O compilador não se importa nem um pouco com a legibilidade do seu código; ele irá compilá-lo em uma fração de segundo. Mas, para os humanos que estão lendo seu código, as escolhas que você faz podem fazer com que ele seja compreendido em segundos, minutos, horas ou nunca. Isso impõe um custo; as pessoas são pagas para ler e entender o código, então torne isso mais fácil para elas. Sempre escreva o código de produção para maximizar a legibilidade e lembre-se de que a concisão não faz necessariamente com que o código tenha um desempenho melhor.
Eric Lippert,
2
@AmadeusDrZaius - MUITO RARAMENTE fiz o mesmo, mas apenas para seções críticas de desempenho. O loop mais apertado que é chamado de 100 milhões de vezes se qualifica - a instrução if em um jogo de cobras e escadas não. Onde você traça a linha entre eles é uma escolha pessoal.
Floris
2
Eu odeio dizer isso, mas tendo feito bastante trabalho corporativo, com iniciantes escrevendo código real, eu teria que recomendar força bruta. Porque aí os caras novos vão entender e não vão quebrar. triste, mas é verdade - em alguns casos é inteligente ser burro.
Richard Le Mesurier
22
Esta é uma pergunta decente, e eu não quero tirar nada do autor da postagem, mas não merece mais de 500 pontos. É assim que acabamos com algumas das bobagens que fazemos com pessoas com milhares de pontos que parecem ser autoridades aqui. (Sinta-se à vontade para mover este comentário se ele pertencer a outro lugar.)
GaTechThomas

Respostas:

86

Para o primeiro, para verificar se um número é múltiplo de uso:

if (num % 10 == 0) // It's divisible by 10

Para o segundo:

if(((num - 1) / 10) % 2 == 1 && num <= 100)

Mas isso é bastante denso, e talvez seja melhor listar as opções explicitamente.


Agora que você deu uma ideia melhor do que está fazendo, eu escreveria o segundo como:

   int getRow(int num) {
      return (num - 1) / 10;
   }

   if (getRow(num) % 2 == 0) {
   }

É a mesma lógica, mas usando a função temos uma ideia mais clara do que ela significa.

Winston Ewert
fonte
79
if((num - 1) / 10) % 2 == 1 && num < 100)- Eu choraria se visse isso.
Daniel Kamil Kozar
32
@DanielKamilKozar, como deveria.
Winston Ewert
2
@ user3419168, por si só deixa a gente se perguntando o que diabos isso significa. Não dá nenhuma indicação do que diabos está tentando fazer. É por isso que na edição mostrei uma versão que divide a lógica em uma função que deixa mais claro o que os cálculos estão realmente fazendo.
Winston Ewert
3
Pode ser prudente também afirmar num >= 11como (1) que o limite inferior é proscrito e (2) %em um número negativo retorna um número negativo também. (Devo admitir que usar & 1aqui é "mais seguro", mas também pressupõe conhecimento adicional.)
usr2564301
2
+1 para a edição, entra no porquê da lista de intervalos e apresenta-o de forma legível. IMO, um passo adiante seria envolver o getRow(num) % 2 == 0em uma função também para deixar bem claro qual é a intenção. bool inEvenRow(int num){ return getRow(num) % 2 ==0;}
Sr. Mindor
40

if (num é um múltiplo de 10) {faça ​​isso}

if (num % 10 == 0) {
  // Do something
}

if (núm está entre 11-20, 31-40, 51-60, 71-80, 91-100) {faça ​​isso}

O truque aqui é procurar algum tipo de semelhança entre os intervalos. Claro, você sempre pode usar o método de "força bruta":

if ((num > 10 && num <= 20) ||
    (num > 30 && num <= 40) ||
    (num > 50 && num <= 60) ||
    (num > 70 && num <= 80) ||
    (num > 90 && num <= 100)) {
  // Do something
}

Mas você pode notar que, se subtrair 1de num, terá os intervalos:

10-19, 30-39, 50-59, 70-79, 90-99

Em outras palavras, todos os números de dois dígitos cujo primeiro dígito seja ímpar. Em seguida, você precisa criar uma fórmula que expresse isso. Você pode obter o primeiro dígito dividindo por 10 e pode testar se ele é estranho verificando o resto de 1 ao dividir por 2. Juntando tudo:

if ((num > 0) && (num <= 100) && (((num - 1) / 10) % 2 == 1)) {
  // Do something
}

Dada a compensação entre um código mais longo, mas passível de manutenção, e um código "inteligente" mais curto, sempre escolheria mais e mais claro. No mínimo, se você tentar ser inteligente, por favor, inclua um comentário que explique exatamente o que você está tentando realizar.

Ajuda supor que o próximo desenvolvedor a trabalhar no código está armado e sabe onde você mora. :-)

Adam Liss
fonte
7
Eu ainda iria para o código inteligente, mas transformá-lo em código sustentável extraindo funções. Seria tão legível se a última parte && isTensDigitOdd(num)fosse dita , talvez com um comentário antes da definição da função explicando o que ela faz. Se tal padrão existir, um comentário explicando o raciocínio para o padrão é esclarecedor para a manutenibilidade.
chris
3
Chris, essa é uma ótima estratégia quando a "inteligência" tem uma vantagem clara: código muito mais curto (o que significa menos chance de um erro de digitação, especialmente se ele mudar) ou uma grande melhoria na eficiência. Quase sempre há uma troca entre brevidade, clareza e eficiência, e encontrar um bom meio-termo é uma grande habilidade a ser desenvolvida. (Consulte stackoverflow.com/a/2151844/29157 para uma risadinha.)
Adam Liss
1
Esta é uma abordagem muito melhor. Muito mais fácil de entender do que o 'código inteligente' e a diferença de desempenho é provavelmente insignificante.
user1477388
@AdamLiss, Sim, minha opinião tem pouco valor, pois não tive experiência suficiente para ver as repercussões dessas decisões. Tenho certeza que farei isso em breve e com certeza terei uma segunda opinião, se necessário.
chris
1
Não se venda a descoberto. Seus instintos são muito sensatos e você parece ansioso para continuar aprendendo. Toda opinião é valiosa se houver uma boa razão por trás dela ... e às vezes até mesmo se não houver. Aposto dinheiro que você irá longe.
Adam Liss
30

Se você estiver usando GCC ou qualquer compilador que ofereça suporte a intervalos de caso, você pode fazer isso, mas seu código não será portátil .

switch(num)
{
case 11 ... 20:
case 31 ... 40:
case 51 ... 60:
case 71 ... 80:
case 91 ... 100:
    // Do something
    break;
default:
    // Do something else
    break;
}
Bryan Chen
fonte
1
Você pode me dizer por que este código não é portátil?
M Sharath Hegde
8
@MSharathHegde porque requer extensão GCC, que não faz parte do padrão e alguns compiladores não a suportam
Bryan Chen
5
Essa é a resposta certa, porque é imediatamente aparente qual é a intenção. Todas aquelas respostas 'inteligentes' com módulo são um pesadelo de manutenção, mesmo com comentários.
smirkingman
@smirkingman Na verdade, foi isso que eu disse em meu comentário à pergunta principal. Só é preciso alguma experiência de novos programadores em um trabalho corporativo para perceber que a maneira óbvia geralmente é muito melhor do que a maneira ninja inteligente.
Richard Le Mesurier
15

Isso é mais para futuros visitantes do que para um iniciante. Para uma solução mais geral, semelhante a um algoritmo, você pode pegar uma lista de valores iniciais e finais e verificar se um valor passado está dentro de um deles:

template<typename It, typename Elem>
bool in_any_interval(It first, It last, const Elem &val) {
    return std::any_of(first, last, [&val](const auto &p) {
        return p.first <= val && val <= p.second;
    });
}

Para simplificar, usei um lambda polimórfico (C ++ 14) em vez de um pairargumento explícito . Isso provavelmente também deve se limitar a usar <e ==ser consistente com os algoritmos padrão, mas funciona assim, contanto que Elemseja <=definido para ele. De qualquer forma, pode ser usado assim:

std::pair<int, int> intervals[]{
    {11, 20}, {31, 40}, {51, 60}, {71, 80}, {91, 100}
};

const int num = 15;
std::cout << in_any_interval(std::begin(intervals), std::end(intervals), num);

Há um exemplo ao vivo aqui .

chris
fonte
Solução legal. Eu provavelmente teria usado uma única matriz, já que você pode formatá-la com 2 números por linha para representar pares.
Kevin Lam
@ HunterGuy2, Muito bom ponto. Na verdade, vou alterá-lo para operar em pares porque, por algum motivo, só estava pensando em iteradores zip.
Chris
Abordagem de stl realmente agradável! Adoro!
higuaro
5

O primeiro é fácil. Você só precisa aplicar o operador de módulo ao seu valor num:

if ( ( num % 10 ) == 0)

Como o C ++ avalia cada número que não seja 0 como verdadeiro, você também pode escrever:

if ( ! ( num % 10 ) )  // Does not have a residue when divided by 10

Para o segundo, acho mais fácil entender:

O padrão se repete a cada 20, então você pode calcular o módulo 20. Todos os elementos que você deseja estarão em uma linha, exceto aqueles que são divisíveis por 20.

Para obtê-los também, use num-1 ou melhor, num + 19, para evitar lidar com números negativos.

if ( ( ( num + 19 ) % 20 ) > 9 )

Isso supõe que o padrão se repita para sempre, portanto, para 111-120 ele se aplicaria novamente e assim por diante. Caso contrário, você precisa limitar os números a 100:

if ( ( ( ( num + 19 ) % 20 ) > 9 ) && ( num <= 100 ) )
Kasimir
fonte
5

Com alguns bons comentários no código, ele pode ser escrito de forma bastante concisa e legível.

// Check if it's a multiple of 10
if (num % 10 == 0) { ... }

// Check for whether tens digit is zero or even (1-10, 21-30, ...)
if ((num / 10) % 2 == 0) { ... }
else { ... }
La-comadreja
fonte
2
O primeiro comentário é desnecessário. Qualquer programador com um pouco de experiência saberá que num % 10 == 0é a mesma coisa que numum múltiplo de 10.
Justin
7
sim, mas os iniciantes também leem este site. Eu normalmente não usaria esse comentário em meu próprio código, mas torna a resposta mais clara para os iniciantes que se beneficiariam com essa pergunta para iniciantes.
La-comadreja
2
Por favor, nunca faça isso. Na verdade, reduz a legibilidade, tornando o leitor lento e forçando-o a ler tudo duas vezes. Qualquer programador que não entende isso if (num % 10 == 0)significa o mesmo // Check if it's a multiple of 10que não deveria manter seu código. Este é um antipadrão bem conhecido.
Dawood ibn Kareem
1
@DavidWallace veja o comentário acima. Não podemos garantir que os leitores deste post conheçam esse antipadrão.
La-comadreja de
1
Não, quero dizer que comentar cada linha para dizer o que ela faz é um antipadrão. Não quero dizer que usar %seja um antipadrão; obviamente não é. Realmente, supondo que muitos dos leitores deste post sejam iniciantes, ensiná-los esse estilo de escrever comentários é uma contribuição negativa para seu desenvolvimento como programadores.
Dawood ibn Kareem
4

Você basicamente explicou a resposta sozinho, mas aqui está o código para garantir.

if((x % 10) == 0) {
  // Do this
}
if((x > 10 && x < 21) || (x > 30 && x < 41) || (x > 50 && x < 61) || (x > 70 && x < 81) || (x > 90 && x < 101)) {
  // Do this
}
Henry Harris
fonte
2
Corrija x < 41 x > 50e coloque parênteses.
101010
1
@ 40two, Tecnicamente, operator&&tem uma precedência maior do que operator||, então está bem, mas tenho certeza que o GCC avisa sobre isso de qualquer maneira.
chris de
18
Considere representar a desigualdade 10 < x < 21como em 10 < x && x < 21vez de x > 10 && x < 21. É mais fácil ler a desigualdade quando ela está na mesma ordem em que você a escreveria matematicamente.
Eric Lippert,
5
Este código é bastante ilegível e diz pouco sobre a lógica real. Eu não gosto dessa resposta.
Dariusz
3
Estou rejeitando isso porque você respondeu exatamente o que o OP fez.
Bruno Ferreira
3

Você pode estar pensando demais nisso.

if (x % 10)
{
   .. code for 1..9 ..
} else
{
   .. code for 0, 10, 20 etc.
}

A primeira linha if (x % 10)funciona porque (a) um valor que é um múltiplo de 10 calcula como '0', outros números resultam em seu restante, (b) um valor de 0 em um ifé considerado false, qualquer outro valor étrue .

Editar:

Para alternar entre os anos 20, use o mesmo truque. Desta vez, o número principal é 10:

if (((x-1)/10) & 1)
{
  .. code for 10, 30, ..
} else
{
   .. code for 20, 40, etc.
}

x/10retorna qualquer número de 0 a 9 como 0, 10 a 19 como 1e assim por diante. Testar em pares ou ímpares - o & 1- informa se é par ou ímpar. Como seus intervalos são, na verdade, "11 a 20", subtraia 1 antes de testar.

usr2564301
fonte
1

Um apelo à legibilidade

Embora você já tenha algumas boas respostas, gostaria de recomendar uma técnica de programação que tornará seu código mais legível para algum leitor futuro - que pode ser você em seis meses, um colega pediu para fazer uma revisão de código, seu sucessor .. .

Isso envolve qualquer instrução "inteligente" em uma função que mostra exatamente (com seu nome) o que está fazendo. Embora haja um impacto minúsculo no desempenho (de "sobrecarga de chamada de função"), isso é realmente insignificante em uma situação de jogo como essa.

Ao longo do caminho, você pode higienizar suas entradas - por exemplo, teste os valores "ilegais". Assim, você pode acabar com um código como este - vê o quanto ele é mais legível? As "funções auxiliares" podem estar escondidas em algum lugar (não precisam estar no módulo principal: é claro pelo nome o que fazem):

#include <stdio.h>

enum {NO, YES, WINNER};
enum {OUT_OF_RANGE=-1, ODD, EVEN};

int notInRange(int square) {
  return(square < 1 || square > 100)?YES:NO;
}

int isEndOfRow(int square) {
  if (notInRange(square)) return OUT_OF_RANGE;
  if (square == 100) return WINNER; // I am making this up...
  return (square % 10 == 0)? YES:NO;
}

int rowType(unsigned int square) {
  // return 1 if square is in odd row (going to the right)
  // and 0 if square is in even row (going to the left)
  if (notInRange(square)) return OUT_OF_RANGE; // trap this error
  int rowNum = (square - 1) / 10;
  return (rowNum % 2 == 0) ? ODD:EVEN; // return 0 (ODD) for 1-10, 21-30 etc.
                                       // and 1 (EVEN) for 11-20, 31-40, ...
}

int main(void) {
  int a = 12;
  int rt;
  rt = rowType(a); // this replaces your obscure if statement

  // and here is how you handle the possible return values:
  switch(rt) {
  case ODD:
    printf("It is an odd row\n");
    break;
  case EVEN:
    printf("It is an even row\n");
    break;
  case OUT_OF_RANGE:
    printf("It is out of range\n");
    break;
  default:
    printf("Unexpected return value from rowType!\n");
  }

  if(isEndOfRow(10)==YES) printf("10 is at the end of a row\n");
  if(isEndOfRow(100)==WINNER) printf("We have a winner!\n");
}
Floris
fonte
3
Não é tentar ir longe demais com YESe NO?
rmobis
@Raphael_ - pode muito bem ser: eu estava apenas mostrando um "por exemplo". Muitas pessoas usam verdadeiro / falso, obviamente. Mas eu nunca consigo lembrar (porque línguas diferentes usam diferentes convenções): é é TRUE, Trueou true? E quais arquivos de cabeçalho, se houver, eu precisaria incluir no C comum? Então eu rolei meu próprio. Imagino se foi isso que teve um downvote ...
Floris
1

Para o primeiro:

if (x % 10 == 0)

se aplicará a:

10, 20, 30, .. 100 .. 1000 ...

Para o segundo:

if (((x-1) / 10) % 2 == 1)

irá candidatar-se a:

11-20, 31-40, 51-60, ..

Basicamente, primeiro fazemos x-1para obter:

10-19, 30-39, 50-59, ..

Em seguida, os dividimos por 10para obter:

1, 3, 5, ..

Portanto, verificamos se esse resultado é estranho.

Khaled.K
fonte
1

Você pode tentar o seguinte:

        // multiple of 10
        if ((num % 10) == 0)
        {
           // Do something
        }
        else if (((num / 10) % 2) != 0)
        {
            //11-20, 31-40, 51-60, 71-80, 91-100
        }
         else
        {
            //other case
        }
ShalakaV
fonte
Na questão OP, a verificação para o múltiplo de 10 não está relacionada à verificação de intervalo, e na verificação de intervalo 20 precisa estar no mesmo intervalo de 11, com seu código ((20/10)% 2) -> ( 2% 2) -> 0
Serpiton
0

Eu sei que essa pergunta tem tantas respostas, mas vou jogar a minha aqui mesmo assim ...

Retirado do código completo de Steve McConnell , 2ª edição: "Tabelas de acesso em escada:

Ainda outro tipo de acesso à mesa é o método de escada. Este método de acesso não é tão direto quanto uma estrutura de índice, mas não desperdiça muito espaço para dados. A ideia geral das estruturas em degraus, ilustrada na Figura 18-5, é que as entradas em uma tabela são válidas para intervalos de dados, e não para pontos de dados distintos.

Insira a descrição da imagem aqui

Figura 18-5 A abordagem em degraus categoriza cada entrada, determinando o nível em que ela atinge uma "escada". O “passo” que atinge determina sua categoria.

Por exemplo, se você está escrevendo um programa de avaliação, o intervalo de entrada “B” pode ser de 75 por cento a 90 por cento. Aqui está uma série de notas que você pode ter que programar algum dia:

Insira a descrição da imagem aqui

Para usar o método da escada, você coloca a extremidade superior de cada faixa em uma tabela e, em seguida, escreve um loop para comparar a pontuação com a extremidade superior de cada faixa. Quando você encontra o ponto em que a pontuação excede o topo de uma faixa, você sabe qual é a nota. Com a técnica de degraus, você deve ter cuidado para lidar com os pontos finais dos intervalos de maneira adequada. Este é o código em Visual Basic que atribui notas a um grupo de alunos com base neste exemplo:

Insira a descrição da imagem aqui

Embora este seja um exemplo simples, você pode facilmente generalizá-lo para lidar com vários alunos, vários esquemas de notas (por exemplo, diferentes notas para diferentes níveis de pontuação em diferentes tarefas) e mudanças no esquema de notas. "

Code Complete , 2ª edição, páginas 426-428 (Capítulo 18).

lauCosma
fonte
por que você acha que esta é a pergunta errada? só porque eu não dei um exemplo no caso OP não significa que eu respondi a pergunta errada ...
lauCosma
0

Como outros apontaram, tornar as condições mais concisas não acelera a compilação ou a execução e não necessariamente ajuda na legibilidade.

Isso pode ajudar a tornar seu programa mais flexível, caso você decida posteriormente que deseja uma versão infantil do jogo em um tabuleiro de 6 x 6 ou uma versão avançada (que você pode jogar a noite toda) em um tabuleiro de 40 x 50 .

Então, eu iria codificá-lo da seguinte maneira:

// What is the size of the game board?
#define ROWS            10
#define COLUMNS         10

// The numbers of the squares go from 1 (bottom-left) to (ROWS * COLUMNS)
// (top-left if ROWS is even, or top-right if ROWS is odd)
#define firstSquare     1
#define lastSquare      (ROWS * COLUMNS)
// We haven't started until we roll the die and move onto the first square,
// so there is an imaginary 'square zero'
#define notStarted(num) (num == 0)
// and we only win when we land exactly on the last square
#define finished(num)   (num == lastSquare)
#define overShot(num)   (num > lastSquare)

// We will number our rows from 1 to ROWS, and our columns from 1 to COLUMNS
// (apologies to C fanatics who believe the world should be zero-based, which would
//  have simplified these expressions)
#define getRow(num)   (((num - 1) / COLUMNS) + 1)
#define getCol(num)   (((num - 1) % COLUMNS) + 1)

// What direction are we moving in?
// On rows 1, 3, 5, etc. we go from left to right
#define isLeftToRightRow(num)    ((getRow(num) % 2) == 1)
// On rows 2, 4, 6, etc. we go from right to left
#define isRightToLeftRow(num)    ((getRow(num) % 2) == 0)

// Are we on the last square in the row?
#define isLastInRow(num)    (getCol(num) == COLUMNS)

// And finally we can get onto the code

if (notStarted(mySquare))
{
  // Some code for when we haven't got our piece on the board yet
}
else
{
  if (isLastInRow(mySquare))
  {
    // Some code for when we're on the last square in a row
  }


  if (isRightToLeftRow(mySquare))
  {
    // Some code for when we're travelling from right to left
  }
  else
  {
    // Some code for when we're travelling from left to right
  }
}

Sim, é prolixo, mas deixa claro exatamente o que está acontecendo no tabuleiro.

Se eu estivesse desenvolvendo este jogo para ser exibido em um telefone ou tablet, faria variáveis ​​ROWS e COLUMNS em vez de constantes, para que pudessem ser definidas dinamicamente (no início de um jogo) para corresponder ao tamanho da tela e orientação.

Eu também permitiria que a orientação da tela fosse alterada a qualquer momento, no meio do jogo - tudo que você precisa fazer é mudar os valores de LINHAS e COLUNAS, deixando todo o resto (o número do quadrado atual em que cada jogador está, e o quadrados inicial / final de todas as cobras e escadas) inalterados. Então você 'apenas' tem que desenhar bem o quadro e escrever o código para suas animações (presumo que seja esse o propósito de suas ifdeclarações) ...

Laurence Renshaw
fonte
Também votei positivamente na resposta de Floris - um estilo diferente de alcançar um resultado semelhante, que não vi antes de escrever minha resposta
Laurence Renshaw
2
você deve usar a função inline em vez de#define
Bryan Chen
É uma boa prática, ao usar #defineinstruções semelhantes a funções , colocar parênteses em torno dos argumentos, onde eles aparecem na expansão. Então, ao invés de #define finished(num) (num == lastSquare)você deve escrever #define finished(num) ((num) == lastSquare). O motivo é que, se você usar essa instrução com uma expressão contendo um operador com precedência baixa o suficiente, não obterá a resposta que espera. Neste caso, se você não usar os parênteses extras, então finished(a & b)expande o (a & b == lastSquare)que quase certamente não é o que você deseja.
Dawood ibn Kareem