在Python 3.6+中将字符串转换为二进制的更快方法?

时间:2019-11-25 07:25:34

标签: python

我有数十亿行这样的字符串:1010101110100111100100101在内存中。 我需要将其转换为二进制整数列表。这将需要几分钟,似乎太慢了。 我的代码:

def string2vec(binary_str):
    return [int(binary_str[i:i + 8], base=2) for i in range(0, 64, 8)]

result= [ string2vec(l) for l in lines ]  # this code is slow


binary_str的长度是64,并且每8个二进制字符变成1个二进制整数。

3 个答案:

答案 0 :(得分:4)

编辑:似乎此功能可能内置在python中;看评论。我将留下这个答案,因为它提供了一个用于Python的C库的最小工作示例,该库可以处理数组,而我在网上找不到其他地方。

我同意许多评论,即如果您在内存中有一堆人类可读格式的二进制字符串,则显然出了问题。但是,如果有无法避免的无法控制的原因,则可以尝试使用C编写相关功能。这是一个简单的示例,从以下开始:

include <Python.h>

static PyObject * binary_string(PyObject * self, PyObject * args);

static PyMethodDef PyBinaryString_methods[] =
{
  { "binary_string", binary_string, METH_VARARGS, "binary string" },
  { NULL, NULL, 0, NULL }
};

static struct PyModuleDef PyBinaryString_module =
{
  PyModuleDef_HEAD_INIT,
  "PyBinaryString",
  "Binary String",
  -1,
  PyBinaryString_methods
};

PyMODINIT_FUNC PyInit_PyBinaryString(void)
{
  return PyModule_Create(&PyBinaryString_module);
}

static PyObject * binary_string(PyObject * self, PyObject * args)
{
  const char * string;

  char buf[8];

  if(!PyArg_ParseTuple(args, "s", &string)) { return NULL; }

  for(int i = 0; i < 8; i++)
  {
    buf[i] = 0;

    for(int j = 0; j < 8; j++)
    {
      buf[i] |= (string[8 * i + j] & 1) << (7 - j);
    }
  }

  return PyByteArray_FromStringAndSize(buf, 8);
}

这里,我利用了一个事实,即字符串将仅由ASCII'0'和'1'字符组成,前者的ASCII码为偶数,而后者的ASCII码为奇数。 / p>

在我的系统上,我可以通过以下方式进行编译

cc -fPIC -shared -O3 -I/usr/include/python -o PyBinaryString.so PyBinaryString.c

然后像这样在Python中使用它:

>>> from PyBinaryString import binary_string
>>> binary_string("1111111111111111111111111111111111111111111111111111111100000000")
bytearray(b'\xff\xff\xff\xff\xff\xff\xff\x00')

我不是Python程序员,所以有人也许可以提供一种更好的方式来获取/输入python对象格式的数据。但是,在我的机器上,它的运行速度比本地python版本快一个数量级。

如果您进一步了解内存的布局-如果您知道所有ASCII'0'和'1'字符的字符串都是连续的-您可以让C代码一次转换所有内容,这很可能会进一步加快速度。

答案 1 :(得分:4)

  

binary_str的长度为64,每8个二进制字符转换为1个二进制整数。

所有的字符串切片和Python循环都很昂贵。使用int(s,2)将整个二进制字符串转换为整数。然后使用array将整数作为64位整数进行管理并转换为8位整数。您可以决定是否要对字节使用大端或小端的结果:

import random
import time
import array

ints = [random.randrange(1<<64) for _ in range(1000)] # Make 1000 integers
strs = [f'{n:064b}' for n in ints]                    # Represent as binary strings
print(f'{ints[0]:016X} {strs[0]}')

start = time.perf_counter()
ints2 = [int(s,2) for s in strs]  # convert all the strings to integers
a = array.array('Q',ints)         # Store in an array.  Q = quadwords (64-bit ints)
a.byteswap()                      # Optional if you want the opposite endian-ness of your machine.
b = array.array('B')              # Another array of bytes
b.frombytes(a.tobytes())          # Populate byte array with the bytes from the quadword array.
print(time.perf_counter() - start)

assert ints == ints2
print([hex(n) for n in b[:8]])

输出:

1E27DFA21406A338 0001111000100111110111111010001000010100000001101010001100111000
0.0005346000000372442
['0x1e', '0x27', '0xdf', '0xa2', '0x14', '0x6', '0xa3', '0x38']

我的机器是低位优先的(大多数是)。它将一千个64位二进制字符串转换为整数,将它们存储在数组中,字节交换它们以表示big-endian,然后将数组的字节重新映射为字节数组...所有这些在我的机器上为534.6微秒。我已经显示了第一个64个字符的字符串及其十六进制表示形式,以及最终结果的前8个字节。如果您确实拥有这些字符串的“十亿”,则每十亿个字符串大约需要9分钟,但不要立即将它们全部读入内存:)

答案 2 :(得分:0)

由于只有2 ^ 8 = 256个可能的值,因此您可以构造一个查找表(以dict的形式),其中包含8个字符的字符串作为键,并作为对应的整数作为值。