segunda-feira, 7 de abril de 2025

SELECÇÕES E FUNÇÕES

 A propósito de uma piada pythoniana que apareceu aí numa rede (ver imagem), lembrei-me de uma solução engraçada, puramente aritmética, para a questão da selecção de 3 hipóteses a partir de 2 valores.

Os valores limite são dois, 18 e 96, pelo que uma função de primeiro grau (linear) resolve o problema.

96-18=78, portanto o zero deste inusitado sistema métrico seria -60 (18-78).

Basta então transpor -60, 18, 96 (+60) para 0, 78, 156 e a expressão:

print(
    ["Sorry, you are not old enough to enter.",
    "You are over 18. Welcome!",
    "I've never met anyone anoe 95! But welcome anyway!"][int((age+60)/78)])

resolve o problema.

Ok! Não funciona para idosos com mais de 173 anos. Havia que fazer uma pequena adaptação:

print(
    ["Sorry, you are not old enough to enter.",
    "You are over 18. Welcome!",
    "I've never met anyone anoe 95! But welcome anyway!",
    "I've never met anyone anoe 95! But welcome anyway!"][int((age+60)/78)])

😁



quinta-feira, 3 de abril de 2025

COBOL para quê?


Toda a gente sabe que a COBOL foi inventada para programar sistemas de "negócio": ficheiros, relatórios, facturas e clientes.

O seu nome é aliás um acrónimo para a expressão Common Business Oriented Language, uma linguagem orientada portanto a negócios comuns.

Fora desse sentido, resolvi rever velhos tempos e implementar algo que mostra que poderá também ser usada para fins menos "cinzentos" e mais lúdicos.

Trata-se pois do conhecido jogo Torres de Hanoi sem grandes artifícios ou fancy UI mesmo à moda antiga quando CRT ainda era uma coisa.

O programa foi compilado em GnuCOBOL 3.2.0 mas a implementação foi feita sem idiomáticas modernas pelo que deve compilar em qualquer compilador.

O projecto está no GitHub e tem um vídeo a acompanhar.

Hope you like it.



segunda-feira, 24 de fevereiro de 2025

Programadores, essa raça de mal-humorados

Donald Knuth, alegadamente o GOAT dos programador de computadores, escreveu, em vários volumes, aquilo a que chamou "A arte da programação de computadores".

Pretendeu criar um repositório de algoritmos e técnicas de programação que pudesse servir de referência para profissionais e estudantes. É uma obra monumental, ainda em curso. Começou o trabalho na década de 1960 e já editou 4 dos 5 previstos volumes.


Como se trata de uma obra sobre algoritmos e técnicas de programação, haveria que incluir, obviamente, trechos de código para exemplificar este ou aquele conceito ou procedimento.

(parêntesis) no final do terceiro volume, descontente com os processos e resultados da edição tipográfica da época, resolveu criar um novo sistema digital para edição e impressão de textos, e criou então o Tex e o
Metafont
. (fecha parêntesis)


Colocava-se então a questão da escolha da linguagem a utilizar nos tais exemplos. À época, a escolha teria quase obrigatoriamente de recair sobre ALGOL ou Fortran e, não contente com a ideia por razões que explica mas que não vêm ao caso, resolveu então criar um novo computador (hipotético claro está) e uma linguagem-máquina para a sua programação.

Nasceu então o computador mítico MIX e a linguagem MIXAL que quem se aventurar na leitura da obra terá de, forçosamente, aprender.

Trinta anos passados, estamos em finais do século XX, e a evolução no meio quer em processadores quer em linguagens, remeteu MIX e MIXAL para o depósito dos fósseis, pelo que, para manter a total relevância da obra, havia que criar um novo computador e uma nova linguagem que acompanhasse essa a evolução. Nasce então o novo MMIX e a MMIXAL e novos suplementos e fascículos de atualização das centenas de rotinas MIX originais para o novo MMIX. Só de pensar dá suores.

A MMIXAL uma linguagem-máquina constituída por 256 operações diferentes; coisas como: ADD para somar, MUL para multiplicar, AND para conjugar, JMP para saltar, etc.

Acontece que foram necessárias apenas 255 pelo que faltava uma operação para perfazer o número redondo de 256.

Termina assim o manual da linguagem MMIXAL com a operação SWYM, nas palavras do próprio Knuth:

"SWYM X,Y,Z (Sympathize With Your Machinery) - The last of MMIX's 256 opcodes is, fortunately, the simplest off all. In fact, it is often called a no-op, because ir performs no operation. It does, however, keep the machine running smoothly, just as a real-world swimming helps to keep programmers healthy. Bytes X, Y, and Z are ignored."

segunda-feira, 25 de novembro de 2024

Ó IA, traduz isto ...

A Torre de Babel
Bruegel o Velho
c. 1563
 

Neste artigo proponho-me relatar a experiência na utilização de uma ferramenta de IA, no caso o Copilot (via Bing), na tradução de um pedaço de código Python para Erlang.

Desde logo convém realçar o desafio, que não se trata apenas de uma tradução gramatical entre duas linguagens mas, mais do que isso, uma tradução semântica entre linguagens de paradigmas totalmente distintos, pelo menos na utilização que aqui lhes dou:


Introdução


Imutabilidade


Esta é uma característica típica de linguagens funcionais (desde logo a Erlang mas também Haskell, Scala, F#, Clojure) e que permite, por exemplo, eliminar do horizonte muitos efeitos secundários perniciosos que advêm da partilha de memória (conteúdos de variáveis) em sistemas com concorrência.

Ao primeiro contacto, apresenta-se como uma limitação ao programador que venha de uma linguagem, normalmente procedimental ou orientada a objectos, em que o conteúdo de uma variável pode ser livremente alterado.

Estruturas como ciclos e acumulações, parecem impossíveis de resolver. 

Um exemplo de um algoritmo trivial de cálculo do valor médio de um vector de números em C.

#include <stdio.h>

int soma(int vector[], int size) {
    int total = 0;
    // ALERTA MUTABILIDADE i++
    for(int i=0; i<size; i++) { //
        // ALERTA MUTABILIDADE total +=
        total += vector[i];
    }
    return total;
}

double media(int vector[], int size) {
    return soma(vector, size) / (size + 0.0);
}

int main()
{
    int valores[] = {10, 12, 15, 30, 50, 12, 45};
    printf("Media: %f", media(valores, 7));
    return 0;
}

Media: 24.857143

...Program finished with exit code 0
Press ENTER to exit console.

Ou uma versão equivalente em Python, um pouco menos mutável mas não totalmente:

def soma(vector):
    total = 0
    for valor in vector:
        # ALERTA MUTABILIDADE total +=
        total += valor
    return total

def media(vector):
    return soma(vector) / len(vector)

vector = [10, 12, 15, 30, 50, 12, 45]

print(media(vector))

PS .\Source\Python\ProjectEuler\media> python.exe .\media.py  
24.857142857142858

Perante problemas deste tipo, as linguagens funcionais com imutabilidade de variáveis fazem uso, extensivo, da recursividade.

Exemplificando o mesmo problema em Erlang, mas antes alguma semântica básica introdutória para os menos versados.

[H|T] - Lista em que H , head, cabeça, é o primeiro elemento da lista e T uma lista com os elementos seguintes.

[_|T] - Lista em que não queremos saber do primeiro elemento para nada.

[H|[]] - Lista composta por um elemento, no caso H sendo o resto a lista vazia [].


Pattern matching


Em Erlang, uma função pode ter várias "diferentes implementações" sendo a sua selecção e utilização dependente dos argumentos fornecidos.

Cada um dos casos será inspeccionado e o primeiro em que os argumentos fornecidos correspondam à definição será utilizado.

Exemplo:

len([_|[]], Acc) será utilizada sempre que a lista fornecida tenha apenas um elemento, caso contrário será utilizada len([_|T], Acc).

-module(media).

-export([soma/2, len/2, media/1, run/1]).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%% Tamanho de uma lista %%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% caso base
len([_|[]], Acc) ->
    Acc + 1;
% caso geral
len([_|T], Acc) ->
    % recursão extraindo o primeiro valor da lista
    len(T, Acc + 1).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%% Soma de uma lista %%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% caso base
soma([H|[]], Acc) ->
    H + Acc;
% caso geral
soma([H|T], Acc) ->
    % recursão usando e extraindo o primeiro valor da lista
    soma(T, H + Acc).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Média dos valores  de uma lista %%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

media(Vector) ->
    soma(Vector, 0) / len(Vector, 0).

run(Vector) ->
    io:format("Media: ~w~n", [media(Vector)]).


Erlang/OTP 26 [erts-14.1.1] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns]

Eshell V14.1.1 (press Ctrl+G to abort, type help(). for help)
1> c(media).                                                                                                                                                    
{ok,media}
2> media:run([10, 12, 15, 30, 50, 12, 45]).                                                                                                                    
Media: 24.857142857142858
ok
3>


O caso


Quem gosta de linguagens de programação (e de programar) está sempre à procura de desafios, e um local salvífico do tédio do programador é o Project Euler (vão ver).

Um problema que num daqueles momentos resolvi tentar resolver é o problema 31 e consiste em calcular quantas maneiras diferentes há de perfazer um determinado valor monetário tendo em conta os tipos de moedas e notas disponíveis.

No problema é dado o exemplo inglês mas pode, sem alteração, ser usado em Portugal com euros: completar 2 euros com moedas de 1, 2, 5, 10, 20 e 50 cêntimos e de 1 e 2 euros.

Segundo o portal, o problema foi solucionado 92203 vezes e é de dificuldade elementar (5%).

Há soluções em todas as linguagens, algumas lindíssimas e curiosíssimas, e eu tentei também a minha sorte com Pyhton.

Cá está ela:

import math

def calc(target, coins):
    if len(coins) == 1:
        return 1 if target % coins[0] == 0 else 0

    total = 0
    for i in range(len(coins)):
        c = math.floor(target/coins[i])
        while c > 0:
            remaining = target - c * coins[i]
            if remaining == 0:
                total += 1
            elif remaining > 0:
                total += calc(remaining, coins[i+1:])
            c -= 1
    return total

coins = [200,100,50,20,10,5,2,1]

print(calc(200, coins))

O algoritmo é básico, poderá certamente ser melhorado, was not my point, mas despacha-se razoavelmente bem até um valor de teste de "bom senso". :)

Por exemplo:
  • 1 cêntimo (1 maneira)
  • 2 cêntimos (2 maneiras)
  • 10 cêntimos (11)
  • 2 euros (73682)
  • 3 euros (471363)
  • 5 euros (6295434) <- 5 segundos
  • 6 euros (16960860) <- 11 segundos
E não vale a pena alargar mais o leque que o tempo é exponencial.


A "funcionalização" do algoritmo


Foi então que resolvi reescrever o mesmo algoritmo em Erlang, e com se ouve dizer que um das ajudas que a IA podia dar na programação é a de, precisamente, traduzir linguagens de programação, passei a palavra ao Copilot no Bing.

Solicito educadamente "Podes traduzir este pedaço de código Python para Erlang?" e replico o código fonte que mostrei acima.

Responde quase imediatamente, embora não pareça totalmente confiante: "Este código Erlang deve replicar a lógica do código Python que forneceste. Se tiveres alguma dúvida ou precisares de mais alguma coisa, estou aqui para ajudar!"

-module(coin_calc).
-export([calc/2]).

calc(Target, Coins) ->
    case length(Coins) of
        1 ->
            if Target rem hd(Coins) =:= 0 -> 1;
               true -> 0
            end;
        _ ->
            calc(Target, Coins, 0)
    end.

calc(_, [], Total) -> Total;
calc(Target, [Coin | Rest], Total) ->
    C = Target div Coin,
    calc(Target, Coin, Rest, Total, C).

calc(_, _, _, Total, 0) -> Total;
calc(Target, Coin, Rest, Total, C) ->
    Remaining = Target - C * Coin,
    NewTotal = if Remaining =:= 0 -> Total + 1;
                  Remaining > 0 -> Total + calc(Remaining, Rest);
                  true -> Total
               end,
    calc(Target, Coin, Rest, NewTotal, C - 1).

Vamos lá experimentar:

Erlang/OTP 26 [erts-14.1.1] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns]

Eshell V14.1.1 (press Ctrl+G to abort, type help(). for help)
1> c(coin_calc).
{ok,coin_calc}
2> coin_calc:calc(200,[200,100,50,20,10,5,2,1]).
1
3>

Hmm! 1?

Voltou ao meu amigo: "Infelizmente não está a funcionar correctamente, devolve sempre o valor 1." ao que me responde solícito: "Entendi, vamos tentar corrigir isso. Aqui está uma versão ajustada do código Erlang. Nesta versão, adicionei uma função calc_loop para iterar sobre as combinações de moedas e ajustar a contagem total. Tente este código e veja se ele resolve o problema. Se precisar de mais alguma coisa, estou aqui para ajudar!"

-module(coin_calc).
-export([calc/2]).

calc(Target, Coins) ->
    calc(Target, Coins, 0).

calc(0, _, Total) ->
    Total + 1;
calc(_, [], Total) ->
    Total;
calc(Target, [Coin | Rest], Total) ->
    C = Target div Coin,
    calc_loop(Target, Coin, Rest, Total, C).

calc_loop(_, _, _, Total, 0) ->
    Total;
calc_loop(Target, Coin, Rest, Total, C) ->
    Remaining = Target - C * Coin,
    NewTotal = if Remaining =:= 0 -> Total + 1;
                  Remaining > 0 -> Total + calc(Remaining, Rest);
                  true -> Total
               end,
    calc_loop(Target, Coin, Rest, NewTotal, C - 1).


Não resolveu.

Insisti: "Mesmo resultado errado; sempre 1." ao que me respondeu com "Vamos tentar uma abordagem diferente para garantir que a lógica está correta. Aqui está uma versão revisada do código Erlang:" mas com o código exactamente igual ao anteriormente fornecido.

Fiz uma última tentativa, dando uma ajuda: "Não. O código é exactamente igual ao anterior. Parece-me que falta uma chamada recursiva algures." mas sem resultado.

Repetiu o código e eu deixei-o em paz.

Posso não estar a fazer as perguntas certas e pode o Copilot via Bing não ser ferramenta adequada (testei no ChatGPT 3.5 com resultados erradamente semelhantes; se alguém ler isto e quiser testar noutra plataforma, dê notícias, obrigado). 

Algo ressalta, a meu ver, desta experiência.


Conclusão


A tradução efectuada está errada (abaixo apresento uma versão corrigida) e pude confirmar que estava errada apenas porque o resultado era óbvia e evidentemente errado: 1, sempre.

Imaginemos agora que o resultado era errado mas não desta forma gritante, só "ligeiramente". Que hipótese teria eu de o validar? Usar o código Python, que sei correcto, para validar respostas da versão Erlang? Imaginemos outro tipo de problemas com entradas e respostas mais complexas.

É impraticável.

E depois de confirmado o erro, como descobrir onde está o bug?

A menos que se saiba algo, bastante mais do que o trivial de Erlang, dificilmente se será capaz de tornar o código utilizável.


Posfácio


O código corrigido (a  amarelo  a alteração):

-module(coin_calc).
-export([calc/2]).

calc(Target, Coins) ->
    calc(Target, Coins, 0).

calc(0, _, Total) ->
    Total + 1;
calc(_, [], Total) ->
    Total;
calc(Target, [Coin | Rest], Total) ->
    C = Target div Coin,
    calc_loop(Target, Coin, Rest, Total, C).

calc_loop(Target, _, Rest, Total, 0) ->
    Total + calc(Target, Rest, 0);
calc_loop(Target, Coin, Rest, Total, C) ->
    Remaining = Target - C * Coin,
    NewTotal = if Remaining =:= 0 -> Total + 1;
                  Remaining > 0 -> Total + calc(Remaining, Rest);
                  true -> Total
               end,
    calc_loop(Target, Coin, Rest, NewTotal, C - 1).

Curiosidade: tempos de execução em Erlang aproximadamente 90% mais rápidos do que a versão em Python.


quinta-feira, 7 de novembro de 2024

Que língua é esta?


 As frequências relativas de cada uma das letras no conjunto de palavras de uma língua constituem uma característica única dessa língua.

Sabemos, por exemplo que a letra a tem uma frequência relativa de 14.6% no português mas cerca de 6.0% no alemão.

Se conhecermos estas frequências relativas para várias línguas, um vector de percentagens para cada língua, basta-nos calcular o vector de frequências relativas das letras do texto que pretendemos analisar e tentar identificar de qual dos vectores de língua mais se aproxima.

É o que pretendo fazer, sem fazer uso de nenhum tipo de repositório lexical (dicionário).

O exercício será executado em linguagem R mas não tem nada de particular, aconselhado ou desaconselhado, que impeça a sua realização noutra linguagem qualquer.

Frequência de letras de idiomas

O repositório de dados que vai ser utilizado é algo reduzido mas suficiente para o conceito que se pretende demonstrar. Representa 15 idiomas (inglês, francês, alemão, espanhol, português, italiano, turco, sueco, polaco, neerlandês, dinamarquês, islandês, finlandês, checo e húngaro) e pode ser encontrado na Wikipedia no verbete Letter frequency.

A extração da tabela foi feita de forma manual, simples copy/paste

Tabela de frequências relativas

com edição também manual mínima, ou até nenhuma, se a seleção for feita com cuidado.



Embora a tabela inclua todas as letras usadas nos idiomas representados, incluindo caracteres acentuados ou específicos, vão ser usadas apenas as letras de [a-z].

Os dados obtidos assim diretamente da página constituem linhas de valores separados por TAB, pelo que o ficheiro resultante é um ficheiro de texto.tsv (tab separated values).

Note-se que os valores são representados de maneira formatada para leitura, em pontos percentuais e incluindo o caractere %. Alguns valores incluem ainda outro tipo de caracteres não numéricos como '(', ')', '~' e '*', assim como a linha de título inclui algumas indicações de notas de rodapé [dd]; tudo isto será retirado no script de forma automatizada.

O processo

1) Ler o conteúdo do ficheiro

Para tal vai usar-se a package readr (Read Rectangular Text Data) que contém variadas funções de leitura de ficheiros de texto em vários formatos, nomeadamente o tsv.

library(readr) 

df <- readr::read_tsv("data/letter_frequencies.tsv", col_names = TRUE)

O que resulta numa estrutura do tipo data-frame com o seguinte aspecto:

2) Limpeza e normalização de dados

Como já referido, há caracteres que devem ser retirados. Para tal vai usar-se a substituição por expressão regular gsub.

Primeiramente a linha de cabeçalho

colnames(df) <- gsub('([a-zA-Z]*)(.*)', '\\1', colnames(df))


De seguida uma função geral de retirada dos caracteres %, ~, ( e )

clean_values <- function(data) {

    as.numeric(gsub('([0-9\\.]*)([%~\\*\\(\\)])', '\\1', data))

}

aplicada através da função sapply às colunas de valores df[2:ncol(df)]

df <- data.frame(df[1], sapply(df[2:ncol(df)], clean_values))

Após estas operações, o data-frame de frequências relativas por idioma fica com o seguinte aspecto:

3) Analisar o texto de que queremos identificar o idioma

O objectivo aqui é calcular um vector de frequências relativas das letras presentes no texto a analisar.

Começamos então por calcular as frequências absolutas:

  letter_count <- (
    text
    %>% tolower 
    %>% strsplit(split="") 
    %>% unlist 
    %>% `[`(!. %in% c("", " ", ".", ",")) 
    %>% table
    %>& data.frame)

Nota: usamos aqui o operador forward-pipe %>% apenas como curiosidade; é possível executar as mesmas operações usando a idiomática funcional típica. text %>% tolower é equivalente a     tolower(text).

Aplicando então à frase "as armas e os baroes assinalados que da ocidental praia lusitana" resulta no data-frame:


Precisamos de tornar este data-frame comparável com os vectores de frequências dos idiomas pelo que temos de incluir nele também as letras que não fazem parte do texto.

Para tal usamos a função merge que faz a junção de dois vectores (de forma semelhante ao JOIN do SQL) com a opção de incluir todos os valores do vector de letras dos idiomas (uma espécie de LEFT JOIN em SQL).

    merge(x = frequency_df[1], y = letter_count, by = c("Letter"), all.x = TRUE)

com o resultado:


Substituídos os NA por 0

    letter_count[is.na(letter_count)] <- 0

e calculadas as frequências relativas

    letter_count$Percentage <- 100 * letter_count$Frequency /
        sum(letter_count$Frequency)

obtemos o vector de frequências relativas que será usado para comparação com os vectores de idioma; a coluna Percentage do data-frame:


A coluna Percentage é directamente comparável com as colunas de idioma, já que tem o mesmo número de valores e dispostos pela mesma ordem (alfabética das letras respectivas), pelo que podemos calcular a distância entre cada uma destas e aquela e verificar então qual dos idiomas se encontra mais próximo.

Notar que cada um destes vectores de 26 valores contém, nem mais nem menos, do que as coordenadas de um ponto de um hipotético espaço 26-dimensional, pelo que é aplicável qualquer uma das fórmulas típicas de distância.

Desde logo a distância euclidiana:


em que n = 26

ou, a que valos utilizar, a distância de Manhatan:

com a instrução

    t(data.frame(lapply(df[2:ncol(df)], manhattan, letter_count$Percentage)))

em que se verifica que idioma mais próximo é o Português.


4) Teste

Partimos então da frase em português "as armas e os baroes assinalados que da ocidental praia lusitana" a qual traduzimos com o google translator para cada um dos restantes 14 idiomas (sem preocupações poéticas):

    phrase_pt <- "as armas e os baroes assinalados que da ocidental praia lusitana"
    phrase_en <- "the weapons and barons marked from the western Lusitanian beach"
    phrase_tk <- "Batı Lusitanya sahilinden işaretlenmiş silahlar ve baronlar"
    phrase_fr <- "les armes et les barons marquaient celui de la plage ouest de la Lusitanienne"
    phrase_de <- "Die Waffen und Barone markierten das vom westlichen lusitanischen Strand aus"
    phrase_es <- "las armas y los barones marcaron eso desde la playa lusitana occidental"
    phrase_it <- "le armi e i baroni lo segnavano dalla spiaggia lusitana occidentale"
    phrase_se <- "vapnen och baronerna markerade det från den västra lusitanska stranden"
    phrase_pl <- "broń i baronowie oznaczali to z zachodniej plaży Lusitanii"
    phrase_nl <- "de wapens en baronnen markeerden dat vanaf het westelijke Lusitaanse strand"
    phrase_dk <- "våbnene og baronerne påpegede, at der fra den vestlige lusitanske"
    phrase_is <- "vopnin og barónarnir merktu það frá vesturhluta Lusitanian ströndinni"
    phrase_fi <- "aseet ja paronit merkitsivät sen Länsi-Lusitanian rannalta"
    phrase_ck <- "Zbraně a baroni označili,To ze západní pláže Lusitana"
    phrase_hu <- "A fegyverek és a bárók megjelölték, A nyugati Lusitana strandé"

agrupadas numa estrutura apropriada:

    test_df <- data.frame(
      rbind(
        c("English", phrase_en), 
        c("French", phrase_fr), 
        c("German", phrase_de), 
        c("Spanish", phrase_es), 
        c("Portuguese", phrase_pt), 
        c("Italian", phrase_it), 
        c("Turkish", phrase_tk), 
        c("Swedish", phrase_se), 
        c("Polish", phrase_pl), 
        c("Dutch", phrase_nl), 
        c("Danish", phrase_dk), 
        c("Icelandic", phrase_is), 
        c("Finnish", phrase_fi), 
        c("Czech", phrase_ck), 
        c("Hungarian", phrase_hu)))

    colnames(test_df) <- c("Idiom", "SampleText")

e encapsulamos numa função as operações referidas acima:

idiom_inference <- function(text, frequency_df, distance_fn) {

  # getting a data frame of the absolute frequencies of individual letters
  # present on `text`
  letter_count <- (
    text
    %>% tolower
    %>% strsplit(split="") 
    %>% unlist 
    %>% `[`(!. %in% c("", " ", ".", ",")) 
    %>% table
    %>% data.frame)

  # giving the letters data frame a proper nice structure
  colnames(letter_count) = c("Letter", "Frequency")

  # and merging it with the original all-letters columns
  # in order to get a complete vector
  letter_count <- merge(x = frequency_df[1], y = letter_count, by = c("Letter"), all.x = TRUE)

  # letter missing in `text` get a NA value so we transform that in 0
  letter_count[is.na(letter_count)] <- 0

  # and calculate the relative frequency
  letter_count$Percentage <- 100 * letter_count$Frequency / sum(letter_count$Frequency)

  # finally we apply the distance function between out text relative frequency vector
  # to each of the idioms relative frequency
  distances <- t(data.frame(lapply(frequency_df[2:ncol(frequency_df)], distance_fn, letter_count$Percentage)))

  # and return then rowname, which is the idiom name, of the lesser
  rownames(distances)[which(distances == min(distances), arr.ind=TRUE)[,1]]
}

que se executa com:

    infered <- lapply(X=test_df$SampleText, FUN=idiom_inference, df, abdiv::manhattan)

ontem os seguintes resultados:

5) Conclusões

A eficiência não é total já que falhou dois idiomas, "confundindo" dinamarquês com holandês e checo com italiano.

Para tal poderá ter contribuído a relativamente reduzida dimensão do texto bem como a não consideração de caracteres acentuados ou específicos em alguns idiomas, como é precisamente o caso do dinamarquês e do checo embora não se tenha confundido no húngaro em que tal também se verifica.

Não sabemos também como se comportará com outro tipo de textos, mais longos ou mais curtos, pelo que há espaço para evolução.

Espero que tenham gostado do exercício e parece que já estou a ouvir: "em Python faz-se em 3 linhas".

Venham elas.

6) Código fonte

O projecto encontra-se no GitHub

quinta-feira, 31 de outubro de 2024

Mais um blogue?


Pois é.

Sou programador de computadores, gosto desta descrição, há mais tempo do que tempo ainda terei de vida.

Nunca fiz outra coisa.

Profissionalmente, terei já contactado com todas as principais linguagens de programação e, como passatempo, por outras tantas ou mais.

Sempre pensei partilhar um pouco da minha paixão pela programação, portanto, vai ser agora.

As publicações terão sempre algo a ver com programação de computadores mas não serão sempre código: breves notas biográficas, histórias e estórias mas, claro, principalmente código.

E nos casos de publicações com código, não pretendo mostrar genialidade ou sequer novidade; serão apenas exercícios sem nenhum outro objectivo que não a partilha de pequenos (ou grandes) prazeres e alguns deslumbramentos.

Em alguns casos serão certamente "invenções da roda"; não tenho, à partida, nenhum objectivo utilitarista, de eficiência ou desempenho, só prazer. E bem sabemos como o prazer pode ser ingénuo.

O primeiro fica já prometido: um script em R para identificar o idioma de um texto.

Liguem as antenas e digam coisas.

NOTA: sim, existem já, lá está, packages em R (e noutras linguagens talvez mais "apropriadas") com esse objectivo.


#c-memes