Стандарт сообщества и плюсы / минусы?Деструктивная и Неразрушающая итерация - PullRequest
0 голосов
/ 20 октября 2018

Я видел два основных способа перебора коллекции элементов (например, list, array и т. Д.). Один - это увеличение или уменьшение индекса, что обычно является значением по умолчанию для многих «встроенных модулей» (например, for циклов в python). Другой - это разрезать коллекцию и всегда оценивать первый элемент.

Эти два подхода могут " работать " (но не могут быть идеальными), IIRC, на многих распространенных веб-языках, включая Ruby, Javascript и Python.Поэтому, хотя я и абстрактно спрашиваю о неязыковом подходе, как указывает @DavidMaze, этот вопрос может быть неприменим к каждому языку или могут существовать огромные языковые различия.

Например,использование Python:

ONE

collection_1 = [0, 1, 2, 3, 4]
x = 0
while x < len(collection_1):
  print(collection_1[x])
  x += 1
print("collection_1: {}".format(collection_1))

Результат:

0
1
2
3
4
collection_1: [0, 1, 2, 3, 4]

ДРУГОЕ

collection_2 = [0, 1, 2, 3, 4]
while len(collection_2) > 0:
  print(collection_2[0])
  collection_2 = collection_2[1:]
print("collection_2: {}".format(collection_2))

результат:

0
1
2
3
4
collection_2: []

Очевидно, что ОДНО необходимо для сохранения данных, и ДРУГОЕ дает некоторое преимущество, если хранение ограничено и данные больше не нужны после итерации.Помимо этих сценариев, я думаю, что ОДИН, как правило, более полезен (и с меньшей вероятностью привносит ошибки).Я также думаю, что ONE - это , вероятно, всегда или почти всегда дешевле для достижения того же результата, но я хочу проверить вышеизложенные предположения.Я заинтересован в:

  • В каких случаях (если они есть) часто встречаются случаи, когда «ДРУГОЕ» предпочтительнее «ОДНОГО»?Почему?
  • Можно ли сказать, что ЕДИНОЕ всегда или почти всегда предпочтительнее ДРУГОГО?
  • Существует ли принятый стандарт или соглашение в отношениичто-то в этом роде?
  • Является ли один подход значительно более дорогим?
  • Являются ли ответы на вышеприведенные вопросы общими или специфичными для языка?

1 Ответ

0 голосов
/ 20 октября 2018

Я предложу рубрику:

1.Соответствуйте стилю существующей кодовой базы.

В большом проекте может быть полезно, если все написано в едином стиле.Для супер-низкоуровневых вещей, таких как итерация списка, это не обязательно имеет большое значение, но если большинство вещей «один» способ выполнить ту же задачу, «другой» способ запутает людей, которые придут позже.

2.Если ваш язык имеет собственную итерацию списка (например, функцию «map»), используйте ее.

В большинстве новых языков есть какой-либо способ выполнить действие со списком в целом, или есть какой-то стандартный способ итерации по списку.списки.

# Python
for x in collection_1:
    print(x)
// Javascript
collection_1.forEach(x => console.log(x))
-- Haskell
mapM_ putStrLn collection1
// Go
for _, x := range collection1 {
        fmt.Printf("%d\n", x)
}

В C и языках, тесно связанных с ним, индексирование массивов является идиоматическим, поэтому сделайте это , если это обычное соглашение в этом языке .

/* C */
void printList(int *l, int len) {
    int i;
    for (i = 0; i < len; i++) {
        printf("%d\n", l[i]);
    }
}

3.Понять, что такое тип «список».

Разные языки имеют разные реализации списков по умолчанию с разными характеристиками.Если «список» является односвязным списком (Lisp, Haskell), то итерация по нему элемента за раз и передача «остальной части списка» на самом деле верны.

-- Haskell
-- printAList is a function that takes a list parameter
-- and returns an IO action producing no value in particular.
printAList :: [Int] -> IO ()
-- To print an empty list, do nothing.
printAList [] = return ()
-- To print a non-empty list:
printAList (x:xs) = do
  -- Print the first element;
  putStrLn (show x)
  -- Then print the list of things starting at the second element.
  printAList xs

Так как этооднако, для списка с однократной связью выбор элемента по индексу может занять O ( n ) времени, и вы определенно не хотите этого делать.

В некоторых языках «список»начальная позиция плюс длина (массивы в C, слайсы в Go), и в этом случае «создать список остальных» не так уж и дорого, хотя обычно выглядит немного странно.

/* C programmers will look at you funny */
void printList2(int *l, int len) {
    for (; len > 0; l++, len--)
        printf("%d\n", l[0]); /* or *l */
    }
}
// Go: also strange, but not wildly inefficient
func printList(l []int) void {
        while (len(l) > 0) {
                fmt.Printf("%d\n", l[0])
                l = l[1:len(l)]
        }
}

Но если «список» - это блок элементов, и для его изменения необходимо скопировать весь список (Python, как предлагает @ Ry- в комментариях), тогда вы определенно не хотите этого;но тогда поиск предметов по индексу относительно дешев.

# Python; this is common to update a list in place
for i in range(len(container_1)):
    container_1[i] += 1

4.Осторожно отдайте предпочтение неразрушающим действиям.

На современном оборудовании дорогостоящие вещи - это ввод-вывод, сетевые вызовы и плохие алгоритмы.Небольшая эффективность в основном не имеет значения.Есть ли практическая разница между:

# Python

def doubleListInPlace(l):
  for i in range(len(l)):
      l[i] *= 2
  return l

def doubleListAndCopy(l):
  return [2 * x for x in l]

def main():
  l = [1, 2, 3]
  ll = doubleList...(l)
  lll = doubleList...(ll)
  print("Doubled list: " + repr(ll))
  print("Quadrupled list: " + repr(lll))

Форма immutable ("... andCopy"), конечно же, выделяет дополнительный список, но когда вы перейдете к использованию результата позже, будет меньше сюрпризов, ожидающих, иВы (обычно) не заметите дополнительное распределение.Использование более неизменного стиля также очень полезно при тестировании (вы можете создать тестовое приспособление один раз и повторно использовать его во всех тестах) и для поддержки стека отмены (это ядро ​​«отладчика путешествий во времени» в среде Javascript Redux, дляinstance).

Является ли один подход значительно более дорогим?

Если ваш "нативный" тип списка является односвязным списком, то попытайтесь перебрать его по индексузанимает O ( n ^ 2) времени (вы должны пройти все n элементов, чтобы добраться до последнего, плюс n -1, чтобы добраться до второго-до последнего плюс ...).Если ваш «родной» тип списка основан на массиве или векторе, то работа с подсписками займет O (n ^ 2) времени (вы должны скопировать все n -1 элементов, которые вы еще не обработали),На этот вопрос нет конкретного универсального кросс-языкового ответа.

...