在Python中从切片对象中检索切片的长度

时间:2016-03-23 20:50:02

标签: python list slice

标题解释了自己,如何从对象中获得2个

slice(0,2)

文档有点令人困惑,或者它是错误的

https://docs.python.org/2/c-api/slice.html

特别是我不明白

输出的含义是什么
slice(0,2).indices(0)  # (0, 0, 1)
slice(0,2).indices(10 ** 10)  # (0, 2, 1)

一种可能的解决方法是使用切片对象

切片列表
a = [1,2,3,4,5]
len(a[slice(0,2)])  # 2

但是对于任意大的切片,这都会失败。

谢谢,我在其他帖子中找不到答案。

5 个答案:

答案 0 :(得分:4)

对此没有完整的答案。 slice没有给出长度,因为结果的长度总是取决于被切片序列的大小,短序列(包括空序列)将产生更少的项目,如果{ {1}}是无界的,然后长度将与序列的长度一起增长;一个slice可能只是顺序结束"拥有slicestart stop

为了快速简便地计算已知长度序列的长度,您只需将None与Py3' .indices(或range组合在Py2中,虽然xrange对Py3 xrange没有的值有限制。 slice.indices gives you the concrete start, stop and stride values derived when a slice applies to a sequence of a given length,它基本上是您在C样式range循环中填写的值,该循环遍历与for相同的索引:

slice

因此,要计算应用于包含1000个元素的序列时 for (ssize_t i = start; i < stop; i += stride) 的长度,您可以这样做:

slice

如果您使用的是Python 2,并且您的值可能超过>>> len(range(*slice(0, 2).indices(1000))) 2 >>> len(range(*slice(10, None, 3).indices(1000))) 330 可以处理的值(它仅限于界限和总长度等于xrange可以容纳的值) ,你可以手工完成计算:

ssize_t

更新:不幸的是,def slice_len_for(slc, seqlen): start, stop, step = slc.indices(seqlen) return max(0, (stop - start + (step - (1 if step > 0 else -1))) // step) >>> slice_len_for(slice(10, None, 3), 1000) 330 本身不会接受slice.indices超出len可以容纳的序列,所以这并不是#&# 39;在Py2中使用long获得任何结果。留给那些感兴趣的人,但除非你perform the work slice does to convert negative values and None to concrete values based on the sequence length.感叹,否则解决方法不会解决任何问题。

答案 1 :(得分:3)

所以看起来slice.indices(n)返回要赋予range的参数,以获得应该反映在长度为n的序列片段中的项目索引(虽然没有记录 编辑:,正如@ShadowRanger指出的那样,确实是documented)。因此,以下行评估相同的值:

# get some list to work on
my_list = list(range(100))

# slice syntax
print(my_list[1:15:3])
# regular item access
print(my_list[slice(1,15,3)])
# reinvent list slicing
print([my_list[i] for i in range(*slice(1,15,3).indices(len(my_list)))])

如您所见,结果列表的长度与range(*slice(1,15,3).indices(len(my_list)))的长度相同,这取决于slice对象本身以及要切片的序列的长度。这就是为什么len(range(*slice.indices(n)))会在Python 3中为您提供正确答案的原因。(范围对象是一个生成器,幸运的是定义了__len__函数,因此它可以为您提供项目计数,无需枚举和计算它们。)

如果你在python 2中使用大数字,你可以像@ShadowRanger建议的那样复制计算。

range.__len__的原始实现如下:

/* Return number of items in range (lo, hi, step).  step != 0
 * required.  The result always fits in an unsigned long.
 */
static unsigned long
get_len_of_range(long lo, long hi, long step)
{
    /* -------------------------------------------------------------
    If step > 0 and lo >= hi, or step < 0 and lo <= hi, the range is empty.
    Else for step > 0, if n values are in the range, the last one is
    lo + (n-1)*step, which must be <= hi-1.  Rearranging,
    n <= (hi - lo - 1)/step + 1, so taking the floor of the RHS gives
    the proper value.  Since lo < hi in this case, hi-lo-1 >= 0, so
    the RHS is non-negative and so truncation is the same as the
    floor.  Letting M be the largest positive long, the worst case
    for the RHS numerator is hi=M, lo=-M-1, and then
    hi-lo-1 = M-(-M-1)-1 = 2*M.  Therefore unsigned long has enough
    precision to compute the RHS exactly.  The analysis for step < 0
    is similar.
    ---------------------------------------------------------------*/
    assert(step != 0);
    if (step > 0 && lo < hi)
    return 1UL + (hi - 1UL - lo) / step;
    else if (step < 0 && lo > hi)
    return 1UL + (lo - 1UL - hi) / (0UL - step);
    else
    return 0UL;
}

slice.indices

int
PySlice_GetIndices(PySliceObject *r, Py_ssize_t length,
                   Py_ssize_t *start, Py_ssize_t *stop, Py_ssize_t *step)
{
    /* XXX support long ints */
    if (r->step == Py_None) {
        *step = 1;
    } else {
        if (!PyInt_Check(r->step) && !PyLong_Check(r->step)) return -1;
        *step = PyInt_AsSsize_t(r->step);
    }
    if (r->start == Py_None) {
        *start = *step < 0 ? length-1 : 0;
    } else {
        if (!PyInt_Check(r->start) && !PyLong_Check(r->step)) return -1;
        *start = PyInt_AsSsize_t(r->start);
        if (*start < 0) *start += length;
    }
    if (r->stop == Py_None) {
        *stop = *step < 0 ? -1 : length;
    } else {
        if (!PyInt_Check(r->stop) && !PyLong_Check(r->step)) return -1;
        *stop = PyInt_AsSsize_t(r->stop);
        if (*stop < 0) *stop += length;
    }
    if (*stop > length) return -1;
    if (*start >= length) return -1;
    if (*step == 0) return -1;
    return 0;
}

来源来自svn

答案 2 :(得分:1)

如果序列的长度已知

ShadowRanger 的回答涵盖了通用解决方案,但是如果您(像我一样)确实知道序列的长度 - 这里有一个简单的方法应该像 range 那样处理它(包括大多数边缘情况),并且无需对潜在的长序列进行迭代。

这与 Markus 写的类似,但处理更多的边缘情况。

from math import ceil

def max_slice_len(s: slice):
assert s.stop or s.stop == 0, "Must define stop for max slice len!"
assert s.step != 0, "Step slice cannot be zero"

start = s.start or 0
stop = s.stop
step = s.step or 1

delta = (stop - start)
dsteps = int(ceil(delta / step))

return dsteps if dsteps >= 0 else 0


def slice_len(s: slice, src_len: int):
    stop = min(s.stop, src_len)
    return max_slice_len(slice(s.start, stop, s.step))

说明: 假设我们可以在没有 src_len 的情况下获得切片的“最大长度”, 然后我们可以通过将 src_len(列表的长度或您想要迭代的任何内容)作为切片的 stop(如果它小于当前的 stop)来构建它。

但这仍然存在找到“最大长度”的问题。


获取切片的最大长度

python 中的切片构造创建了一种 Arithmetic Set
其中 a0 == startd == stepn == len

一个公式告诉我们:a_n = a0+ (n-1)d
[a_n 是序列的第 n 个元素]
如果我们将 stop 视为 a_n,则:stop = start + (len - 1) * step
重新排列我们得到:len = [(stop-start)/step] + 1.

这很好地为我们处理了反向迭代(即 [10:0:-1]),

但它通常会返回一个浮点数,因为停止可能不是开始之后的全部“步骤”。 (即对于 [0:10:3],(10-0) / 3 给我们 3.3333...)。
使用 ceil 解决了这个问题。

剩下的唯一问题是负面结果([10:0:1] 会给我们 (0-10)/1 = -10),但实际的“长度”应该为零。
解决方案是通过返回 dsteps if dsteps >= 0 else 0

来截断负面结果

测试

import unittest
# import max_slice_len, slice_len

class TestSliceUtil(unittest.TestCase):
    def test_max_len_suite(self):
        simple_test_cases = [
            (slice(0, 10, 1), 10),
            (slice(0, 10, 2), 5),
            (slice(0, 10, 3), 4),
            (slice(0, 10, 10), 1),
            (slice(0, 10, 100), 1),
            (slice(-1, 10, 5), 3),
            (slice(-10, -1, 3), 3),
            (slice(15, 10, 1), 0),
            (slice(0, 10, -1), 0),
            (slice(0, 10, -3), 0),
            (slice(15, 10, -1), 5),
            (slice(10, 0, -1), 10),

            # none replacement (without len)
            (slice(None, 10, 1), 10),
            (slice(0, 10, None), 10),
        ]

        def test_len(s: slice, expected_len: int):
            iter_len = s.stop + 1  # simulate some iterable that is longer than the max_len

            enumerated_idxs = list(range(s.start or 0, s.stop, s.step or 1))
            enumerated_len = len(enumerated_idxs)

            result = slice_len(s, iter_len)
            self.assertEqual(result, expected_len, "Not same as expected!")
            self.assertEqual(result, enumerated_len, "Not same as enumerated!")

        def test_max_len(s: slice, expected_len: int):
            result = max_slice_len(s)
            self.assertEqual(result, expected_len,
                             "Max len was not equal! slice: {}. expected: {}. Actual: {}".format(s, expected_len,
                                                                                                 result))
        for case in simple_test_cases:
            s, expected = case
            with self.subTest("max_len {} -> {}".format(s, expected)):
                test_max_len(s, expected)
            with self.subTest("len vs enumerated {} -> {}".format(s, expected)):
                test_len(s, expected)

答案 3 :(得分:0)

使用断言的简化方法

长度取决于要切片的目标对象。 但是可以定义一个最大长度

示例

定义这样的最大长度函数

def slice_len_max(s):
    assert (s.start is not None)
    assert (s.stop is not None)
    step = 1
    if s.step is not None:
        step = s.step
    return max((s.stop - s.start) // step, 1)

并检查输出

>>> slice_len_max(slice(0, 10))
10
>>> slice_len_max(slice(0, 10, 2))
5
>>> slice_len_max(slice(0, 10, 3))
3
>>> slice_len_max(slice(0, 10, 10))
1
>>> slice_len_max(slice(0, 10, 100))
1
>>> slice_len_max(slice(3))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in slice_len_max
AssertionError

由于切片未定义start属性,因此最后一次呼叫失败。

答案 4 :(得分:-3)

>>> slice(0,2).__getattribute__('stop')
2
>>> slice(0,2).__getattribute__('start')
0