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

Пойдем туда вместе.

Недавно меня спросили о природе & и | у панд. Тема, которую несколько сложно найти в Google, потому что она не совсем побитовая… Надеюсь, эта статья хоть немного поможет.

В нем используются некоторые примеры на python и pandas (поскольку это было взято из моего первоначального ответа в чате по науке о данных), но он применим для многих языков и реализаций.

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

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

Например, переменные в Python — такие вещи, как строки, целые числа, списки и т. д. — являются абстракциями. Процессор компьютера не знает об этих вещах, он знает только о 0 и 1 (битах).

Если взять строки, то это просто созданные людьми соглашения о том, как мы располагаем 0 и 1 для их представления. Возможно, вы слышали об этих соглашениях (например, UTF-8 или более ранней версии ISO-8859–1). Основная цель Python 3 заключалась в том, чтобы раз и навсегда решить некоторые проблемы с соглашениями (путем реализации базового набора символов с именем unicode по умолчанию). Это то, что труднее сделать, чем можно подумать, например. Версия PHP 6 не справилась с этой задачей и так и не была выпущена.

Всякий раз, когда вы видите странные символы в тексте, это фактически смесь этих соглашений (которые называются кодировками). Это биты в неправильном порядке и абстракция просачивается.

Короче говоря, гораздо ниже, на уровне процессора все на самом деле биты.

На этом уровне работают побитовые операторы. Например. число 201 равно 1100 1001 в битах, а 15 равно 0000 1111 (полный пример доступен здесь). Если вы сравните их побитово, вы просто пройдетесь по этим битам и сравните каждый бит из первой последовательности со второй.

1100 1001 начинается с 1, а 0000 1111 начинается с 0, побитовое и (&) оценивается как 0. Оба заканчиваются на 1, следовательно, это будет оцениваться как 1 (аналогично тому, как True и True равны True, а True и False равны False).

Если вы сделаете это для всей выборки, вы получите 0000 1001.

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

Перегрузка оператора

Я уже говорил, что вы можете никогда не использовать побитовое сравнение, в то время как вы можете думать, что уже наткнулись на них, например. в сравнениях pandas boolean-vector:

2    2
4    4
dtype: int64 0    0
1    1
5    5
dtype: int64

Но шансы таковы: у вас нет. Вы коснулись поверхности внутренней динамики языка Python.

Операторы — это такие вещи, как * / + - (основные математические операции), сравнения, такие как < <= > >=, и побитовые операторы (& для побитовых и и | для побитовых или). ) сами являются операторами. Самое интересное в python заключается в том, что эти операторы не ограничиваются встроенными вещами, такими как целые числа и строки, каждый, кто пишет класс, может реализовать его самостоятельно — или предпочитает не делать этого.

Например. создатели python, очевидно, реализовали умножение (*) для целых чисел, но они не реализовали побитовый оператор "и" (&) для списков. Это потому, что было бы неинтуитивно то, что он делает - как побитовое сравнение списков? Сравните их значения или битовые последовательности их значения? Хм, кто знает. Когда вы сравниваете список побитово с другим списком, python выдает ошибку, утверждая, что никто не реализовал то, что нужно делать:

unsupported operand type(s) for &: 'list' and 'list'

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

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

Затем вы можете сравнить руки, сделав это:

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

Вот что такое разработка программного обеспечения. Элегантность важна.

Вы также можете перегрузить побитовый оператор, определив методы __and__() и __or__() в классе.

Django ORM (уровень абстракции для SQL и баз данных) использует последний для реализации ИЛИ в SQL. Это не единственный способ сделать это (например, в SQLAlchemy есть метод or_ для этого, но он также выполняет тяжелую перегрузку операторов).

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

Приведу пример, когда это решение было принято неправильно (по крайней мере, на мой взгляд). Строки Python могут делать такие вещи, как:

>>> 'This book costs %i $.' % 112
'This book costs 112 $.'

Это называется интерполяцией строк — чтобы не путать строки, можно написать строку с заполнителями и заменить их значениями в конце. Однако: деление строки на целое число приводит к замене заполнителя — интуитивно понятно?

Я бы сказал нет. Не используйте его. Вместо этого используйте .format(), он также более мощный и очевидный способ сделать это.

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

Что подводит нас к вопросу…

Является ли перегрузка побитового оператора в реализации логического вектора pandas хорошим или плохим дизайном?

Побитовый оператор Pandas для векторов предназначен для логических векторов (упрощенно: списки, содержащие только True или False).

Побитовый оператор в pandas реализован следующим образом: он сравнивает каждую запись первой последовательности списка с записями последней, почти так же, как побитовый оператор проходит через каждую запись в битовой последовательности. А True и False можно рассматривать как биты (1 и 0).

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

Есть и несколько недостатков: это увеличивает кривую обучения для новичков, которые путаются в этих продвинутых функциях языка.

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

Создание непревзойденного бота из камня, ножниц и бумаги

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

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

Хорошо, давайте быстро проведем несколько тестов, если реализация работает:

>>> print('Is paper beating scissor? {}'.format(
    'Yes.' if Selection('paper') > Selection('scissor') else 'No.'
))
Is paper beating scissor? No.

И полный стек случайной игры:

>>> import random
>>> c1 = random.choice(Selection.OPTIONS)
>>> c2 = random.choice(Selection.OPTIONS)
>>> s1 = Selection(c1)
>>> s2 = Selection(c2)
>>> print('{} > {}:'.format(c1, c2), s1 > s2)
rock > scissor: True
>>> print('{} >= {}:'.format(c1, c2), s1 >= s2)
rock >= scissor: True
>>> print('{} == {}:'.format(c1, c2), s1 == s2)
rock == scissor: False
>>> print('{} != {}:'.format(c1, c2), s1 != s2)
rock != scissor: True
>>> print('{} < {}:'.format(c1, c2), s1 < s2)
rock < scissor: False
>>> print('{} <= {}:'.format(c1, c2), s1 <= s2)
rock <= scissor: False

Хорошо, это выглядит хорошо.

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

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

В сложных играх, таких как безлимитный холдем, вы могли бы получить математический выигрыш против худших противников, если бы вы могли играть в GTO (это потому, что ошибки в безлимитном холдеме могут быть катастрофическими, и если ваш противник делает лишь несколько, это принесет прибыль). Однако в случае с камнем, ножницами и бумагой это приводит к тому, что у вашего оппонента нет шансов на победу — и у вас тоже нет шансов на победу.

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

Однако давайте взглянем на смелое заявление о том, что стратегия GTO не может выиграть или проиграть и в конечном итоге сравняется с любой стратегией в долгосрочной перспективе. Мы сделаем это, позволив GTO сыграть миллион раз против другой стратегии, в этом случае стратегия «Всегда бери камень».

Давайте посмотрим, как GTO справляется с этим:

matplotlib сгенерирует что-то вроде этого:

Да, это галстук.

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

С другой стороны, GTO может помочь вам, если ваш противник лучше вас.

Несколько лет назад в европейском футболе был интересный момент, когда особенному, тренеру «Интер Майленда» Жозе Моуринью, удалось очень близко подобраться к GTO, может быть, настолько близко, насколько это вообще возможно в таком комплексе. игра с бесконечными переменными.

Моуринью победил безмерного фаворита ФК «Барселона» со своей командой хороших защитников, фактически научив их очень разрушительному способу игры в футбол. Они сосредоточились только на том, чтобы их вообще нельзя было использовать, что привело к тому, что у них также было мало шансов использовать противника. Итак, тактика заключалась в том, чтобы сыграть 0:0 и надеяться на серию пенальти — если бы не индивидуальная ошибка, которая определяла бы исход. Таким образом, игры против более крупных фаворитов шли со счетом 0:0, 0:1, 1:0 или около того, им повезло и они сумели выиграть лигу чемпионов.

Я надеюсь, что вы могли взять что-то из этого.