Skip to content

Tag: C

Pointers to functions in C++

I need to implements some codes in C++. Just remembering some concepts like pointers to functions.

A simple example:

#include <stdlib.h>
#include <iostream>

using namespace std;

double evalFunction(double (*f)(double), double param){
    return f(param);
}

double function(double x){
    return x/2;
}

int main(int argc, char** argv) {
    cout << function(5.0) << endl;
    cout << evalFunction(function, 5.0) << endl;
    return (EXIT_SUCCESS);
}

Gerando permutações

Muitas vezes para resolver uma única instância de um problema é mais rápido ataca-lo com força bruta do que encontrar um algoritmo geral com uma boa ordem de complexidade. Permutações são de grande utilidade nesse tipo de abordagem.

Permutações em Prolog:

Esse é um código em Prolog que o Wladimir Araujo passou na cadeira de IA.

select(X, [X|Xs], Xs).
select(X, [Y|Ys], [Y|Zs]) :- select(X, Ys, Zs).

permutar([], []).
permutar(Xs, [Z|Zs]) :-
    select(Z, Xs, Ys),
    permutar(Ys, Zs).

Permutações em Python:
Esse é um código de um certo Michael Davies que eu tirei daqui. Ele gera uma lista com todas as permutações de uma lista. Muito bonitinho. 🙂

def all_perms(str):
    if len(str) <=1:
        yield str
    else:
        for perm in all_perms(str[1:]):
            for i in range(len(perm)+1):
                yield perm[:i] + str[0:1] + perm[i:]

Um exemplo de uso:

>>> for p in all_perms(['a','b','c']):
	print p
['a', 'b', 'c']
['b', 'a', 'c']
['b', 'c', 'a']
['a', 'c', 'b']
['c', 'a', 'b']
['c', 'b', 'a']

Outras implementações:
Em outras linguagens o código para gerar permutações geralmente é muito grande, então eu preferi deixar alguns links.

XII Maratona Brasileira de Programação

Logo da Maratona Brasileira de Programação

Esse sábado eu participei, junto com o Carlos Pontual e o Heraldo Carneiro, da décima segunda edição da Maratona Brasileira de Programação.

Maratona Brasileira de Programação

A sede do Ceará na competição ia ser em Sobral, com o pessoal da Engenharia da Computação da UFC, mas acabou sendo na Unifor. Uma pena, eu queria ter viajado pra conhecer o curso novo.

Embora antigamente eu tenha competido na OBI (Olimpíada Brasileira de Informática), eu nunca havia competido na Maratona Brasileira de Computação. Enquanto a OBI é uma competição voltada para alunos do ensino médio e básico a maratona é voltada para alunos do ensino superior da graduação e mestrado. Pelas minhas contas já faziam aí uns 3 anos que eu não competia.

Para quem não conhece esse tipo de competição, funciona assim: uma pessoa ou uma equipe (dependendo da competição) tem um certo tempo para resolver uma série de problemas usando programação. A correção do programa é automatizada. Seu programa é testado através de uma bateria de testes e deve retornar as respostas corretas. É uma ótima forma de melhorar seus conhecimentos sobre grafos, lógica, programação dinâmica, estruturas de dados, programação etc. Também é uma ótima oportunidade para conhecer ou rever o pessoal dos cursos de computação.

Heraldo Carneiro, Silveira Neto e Carlos Pontual

Bem, vamos aos problemas que nós fizemos:

  • Varetas, problema H, era um problema bem simples. Esse nós fizemos em C e foi aceito de primeira.
  • Histórico, problema E, também um problema não muito complicado. Mas foi por ele que nós nos enrolamos. Nós resolvemos o problema em Java e submetemos, mas a correção deu runtime error para ele. Nós re analisamos o problema, modificamos o programa e mandamos novamente e ganhamos outro runtime error. Como nós estávamos bem confiantes que nossa resposta estava certa nós refizemos o programa em C e submetemos. Dessa vez o programa passou sem problemas. Mais Tarde ficamos sabendo que devido a um erro da correção automática, não havia como um programa em Java ter acertado essa questão. Isso fez que passemos 1 hora e 44 minutos nesse problema.
  • Rouba, problema B, basicamente um problema para simular um jogo de cartas. O Heraldo pegou esse problema e fez ele em Java. Depois de 3 submissões e 3 time limit exceeded da correção automática, nós estávamos certos que nosso programa estava correto. Nós já haviamos feitas varias otimizações de velocidade no programa. Havia agora três alternativas: ou abandonar o problema e tentar outra questão ou continuar a otimizar o programa ou refaze-lo em C. Até tentamos sair do problema, mas ele não saiu da cabeça do Heraldo :). Refaze-lo em C implicaria em implementar uma série de estruturas na unha, o que iria ser muito chato e não havia certeza que isso ia resolver nossa vida.Por fim o Heraldo fez mais um última pequena otimização no programa e ele passou.

Nós ainda tentamos sem sucesso resolver os problemas Mário (o problema do armário hehehe) e o Zak.

Algumas estatísticas (parciais) da sede do Ceará:

Problema Submissões Aceitos
Histórico 27 9 (33%)
Rouba 17 6 (35%)
Tubos 1 0
Volei 1 0
Zak 6 2 (33%)
bolhas 4 0 (0%)
caixas 23 5 (22%)
mario 8 2 (25%)
olimp 0 0
varetas 17 10 (59%)

Equipes e problemas resolvidos:

Equipe Resolvidos Problemas
UECE – Camila, Tainara, Leonilia 6 Rouba (7/207), mario (1/172), Histórico (1/41), caixas (1/233), varetas (1/49), Zak (2/273)
UECE – Die aphthe schmerzen 5 Rouba (1/161), mario (2/0-), Histórico (1/47), caixas (2/223), varetas (1/35), Zak (4/186)
AVL Team 5 Rouba (1/135), mario (1/277), Histórico (1/77), caixas (5/296), varetas (1/67)
Os Entrevistados 4 Rouba (1/90), Tubos (1/-), Histórico (1/79), caixas (2/182), varetas (1/66)
GOF 4 Rouba (2/73), Histórico (5/151), Caixas (5/289), Varetas (1/39)
Eupodiatamatando 3 Rouba (4/181), Mário (4/-), Histórico (3/143), Varetas (1/39)
UECE – n^n 2 Rouba (1/-), Histórico (1/90), caixas (1/-), varetas (2/122)
UECE – n! 2 Histórico (2/206), varetas (1/61)
unifor2 2 Histórico (2/206), varetas (1/61)
Mazela.cpp 1 Vôlei (1/-), Histórico (4/-), caixas (7/-), varetas (1/71)
unifor1 1 Histórico (2/-), Varetas 6

Observações: A equipe Singularidade de Sobral não estava presente lá, eu não sei se eles competiram. As equipes da UECE tiveram a boa idéia de colocar o nome da faculdade no nome da equipe.

Eu gostei muito dos resultados. Tivemos muitas equipes com bons resultados. Isso demonstra que os esforços, principalmente do Joel Uchôa, em divulgar e particularizar a competição estão sendo frutíferos. Todas as universidades conseguiram bons resultados. Ano que vem eu espero ver mais universidades competindo.

Sugestões para a organização:

  • Linguagens: segundo o Joel Uchôa me informou há planos para inserir novas linguagens na competição. Fica minha sugestão para que Python e Ruby sejam incluídas.
  • Java: me parece que há um longo histórico de problemas com a correção de programas em Java, sendo inclusive o uso desta desaconselhado por alguns. Eu espero que isso seja melhorado na próxima edição. Eu e minha equipe tivemos sérios problemas por conta disso mas nem por isso tenho planos de usar outra linguagem na próxima edição.
  • Distribuição: há uma distribuição GNU/Linux própria para a competição o Maratona Linux. Ele tem várias sacadas legais como redes separadas para que nenhuma equipe possa usar a Internet ou enxergar as outras equipes, boot remoto etc. Porém é necessário boot pelo disquete o que tem sido uma fonte constante de problemas. O sistema de janelas WindowMaker também é fonte de confusão com usuários iniciantes, se é realmente necessário um sistema minimalista eu recomendaria o Fluxbox, XFCE ou Icewm.
  • Correção: eu não gosto do esquema de correção da Maratona. Eu prefiro o da OBI. Na OBI há varias baterias de testes, cuidadosamente preparadas para filtrar cada tipo de erro ou algoritmos. Cada acerto em uma bateria resulta em pontuação. Já na maratona ou se acerta uma questão completamente ou ela está totalmente errada. Isso impede algoritmos mais triviais, algoritmos com complexidade alta (os não polinomiais) e impede também usar técnicas de Inteligência Artificial. Acho que isso interfere muito na forma de se elaborar os problemas e de se resolver os problemas. No universo dos problemas reais, nem tudo pode ser resolvido em tempo e espaço polinomial. Esse é o universo em que vivemos.

Fotos:


almoço Almoço Maratona Brasileira de Programação Maratona Brasileira de Programação Maratona Brasileira de Programação Maratona Brasileira de Programação Mesa desorganizada Maratona Brasileira de Programação Maratona Brasileira de Programação Maratona Brasileira de Programação Maratona Brasileira de Programação Maratona Brasileira de Programação Maratona Brasileira de Programação Heraldo Carneiro Maratona Brasileira de Programação balões laboratório computadores Maratona Brasileira de Programação Joel Uchôa Maratona Brasileira de Programação Heraldo Carneiro Silveira Neto Carlos Pontual Maratona Brasileira de Programação confraternização Maratona Brasileira de Programação confraternização

bônus: um vídeo que eu fiz quando já estava bem cansado. Aqui.

A Maratona Brasileira de Programação é uma realização da Sociedade Brasileira de Computação, USP, Fundação Carlos Chagas, IBM e diversas universidades e voluntários por todo o Brasi.

Decimal para Binário

Código em C para converter um inteiro positivo em uma representação em string binária.

#include
#include
#include

char * decpbin(unsigned int n){
   int i, r, c;
   char * bin;
   bin = calloc(16,sizeof(char));
   memcpy(bin, "0000000000000000", 16);
   i = n;
   c = 0;
   while(i>0){
      r = i % 2;
      i = i/2;
      bin[15-c] = '0'+r;
      c++;
   }
   return bin;
}

int main(){
   char * dec;
   dec = decpbin(1985);
   printf("%s\n", dec);
   free(dec);
}

Compilando e Testando:

$ gcc decbin.c -o decbin
$ ./decbin
0000011111000001

No caso, ele foi feito para inteiros não sinalizados. Como um inteiro ocupa 16 bits, e não estamos gastando um bit para o sinal, o maior número que pode ser convertido é 65535. Por isso criamos a string bin com 16 casas de tamanho.

Basicamente é o algoritmo que se usa para transformar um inteiro em binário. Você pega o número, pega o resto da divisão por 2, que vai ser 0 ou 1 e usa isso para representar o bit menos significativo, ou seja, o mais a direita. Depois pega o número e divide por dois e pega novamente o resto. Fica fazendo isso até que o número dividido por 2 seja 0.

Olá Mundo Paralelo com MPI

MPI é a sigla para Message Passing Interface, um padrão de comunicação de dados para computação paralela. O MPI oferece diversas abstracções que facilitam e padronizam o desenvolvimento de aplicações paralelas. Por exemplo, você pode programar para vários processadores, nós de um cluster, supercomputadores ou Internet utilizando a mesma infraestrutura transparentemente.

Supercomputador Nasa
Cluster Columbia da NASA, com 1024 nós.

Como MPI é um padrão, existem vários padrões de implementação, abertas, fechadas, comerciais ou gratuitas. MPI é definido a princípio para C e Fortran, mas há implementações em outras linguagens como Java ou Python, por exemplo. A implementação que eu vou utilizar nesse exemplo é a OpenMPI.

A notícia boa é que você não precisa ter um supercomputador em casa para aprender e praticar computação paralela, uma máquina doméstica serve. Se você tiver uma máquina com múltiplos processadores, melhor ainda.

Instalação

Para instalar um ambiente de desenvolvimento para MPI no Ubuntu Linux basta um comando:

sudo apt-get install build-essential openmpi-dev

Isso vai instalar um conjunto básico de compiladores e o ambiente OpenMPI.

O código

Vamos criar um arquivo chamado ola.c com o conteúdo:

#include
#include
int size, rank;
int main(int argc, char *argv[]){
   MPI_Init(&argc,&argv);
   MPI_Comm_size(MPI_COMM_WORLD,&size);
   MPI_Comm_rank(MPI_COMM_WORLD,&rank);
   printf("Oi. Eu sou o processo %d de %d\n", rank, size);
   MPI_Finalize();
}

Compilação

Para compilar esse código vamos usar o comando mpicc que foi instalado junto com o pacote openmpi-dev. Ele é uma interface para o gcc, e vai cuidar de toda a linkagem com as bibliotecas do MPI. Você pode usar os parâmetros do gcc com o mpicc.

mpicc ola.c -o ola

Se tudo der certo esse comando vai criar o binário ola.

Execução

Outra ferramenta importante é o mpirun, que levantar o mpi nos diversos nós e mandar cada nó executar o binário. O mpirun não precisa de um programa mpi para rodar, por exemplo, se dermos esse comando:

mpirun -np 4 echo oi

Você vai ter essa saída:

oi
oi
oi
oi

Você mandou 4 nós (-np 4) executar o comando echo oi (imprime oi). Para mandar 5 nós executarem nosso binário ola:

mpirun -np 5 ola

E vamos ter uma saída mais ou menos assim:

Oi. Eu sou o processo 1 de 5
Oi. Eu sou o processo 4 de 5
Oi. Eu sou o processo 0 de 5
Oi. Eu sou o processo 2 de 5
Oi. Eu sou o processo 3 de 5

Por que as saídas sairam desordenadas? Porque elas rodaram em paralelo e não temos como saber qual foi sua ordem de execução. Assim cada nó entrou no printf em um momento diferente e imprimiu seu rank e seu size naquele momento. Você pode experimentar usar o parâmetro -np com outros números maiores ou menores que 5.

Troca de Mensagens

Até aqui não há muita graça porque não há troca de mensagens. Há muito o que se dizer sobre como trocar mensagens do MPI mas a maneira mais fácil de se começar é com a função mpi_send.

Vamos fazer um programa bem simples onde o nó 0 vai mandar uma mensagem para o nó 1. A mensagem vai ser um número, 42. Criemos um arquivo chamado msg.c com o código:

#include
#include

int size, rank, msg, source, dest, tag;

int main(int argc, char *argv[]){
   MPI_Status stat;

   MPI_Init(&argc,&argv);
   MPI_Comm_size(MPI_COMM_WORLD,&size);
   MPI_Comm_rank(MPI_COMM_WORLD,&rank);

	if(rank==0){
   	msg = 42; dest = 1; tag = 0;
   	MPI_Send(&msg, 1, MPI_INT, dest, tag, MPI_COMM_WORLD);
   	printf("Processo %d enviou %d para %d.\n", rank, msg, dest);
	}

	if(rank==1){
		source = 0; tag = 0;
		MPI_Recv(&msg, 1, MPI_INT, source, tag, MPI_COMM_WORLD, &stat);
		printf("Processo %d recebeu %d de %d.\n", rank, msg, source);
	}

   MPI_Finalize();
}

No processo de rank 0 vamos enviar o conteúdo da variável inteira msg para o processo de rank 1. Note que no processo de rank 1, o valor de msg não está definido. O comando MPI_Send vai receber 6 parâmetros.

int MPI_Send( void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm)

  • void *buf, um ponteiro para a mensagem que você vai mandar. No nosso caso a variável inteira msg.
  • int count, a quantidade de elementos que tem nessa mensagem. No nossa caso só 1. Se quisemos mandar um vetor de dois inteiros, seria 2.
  • MPI_Datatype datatype, uma constante que define o tipo de dados que você está enviando. No nosso caso MPI_INT. Isso evita que ajam incompatibilidade no tamanho de inteiros entre arquiteturas diferentes.
  • int dest, o rank do nó destino, o destinatário. No nosso caso o nó 1.
  • int tag, a tag seria num email o assunto da mensagem. Estamos mandando tag 0 então no outro lado tem que estar esperando uma tag 0, caso contrário não há comunicação.
  • MPI_Comm comm, o comunicador. Nesse e na maioria dos casos a constante MPI_COMM_WORLD.

Do outro lado, no processo 1 vamos usar o MPI_recv, que recebe 7 parâmetros.

int MPI_Recv( void *buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Status *status)

  • void *buf, um ponteiro para onde vai ser guardada a mensagem que vamos receber. No nosso caso a variável msg, que no processo 1 está vazia.
  • int count, a quantidade de elementos que vem nessa mensagem.
  • MPI_Datatype datatype, a mesma constante do MPI_send.
  • int source, o rank do nó remetente. No nosso caso o nó 0.
  • int tag, a tag da mensagem conforme explicado no MPI_send.
  • MPI_Comm comm, o comunicador.
  • MPI_Status *status, uma estrutura para que depois que a função for executada você possa inspecionar detalhes da transmissão. No nosso caso ela é inútil.

Para compilar esse exemplo usamos novamente o mpicc.

mpicc msg.c -o msg

E para executa-lo o mpirun.

mpirun -np 2 msg

O programa vai escrever essa mensagem:

Processo 0 enviou 42 para 1.
Processo 1 recebeu 42 de 0

No processo 1 a msg estava inicialmente vazia e no processo 0 havia 42, mas depois do MPI_recv o processo 1 pode escrever o conteúdo 42 de msg. Logo, houve comunicação.

Dicas

Por um problema no empacotamento do mpich no Ubuntu toda vez que você executa o MPI você recebe umas mensagens horrorosas de erro, que na verdade são só um aviso que ele não encontrou uma placa de rede Infiniband.

Para você silenciar na unha essa chatice use o mpirun assim:

mpiexec –mca btl ^openib -np 1 executável

Onde -np 1 deve ser substituido pelo seu número de processos e executável pelo seu executável.

Outra dica é que você pode utilizar uma distribuição Linux que já venha com o MPI instalado. Por exemplo o Scientific Linux ou o Parallel Knoppix.