The tilde operator in Python

Question:

What’s the usage of the tilde operator in Python?

One thing I can think about is do something in both sides of a string or list, such as check if a string is palindromic or not:

def is_palindromic(s):
    return all(s[i] == s[~i] for i in range(len(s) / 2)) 

Any other good usage?

Asked By: clwen

||

Answers:

~ is the bitwise complement operator in python which essentially calculates -x - 1

So a table would look like

i  ~i
-----
0  -1
1  -2
2  -3
3  -4 
4  -5 
5  -6

So for i = 0 it would compare s[0] with s[len(s) - 1], for i = 1, s[1] with s[len(s) - 2].

As for your other question, this can be useful for a range of bitwise hacks.

Answered By: GWW

It is a unary operator (taking a single argument) that is borrowed from C, where all data types are just different ways of interpreting bytes. It is the “invert” or “complement” operation, in which all the bits of the input data are reversed.

In Python, for integers, the bits of the twos-complement representation of the integer are reversed (as in b <- b XOR 1 for each individual bit), and the result interpreted again as a twos-complement integer. So for integers, ~x is equivalent to (-x) - 1.

The reified form of the ~ operator is provided as operator.invert. To support this operator in your own class, give it an __invert__(self) method.

>>> import operator
>>> class Foo:
...   def __invert__(self):
...     print 'invert'
...
>>> x = Foo()
>>> operator.invert(x)
invert
>>> ~x
invert

Any class in which it is meaningful to have a “complement” or “inverse” of an instance that is also an instance of the same class is a possible candidate for the invert operator. However, operator overloading can lead to confusion if misused, so be sure that it really makes sense to do so before supplying an __invert__ method to your class. (Note that byte-strings [ex: 'xff'] do not support this operator, even though it is meaningful to invert all the bits of a byte-string.)

Answered By: wberry

Besides being a bitwise complement operator, ~ can also help revert a boolean value, though it is not the conventional bool type here, rather you should use numpy.bool_.


This is explained in,

import numpy as np
assert ~np.True_ == np.False_

Reversing logical value can be useful sometimes, e.g., below ~ operator is used to cleanse your dataset and return you a column without NaN.

from numpy import NaN
import pandas as pd

matrix = pd.DataFrame([1,2,3,4,NaN], columns=['Number'], dtype='float64')
# Remove NaN in column 'Number'
matrix['Number'][~matrix['Number'].isnull()]
Answered By: Nicholas

This is minor usage is tilde…

def split_train_test_by_id(data, test_ratio, id_column):
    ids = data[id_column]
    in_test_set = ids.apply(lambda id_: test_set_check(id_, test_ratio)) 
    return data.loc[~in_test_set], data.loc[in_test_set]

the code above is from “Hands On Machine Learning”

you use tilde (~ sign) as alternative to – sign index marker

just like you use minus – is for integer index

ex)

array = [1,2,3,4,5,6]
print(array[-1])

is the samething as

print(array[~1])

Answered By: hyukkyulee

One should note that in the case of array indexing, array[~i] amounts to reversed_array[i]. It can be seen as indexing starting from the end of the array:

[0, 1, 2, 3, 4, 5, 6, 7, 8]
    ^                 ^
    i                ~i
Answered By: Le Frite

I was solving this leetcode problem and I came across this beautiful solution by a user named Zitao Wang.

The problem goes like this for each element in the given array find the product of all the remaining numbers without making use of divison and in O(n) time

The standard solution is:

Pass 1: For all elements compute product of all the elements to the left of it
Pass 2: For all elements compute product of all the elements to the right of it
        and then multiplying them for the final answer 

His solution uses only one for loop by making use of. He computes the left product and right product on the fly using ~

def productExceptSelf(self, nums):
    res = [1]*len(nums)
    lprod = 1
    rprod = 1
    for i in range(len(nums)):
        res[i] *= lprod
        lprod *= nums[i]
        res[~i] *= rprod
        rprod *= nums[~i]
    return res
Answered By: Stuxen

The only time I’ve ever used this in practice is with numpy/pandas. For example, with the .isin() dataframe method.

In the docs they show this basic example

>>> df.isin([0, 2])
        num_legs  num_wings
falcon      True       True
dog        False       True

But what if instead you wanted all the rows not in [0, 2]?

>>> ~df.isin([0, 2])
        num_legs  num_wings
falcon     False       False
dog        True        False
Answered By: Adam Hughes

it’s called Binary One’s Complement (~)

It returns the one’s complement of a number’s binary. It flips the bits. Binary for 2 is 00000010. Its one’s complement is 11111101.

This is binary for -3. So, this results in -3. Similarly, ~1 results in -2.

~-3

Output : 2

Again, one’s complement of -3 is 2.

Answered By: Oubaid Gharbi

Explaining why -x -1 is correct in general (for integers)

Sometimes (example), people are surprised by the mathematical behaviour of the ~ operator. They might reason, for example, that rather than evaluating to -19, the result of ~18 should be 13 (since bin(18) gives '0b10010', inverting the bits would give ‘0b01101’ which represents 13 – right?). Or perhaps they might expect 237 (treating the input as signed 8-bit quantity), or some other positive value corresponding to larger integer sizes (such as the machine word size).

Note, here, that the signed interpretation of the bits 11101101 (which, treated as unsigned, give 237) is… -19. The same will happen for larger numbers of bits. In fact, as long as we use at least 6 bits, and treating the result as signed, we get the same answer: -19.

The mathematical rule – negate, and then subtract one – holds for all inputs, as long as we use enough bits, and treat the result as signed.

And, this being Python, conceptually numbers use an arbitrary number of bits. The implementation will allocate more space automatically, according to what is necessary to represent the number. (For example, if the value would "fit" in one machine word, then only one is used; the data type abstracts the process of sign-extending the number out to infinity.) It also does not have any separate unsigned-integer type; integers simply are signed in Python. (After all, since we aren’t in control of the amount of memory used anyway, what’s the point in denying access to negative values?)

This breaks intuition for a lot of people coming from a C environment, in which it’s arguably best practice to use only unsigned types for bit manipulation and then apply 2s-complement interpretation later (and only if appropriate; if a value is being treated as a group of "flags", then a signed interpretation is unlikely to make sense). Python’s implementation of ~, however, is consistent with its other design choices.

How to force unsigned behaviour

If we wanted to get 13, 237 or anything else like that from inverting the bits of 18, we would need some external mechanism to specify how many bits to invert. (Again, 18 conceptually has arbitrarily many leading 0s in its binary representation in an arbitrary number of bits; inverting them would result in something with leading 1s; and interpreting that in 2s complement would give a negative result.)

The simplest approach is to simply mask off those arbitrarily-many bits. To get 13 from inverting 18, we want 5 bits, so we mask with 0b11111, i.e., 31. More generally (and giving the same interface for the original behaviour):

def invert(value, bits=None):
    result = ~value
    return result if bits is None else (result & ((1 << bits) - 1))

Another way, per Andrew Jenkins’ answer at the linked example question, is to XOR directly with the mask. Interestingly enough, we can use XOR to handle the default, arbitrary-precision case. We simply use an arbitrary-sized mask, i.e. an integer that conceptually has an arbitrary number of 1 bits in its binary representation – i.e., -1. Thus:

def invert(value, bits=None):
    return value ^ (-1 if bits is None else ((1 << bits) - 1))

However, using XOR like this will give strange results for a negative value – because all those arbitrarily-many set bits "before" (in more-significant positions) the XOR mask weren’t cleared:

>>> invert(-19, 5) # notice the result is equal to 18 - 32
-14
Answered By: Karl Knechtel
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.