Paulo Henrique Rodrigues Pinheiro

Um blog sobre programação para programadores!


Como saber se um número é par de forma complicada

Uma coletânea sobre diversas formas de descobrir se um número é par ou não em Go

Definição de um número par

https://pt.wikipedia.org/wiki/Paridade

Um thread bem divertida no twitter

Em um thread, é apresentada uma situação absurda em que a função proposta para descobrir se um número é par era uma sequência de ifs para cada número retornando True ou False, e a autora, ironicamente pergunta se há um meio para fazer isso de maneira mais fácil:

https://twitter.com/ctrlshifti/status/1288745146759000064

Eu peguei a imagem e postei no Linkedin, perguntando "Como proceder se der de cara com isso em um PR? :)" e foi um baita clickbait. Dentro da minha bolha a galera reagiu com bom humor, mas fora teve respostas interessantes sobre como mentorizar ou orientar alguém que fizesse isso, e até mesmo rendendo uns comentários mais exaltados tipo "por que expor dessa forma alguém que está começando?":

https://www.linkedin.com/posts/paulohrpinheiro_como-proceder-se-der-de-cara-com-isso-em-activity-6694579298629685248-eXyQ

No twitter também rolou alguns casos de haters com a mulher que postou isso. Alguns a ironizando por ser mulher. Outros desesperados apontando a solução óbvia em tom de lacração.

É nítido, em tudo isso, a exasperação ou incapacidade de muitos, para entender um texto. Reflexo desse triste momento em que o mundo vive de trevas econômicas, sociais, sanitárias e culturais.

O aprendizado

Mas a galera do bem sempre se sobressai. E as alternativas para esse trivial problema demonstram uma criatividade admirável. Eu escolhi algumas delas, pois a matemática como ciência já é um interesse especial para mim, e também tive a ideia de reescrever em Go os algoritmos, linguagem que estou aprendendo. Tem muito mais coisa lá, vale o passeio pela thread.

Também deixei no backlog outras sugestões para um próximo com ideias ainda mais surpreendentes.

https://los40.com.mx/los40/2017/03/30/moda/1490903640_358806.html

Vamos neeessa!

Isso é pra quem tem referências :)

Mais uma piada antes de começar

O @paullouth sugeriu a seguinte solução:


package main

import "fmt"

func isOdd(n int) bool {
        return !isEven(n)
}

func isEven(n int) bool {
        return !isOdd(n)
}

func main() {
        fmt.Println(isEven(10))
}

Que obviamente congelou minha modesta máquina por alguns minutos:

Execução com sigkill do programa proposto

Não resisto, mais uma

O @panondo argumentou que performance é o que interessa:


func isEven(n int) bool {
    return true
}

Verificar o último dígito (@ctrlshifti)


package main

import (
 "strconv"

 "github.com/paulohrpinheiro/mytest"
)

func isEven(n int) bool {
 s := strconv.Itoa(n)
 l := s[len(s)-1]

 return l == '0' || l == '2' || l == '4' || l == '6' || l == '8'
}

func main() {
 mytest.Run(1000, isEven)
}

Minha versão com expressão regular

Inspirado por essa versão, lembrei dessa grande bala de prata, geralmente usada pra fazer a coisa errada no lugar errado!


package main

import (
        "regexp"
        "strconv"

        "github.com/paulohrpinheiro/mytest"
)

func isEven(n int) bool {
        var r = regexp.MustCompile(`[02468]$`)
        s := strconv.Itoa(n)

        return r.MatchString(s)
}

func main() {
        mytest.Run(1000, isEven)
}

Multiplicando por dez e dividindo por dois (@KaptenUppenbar)

Mais uma genial ideia, afinal, números pares nessa condição terão como último dígito o zero e números ímpares o cinco. Perceba-se que ele multiplicou por dez apenas adicionando um zero ao final do número.

A ideia original for apresentada em Python:


is_even = lambda x: str(int(int(str(x)+'0')/2)).endswith('0')

Em Go pode-se ter:


package main

import (
        "fmt"
        "strconv"
)

func isEven(n int) bool {
        p := strconv.Itoa((n * 10) / 2)

        return p[len(p)-1] == '0'
}

func main() {
        fmt.Printf("127 is even? %v\n", isEven(127))
        fmt.Printf("388 is even? %v\n", isEven(388))
        fmt.Printf("  0 is even? %v\n", isEven(0))
        fmt.Printf("-10 is even? %v\n", isEven(-10))
        fmt.Printf("-11 is even? %v\n", isEven(-11))
}

Não usei o pacote de teste, pois ao multiplicar por 10, estamos fora dos limites do tipo numérico.

Execução do programa em terminal

Inspirado no anterior, criei esse

Aqui, usando a divisão inteira, se o número for par, ao dividi-lo por dois, e então multiplicá=lo por dois, deve-se ter o mesmo número:


package main

import (
        "github.com/paulohrpinheiro/mytest"
)

func isEven(n int) bool {
        return (n/2)*2 == n
}

func main() {
        mytest.Run(1000, isEven)
}

Corre-se o risco de se o compilador otimizar a expressão, cairmos em uma tautologia.

Pegada funcional, com guardas e recursão (@arara_)

Esse aqui me deu uma vontade louca de voltar a estudar Haskell.


package main

import "fmt"

func isEven(n int8) bool {
 if n == 1 {
        return false
 }
 if n >= 2 {
        return isEven(n - 2)
 }
 if n <= -1 {
        return isEven(n + 2)
 }

 // n == 0
 return true
}

func main() {
 fmt.Printf("10 is even? %v\n", isEven(10))
 fmt.Printf("15 is even? %v\n", isEven(15))
}

Não pude usar o pacote de teste por conta dos extremos que ele usa.

Falando em Haskell, perdi um tempo desenferrujando minhas desabilidades nessa linguagem e não é que saiu uma função? Peguei o máximo de um inteiro de 64 bits e chamei a função e deixei calculando. Demorando muito pra ter um resultado, mas a máquina está usável (no tempo em que estou escrevendo esse texto tem um processador torrando aqui). Recursão ali é outra conversa.


isEven :: Int -> Bool
isEven n | n == 1    = False
         | n >= 2    = isEven (n - 2)
         | n <  0    = isEven (n + 2)
         | otherwise = True -- n==0

Testes

Como pode ser observado, há um package para testar as funções:


package mytest

import (
        "fmt"
        "math/rand"
        "time"
)

// https://stackoverflow.com/questions/6878590/the-maximum-value-for-an-int-type-in-go
const maxUint = ^uint(0)
const minUint = 0
const maxInt = int(maxUint >> 1)
const minInt = -maxInt - 1

func Run(randoms int, isEven func(n int) bool) bool {
        zeroTest := zero(isEven)
        maxTest := max(isEven)
        minTest := min(isEven)
        positiveTest := positive(randoms, isEven)
        negativeTest := negative(randoms, isEven)

        final := zeroTest && maxTest && minTest && positiveTest && negativeTest
        s := "PASS"

        if !final {
                s = "NO PASS"
        }

        fmt.Println("Final result: ", s)

        return final
}

func myIsEven(n int) bool {
        return n%2 == 0
}

func zero(f func(n int) bool) bool {
        if f(0) == true {
                return true
        }

        fmt.Printf("zero FAIL! 0 is even.\n")
        return false
}

func max(f func(n int) bool) bool {
        if f(maxInt) == myIsEven(maxInt) {
                return true
        }

        fmt.Printf("max FAIL! %v is even.\n", maxInt)
        return false
}

func min(f func(n int) bool) bool {
        if f(minInt) == myIsEven(minInt) {
                return true
        }

        fmt.Printf("min FAIL! %v is even.\n", minInt)
        return false
}

func positive(randoms int, f func(n int) bool) bool {
        rand.Seed(time.Now().UnixNano())

        for i := 0; i < randoms; i++ {
                r := rand.Int()

                if f(r) != myIsEven(r) {
                        fmt.Printf("random FAIL! %v is not even.\n", r)
                        return false
                }
        }

        return true
}

func negative(randoms int, f func(n int) bool) bool {
        rand.Seed(time.Now().UnixNano())
        maxRandom := maxInt - 1

        for i := 0; i < randoms; i++ {
                r := rand.Intn(maxRandom) * (-1)

                if f(r) != myIsEven(r) {
                        fmt.Printf("random FAIL! %v is not even.\n", r)
                        return false
                }
        }

        return true
}

Espero em breve publicar o restante das soluções que separei.

Por hoje é isso, pratiquei Go me divertindo!

Agora temos a segunda parte:

https://paulohrpinheiro.xyz/texts/go/2020-08-30-como-saber-se-um-numero-e-par-de-forma-complicada-II.html