Applications of '~' (tilde) operator in Python
Asked Answered
K

7

20

I just discovered the bitwise complement unary operation in Python via this question and have been trying to come up with an actual application for it, and if not, to determine if it's generally safe to overload the operator (by overriding the __invert__ method) for other uses. The example given in the question fails with a TypeError, and the link provided seems pretty intimidating. Here's some fiddling around to see ~ in use:

from bitstring import BitArray

x = 7

print(~x)
# -8

print(BitArray(int=x, length=4).bin)
# '0111'

print(BitArray(int=~x, length=4).bin)
# '1000'

print(~~True, ~~False)
# 1 0

for i in range(-100, 100):
    assert i + ~i == -1
    assert i ^ ~i == -1
    assert bool(i) == ~~bool(i)

Are there any examples of valid use-cases for this operator that I should be aware of? And even if there are, is it generally acceptable to override this operator for types other than int?

Katowice answered 8/7, 2016 at 16:54 Comment(4)
In numpy/pandas, it is used for elementwise comparison of arrays. For example, if arr = [True, False, True], ~arr returns [False, True, False].Cherilyncherilynn
@ayhan that's cool, so it applies a casting rule? Looks like it works for arrays of ints as well, but breaks down when combining ints and bools: ~np.array([1, 0, -1, True, False]) -> array([-2, -1, 0, -2, -1]). Could be used as a funky trick to convert bool values into int values though: ~~(np.array([True, False] + [1]))[:-1] -> array([1, 0])Katowice
@Katowice Either you have an array of bools or an array of ints. Mixing bools and ints yields an int array.Tachymetry
It's also really useful for code-golf!Steradian
D
15

The standard use cases for the bitwise NOT operator are bitwise operations, just like the bitwise AND &, the bitwise OR |, the bitwise XOR ^, and bitwise shifting << and >>. Although they are rarely used in higher level applications, there are still some times where you need to do bitwise manipulations, so that’s why they are there.

Of course, you may overwrite these for custom types, and in general you are not required to follow any specific semantics when doing so. Just choose what makes sense for your type and what still fits the operator in some way.

If the operation is obscure and better explained with a word or two, then you should use a standard method instead. But there are some situations, especially when working with number related types, that could have some mathematical-like operations which fit the bitwise operators, and as such are fine to use those.

Just like you would overwrite standard operators like + and - only for meaningful operations, you should try to do the same for bitwise operators.


The reason ~~True, ~~False gives you (1, 0) is because the bool type does not define its own __invert__ operation. However, int does; and bool is actually a subtype of int. So bool actually inherits the logic of all bitwise and arithmetical operators. That’s why True + True == 2 etc.

Dirtcheap answered 8/7, 2016 at 17:2 Comment(2)
That makes sense! So does ~ bear any resemblance to the not keyword? My initial investigations lead me to believe that not doesn't map to ~ and only really applies to boolean values.Katowice
Yes, you’re right. not is a boolean NOT operator while ~ is a bitwise NOT operator. Implementing the bitwise ~ will not impact the result from using the not keyword. not x in general is equivalent to not bool(x) which calls the special method __bool__ instead (which has to return a bool btw.).Dirtcheap
A
7

Are there any examples of valid use-cases for this operator that I should be aware of? And even if there are, is it generally acceptable to override this operator for types other than int?

Typically, you would not want to overload the ~ operator just because it is fun. It makes reading difficult. But sometimes, such overload for types other than int makes sense. Take a look at how SQLAlchemy puts it to good use.

Anticipate answered 8/7, 2016 at 17:1 Comment(3)
That's interesting, so SQLAlchemy uses it as a replacement for the NOT IN operation on columns?Katowice
It is a NOT operator, not a NOT IN operator.Anticipate
Ah, I hadn't read the link carefully enough. Thanks for correcting!Katowice
L
5

As others have mentioned, it can be really neat when traversing lists.

for i in range(n):
    mylist[~i]
    #much prettier than mylist[-i-1]

Take a look at an example which rotates a matrix clockwise by 90 degrees:

def rotate( A):
    n = len(A)
    for i in range(n/2):
        for j in range(n-n/2):
            A[i][j], A[~j][i], A[~i][~j], A[j][~i] = \\
                     A[~j][i], A[~i][~j], A[j][~i], A[i][j]

(This snippet was taken from here)

Lu answered 29/12, 2017 at 16:17 Comment(0)
M
4

It's commonly used in code golf as a shortcut for a few things, such as using ~x rather than -x-1 or ~my_bool rather than not my_bool.

Mishnah answered 8/7, 2016 at 19:41 Comment(0)
F
2

You can use that operator in conjunction with the negation operator (-) to increment a number by 1. For example:

x = 5
assert -~x == 6

This is the only practical way I've ever used the ~ operator. Any other way it is used for anything apart from numbers, is generally context dependent and often add a level of complexity to understanding code. For languages such as C++, Swift, Ruby, etc, you can overload this operator to mean anything which sometimes makes the code harder to digest quickly

Filide answered 8/7, 2016 at 17:23 Comment(9)
-~x (or its friend ~-x) has to be one of the least useful usesXenocryst
@harold this is perhaps true, but I have never used any other variant that is more useful than what I have posted. Can you suggest any other?Filide
And here I thought incrementing in-place was against the rules in python :) I actually think I've seen this in code golf examples before now that you mention it (not that the case for using it in readable code is improved by that observation!)Katowice
Typical/boring use is on bit vectors, such as flagsXenocryst
@harold wouldn't using it on a vector still require overriding __invert__ since otherwise it'll fail with TypeError: bad operand type for unary ~?Katowice
@Katowice sure bit I just meant the typical "all integer types are bit vectors" type of bit vectorXenocryst
@harold oh I see, that makes more sense!Katowice
@Katowice It doesn't increment in-place; it's just an expression equivalent to 1+x (for some types; for example, it doesn't work for floats). The value of x itself is left unchanged.Hornswoggle
@Hornswoggle phew, good to know that all is right with the world!Katowice
S
0

Some methods such as the String find() will return -1 indicating 'not found'. This is because character index 0 is a valid sub-string start position. Because integers are stored as two's compliment the ~ operator can then neatly be used to discriminate between -1 and everything else.

For example:

test_sentence_1 = "There is a tilde in here"
test_sentence_2 = "There is not one in here"

if ~test_sentence_1.find('tilde'):
    print("1. Tilde Found")
else:
    print("1. Tilde NOT Found")

if ~test_sentence_2.find('tilde'):
    print("2. Tilde Found")
else:
    print("2. Tilde NOT Found")

1. Tilde Found
2. Tilde NOT Found
>>>

This works because signed integers are stored as two's compliment and if you bit flip -1, which is what '~' does, then you get 0 ie False. If you bit flip anything else but -1 you get a non-zero ie True

This use of tilde was common in C where many functions would return -1 to indicate failure

Selfdenial answered 15/6, 2021 at 12:34 Comment(0)
E
0

~x is equivalent to (-x) - 1.

So it is handy to traverse an array or list in case we want to simultaneously operate on elements from both sides:

ie:

  • (s[0] and s[len(s) - 1])

  • (s[1] and s[len(s) - 2])

  • ...

    def tilde(self, nums: List[int]) -> int:
      for i in range(len(nums)):
        print(nums[i], nums[~i])
    

For an input [1,2,3,4,5,6,7,8,9] Output is :

1 9
2 8
3 7
4 6
5 5
6 4
7 3
8 2
9 1
Extracellular answered 29/12, 2022 at 12:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.