实现python切片表示法
Posted
技术标签:
【中文标题】实现python切片表示法【英文标题】:Implementing python slice notation 【发布时间】:2012-08-23 19:44:33 【问题描述】:我正在尝试用另一种语言 (php) 重新实现 python slice notation,并寻找可以模仿 python 逻辑的 sn-p(任何语言或伪代码)。也就是说,给定一个列表和一个三元组(start, stop, step)
或其一部分,确定所有参数的正确值或默认值,并返回一个切片作为新列表。
我试着调查the source。该代码远远超出了我的c技能,但我不禁同意评论说:
/* this is harder to get right than you might think */
另外,如果这样的事情已经完成,将不胜感激。
这是我的测试台(确保您的代码在发布前通过):
#place your code below
code = """
def mySlice(L, start=None, stop=None, step=None):
or
<?php function mySlice($L, $start=NULL, $stop=NULL, $step=NULL) ...
or
function mySlice(L, start, stop, step) ...
"""
import itertools
L = [0,1,2,3,4,5,6,7,8,9]
if code.strip().startswith('<?php'):
mode = 'php'
if code.strip().startswith('def'):
mode = 'python'
if code.strip().startswith('function'):
mode = 'js'
if mode == 'php':
var, none = '$L', 'NULL'
print code, '\n'
print '$L=array(%s);' % ','.join(str(x) for x in L)
print "function _c($s,$a,$e)if($a!==$e)echo $s,' should be [',implode(',',$e),'] got [',implode(',',$a),']',PHP_EOL;"
if mode == 'python':
var, none = 'L', 'None'
print code, '\n'
print 'L=%r' % L
print "def _c(s,a,e):\n\tif a!=e:\n\t\tprint s,'should be',e,'got',a"
if mode == 'js':
var, none = 'L', 'undefined'
print code, '\n'
print 'L=%r' % L
print "function _c(s,a,e)if(a.join()!==e.join())console.log(s+' should be ['+e.join()+'] got ['+a.join()+']');"
print
n = len(L) + 3
start = range(-n, n) + [None, 100, -100]
stop = range(-n, n) + [None, 100, -100]
step = range(-n, n) + [100, -100]
for q in itertools.product(start, stop, step):
if not q[2]: q = q[:-1]
actual = 'mySlice(%s,%s)' % (var, ','.join(none if x is None else str(x) for x in q))
slice_ = 'L[%s]' % ':'.join('' if x is None else str(x) for x in q)
expect = eval(slice_)
if mode == 'php':
expect = 'array(%s)' % ','.join(str(x) for x in expect)
print "_c(%r,%s,%s);" % (slice_, actual, expect)
if mode == 'python':
print "_c(%r,%s,%s);" % (slice_, actual, expect)
if mode == 'js':
print "_c(%r,%s,%s);" % (slice_, actual, expect)
如何使用:
保存到文件中 (test.py
)
将您的 python、php 或 javascript 代码放在 """
s 之间
运行python test.py | python
或python test.py | php
或python test.py | node
【问题讨论】:
【参考方案1】:这是基于@ecatmur 的 Python 代码再次移植到 PHP。
<?php
function adjust_endpoint($length, $endpoint, $step)
if ($endpoint < 0)
$endpoint += $length;
if ($endpoint < 0)
$endpoint = $step < 0 ? -1 : 0;
elseif ($endpoint >= $length)
$endpoint = $step < 0 ? $length - 1 : $length;
return $endpoint;
function mySlice($L, $start = null, $stop = null, $step = null)
$sliced = array();
$length = count($L);
// adjust_slice()
if ($step === null)
$step = 1;
elseif ($step == 0)
throw new Exception('step cannot be 0');
if ($start === null)
$start = $step < 0 ? $length - 1 : 0;
else
$start = adjust_endpoint($length, $start, $step);
if ($stop === null)
$stop = $step < 0 ? -1 : $length;
else
$stop = adjust_endpoint($length, $stop, $step);
// slice_indices()
$i = $start;
$result = array();
while ($step < 0 ? ($i > $stop) : ($i < $stop))
$sliced []= $L[$i];
$i += $step;
return $sliced;
【讨论】:
谢谢!这看起来效果不错,但是对于 php 版本的array_slice
优化(见@Jack 的帖子)是必不可少的。【参考方案2】:
我不能说代码中没有错误,但它已经通过了您的测试程序:)
def mySlice(L, start=None, stop=None, step=None):
ret = []
le = len(L)
if step is None: step = 1
if step > 0: #this situation might be easier
if start is None:
start = 0
else:
if start < 0: start += le
if start < 0: start = 0
if start > le: start = le
if stop is None:
stop = le
else:
if stop < 0: stop += le
if stop < 0: stop = 0
if stop > le: stop = le
else:
if start is None:
start = le-1
else:
if start < 0: start += le
if start < 0: start = -1
if start >= le: start = le-1
if stop is None:
stop = -1 #stop is not 0 because we need L[0]
else:
if stop < 0: stop += le
if stop < 0: stop = -1
if stop >= le: stop = le
#(stop-start)*step>0 to make sure 2 things:
#1: step != 0
#2: iteration will end
while start != stop and (stop-start)*step > 0 and start >=0 and start < le:
ret.append( L[start] )
start += step
return ret
【讨论】:
【参考方案3】:我已经基于 C 代码编写了一个 PHP 端口,针对步长 -1 和 1 进行了优化:
function get_indices($length, $step, &$start, &$end, &$size)
if (is_null($start))
$start = $step < 0 ? $length - 1 : 0;
else
if ($start < 0)
$start += $length;
if ($start < 0)
$start = $step < 0 ? -1 : 0;
elseif ($start >= $length)
$start = $step < 0 ? $length - 1 : $length;
if (is_null($end))
$end = $step < 0 ? -1 : $length;
else
if ($end < 0)
$end += $length;
if ($end < 0)
$end = $step < 0 ? - 1 : 0;
elseif ($end >= $length)
$end = $step < 0 ? $length - 1 : $length;
if (($step < 0 && $end >= $start) || ($step > 0 && $start >= $end))
$size = 0;
elseif ($step < 0)
$size = ($end - $start + 1) / $step + 1;
else
$size = ($end - $start - 1) / $step + 1;
function mySlice($L, $start = NULL, $end = NULL, $step = 1)
if (!$step)
return false; // could throw exception too
$length = count($L);
get_indices($length, $step, $start, $end, $size);
// optimize default step
if ($step == 1)
// apply native array_slice()
return array_slice($L, $start, $size);
elseif ($step == -1)
// negative step needs an array reversal first
// with range translation
return array_slice(array_reverse($L), $length - $start - 1, $size);
else
// standard fallback
$r = array();
for ($i = $start; $step < 0 ? $i > $end : $i < $end; $i += $step)
$r[] = $L[$i];
return $r;
【讨论】:
使用array_slice
是个好主意,因为大多数对mySlice
的调用都会使用step == 1
。【参考方案4】:
这是我在 C# .NET 中提出的解决方案,可能不是最漂亮的,但它确实有效。
private object[] Slice(object[] list, int start = 0, int stop = 0, int step = 0)
List<object> result = new List<object>();
if (step == 0) step = 1;
if (start < 0)
for (int i = list.Length + start; i < list.Length - (list.Length + start); i++)
result.Add(list[i]);
if (start >= 0 && stop == 0) stop = list.Length - (start >= 0 ? start : 0);
else if (start >= 0 && stop < 0) stop = list.Length + stop;
int loopStart = (start < 0 ? 0 : start);
int loopEnd = (start > 0 ? start + stop : stop);
if (step > 0)
for (int i = loopStart; i < loopEnd; i += step)
result.Add(list[i]);
else if (step < 0)
for (int i = loopEnd - 1; i >= loopStart; i += step)
result.Add(list[i]);
return result.ToArray();
【讨论】:
【参考方案5】:这是 C 代码的直接移植:
def adjust_endpoint(length, endpoint, step):
if endpoint < 0:
endpoint += length
if endpoint < 0:
endpoint = -1 if step < 0 else 0
elif endpoint >= length:
endpoint = length - 1 if step < 0 else length
return endpoint
def adjust_slice(length, start, stop, step):
if step is None:
step = 1
elif step == 0:
raise ValueError("step cannot be 0")
if start is None:
start = length - 1 if step < 0 else 0
else:
start = adjust_endpoint(length, start, step)
if stop is None:
stop = -1 if step < 0 else length
else:
stop = adjust_endpoint(length, stop, step)
return start, stop, step
def slice_indices(length, start, stop, step):
start, stop, step = adjust_slice(length, start, stop, step)
i = start
while (i > stop) if step < 0 else (i < stop):
yield i
i += step
def mySlice(L, start=None, stop=None, step=None):
return [L[i] for i in slice_indices(len(L), start, stop, step)]
【讨论】:
+1:如有疑问,只需直接移植已知有效的代码 这是一个艰难的选择,因为其他答案同样好,但您的代码更简洁,而且您是第一个 - 所以它是您的。非常感谢! @saul.shana*** 确定:hg.python.org/cpython/file/3d4d52e47431/Objects/… (问题中也有链接)。【参考方案6】:这就是我想出的(python)
def mySlice(L, start=None, stop=None, step=None):
answer = []
if not start:
start = 0
if start < 0:
start += len(L)
if not stop:
stop = len(L)
if stop < 0:
stop += len(L)
if not step:
step = 1
if stop == start or (stop<=start and step>0) or (stop>=start and step<0):
return []
i = start
while i != stop:
try:
answer.append(L[i])
i += step
except:
break
return answer
似乎有效 - 让我知道您的想法
希望对你有帮助
【讨论】:
谢谢,但这似乎不能正确处理负值。 奇怪的模数是怎么回事?切片不能那样工作。此外,slice
是一个糟糕的函数名称选择,因为它隐藏了一个内置函数。
@veredesmarald:我检查了模数。它按预期工作。也许 C 实现与我的不同,但这不会使我的实现无效(我错了吗?)。关于坏名的好点 - 更新
step<0
的事情将会中断。例如,if (stop <= start): return []
将不允许您拥有像 slice(10:1:-1)
这样的切片,这是有效的。
@PierreGM:已更新以处理负面步骤以上是关于实现python切片表示法的主要内容,如果未能解决你的问题,请参考以下文章