递归删除字符串中的最终字符 - 最好的方法是什么?

时间:2016-09-16 13:40:24

标签: python

我正在逐个读取文件中的行,在我存储每行之前,我想根据以下简单规则修改它们:

  • 如果最后一个字符不是,例如,{'a', 'b', 'c'}存储该行。
  • 如果不是这种情况,请删除该字符(类似pop)并再次检查。

我目前所拥有的(感觉就像显而易见的事情)是:

bad_chars = {'a', 'b', 'c'}


def remove_end_del(line_string, chars_to_remove):
    while any(line_string[-1] == x for x in chars_to_remove):
        line_string = line_string[:-1]
    return line_string

example_line = 'jkhasdkjashdasjkd|abbbabbababcbccc'
modified_line = remove_end_del(example_line, bad_chars)
print(modified_line)  # prints -> jkhasdkjashdasjkd|

这当然有效,但字符串切片\重建对我未经训练的眼睛来说似乎有些过分。所以我想知道几件事:

  1. 有更好的方法吗?像pop类型的字符串函数?
  2. rstrip()strip()一般如何实施?是否还有
  3. 这个例子是否值得rstrip()递归?
  4. 最后,以下情况有多好:
  5. def remove_end_del_2(line_string, chars_to_remove):
        i = 1
        while line_string[-i] in chars_to_remove:
            i += 1
        return line_string[:-i+1]
    

    对上述任何一点的任何评论都将受到赞赏<。

    注意:分隔符(&#34; |&#34;)仅用于可视化。

4 个答案:

答案 0 :(得分:4)

re.sub的另一种近乎快速的方法虽然更直观(听起来像是pop你要求的),但是itertools.dropwhile

  

创建一个迭代器,只要在迭代器中删除元素   谓词是真的;

>>> ''.join(dropwhile(lambda x: x in bad_chars, example_line[::-1]))[::-1]
'jkhasdkjashdasjkd|'

然而,看起来rstrip已经制作出来并且更适合这项任务。

一些时间:

In [4]: example_line = 'jkhasdkjashdasjkd|abbbabbababcbccc'

In [5]: bad_chars = {'a', 'b', 'c'}
In [6]: %%timeit
   ...: re.sub(r'[%s]+$' % ''.join(bad_chars), '', example_line)
   ...:
100000 loops, best of 3: 5.24 µs per loop
In [7]: %%timeit
   ...: ''.join(dropwhile(lambda x: x in bad_chars, example_line[::-1]))[::-1]
   ...:
100000 loops, best of 3: 5.72 µs per loop
In [10]: %%timeit
   ....: remove_end_del(example_line, bad_chars)
   ....:
10000 loops, best of 3: 24.1 µs per loop
In [11]: %%timeit
   ....: example_line.rstrip('abc')
   ....:
1000000 loops, best of 3: 579 ns per loop
In [14]: %%timeit
   ....: remove_end_del_2(example_line, bad_chars)
   ....:
100000 loops, best of 3: 4.22 µs per loop

rstrip获胜!

答案 1 :(得分:2)

不是问题的直接答案,但另一种选择是使用正则表达式删除字符串末尾的错误字符:

>>> import re
>>>
>>> example_line = 'jkhasdkjashdasjkd|abbbabbababcbccc'
>>> bad_chars = {'a', 'b', 'c'}
>>>
>>> re.sub(r'[%s]+$' % ''.join(bad_chars), '', example_line)
'jkhasdkjashdasjkd|'

这里的正则表达式是从“坏”字符集动态构造的。在这种情况下,它会(或“可能”,因为集合没有顺序)为[abc]+$

  • [abc]定义“字符类” - 任何“a”,“b”或“c”都匹配
  • +表示1个或更多
  • $定义字符串的结尾

(请注意,如果“坏”字符可以包含可能在字符类中具有特殊含义的字符(例如,[]),则应将其转义为re.escape())。

最后一句话可能证明old saying关于比最初有更多问题。

答案 2 :(得分:2)

切片会创建大量不必要的字符串临时副本。递归会更糟糕 - 仍然会产生副本,并且在它之上会引入函数调用开销。这两种方法都不是很好。

您可以在CPython source code中找到rstrip实施。在那里使用迭代方法(类似于您的上一个代码片段)。

Py_LOCAL_INLINE(PyObject *)
do_xstrip(PyBytesObject *self, int striptype, PyObject *sepobj)
{
    Py_buffer vsep;
    char *s = PyBytes_AS_STRING(self);
    Py_ssize_t len = PyBytes_GET_SIZE(self);
    char *sep;
    Py_ssize_t seplen;
    Py_ssize_t i, j;

    if (PyObject_GetBuffer(sepobj, &vsep, PyBUF_SIMPLE) != 0)
        return NULL;
    sep = vsep.buf;
    seplen = vsep.len;

    i = 0;
    if (striptype != RIGHTSTRIP) {
        while (i < len && memchr(sep, Py_CHARMASK(s[i]), seplen)) {
            i++;
        }
    }

    j = len;
    if (striptype != LEFTSTRIP) {
        do {
            j--;
        } while (j >= i && memchr(sep, Py_CHARMASK(s[j]), seplen));
        j++;
    }

    PyBuffer_Release(&vsep);

    if (i == 0 && j == len && PyBytes_CheckExact(self)) {
        Py_INCREF(self);
        return (PyObject*)self;
    }
    else
        return PyBytes_FromStringAndSize(s+i, j-i);
}

总而言之,您使用基于索引的解析的直觉是正确的。主要优点是不会创建临时字符串,并且会大大减少在内存中复制内容。

答案 3 :(得分:1)

我理解过度的含义,但我认为一般来说,它看起来不错。另一种方法是使用索引,这是不可读的。 (我也碰巧认为正则表达式也不是很易读......)

但是,如果您有memoryview对象可能相关或不相关,则可以使用byteshttps://docs.python.org/3/library/stdtypes.html#memoryview

<强> 1。字符串的pop函数

.pop没有str方法。您必须使用list(line_string).pop(),其中list(s)创建一个列表,其中字符串的每个字符都作为元素。

<强> 2。 (r)strip实施

这可能是暂时实现的,是的。 它应该是所有C代码。

第3。递归rstrip

首先,为什么你需要让它递归? 其次,我认为(递归)会使心理负担不必要地高 - 所以,不。

<强> 4。最后,以下内容有多好:

测量它!肯定会更快。