Beecrowd - Ajuda para iniciantes na programação em Java

Fala, turma. Se você é um iniciante na programação Java e quer tentar aprender um pouco dela resolvendo questões no Beecrowd (antigo Uri Online Judge), este post é para você.

Problema e Solução

Há algumas semanas, fiquei impaciente em resolver questões do Beecrowd por sempre ter que testar a aplicação dentro do site, e isso demorava um pouco para carregar minha resposta que, em seguida, devolveria o feedback dela (se estava correta ou não).

E como eu desejava aprender toda a sintaxe básica do Java, usar a IDE não me parecia interessante, pois há diversos mecanismos de autocompletar para ajudar e eu queria algo mais sólido, que por decisão própria usei o VIM como ferramenta de edição de texto por linha de comando.

Em alguns casos, me incomodava o fato de haver alguns erros semanticos que eu poderia ter evitado testando-o na minha máquina, antes de colar o código elaborado no Beecrowd. Porém era custoso ter de compilar o programa usando o javac <programa.java> e em seguida executá-lo com java <programa>. (Caso esteja curioso e tenha dúvidas, procure entender como funciona o processo de compilação de um programa java). Nisso, notei que ambas as formas de testar era ruim e demorada.

Além disso, outro problema que tive, era poder guardar meus códigos em um repositório, para manter a prática de commitar e documentar meu aprendizado.

Dessa forma, decidi criar um script para facilitar meu processo de compilação e execução, que me permitia, após criar o programa java, compilar, executar e apagar o .class após o fim da execução do código pelo terminal.

E nisso, elaborei o seguinte script run.sh:

#!/bin/bash
# run.sh

# Verifica se o argumento foi passado
if [ -z "$1" ]; then
  echo "Uso: bash run.sh <diretório ou caminho para Main.java>"
  exit 1
fi

# Acessa o argumento passado
ARG=$1

# Verifica se é um diretório ou um arquivo
if [ -d "$ARG" ]; then
  DIR=$ARG
  FILE="$DIR/Main.java"
elif [ -f "$ARG" ]; then
  FILE=$ARG
  DIR=$(dirname "$ARG")
else
  echo "Erro: Argumento inválido. Passe um diretório ou o caminho para Main.java."
  exit 1
fi

# Compila o arquivo Main.java
javac "$FILE"

# Verifica se a compilação foi bem-sucedida
if [ $? -eq 0 ]; then
  # Executa a classe Main
  java -cp "$DIR" Main
  
  # Remove o arquivo .class após a execução
  rm "$DIR/Main.class"
else
  echo "Erro na compilação do arquivo."
  exit 1
fi

Aqui está um exemplo de execução do código:

$ bash run.sh Begginer/1000/
Hello World!

Outra maneira de facilitar mais ainda, é você criar um alias para o run no seu ~/.bashrc. No meu caso, coloquei o seguinte nele:

# alias for run.sh Java
alias run='bash ~/Github/Beecrowd/run.sh'

Note que o caminho de execução está ~/Github/Beecrowd/run.sh, e no seu caso, caso queria replicar, você deve colocar o caminho do seu script run.sh da sua árvore de arquivos.

E em seguida, executei source ~/.bashrc para carregar o bash com as mudanças adicionadas. Me permitindo rodar o código da seguinte maneira (dentro do meu diretório correspondente):

$ run Begginer/1000/
Hello World!

Todo o código está no meu repositório com um README bem explicativo de como está toda estrutura do repositório. E caso você tenha interesse em copiar: Sinta-se à vontade!

É isto, espero ter ajudado em algo. Qualquer dúvida, pode comentar que terei prazer em responder!

Qual versão do Java vc está usando?

Pois a partir do Java 11, para um único arquivo, é possível rodar direto, sem fazer o passo de compilação à parte. Ou seja, dá pra chamar assim:

java Beginner/1000/Main.java

Desta forma, ele já compila e roda, tudo de uma vez. Com o detalhe que o arquivo .class nem sequer é gerado, ou seja, vc não precisa se preocupar em apagá-lo depois.

Esta funcionalidade está definida na JEP 330 (Launch Single-File Source-Code Programs), e como o próprio nome diz, serve para "programas de um arquivo só" (que parece ser justamente o seu caso).

Sendo assim, daria para mudar o run.sh para:

#!/bin/bash

# Verifica se o argumento foi passado
if [ -z "$1" ]; then
  echo "Uso: bash run.sh <diretório ou caminho para Main.java>"
  exit 1
fi

# Verifica se é um diretório ou um arquivo
if [ -d "$1" ]; then
  FILE="$1/Main.java"
elif [ -f "$1" ]; then
  FILE=$1
else
  echo "Erro: Argumento inválido. Passe um diretório ou o caminho para Main.java."
  exit 1
fi

# Compila e executa o arquivo (ou mostra mensagem, caso ocorra algum erro)
java "$FILE" || echo "Erro na compilação/execução do arquivo."

E em vez de bash run.sh, uma sugestão é adicionar a permissão de execução ao arquivo:

chmod u+x run.sh

Desta forma, o usuário terá permissão de execução, aí basta rodá-lo como ./run.sh [argumentos].

Claro que vc pode dar permissões para todos com a+x, entre outras variações. Mas somente para o usuário rodar, u+x é o suficiente.


PS: para projetos com mais arquivos, dependências, etc, aí não recomendo fazer tudo na mão. Neste caso, ferramentas como o Maven ou Gradle são mais adequadas (mais aí acho que já estou desviando demais do assunto).

Sobre a versão do Java, acredito que seja superior a 11 mesmo. Interessante demais isso que voce falou, cara. Não sabia dessa funcionalidade para um único arquivo do Java. Logo mais, estarei reajustando o código como você indicou para evitar esses passos desnecessário. Obrigado pelo comentário, `kht`! Com certeza agregou muito com sua resposta.

Também usei muito o Beecrowd e tinha um script assim. Porém o meu validava a entrada e saída também. Vou deixar aqui para referência.

Minha estrutura:

Eu resolvia os exercícios em c++ e tinha essa estrutura de pastas:

workspace
    - resolvidos
        - categoria (iniciante, geometria ...)
            - 1001.cpp
            - 1002.cpp
    - in (arquivo com a entrada do beecrowd)
    - out (arquivo produzido pelo meu programa)
    - expected (arquivo com a saída do beecrowd)
    - 1081.cpp (arquivo que estou trabalhando no momento)
    - run.sh

run.sh:

g++ $1.cpp -o $1
/usr/bin/time -v ./$1 < in > out
code --diff out expected

O que isso faz?

usarei como exemplo ./run.sh 1081

g++ $1.cpp -o $1 -> compila o arquivo 1081.cpp produzindo o programa 1081 (sim, sem extensão)

/usr/bin/time -v ./$1 < in > out -> chama o programa time, que calcula quanto tempo demora para um programa executar e chama o programa 1081 passando para ele a entrada e escrevendo o que ele printar na saída

code --diff out expected -> compara o arquivo produzido com o esperado

FYC: trabalhando com entrada e saída de arquivos direto no terminal

./$1 < in > out

se digitar somente isso quando o terminal vê o operador '<' ele passa todo o conteúdo do arquivo para dentro do programa. como se você tivesse digitado aquilo.

se o terminal encontrar o operador '>' vai escrever a saída do programa no arquivo substituindo completamente o conteúdo dele. útil nesse caso quando roda várias vezes

se o terminal encontrar o operador '>>' vai escrever a saída do programa no final do arquivo sem apagar o conteúdo existente. Útil para logs

Acredito que algum insight desses que usava possam te inspirar a melhorar seu script

Entendido completamente, `Pilati`. Obrigado pelo comentário e levarei em consideração seus insights. Aliás, me parece que esse possa ser um dos problemas relacionados ao Beecrowd e outras plataformas de Pergunta e Resposta voltadas para programação. Me leva a ideia de, quem sabe um dia, tentar providenciar uma estrutura que possa apoiar quem realmente quer começar na programação por esse meio. Mas por hora, vou ver como posso melhorar meu script. Enfim, agradeço!