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

Индексация последовательностей

В языке Python достаточно удобно реализована индексация последовательностей. Мы легко можем обращаться к элементам последовательности от начала, используя индексы, начиная с нуля. То есть первый элемент последовательности имеет индекс 0, второй элемент последовательности имеет индекс 1 и так далее. В общем случае i-й элемент последовательности от начала имеет индекс i - 1.

Приведенный ниже код:

s = 'ПОКОЛЕНИЕ'

print(s[0])       # первый символ строки
print(s[1])       # второй символ строки

print(s[7])       # предпоследний символ строки
print(s[8])       # последний символ строки

выводит:

П
О
И
Е

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

Мы также легко можем обращаться к элементам последовательности от конца, используя отрицательные индексы, начиная с -1. То есть последний элемент (первый элемент с конца) последовательности имеет индекс -1, предпоследний элемент (второй элемент с конца) последовательности имеет индекс -2 и так далее. В общем случае i-й элемент последовательности от конца имеет индекс -i.

Приведенный ниже код:

s = 'ПОКОЛЕНИЕ'

print(s[-1])   # последний символ строки
print(s[-2])   # предпоследний символ строки
print(s[-3])   # предпредпоследний символ строки

выводит:

Е
И
Н

Симметричная индексация и оператор ~

Как мы видим, в Python индексация от начала и от конца последовательности является несимметричной. Действительно, элементу с индексом 0 от начала соответствует симметричный элемент с индексом -1 от конца, элементу с индексом 1 от начала соответствует симметричный элемент с индексом -2 от конца и так далее.

Ниже приведена таблица соответствия индексов симметричных элементов от начала и конца последовательности.

Индекс от начала

Индекс от конца

0

-1

1

-2

2

-3

3

-4

4

-5

5

-6

...

...

В общем случае элементу с индексом i от начала будет соответствовать симметричный элемент с индексом -(i + 1) от конца.

Не все знают, но в Python для целых чисел можно использовать унарный оператор побитовой инверсии ~. Данный оператор "переворачивает" биты в двоичном представлении числа, выполняя операцию побитового отрицания: 1 преобразуется в 0, а 0 — в 1.

В общем виде оператор ~ преобразует целое число n в соответствии с правилом:

\sim n = -(n + 1)

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

Приведенный ниже код:

print(~0)
print(~1)
print(~2)
print(~3)

print(~-1)
print(~-2)
print(~-3)
print(~-4)

выводит:

-1
-2
-3
-4
0
1
2
3

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

В качестве примера напишем функцию is_palindrome(), использующую симметричную индексацию с помощью оператора ~ для проверки входной строки на палиндром.

Приведенный ниже код:

def is_palindrome(s: str) -> bool:    
    for i in range(len(s) // 2):        
        if s[i] != s[~i]:            
            return False    
    return True


print(is_palindrome('abccba'))
print(is_palindrome('abcpba'))

выводит:

True
False

По сути, выражение s[i] != s[~i] эквивалентно выражениям s[i] != s[-i - 1] и s[i] != s[len(s) - i - 1], однако является более красивым и компактным вариантом записи.

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

Присоединяйтесь к нашему телеграм-каналу, будет интересно и познавательно!

❤️Happy Pythoning!?

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


  1. starik-2005
    19.06.2024 07:16
    +2

    Круто!

    В 1С'е, хоть она и в доску паскаль, не сделали обратный цикл (downto), поэтому приходится так:

    for i = -Count+1 to 0 do
      some = somearray[-i];
    enddo;


  1. Tinkz
    19.06.2024 07:16
    +9

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


    1. CrazyOpossum
      19.06.2024 07:16
      +18

      В реальном коде - никогда. Отличный способ запутать потом себя и коллег.


  1. maximw
    19.06.2024 07:16
    +14

    Добавляет возможность решать ребусы в чужом коде.


  1. wildmathing
    19.06.2024 07:16
    +1

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


  1. qss53770
    19.06.2024 07:16

    А интересно на больших циклах это как скажется на производительности? Или ни как?


    1. tguev Автор
      19.06.2024 07:16
      +2

      Не должно сказаться, оператор ~ выполняется достаточно быстро.


  1. R0bur
    19.06.2024 07:16

    Будет ли этот приём работать как на LSB, так и на MSB архитектурах?


    1. developerxyz
      19.06.2024 07:16
      +2

      Порядок байт тут ни при чём. Этот приём работает за счёт особенностей отрицательных чисел в дополнительном коде