【问题标题】:Implementing python slice notation实现python切片表示法
【发布时间】: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 | pythonpython test.py | phppython test.py | node

【问题讨论】:

    标签: php python arrays algorithm language-comparisons


    【解决方案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.shanabrook 确定: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&lt;0 的事情将会中断。例如,if (stop &lt;= start): return [] 将不允许您拥有像 slice(10:1:-1) 这样的切片,这是有效的。
        • @PierreGM:已更新以处理负面步骤
        猜你喜欢
        • 2019-03-06
        • 1970-01-01
        • 2017-03-08
        • 1970-01-01
        • 1970-01-01
        • 2013-04-12
        • 2013-06-26
        • 1970-01-01
        相关资源
        最近更新 更多