вторник, 17 мая 2011 г.

Генерируем шаблон для решений задач с GCJ

Писал введение, а его редактор сожрал. Вкратце: мне надоело писать одно и то же, решая каждый раз задачу в GCJ (как то: парсинг ввода, распараллеливание тестов, преобразование примеров ввода/вывода из задачи в тест-кейсы, и тому подобное), и я решил сделать шаблонный проект для гцж-шных солюшенов в SharpDevelop.

Решим, например, CandySplitting.

Суть там простая: есть два брата и мешок конфет разного веса. Нужно конфеты поделить поровну. Однако младший брат глупый, он умеет складывать веса конфет в двоичной системе столбиком, но при этом забывает переносить разряд. Короче, он ксорит, вместо того, чтобы складывать.

Старший брат видит такую тему, и решает воспользоваться ситуацией. Стоит вопрос: сколько кг конфет может зохавать старший брат, не обидев при этом младшего?

Итак, приступим. Я исхожу из предположения, что шарп девелоп уже установлен.

1. Скачиваем адд-ин.
2. Запускаем его и устанавливаем.
3. Рестартуем SharpDevelop.
4. Идём в File -> New -> Solution.
5. Выбираем Google Code Jam Solution Project в качестве шаблона и создаём проект CandySplitting.
6. Открываем Unit Tests Pad: View -> Tests -> Unit Tests. Запускаем тесты. Они, естественно, будут красные.
7. Сейчас вместо тестов заглушки. Делаем вместо них нормальные тесты. Открываем CandySplittingTest.cs и чиним:
using System;
using System.IO;
using NUnit.Framework;
using DNNX.GoogleCodeJam.Common;

namespace DNNX.GoogleCodeJam.CandySplitting.Test
{
    [TestFixture]
    public class CandySplittingTest : BaseTest<int[]>
    {
        [Test]
        public void SmokeTest()
        {
            var input = @"2
5
1 2 3 4 5
3
3 5 6";
            
            var expectedOutput = @"
Case #1: NO
Case #2: 11";

            SmokeTest(input, expectedOutput);
        }
        
        [Test]
        public void EdgeCase()
        {
            Assert.AreEqual("NO", CreateSolution().Solve(new[] {1, 2, 2}));
        }
        
        
        protected override Solution<int[]> CreateSolution(TextReader input, TextWriter output)
        {
            return new CandySplitting(input, output);
        }
    }
}
8. Тесты сделать актуальными просто. В SmokeTest просто копипастим тесткейсы прямиком из условия задачи. Этого часто бывает достаточно. Вот как стал выглядеть SmokeTest, например:
        [Test]
        public void SmokeTest()
        {
            var input = @"2
5
1 2 3 4 5
3
3 5 6";
            
            var expectedOutput = @"
Case #1: NO
Case #2: 11";

            SmokeTest(input, expectedOutput);
        }
9. Запускаем тесты опять, они по-прежнему красные. Неудивительно, решения-то ещё не написали. Идём в CandySplitting.cs.
using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using DNNX.GoogleCodeJam.Common;

namespace DNNX.GoogleCodeJam.CandySplitting
{
    public class CandySplitting : Solution<int[]>
    {
        public CandySplitting(TextReader input, TextWriter output)
            : base(input, output)
        {
        }
        
        public CandySplitting() {}
        
        public override object Solve(int[] a)
        {
            return null;
        }
        
        public override int[] ReadTestCase()
        {
            return null;
        }
    
        public static void Main(string[] args)
        {
            new CandySplitting().REPL();
            Console.ReadLine();
        }    
    }
}
10. Нужно перегрузить два метода: ReadTestCase() (он читает данные из входного потока и преобразует их в аргументы задачи), и Solve() (он решает то, что ему прочитал ReadTestCase()). Для чтения из в базовом классе Solution есть специальные методы int[] ReadInt, int[] ReadInts() и подобные, чтобы не приходилось руками писать всякие int.Parse(Console.ReadLine()).
        public override object Solve(int[] a)
        {
            var xor = a.Aggregate(0, (acc, x) => acc ^ x);
            if (xor == 0)
                return a.Sum() - a.Min();
            return "NO";
        }
        
        public override int[] ReadTestCase()
        {
            ReadString();
            return ReadInts();
        }
11. Запускаем тесты - они зелёные. Можно ещё добавить несколько (уже только на метод Solve, чтобы не заморачиваться с многострочными литералами).
12. Отсылаем задачу.
...
N. PROFIT!

Технические детали реализации адд-ина и базового класса для солюшена и тестов не расписываю намеренно. И так длинно получилось. Втыкайте в гитхаб, если интересно.

Комментариев нет:

Отправить комментарий