| сохранено

H dev||bet – битва программистов и технологий



Собаки vs кошки, водители vs пешеходы, Coca-Cola vs Pepsi, BMW vs Mercedes, колбаса vs сыр, узвар vs морс, добро vs зло, в конце концов! Но нет, мы, как всегда, спорили о том, какой язык программирования лучше. В ход шли классические аргументы о производительности со ссылками на бенчмарки, которые никто не проверял, синтаксические плюшки, которые используешь раз в год, графики популярности, списки авторитетных программистов использующих тот или иной язык… Затем разговор плавно перешел в обсуждение баттла Оксимирона с кем-то там. Ну и любой разговор дольше 20 минут сводится к обсуждению цены на биткоин.

Странным образом три темы слились в одну и так родилась идея dev||bet.


Суть проекта


Два разработчика, представляющие разные технологии, решают одну и ту же задачу. Побеждает тот, кто решит ее в кратчайшее время, или получит лучше результат за ограниченное время.

Трое комментаторов наблюдают за процессом на экранах участников. Они обсуждают их подход к решению, подмечают интересные моменты, нестандартные ходы и просто интересные фишки, которые есть у любого программиста. А с другой стороны, обращают внимание на неудачные решения и провалы.

Мы, зрители, хотим увидеть как обычные разработчики думают, гуглят, ошибаются, и в, конце концов, приходят к решению. Более того, нам скорее интереснее наблюдать за затыком лидов над библиотечной функцией, чем щелканье алгоритмов натасканным олимпиадником.

Для пилотного выпуска в качестве участников мы позвали друзей, которые и определили используемые технологии: Python vs JavaScript.

Задача первого выпуска


Чтобы проект был успешным в 2018 году, в его названии обязательно должно быть упоминание криптовалют или блокчейн. Поэтому немного помечтав, мы придумали несложную задачу. Формальное описание, которое получали участники выглядело вот так:

Сегодня все говорят о криптовалютах. Было много случаев, когда люди продавали биткоины по невероятно низкой цене. Ну а я когда-то думал купить 150 BTC за $15…
Но что, если бы у нас была машина времени, которая могла бы передавать команды крипто брокеру в прошлое? Конечно, мы бы обрушили глобальную финансовую систему. Тем не менее давайте представим, что машина у нас есть, ну или, по крайней мере, мы близки к ее созданию. Мы хотим, чтобы ты, грязный голодный фрилансер работающий за еду, создал для нас алгоритм, который генерирует последовательность команд для нашего брокер-клиента. (Которого мы сами создали год назад специально для использования в будущем, конечно же.)

Ты можешь использовать Python или JavaScript. Пожалуйста, проверь свое решение с помощью приложенного тестового раннера. Тот чье решение заработает больше денег, будет выбран, чтобы написать бота для реального клиента.

API specification v0.0.18


Раннер задачи должен быть чистой функцией, которая принимает два аргумента:
prices => [{"btc": 1000}, ...] – Массив цен на биткоин по дням
initialSum => 100000 – Стартовый баланс в USD
Функция должна возвращать массив команд. Команды вызываются по очереди, одна в день. То есть для 14 дней у вас должно быть ровно 14 команд.
Пример:
[{"op":"buy","amount":1},{"op":"sell","amount":1}]
Команды:
"buy" дополнительные атрибуты: amount [float] – Купить BTC используя текущий USD счет
"sell" дополнительные атрибуты: amount [float] – Продать BTC используя текущий USD счет
"pass" – Пропустить день

Описание задачи намеренно было сделано запутанным и нечетким, чтобы сделать процесс интереснее. Правда, в результате это сыграло злую шутку, и сильно увеличило время решения участниками.

По сути же требовалось написать функцию, которая по уже известной истории изменения цен выдаст инструкции когда стоит покупать биткоины, когда продавать, ну а когда просто подождать.

Реализация


В качестве платформы была выбрана площадка Codewars. Не то чтоб мы долго выбирали, но на ней было все необходимое: поддержка более 20 языков, простой интерфейс, возможность добавить черновик задачи доступный по ссылке.

Довольно интересным кажется проект codebattle.hexlet.io, о котором уже писали на Хабре. Но возможность видеть код соперника пока нам показалась лишней.

Поскольку участники использовали Python и JavaScript, то на них и были реализованы тестовые раннеры:

JavaScript
'use strict';

const https = require('https');

const currencies = [
    'btc',
    'eth',
];
const BASE_CURRENCY = 'usd';
const DEFAULT_CURRENCY = 'btc';

const fetchRates = (days, currency) => new Promise((res, rej) => {
    https.get(`https://min-api.cryptocompare.com/data/histoday?aggregate=1&e=CCCAGG&extraParams=CryptoCompare&limit=${days}&tryConversion=false&tsym=${BASE_CURRENCY.toUpperCase()}&fsym=${currency.toUpperCase()}`, (resp) => {
        let data = '';
        resp.on('data', (chunk) => {
            data += chunk;
        });
        resp.on('end', () => {
            data = JSON.parse(data);
            data = data.Data;

            res(data.map(datum => datum['close']).slice(0, -1));
        });

    }).on("error", err => rej(err));
});

const fetchAllRates = async (days, currencies) => {
  const prices = {};
  for (let currency of currencies) {
      prices[currency] = await fetchRates(days, currency);
  }

  const len = prices[Object.keys(prices)[0]].length;
  const ret = [];
  for (let i = 0; i < len; i++) {
      let price = {};
      for (let currency of currencies) {
          price[currency] = prices[currency][i];
      }
      ret.push(price);
  }

  return ret;
};

const checkStash = stash => {
    const vals = Object.values(stash);
    for (let val of vals) {
        Test.expect(val >= -Math.pow(10, -6), 'Invalid operation');
        if (val < -Math.pow(10, -6)) {
            throw new Error(`Debts are not supported. Stash: ${JSON.stringify(stash)}`)
        }
    }
};

const applyTask = (stash, task, prices) => {
    console.log('- performing task', stash, task, prices);
    const currency = task.currency || DEFAULT_CURRENCY;

    switch(task.op) {
        case 'buy':
            stash[currency] += task.amount;
            stash[BASE_CURRENCY] -= task.amount * prices[currency];
            break;
        case 'sell':
            stash[currency] -= task.amount;
            stash[BASE_CURRENCY] += task.amount * prices[currency];
            break;
        case 'pass':
            break;
    }

    return stash;
};

const runner = async (trader, cases) => {
    for (let testCase of cases) {
        let prices = await fetchAllRates(testCase.days, currencies);

        let stash = testCase.amount;
        for (let currency of currencies) {
            stash[currency] = stash[currency] || 0;
        }

        console.log(`Testing amount ${stash[BASE_CURRENCY]}, days ${testCase.days}`);
        let tasks = await trader(prices, stash[BASE_CURRENCY]);
        for (let i in tasks) {
            if (!tasks.hasOwnProperty(i)) {
                continue;
            }
            let job = tasks[i];
            let todo = job.length ? job : [job];
            for (let row of todo) {
                await applyTask(stash, row, prices[i]);
            }
            checkStash(stash);
        }

        let result = Math.floor(stash[BASE_CURRENCY] * 100) / 100;
        console.log(`finished. Resulting amount: ${result}`);
    }
};

runner(trader, [
    {
        amount: {
            [BASE_CURRENCY]: 100,
        },
        days: 100,
    },
]);


Python
import urllib2
import json
import math

currencies = [
    'btc',
    'eth',
]

BASE_CURRENCY = 'usd'
DEFAULT_CURRENCY = 'btc'


def fetch_rates(days, currency):
    data = urllib2.urlopen(
        'https://min-api.cryptocompare.com/data/histoday?aggregate=1&e=CCCAGG&extraParams=CryptoCompare&limit={}&tryConversion=false&tsym={}&fsym={}'.format(
            days, BASE_CURRENCY.upper(), currency.upper())).read()
    data = json.loads(data)['Data']

    return [row['close'] for row in data][:-1]


def fetch_all_rates(days, currencies):
    prices = {currency: fetch_rates(days, currency) for currency in currencies}

    return [{currency: prices[currency][i] for currency in currencies} for i in range(days)]


def check_stash(stash):
    for currency in stash:
        test.assert_equals(stash[currency] >= -0.000001, True, 'Invalid operation')
        if stash[currency] < -0.000001:
            raise Exception('Debts are not supported. Stash: {}'.format(stash))


def apply_task(stash, task, prices):
    print '- performing task {} {} {}'.format(stash, task, prices)
    currency = task['currency'] if 'currency' in task else DEFAULT_CURRENCY

    if task['op'] == 'buy':
        stash[currency] += task['amount']
        stash[BASE_CURRENCY] -= task['amount'] * prices[currency]
    elif task['op'] == 'sell':
        stash[currency] -= task['amount']
        stash[BASE_CURRENCY] += task['amount'] * prices[currency]
    elif task['op'] == 'pass':
        pass

    return stash


def runner(trader, cases):
    for testCase in cases:
        prices = fetch_all_rates(testCase['days'], currencies)

        stash = testCase['amount']
        for currency in currencies:
            if currency not in stash:
                stash[currency] = 0

        print 'Testing amount {}, days {}'.format(stash[BASE_CURRENCY], testCase['days'])
        tasks = trader(prices, stash[BASE_CURRENCY])

        for i, job in enumerate(tasks):
            todo = job if isinstance(job, list) else [job]
            for row in todo:
                stash = apply_task(stash, row, prices[i])
            check_stash(stash)

        result = math.floor(stash[BASE_CURRENCY] * 100) / 100
        print 'finished. Resulting amount: {}'.format(result)


runner(trader, [
    {
        "amount": {
            BASE_CURRENCY: 100,
        },
        "days": 100,
    }
])


Сама задача доступна на Codewars
А решения участников на Github

Спасибо за внимание! Ждём ваших комментариев, критики и мнений.

А что же дальше? PHP vs. JS, .Net vs. Java, iOS vs. Android, React vs. Vue.js?
+32
~2700

комментарии (4)

+3
+4 –1
kagary06 ,  
Чтобы проект был успешным в 2018 году, в его названии обязательно должно быть упоминание криптовалют или блокчейн.
Хайп, хайп, хайп… И при чем тут вообще блокчейн и криптовалюты, если название проекта dev||bet?
Описание задачи намеренно было сделано запутанным и нечетким, чтобы сделать процесс интереснее.
Добавлю еще немного в копилку:
  • А поиграйтесь с цветами.
  • В вашем алгоритме нет души (стиля, индивидуальности)!
  • Сделайте так, чтобы сын члена жури смог понять, что делает этот генетический алгоритм за 5 слов. (Шутка. Так уж и быть. Можете воспользоваться еще тремя прилагательными).
  • Напишите весь код за наименьшее количество символов в одной строке.
  • Вы можете использовать только Vim!


Мы, зрители, хотим увидеть как обычные разработчики думают, гуглят, ошибаются, и в, конце концов, приходят к решению. Более того, нам скорее интереснее наблюдать за затыком лидов над библиотечной функцией, чем щелканье алгоритмов натасканным олимпиадником.

Мне кажется, что это противоречивое сочетание. В современном мире уже многие простые операции и алгоритмы давным давно написаны по нескольку раз в различных библиотеках, чтобы сделать экономически выгодным процесс написания ПО и не повторять код постоянным копированием. Многие современные программисты в большинстве случаев не писали давно известные алгоритмы, потому что они уже давно написаны и есть в интернете. И они проверены миллионы раз и давно работают.
_________________________

Но нет, давайте сыграем в Игру.
Возьмем двух (А лучше пять!.. Нет. Десять!) разработчиков и поставим им несвойственную их опыту задачу и посмотрим, что они напишут за условные 10 минут. В случае поражения отберем у них смузи и котиков в интернете.
Время пошло.
Что же делать? Писать свой велосипед или же поискать в интернете?
Спросить у зала или пойти в чат к группе поддержки из 10 человек, которые уже вооружены интернетом и решениями предыдущих задач? Они уже ждут условного сигнала, чтобы скинуть готовый вариант решения.

А в это время...
А в это время зрители похоже уже устали смотреть за двумя людьми, которые просто будут задавать новые вопросы на StackOverflow или по другим «разрешенным» ресурсам для поиска уже готового решения задачи.

Но начинается то, чего мы долго ждали: люди не справляются с задачей и чтобы не проиграть, начинают мухлевать. Всё или ничего. В ход идут макросы, goto, «макаронный код», переменные перестают терять осмысленное название, у одного из участников совершенно случайно «ломается клавиатура» или «зависает компьютер»…
Страсти накаляются, логика нервно курит в сторонке и мы видим как на лицах участников побеждают эмоции...
А потом...
А потом условные Васи, которые не факт, что сами хотя бы раз в своей жизни решили такую задачу правильно, будут судить наших «жертв» на публику.
При этом судьи скорее всего будут иметь запас времени, чтобы самим изучить уже готовые варианты решений, которые написаны экспертами, но не обладая полностью их знаниями о проблеме будут выносить приговоры.

До финала доходят только двое.
Они с отвращением и ужасом ждут очередного подвоха от судей.
Ведь зрители хотят хлеба и зрелищ. А значит, чтобы стать чемпионом нужно не просто решить задачу, а еще и красиво сыграть.
Кого играть? Это зависит от зрителей. Возможно им понравится образ ботаника в очках, или же татуированного накаченного парня с бицепсами. (а ведь еще недавно без бороды и старой огромной оправы ты бы вряд ли смог победить)
А в чате...
А в чате модераторы уже нещадно банят зрителей, которые хотят увидеть «блондинок» и «индусов» на этом шоу.

В финале нужно решить одну из задач, которую «не решал ни один в мире программист».
(Надеемся, что наши участники об этом точно ни разу не слышали).
Практический пользы в ней нет. Нужно написать алгоритм для автоматического проставления «like» в социальной сети для участников жюри. Ведь сейчас это популярно.
(Пользователи уже ждут не дождутся халявных скриптов для накрутки счетчиков для собственных аккаунтов).
А кто больше всего «лайкнет» постов жюри, тот и победил.
Процесс пошел…

И вот звучит Имя победителя. Организаторы дарят ему спонсорский подарок (очередную крутую клавиатуру, которые компания выпустила специально для участников этого турнира, потому что имеет специальную подставку для кофе, которая так пригодится нашему победителю) и «фирменную чашку» (на самом деле такими чашками уже месяц пользуются члены жюри и им просто хочется заменить их на другие).
А наш герой уходит вдаль, чтобы освободить своё место для новой группы адептов Игры....

Где-то за несколько тысяч километров...
Где-то за несколько тысяч километров в это время группа ученых в тихой и спокойной обстановке разрабатывает новое программное обеспечение для исследования космоса, пытаются обучить нейронные сети, занимаются разработкой нового языка для программирования роботов и уже вечером, перед тем как закончится их рабочий день, обсуждают новые замыслы, идеи и мечтают познать еще неизведанные тайны Вселенной.
Каждый из них пришел сюда со своими способностями и опытом. Многие готовы поделился со всеми желающими своими знаниями и мыслями. Они не пытаются выяснить кто из них самый умный и поставить себя выше остальных (но в душе иногда и у них закрадываются такие мысли, хотя умом они понимают, что вместе они способны сделать больше, чем по одиночке). Ведь они знают, что каждый человек в чем-то уникален, что каждый имеет свои особенности, опыт, навыки, сильные и слабые стороны.
Но день подходит к концу и они заканчивают работать, зная, что дома их ждут родные, которые их любят. Ведь не смотря на все проблемы, которые случаются в их жизни, любовь и счастье близких людей, для которых они и стараются сделать этот мир лучше — это главное в их жизни.
+1
lex08275 ,  
Это божественно, прям картина нарисовалась в воображении.
0
prefrontalCortex ,  
В JS главное — это результат

Где-то я это уже слыш- кх-кх, пхп, кхе-кх

–1
+1 –2
robert_ayrapetyan ,  
Почем десяток плюсов к статье на хабре нынче?