Хабр - не жалобная книга, я знаю. Но тут история про код, с примерами, разбором антипаттернов и всё такое, поэтому я рискну.

Всё началось, когда я узнал про конкурс красоты кода от Сбера. Я как раз хотел поучаствовать в каком-нибудь эпичном конкурсе, а тут он мне и подвернулся, тем более что я - тот человек, которому есть что рассказать про красивый код. Я даже целую статью запилил о том, как писать красивый и понятный код. Так что я решил, что в данном случае мои шансы на победу - в отличие от остальных конкурсов - всё же больше нуля. Кроме того, я хотел выступить на конференции PiterPy (спойлер: хрен мне), чтобы рассказать там про красивый код и всё такое, поэтому участие в конкурсе и сравнение результатов было бы классным подспорьем.

Погромирование

Про конкурс я узнал поздновато, оставалась где-то пара дней, и у меня была установка: не тратить на конкурс слишком много времени. Банально потому что тратить больше пары часов невыгодно - если меня не возьмут в победители, то по крайней мере двух часов мне не жалко. Поэтому я выделил 2 часа и ровно в 1:00 ночи накануне окончания конкурса я начал писать код.

Задание было про симулятор торговли на бирже, вот такое:

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

from decimal import Decimal
from enum import Enum

class AssetPrice(Enum):
	# Котировальный список активов.
	LKOH = Decimal(5896)
	SBER = Decimal(250)

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

@dataclass
class PortfolioSimulator:
	def __post_init__(self):
		self.logo = (Path(__file__).parent / 'logo.txt').read_text()

	def run(self):
		""" Интерактивный режим """
		self.print_greeting()

	def print_greeting(self):
		print(self.logo)
		print('Добро пожаловать в симулятор торговли активами!')
		print('Постарайтесь не разориться в первый день :>')
> python trading-simulation.py

⠀⠀⠀⠀⠀⢀⠤⠐⠒⠀⠀⠀⠒⠒⠤⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⡠⠊⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠑⢄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⡔⠁⠀⠀⠀⠀⠀⢰⠁⠀⠀⠀⠀⠀⠀⠈⠆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⢰⠀⠀⠀⠀⠀⠀⠀⣾⠀⠀⠔⠒⠢⠀⠀⠀⢼⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⡆⠀⠀⠀⠀⠀⠀⠀⠸⣆⠀⠀⠙⠀⠀⠠⠐⠚⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠇⠀⠀⠀⠀⠀⠀⠀⠀⢻⠀⠀⠀⠀⠀⠀⡄⢠⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡀⠀⠀
⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⣀⣀⡠⡌⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡀⢄⣲⣬⣶⣿⣿⡇⡇⠀
⠀⠀⠆⠀⠀⠀⠀⠀⠀⠀⠘⡆⠀⠀⢀⣀⡀⢠⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⢴⣾⣶⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀
⠀⠀⢸⠀⠀⠀⠀⠠⢄⠀⠀⢣⠀⠀⠑⠒⠂⡌⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠛⢿⣿⣿⣿⣿⣿⣿⣿⡇⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠑⠤⡀⠑⠀⠀⠀⡘⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡀⣡⣿⣿⣿⣿⣿⣿⣿⣇⠀
⠀⠀⢀⡄⠀⠀⠀⠀⠀⠀⠀⠈⢑⠖⠒⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠐⣴⣿⣿⣿⡟⠁⠈⠛⠿⣿⠀
⠀⣰⣿⣿⣄⠀⠀⠀⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⢈⣾⣿⣿⣿⠏⠀⠀⠀⠀⠀⠈⠀
⠈⣿⣿⣿⣿⣷⡤⣀⡀⠀⠀⢀⠎⣦⣄⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣢⣿⣿⣿⡿⠃⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠘⣿⣿⣿⣿⣿⣄⠈⢒⣤⡎⠀⢸⣿⣿⣿⣷⣶⣤⣄⣀⠀⠀⠀⢠⣽⣿⠿⠿⣿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠹⣿⣿⣿⣿⣿⣾⠛⠉⣿⣦⣸⣿⣿⣿⣿⣿⣿⣿⣿⣿⡗⣰⣿⣿⣿⠀⠀⣿⠀⠀⠀⠀⠀⠀⣀⡀⠀⠀
⠀⠀⡰⠋⠉⠉⠉⣿⠉⠀⠀⠉⢹⡿⠋⠉⠉⠉⠛⢿⣿⠉⠉⠋⠉⠉⠻⣿⠀⠀⣿⠞⠉⢉⣿⠚⠉⠉⠉⣿⠀
⠀⠀⢧⠀⠈⠛⠿⣟⢻⠀⠀⣿⣿⠁⠀⣾⣿⣧⠀⠘⣿⠀⠀⣾⣿⠀⠀⣿⠀⠀⠋⠀⢰⣿⣿⡀⠀⠛⠻⣟⠀
⠀⠀⡞⠿⠶⠄⠀⢸⢸⠀⠀⠿⢿⡄⠀⠻⠿⠇⠀⣸⣿⠀⠀⣿⣿⠀⠀⣿⠀⠀⣶⡀⠈⢻⣿⠿⠶⠆⠀⢸⡇
⠀⠀⠧⢤⣤⣤⠴⠋⠈⠦⣤⣤⠼⠙⠦⢤⣤⡤⠶⠋⠹⠤⠤⠿⠿⠤⠤⠿⠤⠤⠿⠳⠤⠤⠽⢤⣤⣤⠴⠟⠀
Добро пожаловать в симулятор торговли активами!
Постарайтесь не разориться в первый день :>

Заодно там видны "красивые" фишечки питона: датаклассы с их __post_init__, чтение файла в одну строчку.

Далее я подумал, что прикольно было бы иметь несколько возможных алгоритмов ценообразования, чтобы можно было попробовать себя на каких-то симулируемых данных, а потом попробовать торговать и в реальных условиях. Поэтому я создал три стратегии:

1 - Стратегия "сегодня мне всё лень": цена акций просто всегда фиксированная. Just for fun.

class AssetPriceHistory(ABC):
    """
    История цен активов.

    Можно переопределять в дочерних классах, чтобы симулировать различные ситуации.
    """

    @abstractmethod
    def __iter__(self) -> Iterator[tuple[date, AssetPrice]]:
        """
        Возвращает историю цен активов - кортежи вида (дата, цены активов).
        """
        ...


@dataclass
class ChillAssetPriceHistory(AssetPriceHistory):
    """
    История цен активов, в которой цены не меняются.

    Когда устали от трейдинга и просто хотите не думать.
    """

    def __iter__(self) -> Iterator[tuple[date, AssetPrice]]:
        today = date.today()
        asset_price = AssetPrice()
        for day in count():
            date_ = today + timedelta(days=day)
            yield date_, asset_price

Тут абстрактный базовый класс, type hints, итераторы и itertools.count(). Красота!

2 - Стратегия "мне повезёт": цена скачет псевдорандомно от -50% до +50% базовой цены. Мастерство трейдинга тут не отточишь, зато можно понять уровень удачливости трейдера, что тоже немаловажно >:)

@dataclass
class ChaosAssetPriceHistory(AssetPriceHistory):
    """
    История цен активов, в которой цены меняются случайным образом.

    Только для самых отчаянных трейдеров.
    """

    # множитель цены актива; 
    # по умолчанию актив может просесть на 50% или 
    # взлететь на 50% за один день, хе-хе
    price_multiplier: tuple[float, float] = (0.5, 1.5)
    seed: int = 42

    def __post_init__(self):
        self.random = Random()
        self.random.seed(self.seed)

    def __iter__(self) -> Iterator[tuple[date, AssetPrice]]:
        today = date.today()
        base_asset_price = AssetPrice()
        for day in count():
            date_ = today + timedelta(days=day)
            if day == 0:
                asset_price = base_asset_price
            else:
                asset_price = AssetPrice(**{
                    field: getattr(base_asset_price, field) * multiplier
                    for field in AssetPrice.__dataclass_fields__.keys()
                    if (multiplier := Decimal(self.random.uniform(*self.price_multiplier)))
                })

            yield date_, asset_price

Наверно, walrus в цикле и dict unpacking - это слишком мудрёно и некрасиво, зато можно в одном выражении задать цены на день. Ну если не понравится жюри - ну штош!

3 - Стратегия "как в реале": цена была взята просто из реальных данных акций Сбера и ещё чего-то за 10 дней - чтобы человек просто мог поторговать и понять, насколько всё плохо бывает.

@dataclass
class RealAssetPriceHistory(AssetPriceHistory):
    """
    Настоящая история цен активов, взятая из исторических данных.

    Без шуток.

    В дальнейшем можно брать по API откуда-нибудь.
    """

    def __iter__(self) -> Iterator[tuple[date, AssetPrice]]:
        yield from (
            (date(2023, 9, 10), AssetPrice(LKOH=Decimal(6669), SBER=Decimal(255))),
            (date(2023, 9, 11), AssetPrice(LKOH=Decimal(6456), SBER=Decimal(256))),
            (date(2023, 9, 12), AssetPrice(LKOH=Decimal(6729), SBER=Decimal(262))),
            (date(2023, 9, 13), AssetPrice(LKOH=Decimal(6610), SBER=Decimal(258))),
            (date(2023, 9, 14), AssetPrice(LKOH=Decimal(6519), SBER=Decimal(260))),
            (date(2023, 9, 15), AssetPrice(LKOH=Decimal(6553), SBER=Decimal(260))),
            (date(2023, 9, 16), AssetPrice(LKOH=Decimal(6527), SBER=Decimal(260))),
            (date(2023, 9, 17), AssetPrice(LKOH=Decimal(6566), SBER=Decimal(263))),
        )

Это, конечно, кустарщина, и в идеале нужно в этом классе получать реальные исторические данные по API из какого-нибудь сервиса, но это не для тестового задания. Поэтому просто забиваем значения ручками. Зато yield from - красиво!

А вот как это встраивается в симулятор торговли:

@dataclass
class PortfolioSimulator:
    history: AssetPriceHistory = field(default_factory=RealAssetPriceHistory)  # dependency injection такое, можно подставлять разные истории и тренироваться на разных данных
    
    def __post_init__(self):
        self.days = iter(self.history)
        self.next_day()  # получаем первую дату и цены активов
        ...

    def next_day(self):
        """ Закончить день и получить цены нового дня """
        self.current_date, self.current_prices = next(self.days)

Получился такой вот dependency injection (красиво!), чтобы можно было легко применять стратегии в нашем симуляторе. default_factory по умолчанию ставит реальные исторические данные. В дальнейшем представители Сбера могут взять мой код, вставить туда свой исторический класс и сразу пушить во все репозитории, которые у них есть, и деплоить в продакшн. Очень удобно.

Далее осталось написать саму программу, которая бы обрабатывала пользовательский ввод, считала портфель и так далее. Я ничего особо крутого изобретать не стал, просто постарался логически разбить всё на маленькие кусочки, которые были простые для понимания, плюс добавил match-case просто чтобы выпендриться и показать, что "Хоба! Я знаю про match-case!" (его ведь для этого и создали, да?)

Ниже - код симулятора, всё неинтересное я превратил в троеточие или убрал, для любопытных есть репа.

@dataclass
class PortfolioSimulator:
    history: AssetPriceHistory = field(default_factory=RealAssetPriceHistory)  # dependency injection такое, можно подставлять разные истории и тренироваться на разных данных
    cash: Decimal = Decimal(100_000)  # начальный капитал
    assets: defaultdict = field(default_factory=lambda: defaultdict(int))  # сколько активов у нас есть (в начале мы ничего не имеем)

    @property
    def asset_values(self) -> list[tuple[str, int, Decimal]]:
        """ Стоимость активов """
        return [
            (asset, quantity, getattr(self.current_prices, asset) * quantity)
            for asset in self.current_prices.__dataclass_fields__.keys()
            if (quantity := self.assets[asset]) != 0  # не показываем то, чего не имеем
        ]

    @property
    def value(self) -> Decimal:
        """ Стоимость портфеля """
        return self.cash + sum(price for _, _, price in self.asset_values)

    @property
    def profit(self) -> Decimal:
        """ Прибыль """
        return self.value - self.initial_value

    def run(self):
        """ Интерактивный режим """
        self.print_greeting()

        while True:
            self.print_summary()
            try:
                self.user_action()
            except StopGameException:
                break

        self.print_result()

    def print_greeting(self):
        ...

    def print_summary(self):
        ...

    def user_action(self) -> bool:
        """ Выбор действия пользователя """

        match input(dedent("""
            Что вы хотите сделать?
            1. Купить актив
            2. Продать актив
            3. Закончить день
            4. Зафиксировать значения и завершить программу
        """)):
            case "1":
                asset = input("Какой актив вы хотите купить? ")
                amount = input_int("Сколько? ")
                try:
                    self.buy(asset, amount)
                except (WrongAssetName, NotEnoughCash) as exc:
                    print(exc)
                else:
                    print(f"Вы купили {amount} {asset}")
                sleep(1)

            case "2":
                asset = input("Какой актив вы хотите продать? ")
                amount = input_int("Сколько? ")
                try:
                    self.sell(asset, amount)
                except (WrongAssetName, NotEnoughAsset) as exc:
                    print(exc)
                else:
                    print(f"Вы продали {amount} {asset}")
                sleep(1)

            case "3":
                try:
                    self.next_day()
                except StopIteration as exc:
                    raise StopGameException() from exc

            case "4":
                raise StopGameException()

            case _:
                print("Неправильный выбор, попробуйте ещё раз.")

    def print_result(self):
        if (profit := self.profit) > 0:
            print("Stonks! Вы закончили торговлю с прибылью! Отправляю данные о вас в налоговую! }:)")
        elif profit == 0:
            print("Ну вы хоть не разорились, это уже хорошо")
        else:
            print("Not stonks! Вы закончили торговлю в минус! Штош, бывает :[")

Мне кажется, что тут всё чисто и понятно, потому что код читается почти как естественный язык. За исключением def asset_values, конечно - для конкурса можно было бы и развернуть в обычный for цикл, но я что-то не подумал.

Как раз уже было 3:00 ночи, я подумал, что больше ни минуты не потрачу на эту штуку. Я заполнил форму на сайте и отправил решение. Сайт просто сказал мне, что "окей чувак, всё получено", и больше ничего мне не пришло - ни email, никакого подтверждения вообще, как будто меня не существовало вовсе. Я даже до сих пор не уверен, есть я там в их системе или нет. До жути напоминает электронное голосование :]

Ожидание

Ну и собственно я стал ждать. Было сказано, что победителей объявят через несколько дней. В заветный день я полез на сайт, чтобы поскорее узнать результаты - но даже и на следующее число на сайте ничего не обновилось. Я решил, что Сбер завалило красивым кодом, и они в спешке всё просматривают и не успевают. Но потом я читаю какой-то паблик в телеграме и внезапно вижу запись о том, что Сбер уже наградил победителей этого конкурса на своей SmartDev конференции. Так, ладно, сейчас посмотрим...

Я себе по-другому это представлял...
Я себе по-другому это представлял...

Я выпал в осадок, потому что на самом сайте никаких результатов не было, и почему вообще людей награждают на какой-то конференции, и почему мне никто не написал, что я дебил и что я плохо всё решил? В общем, это была какая-то мутная тема. Меня это, конечно, напрягло, и люди, которые читают Хабр, в комментариях к статье написали:

Каком способом можно будет узнать результаты конкурса? Написано в правилах, что результаты будут сегодня, но не написано, каким способом их оглашают) Заранее спасибо)

А где-то можно посмотреть решения победителей по направлению "Frontend"?

Результаты опубликованы на день позже обозначенного срока, причем никакого уведомления мне как участнику не пришло, не обозначены критерии оценок, сколько баллов набрала моя работа, вывешен просто список фио "победителей". С кодом лидеров хоть бы дали возможность ознакомиться. Мутно как-то все.

да, организация на 2 с плюсом. Никаких рассылок на почту. Откуда я должен знать, что оглашение результатов конкурса будет на какой-то конференции SmartDev? Этого нет в полных правилах конкурса.

Потом Сбер таки сделал жест и написал список победителей в формате {first_name} {last_name}, {city}: Сделали по красоте: победители «Конкурса красоты кода»

Ну уж нет, Сберушка! Результаты должны быть честными, должны учить нас чему-то. Покажите мне победителя, расскажите, какой у него опыт, где он работает, как он научился писать по красоте (чтобы я так же смог), и главное, что я спрашиваю на любых разборках: покажите мне код!

Я, конечно, написал Оксимирону (псевдоним - Артём Фатхуллин), который награждал победителей, с вопросом "как мне связаться с победителями и посмотреть код", но он прочитал и ничего не ответил.

Кто-то (извините), Антон Соломатин, Арина Жук, Артём Фатхуллин
Кто-то (извините), Антон Соломатин, Арина Жук, Артём Фатхуллин

Реальность

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

На самом деле я не мастер по OSINT, я даже сам себя в интернете найти не могу :D Но я постарался, честное слово, и я нашёл только двоих питонистов: мистера "Изящный код" aka Ивана Звонарёва из Ижевска, и мисс "Изящный код" aka Арину Жук из Москвы.

Арина любезно поделилась со мной своим решением по секции Data Science. Я не эксперт по ML, но мне оно понравилось - всё разложено по полочкам, легко читается, задачу решает. Вообще без претензий, по моему дилетантскому мнению - приз заслужен. Почему Сбер это не выложил - я хз.

Код Ивана я нашёл на гитхабе, позже в личной переписке он подтвердил, что он - это он, и это действительно тот код, что он отправил на конкурс и победил. Вот и сам код: https://github.com/Royal00Blood/Sber_task

Мне кажется, что он не самый красивый, и вот почему - по пунктам.

Это "интерфейс", но не в программистском понимании, а как бы "интерфейс взаимодействия с пользователем":

def interface():
    exchange = FExchange()
    action = None

    print(" 1 - Sell \n",
          "2 - Buy \n",
          "3 - Show the value of the financial portfolio ",
          "4 - exit")

    while 1:

        try:
            action = int(input("Enter 1, 2, 3 or 4: "))
        except ValueError:
            print("Sorry! You enter no number!")

        if action == 1:
            exchange.sell_shares()

        elif action == 2:
            exchange.buy_shares()

        elif action == 3:
            exchange.e_cash_show()

        elif action == 4:
            exit()

Тут 3 момента:

  • Ожидается, что весь интерфейс будет реализован в этом модуле (он же называется Interface.py!), однако на самом деле ввод-вывод размазан по модулям. Это некрасиво.

  • action = None нужно, потому что даже при плохом вводе содержимое цикла продолжает выполняться вместо того, чтобы сделать continue и попросить заново ввести корректное значение

  • При выборе 4 программа просто бескомпромиссно завершается. Это некрасиво, exit() лучше делать на "верхних" уровнях (где-нибудь в main), а из функций лучше просто выходить: elif action == 4: break

Ну ладно, что там в FExchange?

from AssetPrice import AssetPrice


class FExchange:
    """
    This is the main class that performs
    the function of simulating the processes
    of selling and buying shares on a financial exchange.
    """
    def __init__(self):
        self.__e_cash = 0  # The value of the financial portfolio
        self.__fin_p = {a.name: 0 for a in AssetPrice}  # Stocks in the financial portfolio
        self.__f_abil = False  # The possibility of selling something

    def e_cash_show(self):
        """
        :return:Displays the value of all shares in the portfolio
        """
        self.__capital_calcul()
        print(self.__e_cash)

    def buy_shares(self):
        """
        The function performs the process of buying shares from the available.
        :return:
        """
        for i in AssetPrice:
            print(f"-----------> You have  {i.name} shares of {self.__fin_p[i.name]} ")
        print("You can to buy :")
        for x in AssetPrice:
            print(f"-----------> Company: {x.name}, coast: {x.value}")
        self.__action_shares()

    def sell_shares(self):
        """
        The function performs the process of selling shares from a financial portfolio.
        :return:
        """
        for i in AssetPrice:
            if not self.__f_abil:
                if self.__fin_p[i.name] > 0:
                    self.__f_abil = True
                    print(f"You have  {i.name} shares of {self.__fin_p[i.name]} ")
        if not self.__f_abil:
            print("You don't have any shares")
        else:
            self.__action_shares(incr=False)

    def __action_shares(self, incr=True):
        """
        Function for changing the values of stocks in the financial portfolio.
        :param incr: А logical variable that determines the increase or decrease of a block of shares.
        :return:
        """
        while 1:
            n_promotion = input('Enter name of the promotion: ')
            if n_promotion in self.__fin_p:
                break
            else:
                print("Enter again. You have  mistake. (Example: SBER)")
        while 1:
            try:
                c_promotion = int(input('Enter the number of shares to buy: '))
            except ValueError:
                print("Enter again")
            else:
                break
        if incr:
            self.__fin_p[n_promotion] += c_promotion
        else:
            self.__fin_p[n_promotion] -= c_promotion

    def __capital_calcul(self):
        """
        The function calculates the sum of all the shares in the financial portfolio.
        :return: Current value of the financial portfolio.
        """
        for i in AssetPrice:
            self.__e_cash += self.__fin_p[i.name] * i.value
  • Почему __f_abil, __fin_p, __e_cash называются так, как называются? Как же читаемость? __e_cash - это цифровой рубль?

  • for i in AssetPrice: - почему i, а не asset?

  • Кажется, понятие "деньги на руках" в этом коде отсутствует в принципе, поэтому в __action_shares нет никакой проверки на лимиты. Кому нужно 100000000000000000 акций сбера? Хоба!

Enter 1, 2, 3 or 4: 2
-----------> You have  LKOH shares of 0 
-----------> You have  SBER shares of 4 
You can to buy :
-----------> Company: LKOH, coast: 5896
-----------> Company: SBER, coast: 250  <--- хочу вот это побережье
Enter name of the promotion: SBER
Enter the number of shares to buy: 100000000000000000
Enter 1, 2, 3 or 4: 3
25000000000000004500   <---- мама, я богат!
  • В __capital_calcul есть side effect, а они очень опасны! Будьте осторожны, а то вдруг при выводе портфолио на экран оно будет само увеличиваться каждый раз? Стоп, что?..

Enter 1, 2, 3 or 4: 3  <-- покажи стоимость портфолио
1500
Enter 1, 2, 3 or 4: 3
2500
Enter 1, 2, 3 or 4: 3
3500
  • __action_shares принимает параметр incr=True/False, типа "купить или продать", но код один и тот же, поэтому при продаже спрашивается, что вы хотите купить:

Enter 1, 2, 3 or 4: 1  <--- продать!
You have  SBER shares of 100000000000000004 
Enter name of the promotion: LKOH
Enter the number of shares to buy: 9999  <--- что хотите купить?
  • Да и вообще продавать можно не то, что покупал, какая нафиг разница! Все эти акции-шмакции только всё усложняют.

Я уже приноровился к местным правилам, поэтому вот моя рабочая стратегия, которая приведёт к успеху.

Сначала у нас ничего нет, ничего продать не можем:

> python main.py
 1 - Sell 
 2 - Buy 
 3 - Show the value of the financial portfolio  4 - exit
Enter 1, 2, 3 or 4: 3
0
Enter 1, 2, 3 or 4: 3
0
Enter 1, 2, 3 or 4: 1
You don't have any shares

Покупаем сбер на всю котлету:

Enter 1, 2, 3 or 4: 2
-----------> You have  LKOH shares of 0 
-----------> You have  SBER shares of 0 
You can to buy :
-----------> Company: LKOH, coast: 5896
-----------> Company: SBER, coast: 250
Enter name of the promotion: SBER
Enter the number of shares to buy: 100

Потом проверяем баланс пару раз, чтобы стало побольше:

Enter 1, 2, 3 or 4: 3
25000
Enter 1, 2, 3 or 4: 3
50000

У меня, конечно, есть только 100 акций Сбера, но Лукойл мне так не нравится, что продам его, чтоб было прям отрицательное число акций:

Enter 1, 2, 3 or 4: 1
You have  SBER shares of 100 
Enter name of the promotion: LKOH
Enter the number of shares to buy: 100
Enter 1, 2, 3 or 4: 3
-514600

Блин, теперь баланс отрицательный! Попробую "отыграться" проверенной схемой - множественной проверкой баланса:

Enter 1, 2, 3 or 4: 3
-514600
Enter 1, 2, 3 or 4: 3
-1079200
Enter 1, 2, 3 or 4: 3
-1643800
Enter 1, 2, 3 or 4: 3
-2208400

Чёрт, теперь баланс уменьшается! У меня 100 Сбера, но попробую продать миллион:

Enter 1, 2, 3 or 4: 1
Enter name of the promotion: SBER
Enter the number of shares to buy: 1000000  <--- это на самом деле продажа
Enter 1, 2, 3 or 4: 3
-252773000

Ладно, кажется, машина меня переиграла. Я должен 252 миллиона. Не буду усугублять.

И заметьте, насколько я плохой трейдер: все эти миллионы я просадил за один день! Хорошо, что в этом симуляторе только один день и есть (то есть цены вообще не меняются) - иначе всё было бы ещё хуже...

И чо

Все, кто участвовал в коде по питону - теперь вы знаете, как надо было писать, чтобы победить.

Рецензия от погромиста:

  • Это не плохой код, но и не красивый

  • Этот код не решает задачу (где симуляция изменения цен?)

  • Этот код работает с критическими ошибками

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

Но у меня есть претензия к Сберу. Вы везде рекламируете свой конкурс, мы шлём вам свой код, вы втихую награждаете каких-то людей, и больше ни слова про этот конкурс. Никаких результатов, кроме имени, фамилии и города. Я должен сам искать этих участников, чтобы узнать результат.

Давай, Сбер, скажи, например, что это я ошибся, и это другой Иван Звонарёв из другого Ижевска выложил решение с конкурса и не отрицал, что он победитель. Или что ты ошибся только с одним участником, а у других участников всё как надо. А код решений вы не успели выложить. А Артём Фатхуллин всегда только читает, но ничего не отвечает, такой он человек. Скажи всё, что ты должен сказать в таких случаях - мы все всё понимаем.

Да, я не открыл Америку. Просто в очередной раз убедился.


Подписывайтесь, котятки: Блог погромиста

Другие мои статьи тут: pogrom.dev

Комментарии (83)


  1. VladimirFarshatov
    12.10.2023 06:34
    +78

    "Это Сбер, детка!" (с) погромистская мудрость. Поэтому даже не стал смотреть что там за "конкурс".. ;)


    1. MasterMentor
      12.10.2023 06:34

      Да там по "зелёным коронам" на головах победителей и по скринам статей с объявлением результатов конкурса вроде уже всё ясно. Такой небольшой и небезвкусный стебочек. :)

      (с) Сбер
      (с) Сбер

      А кто не согласен, тому - "гембу" сделаем. (сберовские оценят :) )

      PS По ходу, интересно что это за "больше 1000 талантливых программистов" которые "прислали" (где бы это на списочек посмотреть), и сколько там "некоторых" которых "даже" "пригласили к себе работать". :)


      1. baldr
        12.10.2023 06:34
        +11

        "Ну ок, хотят проверить знание каких-то базовых вещей" (с) 4-)


  1. MexanicD
    12.10.2023 06:34
    +5

    Очень интересная статья, с утра поднимает настроение.


  1. denis-19
    12.10.2023 06:34
    +23

    Так и сайт http://beautifulcode.sber.ru/ уже не сайт(


    Итоги, кстати, Сбер тут выкладывал.
    https://habr.com/ru/companies/sberbank/articles/762796/


    По поводу публикации кода победителей в открытом виде Хабр также запрашивал Сбер, но там не ответили, хотя обещали спросить у ответственных за этот проект.


  1. onuphrienko
    12.10.2023 06:34
    +8

    @kesnА можешь поделиться кодом Алины Жук, очень интересно посмотреть что там внутри, спасибо!


    1. Breathe_the_pressure
      12.10.2023 06:34
      +18

      Баги :)


      1. ssj100
        12.10.2023 06:34
        +5

        Если кто-то будет переводить через Гугл транслейт, то когда поймет что Aleona Bug / Beatle - это человек

        Cам долго не мог понять в документации Цель-С и Быстрый пока не соотнес с кодом Objective-C и Swift


        1. Breathe_the_pressure
          12.10.2023 06:34
          +22

          У нас в документе к АТС был термин - Тастатура. Мы думали это специальная клавиатура для программирования АТС и заказали у немцев за конские бабки. Пришла обычная клавиатура....

          Спасибо переводчику б. за потраченные бабки, Ди Тастатур это просто клавиатура по немецки.


          1. SuhoffGV
            12.10.2023 06:34
            +6

            Как тут не вспомнить "Паладин и газебо"


          1. MaFrance351
            12.10.2023 06:34
            +3

            Кстати, "тастатура" — как раз таки термин телефонистов, правда, чаще под этим словом подразумевается не компьютерная клава, а кнопочный номеронабиратель.


          1. ssj100
            12.10.2023 06:34

            Тастатур это просто клавиатура по немецки

            И не только по немецки, но да разнотонный перевод не позволяет переключиться
            Скачал как-то фильм Эпоха и Эпоха 2, так в одном главный объект перевели как “Тор” а во продолжении как “Овен” и так удовольствие от фильма не получил так как это название постоянно мелькало, а переключиться было сложно так как привык


    1. kesn Автор
      12.10.2023 06:34

      Во-первых, она Арина. Во-вторых, в отличие от Ивана, она свой код в публичный доступ не выкладывала, поэтому вот так вот лихо им делиться без её согласия я не могу ¯\_(ツ)_/¯ Если вы сгораете от любопытства, то можете написать ей сами - легко ищется во вконтакте.


  1. igorzakhar
    12.10.2023 06:34
    +6

    Документация python не рекомендует использовать встроенную функцию exit()(как и quit()), кроме как в REPL, т.к. при некоторых условиях эта функция может быть недоступна.

    "They are useful for the interactive interpreter shell and should not be used in programs."
    Ссылка на официальную документацию python.


    1. funca
      12.10.2023 06:34
      +1

      На всякий случай, есть одноименная sys.exit(), которой эти ограничения не касаются. Автор благоразумно пропустил импорты в тексте.


      1. igorzakhar
        12.10.2023 06:34

        Возможно, не спорю.


  1. sshmakov
    12.10.2023 06:34
    +12

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


    Ну, и корпоративные игры — они такие корпоративные.


    1. bel1k0v
      12.10.2023 06:34
      +1

      В любом случае за 2 часа макака-кодинга не выигрывается конкурс, тем более без подготовки. Можно выигрывать, но нужно участвовать - 2 часа это не участие. GodObject это не красивый код. Сбер конечно сбер, но кто платит, тот музыку и заказывает.

      На статью больше времени потрачено, чем на код.


      1. vvbob
        12.10.2023 06:34
        +6

        Красота - это слишком размытое понятие. Если задача примитивная, то и богообъект может ее красиво решать. Как по мне - размазывать кашу по роже код по файлам, там где задачка решается в одном файле на пару сотен строк - это не красиво вообще.


        1. bel1k0v
          12.10.2023 06:34
          -1

          Когда в ООП пытаешься (как будто это так легко), нужно декомпозировать.

          Не спорю насчёт скрипта в одном файле в функциональном стиле, но даже он рано или поздно разрастается.


          1. vvbob
            12.10.2023 06:34
            +8

            Не всегда в этом есть какой-то смысл, бывает довольно много примеров, когда код вообще никто не дорабатывает после его создания и закладываться в нем на расширяемость - это бессмысленная трата времени, и выглядит как по мне некрасиво вообще (ну да, и это субъективно). Обычно такой код ,если его все-же придется расширять, проще и лучше переписать, чем заранее в него закладывать расширяемость.


            1. reatfly
              12.10.2023 06:34
              +1

              Слова не мальчика, но мужа!


            1. bel1k0v
              12.10.2023 06:34
              -1

              бывает довольно много примеров

              богообъект может ее красиво решать

              Лирика какая-то - никакой конкретики. Ну да ладно. Клиент есть, который лет 10 на GodObjectах живёт, и нормально ему.


        1. acsent1
          12.10.2023 06:34
          +3

          Корпоративное красиво - это а ля Fizz Buzz Enterprise Edition


  1. mamka2003
    12.10.2023 06:34
    +51

    Задача - провести активность и отчитаться перед начальством. Активность проведена? - Да. Перед начальством отчитались? - Да.
    Не понял предъявы автора, он что, никогда не служил или не работал в Сбере?


    1. eton65
      12.10.2023 06:34
      +12

      Неужели Sber все равно, что он выглядит как параша отстойное место?


      1. AlexGluck
        12.10.2023 06:34
        +5

        Не бывает плохого пиара.


      1. sartorius9
        12.10.2023 06:34
        +4

        А разве возможно что то сделать, чтобы он перестал быть отстоем?


        1. eton65
          12.10.2023 06:34
          +1

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


          (ответил и RomeoGolfу)


          1. sartorius9
            12.10.2023 06:34
            +1

            Очевидно - не нужно им это.

            Но, при всём при том, множество людей продолжают оставаться их клиентами. Как и, скорее всего, все здесь присутствующие (а иначе зачем бы мы ещё в эту тему зашли?).

            Значит, несмотря на отстойность их сервиса, он находит свои клиентов.


            1. eton65
              12.10.2023 06:34

              Исторически просто сложилось, что у них отделение на каждом углу. Да и большинство услуг у них на уровне. Для большинства этого достаточно. И поэтому тем более непонятно, почему в целом нормальном банке такие косяки, которые никто не хочет устранять.


              1. sartorius9
                12.10.2023 06:34
                +1

                Всё верно. И Вы говорите совершенно очевидные вещи.

                Но верно так же и то, что мы не слишком уважаем себя как пользователи их услуг.

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

                Хотя вот с телефоном я все таки ушел со своим номером от красных к жёлтым. Как показал опыт - осиные, зеленые и голубые - те самые "лишь чуть меньше чем другие".

                Полгода - полет нормальный. Так что надо вырабатывать в себе хорошую привычку - хлопать наглых поставщиков дверью по морде.


                1. eton65
                  12.10.2023 06:34

                  Ну вот да, люди себя не уважают и боссы тоже думают "зачем напрягаться, если и так все нормально?". Значит такой уровень культуры в целом.


                  1. sartorius9
                    12.10.2023 06:34

                    А почитайте высказывания Германа Грифовича. Многое сразу же станет гораздо нагляднее.


      1. RomeoGolf
        12.10.2023 06:34
        +1

        В далекие школьные годы слышал задорновский монолог телефона в исполнении Петросяна, и навсегда запомнил оттуда такие строки:


        В общем, теперь, когда дни мои сочтены, честно скажу: лучше бы я тогда с конвейера не по профилю работать пошел. В сферу обслуживания. Телефоном-автоматом. Может, и пользы тоже не много принес, зато всегда бы при деньгах был! Конечно, обругали бы меня пару раз, может быть, даже по физиономии съездили… Но денег от этого не уменьшилось бы…

        Но я их просто запомнил, а кто-то принимает в качестве руководства к действию...


  1. WondeRu
    12.10.2023 06:34
    +25

    1) отдельные поля класса под каждую котировку компаний?! Надеюсь, это специально для холивара вставлено)

    2) юморески в коде бесят, избегайте, даже на тестовых заданиях. В статье можно и нужно, а в коде - нет.


    1. funca
      12.10.2023 06:34
      +5

      юморески в коде бесят

      Кодинг из искусства превратили в энтерпрайзное болото. Когда-то люди программировали just for fun и исходники было читать интересно.


      1. markmariner
        12.10.2023 06:34
        -2

        Так пишите как хотите, кто вам запрещает?


    1. kesn Автор
      12.10.2023 06:34

      1. Отдельные поля класса - этот сниппет кода прилагался к задаче, идея организаторов. Я бы до такого не додумался, наоборот, выбесило знатно.

      2. Этот код не для продакшна, его 1 (или 0) раз посмотрят и выбросят навсегда. В этом контексте считаю шутейки уместными.


  1. LyuMih
    12.10.2023 06:34
    +3

    Сама идея конкурса по нравилась. Интересно посмотреть на конкурс для JavaScript/TypeScript - вот где будет веселье.
    А то, что нельзя ознакомиться с кодом победителей - это очень грустно.
    Получается - "Я сделалъ красиво" и всё


    1. Neusser
      12.10.2023 06:34
      +16

      Почему "по нравилась", но "посмотреть", "получается" и "победитель"? :-Р


      1. exTvr
        12.10.2023 06:34
        +14

        по тому-что/s


    1. ris58h
      12.10.2023 06:34
      +2

      Интересно посмотреть на конкурс для JavaScript/TypeScript - вот где будет веселье.

      https://habr.com/ru/articles/723752/


  1. fox_12
    12.10.2023 06:34
    +8

    https://github.com/Royal00Blood/Sber_task/ - тут еще и мусор в гите - кеш python, файлы окружения... Неужели настолько все плохо было у других участников?


  1. Popadanec
    12.10.2023 06:34

    У меня одного(что в старой, что в новой версии Хабра), вместо публикации пустое поле?
    При обновлении страницы, контент на долю секунды показывается и потом исчезает.
    Это какой то косяк сайта(другие страницы открываются нормально) или новая методика скрытия публикаций?


    1. Tanriol
      12.10.2023 06:34
      +1

      Я бы заподозрил косяк в правилах блокировщика рекламы.


      1. Popadanec
        12.10.2023 06:34

        Вот только он годы назад отключен. Да и проблемы только с этой конкретной страницей.
        P.S. Нашёл в чём дело. Хабросанитайзер, чистящий выдачу, от неинтересных мне авторов, зачем то так сделал. Среагировал на автора в чёрном списке


  1. Dorat79
    12.10.2023 06:34
    +9

    классик сбер, даже и добавить нечего


  1. koldyr
    12.10.2023 06:34
    +12

    Не удержался.

    Два менеджера по персоналу опытный и стажёр сидят в офисе и обсуждают дела. anekdotov.net, Молодой достает огромную пачку резюме, штук 300: "Мы должны просмотреть их все, чтобы подобрать кандидатов на эту вакансию". Опытный хладнокровно берет у него пачку, делит ее пополам, одну часть на стол, вторую в шреддер. У молодого глаза по пятаку: "А как же претенденты?!" Опытный невозмутимо: "А зачем нам неудачники?"


  1. MasterMentor
    12.10.2023 06:34
    +17

    Президент конкурса красоты Алла Маркина призналась, что она продает места на "Миссис Россия". Более того, женщина заверила, что будет делать это "всю свою жизнь".
    2012 (с) https://www.kp.ru/daily/25832/2807082/
    (эпиграф)

    Ну, вот, ещё один разочаровавшийся в конкурсе. Да к тому же в "конкурсе красоты". Будтье оптимистичней, и не относитесь так серьёзно и к "конкурсам", и к их организаторам.

    Кейнсианский конкурс красоты

    Кейнсианский конкурс красоты — концепция, разработанная Джоном Мейнардом Кейнсом и изложенная в главе 12 его работы «Общая теория занятости, процента и денег» (1936) для объяснения колебаний цен на фондовых рынках. В ней описывается конкурс красоты, в котором награждаются не самые популярные участники конкурса, а те судьи, которые наиболее точно смогли угадать, кто из участников конкурса окажется наиболее популярным.

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

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

    «Речь идёт не о том, чтобы выбрать самое красивое лицо по искреннему убеждению выбирающего, и даже не о том, чтобы угадать лицо, действительно удовлетворяющее среднему вкусу. Тут мы достигаем третьей степени, когда наши способности направлены на то, чтобы предугадать, каково будет среднее мнение относительно того, каково будет среднее мнение. И имеются, как я полагаю, такие люди, которые достигают четвёртой, пятой и ещё более высоких степеней».

    https://ru.wikipedia.org/wiki/Кейнсианский_конкурс_красоты


  1. xsunson
    12.10.2023 06:34
    -19

    Считаю, что Сбер правильно сделал в вопросе публикации результатов. Вне зависимости от того, кто и с каким кодом бы победил - на каждого было бы по статье с разбором, что не так. Что конкретно хотелось бы прояснить в следующий раз - что делать если остались вопросы по заданию, объективные критерии оценки (за красоту, так и быть, пусть отвечает жюри), обратная связь. Ее просто нет никакой, отсюда все претензии и вопрос "А где хотя бы код победителей посмотреть". Саму инициативу, я лично, поддерживаю.


    1. myswordishatred
      12.10.2023 06:34
      +21

      Вне зависимости от того, кто и с каким кодом бы победил - на каждого было бы по статье с разбором, что не так.

      А это разве плохо? Обсудили бы код, каждый бы что-то для себя почерпнул нового. А если бы члены жюри написали разбор "Я считаю, что этот код красивый потому-то и посему-то" вообще отлично было бы. Понятно, что для всех работ это сделать нельзя, но для победителей-то можно?


      1. xsunson
        12.10.2023 06:34
        +2

        Я тоже хочу получить обратную связь, чтобы извлечь для себя пользу. И, в теории - да, звучит прекрасно. Но на практике мы имеем, эту статью, в которой разочарованный участник вычислил по IP нашел победителя, его код, рвет и мечет. Только Ивану Звонарёву нужен такой цифровой след, который пишет эта статья? Чтобы его потом на собеседованиях носом в нее тыкали?


        1. myswordishatred
          12.10.2023 06:34
          +2

          Я надеюсь, что @kesnспросил человека перед публикацией, согласен ли он. Впрочем, если код лежит на гитхабе Ивана, то он вроде бы по умолчанию не против им поделиться.

          Ну и во что тут тыкать не вполне ясно. Человек решил задачу, какое-то признание получил. Может быть решения с чьей-то точки зрения не вполне удачные, да, но он ведь объяснить может, почему сделал так, а не иначе. Или научится чему-нибудь новому и переписать код. Это же только в плюс сыграет, мол, смотрите, я адекватный и обучаемый.


        1. ZetaTetra
          12.10.2023 06:34
          +9

          Вообще-то, ради этого и создаются пулбичные конкурсы чтобы оставить о себе цифровой след. Смысла-то иначе делать конкурс, а потом писать в отчёте победителей:

          1. место: Анонми 1

          2. место: Аноним 2

          3. место: Аноним 3


    1. Wolf4D
      12.10.2023 06:34
      +15

      Вы так говорите, как будто "на каждого по статье с разбором" - это что-то плохое. Наоборот, это очень полезно для всех - посмотреть и на частые ошибки и на то, какие они - образцы хорошего кода.


    1. Areso
      12.10.2023 06:34
      +2

      на геймджемах видно все submit'ы, и на ряде геймджемов виден и код.

      т.е. как минимум можно оценить с точки играбельности, как максимум - научиться чему-то


  1. vvbob
    12.10.2023 06:34
    +10

    Вот вам фирменное:

    Вот где карту оформляли куда код кидали, туда и обращайтесь!

    ;)))


  1. Finesse
    12.10.2023 06:34
    +4

    Я тоже разочаровался (не потому, что ничего не получил). Хотелось бы посмотреть на код победителей.

    Я участвовал в направлении frontend (JavaScript). В ТЗ было весьма расплывчатое ограничение на входные данные, и требование подразумевало неоднозначное поведение кода в эдж-кейсах, а на сайте было написано примерно «код должен работать без багов». Поэтому передо мной встала дилема: сделать лаконичный код, не учитывающий эдж-кейсы, или нормальный код. Выбрал «нормальный», потому что так я бы поступил в реальном проекте при фиксированном ТЗ, постарался сделать его максимально читаемым, и написал, как бы я составил ТЗ.

    Моё решение
    /**
     * Индексирует массив объектов по указанному атрибуту.
     *
     * Если у объекта из массива нет указанного атрибута, он игнорируется.
     * 
     * @param {object[]} objects Массив объектов, который надо проиндексировать
     * @param {string} objectKey Название атрибута объектов, по которому они индексируются
     * @returns {object} Хэш-таблица, в которой ключи - значения указанного свойства объекта, а значения - соответствующие
     *  объекты. Также объект имеет атрибут _{objectKey}s, который содержит список всех ключей приведённых к строке (кроме
     *  самого себя).
     */
    function mapArrayToHashByKey(objects, objectKey) {
      const hashMap = {};
      const indexKey = `_${objectKey}s`;
    
      // Некоторые значения нельзя использовать в качестве ключей хэш-таблицы:
      // '__proto__' является ссылкой на прототип; если записать в него объект, то у хэш-таблицы появятся лишние ключи;
      // indexKey зарезервирован для списка ключей.
      const isForbiddenMapKey = key => key === '__proto__' || key === indexKey;
    
      if (isIterable(objects)) {
        for (const object of objects) {
          if (!hasKey(object, objectKey)) {
            continue;
          }
    
          const mapKey = String(object[objectKey]);
    
          if (isForbiddenMapKey(mapKey)) {
            continue;
          }
    
          hashMap[mapKey] = object;
        }
      }
      
      hashMap[indexKey] = Object.keys(hashMap);
      return hashMap;
    }
    
    /**
     * Проверяет, можно ли использовать оператор for..of с указанным значением.
     * 
     * @param {any} value Значение, которое нужно проверить
     * @returns {boolean}
     */
    function isIterable(value) {
      return hasKey(value, Symbol.iterator)
    }
    
    /**
     * Проверяет, если ли в объекте указанный во всей цепочке прототипов.
     * 
     * @param {any} object Объект
     * @param {string} key Название атрибута
     * @returns {boolean}
     */
    function hasKey(object, key) {
      return typeof object === 'object' && object !== null && key in object;
    }
    
    
    
    
    
    /*
     * Выше моё решение задачи. Ниже мой комментарий о задании.
     *
     * Особенности реализации:
     *  - Функция реализована в точности по указанному формату и примерам;
     *  - Я не использовал сторонние библиотеки и хитрые конструкции из функционального программирования, потому что они
     *    сильно усложняют понимание непосвящённому человеку, несмотря на локаничность;
     *  - Я переименовал аргументы, чтобы убрать двусмысленность слова key;
     *  - В качестве стиля кода выбран популярный Airbnb с длиной строки 120; следование привычному стилю упрощает чтение;
     *  - JSDoc используется, чтобы описать формат функции для IDE. Я бы использовал TypeScript, если можно было;
     *  - Сложность функции: O(n) с двумя проходами;
     *  - Задание понято так, что функция не должна выкидывать ошибки, поэтому она сделана всеядной;
     *  - Маленькие функции isIterable и hasKey сделаны в первую очередь для того, чтобы пояснить неочувидные куски кода
     *  - без комментариев. Они вынесены наружу, потому что универсальны.
     *
     * Предложенный в задании формат функции является не самым удачным для решения проблемы. В задании не указаны
     * эдж-кейсы, в которых формат может привести к проблемам:
     *  - Несколько объектов из массива имеют одинаковое значение атрибута. Моя реализация берёт последний из них, то есть
     *    часть данных может потеряться;
     *  - Значение атрибута совпадает с _{objectKey}s. Моя реализация игнорирует такие объекты, чтобы массив _{objectKey}s
     *    содержал только ключи исходных объектов;
     *  - Значение атрибута имеет значение '__proto__'. Это ссылка на прототип объекта. Если записать в это свойство
     *    объект, то у хэш-таблицы появятся лишние атрибуты, поэтому моя реализация игнорирует такие объекты;
     *  - Элемент массива не является объектом. Моя реализация игнорирует их;
     *  - Значение атрибута является объектом (в гарантиях написано про вложенные объекты). Моя реализация приводит его к
     *    строке. Это имеет смысл, когда у вложенного объекта реализован метов toString.
     *
     * Из-за всеядности и обработки всех эдж-кейсов функция получилась довольно объёмной. Я считаю, что правильностью
     * работы кода нельзя жертвовать в пользу локаничности.
     *
     * Чтобы решить указанные выше проблемы, я бы выбрал для функции такой формат:
     */
    
    /**
     * (тоже самое)
     *
     * @param {object[]} objects Массив объектов, который надо проиндексировать
     * @param {string} objectKey Название атрибута объектов, по которому они индексируются
     * @returns {Map} Хэш-таблица, в которой ключи - значения указанного свойства объекта, а значения - массивы
     *  соответствующих объектов
     */
    function mapArrayToHashByKey(objects, objectKey) {
      const hashMap = new Map();
    
      if (isIterable(objects)) {
        for (const object of objects) {
          if (!hasKey(object, objectKey)) {
            continue;
          }
    
          const mapKey = object[objectKey];
          const sameKeyObjects = hashMap.get(mapKey) || [];
    
          sameKeyObjects.push(object);
          hashMap.set(mapKey, sameKeyObjects);
        }
      }
    
      return hashMap;
    }
    
    const data = [
      { id: 1, age: 25, name: 'John' },
      { id: 2, age: 30, name: 'Jane' },
      { id: 3, age: 25, name: 'Jack' },
    ];
    
    console.log(mapArrayToHashByKey(data, 'age'));
    /*
    Map(
      25: [
        { id: 1, age: 25, name: 'John' },
        { id: 3, age: 25, name: 'Jack' },
      ],
      30: [
        { id: 2, age: 30, name: 'Jane '}
      ],
    )
    */
    
    /*
     * Использование Map вместо обычного объекта позволяет использовать ключи любого типа (не только строки) и
     * не беспокоится о подмене прототипа. Нет необходимости в отдельном атрибуте для списка ключей, потому что получить
     * его из Map или обычного объекта не составляет труда:
     *
     * const keys = Array.from(map.keys())
     * const keys = Object.keys(object)
     *
     * Правда эта функция не подходит в случае, когда нужно подготовать объект для сериализации в JSON. В этом случае я бы
     * использовал обычный объект, так же без списка ключей:
     */
    
    /**
     * (тоже самое)
     *
     * @param {object[]} objects Массив объектов, который надо проиндексировать
     * @param {string} objectKey Название атрибута объектов, по которому они индексируются
     * @returns {{hash}} Хэш-таблица, в которой ключи - значения указанного свойства объекта, а значения - массивы
     *  соответствующих объектов
     */
    function mapArrayToHashByKey(objects, objectKey) {
      const hashMap = {};
    
      // '__proto__' является ссылкой на прототип; если записать в него объект, то у хэш-таблицы появятся лишние ключи
      const forbiddenMapKey = '__proto__';
    
      if (isIterable(objects)) {
        for (const object of objects) {
          if (!hasKey(object, objectKey)) {
            continue;
          }
    
          const mapKey = String(object[objectKey]);
    
          if (mapKey === forbiddenMapKey) {
            continue;
          }
    
          hashMap[mapKey] ||= [];
          hashMap[mapKey].push(object);
        }
      }
    
      return hashMap;
    }
    
    console.log(mapArrayToHashByKey(data, 'age'));
    /*
    {
      '25': [
        { id: 1, age: 25, name: 'John' },
        { id: 3, age: 25, name: 'Jack' },
      ],
      '30': [
        { id: 2, age: 30, name: 'Jane' },
      ],
    }
    */
    


    1. acsent1
      12.10.2023 06:34

      Задача состояла в том чтобы написать аналог Object.groupBy() ?


      1. Finesse
        12.10.2023 06:34

        Типо того. Нужно написать функцию, которая принимает массив объектов и название свойства, по которому надо индексировать, а в возвращаемом объекте должно быть отдельное свойство, которое содержим массив всех значений ключа. Самый очевидный нюанс заключается в том, что в отличие от Object.groupBy, поля возвращаемого объекта содержат не массивы, а единичные значения.


  1. Algrinn
    12.10.2023 06:34
    -24

    Не фанат Сбертеха, никаких тёплых чувств к нему нет. Но статья больше похожа на попытку поплести интриги.


  1. Securityhigh
    12.10.2023 06:34
    +2

    Если бы это была Альфа, то они бы заставили писать симулятор «Партнера АО Альфа Банка» :D


  1. Securityhigh
    12.10.2023 06:34
    +4

    Иронично на самом деле, что у сбера такой конкурс, именно с симулятором биржи, поскольку само по себе их направление инвестиций (Сбер Инвест) даже ужасным назвать язык не повернется, скажем так, пробитое дно рынка.


    1. Hlad
      12.10.2023 06:34

      Именно для инвестиций (купил и забыл на полгода/год/пять лет) - оно вполне себе. А за "трейдингом на кончиках пальцев" лучше идти к кому-то другому...


  1. mikronavt
    12.10.2023 06:34
    +5

    Может просто таска на то чтобы опубликовать код зависла где-то на стадии безопасников, через годик опубликуют.


  1. snakers4
    12.10.2023 06:34
    +2

    Ммм, отношение к людям такое же как и к конкурсам на Хабре, который просто "теряет" сабмиты участников. И админы в личке потом признаются, что ой, ну мы просто потеряли. Конечно-конечно!


  1. Daddy_Cool
    12.10.2023 06:34
    +5

    Дважды сталкивался с подобным. У организаторов запала хватает только на начало, а дальше...
    В школьное время - участвовал в "Турнире городов" по математике, кажется прошел во второй тур... решаю задачки... а где когда результаты? Потом как-то в личных беседах выясняется, что где-то в каком-то институте будет собрание где наградят победителей. Приезжаем, вначале собрание - все признаются в любви к институту, потом говорят - а, ну да, вот у нас было какое-то мероприятие, вот фамилии ребят. Всё.

    Обнаружил объявление о всероссийском (!) конкурсе студенческих работ от приличного ВУЗа. Со студенткой посылаем туда работу. Присылают письмо, что мы прошли во второй тур. И тишина. Потом появляется список, оказывается мы заняли какое-то место. И никаких уведомлений, ни приглашений, ничего.


    1. TDMNS
      12.10.2023 06:34

      Я помню, как-то принимал участие в конкурсе в одной из крупных компаний, дабы их не обидеть не буду писать какой.

      Тоже самое было, у организаторов был запал в начале а потом... А потом они сдулись. Мне кажется это золотое правило крупняков.
      До сих пор в хохот пробирает с того, как в чате победители ждали год или с пол года "наград".

      Интересно, дождались ли...


    1. NutsUnderline
      12.10.2023 06:34
      +1

      у меня подобное было много лет назад. за работу обещали зачисление без экзаменов. по факту подарили книгу с надписью и словами что "зачтется это Вам..". Хороший опыт жизни, между прочим


    1. lgorSL
      12.10.2023 06:34
      +1

      Кажется, в 2009 году я выиграл областную олимиаду по астрономии и никаких результатов не нашёл. А потом мне вдруг позвонили и сказали "через неделю приезжайте на сборы по подготовке к всероссийской олимпиаде". И примерно тогда же я узнал, что годом раньше я тоже что-то занял, но сообщить об этом мне никто не удосужился. Сам я в гугле по своим ФИО ничего не нашёл, потому что результаты публиковали очень сильно позже (эдак через месяц после проведения), когда я уже отчаялся что-либо найти.


  1. merrymaker14
    12.10.2023 06:34
    -3

    Автор по красоте всё расписал :)


  1. Ratenti
    12.10.2023 06:34

    У меня, конечно, есть только 100 акций Сбера, но Лукойл мне так не нравится, что продам его, чтоб было прям отрицательное число акций:

    Не слышал про маржинальную торговлю в шорт?

    Маржинальная торговля «в шорт» позволяет зарабатывать на продаже даже тех активов, которых нет в вашем портфеле: вы занимаете их у брокера и продаете. Если эти бумаги дешевеют, позже вы покупаете их по более низкой цене и возвращаете брокеру, а разницу между стоимостью продажи и покупки забираете себе.10 мая 2022 г.


    1. ti_zh_vrach
      12.10.2023 06:34

      Так их же сначала занимают, потом продают. А не наоборот. Разве не так?


      1. MaxxONE
        12.10.2023 06:34

        На практике это выглядит как просто продажа в минус, никаких дополнительных телодвижений для занятия не требуется


        1. ti_zh_vrach
          12.10.2023 06:34
          +1

          А конкурс на торговлю акциями или написание рабочего красивого кода?


  1. Hivemaster
    12.10.2023 06:34
    +4

    Напихали полную сберпанамку, а виновники торжества отмалчиваются.


  1. Oraclist
    12.10.2023 06:34
    +1

    Банк.

    В отдел кадров заходит начальник трейдинга.

    – Где отклики на резюме на вакансию трейдера?

    Кадровик показывает на стопку заявлений.

    Шеф трейдинга достаёт из середины стопки одно заявление.

    – Этого берём.

    Кадровик.

    – А остальных?

    – Остальных в корзину. Неудачники нам не нужны.

    # sudo systemctl disable sarcasm


  1. NutsUnderline
    12.10.2023 06:34
    +4

    К перечню "Ложь, наглая ложь, статистика ..." я бы добавил "конкурс красоты"

    Участвуйте в демосцене: код вообще показывать не надо, лицо и паспорт показывать не надо, призы по почте, таблицы подсчетов голосов доступны.


    1. dlinyj
      12.10.2023 06:34
      +1

      Принимал так участие, мою демку потеряли


      1. NutsUnderline
        12.10.2023 06:34
        +1

        А ну это традиция, как и присылка работ задолго после дедлайна


  1. Santiago_de_Cuba
    12.10.2023 06:34
    +1

    У меня подозрение, что код победителя сгенерирован при помощи ChatGPT или подобной нейросети. Докстринги содержат довольно нетривиальные формулировки на английском, а некоторые текстовые промпты сформулированы весьма неграмотно ("Enter name of the promotion" - promotion как "акция", хотя должно быть "share"; "You have mistake"; "coast" - берег вместо "cost", да, опечатка, но при этом какие идеальные докстринги). Понятно, что программа была доработана руками, но это же конкурс красоты кода?!
    PS: тоже хотел бы взглянуть на код победителя по ML. Я участвовал, и тоже разочарован, что не получил никакого фидбэка. Охотно верю, что из 1000 участников у пары сотен код был лучше моего, но хотелось бы увидеть хотя бы пару из этих лучших решений, чтобы чему-то научиться.


    1. funca
      12.10.2023 06:34
      +1

      Докстринги выглядят как результат автоперевода с русского на английский. В принципе в этом нет ни чего такого, автор же не нейтив спикер.