Geleia de Código

5

jam2014

Não costumo participar de campeonatos de programação por alguns motivos vagos: é perda de tempo (não ganho nada com isso), sou um péssimo programador (ou pasteleiro), dá preguiça (esse é o mais válido) e por aí vai o mimimi. Dessa forma, sempre passei ileso de eventos como o atual Google Code Jam, que pretende levar a categoria de código ofuscado para um novo patamar.

No entanto, esse ano apareceram dois motivos que me levaram a gastar cinco minutos de paciência com as historinhas bestas da equipe do Google. Primeiro o Python, que desde 2013 tem renovado em mim a sensação que programar ainda é divertido (e que o pessoal da Microsoft e do padrão C++ tinham tirado de mim há muito tempo com seus compiladores cada vez mais complexos/lentos e as IDEs que demoram o tempo do cafezinho para abrir). Segundo o que move o mundo: a concorrência. Minha digníssima esposa, levada por alguns pontos-extra na faculdade (uma iniciativa até que louvável do professor), resolveu participar da primeira fase (a classificação desta fase também dava pontos).

O fato é que depois desses cinco minutos eu simplesmente não consegui parar até o minuto final das 23 horas (horário de Brasília) de domingo, quando o tempo-limite esgotou. O aspecto mais divertido do Code Jam é que há liberdade total para a ferramenta que você pretende usar: linguagens de programação, Excel, uma calculadora ou apenas seu cérebro. Você recebe uma "missão" e um arquivo de entrada e precisa cuspir um arquivo de saída de acordo com a missão. Apenas isso. O resto fica por conta da criatividade dos codadores e gambiarreiros de plantão.

Todos os exercícios levam em consideração um arquivo de entrada que possui em sua primeira linha o número de testes que serão feitos e em seguida um número determinado de linhas e parâmetros, geralmente divididos por espaço. O primeiro problema, por exemplo, apenas considerava a suposição de cartas em pequeno truque de mágica e recebia como entrada a disposição dessas cartas junto com a escolha da fileira que o participante dizia onde estava a carta escolhida.

2
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16
3
1 2 5 4
3 11 6 15
9 10 7 12
13 14 8 16
import sys
 
f = open(sys.argv[1])
total = int(f.readline())
 
for case in range(0, total):
	guess1 = int(f.readline())
	row1 = None
	for i in range(1, 5):
		row = f.readline().split()
		if i == guess1:
			row1 = row
	guess2 = int(f.readline())
	row2 = None
	for i in range(1, 5):
		row = f.readline().split()
		if i == guess2:
			row2 = row
	cards = list(set(row1) & set(row2))
	if len(cards) == 1:
		print 'Case #' + str(case+1) + ': ' + cards[0]
	elif len(cards) > 1:
		print 'Case #' + str(case+1) + ': Bad magician!'
	else:
		print 'Case #' + str(case+1) + ': Volunteer cheated!'

O segundo exercício já envolvia um jogo bem divertido em que o jogador ficava clicando em cookies como se não houvese amanhã. Esse deu um pouco mais de trabalho, mas foi mais divertido que o primeiro.

import sys
 
def CookieClicker(farmCost, farmIncrement, cookieTarget):
	cookiePerSecond = 2.0
	bestTime = cookieTarget / cookiePerSecond # melhor tempo soh fazendo cookies
	cookieFarmTime = cookieTarget / (cookiePerSecond + farmIncrement) # melhor tempo ja com fazenda criada
	farmTime = farmCost / cookiePerSecond + cookieFarmTime # quanto vai custar fazer a fazenda e depois fazer cookies com a fazenda
	while farmTime < bestTime: # enquanto fazer a fazenda custar menos tempo que soh fazer cookies...
		bestTime = farmTime # por enquanto melhor tempo
		cookiePerSecond = cookiePerSecond + farmIncrement # novo tempo para fazer cookies (mais uma fazenda ja criada)
		farmTime = farmTime - cookieFarmTime # tiramos o tempo de soh fazer cookies para fazer mais uma fazenda
		cookieFarmTime = cookieTarget / (cookiePerSecond + farmIncrement) # novo tempo com mais uma fazenda criada
		farmTime = farmTime + farmCost / cookiePerSecond + cookieFarmTime # agora com o novo tempo de fazer outra fazenda e soh cookies
	return bestTime
 
f = open(sys.argv[1])
total = int(f.readline())
 
for case in range(1, total + 1):
    args = [float(i) for i in f.readline().split()]
    ret = CookieClicker(args[0], args[1], args[2])
    print 'Case #' + str(case) + ': ' + '{0:.7f}'.format(ret)

Já o terceiro... o terceiro passa. Vamos para o quarto, um dos mais instigantes, pois envolve duas regras distintas de um jogo e a otimização das melhores estratégias para ambos. Isso consumiu bem mais tempo que os outros dois iniciais, pois lembro de ter me isolado por uma hora para conseguir colocar tudo na cabeça.

import sys
 
def BestBlock(block, blocks):
	bestBlock = 0
	for i in range(len(blocks) - 1, -1, -1):
		if blocks[i] < block: break
		bestBlock = blocks[i]
	if not bestBlock:
		bestBlock = blocks[0]
	return bestBlock
 
def War(naomi, ken):
	naomi = sorted(naomi)
	ken = sorted(ken)
	naomiCount = 0
	while len(naomi):
		naomiBlock = naomi[-1]
		kenBlock = BestBlock(naomiBlock, ken)
		if naomiBlock > kenBlock:
			naomiCount = naomiCount + 1
		#print str(naomiBlock) + ' vs ' + str(kenBlock) + ': ' + str(naomiCount)
		naomi.remove(naomiBlock)
		ken.remove(kenBlock)
	return naomiCount
 
def WarCheat(naomi, ken):
	naomi = sorted(naomi)
	ken = sorted(ken)
	naomiCount = 0
	while len(naomi):
		naomiTold = 0
		naomiBlock = naomi[-1]
		bestKen = ken[-1]
		if naomiBlock > bestKen:
			naomiTold = bestKen + 0.00000001
		else:
			naomiTold = bestKen - 0.00000001
		kenBlock = BestBlock(naomiTold, ken)
		naomiBlock = BestBlock(kenBlock, naomi)
		if naomiBlock > kenBlock:
			naomiCount = naomiCount + 1
		#print str(naomiTold) + '(' + str(naomiBlock) + ') vs ' + str(kenBlock) + ': ' + str(naomiCount)
		naomi.remove(naomiBlock)
		ken.remove(kenBlock)
	return naomiCount
 
f = open(sys.argv[1])
total = int(f.readline())
 
for case in range(1, total + 1):
        f.readline()
        naomi = [float(i) for i in f.readline().split()]
        ken = [float(i) for i in f.readline().split()]
        war = War(naomi, ken)
        warCheat = WarCheat(naomi, ken)
        print 'Case #' + str(case) + ': ' + str(warCheat)+ ' ' + str(war)

Já o terceiro foi um fracasso total. Tentei de todas as maneiras resolver o impasse de descobrir qual disposição de um jogo de campo minado poderia ser resolvido em apenas um clique (parece o jogo oposto do viciado clicador de cookies), mas falhei miseravelmente. E desconfio o porquê. Primeiro entendo que meu perfeccionismo me impediu de realizar uma checagem padrão para exceções já conhecidas (quando há apenas uma linha ou coluna, quando há apenas um espaço sem minas, etc). Eu pensei: se o Google fez esse problema, ele deve ter bolado alguma solução genérica que independa de ifs. Bom, não que eu saiba. Depois de terminado o tempo dei uma olhada em algumas soluções dos competidores e não achei nenhuma solução que usasse algum algoritmo maluco e genérico (não achei nenhum indiano, contudo).

Eis a solução porca e mal-resolvida (alguns pontos do códido foram feitos depois de ver o código de outrem):

import sys
 
def FieldToString(field):
        ret = '\n'
	for r in field:
		for c in r:
			ret = ret + str(c)
		ret = ret + '\n'
	return ret
 
def CountMines(field, r, c):
        ret = 0
        row = len(field)
        col = len(field[0])
        if r < row-1 and field[r+1][c] == '*': ret = ret + 1
        if c < col-1 and field[r][c+1] == '*': ret = ret + 1
        if r > 0 and field[r-1][c] == '*': ret = ret + 1
        if c > 0 and field[r][c-1] == '*': ret = ret + 1
        if r < row-1 and c < col-1 and field[r+1][c+1] == '*': ret = ret + 1
        if r > 0 and col > 0 and field[r-1][c-1] == '*': ret = ret + 1
        if r < row-1 and c > 0 and field[r+1][c-1] == '*': ret = ret + 1
        if r > 0 and c < col-1 and field[r-1][c+1] == '*': ret = ret + 1
        return ret
 
def ExpandClick(field, r, c):
        if field[r][c] != '0': return
 
        def Expand(field, r, c):
                if field[r][c] == '.':
                        field[r][c] = str(CountMines(field, r, c))
                        ExpandClick(field, r, c)
 
        row = len(field)
        col = len(field[0])
        if r < row-1 and field[r+1][c] != '*':
                Expand(field, r+1, c)
        if c < col-1 and field[r][c+1] != '*':
                Expand(field, r, c+1)
        if r > 0 and field[r-1][c] != '*':
                Expand(field, r-1, c)
        if c > 0 and field[r][c-1] != '*':
                Expand(field, r, c-1)
        if r < row-1 and c < col-1 and field[r+1][c+1] != '*':
                Expand(field, r+1, c+1)
        if r > 0 and col > 0 and field[r-1][c-1] != '*':
                Expand(field, r-1, c-1)
        if r < row-1 and c > 0 and field[r+1][c-1] != '*':
                Expand(field, r+1, c-1)
        if r > 0 and c < col-1 and field[r-1][c+1] != '*':
                Expand(field, r-1, c+1)
 
def FieldClicker(field):
	row = len(field)
	col = len(field[0])
        for r in range(row):
                for c in range(col):
                        if field[r][c] == 'C':
                                field[r][c] = str(CountMines(field, r, c))
                                ExpandClick(field, r, c)
                                break
        return field
 
def FieldValidate(field):
        ret = True
	row = len(field)
	col = len(field[0])
        for r in range(row):
                for c in range(col):
                        if field[r][c] == '.':
                                ret = False
                                break
        return ret
 
def FieldRender(row, col, mines):
	field = []
        for i in range(row):
                field.append(['.'] * col)
        if row == 2:
                nextRow = 0
                nextCol = 0
                while mines:
                        field[nextRow][nextCol] = '*'
                        nextRow = nextRow + 1
                        if nextRow == row:
                                nextRow = 0
                                nextCol = nextCol + 1
                        mines = mines - 1
                field[0][col-1] = 'C'
        elif col == 2:
                nextRow = 0
                nextCol = 0
                while mines:
                        field[nextRow][nextCol] = '*'
                        nextCol = nextCol + 1
                        if nextCol == col:
                                nextRow = nextRow + 1
                                nextCol = 0
                        mines = mines - 1
                field[row-1][0] = 'C'
	elif row * col - mines < 3:
                nextRow = row - 1
                nextCol = 0
                while mines:
                        field[nextRow][nextCol] = '*'
                        nextCol = nextCol + 1
                        if nextCol == col:
                                nextRow = nextRow - 1
                                nextCol = 0
                        mines = mines - 1
                field[0][col-1] = 'C'
        else:
                for r in range(len(field)):
                        for c in range(len(field[0])):
                                field[r][c] = '*'
                if row * col - mines >= 9 and row >= 3 and col >= 3:
                        empties = row * col - mines
                        nextRow = 0
                        nextCol = 0
                        while empties:
                                
	return field
 
def Mine(row, col, mines):
        if row * col - mines == 2 and row > 1 and col > 1:
		return 'Impossible!'
	if row * col - mines == 3:
		return 'Impossible!'
	elif row * col - mines == 5:
		return 'Impossible!'
	elif row * col - mines == 7:
		return 'Impossible!'
	else:
		return FieldToString(FieldRender(row, col, mines))
 
f = open(sys.argv[1])
total = int(f.readline())
 
for case in range(1, total + 1):
        field = [int(i) for i in f.readline().split()]
        print 'Case #' + str(case) + ': ' + Mine(field[0], field[1], field[2])
 
 
 
#############################################################################3
 
def FieldRenderWrong(row, col, mines):
	field = []
	for i in range(row):
		field.append(['.'] * col)
 
        def GetNextRow(field, clickRow, clickCol):
                row = len(field)
                col = len(field[0])
                nextRow = 0
                nextCol = 0
                rowDist = 0
                colDist = 0
                for r in range(row):
                        for c in range(col):
                                if field[r][c] == '.':
                                        rDist = abs(r - clickRow)
                                        cDist = abs(c - clickCol)
                                        totDist = rDist + cDist
                                        currTotDist = rowDist + colDist
                                        if totDist > currTotDist:
                                                nextRow = r
                                                rowDist = rDist
                                                nextCol = c
                                                colDist = cDist
                                        else:
                                                rowCount = 0
                                                for r2 in range(row):
                                                        if field[r2][c] == '*':
                                                                rowCount = rowCount + 1
                                                colCount = 0
                                                for c2 in range(col):
                                                        if field[r][c2] == '*':
                                                                colCount = colCount + 1
                                                lastRow = rowCount == row - 1
                                                lastCol = colCount == col - 1
                                                i

Não, eu não usei o Google para descobrir a lógica por trás do problema. Vai que os caras ficam monitorando quem fica fazendo pesquisas. E, não, tampouco usei o Bing. Não sou masoquista a esse ponto.

PS: Bom, estou na próxima fase. Veremos o que o futuro nos espera. Esse programador foi fisgado pelo campeonato de pastéis.

Lambda: o Retorno!

3

Lambda: o Retorno

Na última vez que foi abordado o tema "lambda na ferida" falamos brevemente sobre como C++ agora permite criar funções dentro de funções. Hoje vamos apenas falar que aquela construção bizarra que criamos fica ainda mais bizarra se precisarmos retornar alguma coisa dessa função ou usá-la mais de uma vez.

O padrão do lambda é supor que sua função embutida e enlatada não precisa retornar nada, o que torna a sintaxe mais simples: é um void AlgumaCoisa(argumentos). No entanto, para algoritmos como o find_if isso não funciona, então é necessário retornar algo. E, no caso de find_if, chamá-lo mais de uma vez pode ser feito facilmente criando uma variável lambda:

#include "Common.h"
#include <algorithm>
#include <vector>
#include <string>
 
 
int main()
{
	std::vector<Employee> employees; // um bando de empregados
	std::string currentDate = GetCurrentDate();
 
	// definindo uma função, como quem não quer nada, dentro de uma função
	auto FindByBithDate = [&](Employee& employee)->bool // <-- tipo de retorno
	{
		return employee.birthDate == currentDate;
	};
 
	GetEmployees(employees);
 
	auto findIt = std::find_if(employees.begin(), employees.end(), FindByBithDate);
 
	while( findIt != employees.end() )
	{
		SendMail(*findIt);
		findIt = std::find_if(findIt + 1, employees.end(), FindByBithDate);
	}
}

O tipo de retorno que colocamos através de uma flechinha é obrigatória? De fato, não. Se eu omiti-la vai funcionar do mesmo jeito porque o único ponto de saída da minha função retorna um bool.

Esses compiladores estão ficando cada vez mais espertos.

A moda agora é levar lambda na função

5

moda-lambda

A nova moda de programar C++ nos últimos anos com certeza é usar lambda. Mas, afinal, o que é lambda? Bom, pra começar, é um nome muito feio.

O que esse nome quer dizer basicamente é que agora é possível criar função dentro de função. Não só isso, mas passar funções inteiras, com protótipo, corpo e retorno, como parâmetro de função.

Isso significa que finalmente os algoritmo da STL vão ser úteis e não um "pain in the ass".

Por exemplo, antes, tínhamos que fazer o seguinte malabarismo para mexer com arrays/vetores/listas:

#include "Common.h"
#include <algorithm>
#include <vector>
#include <string>
 
 
void NewYearMail(Employee& employee)
{
	// isso s? tem duas linhas, mas poderia ter... 10!
	employee.age += 1;
	SendMail(employee);
}
 
 
int main()
{
	std::vector<Employee> employees; // um bando de empregados
	GetAnniversaryEmployees(employees);
	std::for_each(employees.begin(), employees.end(), NewYearMail);
}

Imagine que para cada interação devíamos criar uma função que manipulasse os elementos do vetor.

Uma alternativa que costumava utilizar era a de roubar na brincadeira e criar um tipo dentro da função (permitido) e dentro desse tipo criar uma função (permitido):

#include "Common.h"
#include <algorithm>
#include <vector>
#include <string>
 
 
int main()
{
	struct NewYearMail { void operator()(Employee& employee)
	{
		employee.age += 1;
		SendMail(employee);
	}}; // note o final com chaves-duplas, fechando a fun??o e a struct
 
	std::vector<Employee> employees; // um bando de empregados
	GetAnniversaryEmployees(employees);
	std::for_each(employees.begin(), employees.end(), NewYearMail());
}

Apesar disso gerar INTERNAL_COMPILER_ERROR em muitos builds com o Visual Studio 2003 (e o rápido, mas anos noventa, Visual Studio 6) na maioria das vezes o código compilava e rodava sem problemas. No entanto, deixava um rastro sutil de gambi no ar...

Agora isso não é mais necessário. Desde o Visual Studio 2010 (que eu uso) a Microsoft tem trabalhado essas novidades do padrão no compilador, e aos poucos podemos nos sentir mais confortáveis em usar essas modernices sem medo. Por exemplo:

#include "Common.h"
#include <algorithm>
#include <vector>
#include <string>
 
 
int main()
{
	std::vector<Employee> employees; // um bando de empregados
	GetAnniversaryEmployees(employees);
 
	std::for_each(employees.begin(), employees.end(), [&](Employee& employee)
	{
		employee.age += 1;
		SendMail(employee);
	}); // note o final com chave e par?ntese, fechando a fun??o e a chamada do for_each
}

"Caraca, mas o que é esse código alienígena?", diria alguém como eu alguns anos atrás (talvez até meses). Bom, nada vem de graça em C++ e dessa vez houve algumas mudanças meio drásticas na sintaxe para acomodar o uso dessa lambida inline.

#include <algorithm>
#include <iostream>
 
int main()
{
	int arrayDeInts[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
 
	std::for_each( // estou chamando uma fun??o aqui
		&arrayDeInts[0], &arrayDeInts[10], // come?o e final (begin e end para STL)
		[&] // opa! sintaxe nova. os [] dizem que come?a uma fun??o lambida e 
			//o & diz que posso acessar todo mundo
 
		(int umInt) // depois dos []s vem o formato de recebimento de par?metros de uma fun??o
			// (no caso, como ? um array de ints, o par?metro para o for_each tem que ser um int)
 
		{ // iniciamos a fun??o dentro da chamada do for_each
 
			std::cout << umInt << ' '; // podemos acessar umInt, arrayDeInts 
			// e qualquer vari?vel da fun??o ou objeto (se fosse um objeto)
 
		} // finalizamos a fun??o dentro da chamada do for_each
 
	); // e precisamos fechar a chamada do for_each depois dessa suruba toda
 
}

E não é só isso. Tem muito mais esquisitices de onde veio essa.

Houaiss para Babylon em Python!

0

O Fabio Montefuscolo expandiu mais ainda o acesso do conversor Houaiss para Babylon implementando uma versão em Python, uma linguagem que estou aprendendo a adorar. Tudo é mais simples, rápido e direto em Python, e o código que ele escreveu utiliza todo esse potencial:

#!/usr/bin/python2
# -*- coding: utf-8 -*-
 
#
# Coloque esse script na pasta com os arquivos dhx.
# O resultado estará em iso-8859-1
#
 
#
# Segui o tutorial em http://www.caloni.com.br/blog/archives/conversor-de-houaiss-para-babylon-parte-1
#
 
import os
 
files = os.listdir('.')
 
for arq in files:
    if not arq.endswith('dhx'):
        continue
 
    print 'Abrindo "%s"' % arq
    origin = open(arq, 'r')
    target = open('%s.txt' % arq, 'w+')
 
    char = origin.read(1)
    while char:
        byte = ord(char) + 0x0B
        new_char = chr(byte % 256)
        target.write(new_char)
        char = origin.read(1)
 
    origin.close()
    target.close()

 

Real Programmers Don’t Use Java

0

real-programmer

When I was a newbie (and a wanna-be) I enjoyed reading "Real Programmers Don't Use Pascal", a satiric text that influenced and encouraged me into the path of "C/C++ enlightenment", most even than K&R's book. Since then I thought that being a "Real Programmer" was something close to everything one needs to know to get (hard) things done (quickly). Being a "Quiche Eater" was, in couterpart, comparable to nothing. Real Programmers solve real problems! Quiche Eaters are losers who study the academic concepts of computer science and never do a damn useful and/or working program (maybe you know some guy like this).

Jokes apart, the spirit of the text can also be used by those who already find them very good programmers and believe no longer have to grow professionally. The times my ego inflates I still remember that my code use child APIs and an operating system that is a joke. I also remember that there are some people out there designing a starship that will leave the orbit of the Solar System!

On the other hand, many people that just got out of CS course still find programming a difficult matter. This text reminds us that life was difficult 20, 40, 70 years ago, when engineers and programmers were the same person and when you didn't know that what you were doing could put millions at risk in a project.

Hence, the Real Programmer live in the past. And he always will be worthier than young folks, because he knows how to solve that blue screen problem that nobody else does. As I always say, paraphrasing an illustrious figure in Brazilian television, who is afraid to open Visual Studio and is eternally designing the software instead does not go very far: "who knows to do, do it right way!" .

Here follows a brief summary of the original text adapted to the current times and with my prejucided view of thinking about it. If you wish to use your politically correct piece of mind and criticize me, be my guest!

Languages. Remember: the need to invent more languages/resources to do your job is to remind yourself about your own incompetence to invent such excuse. You are one of those who says "every problem has a specific tool" or something like that. In other words: an inefficient programmer. Don't you see that everything you need is C. If C won't do, then assembly will. If none of them, then is isn't worth doing.

Structured Programming. It is the first and last paradigm to be applied. After all, Object Orientation is another excuse to not program. They are more abstractions that, once you are a dead weight, you are unable to solve a problem using just functions and variables. No, you need classes, inheritance, templates and whatever the hell that will transform your simple and straight code into a magical horn of plenty that will only impress others at the futility and complexity of the solution.

Data structure. Another great concept to fool yourself. Today are many who enslave us to weird SQL layouts and weird frameworks that do all the work. We all know that the only really useful to know the structure is the array. The rest are variants of the same theme: queues and stacks.

Operating system. Mac and Windows are just toys and Linux is a video game that takes more work to set up than playing. The programmer actually uses something like mainframes or other beta operating system, which are too weird and can make a real mess in the hands of those who have not read the WHOLE manual. And knowing all known major kernel bugs and its location by heart at the time of booting is vital.

Tools. If you depend on an IDE that have Code Completion and other fancy stuff or any other editor that depends on your favorite 17,459 plugins installed, then you are not a Real Programmer. A Real Programmer actually use what he have on hand at the time, like notepad, hexdump or even some beeps . The tool is no limit to one that can really code.

Debugging. Are you saying that you need the source code in order to debug? So you do not have a clue of what the program does. Just a few glances at call stack and the registers can make a Real Programmer solve a bug that Quiche Eaters would not get after analyzing those charts with boxes inside UML and use cases for months.

The Real Programmers Work is certainly not doing trivial databases to trivial programs that access SQL with trivial queries. Neither are those horrible websites with PHP/Apache and scripts and more scripts written by kids. No, sir. These are programs that deal with the OS in a more intimate way (HD encryption, file system drivers, critical communication services, etc.), or are programs that do something really useful (compilers, the operating system itself). Or maybe those programs that deal directly with hardware (complex microcontrollers, robots, ships, medical devices, etc.).

The Fun of every Real Programmer is actually chat with friends (about programming), read something (about programming) and watch intelligent movies (about programming or people who have some kind of intellectual challenge to solve "the hard way"). Is there anything more fun than that?

And, finally, in their Natural Habitat, we can find pages and pages of assembly code scattered around the table, a computer locked by a remote kernel debugging serial cable, some notes in hex on a piece of paper, a few dozen browser pages openned about the behavior of functions in BIOS SATA HDDs with 500 GB working on RAID4, coffee (of course), chips, stains on the carpet. When there's nothing to do the environment is pretty tidy and one cannot notice the presence of Real Programmers in sight.

And the Future of Real Programmer? Well, C may even be dying. But so what? It seems C++ supports pointers as well. The rest of the useless abstractions like classes and inheritance may be totally ignored. The basics will always exist. Forget versions with multiple inheritance and enigmatic concepts. Be a (wo)man!

The real, happy, final truth is: regardless of how much more the world becomes "managed" behind frameworks and programmers who prefer to "do projects" behind their office packages and use cases, when problems pop up, some bug murky life threatening useful for a project, a Real Programmer will be there to save the day, because only a programmer really knows how to do his job well done and have a good night of sleep knowing that everything will just be OK.

If it doesn't, there will be always a Real Programmer to save the day.

"As long as there are ill-defined goals, bizarre bugs, and unrealistic schedules, there will be Real Programmers willing to jump in and Solve The Problem, saving the documentation for later. Long live FORTRAN!"

remove_if até remove, só que diferente

7

A surpresa de hoje foi descobrir (vejam só) que o remove_if, como todo algoritmo da STL, deve ser olhado de perto antes de usado. Nesse caso em específico porque, apesar do nome, a função NÃO remove elementos, mas os sobrescreve.

Imagine uma função que usa remove_if para remover todas as idades de potenciais lolitas:

void RemoveIfLolita(vector<int>& ages)
{
	remove_if(ages.begin(), ages.end(), [&](int age) { return age < 18; } );
}

Ou até sua contraparte usando um array C:

void RemoveIfLolita(int* ages, int size)
{
	remove_if(ages, ages + size, [&](int age) { return age < 18; } );
}

Um uso trivial pode não cuspir um resultado trivial, ou seja, os elementos não serão removidos como se espera:

#include <algorithm>
#include <iostream>
#include <vector>
 
using namespace std;
 
 
void RemoveIfLolita(int* ages, int size)
{
	remove_if(ages, ages + size, [&](int age) { return age < 18; } );
}
 
void RemoveIfLolita(vector<int>& ages)
{
	remove_if(ages.begin(), ages.end(), [&](int age) { return age < 18; } );
}
 
 
int main()
{
	vector<int> ages;
 
	ages.push_back(10);
	ages.push_back(21);
	ages.push_back(66);
	ages.push_back(18);
	ages.push_back(16);
	ages.push_back(15);
	ages.push_back(8);
	ages.push_back(24);
	ages.push_back(12);
	ages.push_back(20);
	ages.push_back(13);
	ages.push_back(13);
 
	RemoveIfLolita(ages);
	cout << "Vector (" << ages.size() << "):\n";
	for_each(ages.begin(), ages.end(), [&](int age) { cout << age << endl; });
 
	int newAges[] = { 10, 21, 66, 18, 16, 15, 8, 24, 12, 20, 13, 13 };
	const int newAgesSz = (int) ( sizeof(newAges) / sizeof(int) );
	RemoveIfLolita(newAges, newAgesSz);
	cout << "\n\nArray (" << newAgesSz << "):\n";
	for_each(newAges, newAges + newAgesSz, [&] (int age) { cout << age << endl; } );
}

RemoveIfErrado

Isso ocorre porque o comportamento do remove_if é copiar todos os elementos que retornem false (não remova) e pular elementos que retornem true (remova). No entanto, o tamanho do contêiner, e consequentemente seu ponteiro end(), permanecem o mesmo.

RemoveIfComportamento

De acordo com o saite cplusplus.com, o algoritmo STL é previsível, simples, e por isso mesmo sujeito a otimizações do compilador:

template <class ForwardIterator, class UnaryPredicate>
ForwardIterator remove_if (ForwardIterator first, ForwardIterator last,
	UnaryPredicate pred)
{
	ForwardIterator result = first;
	while (first!=last) {
		if (!pred(*first)) {
			*result = *first;
			++result;
		}
		++first;
	}
	return result;
}

Para obtermos qual seria o "novo end()", precisamos obter esse valor do retorno de remove_if. Com base nisso, podemos alterar o tamanho do contêiner ajustado:

#include <algorithm>
#include <iostream>
#include <vector>
 
using namespace std;
 
 
int RemoveIfLolita(int* ages, int size)
{
	auto newEnd = remove_if(ages, ages + size, [&](int age) { return age < 18; } );
	return newEnd - ages;
}
 
void RemoveIfLolita(vector<int>& ages)
{
	auto newEnd = remove_if(ages.begin(), ages.end(), [&](int age) { return age < 18; } );
	ages.resize(distance(ages.begin(), newEnd));
}
 
 
int main()
{
	vector<int> ages;
 
	ages.push_back(10);
	ages.push_back(21);
	ages.push_back(66);
	ages.push_back(18);
	ages.push_back(16);
	ages.push_back(15);
	ages.push_back(8);
	ages.push_back(24);
	ages.push_back(12);
	ages.push_back(20);
	ages.push_back(13);
	ages.push_back(13);
 
	RemoveIfLolita(ages);
	cout << "Vector (" << ages.size() << "):\n";
	for_each(ages.begin(), ages.end(), [&](int age) { cout << age << endl; });
 
	int newAges[] = { 10, 21, 66, 18, 16, 15, 8, 24, 12, 20, 13, 13 };
	int newAgesSz = (int) ( sizeof(newAges) / sizeof(int) );
	newAgesSz = RemoveIfLolita(newAges, newAgesSz);
	cout << "\n\nArray (" << newAgesSz << "):\n";
	for_each(newAges, newAges + newAgesSz, [&] (int age) { cout << age << endl; } );
}

RemoveIfFunciona

Esse C++... intuitivo como nunca!

BovespaBacktesting

4

Eu não sou apenas um programador: sou um especulador. Ou, para quem ficou com medo, um investidor. Ficou bonito, agora? Trocando em miúdos, isso quer dizer que muitas vezes aposto na bolsa de valores, aquela onde as pessoas ganham e perdem dinheiro loucamente. Porém, assim como faço com minha carreira de desenvolvedor, não deixo de estudar e aprimorar minhas habilidades. Tirando alguns anos de estudo com livros de finanças, economia e contabilidade, foi com base nisso que eu fiz uma série de scripts que realiza operações de backtesting nos papéis da Bovespa.

Gordon Gecko

Que mané backtesting?

Backtesting é uma maneira dos especuladores terem uma noção de quão bom ou ruim é sua estratégia de compra e venda. É uma maneira profissional de se aproximar do mercado caótico das ações. Basicamente um backtesting simula o que o especulador faria na vida real com um histórico razoável de variação de preços das ações que pretende operar. Se esse monte de palavras novas neste blogue está te deixando com medo, recomendo dar uma passada no Senhor Mercado (lá você irá também aprender mais sobre técnicas de backtesting).

Vamos supor que minha ideia de estratégia seja comprar quando o preço de uma determinada ação estiver na metade do seu topo histórico e vender quando ele estiver no dobro do momento da compra. Uma estratégia bem tosca, mas se fizer dinheiro, quem liga para vaidade? Outra estratégia mais refinada usa médias móveis para estabelecer pontos de compra e venda dependendo da tendência do mercado. Qual das duas dá mais dinheiro? Existem duas maneiras de saber: a dolorosa e a indolor. A dolorosa seria sacar uma grana do banco e começar a operar em sua corretora favorita seguindo ambas as estratégias e ver qual te deixou mais rico e qual te levou à falência. A indolor seria baixar o histórico de preços dos papéis que está interessado em usar essas estratégias e rodar uma simulação que opere seguindo ambas as estratégias e descubra qual é a perdedora. Qual você preferiria?

OK, esse assunto já está ficando bem monótono para quem acompanha um blogue de programação. Vamos ao código!

GitHub na veia

BovespaBacktesting

O projeto que mantenho no GitHub possui algumas ideias que gostaria de compartilhar com todos que estão interessados em realizar um backtesting, independente de sua estratégia. A primeira delas seria de onde baixar o histórico de preços de maneira simples e barata. Eu recomendo e uso o software Grafix, que consegue baixar as informações diretamente do saite da Bovespa e realizar os ajustes necessários para montar e exibir as informações. Com base no banco de dados do Grafix é que o BovespaBacktesting (meu projeto) importa as informações que ele precisa. Ele irá importar apenas os códigos que estiverem em uma lista disponível no arquivo data/filterCodes relativo de onde o script estiver rodando. Esse arquivo é apenas texto com um código por linha.

def import_quote_from_jgrafix(dataPath):

A partir dessa importação é possível realizar queries com as variações diárias, semanais e mensais dos preços dos ativos conhecidos (a mesma lista de código). A própria lista de ativos conhecidos está disponível através de uma função, tornando a iteração simples e direta.

def load_quote_data(code):
def load_week_quote_data(code):
def load_month_quote_data(code):
def load_known_codes():

Com essas informações de preço é possível aplicar qualquer tipo de indicador. O BovespaBackteting possui apenas os mais usuais, mas basta implementar a lógica de tratamento em Python, o que não deve consumir nem muito tempo nem muitos neurônios, pois com o histórico disponível tudo fica mais fácil.

def sma(quote, days = 10):
def ema(quote, days = 10):
def macd(quote, shortDays = 12, longDays = 26, signalDays = 9):
def stop_safeplace(quote, multiplier = 4):
def stop_atr(quote, multiplier = 3):

As funções-macro calculam trades (operações) a partir de alguns parâmetros definidos no código ou por parâmetros. As versões do BovespaBacktesting foram variando nesse sentido. Ainda não há uma maneira saudável de comparar diversas estratégias, pois o que eu tenho feito basicamente é alterar alguns parâmetros, rodar o backtesting e exportar para um CSV (função já disponível).

def calc_trades(code, trend, signal):
def calc_all_trades():
def calc_total_trades(equity, risk, b1, bs):
def calc_money(trades, equity, risk, deposit, wage):
def backtesting_analysis():

Já existem algumas firulas caso você esteja pensando em uma estratégia em que seja viável viver de operar, como cálculo de salário e a inclusão de variáveis que levem em conta que parte do dinheiro ganho será usado. Ainda é um código bem tosco, mas funciona e pode ser o ponto de entrada de quem deseja conhecer mais sobre o mercado de ações e como os profissionais conseguem tirar dinheiro deste grande cassino.

logo-sun

Uma nova linguagem

4

 

Tenho que me atualizar. Faz um tempo (anos) em que deixei de lado esse mundo "frescurento" de C++2030 e me foquei única e exclusivamente em resolver problemas da melhor forma possível com o que a linguagem já tinha a oferecer em uma implementação estável de compilador e bibliotecas.

Agora o mundo está mudando. Para quem é do Universo Windows/Microsoft, a empresa do Uncle Bill vem liberando algumas versões interessantes do seu compilador (VS2012, 2013 e agora o CTP), cada vez mais próxima de um C++11/14 100% compliance. Não acredito que cheguem lá, mas o fato de estarem empenhados indica para a indústria e seus clientes que há uma demanda sendo atendida. Não é mais frescurite de acadêmicos. Algumas features ultra-novas começam a ser usadas e permitidas em projetos.

Estamos falando de uma nova linguagem que se forma com um novo ritmo. O padrão C++11 demorou "apenas" 2 anos para cair em nossas linhas de comando, há um patch já confirmado para o ano que vem e já existem menções para um novo release em 2017. Para o programador C++ que se acostumou a contar as evoluções em décadas, um novo ritmo se impõe. Não há tempo para cristalização de conceitos. O boost já nos forneceu isso por todos esses anos e hoje ele é reconhecidamente a versão alpha que precisávamos.

Veremos o que o futuro cada vez mais presente nos reserva.

ShowCast

MVP ShowCast 2013: C++11 e C++14 no Visual Studio 2013

4

ShowCast

Uma notícia meio relâmpago (leia "publicada tarde demais"): amanhã (Segunda-Feira, 02/12/2013) às 17:00 (horário de Brasília) será exibida uma web-palestra web-ao vivo pelo web-Strauss e moderada por web-mim. O assunto é: todas as bugingangas novas que o recém-chegado Visual Studio 2013 suporta da (praticamente) nova linguagem C++11 (e seu amigo do futuro, C++14).

Estarei me guiando pela tabela da MSDN que relaciona essas novidades entre as diferentes versões do Visual Studio. São muitas mudanças de uma vez só, e pode haver detalhes obscuros que eu ainda não ouvi falar, mas tentarei obter o máximo de informações possíveis para juntos tentarmos extrair o que pudermos nessa 1 hora e 15 minutos de web-interatividade.

Quer se inscrever? Acesse o web-linque do evento e nos aguarde por lá!

Update

 

O evento foi realizado e gravado e em breve tanto o áudio quanto os slides estarão disponíveis. Foi uma explicação sucinta sobre as novidades da linguagem, mas muito bem explicada por Strauss, o que deve ajudar a galera a se atualizar aos poucos.

Não foi uma palestra muito popular, talvez pelo dia/horário, mas tivemos a participação de cerca de 7 pessoas. A avaliação final não reflete isso, mas pudemos contar com uma série de perguntas pertinentes do participante Andre.

PoolVS2013CPP11

 

Update 10/12/2013

 

Seguem os slides da palestra:

Ponto Flutuante Afundando (2)

Ponto Flutuante Afundando

8

Quando armazenamos valores monetários em doubles seus cálculos conseguem manter a precisão e na maioria das vezes o ajuste de precisão funciona. Porém, encontrei alguns casos onde a subtração de dois valores fazia "perder" um centavo (ou comparações exatas) justamente pela limitação da precisão do ponto flutuante. Nesse exemplo os valores são 2.358,93 - 1.386,93, que em uma conta de padaria (mas correta) dá 972,00 (até a Calc do Windows e o Excel funcionam), mas pelo Visual Studio 2010 chega perto, mas erra o alvo:

#include <iostream>
 
int main()
{
	double d1 = 2358.93;
	double d2 = 1386.93;
	double d3 = d1 - d2;
 
	std::cout << "d1: " << d1 << "\n";
	std::cout << "d2: " << d2 << "\n";
	std::cout << "d1 - d2 = 3d: " << d3 << "\n";
 
	// comparando armazenamentos que diferem
	std::cout << "d3 == 972.0: " << std::boolalpha << ( d3 == 972.0 ) << "\n";
 
	// comparando armazenamentos similares
	std::cout << "d1 == 2358.93: " << std::boolalpha << ( d1 == 2358.93 ) << "\n";
	std::cout << "d2 == 1386.93: " << std::boolalpha << ( d2 == 1386.93 ) << "\n";
}

Isso ocorre porque sua representação dentro da variável double é diferente de 272.0 do outro double. Depurando vemos mais claramente:

Ponto Flutuante Afundando

Ou seja, quando fazemos a subtração de d2 em d1, nossa precisão raspa um pouquinho e escapa pela beirada:

d1 2358.9299999999998
d2 1386.9300000000001
======================
d3 971.999999999999777
||||||
Esse é o valor "desejado".

Na comparação com o valor redondo aparece a falha, mas note que isso não ocorre com os outros valores d1 e d2, já que o armazenamento adquire o mesmo formato:

Ponto Flutuante Afundando (2)

Corrigindo o incorrigível

Há uma forma de arredondamento já disponível no C99 (mas não no Visual Studio 2010) que pode ser útil para esses casos. A única coisa que é preciso fazer é arredondar os valores antes do cálculo:

#include <iostream>
 
double round(double r)
{
    return (r > 0.0) ? floor(r + 0.5) : ceil(r - 0.5);
}
 
int main()
{
	double d1 = 2358.93;
	double d2 = 1386.93;
	double d3 = round(d1) - round(d2);
 
	std::cout << "d1: " << d1 << "\n";
	std::cout << "d2: " << d2 << "\n";
	std::cout << "d1 - d2 = 3d: " << d3 << "\n";
	std::cout << "d3 == 972.0: " << std::boolalpha << ( d3 == 972.0 ) << "\n";
}

É uma decisão arbitrária essa de arredondar para cima, mas se for adotada em todo o sistema (e já fazendo parte de um padrão, no caso, o C99), não deverão existir problemas de interpretação de cálculos entre os componentes.

O mercado financeiro agradece =).

UPDATE

Não estou de acordo com o armazenamento de valores monetários em doubles em vez de inteiros pelo simples motivo que não há moedas no sistema financeiro com unidades que se dividem ad infinitum. Por consequência, existe sempre uma unidade básica e indivisível (que no caso do Brasil é o centavo de real). Ou seja, como o objetivo é contar o total dessas unidades que não se dividem, o uso de inteiros é brainless.

UPDATE 2

Existe uma discussão exatamente sobre isso no Grupo C/C++ Brasil, que recomendo a leitura, o que me levou a escrever o post. Particularmente, sigo o raciocínio do Pedro Lamarão.

Go to Top