Чи є якісь переваги використання цієї додаткової змінної в анотації циклу?


11

У великому проекті, над яким я працюю (я псевдокод), я знайшов таку анотацію циклу:

var someOtherArray = [];
for (var i = 0, n = array.length; i < n; i++) {
    someOtherArray[i] = modifyObjetFromArray(array[i]);
}

Що привернуло мою увагу, це додаткова змінна "n". Я ніколи раніше не бачив лопа, написаного таким чином.

Очевидно, що в цьому сценарії немає жодної причини, чому цей код не можна було записати наступним чином (до чого я дуже звик):

var someOtherArray = [];
for (var i = 0; i < array.length; i++) {
    someOtherArray[i] = modifyObjetFromArray(array[i]);
}

Але це змусило мене задуматися.

Чи є сценарій, коли написання такого циклу мало б сенс? Ідея приходить в голову, що довжина "масиву" може змінюватися під час виконання циклу, але ми не хочемо циклічити далі, ніж початковий розмір, але такого сценарію я не уявляю.

Скорочення масиву всередині циклу теж не має особливого сенсу, тому що ми, швидше за все, отримаємо OutOfBoundsException.

Чи існує відома модель дизайну, де ця примітка корисна?

Редагування Як зазначає @ Jerry101, причина - продуктивність. Ось посилання на створений мною тест на працездатність: http://jsperf.com/ninforloop . На мій погляд, різниця недостатньо велика, якщо ви не повторюєте, хоча це дуже великий масив. Код, з якого я скопіював, мав до 20 елементів, тому я вважаю, що читаемості в цьому випадку переважають над врахуванням продуктивності.


Відповідно до редагування: це об'єктно-орієнтоване програмування. Стандартний array.length - це швидко і дешево. Але з будь-якої причини реалізація .length може змінитися на щось дороге. Якщо значення залишається однаковим, то не отримуйте його кілька разів.
Пітер Б

Я згоден. Добре знати про цей варіант, але я, мабуть, не використовував би його дуже часто, якщо тільки не отримаю подібний приклад до вашого запиту sql. Дякую☺
Влад Шпрейс

Річ у тому, що якщо ваш масив складає мільйон елементів, ви збираєтесь зателефонувати на array.length мільйон разів замість 1 разу, тоді як значення залишається таким же. Запитувати щось мільйон разів і знаючи, що кожна наступна відповідь буде такою ж, як перша відповідь ...... просто звучить для мене трохи безглуздо.
Пітер Б

1
Я не вважаю, що це робить код значно важчим для читання. (Серйозно, якщо хтось має проблеми з таким простим циклом, він повинен хвилюватися.) Але: 1) Я був би страшенно здивований, якби через 4 десятиліття мов, що походять з С і С, кожен серйозний компілятор цього ще не робив для вас; і 2) це може бути не гарячою точкою. Мабуть, не варто змушувати когось витрачати зайві 5 секунд на їх розбір, якщо це не всередині бібліотеки (наприклад, реалізація структури даних.)
Doval

1
У C # /. NET варіант, що використовується, nможе бути повільнішим, ніж при використанні варіанту, array.Lengthоскільки JITter може не помітити, що він може усунути перевірку меж масиву.
CodesInChaos

Відповіді:


27

Змінна nгарантує, що створений код не отримує довжину масиву для кожної ітерації.

Це оптимізація, яка може змінити час виконання, залежно від мови, що використовується, незалежно від того, чи є масив об'єктом колекції чи "масивом" JavaScript та іншими деталями оптимізації.


3
Ні, це не так. Від того, чи буде довжина масиву підібрана для кожної ітерації, залежить не лише від мови, але й від компілятора та параметрів компілятора та того, що робиться в циклі (я говорю про C та C ++)
BЈовић

.. І навіть так, я б особисто поставив російське завдання трохи вище циклу, якби цього я дуже хотів, оскільки - як зазначив ОП - це рідкісна (YMMV) конструкція, яку слід використовувати, і її легко пропускати / не розуміти інші члени читання код.
обмін

20
Як написано, він охоплює nпетлю, що, як правило, є хорошою справою. Я б визначив його перед циклом, лише якщо я хотів би використати його знову пізніше.
Джеррі101

4
@ Jerry101: Мабуть, прикладом фрагменту є JavaScript, де немає такого поняття, як область циклу…
Bergi

1
@ BЈовић: на практиці компілятору рідко вдається довести, що розмір масиву не змінюється (як правило, це тривіально доказується лише в тому випадку, якщо вектор, над яким ви перебуваєте в циклі, є локальним для функції і ви викликаєте лише циклічні функції в циклі).
Маттео Італія

2

Отримати довжину масиву можна легко «дорожче», ніж фактична дія, яку ви повторюєте.

Тож якщо набір не змінюється, запитуйте довжину лише один раз.

Не з масивами, а з наборами записів, що надходять із сервера sql, я побачив суттєві вдосконалення, не запитуючи кількість записів під час кожної ітерації. (звичайно, робіть це лише в тому випадку, якщо ви можете гарантувати, що масив або запис записів не зміниться під час цього процесу).


Якщо кількість записів змінюється, то що? Скажіть, що було 100 елементів, і поки ви обробляєте елемент 50, елемент після # 25 вставляється. Отже, коли ви обробляєте елемент № 51, це те саме, що №50, яке ви вже обробляли, і справи йдуть не так. Тож проблема полягає не в тому, що довжина змінюється, вона набагато більш поширена.
gnasher729

@ gnasher729, коли ви потрапите в асиметричну поведінку, багато може піти не так. Але є такі речі, які можна зробити проти цього, наприклад, запуск та sql транзакції.
Пітер Б

2

Ідея приходить в голову, що довжина "масиву" може змінюватися під час виконання циклу, але ми не хочемо циклічити далі, ніж початковий розмір, але такого сценарію я не уявляю.

Люди, які говорять про продуктивність, ймовірно, правильно, що саме тому було написано саме так (людина, яка написала це таким чином, може бути неправильною, що це суттєво впливає на продуктивність, але це інша справа).

Однак, щоб відповісти на цю частину вашого питання, я думаю, що це, швидше за все, станеться тоді, коли головна мета циклу - це змінити масив, на якому він перекидається. Розглянемо щось подібне у псевдо-коді:

guests = [];
for (i = 0; i < invitations.length; ++i) {
    if (! invitations[i].is_declined()) {
        guests.append(invitations[i].recipient())
    }
}
// maybe some other stuff to modify the guestlist
for (i = 0, n = guests.length; i < n; ++i) {
    if (guests[i].has_plus_one()) {
        guests.append(guests[i].get_plus_one());
    }
}

Звичайно, є й інші способи її написання. У цьому випадку я міг би одночасно перевірити, чи приймають одержувачі їхні запрошення, плюс додаткові, і створив повний список гостей по одному запрошення за раз. Мені не обов’язково хочеться поєднувати ці дві операції, але я не можу відразу придумати причину, чому це однозначно неправильно, і тому така конструкція потрібна . Це варіант, який потрібно розглядати, зрідка.

Насправді я думаю, що в цьому коді найбільше не так - це те, що членство в guestsмасиві означає різні речі в різних точках коду. Тому я, звичайно, не називав би це "шаблоном", але я не знаю, що достатньо дефекту, щоб виключити будь-що робити щось подібне :-)


Масив також може бути змінений іншим потоком. Це заважає компілятору оптимізувати пошук довжини. Тому в питанні програміст відверто каже, що масив не змінюється навіть в інших потоках.
Basilevs

0

Якщо це взагалі можливо, слід використовувати ітератор, який обходить всі елементи масиву. Ви використовуєте масив як послідовність, а не як сховище з випадковим доступом, тому очевидно, що ітератор, який просто робить послідовний доступ, буде швидшим. Уявіть, що ви думаєте, що масив - це насправді двійкове дерево, але хтось написав код, який визначає "довжину" шляхом підрахунку елементів, і код, який отримує доступ до i-го елемента, також шляхом обходу елемента, кожен з O (n ). Ітератор може бути дуже швидким, ваш цикл буде sloooooow.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.