Реализация нотации среза Python

Я пытаюсь повторно реализовать python обозначение фрагмента на другом языке (php) и ищем фрагмент (на любом языке или псевдокоде), который бы имитировал логику Python. То есть, учитывая список и тройку (start, stop, step) или ее часть, определить правильные значения или значения по умолчанию для всех параметров и вернуть срез в виде нового списка.

Я попытался просмотреть источник. Этот код выходит далеко за рамки моих навыков 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

person georg    schedule 29.08.2012    source источник


Ответы (6)


Вот прямой порт кода 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)]
person ecatmur    schedule 31.08.2012
comment
+1: если сомневаетесь, просто сделайте простой порт кода, который, как известно, работает - person Claudiu; 31.08.2012
comment
Это был трудный выбор, так как другие ответы одинаково хороши, но ваш код чище, и вы были первыми, так что он ваш. Большое спасибо! - person georg; 07.09.2012
comment
@saul.shanabrook уверен: hg.python.org/cpython/ file/3d4d52e47431/Objects/ (он также связан с вопросом). - person ecatmur; 25.09.2014

Вот что я придумал (питон)

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

Кажется, работает - дайте мне знать, что вы думаете

Надеюсь, поможет

person inspectorG4dget    schedule 29.08.2012
comment
Спасибо, но, похоже, это неправильно обрабатывает отрицательные значения. - person georg; 29.08.2012
comment
Что случилось со странными вещами по модулю? Слайс так не работает. Также slice — плохой выбор имени функции, так как оно затеняет встроенную функцию. - person verdesmarald; 29.08.2012
comment
@veredesmarald: я проверил математику по модулю. Он работает так, как ожидалось. Возможно, реализация C отличается от моей, но это не делает мою недействительной (я ошибаюсь?). Хорошее замечание о плохом имени - обновлено - person inspectorG4dget; 29.08.2012
comment
Все сломается для step<0. Например, if (stop <= start): return [] не позволит вам иметь срез, подобный slice(10:1:-1), который допустим. - person Pierre GM; 29.08.2012
comment
@PierreGM: обновлено для обработки отрицательного шага - person inspectorG4dget; 29.08.2012
comment
Я не уверен, что вы подразумеваете под ожидаемым, поскольку, насколько я знаю, нарезка вообще не включает арифметику по модулю... Например: [1,2,3][6:4] -> [], но mySlice([1,2,3], 6, 4) -> [1]. - person verdesmarald; 29.08.2012
comment
Все еще не работает... mySlice(range(5), 1) возвращает [1, 2, 3] - person georg; 30.08.2012

Это решение, которое я придумал в 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();
}
person westin    schedule 03.09.2012

Я написал порт PHP на основе кода C, оптимизированный для размеров шага -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;
        }
}
person Ja͢ck    schedule 05.09.2012
comment
Очень хорошая идея использовать array_slice, так как большинство вызовов mySlice будут использовать step == 1. - person georg; 07.09.2012

Это основано на коде Python @ecatmur, снова перенесенном на 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;
}
person mcrumley    schedule 06.09.2012
comment
Спасибо! Похоже, это работает хорошо, но для версии php оптимизация array_slice (см. пост @Jack) необходима. - person georg; 07.09.2012

Я не могу сказать, что в кодах нет ошибок, но они прошли вашу тестовую программу :)

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
person Marcus    schedule 06.09.2012